[automerger skipped] Merge "RESTRICT AUTOMERGE Fixed leak of cross user data in multiple settings." into rvc-dev am: 990b6c6625 am: 1b01a94954 -s ours
am skip reason: subject contains skip directive
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Telephony/+/23515301
Change-Id: If454dca4c60376ff53a2cd6f5dac3400cceb4116
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 1887a8a..5eb8765 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",
@@ -24,6 +41,7 @@
"ims-common",
"libprotobuf-java-lite",
"unsupportedappusage",
+ "org.apache.http.legacy",
],
static_libs: [
@@ -35,6 +53,8 @@
"com.android.phone.common-lib",
"guava",
"PlatformProperties",
+ "modules-utils-os",
+ "nist-sip",
],
srcs: [
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 041ca8d..1f4beb5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -38,8 +38,10 @@
<protected-broadcast android:name="android.intent.action.ACTION_MDN_STATE_CHANGED" />
<protected-broadcast android:name="android.telephony.action.SERVICE_PROVIDERS_UPDATED" />
<protected-broadcast android:name="android.provider.Telephony.SIM_FULL" />
+ <protected-broadcast android:name="com.android.internal.telephony.carrier_key_download_alarm" />
<protected-broadcast android:name="com.android.internal.telephony.data-restart-trysetup" />
<protected-broadcast android:name="com.android.internal.telephony.data-stall" />
+ <protected-broadcast android:name="com.android.internal.telephony.provisioning_apn_alarm" />
<protected-broadcast android:name="android.intent.action.DATA_SMS_RECEIVED" />
<protected-broadcast android:name="android.provider.Telephony.SMS_RECEIVED" />
<protected-broadcast android:name="android.provider.Telephony.SMS_DELIVER" />
@@ -82,7 +84,9 @@
<protected-broadcast android:name= "com.android.internal.telephony.CARRIER_VVM_PACKAGE_INSTALLED" />
<protected-broadcast android:name= "com.android.cellbroadcastreceiver.GET_LATEST_CB_AREA_INFO" />
<protected-broadcast android:name= "com.android.internal.telephony.ACTION_CARRIER_CERTIFICATE_DOWNLOAD" />
+ <protected-broadcast android:name= "com.android.internal.telephony.action.COUNTRY_OVERRIDE" />
<protected-broadcast android:name= "com.android.internal.telephony.OPEN_DEFAULT_SMS_APP" />
+ <protected-broadcast android:name= "com.android.internal.telephony.ACTION_TEST_OVERRIDE_CARRIER_ID" />
<protected-broadcast android:name= "android.telephony.action.SIM_CARD_STATE_CHANGED" />
<protected-broadcast android:name= "android.telephony.action.SIM_APPLICATION_STATE_CHANGED" />
<protected-broadcast android:name= "android.telephony.action.SIM_SLOT_STATUS_CHANGED" />
@@ -92,10 +96,22 @@
<protected-broadcast android:name= "android.telephony.action.NETWORK_COUNTRY_CHANGED" />
<protected-broadcast android:name= "android.telephony.action.PRIMARY_SUBSCRIPTION_LIST_CHANGED" />
<protected-broadcast android:name= "android.telephony.action.MULTI_SIM_CONFIG_CHANGED" />
+ <protected-broadcast android:name= "android.telephony.action.CARRIER_SIGNAL_RESET" />
+ <protected-broadcast android:name= "android.telephony.action.CARRIER_SIGNAL_PCO_VALUE" />
+ <protected-broadcast android:name= "android.telephony.action.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE" />
+ <protected-broadcast android:name= "android.telephony.action.CARRIER_SIGNAL_REDIRECTED" />
+ <protected-broadcast android:name= "android.telephony.action.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED" />
+
+ <protected-broadcast android:name= "com.android.phone.settings.CARRIER_PROVISIONING" />
+ <protected-broadcast android:name= "com.android.phone.settings.TRIGGER_CARRIER_PROVISIONING" />
<!-- For Vendor Debugging in Telephony -->
<protected-broadcast android:name="android.telephony.action.ANOMALY_REPORTED" />
+ <protected-broadcast android:name= "android.intent.action.SUBSCRIPTION_INFO_RECORD_ADDED" />
+ <protected-broadcast android:name= "android.intent.action.ACTION_MANAGED_ROAMING_IND" />
+ <protected-broadcast android:name= "android.telephony.ims.action.RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE" />
+
<!-- Allows granting runtime permissions to telephony related components. -->
<uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS" />
@@ -118,8 +134,8 @@
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
<uses-permission android:name="android.permission.VIBRATE" />
- <uses-permission android:name="android.permission.BLUETOOTH" />
- <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.REORDER_TASKS" />
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
@@ -211,13 +227,17 @@
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="android.permission.READ_PRECISE_PHONE_STATE" />
<uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
- <!-- Allows us to whitelist receivers of the
+ <!-- Allows us to allow list receivers of the
ACTION_SIM_SLOT_STATUS_CHANGED broadcast to start activities
from the background. -->
<uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" />
<uses-permission android:name="android.permission.NETWORK_STATS_PROVIDER" />
- <uses-permission android:name="android.permission.HANDLE_CAR_MODE_CHANGES"/>
<uses-permission android:name="android.permission.MANAGE_SUBSCRIPTION_PLANS"/>
+ <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"/>
+ <uses-permission android:name="android.permission.BIND_GBA_SERVICE"/>
+
+ <!-- Needed to listen to changes in projection state. -->
+ <uses-permission android:name="android.permission.READ_PROJECTION_STATE"/>
<!-- Needed to block unwanted malicious pop up overlays -->
<uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/>
@@ -239,11 +259,23 @@
android:readPermission="android.permission.READ_CONTACTS"
android:writePermission="android.permission.WRITE_CONTACTS" />
+ <provider android:name=".SimPhonebookProvider"
+ android:authorities="com.android.simphonebook"
+ android:multiprocess="true"
+ android:exported="true"
+ android:readPermission="android.permission.READ_CONTACTS"
+ android:writePermission="android.permission.WRITE_CONTACTS" />
+
+ <provider android:name="com.android.ims.rcs.uce.eab.EabProvider"
+ android:authorities="eab"
+ android:exported="false"/>
+
<!-- Dialer UI that only allows emergency calls -->
<activity android:name="EmergencyDialer"
android:label="@string/emergencyDialerIconLabel"
android:theme="@style/EmergencyDialerTheme"
android:screenOrientation="portrait"
+ android:exported="true"
android:resizeableActivity="false">
<intent-filter>
<action android:name="com.android.phone.EmergencyDialer.DIAL" />
@@ -271,6 +303,7 @@
android:label="@string/simContacts_title"
android:theme="@style/SimImportTheme"
android:screenOrientation="portrait"
+ android:exported="true"
android:icon="@mipmap/ic_launcher_contacts">
<intent-filter>
@@ -282,6 +315,7 @@
<activity android:name="com.android.phone.settings.fdn.FdnList"
android:label="@string/fdnListLabel"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@@ -296,6 +330,7 @@
<activity android:name="GsmUmtsCallOptions"
android:label="@string/gsm_umts_options"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -304,6 +339,7 @@
<activity android:name="CdmaCallOptions"
android:label="@string/cdma_options"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -313,6 +349,17 @@
<activity android:name="GsmUmtsCallForwardOptions"
android:label="@string/labelCF"
android:configChanges="orientation|screenSize|keyboardHidden"
+ android:exported="true"
+ android:theme="@style/DialerSettingsLight">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="CdmaCallForwardOptions"
+ android:label="@string/labelCF"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -322,6 +369,7 @@
<activity android:name="GsmUmtsCallBarringOptions"
android:label="@string/labelCallBarring"
android:configChanges="orientation|screenSize|keyboardHidden"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -331,6 +379,7 @@
<activity android:name="GsmUmtsAdditionalCallOptions"
android:label="@string/labelGSMMore"
android:configChanges="orientation|screenSize|keyboardHidden"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -340,6 +389,7 @@
<!-- fdn setting -->
<activity android:name="com.android.phone.settings.fdn.FdnSetting"
android:label="@string/fdn"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -349,6 +399,7 @@
<!-- SIM PIN setting -->
<activity android:name="EnableIccPinScreen"
android:label="@string/enable_pin"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -358,6 +409,7 @@
<activity android:name="ChangeIccPinScreen"
android:label="@string/change_pin"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -385,6 +437,7 @@
<activity android:name="CallFeaturesSetting"
android:label="@string/call_settings"
android:configChanges="orientation|screenSize|keyboardHidden"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@@ -397,6 +450,7 @@
<!-- Activation service that trigger OTASP sim provisioning -->
<service android:name=".otasp.OtaspActivationService" android:launchMode="singleInstance"
androidprv:systemUserOnly="true"
+ android:exported="true"
android:permission="android.permission.MODIFY_PHONE_STATE">
<intent-filter>
<action android:name="android.service.simActivation.SimActivationService" />
@@ -413,6 +467,7 @@
<!-- "Accessibility" settings UI. Referenced by Dialer application. -->
<activity android:name="com.android.phone.settings.AccessibilitySettingsActivity"
android:label="@string/accessibility_settings_activity_title"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -427,6 +482,7 @@
<!-- service to dump telephony information -->
<service android:name="com.android.phone.TelephonyDebugService"
+ android:exported="true"
android:permission="android.permission.DUMP">
<intent-filter>
<action android:name="com.android.phone.TelephonyDebugService" />
@@ -436,6 +492,7 @@
<!-- Handler for EuiccManager's public-facing intents. -->
<activity android:name=".euicc.EuiccUiDispatcherActivity"
android:theme="@android:style/Theme.NoDisplay"
+ android:exported="true"
android:permission="android.permission.MODIFY_PHONE_STATE">
<!-- Max out priority to ensure nobody else will handle these intents. -->
<intent-filter android:priority="1000">
@@ -456,6 +513,7 @@
EuiccController#RESOLUTION_ACTIVITY_CLASS_NAME
-->
<activity android:name=".euicc.EuiccResolutionUiDispatcherActivity"
+ android:exported="true"
android:permission="android.permission.CALL_PRIVILEGED">
<!-- Max out priority to ensure nobody else will handle these intents. -->
<intent-filter android:priority="1000">
@@ -471,6 +529,7 @@
-->
<activity android:name=".euicc.EuiccPrivilegedActionUiDispatcherActivity"
android:theme="@android:style/Theme.NoDisplay"
+ android:exported="true"
android:permission="android.permission.CALL_PRIVILEGED">
<!-- Max out priority to ensure nobody else will handle these intents. -->
<intent-filter android:priority="1000">
@@ -490,6 +549,7 @@
whitelisted by the underlying eUICC service implementation (i.e. the LPA).
-->
<activity android:name=".euicc.EuiccPublicActionUiDispatcherActivity"
+ android:exported="true"
android:theme="@android:style/Theme.NoDisplay">
<!-- Max out priority to ensure nobody else will handle these intents. -->
<intent-filter android:priority="1000">
@@ -503,6 +563,7 @@
android:excludeFromRecents="true"
android:label="@string/ecm_exit_dialog"
android:launchMode="singleTop"
+ android:exported="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="com.android.phone.action.ACTION_SHOW_ECM_EXIT_DIALOG" />
@@ -515,33 +576,27 @@
<service android:name="com.android.services.telephony.sip.SipConnectionService"
android:label="@string/sip_connection_service_label"
android:singleUser="true"
+ android:exported="true"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
<intent-filter>
<action android:name="android.telecom.ConnectionService" />
</intent-filter>
</service>
- <receiver android:name="com.android.services.telephony.sip.SipIncomingCallReceiver">
+ <receiver android:name="com.android.services.telephony.sip.SipIncomingCallReceiver"
+ android:exported="true">
<intent-filter>
<action android:name="android.net.sip.action.SIP_INCOMING_CALL" />
</intent-filter>
</receiver>
- <activity android:name="com.android.services.telephony.sip.SipPhoneAccountSettingsActivity"
- android:theme="@android:style/Theme.NoDisplay"
- android:excludeFromRecents="true">
- <intent-filter>
- <action android:name="android.telecom.action.CONFIGURE_PHONE_ACCOUNT" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
-
<activity android:label="Sip Settings"
android:name="com.android.services.telephony.sip.SipSettings"
android:theme="@style/DialerSettingsLight"
android:launchMode="singleTop"
android:configChanges="orientation|screenSize|keyboardHidden"
android:uiOptions="splitActionBarWhenNarrow"
+ android:exported="true"
android:parentActivityName="com.android.phone.CallFeaturesSetting" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -554,7 +609,8 @@
android:uiOptions="splitActionBarWhenNarrow">
</activity>
- <service android:name="com.android.services.telephony.sip.components.TelephonySipService">
+ <service android:name="com.android.services.telephony.sip.components.TelephonySipService"
+ android:exported="true">
<intent-filter>
<action android:name="android.net.sip.action.START_SIP" />
</intent-filter>
@@ -571,6 +627,7 @@
<activity android:name="com.android.phone.settings.PhoneAccountSettingsActivity"
android:label="@string/phone_accounts"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.telecom.action.CHANGE_PHONE_ACCOUNTS" />
@@ -582,6 +639,7 @@
android:label="@string/voicemail"
android:configChanges="orientation|screenSize|keyboardHidden|screenLayout"
android:screenOrientation="portrait"
+ android:exported="true"
android:theme="@style/DialerSettingsLight">
<intent-filter >
<!-- DO NOT RENAME. There are existing apps which use this string. -->
@@ -599,6 +657,7 @@
android:singleUser="true"
android:name="com.android.services.telephony.TelephonyConnectionService"
android:label="@string/pstn_connection_service_label"
+ android:exported="true"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
<intent-filter>
<action android:name="android.telecom.ConnectionService" />
@@ -641,12 +700,14 @@
android:name="com.android.phone.vvm.RemoteVvmTaskManager"
android:exported="false"/>
<service android:name="com.android.internal.telephony.CellularNetworkService"
+ android:exported="true"
android:permission="android.permission.BIND_TELEPHONY_NETWORK_SERVICE" >
<intent-filter>
<action android:name="android.telephony.NetworkService" />
</intent-filter>
</service>
<service android:name="com.android.internal.telephony.dataconnection.CellularDataService"
+ android:exported="true"
android:permission="android.permission.BIND_TELEPHONY_DATA_SERVICE" >
<intent-filter>
<action android:name="android.telephony.data.DataService" />
@@ -656,6 +717,7 @@
<activity
android:name=".settings.RadioInfo"
android:label="@string/phone_info_label"
+ android:exported="true"
android:theme="@style/Theme.AppCompat.DayNight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -665,6 +727,7 @@
<activity android:name=".settings.BandMode"
android:label="@string/band_mode_title"
+ android:exported="true"
android:theme="@style/Theme.AppCompat.DayNight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
deleted file mode 100644
index e69de29..0000000
--- a/MODULE_LICENSE_APACHE2
+++ /dev/null
diff --git a/OWNERS b/OWNERS
index 3059d4d..1ea2d9e 100644
--- a/OWNERS
+++ b/OWNERS
@@ -2,14 +2,16 @@
breadley@google.com
fionaxu@google.com
jackyu@google.com
-hallliu@google.com
rgreenwalt@google.com
tgunn@google.com
jminjie@google.com
shuoq@google.com
-refuhoo@google.com
nazaninb@google.com
sarahchin@google.com
-dbright@google.com
xiaotonj@google.com
+huiwang@google.com
+jayachandranc@google.com
+chinmayd@google.com
+amruthr@google.com
+per-file *SimPhonebookProvider* = file:platform/packages/apps/Contacts:/OWNERS
diff --git a/TEST_MAPPING b/TEST_MAPPING
index e75dcb0..75b9d49 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -7,6 +7,9 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "CarrierAppIntegrationTestCases"
}
]
}
diff --git a/apex/Android.bp b/apex/Android.bp
index a7137d9..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",
@@ -9,6 +18,8 @@
key: "com.android.telephony.key",
certificate: ":com.android.telephony.certificate",
+
+ updatable: false,
}
apex {
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/ecc/conversion_toolset_v1/proto/protobuf_ecc_data.proto b/ecc/conversion_toolset_v1/proto/protobuf_ecc_data.proto
index 5bd7bc3..c1a826a 100644
--- a/ecc/conversion_toolset_v1/proto/protobuf_ecc_data.proto
+++ b/ecc/conversion_toolset_v1/proto/protobuf_ecc_data.proto
@@ -13,6 +13,10 @@
POLICE = 1;
AMBULANCE = 2;
FIRE = 3;
+ MARINE_GUARD = 4;
+ MOUNTAIN_RESCUE = 5;
+ MIEC = 6;
+ AIEC = 7;
}
// Required: Every EccInfo shall contain a phone number.
diff --git a/ecc/input/eccdata.txt b/ecc/input/eccdata.txt
index a674319..a8b021e 100644
--- a/ecc/input/eccdata.txt
+++ b/ecc/input/eccdata.txt
@@ -1,4 +1,4 @@
-revision: 1
+revision: 2
countries {
iso_code: "AD"
eccs {
@@ -793,6 +793,54 @@
types: AMBULANCE
types: FIRE
}
+ eccs {
+ phone_number: "15"
+ types: AMBULANCE
+ }
+ eccs {
+ phone_number: "17"
+ types: POLICE
+ }
+ eccs {
+ phone_number: "18"
+ types: FIRE
+ }
+ eccs {
+ phone_number: "115"
+ types: TYPE_UNSPECIFIED
+ }
+ eccs {
+ phone_number: "116000"
+ types: TYPE_UNSPECIFIED
+ }
+ eccs {
+ phone_number: "114"
+ types: TYPE_UNSPECIFIED
+ }
+ eccs {
+ phone_number: "191"
+ types: TYPE_UNSPECIFIED
+ }
+ eccs {
+ phone_number: "196"
+ types: MARINE_GUARD
+ }
+ eccs {
+ phone_number: "197"
+ types: TYPE_UNSPECIFIED
+ }
+ eccs {
+ phone_number: "116117"
+ types: TYPE_UNSPECIFIED
+ }
+ eccs {
+ phone_number: "116111"
+ types: TYPE_UNSPECIFIED
+ }
+ eccs {
+ phone_number: "119"
+ types: TYPE_UNSPECIFIED
+ }
ecc_fallback: "112"
}
countries {
diff --git a/ecc/output/eccdata b/ecc/output/eccdata
index 327e300..8427603 100644
--- a/ecc/output/eccdata
+++ b/ecc/output/eccdata
Binary files differ
diff --git a/proguard.flags b/proguard.flags
index c707f76..8eafd30 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -7,4 +7,9 @@
-keepclassmembers class * {
@**.NeededForTesting *;
}
+# TODO: remove this after call composer gets more integrated.
+# for the time being, this is here so that the tests don't fail when encountering dead code.
+-keep class com.android.phone.callcomposer.** {
+ *;
+}
-verbose
\ No newline at end of file
diff --git a/res/drawable/cupcake.png b/res/drawable/cupcake.png
new file mode 100644
index 0000000..dcc74e5
--- /dev/null
+++ b/res/drawable/cupcake.png
Binary files differ
diff --git a/res/layout/radio_info.xml b/res/layout/radio_info.xml
index 71cd32a..68c67a3 100644
--- a/res/layout/radio_info.xml
+++ b/res/layout/radio_info.xml
@@ -153,6 +153,12 @@
<TextView android:id="@+id/nr_frequency" style="@style/info_value" />
</LinearLayout>
+ <!-- NR Frequency -->
+ <LinearLayout style="@style/RadioInfo_entry_layout" android:orientation="horizontal">
+ <TextView android:id="@+id/network_slicing_config_label" android:text="@string/radio_info_network_slicing_config" style="@style/info_label" />
+ <TextView android:id="@+id/network_slicing_config" style="@style/info_value" />
+ </LinearLayout>
+
<!-- Physical Channel Config -->
<LinearLayout style="@style/RadioInfo_entry_layout">
<TextView android:text="@string/radio_info_phy_chan_config" style="@style/info_label" />
@@ -334,11 +340,6 @@
android:background="?android:attr/listDivider" />
<!-- CellInfoListRate Selection -->
- <!-- Location -->
- <LinearLayout style="@style/RadioInfo_entry_layout">
- <TextView android:text="@string/radio_info_signal_location_label" style="@style/info_label" />
- <TextView android:id="@+id/location" style="@style/info_value" />
- </LinearLayout>
<TextView
android:layout_width="match_parent"
diff --git a/res/layout/sim_ndp.xml b/res/layout/sim_ndp.xml
index 5f03d7b..e16f99a 100644
--- a/res/layout/sim_ndp.xml
+++ b/res/layout/sim_ndp.xml
@@ -35,6 +35,13 @@
android:layout_height="wrap_content"
android:text="@string/label_ndp"/>
+ <TextView
+ android:id="@+id/perso_phoneid_text"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/label_phoneid"/>
+
<EditText android:id="@+id/pin_entry"
android:inputType="textPassword"
android:imeOptions="actionDone"
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 600b497..76b13c6 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Stemboodskapinstellings kan slegs deur die primêre gebruiker gewysig word."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Jou SIM-kaart is oopgesluit. Jou foon sluit tans oop…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM-netwerk ontsluit PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM gesluit vir operateur"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Ontsluit"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Maak toe"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Versoek tans netwerkontsluiting…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Stuur aan na <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Af"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Jou diensverskaffer ondersteun nie die deaktivering van oproepaanstuur wanneer jou foon onbereikbaar is nie."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Jou diensverskaffer ondersteun nie oproepaanstuur nie."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Skakel Oproep Wat Wag aan?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Jy sal tydens \'n oproep in kennis gestel word van inkomende oproepe"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Skakel aan"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Kanselleer"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA-Oproep Wat Wag onder IMS is Aan"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA-Oproep Wat Wag onder IMS is Af"</string>
<string name="updating_title" msgid="6130548922615719689">"Belinstellings"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Oproepinstellings kan net deur die administrateur verander word."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Instellings (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Beperkte SIM-funksionaliteit"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> oproepe- en datadienste kan geblokkeer word terwyl <xliff:g id="PHONE_NUMBER">%2$s</xliff:g> gebruik word."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> oproepe- en datadienste kan geblokkeer word terwyl \'n ander SIM gebruik word."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Opgeskorte SIP-rekeninge is gevind en verwyder"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP-bel word nie meer deur die Android-platform ondersteun nie.\nJou bestaande SIP-rekeninge <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> is verwyder.\nBevestig asseblief jou verstekbelinstelling."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Gaan na instellings"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Programdatagebruik"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> mobiele data gebruik <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Gevorderd"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Wagwoord is verkeerd. SIM is nou geblokkeer. Voer PUK2 in."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 is permanent geblokkeer."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Jy het <xliff:g id="NUMBER">%d</xliff:g> oorblywende pogings."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 is gesluit. Kontak diensverskaffer om te ontsluit."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 is nie meer geblokkeer nie"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Netwerk- of SIM-kaartfout"</string>
<string name="doneButton" msgid="7371209609238460207">"Klaar"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Skakel selnetwerk aan, skakel vliegtuigmodus af of skakel batterybespaardermodus af om \'n oproep te maak."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Skakel vliegtuigmodus af om \'n oproep te maak."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Skakel vliegtuigmodus af of koppel aan \'n draadlose netwerk om \'n oproep te maak."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Foon is te warm"</b>\n\n"Kan nie hierdie oproep voltooi nie. Probeer weer wanneer jou foon afkoel.\n\nJy kan nog noodoproepe maak."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Verlaat noodterugbelmodus om \'n nienoodoproep te maak."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Nie geregistreer op netwerk nie."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobiele netwerk nie beskikbaar nie."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Sub-ID van verstekdata-SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Aflaaibandwydte (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Oplaaibandwydte (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Selligginginligting (opgeskort):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Fisieke LTE-kanaalopstelling:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Herlaaikoers van selinligting:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Alle selmetingsinligting:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Fooninligting"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Diensverskaffervoorsiening-inligting"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Trigger-diensverskaffervoorsiening"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Jou bluetooth-sein is swak. Probeer om na luidsprekerfoon oor te skakel."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Kennisgewing oor oproepgehalte"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Opgeskorte SIP-rekeninge"</string>
</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index b9fbebc..a808559 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"የድምፅ መልዕክት ቅንብሮች መለወጥ የሚችሉት በቀዳሚ ተጠቃሚው ብቻ ነው።"</string>
<string name="puk_unlocked" msgid="4627340655215746511">"የSIM ካርድዎ አልታገደም። ስልክዎ በመከፈት ላይነው..."</string>
<string name="label_ndp" msgid="7617392683877410341">"የSIM አውታረመረብ መክፈቻ ፒን"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM ለከዋኙ ተቆልፏል"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"ክፈት"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"አሰናብት"</string>
<string name="requesting_unlock" msgid="930512210309437741">"አውታረመረብ ለማስከፈት በመጠየቅ ላይ..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"ወደ <xliff:g id="PHONENUMBER">{0}</xliff:g>በማስተላለፍ ላይ"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"ጠፍቷል"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"ስልክህ በማይደረስበት ጊዜ የድምጽ ተያያዥ ሞደምህ የጥሪ-ማስተላለፍን አይደግፍም።"</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"አገልግሎት አቅራቢዎ ጥሪ ማስተላለፍን አይደግፍም።"</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"ጥሪ መጠበቂያ ይብራ?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"በጥሪ ጊዜ፣ ስለ ገቢ ጥሪዎች እንዲያውቁት ይደረጋል"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"አብራ"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"ይቅር"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"በIMS ስር ያለው የCDMA ጥሪ መጠበቂያ በርቷል"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"በIMS ስር ያለው የCDMA ጥሪ መጠበቂያ ጠፍቷል"</string>
<string name="updating_title" msgid="6130548922615719689">"የጥሪ ቅንብሮች"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"የጥሪ ቅንብሮች በአስተዳዳሪ ተጠቃሚው ብቻ ነው ሊለወጡ የሚችሉት።"</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"ቅንብሮች (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"የተገደበ የሲም ተግባር"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"የ <xliff:g id="CARRIER_NAME">%1$s</xliff:g> ጥሪዎች እና የውሂብ አገልግሎቶች <xliff:g id="PHONE_NUMBER">%2$s</xliff:g> በሚጠቀሙ ጊዜ ሊታገዱ ይችላሉ።"</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"ሌላ ሲም በሚጠቀሙ ጊዜ የ <xliff:g id="CARRIER_NAME">%1$s</xliff:g> ጥሪዎች እና የውሂብ አገልግሎቶች ሊታገዱ ይችላሉ።"</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"የተቋረጡ የSIP መለያዎች ተገኝተው ተወግደዋል"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"የSIP መደወያ ከአሁን በኋላ በAndroid መሰረተ ስርዓት ላይ አይደገፍም።\nነባር የSIP መለያዎችዎ <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> ተወግደዋል።\nየእርስዎ ነባሪ መደወያ የመለያ ቅንብሮችን እባክዎ ያረጋግጡ።"</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"ወደ ቅንብሮች ሂድ"</string>
<string name="data_usage_title" msgid="8438592133893837464">"የመተግበሪያ ውሂብ አጠቃቀም"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> ጥቅም ላይ የዋለ የተንቀሳቃሽ ስልክ ውሂብ <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"የላቀ"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"የይለፍ ቃል ልክ አይደለም። SIM አሁን ተቆልፏል። PUK2 ያስገቡ።"</string>
<string name="puk2_blocked" msgid="3131139031779319911">"ፒን2 በቋሚነት ታግዷል።"</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"እርስዎ <xliff:g id="NUMBER">%d</xliff:g> ቀሪ ሙከራዎች አልዎት።"</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 ተቆልፏል። ለማስከፈት የአገልግሎት አቅራቢን ያነጋግሩ።"</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 ከአሁን በኋላ አልታገደም"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"የአውታረ መረብ ወይም የሲም ካርድ ስህተት"</string>
<string name="doneButton" msgid="7371209609238460207">"ተከናውኗል"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"የተንቀሳቃሽ ስልክ አውታረ መረብ ያብሩ፣ ጥሪ ለማድረግ የአውሮፕላን ሁነታን ወይም የባትሪ ኃይል ቆጣቢ ሁነታን ያጥፉ።"</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"ጥሪ ለማድረግ የአውሮፕላን ሁነታን ያጥፉ።"</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"ጥሪ ለማድረግ የአውሮፕላን ሁኔታን ያጥፉ ወይም ወደ ሽቦ አልባ አውታረ መረብ ያገናኙ።"</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"ስልኩ በጣም ሞቋል"</b>\n\n" ይህንን ጥሪ ማጠናቀቅ አልተቻለም። ስልክዎ ሲቀዘቅዝ እንደገና ይሞክሩ።\n\nየአደጋ ጊዜ ጥሪዎችን አሁንም ማድረግ ይችላሉ።"</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"አስቸኳይ ያልሆነ ጥሪ ለማድረግ ከአስቸኳይ መልሰህ ደውል ሁነታ ይውጡ።"</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"በአውታረ መረቡ ላይ አልተመዘገበም።"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"የተንቀሳቃሽ አደራጅ የለም።"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"የነባሪ ውሂብ ሲም SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL መተላለፊያ ይዘት (ኪቢ/ሴ)፡"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"የUL መተላለፊያ ይዘት (ኪቢ/ሴ)፡"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"የሕዋስ የአካባቢ መረጃ (የተቋረጠ)፦"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"የLTE አካላዊ ሰርጥ ውቅረት:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"የሕዋስ መረጃ እድሳት ፍጥነት፡"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"ሁሉም የሕዋስ መለኪያ መረጃ፡"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"የስልክ መረጃ"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"የአገልግሎት አቅራቢ አቅርቦት መረጃ"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"የቀስቅሴ አገልግሎት አቅራቢ አቅርቦት"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"የእርስዎ የብሉቱዝ ሲግናል ደካማ ነው። ወደ የስልክ ድምፅ ማጉያ ለመቀየር ይሞክሩ።"</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"የጥሪ ጥራት ማሳወቂያ"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"የተቋረጡ የSIP መለያዎች"</string>
</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 8f89ae9..21a540f 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"يمكن تعديل إعدادات البريد الصوتي عن طريق المستخدم الأساسي فقط."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"تم إلغاء حظر شريحة SIM. جارٍ فتح قفل الهاتف..."</string>
<string name="label_ndp" msgid="7617392683877410341">"رمز PIN لإلغاء تأمين شبكة SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"شريحة SIM مقيدَّة إلى مشغّل الشبكة"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"فتح قفل"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"استبعاد"</string>
<string name="requesting_unlock" msgid="930512210309437741">"جارٍ طلب فتح قفل الشبكة..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"إعادة التوجيه إلى <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"غير مفعّل"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"لا يتيح مشغل شبكة الجوال إيقاف اعادة توجيه المكالمة عند عدم التمكن من الوصول إلى هاتفك."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"لا يتيح مشغّل شبكة الجوّال ميزة إعادة توجيه المكالمات."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"هل تريد تفعيل ميزة انتظار المكالمات؟"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"خلال إحدى المكالمات، سيتم إخطارك بشأن المكالمات الواردة."</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"تفعيل"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"إلغاء"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"تفعيل ميزة انتظار مكالمات CDMA في ظل IMS"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"إيقاف ميزة انتظار مكالمات CDMA في ظل IMS"</string>
<string name="updating_title" msgid="6130548922615719689">"إعدادات الاتصال"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"لا يمكن تغيير إعدادات المكالمات إلا بواسطة المستخدم الإداري."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"الإعدادات (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"وظائف محدودة لشريحة SIM"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"ربما يتم حظر المكالمات وخدمات البيانات المُقدّمة من <xliff:g id="CARRIER_NAME">%1$s</xliff:g> أثناء استخدام <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"ربما يتم حظر المكالمات وخدمات البيانات المقدّمة من <xliff:g id="CARRIER_NAME">%1$s</xliff:g> أثناء استخدام شريحة SIM أخرى."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"تم العثور على حسابات SIP المتوقّفة وحذفها"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"لم تعد ميزة إجراء مكالمات عبر بروتوكول SIP متوافقة مع نظام Android الأساسي. \nتم حذف حسابات SIP الحالية، وهي <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>.\nيُرجى تأكيد إعدادات الاتصال التلقائية في حسابك."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"الانتقال إلى الإعدادات"</string>
<string name="data_usage_title" msgid="8438592133893837464">"استخدام بيانات التطبيق"</string>
<string name="data_usage_template" msgid="6287906680674061783">"تم استخدام <xliff:g id="ID_1">%1$s</xliff:g> من بيانات الجوّال خلال الفترة بين <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"إعدادات متقدمة"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"كلمة المرور غير صحيحة. تم قفل شريحة SIM الآن. أدخل رمز PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"تم حظر PUK2 بشكل دائم."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"يتبقى لديك <xliff:g id="NUMBER">%d</xliff:g> من المحاولات."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"تم قفل شريحة SIM برمز PUK2. تواصَل مع مقدّم الخدمة لفتح قفل الشريحة."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"لم يعد رمز PIN2 محظورًا"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"حدث خطأ في الشبكة أو ببطاقة SIM"</string>
<string name="doneButton" msgid="7371209609238460207">"تم"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"شغَّل شبكة الجوال، ثم أوقف تفعيل وضع الطيران أو أوقف تفعيل وضع توفير شحن البطارية لإجراء مكالمة."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"عليك إيقاف وضع الطيران لإجراء مكالمة."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"عليك إيقاف وضع الطيران أو الاتصال بشبكة لاسلكية لإجراء مكالمة."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"الهاتف ساخن جدًا"</b>\n\n"يتعذّر إتمام هذه المكالمة. يمكن المحاولة مرة أخرى عندما تنخفض درجة حرارة الهاتف.\n\nسيظلّ بإمكانك إجراء مكالمات الطوارئ."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"ينبغي الخروج من وضع معاودة الاتصال بالطوارئ لإجراء مكالمة غير طارئة."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"غير مسجل على الشبكة."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"شبكة الجوال غير متاحة."</string>
@@ -879,7 +892,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"المعرّف الفرعي لشريحة SIM التلقائية للبيانات:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"معدّل نقل بيانات DL (كيلوبت في الثانية):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"معدّل نقل بيانات UL (كيلوبت في الثانية):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"معلومات الموقع الخلوية (تم الإيقاف):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"ضبط قناة LTE Physical:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"معدّل إعادة تحميل المعلومات الخلوية:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"جميع معلومات القياس الخلوية:"</string>
@@ -924,4 +936,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"معلومات الهاتف"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"معلومات توفير خدمة مشغّل شبكة الجوّال"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"توفير خدمة مشغّل شبكة الجوّال"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"إشارة البلوتوث ضعيفة. حاوِل التبديل إلى مكبّر الصوت."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"إشعار بشأن جودة المكالمة"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"حسابات SIP المتوقّفة"</string>
</resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 764eb5c..607be68 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"ভইচমেইল ছেটিংসমূহ কেৱল মুখ্য ব্যৱহাৰকাৰীয়ে হে সংশোধন কৰিব পাৰে।"</string>
<string name="puk_unlocked" msgid="4627340655215746511">"আপোনাৰ ছিম কাৰ্ডখন অৱৰোধৰ পৰা আঁতৰোৱা হ’ল। আপোনাৰ ফ\'নটো আনলক কৰি থকা হৈছে…"</string>
<string name="label_ndp" msgid="7617392683877410341">"ছিম নেটৱৰ্ক আনলক পিন"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"অপাৰেটৰৰ বাবে ছিম লক কৰা হৈছে"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"আনলক"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"অগ্ৰাহ্য কৰক"</string>
<string name="requesting_unlock" msgid="930512210309437741">"নেটৱৰ্ক আনলক কৰিবলৈ অনুৰোধ কৰি থকা হৈছে…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g>লৈ ফৰৱাৰ্ড কৰা হৈছে"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"অফ"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"আপোনাৰ অপাৰেটৰে আপোনাৰ ফ\'নটো সংযোগ সীমাৰ পৰা বাহিৰত থাকোতে কল ফৰৱাৰ্ডিং অক্ষম কৰাৰ সুবিধা নিদিয়ে।"</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"আপোনাৰ বাহকে কল ফৰৱাৰ্ডিং সমৰ্থন নকৰে।"</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"কল অপেক্ষাৰত সুবিধাটো অন কৰিবনে?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"এটা কল চলি থাকোঁতে আপোনাক অন্তৰ্গামী কলসমূহৰ বিষয়ে জনোৱা হ\'ব"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"অন কৰক"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"বাতিল কৰক"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMSত CDMA কল অপেক্ষাৰত সুবিধাটো অন আছে"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMSত CDMA কল অপেক্ষাৰত সুবিধাটো অফ আছে"</string>
<string name="updating_title" msgid="6130548922615719689">"কল ছেটিংসমূহ"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"কেৱল প্ৰশাসকে কল ছেটিংসমূহ সলনি কৰিব পাৰে।"</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"ছেটিংসমূহ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"ছিমৰ সীমিত কাৰ্যকাৰিতা"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g> ব্যৱহাৰ কৰি থাকোঁতে <xliff:g id="CARRIER_NAME">%1$s</xliff:g>ৰ কল আৰু ডেটা সেৱাবোৰ অৱৰোধ কৰা হ’ব পাৰে।"</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"অন্য এটা ছিম ব্যৱহাৰ কৰি থাকোঁতে <xliff:g id="CARRIER_NAME">%1$s</xliff:g>ৰ কল আৰু ডেটা সেৱাবোৰ অৱৰোধ কৰা হ’ব পাৰে।"</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"বন্ধ হৈ যোৱা SIP একাউণ্ট বিচাৰি পোৱা গৈছে আৰু আঁতৰাই পেলোৱা হৈছে"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Android প্লেটফৰ্মত আৰু SIP কলিং ব্যৱহাৰ কৰিব পৰা নাযাব।\nআপোনাৰ পূৰ্বৰে পৰা থকা SIP একাউণ্টসমূহ <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> আঁতৰাই পেলোৱা হ’ল।\nঅনুগ্ৰহ কৰি আপোনাৰ ডিফ’ল্ট কলিং একাউণ্টৰ ছেটিং নিশ্চিত কৰক।"</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"ছেটিঙলৈ যাওক"</string>
<string name="data_usage_title" msgid="8438592133893837464">"এপৰ ডেটা ব্যৱহাৰ"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> ব্যৱহৃত ম’বাইল ডেটা <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"উচ্চখাপৰ ছেটিংসমূহ"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"ভুল পাছৱৰ্ড। এতিয়া ছিম লক কৰা হৈছে। PUK2 লিখক।"</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 স্থায়ীভাৱে অৱৰোধ কৰা হৈছে।"</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"আপোনাৰ হাতত <xliff:g id="NUMBER">%d</xliff:g>টা প্ৰয়াস বাকী আছে।"</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 লক কৰা আছে। আনলক কৰিবলৈ সেৱা প্ৰদানকাৰীৰ সৈতে যোগাযোগ কৰক।"</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 আৰু অৱৰোধ হৈ থকা নাই"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"নেটৱৰ্ক বা ছিম কাৰ্ডৰ আসোঁৱাহ।"</string>
<string name="doneButton" msgid="7371209609238460207">"হ’ল"</string>
@@ -522,9 +534,10 @@
<string name="notification_voicemail_no_vm_number" msgid="3423686009815186750">"ভইচমেইলৰ নম্বৰ অজ্ঞাত"</string>
<string name="notification_network_selection_title" msgid="255595526707809121">"কোনো সেৱা নাই"</string>
<string name="notification_network_selection_text" msgid="553288408722427659">"বাছনি কৰা (<xliff:g id="OPERATOR_NAME">%s</xliff:g>) নেটৱৰ্কটো উপলব্ধ নহয়"</string>
- <string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"কল কৰিবৰ কাৰণে ম’বাইল নেটৱৰ্ক অন কৰক, এয়াৰপ্লেইন ম\'ড অফ কৰক বা বেটাৰি সঞ্চয়কাৰী ম\'ড অফ কৰক।"</string>
+ <string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"কল কৰিবৰ কাৰণে ম’বাইল নেটৱৰ্ক অন কৰক, এয়াৰপ্লেইন ম’ড অফ কৰক বা বেটাৰী সঞ্চয়কাৰী ম’ড অফ কৰক।"</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"কল কৰিবৰ কাৰণে এয়াৰপ্লেইন ম\'ড অফ কৰক।"</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"কল কৰিবৰ কাৰণে এয়াৰপ্লেইন ম\'ড অফ কৰক বা কোনো বেতাঁৰ নেটৱৰ্কৰ সৈতে সংযোগ কৰক।"</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"ফ’নটো বেছি গৰম হৈছে"</b>\n\n"এই কলটো সম্পূৰ্ণ কৰিব নোৱাৰি। আপোনাৰ ফ’নটো ঠাণ্ডা হোৱাৰ পাছত পুনৰ চেষ্টা কৰক।\n\nআপুনি তথাপি জৰুৰীকালীন কল কৰিব পাৰিব।"</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"সাধাৰণ কল কৰিবৰ কাৰণে জৰুৰীকালীন কলবেক ম\'ডৰ পৰা বাহিৰ হওক।"</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"নেটৱৰ্কত পঞ্জীকৃত নহয়।"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"ম’বাইল নেটৱৰ্ক উপলব্ধ নহয়।"</string>
@@ -676,8 +689,8 @@
<string name="callFailed_cdma_call_limit" msgid="1074219746093031412">"কেইবাটাও কল ইতিমধ্যে সক্ৰিয় হৈ আছে। নতুন কল এটা কৰাৰ আগেয়ে অনুগ্ৰহ কৰি সেইবোৰ একেলগ কৰক বা সমাপ্ত কৰক।"</string>
<string name="callFailed_imei_not_accepted" msgid="7257903653685147251">"সংযোগ কৰিব পৰা নাই, অনুগ্ৰহ কৰি এখন মান্য ছিম কাৰ্ড ভৰাওক।"</string>
<string name="callFailed_wifi_lost" msgid="1788036730589163141">"ৱাই-ফাইৰ সৈতে সংযোগ বিচ্ছিন্ন হৈছে। কলৰ অন্ত পৰিছে।"</string>
- <string name="dialFailed_low_battery" msgid="6857904237423407056">"বেটাৰি কম থকাৰ বাবে ভিডিঅ\' কল কৰিব নোৱাৰি।"</string>
- <string name="callFailed_low_battery" msgid="4056828320214416182">"বেটাৰি কম থকাৰ বাবে ভিডিঅ\' কলৰ অন্ত পৰিছে।"</string>
+ <string name="dialFailed_low_battery" msgid="6857904237423407056">"বেটাৰী কম থকাৰ বাবে ভিডিঅ’ কল কৰিব নোৱাৰি।"</string>
+ <string name="callFailed_low_battery" msgid="4056828320214416182">"বেটাৰী কম থকাৰ বাবে ভিডিঅ’ কল সমাপ্ত হৈছে।"</string>
<string name="callFailed_emergency_call_over_wfc_not_available" msgid="5944309590693432042">"এই অৱস্থানটোত ৱাই-ফাই কলিঙৰ জৰিয়তে জৰুৰীকালীন কল কৰাৰ সুবিধা উপলব্ধ নহয়।"</string>
<string name="callFailed_wfc_service_not_available_in_this_location" msgid="3624536608369524988">"এই অৱস্থানটোত ৱাই-ফাই কলিং উপলব্ধ নহয়।"</string>
<string name="change_pin_title" msgid="3564254326626797321">"ভইচমেইলৰ পিন সলনি কৰক"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ডিফ’ল্ট ডেটা ছিমৰ ছাবআইডি:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL বেণ্ডৱিথ (কেবিপিএছ):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL বেণ্ডৱিথ (কেবিপিএছ):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"চেল অৱস্থানৰ তথ্য (অনুমোদিত নহয়):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ফিজিকেল চেনেলৰ কনফিগাৰেশ্বন:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"চেল তথ্য ৰিফ্ৰেশ্বৰ হাৰ:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"সকলো চেল পৰিমাপৰ তথ্য:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"ফ\'নৰ তথ্য"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"বাহকৰ প্ৰ’ভিজনিং তথ্য"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"বাহকৰ প্ৰ’ভিজনিং সূচনা কৰক"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"আপোনাৰ ব্লুটুথৰ ছিগনেল দুৰ্বল। স্পীকাৰফ’নলৈ সলনি কৰি চাওক।"</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"কলৰ গুণগত মানৰ জাননী"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"বন্ধ হৈ যোৱা SIP একাউণ্ট"</string>
</resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 37df7db..5ec4dc5 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Səsli E-məktub Ayarları yalnız Əsas İstifadəçi tərəfindən dəyişdirilə bilər."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"SIM kartınızın kilidi açıldı. Telefonunuzun da kilidi açılır…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM şəbəkəni kiliddən çıxaran PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SİM operator üçün kilidlənib"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Kilidi aç"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Kənarlaşdır"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Şəbəkə kilidinin açılması tələbi göndərilir…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> nömrəsinə yönləndirilir"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Deaktiv"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Sizin mobil daşıyıcı telefon əlçatmaz olduğu zaman zəng yönləndirməni deaktiv etməyi dəstəkləmir."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Operatorunuz zəngin yönləndirilməsini dəstəkləmir."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Zəng gözlətmə aktiv edilsin?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Zəng zamanı gələn zənglər haqqında bildiriş alacaqsınız"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Aktiv edin"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Ləğv edin"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS ilə CDMA zəng gözlətməsi aktivdir"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS ilə CDMA zəng gözlətməsi deaktivdir"</string>
<string name="updating_title" msgid="6130548922615719689">"Zəng ayarları"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Zəng parametrləri yalnız admin olan istifadəçi tərəfindən dəyişdirilə bilər."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Ayarlar ( <xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g> )"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Məhdud SIM funksionallığı"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g> nömrəsindən istifadə edərkən <xliff:g id="CARRIER_NAME">%1$s</xliff:g> zəng və data xidmətləri bloklana bilər."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Başqa SIM-dən istifadə edərkən <xliff:g id="CARRIER_NAME">%1$s</xliff:g> zəng və data xidmətləri bloklana bilər."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Ləğv edilmiş SIP hesabları tapıldı və silindi"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP zəngi artıq Android platforması tərəfindən dəstəklənmir.\nMövcud SIP hesablarınız <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> silinib.\nDefolt zəng hesabı ayarınızı təsdiq edin."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Ayarlara keçin"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Tətbiq trafiki"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g> ərzində <xliff:g id="ID_1">%1$s</xliff:g> mobil data istifadə edildi"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Qabaqcıl"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Parol səhvdir. SIM indi kilidlənib. PUK2-ni daxil et."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 həmişəlik qapadıldı."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Sizin <xliff:g id="NUMBER">%d</xliff:g> cəhdiniz qalıb."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 kilidlidir. Açmaq üçün xidmət provayderi ilə əlaqə saxlayın."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 artıq bloklanmayıb"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Şəbəkə və ya SIM kart xətası"</string>
<string name="doneButton" msgid="7371209609238460207">"Tamam"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Zəng etmək üçün mobil şəbəkəni aktiv edin, uçuş rejimini deaktiv edin və ya enerji qənaətini deaktiv edin."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Zəng etmək üçün təyyarə rejimini deaktiv edin."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Zəng etmək üçün təyyarə rejimini deaktiv edin və ya Wi-Fi şəbəkəsinə qoşulun."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefonunuz çox qızıb"</b>\n\n"Bu zəngi tamamlamaq mümkün deyil. Telefonunuz soyuduqda yenidən cəhd edin.\n\nHələ də təcili zənglər edə bilərsiniz."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Qeyri-fövqəladə zəng etmək üçün fövqəladə zəng rejimindən çıxın."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Şəbəkədə qeydə alınmayıb."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobil şəbəkə əlçatımlı deyil."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Defolt data SIM üçün alt Id:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL Buraxılışı (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL Buraxılışı (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Xananın məkan məlumatı (ləğv edilib):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE Fiziki Kanal Konfiqurasiyası:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Mobil məlumatın yenilənmə göstəricisi:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Operatorun bütün mobil ölçü məlumatı:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Telefon məlumatı"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Operator Təchizat Məlumatı"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Operator Təchizatlanmasını aktiv edin"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Bluetooth siqnalı zəifdir. Telefon spikerinə keçin."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Zəng keyfiyyəti bildirişi"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Ləğv edilmiş SIP hesabları"</string>
</resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 97087d4..d56f319 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Samo primarni korisnik može da menja podešavanja govorne pošte."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Vaša SIM kartica je odblokirana. Telefon se otključava..."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN za otključavanje mreže na SIM kartici"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM kartica je zaključana za operatera"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Otključaj"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Odbaci"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Traženje otključavanja mreže"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Prosleđuje se na <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Isključeno"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Mobilni operater ne podržava onemogućavanje preusmeravanja poziva kada je telefon nedostupan."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Vaš mobilni operater ne podržava preusmeravanje poziva."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Želite da uključite stavljanje poziva na čekanje?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Tokom poziva ćete dobijati obaveštenja o dolaznim pozivima"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Uključi"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Otkaži"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Stavljanje CDMA poziva na čekanje u IMS-u je uključeno"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Stavljanje CDMA poziva na čekanje u IMS-u je isključeno"</string>
<string name="updating_title" msgid="6130548922615719689">"Podešavanja poziva"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Samo korisniku sa administratorskim pravima je dozvoljeno da menja podešavanja poziva."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Podešavanja (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Ograničena funkcionalnost SIM-a"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> usluge za pozive i podatke se blokiraju dok koristite <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> usluge za pozive i podatke se blokiraju dok koristite drugi SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Zastareli SIP nalozi su pronađeni i uklonjeni"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Android platforma više ne podržava SIP pozivanje.\nPostojeći SIP nalozi <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> su uklonjeni.\nPotvrdite podrazumevano podešavanje naloga pozivanja."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Idi u podešavanja"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Potrošnja podataka aplikacije"</string>
<string name="data_usage_template" msgid="6287906680674061783">"Potrošili ste <xliff:g id="ID_1">%1$s</xliff:g> mobilnih podataka u periodu <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Napredno"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Lozinka nije tačna. SIM je sada blokiran. Unesite PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 je trajno blokiran."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Imate još <xliff:g id="NUMBER">%d</xliff:g> pokušaja."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 je zaključan. Za otključavanje se obratite dobavljaču usluge."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 više nije blokiran"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Greška na mreži ili SIM kartici"</string>
<string name="doneButton" msgid="7371209609238460207">"Gotovo"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Uključite mobilnu mrežu i isključite režim rada u avionu ili režim uštede baterije da biste uputili poziv."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Isključite režim rada u avionu da biste uputili poziv."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Isključite režim rada u avionu ili se povežite na bežičnu mrežu da biste uputili poziv."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefon je previše vruć"</b>\n\n"Poziv ne može da se završi. Probajte ponovo kada se telefon ohladi.\n\nI dalje možete da upućujete hitne pozive."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Izađite iz režima hitnog povratnog poziva da biste uputili poziv koji nije hitan."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Nije registrovano na mreži."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobilna mreža nije dostupna."</string>
@@ -602,7 +615,7 @@
<string name="ota_title_activate" msgid="4049645324841263423">"Aktivirajte telefon"</string>
<string name="ota_touch_activate" msgid="838764494319694754">"Potrebno je da uputite poseban poziv da biste aktivirali uslugu svoj telefona. \n\nNakon što pritisnete dugme „Aktiviraj“, slušajte data uputstva da biste da biste aktivirali svoj telefon."</string>
<string name="ota_hfa_activation_title" msgid="3300556778212729671">"Aktiviranje..."</string>
- <string name="ota_hfa_activation_dialog_message" msgid="7921718445773342996">"Telefon aktivira uslugu mobilnog prenosa podataka.\n\nTo može da potraje i do 5 minuta."</string>
+ <string name="ota_hfa_activation_dialog_message" msgid="7921718445773342996">"Telefon aktivira uslugu mobilnih podataka.\n\nTo može da potraje i do 5 minuta."</string>
<string name="ota_skip_activation_dialog_title" msgid="7666611236789203797">"Želite li da preskočite aktivaciju?"</string>
<string name="ota_skip_activation_dialog_message" msgid="6691722887019708713">"Ako preskočite aktivaciju, ne možete da upućujete pozive ili da se povezujete sa mrežama za mobilni prenos podataka (iako možete da se povežete sa WiFi mrežama). Sve dok ne aktivirate svoj telefon, bićete upitani da to učinite svaki put kada ga uključite."</string>
<string name="ota_skip_activation_dialog_skip_label" msgid="5908029466817825633">"Preskoči"</string>
@@ -870,7 +883,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubID podrazumevanog SIM-a za podatke:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL propusni opseg (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL propusni opseg (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Informacije o lokaciji predajnika (zastarelo):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfiguracija LTE fizičkog kanala:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Učestalost osvežavanja informacija o predajniku:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Sve informacije o merenju za predajnik:"</string>
@@ -915,4 +927,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Informacije o telefonu"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Informacije o dodeli mobilnog operatera"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Pokretanje dodele mobilnog operatera"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Bluetooth signal je slab. Probajte da pređete na spikerfon."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Obaveštenje o kvalitetu poziva"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Zastareli SIP nalozi"</string>
</resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index e9ebdea..6674bee 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Налады галасавой пошты можа мяняць толькі першасны карыстальнік."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Ваша SIM-карта была разблакаваная. Ваш тэлефон разблакоўваецца..."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN-код разблакавання сеткі SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"Можна выкарыстоўваць толькі SIM-карту аператара"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Разблакаваць"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Адхіліць"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Запыт разблакавання сеткі..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Пераадрасацыя на <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Выключаны"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Ваш аператар не падтрымлівае адключэнне пераадрасацыi выклікаў, калі тэлефон недаступны."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Ваш аператар не падтрымлівае пераадрасацыю выкліку."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Уключыць чаканне выкліку?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Вы будзеце атрымліваць апавяшчэнні аб уваходных выкліках падчас бягучых размоў"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Уключыць"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Скасаваць"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Чаканне выкліку па тэхналогіі CDMA (IMS) уключана"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Чаканне выкліку па тэхналогіі CDMA (IMS) выключана"</string>
<string name="updating_title" msgid="6130548922615719689">"Налады выклікаў"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Налады выклікаў можа мяняць толькі адміністратар."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Налады (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Абмежаваныя функцыі SIM-карты"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Выклікі і перадача даных праз <xliff:g id="CARRIER_NAME">%1$s</xliff:g> могуць блакіравацца пры выкарыстанні нумара <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Выклікі і перадача даных праз <xliff:g id="CARRIER_NAME">%1$s</xliff:g> блакіруюцца пры выкарыстанні іншай SIM-карты."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Знойдзены і выдалены састарэлыя ўліковыя запісы SIP"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"На платформе Android больш не падтрымліваецца SIP-тэлефанія.\nВашы існуючыя ўліковыя запісы SIP (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>) выдалены.\nПацвердзіце стандартны ўліковы запіс для выклікаў."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Перайсці ў налады"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Выкарыстанне трафіка"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> мабільнага трафіку выкарыстана ў перыяд <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Пашыраныя налады"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Пароль няправільны. Зараз SIM-карта заблакіравана. Увядзіце код PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"Код PUK2 заблакіраваны назаўжды."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"У вас засталося спроб: <xliff:g id="NUMBER">%d</xliff:g>."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"Код PUK2 заблакіраваны. Для разблакіроўкі звярніцеся да пастаўшчыка паслуг."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"Код PIN2 больш не заблакіраваны"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Памылка сеткі або SIM-карты"</string>
<string name="doneButton" msgid="7371209609238460207">"Гатова"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Каб пазваніць, уключыце мабільную сетку, выключыце рэжым палёту або рэжым эканоміі зараду."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Адключыце рэжым палёту, каб зрабіць выклік."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Адключыце рэжым палёту або падлучыцеся да бесправадной сеткі, каб зрабіць выклік."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Тэлефон перагрэўся"</b>\n\n"Не ўдалося зрабіць выклік. Паўтарыце спробу, калі тэлефон астыне.\n\nВы ўсё роўна можаце рабіць экстранныя выклікі."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Каб зрабіць звычайны выклік, выйдзіце з рэжыму экстранных зваротных выклікаў."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Не зарэгістраваны ў сетцы."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Мабільная сетка недаступная."</string>
@@ -873,7 +886,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubID стандартнай SIM-карты для перадачы даных:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Прапускная здольнасць канала спампоўвання (кбіт/с):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Прапускная здольнасць канала запампоўвання (кбіт/с):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Інфармацыя пра месцазнаходжанне сотавай ячэйкі (састарэлая):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Канфігурацыя фізічнага канала LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Частата абнаўлення даных сотавай сеткі:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Усе паказчыкі сотавай сеткі:"</string>
@@ -918,4 +930,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Звесткі аб тэлефоне"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Звесткі аб аператары"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Запусціць сінхранізацыю з аператарам"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Сігнал Bluetooth слабы. Паспрабуйце пераключыцца на гучную сувязь."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Апавяшчэнне пра якасць выкліку"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Састарэлыя ўліковыя запісы SIP"</string>
</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index d5a1a53..216c789 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Само основният потребител може да променя настройките на гласовата поща."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"SIM картата ви е отблокирана. Телефонът ви се отключва…"</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN за отключване на SIM мрежа"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM картата е заключена към оператора"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Отключване"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Отхвърляне"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Изпраща се заявка за отключване на мрежата..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Пренасочва се към <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Изкл."</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Операторът ви не поддържа деактивиране на пренасочването на обаждания, когато няма връзка с телефона."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Операторът ви не поддържа пренасочване на обаждания."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Да се включат ли изчакващите обаждания?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Ще получавате известие за входящи обаждания по време на обаждане"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Включване"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Отказ"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Изчакващите обаждания за CDMA с IMS са включени"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Изчакващите обаждания за CDMA с IMS са изключени"</string>
<string name="updating_title" msgid="6130548922615719689">"Настройки за обаждане"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Само администраторът може да променя настройките за обаждане."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Настройки (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Ограничена функционалност на SIM картата"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Обажданията и услугите за данни от <xliff:g id="CARRIER_NAME">%1$s</xliff:g> може да са блокирани, докато използвате <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Обажданията и услугите за данни от <xliff:g id="CARRIER_NAME">%1$s</xliff:g> може да са блокирани с друга SIM карта."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Открити са и са премахнати оттеглени профили за SIP"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Обажданията през SIP вече не се поддържат от платформата Android.\nСъществуващите ви профили за SIP <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> бяха премахнати.\nМоля, потвърдете настройката за стандартния си профил за обаждания."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Към настройките"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Пренос на данни от приложенията"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> мобилни данни са използвани за периода <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Разширени"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Паролата е неправилна. SIM картата вече е заключена. Въведете PUK2 кода."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 е блокиран завинаги."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Остават ви <xliff:g id="NUMBER">%d</xliff:g> опита."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"Заключена с PUK2. За отключване се обърнете към оператора."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"ПИН2 кодът вече не е блокиран"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Грешка в мрежата или SIM картата"</string>
<string name="doneButton" msgid="7371209609238460207">"Готово"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Включете мобилната мрежа, изключете самолетния режим или режима за запазване на батерията, за да се обадите."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Изключете самолетния режим, за да осъществите обаждане."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Изключете самолетния режим или се свържете с безжична мрежа, за да осъществите обаждане."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Телефонът е твърде горещ"</b>\n\n"Обаждането не може да се извърши. Опитайте отново, когато телефонът се охлади.\n\nМожете да извършвате спешни обаждания."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Излезте от режима на обратно обаждане при спешност, за да можете да извършвате обаждания, които не са спешни."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Няма регистрация в мрежата."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Няма мобилна мрежа."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Идентификационен подномер на SIM картата за данни по подразбиране:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Пропускателна способност при изтегляне (кб/сек):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Пропускателна способност при качване (кб/сек):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Информация за местоположението на клетката (оттеглено):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Конфигурация на физическия канал на LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Честота на опресняване на информацията за клетките:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Цялата измервателна информация за клетките:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Информация за телефона"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Информация за обезпечаването от оператора"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Задействане на обезпечаването от оператора"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Сигналът ви за Bluetooth е слаб. Опитайте да превключите на високоговорител."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Известия за качеството на обаждането"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Оттеглени профили за SIP"</string>
</resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 7410e96..81a2b33 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"ভয়েসমেল সেটিংস কেবলমাত্র প্রাথমিক ব্যবহারকারী দ্বারা পরিবর্তন করা যাবে।"</string>
<string name="puk_unlocked" msgid="4627340655215746511">"আপনার সিম কার্ডকে অবরোধ মুক্ত করা হয়েছে৷ আপনার ফোন আনলক করা হচ্ছে..."</string>
<string name="label_ndp" msgid="7617392683877410341">"সিম নেটওয়ার্ক আনলক পিন"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"অপারেটরের জন্য সিম লক করা আছে"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"আনলক করুন"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"খারিজ করুন"</string>
<string name="requesting_unlock" msgid="930512210309437741">"নেটওয়ার্ক আনলকের অনুরোধ করা হচ্ছে..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> এ ফরওয়ার্ড করা হচ্ছে"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"বন্ধ আছে"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"আপনার ক্যারিয়ার আপনার ফোনের সাথে যখন সংযোগ স্থাপন করা যাচ্ছে না সেই অবস্থায় কল ফরওয়ার্ডিংকে অক্ষম করা সমর্থন করে না৷"</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"আপনার পরিষেবা প্রদানকারী নম্বর ব্যবহার করে কল ফরওয়ার্ড করার সুবিধা ব্যবহার করতে পারবেন না।"</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"কল ওয়েটিং সুবিধা চালু করতে চান?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"কল চলার সময়ে ইনকামিং কল এলে আপনাকে জানানো হবে"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"চালু করুন"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"বাতিল করুন"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS-এ CDMA কল ওয়েটিং সুবিধা চালু আছে"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS-এ CDMA কল ওয়েটিং সুবিধা বন্ধ আছে"</string>
<string name="updating_title" msgid="6130548922615719689">"কল সেটিংস"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"কেবলমাত্র প্রশাসক ব্যবহারকারী কল সেটিংস পরিবর্তন করতে পারবেন৷"</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"সেটিংস (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"সিমের কিছু কাজ করবে না"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g> ব্যবহার করার সময় <xliff:g id="CARRIER_NAME">%1$s</xliff:g> কল এবং ডেটা পরিষেবা হয়তো ব্লক করা থাকতে পারে।"</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"অন্য সিম ব্যবহার করার সময় <xliff:g id="CARRIER_NAME">%1$s</xliff:g> কল এবং ডেটা পরিষেবা হয়ত ব্লক হয়ে যেতে পারে।"</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"পুরনো SIP অ্যাকাউন্ট খুঁজে পাওয়া গেছে এবং সরানো হয়েছে"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Android প্ল্যাটফর্মে \'SIP কলিং\'-এর সুবিধা আর কাজ করবে না।\nআগে থেকে থাকা আপনার SIP অ্যাকাউন্ট <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> সরানো হয়েছে।\nকল করার জন্য ব্যবহৃত ডিফল্ট অ্যাকাউন্ট সেটিং কনফার্ম করুন।"</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"সেটিংসে যান"</string>
<string name="data_usage_title" msgid="8438592133893837464">"অ্যাপ ডেটার ব্যবহার"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g> এর মধ্যে <xliff:g id="ID_1">%1$s</xliff:g> মোবাইল ডেটা ব্যবহার করা হয়েছে"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"উন্নত"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"পাসওয়ার্ডটি ভুল৷ সিম এখন অবরুদ্ধ৷ PUK2 লিখুন৷"</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 স্থায়ীভাবে অবরুদ্ধ করা হয়েছে৷"</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"আপনার কাছে <xliff:g id="NUMBER">%d</xliff:g>টি প্রচেষ্টা অবশিষ্ট রয়েছে৷"</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 লক করা আছে। আনলক করতে পরিষেবা প্রদানকারীর সাথে যোগাযোগ করুন।"</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 এখন আর অবরুদ্ধ নয়"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"নেটওয়ার্ক বা সিম কার্ড ত্রুটি৷"</string>
<string name="doneButton" msgid="7371209609238460207">"সম্পন্ন হয়েছে"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"কল করতে মোবাইল নেটওয়ার্ক চালু করুন, বিমান মোড বা ব্যাটারি সেভার বন্ধ করুন৷"</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"কল করতে বিমান মোড বন্ধ করুন৷"</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"বিমান মোড বন্ধ করুন বা কল করতে কোনো ওয়্যারলেস নেটওয়ার্কে সংযোগ করুন৷"</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"ফোনটি খুব গরম হয়ে গেছে"</b>\n\n"এই কলটি সম্পূর্ণ করা যাচ্ছে না। ফোনটি ঠাণ্ডা হয়ে গেলে আবার চেষ্টা করুন।\n\nআপনি এখনও জরুরি কল করতে পারেন।"</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"একটি সাধারণ কল করতে জরুরি কলব্যাক মোডের বাইরে আসুন৷"</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"নেটওয়ার্কে নিবন্ধিত নয়৷"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"মোবাইল নেটওয়ার্ক উপলব্ধ নয়৷"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ডিফল্ট ডেটা সিমের SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"ডিএল ব্যান্ডউইথ (কেবিপিএস):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"ইউএল ব্যান্ডউইথ (কেবিপিএস):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"সেল লোকেশন তথ্য (বন্ধ করা হয়েছে):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ফিজিক্যাল চ্যানেল কনফিগারেশন:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"সেল তথ্যে রিফ্রেশ রেট:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"সমস্ত সেল পরিমাপ তথ্য:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"ফোনের তথ্য"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"পরিষেবা প্রদানকারীর ব্যবস্থামূলক তথ্য"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"ট্রিগার পরিষেবা প্রদানকারীর ব্যবস্থা"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"আপনার ডিভাইসের ব্লুটুথ সিগনাল ভাল না। বদল করে স্পিকারফোন বেছে নিন।"</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"ফোন কলের কোয়ালিটি সংক্রান্ত বিজ্ঞপ্তি"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"পুরনো SIP অ্যাকাউন্ট"</string>
</resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 2dd8b0c..261506d 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Postavke govorne pošte može promijeniti samo Primarni korisnik."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Vaša SIM kartica je deblokirana. Telefon se otključava…"</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN za otključavanje mreže na SIM kartici"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM je zaključan za operatera"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Otključaj"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Odbaci"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Traži se otključavanje mreže…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Preusmjerava se na <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Isključeno"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Vaš operater ne podržava onemogućavanje prosljeđivanja poziva kada ste nedostupni."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Vaš mobilni operater ne podržava prosljeđivanje poziva."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Uključiti poziv na čekanju?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Tokom poziva ćete primati obavještenja o dolaznim pozivima"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Uključi"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Otkaži"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA poziv na čekanju u okviru IMS-a je uključen"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA poziv na čekanju u okviru IMS-a je isključen"</string>
<string name="updating_title" msgid="6130548922615719689">"Postavke poziva"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Postavke poziva može promijeniti samo administrator."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Postavke (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -188,7 +196,7 @@
<string name="network_connecting" msgid="160901383582774987">"Povezivanje…"</string>
<string name="network_could_not_connect" msgid="6547460848093727998">"Povezivanje nije uspjelo"</string>
<string-array name="preferred_network_mode_choices">
- <item msgid="4531933377509551889">"Preferiraj GSM/WCDMA"</item>
+ <item msgid="4531933377509551889">"Preferira se GSM/WCDMA"</item>
<item msgid="5120532750027435355">"Samo GSM"</item>
<item msgid="8973311380798000102">"Samo WCDMA"</item>
<item msgid="4139961027147085705">"Automatski GSM/WCDMA"</item>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Ograničena funkcionalnost SIM-a"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Pozivi i usluge prijenosa podataka operatera <xliff:g id="CARRIER_NAME">%1$s</xliff:g> mogu biti blokirane kada koristite broj <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Pozivi i usluge prijenosa pod. op. <xliff:g id="CARRIER_NAME">%1$s</xliff:g> mogu biti blok. kada koristite drugi SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Zastarjeli SIP računi su pronađeni i uklonjeni"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Android platforma više ne podržva SIP pozivanje.\nVaši postojeći SIP računi <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> su uklonjeni.\nPotvrdite zadanu postavku računa za pozivanje."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Idi u postavke"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Prijenos podataka u aplikaciji"</string>
<string name="data_usage_template" msgid="6287906680674061783">"Iskorišteno je <xliff:g id="ID_1">%1$s</xliff:g> prijenosa podataka u periodu <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Napredno"</string>
@@ -299,7 +310,7 @@
<string name="sim_change_data_title" msgid="9142726786345906606">"Promijeniti SIM za prijenos podataka?"</string>
<string name="sim_change_data_message" msgid="3567358694255933280">"Koristiti SIM karticu <xliff:g id="NEW_SIM">%1$s</xliff:g> umjesto SIM kartice <xliff:g id="OLD_SIM">%2$s</xliff:g> za prijenos podataka na mobilnoj mreži?"</string>
<string name="wifi_calling_settings_title" msgid="5800018845662016507">"Pozivanje putem WiFi-ja"</string>
- <string name="video_calling_settings_title" msgid="342829454913266078">"Operater video pozivanja"</string>
+ <string name="video_calling_settings_title" msgid="342829454913266078">"Video pozivi putem operatera"</string>
<string name="gsm_umts_options" msgid="4968446771519376808">"GSM/UMTS opcije"</string>
<string name="cdma_options" msgid="3669592472226145665">"CDMA opcije"</string>
<string name="throttle_data_usage" msgid="1944145350660420711">"Korištenje podataka"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Lozinka je netačna. SIM je sada zaključan. Unesite PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 je trajno blokiran."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Broj preostalih pokušaja: <xliff:g id="NUMBER">%d</xliff:g>."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"SIM je zaključan kodom PUK2. Kontaktirajte pružaoca usluge da ga otključate."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 više nije blokiran"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Greška na mreži ili SIM kartici"</string>
<string name="doneButton" msgid="7371209609238460207">"Gotovo"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Uključite mobilnu mrežu, isključite Način rada u avionu ili isključite Uštedu baterije da pozovete."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Isključite način rada u avionu da pozovete."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Isključite način rada u avionu ili se povežite na bežičnu mrežu da pozovete."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefon se pregrijao"</b>\n\n"Nije moguće završiti ovaj poziv. Pokušajte ponovo kada se telefon ohladi.\n\nI dalje možete upućivati hitne pozive."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Izađite iz načina rada za hitni povratni poziv da uputite poziv koji nije hitan."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Nije registrirano na mreži."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobilna mreža nije dostupna."</string>
@@ -826,7 +839,7 @@
<string name="radio_info_data_connection_enable" msgid="6183729739783252840">"Omogućite vezu za prijenos podataka"</string>
<string name="radio_info_data_connection_disable" msgid="6404751291511368706">"Onemogući vezu za prijenos podataka"</string>
<string name="volte_provisioned_switch_string" msgid="4812874990480336178">"VoLTE omogućen"</string>
- <string name="vt_provisioned_switch_string" msgid="8295542122512195979">"Video poziv obezbijeđen"</string>
+ <string name="vt_provisioned_switch_string" msgid="8295542122512195979">"Video pozivi su omogućeni"</string>
<string name="wfc_provisioned_switch_string" msgid="3835004640321078988">"WiFi poziv obezbijeđen"</string>
<string name="eab_provisioned_switch_string" msgid="4449676720736033035">"EAB/Omogućeno prisustvo"</string>
<string name="cbrs_data_switch_string" msgid="6060356430838077653">"Cbrs podaci"</string>
@@ -845,7 +858,7 @@
<string name="radio_info_ims_reg_status_not_registered" msgid="8045821447288876085">"Nije registrirano"</string>
<string name="radio_info_ims_feature_status_available" msgid="6493200914756969292">"Dostupno"</string>
<string name="radio_info_ims_feature_status_unavailable" msgid="8930391136839759778">"Nedostupno"</string>
- <string name="radio_info_ims_reg_status" msgid="25582845222446390">"IMS registracija: <xliff:g id="STATUS">%1$s</xliff:g>\nGovor putem LTE mreže: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nGovor putem WiFi mreže: <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nVideo pozivanje: <xliff:g id="AVAILABILITY_2">%4$s</xliff:g>\nUT interfejs: <xliff:g id="AVAILABILITY_3">%5$s</xliff:g>"</string>
+ <string name="radio_info_ims_reg_status" msgid="25582845222446390">"IMS registracija: <xliff:g id="STATUS">%1$s</xliff:g>\nGovor putem LTE mreže: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nGovor putem WiFi mreže: <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nVideo pozivi: <xliff:g id="AVAILABILITY_2">%4$s</xliff:g>\nUT interfejs: <xliff:g id="AVAILABILITY_3">%5$s</xliff:g>"</string>
<string name="radioInfo_service_in" msgid="45753418231446400">"Aktivno"</string>
<string name="radioInfo_service_out" msgid="287972405416142312">"Ne radi"</string>
<string name="radioInfo_service_emergency" msgid="4763879891415016848">"Samo hitni pozivi"</string>
@@ -870,7 +883,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Pomoćni ID za zadani SIM za prijenos podataka:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL propusnost (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL propusnost (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Informacije o lokaciji ćelije (zastarjele):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfiguracija LTE fizičkog kanala:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Interval osvježavanja informacija o ćeliji:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Sve informacije o mjerenju ćelije:"</string>
@@ -915,4 +927,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Informacije o telefonu"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Operater koji pruža informacije"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Aktiviranje dodjele resursa operatera"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Vaš Bluetooth signal je slab. Pokušajte prebaciti na zvučnik."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Obavještenje o kvalitetu poziva"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Zastarjeli SIP računi"</string>
</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index f529df6..5bc13e0 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Només l\'usuari principal pot modificar la configuració dels missatges de veu."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"La targeta SIM s\'ha desbloquejat. El telèfon s\'està desbloquejant..."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN de desbloqueig de xarxa SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM bloquejada per a l\'operador"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Desbloqueja"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Ignora"</string>
<string name="requesting_unlock" msgid="930512210309437741">"S\'està sol·licitant el desbloqueig de la xarxa..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"S\'està desviant a <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Desactivat"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"El teu operador de telefonia mòbil no permet desactivar la desviació de trucades quan el telèfon estigui il·localitzable."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"El teu operador no admet la desviació de trucades."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Vols activar l\'opció de trucada en espera?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Durant una trucada, rebràs una notificació si tens cap trucada entrant"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Activa"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Cancel·la"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"L\'opció de trucada en espera de CDMA a IMS està activada"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"L\'opció de trucada en espera de CDMA a IMS està desactivada"</string>
<string name="updating_title" msgid="6130548922615719689">"Configuració de trucada"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Només l\'administrador pot canviar la configuració de trucades."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Configuració (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Funcionalitat de la SIM limitada"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Pot ser que les trucades i els serveis de dades de <xliff:g id="CARRIER_NAME">%1$s</xliff:g> es bloquegin mentre fas servir el número <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Les trucades i dades que t\'ofereix <xliff:g id="CARRIER_NAME">%1$s</xliff:g> es poden bloquejar quan tens una altra SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"S\'han trobat comptes SIP obsolets i s\'han suprimit"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"La plataforma Android ja no admet les trucades SIP.\nEls teus comptes SIP existents <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> s\'han suprimit.\nConfirma la configuració predeterminada de trucades del teu compte."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Ves a la configuració"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Ús de dades de l\'aplicació"</string>
<string name="data_usage_template" msgid="6287906680674061783">"S\'han utilitzat <xliff:g id="ID_1">%1$s</xliff:g> en el període <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Configuració avançada"</string>
@@ -400,7 +411,7 @@
<string name="enable_disable_multi_category" msgid="5958248155437940625">"Multicategoria"</string>
<string name="multi_category_enable" msgid="4531915767817483960">"Multicategoria activada"</string>
<string name="multi_category_disable" msgid="6325934413701238104">"Multicategoria desactivada"</string>
- <string name="network_recommended" msgid="3444321100580250926">" (recomanat)"</string>
+ <string name="network_recommended" msgid="3444321100580250926">" (opció recomanada)"</string>
<string name="network_lte" msgid="7206879277095094280">"LTE (opció recomanada)"</string>
<string name="network_4G" msgid="6800527815504223913">"4G (opció recomanada)"</string>
<string name="network_global" msgid="3289646154407617631">"Global"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"La contrasenya no és correcta i la SIM s\'ha bloquejat. Introdueix el PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"El PUK2 s\'ha bloquejat permanentment."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Et queden <xliff:g id="NUMBER">%d</xliff:g> intents."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"El PUK2 s\'ha bloquejat. Contacta amb el proveïdor de serveis per desbloquejar-lo."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"El PIN2 ja no està bloquejat."</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Error de la xarxa o de targeta SIM"</string>
<string name="doneButton" msgid="7371209609238460207">"Fet"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Per fer una trucada, activa la xarxa mòbil o desactiva el mode d\'avió o el mode d\'estalvi de bateria."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Per fer una trucada, desactiva el mode d\'avió."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Per fer una trucada, desactiva el mode d\'avió o connecta amb una xarxa sense fil."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"El telèfon està massa calent"</b>\n\n"No es pot completar aquesta trucada. Torna-ho a provar quan el telèfon es refredi.\n\nPots fer trucades d\'emergència igualment."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Surt del mode de devolució de trucada d\'emergència per fer un altre tipus de trucada."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"No registrat a la xarxa."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"La xarxa mòbil no està disponible."</string>
@@ -844,7 +857,7 @@
<string name="radio_info_ims_feature_status_unavailable" msgid="8930391136839759778">"No disponible"</string>
<string name="radio_info_ims_reg_status" msgid="25582845222446390">"Registre de MI: <xliff:g id="STATUS">%1$s</xliff:g>\nVeu per LTE: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nVeu per Wi-Fi: <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nVideotrucades: <xliff:g id="AVAILABILITY_2">%4$s</xliff:g>\nInterfície d\'UT: <xliff:g id="AVAILABILITY_3">%5$s</xliff:g>"</string>
<string name="radioInfo_service_in" msgid="45753418231446400">"En servei"</string>
- <string name="radioInfo_service_out" msgid="287972405416142312">"Sense servei"</string>
+ <string name="radioInfo_service_out" msgid="287972405416142312">"Fora de servei"</string>
<string name="radioInfo_service_emergency" msgid="4763879891415016848">"Només trucades d\'emergència"</string>
<string name="radioInfo_service_off" msgid="3456583511226783064">"Senyal mòbil desactivat"</string>
<string name="radioInfo_roaming_in" msgid="3156335577793145965">"Itinerància"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Subidentificador de la SIM de dades predeterminada:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Amplada de banda de baixada (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Amplada de banda de pujada (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Informació d\'ubicació del mòbil (obsoleta):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuració del canal físic de LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Freqüència d\'actualització de la informació del mòbil:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Tota la informació de mesures del mòbil:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Informació del telèfon"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Informació de serveis de l\'operador"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Activa els serveis de l\'operador"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"El senyal del Bluetooth és feble. Fes servir l\'altaveu."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Notificació sobre la qualitat de la trucada"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Comptes SIP obsolets"</string>
</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 31119ee..926681b 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Nastavení hlasové schránky může změnit pouze primární uživatel."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"SIM karta byla odblokována. Telefon se odblokovává..."</string>
<string name="label_ndp" msgid="7617392683877410341">"Kód PIN odblokování sítě pro SIM kartu"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"Na kartě SIM je zámek operátora"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Odemknout"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Zavřít"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Žádost o odblokování sítě..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Hovory budou přesměrovány na číslo <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Vypnuto"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Váš operátor neumožňuje deaktivovat přesměrování hovorů, když je telefon nedostupný."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Váš operátor nepodporuje přesměrování hovorů."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Zapnout funkci Další hovor na lince?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"V průběhu hovoru budete dostávat upozornění na příchozí hovory."</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Zap."</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Zrušit"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA – další hovor na lince ve službě IMS – zapnuto"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA – další hovor na lince ve službě IMS – vypnuto"</string>
<string name="updating_title" msgid="6130548922615719689">"Nastavení hovorů"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Nastavení hovorů může změnit pouze uživatel s oprávněním administrátora."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Nastavení (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Omezená funkčnost SIM karty"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Hovory a datové služby operátora <xliff:g id="CARRIER_NAME">%1$s</xliff:g> mohou být zablokovány, když používáte číslo <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Hovory a datové služby operátora <xliff:g id="CARRIER_NAME">%1$s</xliff:g> mohou být s jinou SIM kartou blokovány."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Zastaralé účty SIP byly vyhledány a odstraněny"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Volání SIP už na platformě Android není podporováno.\nVaše stávající účty SIP (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>) byly odstraněny.\nPotvrďte výchozí nastavení účtu pro volání."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Přejít do nastavení"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Využití dat aplikací"</string>
<string name="data_usage_template" msgid="6287906680674061783">"V období <xliff:g id="ID_2">%2$s</xliff:g> jste využili <xliff:g id="ID_1">%1$s</xliff:g> mobilních dat"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Rozšířená nastavení"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Nesprávné heslo. SIM karta je teď uzamčena. Zadejte kód PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 je trvale zablokován."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Počet zbývajících pokusů: <xliff:g id="NUMBER">%d</xliff:g>."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"Uzamčeno kódem PUK2. Se žádostí o odemknutí SIM karty se obraťte na poskytovatele služeb."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 již není blokován"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Došlo k chybě sítě nebo SIM karty."</string>
<string name="doneButton" msgid="7371209609238460207">"Hotovo"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Pokud chcete volat, zapněte mobilní síť a vypněte režim letadla nebo spořič baterie."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Chcete-li provést hovor, vypněte režim Letadlo."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Chcete-li se připojit k bezdrátové síti a provést hovor, vypněte režim Letadlo."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefon je příliš zahřátý"</b>\n\n"Hovor nelze provést. Zkuste to znovu, až telefon vychladne.\n\nTísňové volání máte pořád k dispozici."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Chcete-li uskutečnit běžný hovor, opusťte režim tísňového volání."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Přihlášení k síti nebylo úspěšné."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobilní síť je nedostupná."</string>
@@ -873,7 +886,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId výchozí datové SIM karty:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Rychlost stahování (kB/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Rychlost nahrávání (kB/s):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Informace o poloze mobilní sítě (podpora ukončena):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfigurace fyzického kanálu LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Interval obnovení informací o mobilní síti:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Všechny údaje o měření mobilní sítě:"</string>
@@ -918,4 +930,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Informace o telefonu"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Informace o zprovoznění služby u operátora"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Spustit zprovoznění služby u operátora"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Signál sítě Bluetooth je slabý. Zkuste přepnout na hlasitý odposlech."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Oznámení o kvalitě hovoru"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Zastaralé účty SIP"</string>
</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 6516de4..ecfb98d 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Indstillingerne for telefonsvareren kan kun ændres af den primære bruger."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Blokeringen af dit SIM-kort er blevet ophævet. Din telefon låser op ..."</string>
<string name="label_ndp" msgid="7617392683877410341">"Pinkode til oplåsning af SIM-netværket"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM-kortet er låst for operatøren"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Lås op"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Luk"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Anmoder om oplåsning af netværk ..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Viderestiller til <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Fra"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Dit mobilselskab understøtter ikke deaktivering af viderestilling af opkald, når telefonen ikke er tilgængelig."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Dit mobilselskab understøtter ikke viderestilling af opkald."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Vil du aktivere ventende opkald?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Under et opkald får du en notifikation om indgående opkald."</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Aktivér"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Annuller"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Ventende CDMA-opkald under IMS er aktiveret"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Ventende CDMA-opkald under IMS er deaktiveret"</string>
<string name="updating_title" msgid="6130548922615719689">"Indstillinger for opkald"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Opkaldsindstillingerne kan kun ændres af administratorbrugeren."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Indstillinger (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"SIM-kortets funktionalitet er begrænset"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Opkalds- og datatjenester fra <xliff:g id="CARRIER_NAME">%1$s</xliff:g> blokeres muligvis, mens <xliff:g id="PHONE_NUMBER">%2$s</xliff:g> bruges."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Opkalds- og datatjenester fra <xliff:g id="CARRIER_NAME">%1$s</xliff:g> blokeres muligvis, mens der bruges et andet SIM-kort."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Udfasede SIP-konti blev fundet og fjernet"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP-opkald understøttes ikke længere af Android-platformen.\nDine eksisterende SIP-konti <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> er blevet fjernet.\nBekræft din standardkontoindstilling for opkald."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Gå til Indstillinger"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Dataforbrug i apps"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> mobildata er brugt i perioden <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Avanceret"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Forkert adgangskode. SIM-kortet er nu låst. Angiv PUK-kode 2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 er permanent blokeret."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Du har <xliff:g id="NUMBER">%d</xliff:g> forsøg tilbage."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2-låst. Kontakt din udbyder for at låse op."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"Pinkode 2 er ikke længere blokeret"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Fejl i netværk eller SIM-kort"</string>
<string name="doneButton" msgid="7371209609238460207">"Afslut"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Slå Mobilnetværk til, Flytilstand fra eller Batterisparefunktion fra for at foretage et opkald."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Slå flytilstand fra for at foretage et opkald."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Slå flytilstand fra, eller opret forbindelse til et trådløst netværk for at foretage et opkald."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefonen er for varm"</b>\n\n"Opkaldet kan ikke foretages. Prøv igen, når din telefon er kølet af.\n\nDu kan stadig foretage nødopkald."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Afslut nødtilbagekaldstilstand for at foretage et almindeligt opkald."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Ikke registreret på netværk."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobilnetværket er ikke tilgængeligt."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Under-id for standard-SIM-kort til data:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Båndbredde til download (kB/sek.):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Båndbredde til upload (kB/sek.):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Oplysning om lokation af celle (udfaset):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Fysisk kanalkonfiguration for LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Opdateringsfrekvens for celleoplysninger:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Alle oplysninger om cellemåling:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Telefonoplysninger"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Oplysninger om mobilselskab"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Aktivér mobilselskab"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Dit Bluetooth-signal er svagt. Prøv at skifte til medhør."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Notifikation om opkaldskvalitet"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Udfasede SIP-konti"</string>
</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index cfff56e..7af3720 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Mailboxeinstellungen können nur vom primären Nutzer geändert werden."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Deine SIM-Karte wurde entsperrt. Dein Telefon wird nun entsperrt..."</string>
<string name="label_ndp" msgid="7617392683877410341">"Entsperr-PIN für netzgebundenes Gerät"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM für Anbieter gesperrt"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Entsperren"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Verwerfen"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Entsperrung des netzgebundenen Geräts wird angefordert..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Weiterleitung an <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Aus"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Dein Mobilfunkanbieter unterstützt die Deaktivierung der Anrufweiterleitung bei Nichterreichbarkeit nicht."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Dein Mobilfunkanbieter unterstützt die Anrufweiterleitung nicht."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Anklopfen aktivieren?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Du wirst während eines Anrufs über eingehende Anrufe benachrichtigt"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Aktivieren"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Abbrechen"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA-Anklopfen unter IMS aktiviert"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA-Anklopfen unter IMS deaktiviert"</string>
<string name="updating_title" msgid="6130548922615719689">"Anrufeinstellungen"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Anrufeinstellungen können nur vom Administrator geändert werden."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Einstellungen (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Eingeschränkte SIM-Funktion"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Anrufe und Datendienste über <xliff:g id="CARRIER_NAME">%1$s</xliff:g> stehen möglicherweise nicht zur Verfügung, während du <xliff:g id="PHONE_NUMBER">%2$s</xliff:g> nutzt."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Anrufe/Datendienste über <xliff:g id="CARRIER_NAME">%1$s</xliff:g> sind bei Nutzung einer anderen SIM eventuell blockiert."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Eingestellte SIP-Konten gefunden und entfernt"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP-Anrufe werden von der Android-Plattform nicht mehr unterstützt.\nDie vorhandenen SIP-Konten (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>) wurden entfernt.\nPrüfe die Einstellung für das Standardkonto für Anrufe."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Zu den Einstellungen"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Datenverbrauch durch Apps"</string>
<string name="data_usage_template" msgid="6287906680674061783">"Im Zeitraum <xliff:g id="ID_2">%2$s</xliff:g> genutzte mobile Daten: <xliff:g id="ID_1">%1$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Erweitert"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Passwort falsch. SIM-Karte ist jetzt gesperrt. Bitte PUK2 eingeben."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 wurde endgültig gesperrt."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Du hast noch <xliff:g id="NUMBER">%d</xliff:g> Versuche."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 gesperrt. Wende dich zum Entsperren an den Mobilfunkanbieter."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 nicht mehr gesperrt"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Netzwerk- oder SIM-Kartenfehler"</string>
<string name="doneButton" msgid="7371209609238460207">"Fertig"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Zum Anrufen Mobilfunknetz aktivieren, Flugmodus oder Energiesparmodus deaktivieren."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Deaktiviere zum Telefonieren den Flugmodus."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Deaktiviere zum Telefonieren den Flugmodus oder stelle eine WLAN-Verbindung her."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Smartphone ist überhitzt."</b>\n\n"Anruf nicht möglich. Versuche es noch einmal, wenn das Smartphone abgekühlt ist.\n\nDu kannst weiterhin Notrufe absetzen."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Beende den Notfallrückrufmodus, um einen Anruf zu tätigen, bei dem es sich nicht um einen Notfall handelt."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Nicht in Netzwerk registriert."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobilfunknetz ist nicht verfügbar."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Unter-ID der standardmäßigen Daten-SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL-Bandbreite (kbit/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL-Bandbreite (kbit/s):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Standortinformationen des Mobiltelefons (eingestellt):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfiguration des physischen LTE-Kanals:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Aktualisierungsrate der Mobiltelefoninformationen:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Alle Informationen zu Zellenmesswerten:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Über das Telefon"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Informationen zur Nutzerverwaltung durch den Anbieter"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Nutzerverwaltung durch den Anbieter auslösen"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Das Bluetooth-Signal ist schwach. Verwende die Freisprechfunktion."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Benachrichtigung zu Anrufqualität"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Eingestellte SIP-Konten"</string>
</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 3389815..1c88d8e 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Οι ρυθμίσεις αυτόματου τηλεφωνητή μπορούν να αλλάξουν μόνο από τον κύριο χρήστη."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Καταργήθηκε ο αποκλεισμός της κάρτας SIM. Το τηλέφωνό σας ξεκλειδώνεται..."</string>
<string name="label_ndp" msgid="7617392683877410341">"Αριθμός PIN ξεκλειδώματος δικτύου κάρτας SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"Η κάρτα SIM κλειδώθηκε για τον πάροχο"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Ξεκλείδωμα"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Παράβλεψη"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Αίτηση ξεκλειδώματος δικτύου..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Προώθηση στο <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Ανενεργό"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Η εταιρεία κινητής τηλεφωνίας δεν υποστηρίζει την απενεργοποίηση της προώθησης κλήσεων όταν το τηλέφωνό σας δεν έχει σήμα."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Η εταιρεία κινητής τηλεφωνίας σας δεν υποστηρίζει την προώθηση κλήσεων."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Ενεργοποίηση αναμονής κλήσης;"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Κατά τη διάρκεια μιας κλήσης θα ειδοποιείστε για εισερχόμενες κλήσεις"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Ενεργοποίηση"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Άκυρο"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Η αναμονή κλήσης σε λειτουργία άμεσων μηνυμάτων (ΙΜ) είναι απενεργοποιημένη"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Η αναμονή κλήσης σε λειτουργία άμεσων μηνυμάτων (ΙΜ) είναι απενεργοποιημένη"</string>
<string name="updating_title" msgid="6130548922615719689">"Ρυθμίσεις κλήσης"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Οι ρυθμίσεις κλήσεων μπορούν να αλλάξουν μόνο από τον χρήστη που έχει ρόλο διαχειριστή."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Ρυθμίσεις (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Περιορισμένη λειτουργικότητα SIM"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Οι κλήσεις και οι υπηρεσίες δεδομένων από την <xliff:g id="CARRIER_NAME">%1$s</xliff:g> ενδέχεται να αποκλειστούν κατά τη χρήση του αριθμού <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Κλήσεις και υπηρεσίες δεδομένων από <xliff:g id="CARRIER_NAME">%1$s</xliff:g> ίσως αποκλειστούν κατά τη χρήση άλλης SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Βρέθηκαν και καταργήθηκαν λογαριασμοί SIP, οι οποίοι έχουν καταργηθεί"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Η δυνατότητα κλήσεων SIP δεν υποστηρίζεται πλέον στην πλατφόρμα Android.\nΟι υπάρχοντες λογαριασμοί σας SIP <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> έχουν καταργηθεί.\nΕπιβεβαιώστε την προεπιλεγμένη σας ρύθμιση λογαριασμού κλήσεων."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Μετάβαση στις ρυθμίσεις"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Χρήση δεδομένων εφαρμογής"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> δεδομένων κινητής τηλεφωνίας χρησιμοποιήθηκαν μεταξύ <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Σύνθετες ρυθμίσεις"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Ο κωδικός πρόσβασης είναι λανθασμένος. Η SIM κλειδώθηκε. Καταχωρίστε το PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"Ο κωδικός PUK2 έχει μπλοκαριστεί οριστικά."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Απομένουν <xliff:g id="NUMBER">%d</xliff:g> προσπάθειες."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"Το PUK2 κλειδώθηκε. Επικοινωνήστε με τον πάροχο υπηρεσιών για ξεκλείδωμα."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"Το PIN2 δεν είναι πλέον αποκλεισμένο"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Σφάλμα δικτύου ή κάρτας SIM"</string>
<string name="doneButton" msgid="7371209609238460207">"Τέλος"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Ενεργοποιήστε το δίκτυο κινητής τηλεφωνίας, απενεργοποιήστε τη λειτουργία πτήσης ή τη λειτουργία εξοικονόμησης μπαταρίας, για να πραγματοποιήσετε μια κλήση."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Απενεργοποιήστε τη λειτουργία κλήσης για να πραγματοποιήσετε μια κλήση."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Απενεργοποιήστε τη λειτουργία πτήσης ή συνδεθείτε σε ένα ασύρματο δίκτυο για να πραγματοποιήσετε μια κλήση."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Το τηλέφωνο έχει υπερθερμανθεί"</b>\n\n"Δεν είναι δυνατή η ολοκλήρωση αυτής της κλήσης. Δοκιμάστε ξανά όταν η θερμοκρασία της συσκευής επανέλθει σε φυσιολογικά επίπεδα.\n\nΩστόσο, μπορείτε ακόμα να πραγματοποιείτε κλήσεις έκτακτης ανάγκης."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Πραγματοποιήστε έξοδο από τη λειτουργία επιστροφής κλήσης έκτακτης ανάγκης για να πραγματοποιήσετε μια κλήση μη έκτακτης ανάγκης."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Δεν έχετε εγγραφεί στο δίκτυο."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Το δίκτυο κινητής τηλεφωνίας δεν είναι διαθέσιμο."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId προεπιλεγμένης SIM δεδομένων:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Εύρος ζώνης DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Εύρος ζώνης UL (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Πληροφορίες τοποθεσίας κινητής τηλεφωνίας (καταργήθηκε):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Διαμόρφωση φυσικού καναλιού LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Ρυθμός ανανέωσης στοιχείων κινητής τηλεφωνίας:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Όλα τα στοιχεία μετρήσεων κινητής τηλεφωνίας:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Πληροφορίες τηλεφώνου"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Πληροφορίες παροχής εταιρείας κινητής τηλεφωνίας"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Ενεργοποίηση παροχής εταιρείας κινητής τηλεφωνίας"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Το σήμα bluetooth είναι ασθενές. Δοκιμάστε να αλλάξετε σε ανοιχτή ακρόαση."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Ειδοποίηση ποιότητας κλήσης"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Καταργημένοι λογαριασμοί SIP"</string>
</resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index aacdc53..35999b8 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Voicemail settings can only be modified by the primary user."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Your SIM card has been unblocked. Your phone is unlocking…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM network unlock PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM locked for operator"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Unlock"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Dismiss"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Requesting network unlock…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Forwarding to <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Off"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Your operator doesn\'t support disabling call forwarding when your phone is unreachable."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Your operator doesn\'t support call forwarding."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Turn on call waiting?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"During a call, you\'ll be notified about incoming calls"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Turn on"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Cancel"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA call waiting under IMS on"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA call waiting under IMS off"</string>
<string name="updating_title" msgid="6130548922615719689">"Call settings"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Call settings can only be changed by the admin user."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Settings (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Limited SIM functionality"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> calls and data services may be blocked while using <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> calls and data services may be blocked while using another SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Deprecated SIP accounts found and removed"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP calling is no longer supported by the Android platform.\nYour existing SIP accounts <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> have been removed.\nPlease confirm your default calling account setting."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Go to settings"</string>
<string name="data_usage_title" msgid="8438592133893837464">"App data usage"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> mobile data used <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Advanced"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Password incorrect. SIM now locked. Enter PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 is permanently blocked."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"You have <xliff:g id="NUMBER">%d</xliff:g> remaining attempts."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 locked. Contact service provider to unlock."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 no longer blocked"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Network or SIM card error"</string>
<string name="doneButton" msgid="7371209609238460207">"Done"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Turn on mobile network, turn off airplane mode or turn off battery saver mode to make a call."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Turn off aeroplane mode to make a call."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Turn off aeroplane mode or connect to a wireless network to make a call."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Phone too hot"</b>\n\n"Can\'t complete this call. Try again when your phone cools down.\n\nYou can still make emergency calls."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Exit emergency callback mode to make a non-emergency call."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Not registered on network."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobile network not available."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId of default data SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL bandwidth (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL bandwidth (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Mobile location info (deprecated):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE physical channel configuration:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Mobile info refresh rate:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"All mobile measurement info:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Phone info"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Operator provisioning info"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Trigger operator provisioning"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Your Bluetooth signal is weak. Try switching to speakerphone."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Call quality notification"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Deprecated SIP accounts"</string>
</resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index 16b1df4..2ca6c18 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Voicemail settings can only be modified by the primary user."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Your SIM card has been unblocked. Your phone is unlocking…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM network unlock PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM locked for operator"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Unlock"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Dismiss"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Requesting network unlock…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Forwarding to <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Off"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Your operator doesn\'t support disabling call forwarding when your phone is unreachable."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Your operator doesn\'t support call forwarding."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Turn on call waiting?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"During a call, you\'ll be notified about incoming calls"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Turn on"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Cancel"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA call waiting under IMS on"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA call waiting under IMS off"</string>
<string name="updating_title" msgid="6130548922615719689">"Call settings"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Call settings can only be changed by the admin user."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Settings (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Limited SIM functionality"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> calls and data services may be blocked while using <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> calls and data services may be blocked while using another SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Deprecated SIP accounts found and removed"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP calling is no longer supported by the Android platform.\nYour existing SIP accounts <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> have been removed.\nPlease confirm your default calling account setting."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Go to settings"</string>
<string name="data_usage_title" msgid="8438592133893837464">"App data usage"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> mobile data used <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Advanced"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Password incorrect. SIM now locked. Enter PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 is permanently blocked."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"You have <xliff:g id="NUMBER">%d</xliff:g> remaining attempts."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 locked. Contact service provider to unlock."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 no longer blocked"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Network or SIM card error"</string>
<string name="doneButton" msgid="7371209609238460207">"Done"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Turn on mobile network, turn off airplane mode or turn off battery saver mode to make a call."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Turn off Airplane mode to make a call."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Turn off Airplane mode or connect to a wireless network to make a call."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Phone too hot"</b>\n\n"Can\'t complete this call. Try again when your phone cools down.\n\nYou can still make emergency calls."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Exit emergency callback mode to make a non-emergency call."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Not registered on network."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobile network not available."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId of default data SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL bandwidth (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL bandwidth (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Mobile location info (deprecated):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE physical channel configuration:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Mobile info refresh rate:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"All mobile measurement info:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Phone info"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Operator provisioning info"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Trigger operator provisioning"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Your Bluetooth signal is weak. Try switching to speakerphone."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Call quality notification"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Deprecated SIP accounts"</string>
</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index aacdc53..35999b8 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Voicemail settings can only be modified by the primary user."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Your SIM card has been unblocked. Your phone is unlocking…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM network unlock PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM locked for operator"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Unlock"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Dismiss"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Requesting network unlock…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Forwarding to <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Off"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Your operator doesn\'t support disabling call forwarding when your phone is unreachable."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Your operator doesn\'t support call forwarding."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Turn on call waiting?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"During a call, you\'ll be notified about incoming calls"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Turn on"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Cancel"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA call waiting under IMS on"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA call waiting under IMS off"</string>
<string name="updating_title" msgid="6130548922615719689">"Call settings"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Call settings can only be changed by the admin user."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Settings (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Limited SIM functionality"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> calls and data services may be blocked while using <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> calls and data services may be blocked while using another SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Deprecated SIP accounts found and removed"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP calling is no longer supported by the Android platform.\nYour existing SIP accounts <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> have been removed.\nPlease confirm your default calling account setting."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Go to settings"</string>
<string name="data_usage_title" msgid="8438592133893837464">"App data usage"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> mobile data used <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Advanced"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Password incorrect. SIM now locked. Enter PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 is permanently blocked."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"You have <xliff:g id="NUMBER">%d</xliff:g> remaining attempts."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 locked. Contact service provider to unlock."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 no longer blocked"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Network or SIM card error"</string>
<string name="doneButton" msgid="7371209609238460207">"Done"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Turn on mobile network, turn off airplane mode or turn off battery saver mode to make a call."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Turn off aeroplane mode to make a call."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Turn off aeroplane mode or connect to a wireless network to make a call."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Phone too hot"</b>\n\n"Can\'t complete this call. Try again when your phone cools down.\n\nYou can still make emergency calls."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Exit emergency callback mode to make a non-emergency call."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Not registered on network."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobile network not available."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId of default data SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL bandwidth (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL bandwidth (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Mobile location info (deprecated):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE physical channel configuration:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Mobile info refresh rate:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"All mobile measurement info:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Phone info"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Operator provisioning info"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Trigger operator provisioning"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Your Bluetooth signal is weak. Try switching to speakerphone."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Call quality notification"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Deprecated SIP accounts"</string>
</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index aacdc53..35999b8 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Voicemail settings can only be modified by the primary user."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Your SIM card has been unblocked. Your phone is unlocking…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM network unlock PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM locked for operator"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Unlock"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Dismiss"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Requesting network unlock…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Forwarding to <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Off"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Your operator doesn\'t support disabling call forwarding when your phone is unreachable."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Your operator doesn\'t support call forwarding."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Turn on call waiting?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"During a call, you\'ll be notified about incoming calls"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Turn on"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Cancel"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA call waiting under IMS on"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA call waiting under IMS off"</string>
<string name="updating_title" msgid="6130548922615719689">"Call settings"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Call settings can only be changed by the admin user."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Settings (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Limited SIM functionality"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> calls and data services may be blocked while using <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> calls and data services may be blocked while using another SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Deprecated SIP accounts found and removed"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP calling is no longer supported by the Android platform.\nYour existing SIP accounts <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> have been removed.\nPlease confirm your default calling account setting."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Go to settings"</string>
<string name="data_usage_title" msgid="8438592133893837464">"App data usage"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> mobile data used <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Advanced"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Password incorrect. SIM now locked. Enter PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 is permanently blocked."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"You have <xliff:g id="NUMBER">%d</xliff:g> remaining attempts."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 locked. Contact service provider to unlock."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 no longer blocked"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Network or SIM card error"</string>
<string name="doneButton" msgid="7371209609238460207">"Done"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Turn on mobile network, turn off airplane mode or turn off battery saver mode to make a call."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Turn off aeroplane mode to make a call."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Turn off aeroplane mode or connect to a wireless network to make a call."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Phone too hot"</b>\n\n"Can\'t complete this call. Try again when your phone cools down.\n\nYou can still make emergency calls."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Exit emergency callback mode to make a non-emergency call."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Not registered on network."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobile network not available."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId of default data SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL bandwidth (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL bandwidth (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Mobile location info (deprecated):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE physical channel configuration:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Mobile info refresh rate:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"All mobile measurement info:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Phone info"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Operator provisioning info"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Trigger operator provisioning"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Your Bluetooth signal is weak. Try switching to speakerphone."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Call quality notification"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Deprecated SIP accounts"</string>
</resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 91835c5..04094fb 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Voicemail Settings can only be modified by the Primary User."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Your SIM card has been unblocked. Your phone is unlocking…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM network unlock PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM locked for operator"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Unlock"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Dismiss"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Requesting network unlock…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Forwarding to <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Off"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Your carrier doesn\'t support disabling call forwarding when your phone is unreachable."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Your carrier doesn\'t support call forwarding."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Turn on call waiting?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"During a call, you\'ll be notified about incoming calls"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Turn on"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Cancel"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA Call Waiting under IMS On"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA Call Waiting under IMS Off"</string>
<string name="updating_title" msgid="6130548922615719689">"Call settings"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Call settings can only be changed by the admin user."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Settings (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Limited SIM functionality"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> calls and data services may be blocked while using <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> calls and data services may be blocked while using another SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Deprecated SIP accounts found and removed"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP calling is no longer supported by Android platform.\nYour existing SIP accounts <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> have been removed.\nPlease confirm your default calling account setting."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Go to settings"</string>
<string name="data_usage_title" msgid="8438592133893837464">"App data usage"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> mobile data used <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Advanced"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Password incorrect. SIM now locked. Enter PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 is permanently blocked."</string>
<string name="pin2_attempts" msgid="5625178102026453023">"\nYou have <xliff:g id="NUMBER">%d</xliff:g> remaining attempts."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 locked. Contact service provider to unlock."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 no longer blocked"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Network or SIM card error"</string>
<string name="doneButton" msgid="7371209609238460207">"Done"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Turn on mobile network, turn off airplane mode or turn off battery saver mode to make a call."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Turn off airplane mode to make a call."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Turn off airplane mode or connect to a wireless network to make a call."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168">""<b>"Phone too hot"</b>"\n\nCan\'t complete this call. Try again when your phone cools down.\n\nYou can still make emergency calls."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Exit emergency callback mode to make a non-emergency call."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Not registered on network."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobile network not available."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId of default data SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL Bandwidth (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL Bandwidth (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Cell Location Info (deprecated):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE Physical Channel Configuration:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Cell Info Refresh Rate:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"All Cell Measurement Info:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Phone info"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Carrier Provisioning Info"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Trigger Carrier Provisioning"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Your bluetooth signal is weak. Try switching to speakerphone."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Call Quality Notification"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Deprecated SIP accounts"</string>
</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 1c35a25..c4c0ea2 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Solo el usuario principal puede cambiar la configuración del buzón de voz."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Tu tarjeta SIM ha sido desbloqueada. Tu dispositivo está desbloqueando..."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN de desbloqueo de red de tarjeta SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM bloqueada para el operador"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Desbloquear"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Descartar"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Solicitando desbloqueo de red..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Reenviar a <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Desactivado"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Tu proveedor no admite la inhabilitación del desvío de llamadas cuando no se puede acceder a tu teléfono."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Tu proveedor no admite el desvío de llamadas."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"¿Quieres activar la función de llamada en espera?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Durante una llamada, recibirás notificaciones de llamadas entrantes"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Activar"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Cancelar"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"La función de llamada en espera de CDMA en IMS está activada"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"La función de llamada en espera de CDMA en IMS está desactivada"</string>
<string name="updating_title" msgid="6130548922615719689">"Config. de llamada"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Solo el usuario administrador puede cambiar la configuración de llamadas."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Configuración (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Funcionalidad de SIM limitada"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Los servicios de llamadas y datos de <xliff:g id="CARRIER_NAME">%1$s</xliff:g> pueden bloquearse mientras uses el número <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Las llamadas y los servicios de datos de <xliff:g id="CARRIER_NAME">%1$s</xliff:g> podrían bloquearse al usar otra SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Se encontraron y se quitaron cuentas SIP obsoletas"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"La plataforma de Android ya no admite las llamadas SIP.\nSe quitaron las cuentas SIP <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> existentes.\nConfirma la configuración predeterminada de tu cuenta para las llamadas SIP."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Ir a Configuración"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Uso de datos de la app"</string>
<string name="data_usage_template" msgid="6287906680674061783">"Datos móviles usados: <xliff:g id="ID_1">%1$s</xliff:g>, <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Avanzada"</string>
@@ -504,9 +515,10 @@
<string name="puk2_requested" msgid="6992374450720307514">"Contraseña incorrecta. SIM bloqueada. Ingresa el PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"El código PUK2 se bloqueó de forma permanente."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Te quedan <xliff:g id="NUMBER">%d</xliff:g> intentos."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"SIM bloqueada con PUK2. Comunícate con el proveedor para desbloquearla."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"El PIN2 ya no está bloqueado."</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Error en la red o en la tarjeta SIM"</string>
- <string name="doneButton" msgid="7371209609238460207">"Finalizado"</string>
+ <string name="doneButton" msgid="7371209609238460207">"Listo"</string>
<string name="voicemail_settings_number_label" msgid="1265118640154688162">"Número de buzón de voz"</string>
<string name="card_title_dialing" msgid="8742182654254431781">"Marcando"</string>
<string name="card_title_redialing" msgid="18130232613559964">"Volviendo a marcar"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Activa la red móvil y desactiva el modo de avión o el modo de ahorro de batería para realizar una llamada."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Desactivar modo de avión para hacer una llamada"</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Desactivar el modo de avión o conectarse a una red inalámbrica para hacer una llamada"</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Se sobrecalentó el teléfono"</b>\n\n"No se puede completar esta llamada. Vuelve a intentar cuando se enfríe el teléfono.\n\nDe todos modos, puedes hacer llamadas de emergencia."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Para realizar una llamada que no sea de emergencia, sal del modo de devolución de llamada de emergencia."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"No registrado en la red."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"La red móvil no está disponible."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ID secundario de SIM de datos predeterminada:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Ancho de banda de descarga (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Ancho de banda de carga (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Datos de ubicación del celular (obsoleto):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuración del canal físico de LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Frecuencia de actualización de datos del celular:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Información sobre las dimensiones del celular:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Información del teléfono"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Información del aprovisionamiento del proveedor"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Activar aprovisionamiento del proveedor"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Tu señal de Bluetooth es débil. Intenta cambiar al altavoz."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Notificación de calidad de llamada"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Cuentas SIP obsoletas"</string>
</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 1b5b897..a106ba2 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"El usuario principal es el único que puede modificar los ajustes del buzón de voz."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"La tarjeta SIM se ha desbloqueado. El teléfono se está desbloqueando..."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN de desbloqueo de red de tarjeta SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM bloqueada por el operador"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Desbloquear"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Cerrar"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Solicitando desbloqueo de red..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Desviando a <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Desactivado"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Tu operador no permite inhabilitar el desvío de llamadas si no se puede establecer la llamada."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Tu operador no admite el desvío de llamadas."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"¿Quieres activar la llamada en espera?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Se te notificarán las llamadas entrantes mientras estés en una llamada"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Activar"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Cancelar"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Llamada en espera de CDMA en IMS activada"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Llamada en espera de CDMA en IMS desactivada"</string>
<string name="updating_title" msgid="6130548922615719689">"Ajustes de llamadas"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"El administrador es el único usuario que puede cambiar los ajustes de llamada."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Ajustes (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Funcionalidad de SIM limitada"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Puede los servicios de datos y llamadas de <xliff:g id="CARRIER_NAME">%1$s</xliff:g> se bloqueen mientras se utilice el <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Puede que los servicios de datos y llamadas de <xliff:g id="CARRIER_NAME">%1$s</xliff:g> se bloqueen al usar otra SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Cuentas SIP obsoletas encontradas y eliminadas"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"La plataforma Android ya no admite llamadas SIP.\nTus cuentas SIP (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>) se han eliminado.\nConfirma la configuración predeterminada de tu cuenta de llamadas."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Ir a Ajustes"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Uso de datos de la aplicación"</string>
<string name="data_usage_template" msgid="6287906680674061783">"Se han usado <xliff:g id="ID_1">%1$s</xliff:g> en el periodo del <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Ajustes avanzados"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Contraseña incorrecta. SIM bloqueada. Introducir PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"El PUK2 se ha bloqueado de forma permanente."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Quedan <xliff:g id="NUMBER">%d</xliff:g> intentos."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 bloqueado. Ponte en contacto con tu proveedor de servicios para desbloquearlo."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 desbloqueado"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Error en la tarjeta SIM o en la red"</string>
<string name="doneButton" msgid="7371209609238460207">"Hecho"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Activa la red móvil o desactiva el modo avión o el modo de ahorro de batería para hacer una llamada."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Desactiva el modo avión parra hacer llamadas."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Desactiva el modo avión o conéctate a una red inalámbrica para hacer llamadas."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"El teléfono está demasiado caliente"</b>\n\n"No se ha podido completar esta llamada. Vuelve a intentarlo cuando el teléfono se enfríe.\n\nNo obstante, puedes hacer llamadas de emergencia."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Sal del modo de devolución de llamada de emergencia para hacer otro tipo de llamada."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"No se ha podido conectar a la red"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"La red móvil no está disponible."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ID secundario de la SIM de datos predeterminada:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Ancho de banda de bajada (Kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Ancho de banda de subida (Kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Información sobre la ubicación del teléfono (obsoleto):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuración del canal físico de LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Frecuencia de actualización de la información del teléfono:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Información sobre las dimensiones de los teléfonos:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Información sobre el teléfono"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Información de servicios del operador"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Activar servicios del operador"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Tu señal de Bluetooth es débil. Prueba a cambiar al altavoz."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Notificación de calidad de la llamada"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Cuentas SIP obsoletas"</string>
</resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 50e54e5..076160a 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Kõneposti seadeid saab muuta ainult peamine kasutaja."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Teie SIM-kaardi blokeering on tühistatud. Teie telefoni avamine ..."</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM-võrgu avamise PIN-kood"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM-kaart on operaatori jaoks lukustatud"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Ava"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Loobu"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Võrgu avamise taotlemine ..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Edastamine numbrile <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Väljas"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Teie operaator ei toeta kõneedastuse keelamist, kui teie telefon on levist väljas."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Teie operaator ei toeta kõnede suunamist."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Kas lülitada ootel kõne sisse?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Kõne ajal teavitatakse teid sissetulevatest kõnedest"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Lülita sisse"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Tühista"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA ootel kõne on IMS-i all sisse lülitatud"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA ootel kõne on IMS-i all välja lülitatud"</string>
<string name="updating_title" msgid="6130548922615719689">"Kõneseaded"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Kõne seadeid saab muuta ainult administraator."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Seaded (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"SIM-i piiratud funktsionaalsus"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Operaatori <xliff:g id="CARRIER_NAME">%1$s</xliff:g> kõned ja andmesideteenused võivad numbri <xliff:g id="PHONE_NUMBER">%2$s</xliff:g> kasutamise ajal olla blokeeritud."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>: kõned ja andmesideteenused võivad teise SIM-i kasutamisel olla blokeeritud."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Leiti katkestatud toega SIP-kontod ja need eemaldati"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Androidi platvorm ei toeta enam SIP-kõnesid.\nTeie olemasolevad SIP-kontod <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> on eemaldatud.\nKinnitage oma helistamise vaikekonto seadistus."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Ava seaded"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Rakenduste andmekasutus"</string>
<string name="data_usage_template" msgid="6287906680674061783">"Vahemikus <xliff:g id="ID_2">%2$s</xliff:g> on kasutatud <xliff:g id="ID_1">%1$s</xliff:g> mobiilset andmesidet"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Täpsemad"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Parool on vale. SIM on nüüd lukus. Sisestage PUK-kood 2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 on püsivalt blokeeritud."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Teil on veel <xliff:g id="NUMBER">%d</xliff:g> katset."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 on lukustatud. Avamiseks võtke ühendust teenusepakkujaga."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN-kood 2 ei ole enam blokeeritud"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Võrgu või SIM-kaardi viga"</string>
<string name="doneButton" msgid="7371209609238460207">"Valmis"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Helistamiseks lülitage mobiilsidevõrk sisse, lennu- või akusäästjarežiim välja."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Helistamiseks lülitage lennurežiim välja."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Helistamiseks lülitage lennurežiim välja või looge ühendus traadita võrguga."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefon on liiga kuum"</b>\n\n"Kõnet ei saa teha. Proovige uuesti, kui telefon on jahtunud.\n\nSaate endiselt teha hädaabikõnesid."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Muude kui hädaabikõne tegemiseks väljuge hädaabikõnede režiimist."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Ei ole võrku registreeritud."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobiilsidevõrk pole saadaval."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Andmete vaike-SIM-kaardi alam-ID:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL-i ribalaius (kbit/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL-i ribalaius (kbit/s):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Kärje asukohateave (tugi on katkestatud):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE füüsilise kanali konfiguratsioon:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Kärje teabe värskendamissagedus:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Kõik kärje mõõteandmed:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Telefoniteave"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Operaatori ettevalmistamise teave"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Operaatori ettevalmistamise päästik"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Teie Bluetoothi signaal on nõrk. Lülitage valjuhääldile."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Kõnekvaliteedi märguanne"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Katkestatud toega SIP-kontod"</string>
</resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index f468af6..85f4776 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Erabiltzaile nagusiak bakarrik alda ditzake erantzungailuaren ezarpenak."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"SIM txartela desblokeatu da. Telefonoa desblokeatzen…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIMaren sarea desblokeatzeko PINa"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"Operadore hau dauka finkatuta SIM txartelak:"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Desblokeatu"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Baztertu"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Sarea desblokeatzeko eskatzen…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> zenbakira desbideratzen dira"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Desaktibatuta"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Operadoreak ez du dei-desbideratzeak desgaitzea onartzen telefonoa eskuragarri ez dagoen bitartean."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Operadoreak ez du onartzen dei-desbideratzea."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Deia zain uzteko aukera aktibatu nahi duzu?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Dei batean zaudenean, jakinarazpen bat jasoko duzu deiak jasotzen badituzu"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Aktibatu"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Utzi"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS zerbitzupean CDMA deiak zain uzteko aukera aktibatuta dago"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS zerbitzupean CDMA deiak zain uzteko aukera desaktibatuta dago"</string>
<string name="updating_title" msgid="6130548922615719689">"Deien ezarpenak"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Administratzaileak soilik alda ditzake deien ezarpenak."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Ezarpenak (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"SIMaren funtzionaltasuna mugatua da"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Baliteke <xliff:g id="CARRIER_NAME">%1$s</xliff:g> bidezko deiak eta datu-zerbitzuak blokeatuta egotea <xliff:g id="PHONE_NUMBER">%2$s</xliff:g> telefono-zenbakia erabiltzean."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Baliteke <xliff:g id="CARRIER_NAME">%1$s</xliff:g> bidezko deiak eta datu-zerbitzuak blokeatuta egotea beste SIM txartel bat erabiltzean."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"SIP-eko kontu zaharkituak aurkitu eta kendu dira"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP bidezko deiak jadanik ez dira bateragarriak Android-en plataformarekin.\nZeneuzkan SIP-eko kontuak (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>) kendu egin dira.\nBerretsi deietarako ezarri duzun kontu lehenetsia."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Joan ezarpenetara"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Aplikazioak erabilitako datuak"</string>
<string name="data_usage_template" msgid="6287906680674061783">"Datuen <xliff:g id="ID_1">%1$s</xliff:g> erabili dira data hauen artean: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Aurreratuak"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Pasahitza ez da zuzena. SIM txartela blokeatu egin da. Idatzi PUK2 kodea."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 kodea betiko blokeatu da."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"<xliff:g id="NUMBER">%d</xliff:g> saiakera gelditzen zaizkizu."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 kodearekin blokeatuta dago SIM txartela. Desblokeatzeko, jarri operadorearekin harremanetan."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 kodea desblokeatu da"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Sarearen edo SIM txartelaren errorea"</string>
<string name="doneButton" msgid="7371209609238460207">"Eginda"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Deitzeko, aktibatu sare mugikorra, desaktibatu hegaldi modua edo desaktibatu bateria-aurrezlea."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Deia egiteko, desaktibatu hegaldi modua."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Deia egiteko, desaktibatu hegaldi modua edo konektatu haririk gabeko sare batera."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefonoa beroegi dago"</b>\n\n"Ezin da egin deia. Saiatu berriro telefonoa hozten denean.\n\nHala ere, larrialdi-deiak egin ditzakezu."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Larrialdikoak ez diren deiak egiteko, irten larrialdi-zerbitzuen deiak jasotzeko modutik."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Ez dago sarean erregistratuta."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Sare mugikorra ez dago erabilgarri."</string>
@@ -608,7 +621,7 @@
<string name="ota_skip_activation_dialog_skip_label" msgid="5908029466817825633">"Saltatu"</string>
<string name="ota_activate" msgid="7939695753665438357">"Aktibatu"</string>
<string name="ota_title_activate_success" msgid="1272135024761004889">"Telefonoa aktibatuta dago."</string>
- <string name="ota_title_problem_with_activation" msgid="7019745985413368726">"Arazo bat gertatu da aktibazioarekin"</string>
+ <string name="ota_title_problem_with_activation" msgid="7019745985413368726">"Arazo bat izan da aktibazioarekin"</string>
<string name="ota_listen" msgid="2772252405488894280">"Jarraitu hitzezko argibideei aktibazioa osatu dela entzun arte."</string>
<string name="ota_speaker" msgid="1086766980329820528">"Bozgorailua"</string>
<string name="ota_progress" msgid="8837259285255700132">"Telefonoa programatzen…"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Datu-konexioetarako SIM lehenetsiaren azpiIDa:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Deskargatzeko banda-zabalera (Kb/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Kargen banda-zabalera (Kb/s):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Sare mugikorraren kokapenaren informazioa (zaharkitua):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE kanal fisikoaren konfigurazioa:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Sare mugikorraren informazioa eguneratzeko maiztasuna:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Sare mugikorraren neurketa guztien informazioa:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Telefonoaren informazioa"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Operadorearen hornikuntza-informazioa"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Abiarazi operadorearen hornikuntza"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Bluetooth seinalea ahula da. Erabili telefonoko bozgorailua."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Deien kalitateari buruzko jakinarazpena"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"SIP-eko kontu zaharkituak"</string>
</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 56059c8..b310640 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"فقط «کاربر اصلی» میتواند «تنظیمات پست صوتی» را تغییر دهد."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"قفل سیم کارت شما باز شده است. قفل گوشی در حال باز شدن است..."</string>
<string name="label_ndp" msgid="7617392683877410341">"پین بازگشایی قفل شبکه سیم"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"سیمکارت برای اپراتور قفل شد"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"بازگشایی قفل"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"رد کردن"</string>
<string name="requesting_unlock" msgid="930512210309437741">"در حال درخواست بازگشایی قفل شبکه..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"هدایت به <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"خاموش"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"شرکت مخابراتی شما از غیرفعال کردن هدایت تماس هنگامی که تلفن شما در دسترس نیست پشتیبانی نمیکند."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"شرکت مخابراتی شما از هدایت تماس پشتیبانی نمیکند."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"«انتظار مکالمه» روشن شود؟"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"درحین تماس، از تماسهای ورودی مطلع خواهید شد"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"روشن کردن"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"لغو"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"«انتظار مکالمه CDMA» تحت IMS روشن است"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"«انتظار مکالمه CDMA» تحت IMS خاموش است"</string>
<string name="updating_title" msgid="6130548922615719689">"تنظیمات تماس"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"فقط کاربر سرپرست میتواند تنظیمات تماس را تغییر دهد."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"تنظیمات (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"عملکرد محدود سیمکارت"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"ممکن است درحین استفاده از <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>، سرویسهای داده و تماس <xliff:g id="CARRIER_NAME">%1$s</xliff:g> مسدود شوند."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"ممکن است درحین استفاده از سیمکارت دیگر، سرویسهای داده و تماس <xliff:g id="CARRIER_NAME">%1$s</xliff:g> مسدود شوند."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"حسابهای SIP منسوخشده پیدا و برداشته شدند"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"تماس SIP دیگر در پلاتفرم Android پشتیبانی نمیشود.\nحسابهای SIP موجود شما (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>) برداشته شدند.\nلطفاً تنظیم حساب تماس پیشفرض را تأیید کنید."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"رفتن به تنظیمات"</string>
<string name="data_usage_title" msgid="8438592133893837464">"مصرف داده برنامه"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> داده تلفن همراه مصرفشده در <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"پیشرفته"</string>
@@ -306,7 +317,7 @@
<string name="throttle_current_usage" msgid="7483859109708658613">"دادههای مورد استفاده در دوره زمانی فعلی"</string>
<string name="throttle_time_frame" msgid="1813452485948918791">"مدت زمان مصرف داده"</string>
<string name="throttle_rate" msgid="7641913901133634905">"قانون نرخ داده"</string>
- <string name="throttle_help" msgid="2624535757028809735">"بیشتر بیاموزید"</string>
+ <string name="throttle_help" msgid="2624535757028809735">"بیشتر بدانید"</string>
<string name="throttle_status_subtext" msgid="1110276415078236687">"<xliff:g id="USED_0">%1$s</xliff:g> (<xliff:g id="USED_1">%2$d</xliff:g>٪) از <xliff:g id="USED_2">%3$s</xliff:g> حداکثر مدت زمان\nمدت زمان بعدی در فاصله <xliff:g id="USED_3">%4$d</xliff:g> روز (<xliff:g id="USED_4">%5$s</xliff:g>) شروع میشود."</string>
<string name="throttle_data_usage_subtext" msgid="3185429653996709840">"<xliff:g id="USED_0">%1$s</xliff:g> (<xliff:g id="USED_1">%2$d</xliff:g>٪) از حداکثر مدت زمان <xliff:g id="USED_2">%3$s</xliff:g>"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"<xliff:g id="USED_0">%1$s</xliff:g> بیش از حداکثر مجاز\nنرخ داده به <xliff:g id="USED_1">%2$d</xliff:g> کیلو بیت در ثانیه کاهش یافت"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"گذرواژه نادرست است. اکنون سیمکارت قفل شده است. PUK2 را وارد کنید."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 به صورت دائمی مسدود شده است."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"<xliff:g id="NUMBER">%d</xliff:g> تلاش باقیمانده دارید."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 قفل است. برای باز کردن قفل، با ارائه دهنده خدمات تماس بگیرید."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"پین۲ دیگر مسدود نیست"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"خطای شبکه یا سیمکارت"</string>
<string name="doneButton" msgid="7371209609238460207">"تمام"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"برای برقراری تماس، شبکه تلفن همراه را روشن کنید، حالت هواپیما یا «بهینهسازی باتری» را غیرفعال کنید."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"برای برقراری تماس، حالت پرواز را خاموش کنید."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"برای برقرای تماس، حالت پرواز را خاموش کنید یا به شبکه بیسیم وصل شوید."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"تلفن خیلی داغ است"</b>\n\n"نمیتوان این تماس را بهپایان رساند. وقتی تلفن خنک شد، دوباره امتحان کنید.\n\nهمچنان میتوانید تماسهای اضطراری برقرار کنید."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"برای برقراری تماس غیراضطراری از حالت پاسخ تماس اضطراری خارج شوید."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"در شبکه ثبت نشده است."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"شبکهٔ تلفن همراه موجود نیست."</string>
@@ -867,9 +880,8 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId مربوط به سیمکارت داده پیشفرض:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"پهنای باند DL (کیلوبیت بر ثانیه):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"پهنای باند UL (کیلوبیت بر ثانیه):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"اطلاعات مکان شبکه همراه (منسوخ شده):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"پیکربندی کانال فیزیکی LTE:"</string>
- <string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"نرخ بازخوانی اطلاعات شبکه همراه:"</string>
+ <string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"نرخ بازآوری اطلاعات شبکه همراه:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"همه اطلاعات اندازهگیری شبکه همراه:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"سرویس داده:"</string>
<string name="radio_info_roaming_label" msgid="6636932886446857120">"فراگردی:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"اطلاعات تلفن"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"اطلاعات ارائه شرکت مخابراتی"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"شروع ارائه شرکت مخابراتی"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"سیگنال بلوتوث شما ضعیف است. از بلندگوی تلفن استفاده کنید."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"اعلان کیفیت تماس"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"حسابهای SIP منسوخشده"</string>
</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index dd192ec..34d40a8 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Vain ensisijainen käyttäjä voi muuttaa vastaajan asetuksia."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"SIM-korttisi esto on purettu. Puhelimen lukitusta poistetaan..."</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM-verkko, PIN-koodin lukituksen poisto"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"Operaattorin SIM lukittu"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Poista lukitus"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Hylkää"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Pyydetään verkon lukituksen poistoa..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Puhelu siirretään numeroon <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Ei päällä"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Operaattorisi ei tue soitonsiirtojen poistamista käytöstä puhelimesi ollessa saavuttamattomissa."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Operaattorisi ei tue soitonsiirtoa."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Otetaanko koputus käyttöön?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Sinulle ilmoitetaan puhelun aikana saapuvista puheluista"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Ota käyttöön"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Peru"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS:n alainen CDMA-koputus pois käytöstä"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS:n alainen CDMA-koputus pois käytöstä"</string>
<string name="updating_title" msgid="6130548922615719689">"Puheluasetukset"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Vain järjestelmänvalvoja voi muuttaa puheluasetuksia."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Asetukset (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Rajoituksia SIM-kortin toiminnassa"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Operaattorin <xliff:g id="CARRIER_NAME">%1$s</xliff:g> puhelut ja datapalvelut saatetaan estää, kun puhelinnumero <xliff:g id="PHONE_NUMBER">%2$s</xliff:g> on käytössä."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Operaattorin <xliff:g id="CARRIER_NAME">%1$s</xliff:g> puhelut ja datapalvelut voidaan estää, kun käytössä on toinen SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Käytöstä poistettuja SIP-tilejä löytyi ja ne poistettiin"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Android-alusta ei enää tue SIP-puheluja.\nAiemmat SIP-tilit <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> on poistettu.\nVahvista oletusarvoisen puhelutilin asetus."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Avaa asetukset"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Sovelluksen datan käyttö"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> mobiilidataa käytetty <xliff:g id="ID_2">%2$s</xliff:g>."</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Lisäasetukset"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Virheellinen salasana. SIM-kortti on lukittu. Anna PUK2-koodi."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2-koodi on pysyvästi estetty."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"<xliff:g id="NUMBER">%d</xliff:g> yritystä jäljellä."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 lukittu. Ota yhteyttä palveluntarjoajaan lukituksen avaamiseksi."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2-koodi ei ole enää estetty"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Verkko- tai SIM-korttivirhe"</string>
<string name="doneButton" msgid="7371209609238460207">"Valmis"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Ota mobiiliverkko käyttöön tai poista lentokonetila ja virransäästötila käytöstä, jos haluat soittaa puhelun."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Poista lentokonetila käytöstä, jos haluat soittaa puhelun."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Poista lentokonetila käytöstä tai yhdistä langattomaan verkkoon, jos haluat soittaa puhelun."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Liian kuuma puhelin"</b>\n\n"Puhelua ei voida soittaa. Yritä uudelleen, kun puhelin on jäähtynyt.\n\nVoit silti soittaa hätäpuheluita."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Poistu hätäpuhelujen takaisinsoittotilasta soittaaksesi muun kuin hätäpuhelun."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Ei rekisteröity verkkoon."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobiiliverkko ei käytettävissä."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Oletusarvoisen data-SIM-kortin alitunnus:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL-kaistanleveys (kt/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL-kaistanleveys (kt/s):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Matkapuhelimen sijaintitiedot (poistettu käytöstä):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Fyysisen LTE-kanavan kokoonpano:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Matkapuhelintietojen päivitysaikaväli:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Matkapuhelimen kaikki mittaustiedot:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Puhelimen tiedot"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Operaattorin hallintatiedot"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Ota operaattorin hallinta käyttöön"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Bluetooth-signaali on heikko. Kokeile vaihtaa kaiutinpuhelimeen."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Puhelun laatua koskeva ilmoitus"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Käytöstä poistetut SIP-tilit"</string>
</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index f1f9ce4..3236666 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Seul l\'utilisateur principal peut modifier les paramètres de la messagerie vocale."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Votre carte SIM a été déverrouillée. Votre téléphone est en cours de déverrouillage..."</string>
<string name="label_ndp" msgid="7617392683877410341">"NIP de déblocage du réseau SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"Carte SIM verrouillée pour le fournisseur de services :"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Déverrouiller"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Ignorer"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Demande de déblocage du réseau…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Transfert vers <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Désactivé"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Votre opérateur ne permet pas la désactivation du transfert d\'appel lorsque votre téléphone n\'est pas joignable."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Votre fournisseur de services ne prend pas en charge le transfert d\'appels."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Activer la mise en attente d\'appels?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Vous serez notifié des appels entrant pendant les appels."</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Activer"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Annuler"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Mise en attente d\'appels CDMA sous IMS activée"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Mise en attente d\'appels CDMA sous IMS désactivée"</string>
<string name="updating_title" msgid="6130548922615719689">"Paramètres d\'appel"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Seul l\'administrateur peut modifier les paramètres d\'appel."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Paramètres (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Fonctionnalité de carte SIM limitée"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Les services d\'appels et de données de <xliff:g id="CARRIER_NAME">%1$s</xliff:g> peuvent être bloqués lors de l\'utilisation du numéro <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Les données et appels de <xliff:g id="CARRIER_NAME">%1$s</xliff:g> peuvent être bloqués si vous utilisez une autre SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Comptes SIP obsolètes trouvés et retirés"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Les appels SIP ne sont plus pris en charge par la plateforme Android.\nVos comptes SIP <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> existants ont été retirés.\nVeuillez confirmer votre paramètre de compte d\'appel par défaut."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Accéder aux paramètres"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Utilisation des données des applications"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> de données mobiles utilisées pendant la période suivante : <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Avancés"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Le mot de passe est erroné. La carte SIM a été verrouillée. Entrez le code PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"Le code PUK2 est bloqué définitivement."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Il vous reste <xliff:g id="NUMBER">%d</xliff:g> tentatives."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"Verrouillage PUK2. Communiquez avec le fournisseur de services pour procéder au déverrouillage."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"Le NIP2 n\'est plus bloqué."</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Erreur de réseau ou de carte SIM"</string>
<string name="doneButton" msgid="7371209609238460207">"Terminé"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Activez le réseau cellulaire ou désactivez le mode Avion ou le mode Économiseur de pile pour faire un appel."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Désactivez le mode Avion pour faire un appel."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Désactivez le mode Avion ou connectez-vous à un réseau Wi-Fi pour faire un appel."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Le téléphone est trop chaud"</b>\n\n"Impossible de passer cet appel. Réessayez plus tard quand le téléphone aura refroidi.\n\nVous pouvez toujours effectuer des appels d\'urgence."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Quittez le mode de rappel d\'urgence pour effectuer un appel non urgent."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Non enregistré sur le réseau"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Réseau pour mobile non disponible"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Sous-identifiant de la carte SIM par défaut :"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Bande passante de téléchargement (kb/s) :"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Bande passante de téléversement (kb/s) :"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Données de la position de la cellule (obsolètes) :"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuration du canal physique LTE :"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Taux d\'actualisation des données de la cellule :"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Données des mesures de toutes les cellules :"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Informations sur le téléphone"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Données d\'approvisionnement du fournisseur de services"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Déclencher l\'approvisionnement du fournisseur de services"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Votre signal Bluetooth est faible. Essayez de passer au haut-parleur mains libres."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Notification de qualité d\'appel"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Comptes SIP obsolètes"</string>
</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 597fdbd..957e361 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Seul l\'utilisateur principal de la messagerie vocale peut en modifier les paramètres."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Votre carte SIM a été déverrouillée. Votre téléphone est en cours de déverrouillage..."</string>
<string name="label_ndp" msgid="7617392683877410341">"Code PIN de déblocage du réseau SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"Carte SIM verrouillée par l\'opérateur"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Déverrouiller"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Rejeter"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Demande de déblocage du réseau…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Transfert vers <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Désactivé"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Votre opérateur ne permet pas la désactivation du transfert d\'appel lorsque votre téléphone n\'est pas joignable."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Votre opérateur ne permet pas le transfert des appels."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Activer la fonctionnalité d\'appel en attente ?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Lorsque vous êtes en communication, des notifications vous signalent les appels entrants"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Activer"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Annuler"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Fonctionnalité d\'appel CDMA en attente sous IMS activée"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Fonctionnalité d\'appel CDMA en attente sous IMS désactivée"</string>
<string name="updating_title" msgid="6130548922615719689">"Paramètres d\'appel"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Seul l\'administrateur peut modifier les paramètres d\'appel."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Paramètres (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Fonction de la carte SIM limitée"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Les appels et les services de données disponibles avec <xliff:g id="CARRIER_NAME">%1$s</xliff:g> sont susceptibles d\'être bloqués lorsque vous utilisez le numéro <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Les appels et services de données <xliff:g id="CARRIER_NAME">%1$s</xliff:g> peuvent être bloqués avec une deuxième SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Des comptes SIP obsolètes ont été trouvés et supprimés"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Les appels SIP ne sont plus compatibles avec la plate-forme Android.\nVos comptes SIP existants, <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>, ont été supprimés.\nVeuillez confirmer le compte utilisé par défaut pour passer les appels."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Accéder aux paramètres"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Consommation des données par les applications"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> de données mobiles utilisées pendant la période suivante : <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Paramètres avancés"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Le mot de passe est erroné. La carte SIM est désormais verrouillée. Veuillez saisir le code PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"La clé PUK2 est bloquée de manière définitive."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Il vous reste <xliff:g id="NUMBER">%d</xliff:g> tentatives."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"Verrouillage par clé PUK2. Contactez votre fournisseur de services pour déverrouiller la carte SIM."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"Le code PIN2 n\'est plus bloqué."</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Erreur de réseau ou de carte SIM."</string>
<string name="doneButton" msgid="7371209609238460207">"OK"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Pour passer un appel, activez le réseau mobile, et désactivez les modes Avion et Économiseur de batterie."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Désactivez le mode Avion pour passer un appel."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Désactivez le mode Avion ou connectez-vous à un réseau sans fil pour passer un appel."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Téléphone trop chaud"</b>\n\n"Impossible de passer cet appel. Réessayez lorsque votre téléphone aura refroidi.\n\nVous pouvez toujours passer des appels d\'urgence."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Veuillez quitter le mode de rappel d\'urgence pour passer un appel standard."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Non enregistré sur le réseau."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Réseau mobile non disponible"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Sous-identifiant de la carte SIM par défaut pour les données :"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Bande passante de téléchargement (kbit/s) :"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Bande passante d\'importation (kbit/s) :"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Informations de géolocalisation mobiles (obsolètes) :"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuration de la chaîne physique LTE :"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Fréquence d\'actualisation des informations mobiles :"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Toutes les informations mobiles liées aux mesures :"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Informations sur le téléphone"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Informations de gestion des comptes opérateur"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Déclencher la gestion des comptes opérateur"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Votre signal Bluetooth est faible. Essayez d\'utiliser le haut-parleur."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Notification concernant la qualité de l\'appel"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Comptes SIP obsolètes"</string>
</resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 3137f92..6fdca2d 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"O usuario principal é o único que pode modificar a configuración do correo de voz."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Desbloqueouse a tarxeta SIM. O teléfono estase desbloqueando..."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN de desbloqueo da rede SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM bloqueada para o operador"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Desbloquear"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Rexeitar"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Solicitando desbloqueo da rede..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Desvío ao <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Desactivado"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"O teu operador non admite a desactivación do desvío de chamadas cando non se pode acceder ao teléfono."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"O teu operador non admite o desvío de chamadas."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Queres activar a opción de chamada en espera?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Durante as chamadas, recibirás notificacións das chamadas entrantes"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Activar"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Cancelar"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"A función de chamada en espera de CDMA en IMS está activada"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"A función de chamada en espera de CDMA en IMS está desactivada"</string>
<string name="updating_title" msgid="6130548922615719689">"Configuración de chamada"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Só o usuario administrador pode cambiar a configuración de chamada."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Configuración (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Funcionalidade da SIM limitada"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Pode que as chamadas e os servizos de datos de <xliff:g id="CARRIER_NAME">%1$s</xliff:g> se bloqueen cando se use o <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Pode que as chamadas e os servizos de datos de <xliff:g id="CARRIER_NAME">%1$s</xliff:g> se bloqueen ao usar outra SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Quitáronse as contas SIP obsoletas que se atoparon"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Xa non se permiten as chamadas SIP na plataforma Android.\nQuitáronse as túas contas SIP (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>).\nConfirma a configuración predeterminada da conta para as chamadas."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Ir á configuración"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Uso de datos da aplicación"</string>
<string name="data_usage_template" msgid="6287906680674061783">"Datos móbiles usados no período do <xliff:g id="ID_2">%2$s</xliff:g>: <xliff:g id="ID_1">%1$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Configuración avanzada"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Contrasinal incorrecto. A SIM está bloqueada agora. Introduce o PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"O PUK2 está permanentemente bloqueado."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Quédanche <xliff:g id="NUMBER">%d</xliff:g> intentos."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 bloqueado. Contacta co fornecedor de servizo para desbloquealo."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"O PIN2 xa non está bloqueado"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Erro de rede ou de tarxeta SIM"</string>
<string name="doneButton" msgid="7371209609238460207">"Feito"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Para facer unha chamada, activa a rede de telefonía móbil, desactiva o modo avión ou desactiva o modo Aforro de batería."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Desactiva o modo avión para facer unha chamada."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Desactiva o modo avión ou conéctate a unha rede sen fíos para facer unha chamada."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"A temperatura do teléfono é moi alta"</b>\n\n"Non se pode completar esta chamada. Téntao de novo cando arrefríe o teléfono.\n\nAínda así, podes facer chamadas de emerxencia."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Sae do modo de devolución de chamada de emerxencia para facer unha chamada que non sexa de emerxencia."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Sen rexistro na rede"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"A rede móbil non está dispoñible."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Código de identificación secundario da SIM de datos predeterminada:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Largura de banda de descarga (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Largura de banda de carga (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Información da localización para móbiles (obsoleto):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuración de canle física de LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Taxa de actualización da información para móbiles:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Toda a información de medición para móbiles:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Información do teléfono"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Información de aprovisionamento do operador"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Aprovisionamento do operador do activador"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"O teu sinal de Bluetooth é feble. Proba a cambiar ao altofalante."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Notificación sobre a calidade da chamada"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Contas SIP obsoletas"</string>
</resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 89f7573..454dc44 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"વૉઇસમેઇલ સેટિંગ ફક્ત પ્રાથમિક વપરાશકર્તા દ્વારા જ ફેરફાર કરી શકાય છે."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"તમારા SIM કાર્ડને અનાવરોધિત કરવામાં આવ્યું છે. તમારા ફોનને અનલૉક કરી રહ્યાં છે…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM નેટવર્ક અનલૉક PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"ઑપરેટર દ્વારા સિમ કાર્ડ લૉક કરવામાં આવ્યું છે"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"અનલૉક કરો"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"છોડી દો"</string>
<string name="requesting_unlock" msgid="930512210309437741">"નેટવર્ક અનલૉકની વિનંતી કરી રહ્યાં છે…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> પર ફોરવર્ડ કરી રહ્યાં છે"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"બંધ"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"જ્યારે તમારો ફોન પહોંચયોગ્ય ન હોય ત્યારે તમારા કૅરિઅર કૉલ ફૉરવર્ડિંગ બંધ કરવાને સપોર્ટ કરતા નથી."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"તમારા મોબાઇલ ઑપરેટર કૉલ ફૉર્વર્ડિંગને સપોર્ટ કરતા નથી."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"કૉલ પ્રતીક્ષા ચાલુ કરવા માગો છો?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"કૉલ દરમ્યાન, તમને ઇનકમિંગ કૉલ વિશે નોટિફિકેશન આપવામાં આવશે"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"ચાલુ કરો"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"રદ કરો"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS હેઠળ CDMA કૉલ પ્રતીક્ષા ચાલુ છે"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS હેઠળ CDMA કૉલ પ્રતીક્ષા બંધ છે"</string>
<string name="updating_title" msgid="6130548922615719689">"કૉલ સેટિંગ"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"કૉલ સેટિંગને ફક્ત ઍડમિન વપરાશકર્તા દ્વારા જ બદલી શકાય છે."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"સેટિંગ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"સિમની મર્યાદિત કાર્યક્ષમતા"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g>નો ઉપયોગ કરતી વખતે <xliff:g id="CARRIER_NAME">%1$s</xliff:g>ના કૉલ અને ડેટાની સેવાઓ બ્લૉક થઈ શકે છે."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"બીજા સિમનો ઉપયોગ કરતી વખતે <xliff:g id="CARRIER_NAME">%1$s</xliff:g>ના કૉલ અને ડેટાની સેવાઓ બ્લૉક થઈ શકે છે."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"ટાળવામાં આવેલા SIP એકાઉન્ટ મળી આવ્યા અને તેને કાઢી નાખ્યા"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP કૉલિંગની સુવિધાને હવે Android પ્લૅટફૉર્મ દ્વારા સપોર્ટ આપવામાં આવતો નથી.\n તમારા વર્તમાન SIP એકાઉન્ટ <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> કાઢી નાખવામાં આવ્યા છે.\nકૃપા કરીને તમારા ડિફૉલ્ટ કૉલિંગ એકાઉન્ટનું સેટિંગ કન્ફર્મ કરો."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"સેટિંગ પર જાઓ"</string>
<string name="data_usage_title" msgid="8438592133893837464">"ઍપ ડેટા વપરાશ"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> મોબાઇલ ડેટાનો ઉપયોગ <xliff:g id="ID_2">%2$s</xliff:g> ના રોજ સુધી કરવામાં આવ્યો"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"વિગતવાર"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"પાસવર્ડ ખોટો છે, SIM હમણાં અવરોધિત છે. PUK2 દાખલ કરો."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 ને કાયમી રૂપે અવરોધિત કરેલ છે."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"તમારી પાસે <xliff:g id="NUMBER">%d</xliff:g> પ્રયાસ બાકી છે."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 વડે લૉક થયું છે. અનલૉક કરવા માટે સેવા પ્રદાતાનો સંપર્ક કરો."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 હવે અવરોધિત નથી"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"નેટવર્ક અથવા SIM કાર્ડ ભૂલ"</string>
<string name="doneButton" msgid="7371209609238460207">"થઈ ગયું"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"કૉલ કરવા માટે મોબાઇલ નેટવર્ક ચાલુ કરો, એરપ્લેન મોડ બંધ કરો અથવા બૅટરી બચતકર્તા મોડ બંધ કરો."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"કૉલ કરવા માટે એરપ્લેન મોડ બંધ કરો."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"કૉલ કરવા માટે એરપ્લેન મોડ બંધ કરો અથવા વાયરલેસ નેટવર્કથી કનેક્ટ કરો."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"ફોન ખૂબ ગરમ છે"</b>\n\n"આ કૉલ પૂર્ણ કરી શકતા નથી. તમારો ફોન ઠંડો પડે ત્યાર પછી ફરીથી પ્રયાસ કરો.\n\nતમે હજી પણ ઇમર્જન્સી કૉલ કરી શકો છો."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"બિન-કટોકટીનો કૉલ કરવા માટે કટોકટી કૉલબૅક મોડમાંથી બહાર નીકળો."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"નેટવર્ક પર નોંધણી કરાયેલ નથી."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"મોબાઇલ નેટવર્ક ઉપલબ્ધ નથી."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ડિફૉલ્ટ ડેટા સિમનું SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL બૅન્ડવિડ્થ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL બૅન્ડવિડ્થ (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"સેલ સ્થાન માહિતી (ટાળેલ):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ભૌતિક ચૅનલની ગોઠવણી:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"સેલ માહિતી રિફ્રેશ થવાનો રેટ:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"તમામ સેલ માપ માહિતી:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"ફોનની માહિતી"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"કૅરિઅરની જોગવાઈ વિશે માહિતી"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"કૅરિઅરની જોગવાઈ ટ્રિગર કરો"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"તમારા બ્લૂટૂથનું સિગ્નલ નબળું છે. સ્પીકરફોન પર સ્વિચ કરવાનો પ્રયાસ કરો."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"કૉલની ક્વૉલિટી માટે નોટિફિકેશન"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"ટાળવામાં આવેલા SIP એકાઉન્ટ"</string>
</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 1520ffb..24399f2 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"वॉइसमेल सेटिंग को केवल प्राथमिक उपयोगकर्ता ही बदल सकता है."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"आपका सिम कार्ड अनब्लॉक कर दिया गया है. आपका फ़ोन अनलॉक किया जा रहा है..."</string>
<string name="label_ndp" msgid="7617392683877410341">"सिम नेटवर्क अनलॉक पिन"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"ऑपरेटर के लिए सिम को लॉक किया गया"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"अनब्लॉक करें"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"खारिज करें"</string>
<string name="requesting_unlock" msgid="930512210309437741">"नेटवर्क अनलॉक के लिए अनुरोध कर रहा है..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> को अग्रेषित कर रहा है"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"बंद"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"जब आपका फ़ोन पहुंच योग्य न हो, तो आपका कैरियर कॉल अग्रेषण अक्षम करने का समर्थन नहीं करता."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"आपको मोबाइल और इंटरनेट सेवा देने वाली कंपनी, कॉल को दूसरे नंबर पर भेजने की सुविधा नहीं देती."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"क्या आप कॉल वेटिंग (कॉल के दौरान आ रहा दूसरा कॉल) की सुविधा चालू करना चाहते हैं?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"कॉल के दौरान, आपको आने वाले कॉल (इनकमिंग) के बारे में सूचना दी जाएगी"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"चालू करें"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"रद्द करें"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"आईएमएस में CDMA कॉल वेटिंग की सुविधा चालू है"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"आईएमएस में CDMA कॉल वेटिंग की सुविधा बंद है"</string>
<string name="updating_title" msgid="6130548922615719689">"कॉल सेटिंग"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"कॉल सेटिंग केवल व्यवस्थापक उपयोगकर्ता द्वारा ही बदली जा सकती हैं."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"सेटिंग (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"सिम से सीमित सुविधाएं मिलेंगी"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g> का इस्तेमाल करते समय <xliff:g id="CARRIER_NAME">%1$s</xliff:g> के कॉल और डेटा सेवाओं पर रोक लगाई जा सकती है."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"किसी और सिम का इस्तेमाल करते समय <xliff:g id="CARRIER_NAME">%1$s</xliff:g> की कॉल और डेटा सेवाएं ब्लॉक की जा सकती हैं."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"ऐसे SIP खाते मिले जिनका समर्थन रोक दिया गया है और उन्हें हटाया गया"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP कॉल की सुविधा, Android प्लैटफ़ॉर्म पर अब काम नहीं करेगी.\nआपके मौजूदा SIP खाते <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> हटा दिए गए हैं.\nकृपया कॉल करने की अपनी डिफ़ॉल्ट खाता सेटिंग की पुष्टि करें."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"सेटिंग पर जाएं"</string>
<string name="data_usage_title" msgid="8438592133893837464">"ऐप्लिकेशन का डेटा खर्च"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g> में <xliff:g id="ID_1">%1$s</xliff:g> मोबाइल डेटा का उपयोग किया गया"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"बेहतर विकल्प"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"पासवर्ड गलत है. सिम अब लॉक हो गई है. PUK2 डालें."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 स्थायी रूप से अवरोधित है."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"आपके पास <xliff:g id="NUMBER">%d</xliff:g> प्रयास शेष हैं."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"सिम कार्ड PUK2 से लॉक है. अनलॉक करने के लिए, सेवा देने वाली कंपनी से संपर्क करें."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 अब अवरोधित नहीं है"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"नेटवर्क या SIM कार्ड गड़बड़ी"</string>
<string name="doneButton" msgid="7371209609238460207">"पूरा हो गया"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"कॉल करने के लिए मोबाइल नेटवर्क चालू करें, हवाई जहाज़ मोड या बैटरी सेवर मोड बंद करें."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"कॉल करने के लिए हवाई जहाज़ मोड बंद करें."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"कॉल करने के लिए हवाई जहाज़ मोड बंद करें या किसी वायरलेस नेटवर्क से कनेक्ट करें."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"आपका फ़ोन बहुत गर्म हो गया है"</b>\n\n"यह कॉल नहीं लगाया जा सकता. फ़ोन का तापमान कम होने के बाद, फिर से कोशिश करें.\n\nहालांकि, आप अब भी आपातकालीन कॉल कर सकते हैं."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"गैर-आपातकालीन कॉल करने के लिए आपातकालीन कॉलबैक मोड से बाहर निकलें."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"नेटवर्क पर पंजीकृत नहीं."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"मोबाइल नेटवर्क उपलब्ध नहीं."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"डिफ़ॉल्ट डेटा सिम का सब-आईडी:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"डीएल बैंडविड्थ (केबीपीएस):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"यूएल बैंडविड्थ (केबीपीएस):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"सेल की जगह की जानकारी (रोकी गयी है):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"एलटीई की फ़िज़िकल चैनल कॉन्फ़िगरेशन:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"सेल की जानकारी रीफ़्रेश होने की दर:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"सभी सेल के माप की पूरी जानकारी:"</string>
@@ -878,14 +890,14 @@
<string name="radio_info_ppp_resets_label" msgid="9131901102339077661">"चालू करने के बाद से पीपीपी रीसेट की संख्या:"</string>
<string name="radio_info_current_network_label" msgid="3052098695239642450">"मौजूदा नेटवर्क:"</string>
<string name="radio_info_ppp_received_label" msgid="5753592451640644889">"मिलने वाला डेटा :"</string>
- <string name="radio_info_gsm_service_label" msgid="6443348321714241328">"Voice की सेवा:"</string>
+ <string name="radio_info_gsm_service_label" msgid="6443348321714241328">"वॉइस सेवा:"</string>
<string name="radio_info_signal_strength_label" msgid="5545444702102543260">"सिग्नल कितना अच्छा है:"</string>
- <string name="radio_info_call_status_label" msgid="7693575431923095487">"Voice कॉल की स्थिति:"</string>
+ <string name="radio_info_call_status_label" msgid="7693575431923095487">"वॉइस कॉल की स्थिति:"</string>
<string name="radio_info_ppp_sent_label" msgid="6542208429356199695">"भेजा गया डेटा :"</string>
<string name="radio_info_message_waiting_label" msgid="1886549432566952078">"मैसेज वेटिंग:"</string>
<string name="radio_info_phone_number_label" msgid="2533852539562512203">"फ़ोन नंबर:"</string>
<string name="radio_info_band_mode_label" msgid="23480556225515290">"रेडियो का बैंड चुनें"</string>
- <string name="radio_info_voice_network_type_label" msgid="2395347336419593265">"Voice के नेटवर्क प्रकार:"</string>
+ <string name="radio_info_voice_network_type_label" msgid="2395347336419593265">"वॉइस नेटवर्क टाइप:"</string>
<string name="radio_info_data_network_type_label" msgid="8886597029237501929">"डेटा नेटवर्क प्रकार:"</string>
<string name="phone_index_label" msgid="6222406512768964268">"फ़ोन इंडेक्स चुनें"</string>
<string name="radio_info_set_perferred_label" msgid="7408131389363136210">"पसंदीदा नेटवर्क प्रकार सेट करें:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"फ़ोन की जानकारी"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"मोबाइल और इंटरनेट सेवा देने वाली कंपनी के प्रावधान की जानकारी"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"मोबाइल और इंटरनेट सेवा देने वाली कंपनी के प्रावधान चालू करें"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"आपका ब्लूटूथ सिग्नल कमज़ोर है. स्पीकरफ़ोन की सुविधा का इस्तेमाल करें."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"कॉल की क्वालिटी की सूचना"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"ऐसे SIP खाते जिनका समर्थन रोक दिया गया है"</string>
</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 6028212..b9a8d0b 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Postavke govorne pošte može izmijeniti samo primarni korisnik."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Vaša je SIM kartica odblokirana. Telefon se odblokira…"</string>
<string name="label_ndp" msgid="7617392683877410341">"Mrežni PIN za otključavanje SIM-a"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM je zaključan za operatera"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Otključaj"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Odbaci"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Traženje mrežnog otključavanja..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Preusmjeravanje na <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Isključeno"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Vaš mobilni operater ne podržava onemogućavanje preusmjeravanja poziva ako je vaš telefon nedostupan."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Vaš operater ne podržava preusmjeravanje poziva."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Uključiti poziv na čekanju?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Tijekom poziva primat ćete obavijesti o dolaznim pozivima"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Uključi"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Odustani"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA poziv na čekanju u okviru IMS-a uključen"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA poziv na čekanju u okviru IMS-a isključen"</string>
<string name="updating_title" msgid="6130548922615719689">"Postavke poziva"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Postavke poziva može mijenjati samo korisnik koji je administrator."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Postavke (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Ograničene funkcije SIM-a"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Pozivi i podatkovne usluge operatera <xliff:g id="CARRIER_NAME">%1$s</xliff:g> mogu biti blokirane dok upotrebljavate broj <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Pozivi i podatkovne usluge operatera <xliff:g id="CARRIER_NAME">%1$s</xliff:g> mogu biti blokirane dok upotrebljavate drugi SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Obustavljeni SIP računi pronađeni su i uklonjeni"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Pozivanje putem protokola SIP više nije podržano na Android platformi.\nVaši postojeći SIP računi <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> uklonjeni su.\nPotvrdite zadanu postavku za pozivanje na računu."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Otvorite postavke"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Potrošnja podatkovnog prometa za aplikacije"</string>
<string name="data_usage_template" msgid="6287906680674061783">"Potrošeno je <xliff:g id="ID_1">%1$s</xliff:g> mobilnih podataka u sljedećem razdoblju: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Napredno"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Zaporka nije točna. SIM je sada zaključan. Unesite PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 je trajno blokiran."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Imate još <xliff:g id="NUMBER">%d</xliff:g> pokušaja."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 zaključan. Obratite se davatelju usluga za otključavanje."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 više nije blokiran"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Pogreška mreže ili SIM kartice"</string>
<string name="doneButton" msgid="7371209609238460207">"Gotovo"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Uključite mobilnu mrežu, isključite način rada u zrakoplovu ili štednju baterije da biste uputili poziv."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Isključite način rada u zrakoplovu da biste uputili poziv."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Isključite način rada u zrakoplovu ili se povežite s bežičnom mrežom da biste uputili poziv."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefon je prevruć"</b>\n\n"Poziv se ne može dovršiti. Pokušajte ponovno kad se telefon ohladi.\n\nI dalje možete upućivati hitne pozive."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Isključite način hitnih poziva da biste uputili poziv koji nije hitan."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Nije registrirano na mreži."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobilna mreža nije dostupna."</string>
@@ -870,7 +883,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SUBID zadanog SIM-a za podatkovni promet:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL propusnost (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL propusnost (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Informacije o lokaciji ćelije (obustavljeno):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfiguracija LTE fizičkog kanala:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Stopa osvježavanja informacija u ćeliji:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Sve informacije ćelija o mjerenju:"</string>
@@ -915,4 +927,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Informacije o telefonu"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Informacije o uslugama mobilnog operatera"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Pokretanje usluga mobilnog operatera"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Signal Bluetootha je slab. Pokušajte se prebaciti na zvučnik."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Obavijest o kvaliteti poziva"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Obustavljeni SIP računi"</string>
</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 75b1783..80d1865 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"A hangposta-beállításokat csak az elsődleges felhasználó módosíthatja."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"SIM kártyája letiltása megszűnt. Feloldás folyamatban..."</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM hálózati függetlenítő PIN kódja"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"A SIM zárolva a szolgálató esetében"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Feloldás"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Elvetés"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Függetlenítés kérése..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Átirányítás ide: <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Ki"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Szolgáltatója nem támogatja a hívásátirányítás letiltását, ha a telefon nem érhető el."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"A szolgáltatója nem teszi lehetővé a hívásátirányítást."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Bekapcsolja a hívásvárakoztatást?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Hívás közben értesítést kap a bejövő hívásokról"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Igen"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Mégse"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS alatti CDMA-hívásvárakoztatás bekapcsolva"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS alatti CDMA-hívásvárakoztatás kikapcsolva"</string>
<string name="updating_title" msgid="6130548922615719689">"Hívásbeállítások"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"A hívásbeállításokat csak a rendszergazda módosíthatja."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Beállítások (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Korlátozott SIM-funkció"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"A(z) <xliff:g id="PHONE_NUMBER">%2$s</xliff:g> szám használatakor előfordulhat, hogy a(z) <xliff:g id="CARRIER_NAME">%1$s</xliff:g>-hívások és az adatszolgáltatások nem működnek."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Másik SIM esetén lehet, hogy a(z) <xliff:g id="CARRIER_NAME">%1$s</xliff:g>-hívások és -adatszolgáltatások nem működnek."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Elavult SIP-fiókokat találtunk, és eltávolítottuk őket"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Az Android platform már nem támogatja a SIP-hívást.\nA meglévő SIP-fiókjait (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>) eltávolítottuk.\nErősítse meg az alapértelmezett hívásfiók beállítását."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Ugrás a beállításokhoz"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Alkalmazás adathasználata"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> mobiladat felhasználva a következő dátumok között: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Speciális"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"A jelszó helytelen. SIM kártya zárolva. Adja meg a PUK2 kódot."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"A PUK2 végleg letiltva."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"<xliff:g id="NUMBER">%d</xliff:g> próbálkozási lehetősége maradt."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"A PUK2 zárolva van. A feloldás ügyében forduljon a szolgáltatóhoz."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 kód letiltása feloldva"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Hálózattal vagy SIM kártyával kapcsolatos hiba"</string>
<string name="doneButton" msgid="7371209609238460207">"Kész"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"A hívás indításához kapcsolja be a hálózatot, és kapcsolja ki a repülős vagy akkumulátorkímélő üzemmódot."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Repülős üzemmód kikapcsolása hívás indításához."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Hívás indításához kapcsolja ki a repülős üzemmódot, illetve csatlakozzon egy vezeték nélküli hálózathoz."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Túlmelegedett a telefon"</b>\n\n"Ezt a hívást nem lehet végrehajtani. Próbálkozzon újra, ha már lehűlt a telefon.\n\nSegélyhívást így is indíthat."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Lépjen ki a Segélykérő visszahívása módból nem vészjellegű hívás kezdeményezéséhez."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Nincs regisztrálva a hálózaton."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"A mobilhálózat nem érhető el."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Alapértelmezett adatok SIM-alazonosítója:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Letöltési sávszélesség (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Feltöltési sávszélesség (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Mobilos helyadatok (megszűnt):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE fizikai csatorna konfigurációja:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Cellainformáció frissítési gyakorisága:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Minden cellamérési információ:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Telefon adatai"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Információ a szolgáltatói ellátásról"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Szolgáltatói ellátás aktiválása"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Gyenge a Bluetooth-jel. Próbáljon kihangosítóra váltani."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Értesítés a hívás minőségéről"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Elavult SIP-fiókok"</string>
</resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index de96e1b..7ccb20d 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Ձայնային փոստի կարգավորումները կարող է փոխել միայն հիմնական օգտագործողը:"</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Ձեր SIM քարտը ապակողպված է: Ձեր հեռախոսը ապակողպվում է..."</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM ցանցով PIN-ի ապակողպում"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"Աշխատում է միայն այս օպերատորի SIM քարտով"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Ապակողպել"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Անտեսել"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Հայցվում է ցանցի ապակողպում..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Վերահասցեավորվում է դեպի <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Անջատված է"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Ձեր սպասարկողը չի աջակցում զանգի վերահասցեավորման կասեցում, երբ ձեր հեռախոսն անհասանելի է:"</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Ձեր օպերատորը չի աջակցում զանգի վերահասցեավորում։"</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Միացնե՞լ զանգի սպասումը"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Հեռախոսազրույցի ժամանակ դուք կծանուցվեք մուտքային զանգերի մասին"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Միացնել"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Չեղարկել"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA զանգի սպասումը IMS-ում միացված է"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA զանգի սպասումը IMS-ում անջատված է"</string>
<string name="updating_title" msgid="6130548922615719689">"Զանգի կարգավորումներ"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Զանգի կարգավորումները կարող է փոխել միայն ադմինիստրատոր հանդիսացող օգտատերը:"</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Կարգավորումներ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"SIM քարտի սահմանափակ գործառույթներ"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g> համարն օգտագործելիս <xliff:g id="CARRIER_NAME">%1$s</xliff:g>-ով զանգերն ու բջջային ինտերնետը կարող են արգելափակվել:"</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Այլ SIM քարտ օգտագործելիս <xliff:g id="CARRIER_NAME">%1$s</xliff:g>-ով զանգերն ու բջջային ինտերնետը կարող են արգելափակվել"</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Հնացած SIP հաշիվներ են հայտնաբերվել և հեռացվել"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP-ի միջոցով զանգերն այլևս չեն աջակցվում Android հարթակի կողմից։\nՁեր ունեցած SIP հաշիվները <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> հեռացվել են։\nՀաստատեք զանգերի կանխադրված ծառայության հաշվի կարգավորումները։"</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Անցնել կարգավորումներ"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Թրաֆիկի օգտագործումը հավելվածի կողմից"</string>
<string name="data_usage_template" msgid="6287906680674061783">"Oգտագործվել է <xliff:g id="ID_1">%1$s</xliff:g> բջջային ինտերնետ (<xliff:g id="ID_2">%2$s</xliff:g>)"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Լրացուցիչ"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Գաղտնաբառը սխալ է: SIM քարտն այժմ կողպված է: Մուտքագրեք PUK2 կոդը:"</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2-ն ընդմիշտ արգելափակված է:"</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Ձեզ մնացել է <xliff:g id="NUMBER">%d</xliff:g> փորձ:"</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"Կողպված է PUK2-ով։ Ապակողպելու համար կապվեք ձեր օպերատորի հետ։"</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 կոդն այլևս արգելափակված չէ"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Ցանցի կամ SIM քարտի սխալ"</string>
<string name="doneButton" msgid="7371209609238460207">"Պատրաստ է"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Զանգ կատարելու համար միացեք բջջային ցանցին, անջատեք մարտկոցի տնտեսումը կամ ավիառեժիմը:"</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Զանգ կատարելու համար անջատեք Ավիառեժիմը:"</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Զանգ կատարելու համար անջատեք Ավիառեժիմը կամ միացեք անլար ցանցին:"</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Հեռախոսը գերտաքացել է"</b>\n\n"Չհաջողվեց կատարել զանգը։ Նորից փորձեք, երբ հեռախոսը հովանա։\n\nԴուք նախկինի պես կկարողանաք շտապ կանչեր կատարել։"</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Սովորական զանգ կատարելու համար դուրս եկեք շտապ կանչի ռեժիմից։"</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Ցանցում գրանցված չէ:"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Բջջային ցանցն անհասանելի է:"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SIM քարտի հավելյալ ID կանխադրված բջջային ինտերնետի համար՝"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL թողունակությունը (կբ/վ)՝"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL թողունակությունը (կբ/վ)՝"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Բջիջի տեղադրության տվյալներ (հնացած)՝"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ֆիզիկական ալիքի կարգավորում՝"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Բջիջի տվյալների թարմացման հաճախականությունը՝"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Բոլոր բջիջների չափման տվյալները՝"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Հեռախոսի տեղեկություններ"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Օպերատորի նախապատրաստման տվյալներ"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Գործարկել օպերատորի նախապատրաստումը"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Ձեր Bluetooth-ի ազդանշանը թույլ է։ Փորձեք միացնել բարձրախոսը։"</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Զանգի որակի մասին ծանուցում"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Հնացած SIP հաշիվներ"</string>
</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 8d9bcc3..b36e02f 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Setelan Pesan Suara hanya dapat diubah oleh Pengguna Utama."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Kartu SIM Anda tidak lagi dicekal. Ponsel Anda sedang dibuka kuncinya..."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN pembuka kunci jaringan SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM dikunci untuk operator"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Buka kunci"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Tutup"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Meminta membuka jaringan..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Meneruskan ke <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Nonaktif"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Operator Anda tidak mendukung penonaktifan penerusan panggilan ketika ponsel tidak dapat dijangkau."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Operator Anda tidak mendukung penerusan panggilan."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Aktifkan nada tunggu?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Selama melakukan panggilan, Anda akan menerima notifikasi jika ada panggilan masuk"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Aktifkan"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Batal"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Nada tunggu CDMA dalam IMS aktif"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Nada tunggu CDMA dalam IMS nonaktif"</string>
<string name="updating_title" msgid="6130548922615719689">"Setelan panggilan"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Setelan panggilan telepon hanya dapat diubah oleh pengguna admin."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Setelan (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Fungsi SIM terbatas"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Layanan data dan panggilan <xliff:g id="CARRIER_NAME">%1$s</xliff:g> dapat diblokir saat menggunakan <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Layanan data dan panggilan <xliff:g id="CARRIER_NAME">%1$s</xliff:g> dapat diblokir saat menggunakan SIM lain."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Akun SIP yang tidak digunakan lagi telah ditemukan dan dihapus"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Panggilan SIP tidak lagi didukung oleh platform Android.\nAkun SIP yang ada, <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>, telah dihapus.\nHarap konfirmasi setelan akun panggilan default Anda."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Buka setelan"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Penggunaan data aplikasi"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> data seluler digunakan pada <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Lanjutan"</string>
@@ -302,7 +313,7 @@
<string name="video_calling_settings_title" msgid="342829454913266078">"Panggilan video operator"</string>
<string name="gsm_umts_options" msgid="4968446771519376808">"Opsi GSM//UMTS"</string>
<string name="cdma_options" msgid="3669592472226145665">"Opsi CDMA"</string>
- <string name="throttle_data_usage" msgid="1944145350660420711">"Penggunaan kuota"</string>
+ <string name="throttle_data_usage" msgid="1944145350660420711">"Penggunaan data"</string>
<string name="throttle_current_usage" msgid="7483859109708658613">"Data yang digunakan dalam periode saat ini"</string>
<string name="throttle_time_frame" msgid="1813452485948918791">"Periode penggunaan data"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Kebijakan kecepatan data"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Sandi salah. SIM kini dikunci. Masukkan PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 diblokir selamanya."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Tersisa <xliff:g id="NUMBER">%d</xliff:g> upaya lagi."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 terkunci. Hubungi penyedia layanan untuk membuka kunci."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 tidak lagi diblokir"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Kesalahan jaringan atau kartu SIM"</string>
<string name="doneButton" msgid="7371209609238460207">"Selesai"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Aktifkan jaringan seluler, lalu nonaktifkan mode pesawat atau penghemat baterai untuk melakukan panggilan."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Nonaktifkan mode pesawat untuk melakukan panggilan"</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Nonaktifkan mode pesawat atau sambungkan ke jaringan nirkabel untuk melakukan panggilan"</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Ponsel terlalu panas"</b>\n\n"Tidak dapat menyelesaikan panggilan ini. Coba lagi saat ponsel mendingin.\n\nAnda masih dapat melakukan panggilan darurat."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Keluar dari mode telepon balik darurat untuk melakukan panggilan non-darurat."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Tidak terdaftar pada jaringan."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Jaringan seluler tidak tersedia."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId SIM data default:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Bandwidth DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Bandwidth UL (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Info Lokasi Sel (tidak digunakan lagi):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfigurasi Saluran Fisik LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Rasio Pembaruan Info Sel"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Semua Info Pengukuran Sel:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Info telepon"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Info Provisioning Operator"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Memicu Provisioning Operator"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Sinyal bluetooth Anda lemah. Coba beralih ke speaker ponsel."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Notifikasi Kualitas Panggilan"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Akun SIP yang tidak digunakan lagi"</string>
</resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 7521fda..b44afa2 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Aðeins aðalnotandinn má breyta talhólfsstillingum."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Opnað var fyrir SIM-kortið. Verið er að opna símann…"</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN-númer SIM-korts til að opna síma"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM-kort læst fyrir símafyrirtæki"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Taka úr lás"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Hunsa"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Biður um að símkerfi sé opnað…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Áframsendir í <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Slökkt"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Símafyrirtækið leyfir ekki að slökkt sé á símtalsflutningi þegar ekki næst í símann."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Símafyrirtækið þitt styður ekki símtalsflutning."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Viltu kveikja á símtölum í bið?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Þú færð tilkynningu um móttekið símtal ef þú ert að tala í símann"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Kveikja"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Hætta"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Kveikt er á CDMA-símtölum í bið undir spjalli"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Slökkt er á CDMA-símtölum í bið undir spjalli"</string>
<string name="updating_title" msgid="6130548922615719689">"Símtalsstillingar"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Aðeins stjórnandinn má breyta símtalsstillingum."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Stillingar (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Takmörkuð virkni SIM-korts"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Lokað gæti verið fyrir símtöl og gagnaþjónustu <xliff:g id="CARRIER_NAME">%1$s</xliff:g> þegar <xliff:g id="PHONE_NUMBER">%2$s</xliff:g> er notað."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Lokað gæti verið fyrir símtöl og gagnaþjónustu <xliff:g id="CARRIER_NAME">%1$s</xliff:g> þegar annað SIM-kort er notað."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Úreldir SIP-reikningar fundust og voru fjarlægðir"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Android kerfið styður ekki lengur SIP-símtöl.\nNúverandi SIP-reikningarnir þínir <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> voru fjarlægðir.\nStaðfestu stillingar sjálfgefna símareikningsins."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Opna stillingar"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Gagnanotkun forrits"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> af farsímagögnum notuð <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Ítarlegt"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Rangt aðgangsorð. SIM-kortinu var læst. Sláðu inn PUK2-númerið."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 er læst fyrir fullt og allt."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"<xliff:g id="NUMBER">%d</xliff:g> tilraunir eru eftir."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"Læst með PUK2. Hafðu samband við þjónustuaðila til að taka úr lás."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2-númer ekki lengur læst"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Villa í símkerfi eða á SIM-korti"</string>
<string name="doneButton" msgid="7371209609238460207">"Lokið"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Kveiktu á farsímakerfie eða slökktu á flugstillingu eða rafhlöðusparnaði til að hringja."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Slökktu á flugstillingu til að hringja."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Slökktu á flugstillingu eða tengstu þráðlausu neti til að hringja."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Síminn er of heitur"</b>\n\n"Ekki er hægt að ljúka þessu símtali. Reyndu aftur þegar síminn hefur kólnað.\n\nÞú getur ennþá hringt neyðarsímtöl."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Hætta í stillingu fyrir svarhringingu neyðarsímtala til að hringja símtal sem ekki er neyðarsímtal."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Ekki skráð á símkerfi."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Farsímakerfi ekki tiltækt."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Undirauðkenni sjálfgefins SIM-korts fyrir gögn:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Niðurhalsbandvídd (kb/sek.):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Upphleðslubandvídd (kb/sek.):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Staðsetningarupplýsingar loftneta (úrelt):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE-rásarstilling:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Endurnýjunartíðni loftnetaupplýsinga:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Allar mælingarupplýsingar loftneta:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Upplýsingar um síma"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Úthlutunarupplýsingar símafyrirtækis"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Kveikja á úthlutun símafyrirtækis"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Bluetooth-tengingin er léleg. Prófaðu að nota hátalara í staðinn."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Tilkynning um símtalsgæði"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Úreldir SIP-reikningar"</string>
</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 62b91e5..f7ff030 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Le impostazioni della segreteria possono essere modificate solo dall\'utente principale."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"La SIM è stata sbloccata. Sblocco del telefono..."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN sblocco rete SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM bloccata per l\'operatore"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Sblocca"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Chiudi"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Richiesta sblocco rete..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Deviazione al numero <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"OFF"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Il tuo operatore non supporta la disattivazione dell\'inoltro chiamate quando il telefono non è raggiungibile."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Il tuo operatore non supporta la deviazione chiamate."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Attiva avviso di chiamata"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Durante una telefonata, riceverai una notifica per le chiamate in entrata"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Attiva"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Annulla"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Avviso di chiamata CDMA con IMS attivato"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Avviso di chiamata CDMA con IMS disattivato"</string>
<string name="updating_title" msgid="6130548922615719689">"Impostazioni chiamate"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Le impostazioni delle chiamate possono essere modificate solo dall\'utente amministratore."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Impostazioni (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,7 +294,10 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Funzionalità SIM limitata"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Le chiamate e i servizi dati di <xliff:g id="CARRIER_NAME">%1$s</xliff:g> potrebbero essere bloccati quando utilizzi il numero <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Le chiamate e i servizi dati <xliff:g id="CARRIER_NAME">%1$s</xliff:g> possono essere bloccati quando usi un\'altra SIM."</string>
- <string name="data_usage_title" msgid="8438592133893837464">"Utilizzo dei dati delle app"</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Account SIP deprecati trovati e rimossi"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Le chiamate SIP non sono più supportate dalla piattaforma Android.\nI tuoi account SIP esistenti (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>) sono stati rimossi.\nConferma l\'impostazione del tuo account predefinito per le chiamate."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Vai alle impostazioni"</string>
+ <string name="data_usage_title" msgid="8438592133893837464">"Utilizzo dati delle app"</string>
<string name="data_usage_template" msgid="6287906680674061783">"Dati mobili usati: <xliff:g id="ID_1">%1$s</xliff:g> nel periodo <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Avanzate"</string>
<string name="carrier_settings_euicc" msgid="1190237227261337749">"Operatore"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Password errata. SIM bloccata. Inserisci codice PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 bloccato definitivamente."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Hai a disposizione ancora <xliff:g id="NUMBER">%d</xliff:g> tentativi."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 bloccato. Per sbloccarlo, contatta il fornitore di servizi."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 sbloccato"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Errore di rete o della SIM"</string>
<string name="doneButton" msgid="7371209609238460207">"Fine"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Attiva la rete mobile e disattiva la modalità aereo o il risparmio energetico per effettuare una chiamata."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Disattiva la modalità aereo per effettuare una chiamata."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Disattiva la modalità aereo o connettiti a una rete wireless per effettuare una chiamata."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefono surriscaldato"</b>\n\n"Impossibile completare la chiamata. Riprova quando il telefono si sarà raffreddato.\n\nPuoi comunque effettuare chiamate di emergenza."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Per effettuare chiamate non di emergenza, esci dalla modalità di richiamata di emergenza."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Non registrato sulla rete."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Rete cellulare non disponibile."</string>
@@ -625,12 +638,12 @@
<string name="phone_in_ecm_call_notification_text" msgid="653972232922670335">"Connessione dati disabilitata"</string>
<string name="phone_in_ecm_notification_complete_time" msgid="7341624337163082759">"Nessuna connessione dati fino alle ore <xliff:g id="COMPLETETIME">%s</xliff:g>"</string>
<plurals name="alert_dialog_exit_ecm" formatted="false" msgid="5425906903766466743">
+ <item quantity="one">The phone will be in Emergency Callback mode for <xliff:g id="COUNT_1">%s</xliff:g> minutes. While in this mode no applications using a data connection can be used. Do you want to exit now?</item>
<item quantity="other">Il telefono sarà in modalità Richiamata di emergenza per <xliff:g id="COUNT_1">%s</xliff:g> minuti. In questa modalità non è possibile utilizzare applicazioni che impiegano una connessione dati. Vuoi uscire ora?</item>
- <item quantity="one">Il telefono sarà in modalità Richiamata di emergenza per <xliff:g id="COUNT_0">%s</xliff:g> minuto. In questa modalità non è possibile utilizzare app che impiegano una connessione dati. Vuoi uscire ora?</item>
</plurals>
<plurals name="alert_dialog_not_avaialble_in_ecm" formatted="false" msgid="1152682528741457004">
+ <item quantity="one">The selected action isn\'t available while in the Emergency Callback mode. The phone will be in this mode for <xliff:g id="COUNT_1">%s</xliff:g> minutes. Do you want to exit now?</item>
<item quantity="other">L\'azione selezionata non è disponibile in modalità Richiamata di emergenza. Il telefono sarà in questa modalità per <xliff:g id="COUNT_1">%s</xliff:g> minuti. Vuoi uscire ora?</item>
- <item quantity="one">L\'azione selezionata non è disponibile in modalità Richiamata di emergenza. Il telefono sarà in questa modalità per <xliff:g id="COUNT_0">%s</xliff:g> minuto. Vuoi uscire ora?</item>
</plurals>
<string name="alert_dialog_in_ecm_call" msgid="1207545603149771978">"L\'azione selezionata non è disponibile in modalità di chiamata di emergenza."</string>
<string name="progress_dialog_exiting_ecm" msgid="9159080081676927217">"Uscita dalla modalità di richiamata di emergenza"</string>
@@ -640,8 +653,8 @@
<string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Il telefono è in modalità di richiamata di emergenza"</string>
<string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Fino alle ore <xliff:g id="COMPLETETIME">%s</xliff:g>"</string>
<plurals name="alert_dialog_exit_ecm_without_data_restriction_hint" formatted="false" msgid="6477733043040328640">
+ <item quantity="one">Il telefono sarà in modalità di richiamata di emergenza per <xliff:g id="COUNT_1">%s</xliff:g> minuti.\nVuoi uscire ora?</item>
<item quantity="other">Il telefono sarà in modalità di richiamata di emergenza per <xliff:g id="COUNT_1">%s</xliff:g> minuti.\nVuoi uscire ora?</item>
- <item quantity="one">Il telefono sarà in modalità di richiamata di emergenza per <xliff:g id="COUNT_0">%s</xliff:g> minuto.\nVuoi uscire ora?</item>
</plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Servizio"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Configurazione"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ID secondario della SIM dati predefinita:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Larghezza di banda DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Larghezza di banda UL (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Informazioni sulla posizione delle celle (obsoleto):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configurazione canale fisico LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Frequenza di aggiornamento delle informazioni sulle celle:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Informazioni sulla misurazione di tutte le celle:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Info telefono"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Informazioni sul provisioning operatore"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Attiva provisioning operatore"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Il segnale del Bluetooth è debole. Prova a passare al vivavoce."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Notifica sulla qualità della chiamata"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Account SIP deprecati"</string>
</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 03546a3..5fbf20d 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"רק המשתמש הראשי יכול לשנות את ההגדרות של הדואר הקולי."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"בוטלה החסימה של כרטיס SIM. ביטול חסימת הטלפון מתבצעת..."</string>
<string name="label_ndp" msgid="7617392683877410341">"קוד אימות לביטול נעילה של רשת SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"כרטיס ה-SIM נעול על ידי הספק הסלולרי"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"ביטול נעילה"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"סגירה"</string>
<string name="requesting_unlock" msgid="930512210309437741">"בקשה לביטול נעילת הרשת מתבצעת..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"העברה אל <xliff:g id="PHONENUMBER">{0}</xliff:g> מתבצעת"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"כבוי"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"הספק שלך לא תומך בהשבתה של העברת שיחות כאשר הטלפון כבוי."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"הספק הסלולרי שלך לא תומך בהעברת שיחות."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"רוצה להפעיל את השיחה הממתינה?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"יוצגו לך התראות על שיחות שנכנסות במהלך שיחות אחרות"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"הפעלה"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"ביטול"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"השיחה הממתינה ברשת CDMA ב-IMS פועלת"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"השיחה הממתינה ברשת CDMA ב-IMS מושבתת"</string>
<string name="updating_title" msgid="6130548922615719689">"הגדרות שיחה"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"רק מנהל המערכת יכול לשנות הגדרות שיחה."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"הגדרות (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"יכולת השימוש בכרטיס ה-SIM עלולה להיות מוגבלת"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"יכול להיות שאי אפשר יהיה להתקשר ולהשתמש בחבילת הגלישה דרך <xliff:g id="CARRIER_NAME">%1$s</xliff:g> בזמן השימוש במספר <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"ייתכן שאי אפשר להתקשר ולהשתמש בחבילת הגלישה דרך <xliff:g id="CARRIER_NAME">%1$s</xliff:g> בזמן השימוש בכרטיס SIM אחר."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"נמצאו חשבונות SIP שהוצאו משימוש והם הוסרו"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"שיחות SIP לא נתמכות יותר בפלטפורמת Android.\nלכן, חשבונות SIP שהיו לך <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> הוסרו.\nעליך לבחור איזה חשבון יוגדר כברירת מחדל לשיחות."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"פתיחת ההגדרות"</string>
<string name="data_usage_title" msgid="8438592133893837464">"שימוש בחבילת הגלישה דרך אפליקציות"</string>
<string name="data_usage_template" msgid="6287906680674061783">"השתמשת ב-<xliff:g id="ID_1">%1$s</xliff:g> מחבילת הגלישה בתאריכים <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"הגדרות מתקדמות"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"סיסמה שגויה. ה-SIM נעול כעת. יש להזין PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 נחסם לצמיתות."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"נותרו לך עוד <xliff:g id="NUMBER">%d</xliff:g> ניסיונות."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"נעילת PUK2. כדי לבטל את הנעילה, צריך לפנות לספק השירות."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"קוד אימות 2 אינו חסום עוד"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"שגיאת רשת או כרטיס SIM"</string>
<string name="doneButton" msgid="7371209609238460207">"סיום"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"עליך להפעיל את הרשת הסלולרית, להשבית את מצב הטיסה או להשבית את מצב החיסכון בסוללה כדי להתקשר."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"יש לבטל את מצב טיסה כדי לבצע שיחה."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"יש לבטל את מצב טיסה או להתחבר לרשת אלחוטית כדי לבצע שיחה."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"הטמפרטורה של הטלפון גבוהה מדי"</b>\n\n"אי אפשר להשלים את השיחה. אפשר לנסות שוב כשהטלפון יתקרר.\n\nבינתיים אפשר להתקשר רק למספרי חירום"</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"עליך לצאת ממצב חירום של התקשרות חזרה כדי לבצע שיחות שאינן שיחות חירום."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"לא רשום ברשת."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"הרשת הסלולרית אינה זמינה."</string>
@@ -873,7 +886,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"תת-מזהה של כרטיס ה-SIM עם חבילת גלישה המוגדר כברירת מחדל:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"רוחב פס DL (ב-kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"רוחב פס UL (ב-kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"מידע על מיקום סלולרי (הוצא משימוש):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"תצורת ערוץ פיזי של LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"קצב רענון של מידע סלולרי:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"כל המידע של מדידה סלולרית:"</string>
@@ -918,4 +930,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"פרטי טלפון"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"פרטי ניהול תצורה של ספק סלולרי"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"הפעלת ניהול תצורה של ספק סלולרי"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"הקליטה של ה-Bluetooth חלשה. כדאי לעבור לדיבורית."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"התראה על איכות השיחה"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"חשבונות SIP שהוצאו משימוש"</string>
</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index aeeb83e..7e62fee 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -44,12 +44,13 @@
<string name="pause_prompt_yes" msgid="8184132073048369575">"はい"</string>
<string name="pause_prompt_no" msgid="2145264674774138579">"いいえ"</string>
<string name="wild_prompt_str" msgid="5858910969703305375">"ワイルド文字を置換:"</string>
- <string name="no_vm_number" msgid="6623853880546176930">"ボイスメール番号がありません"</string>
- <string name="no_vm_number_msg" msgid="5165161462411372504">"SIMカードにボイスメールの番号がありません。"</string>
+ <string name="no_vm_number" msgid="6623853880546176930">"留守番電話の番号がありません"</string>
+ <string name="no_vm_number_msg" msgid="5165161462411372504">"SIMカードに留守番電話の番号がありません。"</string>
<string name="add_vm_number_str" msgid="7368168964435881637">"番号を追加"</string>
- <string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"ボイスメール設定を変更できるのはメインユーザーのみに限られています。"</string>
+ <string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"留守番電話の設定を変更できるのはメインユーザーのみに限られています。"</string>
<string name="puk_unlocked" msgid="4627340655215746511">"SIMカードロックを解除しました。デバイスのロックを解除しています..."</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM のネットワーク ロック解除 PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"携帯通信会社の SIM ロックがかかっています"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"ロック解除"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"無効"</string>
<string name="requesting_unlock" msgid="930512210309437741">"ネットワークのロック解除をリクエスト中..."</string>
@@ -76,8 +77,8 @@
<string name="phone_accounts_all_calling_accounts_summary" msgid="2214134955430107240">"通話できるアカウントを選択"</string>
<string name="wifi_calling" msgid="3650509202851355742">"Wi-Fi 通話"</string>
<string name="connection_service_default_label" msgid="7332739049855715584">"組み込みの接続サービス"</string>
- <string name="voicemail" msgid="7697769412804195032">"ボイスメール"</string>
- <string name="voicemail_settings_with_label" msgid="4228431668214894138">"ボイスメール(<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
+ <string name="voicemail" msgid="7697769412804195032">"留守番電話"</string>
+ <string name="voicemail_settings_with_label" msgid="4228431668214894138">"留守番電話(<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="voicemail_abbreviated" msgid="7746778673131551185">"VM:"</string>
<string name="make_and_receive_calls" msgid="4868913166494621109">"通話の発着と着信"</string>
<string name="smart_forwarding_settings_menu" msgid="8850429887958938540">"スマート転送"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g>に転送する"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"OFF"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"ご利用の携帯通信会社は着信不能時の転送の無効化をサポートしていません。"</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"ご利用の携帯通信会社では電話の転送機能がサポートされていません。"</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"通話中着信を ON にしますか?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"通話中でも他の通話の着信通知が届きます"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"ON にする"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"キャンセル"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS での CDMA 通話中着信が ON になっています"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS での CDMA 通話中着信が OFF になっています"</string>
<string name="updating_title" msgid="6130548922615719689">"通話設定"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"通話設定は管理者ユーザーのみが変更できます。"</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"設定(<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -147,12 +155,12 @@
<item msgid="6813323051965618926">"番号を非通知"</item>
<item msgid="9150034130629852635">"番号を通知"</item>
</string-array>
- <string name="vm_changed" msgid="4739599044379692505">"ボイスメールの番号が変更されました。"</string>
+ <string name="vm_changed" msgid="4739599044379692505">"留守番電話の番号が変更されました。"</string>
<string name="vm_change_failed" msgid="7877733929455763566">"ボイスメール番号を変更できませんでした。\nこの問題が続くようであれば携帯通信会社にお問い合わせください。"</string>
<string name="fw_change_failed" msgid="9179241823460192148">"転送番号を変更できませんでした。\nこの問題が続くようであれば携帯通信会社にお問い合わせください。"</string>
<string name="fw_get_in_vm_failed" msgid="2432678237218183844">"現在の転送先番号設定の取得と保存ができませんでした。\nかまわず新しい携帯通信会社に切り替えますか?"</string>
<string name="no_change" msgid="3737264882821031892">"変更されていません。"</string>
- <string name="sum_voicemail_choose_provider" msgid="6750824719081403773">"ボイスメールサービスを選択"</string>
+ <string name="sum_voicemail_choose_provider" msgid="6750824719081403773">"留守番電話サービスを選択"</string>
<string name="voicemail_default" msgid="6427575113775462077">"携帯通信会社"</string>
<string name="vm_change_pin_old_pin" msgid="7154951790929009241">"古い PIN"</string>
<string name="vm_change_pin_new_pin" msgid="2656200418481288069">"新しい PIN"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"SIM 機能の制限"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> の通話およびデータサービスは、<xliff:g id="PHONE_NUMBER">%2$s</xliff:g> を使用している間はブロックされる可能性があります。"</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"別の SIM を使用中は、<xliff:g id="CARRIER_NAME">%1$s</xliff:g> の通話とデータサービスがブロックされる可能性があります。"</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"サポートが終了した SIP アカウントが検出されたため、削除いたしました"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP 通話は、Android プラットフォームではサポートされなくなりました。\nこのため、お客様の既存の SIP アカウント(<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>)を削除いたしました。\nデフォルトの通話アカウントの設定をご確認ください。"</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"設定に移動"</string>
<string name="data_usage_title" msgid="8438592133893837464">"アプリのデータ使用量"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g>のモバイルデータ使用量: <xliff:g id="ID_1">%1$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"詳細設定"</string>
@@ -504,10 +515,11 @@
<string name="puk2_requested" msgid="6992374450720307514">"パスワードが正しくありません。SIMはロックされました。PUK2を入力してください。"</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2は完全にブロックされています。"</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"入力できるのはあと<xliff:g id="NUMBER">%d</xliff:g>回です。"</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 でロックされています。ロックを解除するには、サービス プロバイダにお問い合わせください。"</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2はブロックされなくなりました"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"ネットワークまたはSIMカードのエラー"</string>
<string name="doneButton" msgid="7371209609238460207">"完了"</string>
- <string name="voicemail_settings_number_label" msgid="1265118640154688162">"ボイスメールの番号"</string>
+ <string name="voicemail_settings_number_label" msgid="1265118640154688162">"留守番電話の番号"</string>
<string name="card_title_dialing" msgid="8742182654254431781">"発信中"</string>
<string name="card_title_redialing" msgid="18130232613559964">"リダイヤル中"</string>
<string name="card_title_conf_call" msgid="901197309274457427">"グループ通話"</string>
@@ -516,15 +528,16 @@
<string name="card_title_on_hold" msgid="9028319436626975207">"保留中"</string>
<string name="card_title_hanging_up" msgid="814874106866647871">"通話終了"</string>
<string name="card_title_in_call" msgid="8231896539567594265">"着信"</string>
- <string name="notification_voicemail_title" msgid="3932876181831601351">"新しいボイスメール"</string>
- <string name="notification_voicemail_title_count" msgid="2806950319222327082">"新しいボイスメール(<xliff:g id="COUNT">%d</xliff:g>)"</string>
+ <string name="notification_voicemail_title" msgid="3932876181831601351">"新しい留守番電話"</string>
+ <string name="notification_voicemail_title_count" msgid="2806950319222327082">"新しい留守番電話(<xliff:g id="COUNT">%d</xliff:g>)"</string>
<string name="notification_voicemail_text_format" msgid="5720947141702312537">"<xliff:g id="VOICEMAIL_NUMBER">%s</xliff:g>にダイヤル"</string>
- <string name="notification_voicemail_no_vm_number" msgid="3423686009815186750">"ボイスメールの番号が不明です"</string>
+ <string name="notification_voicemail_no_vm_number" msgid="3423686009815186750">"留守番電話の番号が不明です"</string>
<string name="notification_network_selection_title" msgid="255595526707809121">"通信サービスなし"</string>
<string name="notification_network_selection_text" msgid="553288408722427659">"選択したネットワーク(<xliff:g id="OPERATOR_NAME">%s</xliff:g>)は利用できません"</string>
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"通話を発信するには、モバイル ネットワークを ON にし、機内モードまたはバッテリー セーバー モードを OFF にしてください。"</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"電話をかけるには機内モードをオフにしてください。"</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"電話をかけるには、機内モードをオフにするか無線ネットワークに接続してください。"</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"スマートフォンが熱くなりすぎています"</b>\n\n"この通話を完了できません。スマートフォンの熱が冷めてから、もう一度お試しください。\n\n緊急通報は通常どおり発信できます。"</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"緊急通報以外の通話を発信するには、緊急通報待機モードを終了してください。"</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"ご加入の通信サービスがありません"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"モバイルネットワークが利用できません。"</string>
@@ -655,7 +668,7 @@
<string name="description_dial_button" msgid="8614631902795087259">"発信"</string>
<string name="description_dialpad_button" msgid="7395114120463883623">"ダイヤルパッドを表示"</string>
<string name="pane_title_emergency_dialpad" msgid="3627372514638694401">"緊急通報ダイヤルパッド"</string>
- <string name="voicemail_visual_voicemail_switch_title" msgid="6610414098912832120">"ビジュアルボイスメール"</string>
+ <string name="voicemail_visual_voicemail_switch_title" msgid="6610414098912832120">"ビジュアル留守番電話"</string>
<string name="voicemail_set_pin_dialog_title" msgid="7005128605986960003">"PIN の設定"</string>
<string name="voicemail_change_pin_dialog_title" msgid="4633077715231764435">"PIN の変更"</string>
<string name="preference_category_ringtone" msgid="8787281191375434976">"着信音とバイブレーション"</string>
@@ -680,17 +693,17 @@
<string name="callFailed_low_battery" msgid="4056828320214416182">"電池残量が低下したため、ビデオハングアウトが終了しました。"</string>
<string name="callFailed_emergency_call_over_wfc_not_available" msgid="5944309590693432042">"この場所では Wi-Fi 通話による緊急通報はご利用いただけません。"</string>
<string name="callFailed_wfc_service_not_available_in_this_location" msgid="3624536608369524988">"この場所では Wi-Fi 通話はご利用いただけません。"</string>
- <string name="change_pin_title" msgid="3564254326626797321">"ボイスメール PIN の変更"</string>
+ <string name="change_pin_title" msgid="3564254326626797321">"留守番電話 PIN の変更"</string>
<string name="change_pin_continue_label" msgid="5177011752453506371">"続行"</string>
<string name="change_pin_cancel_label" msgid="2301711566758827936">"キャンセル"</string>
<string name="change_pin_ok_label" msgid="6861082678817785330">"OK"</string>
<string name="change_pin_enter_old_pin_header" msgid="853151335217594829">"古い PIN の確認"</string>
- <string name="change_pin_enter_old_pin_hint" msgid="8801292976275169367">"続行するにはボイスメール PIN を入力してください。"</string>
+ <string name="change_pin_enter_old_pin_hint" msgid="8801292976275169367">"続行するには留守番電話 PIN を入力してください。"</string>
<string name="change_pin_enter_new_pin_header" msgid="4739465616733486118">"新しい PIN の設定"</string>
<string name="change_pin_enter_new_pin_hint" msgid="2326038476516364210">"PIN は <xliff:g id="MIN">%1$d</xliff:g>~<xliff:g id="MAX">%2$d</xliff:g> 桁にしてください。"</string>
<string name="change_pin_confirm_pin_header" msgid="2606303906320705726">"PIN の確認"</string>
<string name="change_pin_confirm_pins_dont_match" msgid="305164501222587215">"PIN が一致しません"</string>
- <string name="change_pin_succeeded" msgid="2504705600693014403">"ボイスメール PIN を更新しました"</string>
+ <string name="change_pin_succeeded" msgid="2504705600693014403">"留守番電話 PIN を更新しました"</string>
<string name="change_pin_system_error" msgid="7772788809875146873">"PIN を設定できません"</string>
<string name="mobile_data_status_roaming_turned_off_subtext" msgid="6840673347416227054">"データ ローミングが OFF になっています"</string>
<string name="mobile_data_status_roaming_turned_on_subtext" msgid="5615757897768777865">"データ ローミングが ON になっています"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"デフォルトのデータ SIM の SUBID:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL 帯域幅(kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL 帯域幅(kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"セルの位置情報(サポート終了):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE の物理チャネル設定:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"セル情報の更新間隔:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"すべてのセルの測定情報:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"スマートフォン情報"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"携帯通信会社のプロビジョニング情報"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"携帯通信会社のプロビジョニングをトリガー"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Bluetooth の信号強度が十分ではありません。スピーカーフォンに切り替えてみてください。"</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"通話品質に関するお知らせ"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"サポートが終了した SIP アカウント"</string>
</resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 4d26062..dfd6c9a 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"ხმოვანი ფოსტის პარამეტრების შეცვლა მხოლოდ ძირითად მომხმარებელს შეუძლია."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"თქვენი SIM ბარათი განიბლოკა. მიმდინარეობს თქვენი ტელეფონის განბლოკვა…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM ქსელის განბლოკვის PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM ჩაკეტილია ოპერატორისთვის"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"განბლოკვა"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"იგნორირება"</string>
<string name="requesting_unlock" msgid="930512210309437741">"ქსელის განბლოკვის მოთხოვნა…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"გადამისამართება <xliff:g id="PHONENUMBER">{0}</xliff:g>-ზე"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"გამორთული"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"თქვენს ოპერატორს ტელეფონის მიუწვდომელობის დროს ზარის გადამისამართების გაუქმების მხარდაჭერა არ გააჩნია."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"თქვენი ოპერატორი ზარის გადამისამართებას მხარს არ უჭერს."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"გსურთ ზარის ლოდინის ჩართვა?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"ზარის დროს გეცნობებათ შემომავალი ზარების შესახებ"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"ჩართვა"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"გაუქმება"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA ზარის ლოდინი IMS-ში ჩართულია"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA ზარის ლოდინი IMS-ში გამორთულია"</string>
<string name="updating_title" msgid="6130548922615719689">"ზარის პარამეტრები"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"ზარის პარამეტრების შეცვლა მხოლოდ მომხმარებელ-ადმინისტრატორს შეუძლია."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"პარამეტრები (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"SIM ბარათის შეზღუდული ფუნქციონალობა"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>-ის ზარებისა და მობილური ინტერნეტის სერვისები შეიძლება დაიბლოკოს <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>-ის გამოყენებისას."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> ზარები და მობ.ინტ. სერვისები შეიძლება დაიბლოკოს სხვა SIM ბარათის გამოყენებისას."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"მოძველებული SIP ანგარიშები მოიძებნა და წაიშალა"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP დარეკვა აღარ არის მხარდაჭერილი Android პლატფორმით.\nთქვენი არსებული SIP ანგარიშები <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> წაშლილია.\nგთხოვთ, დაადასტუროთ თქვენი დარეკვის ნაგულისხმევი ანგარიშის პარამეტრი."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"პარამეტრებზე გადასვლა"</string>
<string name="data_usage_title" msgid="8438592133893837464">"აპის მონაცემთა გამოყენება"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> მობილური ინტერნეტი გამოყენებულია: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"გაფართოებული"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"პაროლი არასწორია. SIM ახლა დაბლოკილია. შეიყვანეთ PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 სამუდამოდ დაიბლოკა."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n" თქვენ გაქვთ <xliff:g id="NUMBER">%d</xliff:g> დარჩენილი მცდელობა."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 ჩაკეტილია. განსაბლოკად დაუკავშირდით სერვისის პროვაიდერს."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 აღარ არის დაბლოკილი"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"ქსელის ან SIM ბარათის შეცდომა"</string>
<string name="doneButton" msgid="7371209609238460207">"დასრულდა"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"დასარეკად ჩართეთ მობილური ქსელი, გამორთეთ თვითმფრინავის რეჟიმი ან ბატარეის დაზოგვის რეჟიმი."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"ზარის განსახორციელებლად, გამორთეთ თვითმფრინავის რეჟიმი."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"ზარის განსახორციელებლად, გამორთეთ თვითმფრინავის რეჟიმი ან დაუკავშირდით უსადენო ქსელს."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"ტელეფონი ძალიან ცხელია"</b>\n\n"ამ ზარის დასრულება შეუძლებელია. სცადეთ ხელახლა, როდესაც ტელეფონი გაგრილდება.\n\nგადაუდებელი ზარების განხორციელება მაინც შეგიძლიათ."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"გამოდით გადაუდებელი გადმორეკვის რეჟიმიდან არაგადაუდებელი ზარის განსახორციელებლად."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"ქსელში რეგისტრირებული არ არის."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"მობილური ქსელი მიუწვდომელია."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"მონაცემების ნაგულისხმევი SIM-ის subId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL-არხის გამტარუნარიანობა (კბიტ/წმ):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL-არხის გამტარუნარიანობა (კბიტ/წმ):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"ფიჭური მდებარეობის ინფორმაცია (მოძველდა):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ფიზიკური არხის კონფიგურაცია:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"ფიჭური ინფორმაციის განახლების სიხშირე:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"სრული ინფორმაცია ფიჭური ქსელის შესახებ:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"ტელეფონის ინფორმაცია"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"ოპერატორის უზრუნველყოფის ინფორმაცია"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"ოპერატორის უზრუნველყოფის გააქტიურება"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"თქვენი Bluetooth სიგნალი სუსტია. სცადეთ სპიკერფონზე გადართვა."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"შეტყობინება ზარის ხარისხის შესახებ"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"მოძველებული SIP ანგარიშები"</string>
</resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 5ea8166..baf170c 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Дауыстық пошта параметрлерін тек негізгі пайдаланушы өзгерте алады."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"SIM картаңыз ашылды. Телефоныңыздың бекітпесі ашылуда…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM желісін ашатын PIN код"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM картасы келесі операторға құлыпталған"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Бекітпесін ашу"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Бас тарту"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Желінің бекітпесін ашуды өтінуде…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> нөміріне бағытталуда"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Өшірулі"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Телефоныңыз қол жетімсіз болғанда жабдықтаушы қоңырауды басқа нөмірге бағыттауды өшіруді қолдамайды."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Операторыңыз қоңырауды басқа нөмірге бағыттауды қолдамайды."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Қоңырауды ұстап тұру мүмкіндігін қосу керек пе?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Сөйлесіп жатқанда сізге кіріс қоңыраулар туралы хабарландыру көрсетіледі."</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Қосу"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Бас тарту"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS бойынша басқа CDMA желісіндегі қоңырауды ұстап тұру мүмкіндігі қосулы."</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS бойынша басқа CDMA желісіндегі қоңырауды ұстап тұру мүмкіндігі өшірулі."</string>
<string name="updating_title" msgid="6130548922615719689">"Қоңырау параметрлері"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Қоңырау параметрлерін тек әкімші пайдаланушы өзгерте алады."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Параметрлер (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"SIM картасының жұмысы шектеулі"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g> нөмірін пайдаланған кезде <xliff:g id="CARRIER_NAME">%1$s</xliff:g> қоңыраулары мен дерек тасымалдау қызметтері бөгелуі мүмкін."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Екінші SIM картасын пайдаланылғанда, <xliff:g id="CARRIER_NAME">%1$s</xliff:g> қоңырауы мен дерек қызметі бөгелуі мүмкін."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Қолданыстан шыққан SIP аккаунттары табылды және өшірілді"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Android платформасында бұдан былай SIP арқылы қоңырау шалу мүмкін емес.\nҚазіргі SIP аккаунтларыңыз (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>) өшірілді.\nҚоңырау шалуға қолданылатын әдепкі аккаунт параметрін растаңыз."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Параметрлерге өту"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Қолданба деректерінің трафигі"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g> аралығында <xliff:g id="ID_1">%1$s</xliff:g> мобильдік дерек қолданылды"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Қосымша"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Құпия сөз дұрыс емес. SIM қазір бекітілген. PUK2 кодын енгізіңіз."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 уақытша бөгелген."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"<xliff:g id="NUMBER">%d</xliff:g> әрекет қалды."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 құлыптаулы. Құлпын ашу үшін қызмет көрсетушіге хабарласыңыз."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 енді бөгелмеген"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Желі немесе SIM картасының қатесі"</string>
<string name="doneButton" msgid="7371209609238460207">"Дайын"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Қоңырау шалу мүмкін болуы үшін мобильдік байланысты қосып, ұшақ режимін немесе батарея үнемдеу режимін өшіріңіз."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Қоңырау шалу үшін ұшақ режимін өшіріңіз."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Қоңырау шалу үшін ұшақ режимін өшіріңіз не сымсыз желіге қосылыңыз."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Телефон қызып кетті"</b>\n\n"Қоңырауды аяқтау мүмкін емес. Телефон суығаннан кейін, әрекетті қайталап көріңіз.\n\nҚұтқару қызметіне қоңырау шалу бұрынғыша мүмкін болады."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Төтенше емес қоңырау шалу үшін төтенше қоңырауды кері шалу режимінен шығыңыз."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Желіде тіркелмеген."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Ұялы желі қол жетімсіз."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Әдепкі деректер SIM картасының қосалқы идентификаторы:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL өткізу мүмкіндігі (кбит/сек):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL өткізу мүмкіндігі (кбит/сек):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Ұялы желінің орналасқан жері туралы ақпарат (қолданыстан шығарылды):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE физикалық арна конфигурациясы:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Ұялы желі туралы ақпаратты жаңарту жиілігі:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Барлық ұялы желі өлшемдері туралы ақпарат:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Телефон ақпараты"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Операторды инициализациялау туралы ақпарат"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Операторды инциализациялауды іске қосу"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Bluetooth сигналы нашар. Спикерфонға ауысып көріңіз."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Қоңырау сапасы туралы хабарландыру"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Қолданыстан шыққан SIP аккаунттары"</string>
</resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index d8bd447..fb246f9 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"មានតែអ្នកប្រើប្រាស់ចម្បងទើបអាចកែប្រែការកំណត់សារជាសំឡេងបាន។"</string>
<string name="puk_unlocked" msgid="4627340655215746511">"បានដោះសោស៊ីមកាតរបស់អ្នក។ ទូរស័ព្ទរបស់អ្នកកំពុងដោះសោ…"</string>
<string name="label_ndp" msgid="7617392683877410341">"ដោះសោកូដ PIN បណ្ដាញស៊ីមកាត"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"ស៊ីមត្រូវបានចាក់សោសម្រាប់ប្រតិបត្តិករ"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"ដោះសោ"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"បោះបង់"</string>
<string name="requesting_unlock" msgid="930512210309437741">"កំពុងស្នើដោះសោបណ្ដាញ…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"បញ្ជូនបន្តទៅ <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"បិទ"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"ក្រុមហ៊ុនបញ្ជូនរបស់អ្នកមិនគាំទ្រការបិទការហៅបញ្ជូនបន្ត នៅពេលទូរស័ព្ទរបស់អ្នកមិនអាចហៅចូល។"</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"ក្រុមហ៊ុនសេវាទូរសព្ទរបស់អ្នកមិនផ្ដល់ជូនការហៅបន្តទេ។"</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"បើកការរង់ចាំការហៅទូរសព្ទឬ?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"ក្នុងពេលហៅទូរសព្ទ អ្នកនឹងទទួលបានការជូនដំណឹងអំពីការហៅចូល"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"បើក"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"បោះបង់"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"ការរង់ចាំការហៅ ទូរសព្ទ CDMA ក្រោម IMS ត្រូវបានបើក"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"ការរង់ចាំការហៅ ទូរសព្ទ CDMA ក្រោម IMS ត្រូវបានបិទ"</string>
<string name="updating_title" msgid="6130548922615719689">"កំណត់ការហៅ"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"ការកំណត់ការហៅអាចផ្លាស់ប្តូរបានដោយអ្នកប្រើដែលមានសិទ្ធិគ្រប់គ្រងតែប៉ុណ្ណោះ។"</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"ការកំណត់ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"មុខងារស៊ីមមានកម្រិត"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"សេវាកម្មទិន្នន័យ និងការហៅទូរសព្ទតាម <xliff:g id="CARRIER_NAME">%1$s</xliff:g> អាចត្រូវបានទប់ស្កាត់ ពេលប្រើប្រាស់ <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>។"</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"សេវាកម្មទិន្នន័យ និងការហៅទូរសព្ទតាម <xliff:g id="CARRIER_NAME">%1$s</xliff:g> អាចត្រូវបានទប់ស្កាត់ ពេលប្រើស៊ីមផ្សេង។"</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"បានរកឃើញ និងបានលុបគណនី SIP ដែលបានបញ្ឈប់"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"ការហៅទូរសព្ទតាម SIP មិនអាចប្រើជាមួយប្រព័ន្ធ Android បានទៀតទេ។\nគណនី SIP ដែលមានស្រាប់របស់អ្នក <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> ត្រូវបានលុប។\nសូមបញ្ជាក់ការកំណត់គណនីនៃការហៅទូរសព្ទលំនាំដើមរបស់អ្នក។"</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"ចូលទៅកាន់ការកំណត់"</string>
<string name="data_usage_title" msgid="8438592133893837464">"ការប្រើប្រាស់ទិន្នន័យកម្មវិធី"</string>
<string name="data_usage_template" msgid="6287906680674061783">"បានប្រើទិន្នន័យទូរសព្ទអស់ <xliff:g id="ID_1">%1$s</xliff:g> នៅថ្ងៃទី <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"កម្រិតខ្ពស់"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"ពាក្យសម្ងាត់មិនត្រឹមត្រូវ។ ឥឡូវនេះស៊ីមកាតត្រូវបានចាក់សោ។ បញ្ចូល PUK2។"</string>
<string name="puk2_blocked" msgid="3131139031779319911">"លេខកូដ PUK2 ត្រូវបានទប់ស្កាត់ជាអចិន្ត្រៃយ៍។"</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"អ្នកនៅសល់ការព្យាយាមបញ្ចូល <xliff:g id="NUMBER">%d</xliff:g> ដងទៀត។"</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"បានចាក់សោដោយប្រើ PUK2។ សូមទាក់ទងទៅក្រុមហ៊ុនផ្ដល់សេវា ដើម្បីដោះសោ។"</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"លេខសម្ងាត់ PIN2 មិនត្រូវបានរារាំងទៀតទេ"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"បញ្ហាស៊ីមកាត ឬបណ្ដាញ។"</string>
<string name="doneButton" msgid="7371209609238460207">"រួចរាល់"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"បើកបណ្ដាញទូរសព្ទចល័ត បិទមុខងារពេលជិះយន្តហោះ ឬបិទមុខងារកម្មវិធីសន្សំថ្ម ដើម្បីហៅទូរសព្ទ។"</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"បិទរបៀបយន្តហោះដើម្បីធ្វើការហៅ។"</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"បិទរបៀបយន្តហោះ ឬភ្ជាប់ទៅបណ្តាញឥតខ្សែដើម្បីធ្វើការហៅ។"</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"ទូរសព្ទក្ដៅពេក"</b>\n\n"មិនអាចធ្វើការហៅទូរសព្ទនេះបានទេ។ សូមព្យាយាមម្ដងទៀត នៅពេលទូរសព្ទរបស់អ្នកចុះត្រជាក់។\n\nអ្នកនៅតែអាចធ្វើការហៅទៅលេខសង្គ្រោះបន្ទាន់បាន។"</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"ចាកចេញពីរបៀបហៅទៅវិញពេលមានអាសន្នដើម្បីធ្វើការហៅធម្មតា។"</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"មិនបានចុះឈ្មោះនៅលើបណ្ដាញ។"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"បណ្ដាញឧបករណ៍ចល័តមិនអាចប្រើបាន។"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"លេខសម្គាល់រងរបស់ស៊ីមទិន្នន័យលំនាំដើម៖"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"កម្រិតបញ្ជូន DL (kbps) ៖"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"កម្រិតបញ្ជូន UL (kbps) ៖"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"ព័ត៌មានទីតាំងទូរសព្ទចល័ត (បានបញ្ឈប់)៖"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"ការកំណត់រចនាសម្ព័ន្ធបណ្ដាញរូបវ័ន្ត LTE ៖"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"អត្រាផ្ទុកឡើងវិញនៃព័ត៌មានទូរសព្ទចល័ត៖"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"ព័ត៌មានវាស់ទូរសព្ទចល័តទាំងអស់៖"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"ព័ត៌មានទូរសព្ទ"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"ព័ត៌មានអំពីការផ្តល់ទិន្នន័យរបស់ក្រុមហ៊ុនសេវាទូរសព្ទ"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"នាំឱ្យមានការផ្តល់ទិន្នន័យពីក្រុមហ៊ុនសេវាទូរសព្ទ"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"រលកសញ្ញាប៊្លូធូសរបស់អ្នកមានកម្រិតខ្សោយ។ សូមសាកល្បងប្ដូរទៅឧបករណ៍បំពងសំឡេងទូរសព្ទ។"</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"ការជូនដំណឹងអំពីគុណភាពហៅទូរសព្ទ"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"គណនី SIP ដែលបានបញ្ឈប់"</string>
</resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index c5c0eeb..e1b6827 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"ಧ್ವನಿಮೇಲ್ ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಕೇವಲ ಪ್ರಾಥಮಿಕ ಬಳಕೆದಾರರು ಮಾತ್ರ ಮಾರ್ಪಡಿಸಬಹುದು."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"ನಿಮ್ಮ ಸಿಮ್ ಕಾರ್ಡ್ನ ನಿರ್ಬಂಧವನ್ನು ತೆಗೆಯಲಾಗಿದೆ. ನಿಮ್ಮ ಫೋನ್ ಅನ್ಲಾಕ್ ಆಗುತ್ತಿದೆ…"</string>
<string name="label_ndp" msgid="7617392683877410341">"ಸಿಮ್ ನೆಟ್ವರ್ಕ್ನ ಅನ್ಲಾಕ್ ಮಾಡುವ ಪಿನ್"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"ಆಪರೇಟರ್ಗಾಗಿ SIM ಅನ್ನು ಲಾಕ್ ಮಾಡಲಾಗಿದೆ"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"ಅನ್ಲಾಕ್"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"ವಜಾಗೊಳಿಸಿ"</string>
<string name="requesting_unlock" msgid="930512210309437741">"ನೆಟ್ವರ್ಕ್ ಅನ್ನು ಅನ್ಲಾಕ್ ಮಾಡಲು ವಿನಂತಿಸಲಾಗುತ್ತಿದೆ…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> ಗೆ ಫಾರ್ವರ್ಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"ಆಫ್"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"ನಿಮ್ಮ ಫೋನ್ ಅನ್ನು ತಲುಪಲಾಗದಿದ್ದಾಗ ನಿಮ್ಮ ವಾಹಕ ಕರೆ ಫಾರ್ವರ್ಡ್ ಮಾಡುವಿಕೆ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"ನಿಮ್ಮ ವಾಹಕವು ಕರೆ ಫಾರ್ವರ್ಡ್ ಮಾಡುವಿಕೆಯನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"ಕರೆ ನಿರೀಕ್ಷೆಯನ್ನು ಆನ್ ಮಾಡಬೇಕೆ?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"ಕರೆಯ ಸಮಯದಲ್ಲಿ, ಒಳಬರುವ ಕರೆಗಳ ಕುರಿತು ನಿಮಗೆ ಸೂಚಿಸಲಾಗುತ್ತದೆ"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"ಆನ್ ಮಾಡಿ"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"ರದ್ದುಮಾಡಿ"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS ಆನ್ ಅಡಿಯಲ್ಲಿ CDMA ಕರೆ ನಿರೀಕ್ಷೆ"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS ಆಫ್ ಅಡಿಯಲ್ಲಿ CDMA ಕರೆ ನಿರೀಕ್ಷೆ"</string>
<string name="updating_title" msgid="6130548922615719689">"ಕರೆ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"ಕರೆ ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಕೇವಲ ನಿರ್ವಾಹಕ ಬಳಕೆದಾರರು ಮಾತ್ರ ಬದಲಾಯಿಸಬಹುದು."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"ಸೆಟ್ಟಿಂಗ್ಗಳು (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"ಸೀಮಿತ ಸಿಮ್ ಕಾರ್ಯನಿರ್ವಹಣೆ"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g> ಬಳಸುತ್ತಿರುವಾಗ <xliff:g id="CARRIER_NAME">%1$s</xliff:g> ಕರೆಗಳು ಮತ್ತು ಡೇಟಾ ಸೇವೆಗಳನ್ನು ನಿರ್ಬಂಧಿಸಬಹುದು."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"ಬೇರೊಂದು ಸಿಮ್ ಬಳಸುತ್ತಿರುವಾಗ <xliff:g id="CARRIER_NAME">%1$s</xliff:g> ಕರೆಗಳು ಮತ್ತು ಡೇಟಾ ಸೇವೆಗಳನ್ನು ನಿರ್ಬಂಧಿಸಬಹುದು."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"ತಡೆಹಿಡಿಯಲಾಗಿರುವ SIP ಖಾತೆಗಳನ್ನು ಪತ್ತೆಮಾಡಲಾಗಿದೆ ಮತ್ತು ತೆಗೆದುಹಾಕಲಾಗಿದೆ"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Android ಪ್ಲ್ಯಾಟ್ಫಾರ್ಮ್ ಇನ್ನು ಮುಂದೆ SIP ಕರೆ ಮಾಡುವಿಕೆಯನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ.\nನಿಮ್ಮ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ SIP ಖಾತೆಗಳನ್ನು <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> ತೆಗೆದುಹಾಕಲಾಗಿದೆ.\nನಿಮ್ಮ ಡೀಫಾಲ್ಟ್ ಕರೆ ಮಾಡುವಿಕೆ ಖಾತೆ ಸೆಟ್ಟಿಂಗ್ ಅನ್ನು ಖಚಿತಪಡಿಸಿ."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"ಸೆಟ್ಟಿಂಗ್ಗಳಿಗೆ ಹೋಗಿ"</string>
<string name="data_usage_title" msgid="8438592133893837464">"ಅಪ್ಲಿಕೇಶನ್ ಡೇಟಾ ಬಳಕೆ"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> ಬಳಸಲಾದ ಮೊಬೈಲ್ ಡೇಟಾ <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"ಸುಧಾರಿತ"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"ಪಾಸ್ವರ್ಡ್ ತಪ್ಪಾಗಿದೆ. SIM ಅನ್ನು ಇದೀಗ ಲಾಕ್ ಮಾಡಲಾಗಿದೆ. PUK2 ನಮೂದಿಸಿ."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 ಅನ್ನು ಶಾಶ್ವತವಾಗಿ ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"ನೀವು <xliff:g id="NUMBER">%d</xliff:g> ಬಾಕಿ ಉಳಿದಿರುವ ಪ್ರಯತ್ನಗಳನ್ನು ಹೊಂದಿರುವಿರಿ."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 ಲಾಕ್ ಆಗಿದೆ. ಅನ್ಲಾಕ್ ಮಾಡಲು ಸೇವೆ ಒದಗಿಸುವವರನ್ನು ಸಂಪರ್ಕಿಸಿ."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 ಇನ್ನು ಮುಂದೆ ನಿರ್ಬಂಧಿತವಾಗಿರುವುದಿಲ್ಲ"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"ನೆಟ್ವರ್ಕ್ ಅಥವಾ ಸಿಮ್ ಕಾರ್ಡ್ ದೋಷ"</string>
<string name="doneButton" msgid="7371209609238460207">"ಮುಗಿದಿದೆ"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"ಕರೆ ಮಾಡಲು ಮೊಬೈಲ್ ನೆಟ್ವರ್ಕ್ ಆನ್ ಮಾಡಿ, ಏರ್ಪ್ಲೇನ್ ಮೋಡ್ ಅಥವಾ ಬ್ಯಾಟರಿ ಉಳಿತಾಯ ಮೋಡ್ ಅನ್ನು ಆಫ್ ಮಾಡಿ."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"ಕರೆ ಮಾಡಲು ಏರ್ಪ್ಲೇನ್ ಮೋಡ್ ಆಫ್ ಮಾಡಿ."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"ಏರ್ಪ್ಲೇನ್ ಮೋಡ್ ಆಫ್ ಮಾಡಿ ಅಥವಾ ಕರೆ ಮಾಡಲು ವೈರ್ಲೆಸ್ ನೆಟ್ವರ್ಕ್ಗೆ ಸಂಪರ್ಕಿಸಿ."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"ಫೋನ್ ತುಂಬಾ ಬಿಸಿಯಾಗಿದೆ"</b>\n\n"ಈ ಕರೆಯನ್ನು ಪೂರ್ಣಗೊಳಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ. ಫೋನ್ ತಣ್ಣಗಾದ ನಂತರ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ.\n\nನೀವು ಈಗಲೂ ತುರ್ತು ಕರೆಗಳನ್ನು ಮಾಡಬಹುದು."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"ತುರ್ತು ರಹಿತ ಕರೆಯನ್ನು ಮಾಡಲು ತುರ್ತು ಮರು ಕರೆಮಾಡುವಿಕೆ ಮೋಡ್ ಅನ್ನು ನಿರ್ಗಮಿಸಿ."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"ನೆಟ್ವರ್ಕ್ನಲ್ಲಿ ಇನ್ನೂ ನೋಂದಣಿಯಾಗಿಲ್ಲ."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"ಮೊಬೈಲ್ ನೆಟ್ವರ್ಕ್ ಲಭ್ಯವಿಲ್ಲ."</string>
@@ -606,7 +619,7 @@
<string name="ota_skip_activation_dialog_title" msgid="7666611236789203797">"ಸಕ್ರಿಯಗೊಳಿಸುವುದನ್ನು ಸ್ಕಿಪ್ ಮಾಡುವುದೇ?"</string>
<string name="ota_skip_activation_dialog_message" msgid="6691722887019708713">"ನೀವು ಸಕ್ರಿಯಗೊಳಿಸುವುದನ್ನು ಸ್ಕಿಪ್ ಮಾಡಿದರೆ, ನೀವು ಕರೆಗಳನ್ನು ಮಾಡಲು ಅಥವಾ ಮೊಬೈಲ್ ಡೇಟಾ ನೆಟ್ವರ್ಕ್ಗಳಿಗೆ ಸಂಪರ್ಕಗೊಳಿಸಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ (ಆದರೂ ನೀವು ವೈ-ಫೈ ನೆಟ್ವರ್ಕ್ಗಳಿಗೆ ಸಂಪರ್ಕಗೊಳಿಸಬಹುದು). ನಿಮ್ಮ ಫೋನ್ ಅನ್ನು ನೀವು ಸಕ್ರಿಯಗೊಳಿಸುವವರೆಗೂ, ನೀವು ಅದನ್ನು ಪ್ರತಿಬಾರಿ ಆನ್ ಮಾಡಿದಾಗಲೆಲ್ಲಾ ಅದನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲು ನಿಮಗೆ ತಿಳಿಸಲಾಗುತ್ತದೆ."</string>
<string name="ota_skip_activation_dialog_skip_label" msgid="5908029466817825633">"ಸ್ಕಿಪ್"</string>
- <string name="ota_activate" msgid="7939695753665438357">"ಸಕ್ರಿಯಗೊಳಿಸು"</string>
+ <string name="ota_activate" msgid="7939695753665438357">"ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
<string name="ota_title_activate_success" msgid="1272135024761004889">"ಫೋನ್ ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ."</string>
<string name="ota_title_problem_with_activation" msgid="7019745985413368726">"ಸಕ್ರಿಯಗೊಳಿಸುವಲ್ಲಿ ಸಮಸ್ಯೆ"</string>
<string name="ota_listen" msgid="2772252405488894280">"ಸಕ್ರಿಯಗೊಳಿಸುವಿಕೆ ಪೂರ್ಣಗೊಂಡಿದೆ ಎಂಬುದು ನಿಮ್ಮ ಕಿವಿಗೆ ಬೀಳುವ ತನಕ ಧ್ವನಿ ಸೂಚನೆಗಳನ್ನು ಅನುಸರಿಸಿ."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ಡೀಫಾಲ್ಟ್ ಡೇಟಾ ಸಿಮ್ನ ವಿಷಯಐಡಿ:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL ಬ್ಯಾಂಡ್ವಿಡ್ತ್ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL ಬ್ಯಾಂಡ್ವಿಡ್ತ್ (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"ಸೆಲ್ ಸ್ಥಳ ಮಾಹಿತಿ (ತಡೆಹಿಡಿಯಲಾಗಿದೆ):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ಭೌತಿಕ ಚಾನೆಲ್ ಕಾನ್ಫಿಗರೇಶನ್:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"ಸೆಲ್ ಮಾಹಿತಿ ರಿಫ್ರೆಶ್ ದರ:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"ಎಲ್ಲಾ ಸೆಲ್ ಮಾಪನ ಮಾಹಿತಿ:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"ಫೋನ್ ಮಾಹಿತಿ"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"ವಾಹಕ ಪೂರೈಕೆಯ ಮಾಹಿತಿ"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"ಟ್ರಿಗರ್ ವಾಹಕ ಪೂರೈಕೆ"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"ನಿಮ್ಮ ಬ್ಲೂಟೂತ್ ಸಿಗ್ನಲ್ ದುರ್ಬಲವಾಗಿದೆ. ಸ್ಪೀಕರ್ಫೋನ್ಗೆ ಬದಲಾಯಿಸಲು ಪ್ರಯತ್ನಿಸಿ."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"ಕರೆ ಗುಣಮಟ್ಟದ ಅಧಿಸೂಚನೆ"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"ತಡೆಹಿಡಿಯಲಾಗಿರುವ SIP ಖಾತೆಗಳು"</string>
</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 462af54..52ad67b 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"기본 사용자만 음성사서함 설정을 변경할 수 있습니다."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"SIM 카드의 잠금이 해제되었습니다. 휴대전화의 잠금해제 중..."</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM 네트워크 잠금해제 PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"이동통신사 전용으로 SIM 잠김"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"잠금해제"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"취소"</string>
<string name="requesting_unlock" msgid="930512210309437741">"네트워크 잠금해제 요청 중..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g>(으)로 착신전환"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"사용 안함"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"이동통신사에서 전화를 받을 수 없을 때 착신전환 사용 중지를 지원하지 않습니다."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"사용 중인 이동통신사에서는 착신전환 기능을 지원하지 않습니다."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"통화중 대기를 사용 설정하시겠습니까?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"통화 중 다른 전화가 걸려 오면 알림을 받습니다."</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"사용 설정"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"취소"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS에서 CDMA 통화중 대기 사용"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS에서 CDMA 통화중 대기 사용 안함"</string>
<string name="updating_title" msgid="6130548922615719689">"통화 설정"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"관리자만 통화 설정을 변경할 수 있습니다."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"설정(<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"제한된 SIM 기능"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g>번을 사용하는 동안 <xliff:g id="CARRIER_NAME">%1$s</xliff:g> 통화 및 데이터 서비스가 차단될 수 있습니다."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"다른 SIM을 사용하는 동안 <xliff:g id="CARRIER_NAME">%1$s</xliff:g> 통화 및 데이터 서비스가 차단될 수 있습니다."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"지원 중단된 SIP 계정 발견 및 삭제됨"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP 통화가 더 이상 Android 플랫폼에서 지원되지 않습니다.\n기존 SIP 계정(<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>)이 삭제되었습니다.\n기본 통화 계정 설정을 확인해 주세요."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"설정으로 이동"</string>
<string name="data_usage_title" msgid="8438592133893837464">"앱 데이터 사용량"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g>에 사용된 모바일 데이터: <xliff:g id="ID_1">%1$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"고급"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"비밀번호가 잘못되어 SIM이 잠겼습니다. PUK2를 입력하세요."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2가 완전히 차단되었습니다."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"<xliff:g id="NUMBER">%d</xliff:g>번의 기회가 남았습니다."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2로 잠금 설정되었습니다. 모든 통신사에서 사용할 수 있도록 하려면 서비스 제공업체에 문의하세요."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 차단이 해제되었습니다."</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"네트워크 또는 SIM 카드 오류"</string>
<string name="doneButton" msgid="7371209609238460207">"완료"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"전화를 걸려면 모바일 네트워크를 사용 설정하고 비행기 모드 또는 절전 모드를 사용 중지하세요."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"전화를 걸려면 비행기 모드를 사용 중지하세요."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"전화를 걸려면 비행기 모드를 사용 중지하거나 무선 네트워크에 연결하세요."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"휴대전화의 온도가 너무 높습니다."</b>\n\n"현재 전화를 걸 수 없습니다. 휴대전화의 온도가 내려가면 다시 시도해 보세요.\n\n긴급 전화 기능은 계속해서 사용할 수 있습니다."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"일반 전화를 걸려면 긴급 콜백 모드를 해제하세요."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"네트워크에서 등록되지 않았습니다."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"모바일 네트워크를 사용할 수 없습니다."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"기본 데이터 SIM의 subId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL 대역폭(kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL 대역폭(kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"셀 위치 정보(사용 중단됨):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE 물리적 채널 구성:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"셀 정보 새로고침 빈도:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"모든 셀 측정 정보:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"휴대전화 정보"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"이동통신사 프로비저닝 정보"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"이동통신사 프로비저닝 실행"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"블루투스 신호 강도가 약합니다. 스피커폰으로 전환해 보세요."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"통화 품질 알림"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"지원 중단된 SIP 계정"</string>
</resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 01454c4..1420182 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Үн почта жөндөөлөрүн алгачкы колдонуучу гана өзгөртө алат."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"SIM картаңыз бөгөттөн чыгарылган. Телефонуңуздун кулпусу ачылууда…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM-карта тармагынын кулпусун ачуучу PIN код"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM-карта оператор үчүн кулпуланган"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Кулпусун ачуу"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Этибарга албоо"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Тармак кулпусун ачуу суралууда…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> номерине багытталууда"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Өчүк"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Сиздин операторуңуз, телефон жеткиликсиз болгондо чалууну багыттоону токтотууну колдобойт."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Операторуңуз чалууну багыттоо функциясын колдобойт."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Чалууну күтүү күйгүзүлсүнбү?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Чалуу учурунда кирүүчү чалуулар тууралуу кабар аласыз"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Күйгүзүү"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Баш тартуу"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS платформасында CDMA чалуу күтүүcү күйгүзүлгөн"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS платформасында CDMA чалуу күтүүcү өчүрүлгөн"</string>
<string name="updating_title" msgid="6130548922615719689">"Чалуу жөндөөлөрү"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Чалуу жөндөөлөрүн администратор гана өзгөртө алат."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Жөндөөлөр (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"SIM-картанын функциялары чектелген"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g> номерин колдонгондо <xliff:g id="CARRIER_NAME">%1$s</xliff:g> чалуулары жана дайындар кызматтары бөгөттөлүшү мүмкүн."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Башка SIM картаны колднгндо <xliff:g id="CARRIER_NAME">%1$s</xliff:g> чалуулары жана дайындар кызмттары бөгөттлшү мүмкүн."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Колдонуудан чыккан SIP аккаунттары табылды жана өчүрүлдү"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP аркылуу чалуу Android платформасында мындан ары колдоого алынбайт.\nУчурдагы SIP аккаунттарыңыз (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>) өчүрүлдү.\nЧалууларга колдонулган демейки аккаунтун жөндөөсүн ырастаңыз."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Жөндөөлөргө өтүү"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Колдонмолордун трафиги"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g> аралыгында <xliff:g id="ID_1">%1$s</xliff:g> мобилдик трафик колдонулду"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Өркүндөтүлгөн"</string>
@@ -330,7 +341,7 @@
<string name="general_news_settings" msgid="2670499575962080411">"Жалпы жаңылыктар"</string>
<string name="bf_news_settings" msgid="8571709425370794221">"Ишкердик жана каржы жаңылыктары"</string>
<string name="sports_news_settings" msgid="2684364556989168438">"Спорт жаңылыктары"</string>
- <string name="entertainment_news_settings" msgid="4228527702346305543">"Көңүл ачуу жаңылыктары"</string>
+ <string name="entertainment_news_settings" msgid="4228527702346305543">"Көңүл ачуу жаатындагы жаңылыктар"</string>
<string name="enable_disable_local" msgid="7654175079979415572">"Жергиликтүү"</string>
<string name="local_enable" msgid="790606890868710629">"Жергиликтүү жаңылыктар иштетилген"</string>
<string name="local_disable" msgid="7649945293198602877">"Жергиликтүү жаңылыктар өчүрүлгөн"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Сырсөз туура эмес. SIM азыр кулпуланды. PUK2 киргизиңиз."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 биротоло бөгөттөлдү."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Сизде <xliff:g id="NUMBER">%d</xliff:g> аракет калды."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 кулпуланды. Кулпуну ачуу үчүн оператор менен байланышыңыз."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"Мындан ары PIN2 бөгөттөлбөйт"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Тармак же SIM карта катасы"</string>
<string name="doneButton" msgid="7371209609238460207">"Даяр"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Мобилдик тармакты күйгүзүңүз, чалуу үчүн \"Учакта\" режимин же \"Батареяны үнөмдөө\" режимин өчүрүңүз."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Чалуу үчүн учак режимин өчүрүңүз."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Чалуу үчүн учак режимин өчүрүңүз же зымсыз тармакка туташыңыз."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Телефон ысып кетти"</b>\n\n"Бул чалуу аяктабай жатат. Телефон муздагандан кийин кайра аракет кылыңыз.\n\nШашылыш чалууларды аткара берсеңиз болот."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Кадимки шартта чалуу үчүн шашылыш кайра чалуу режиминен чыгыңыз."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Тармакта катталган эмес."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Мобилдик тармак жок."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Демейки оператордун SIM картасынын көз салуу идентификатору:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL өткөрүү жөндөмдүүлүгү (кб/сек.):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL өткөрүү жөндөмдүүлүгү (кб/сек.):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Уюлдук тармакта жайгашкан жери тууралуу маалымат (жоюлган):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE физикалык каналынын конфигурациясы:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Уюлдук маалыматты жаңылоо ылдамдыгы:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Уюлдук чен-өлчөм маалыматтары:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Телефондун маалыматы"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Байланыш оператору жөнүндө маалымат"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Байланыш операторун өзгөртүү"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Bluetooth сигналыңыз начар. Спикерфонго которулуп көрүңүз."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Чалуунун сапаты тууралуу билдирме"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Колдонуудан чыккан SIP аккаунттары"</string>
</resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 08947eb..a7b1640 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"ການຕັ້ງຄ່າຂໍ້ຄວາມສຽງສາມາດແກ້ໄຂໄດ້ໂດຍຜູ້ໃຊ້ຫຼັກເທົ່ານັ້ນ."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"ຍົກເລີກການປິດກັ້ນ SIM card ຂອງທ່ານແລ້ວ. ໂທລະສັບຂອງທ່ານກຳລັງຖືກປົດລັອກ..."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN ປົດລັອກເຄືອຂ່າຍ SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"ລັອກ SIM ສຳລັບຜູ້ໃຫ້ບໍລິການແລ້ວ"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"ປົດລັອກ"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"ປິດໄວ້"</string>
<string name="requesting_unlock" msgid="930512210309437741">"ກຳລັງຮ້ອງຂໍການປົດລັອກເຄືອຂ່າຍ..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"ໂອນສາຍໄປໃຫ້ <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"ປິດ"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"ຜູ່ໃຫ້ບໍລິການຂອງທ່ານ ບໍ່ຮອງຮັບການປິດການໂອນສາຍ ເມື່ອໂທລະສັບຂອງທ່ານບໍ່ມີສັນຍານ."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"ຜູ້ໃຫ້ບໍລິການຂອງທ່ານບໍ່ຮອງຮັບການໂອນສາຍ."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"ເປີດການລໍຖ້າສາຍບໍ?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"ໃນລະຫວ່າງການໂທ, ທ່ານຈະໄດ້ຮັບແຈ້ງກ່ຽວກັບສາຍໂທເຂົ້າ"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"ເປີດ"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"ຍົກເລີກ"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"ການລໍຖ້າສາຍ CDMA ພາຍໃຕ້ IMS ເປີດຢູ່"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"ການລໍຖ້າສາຍ CDMA ພາຍໃຕ້ IMS ປິດຢູ່"</string>
<string name="updating_title" msgid="6130548922615719689">"ການຕັ້ງຄ່າການໂທ"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"ມີແຕ່ຜູ້ໃຊ້ທີ່ເປັນຜູ້ດູແລລະບົບເທົ່ານັ້ນທີ່ສາມາດປ່ຽນການຕັ້ງຄ່າການໂທໄດ້."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"ການຕັ້ງຄ່າ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"ຈຳກັດການເຮັດວຽກຂອງຊິມ"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"ບໍລິການໂທ ແລະ ອິນເຕີເນັດ <xliff:g id="CARRIER_NAME">%1$s</xliff:g> ອາດຖືກບລັອກໄວ້ເມື່ອໃຊ້ເບີໂທ <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"ບໍລິການໂທ ແລະ ອິນເຕີເນັດ <xliff:g id="CARRIER_NAME">%1$s</xliff:g> ອາດຖືກບລັອກໄວ້ເມື່ອໃຊ້ຊິມອື່ນ."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"ພົບ ແລະ ລຶບບັນຊີ SIP ທີ່ເຊົາສະໜັບສະໜູນອອກແລ້ວ"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"ແພລດຟອມ Android ບໍ່ຮອງຮັບການໂທ SIP ອີກຕໍ່ໄປ.\nບັນຊີ SIP ທີ່ທ່ານມີຢູ່ <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> ໄດ້ຖືກລຶບອອກແລ້ວ.\nກະລຸນາຢັ້ງຢືນການຕັ້ງຄ່າບັນຊີການໂທເລີ່ມຕົ້ນຂອງທ່ານ."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"ໄປທີ່ການຕັ້ງຄ່າ"</string>
<string name="data_usage_title" msgid="8438592133893837464">"ການໃຊ້ຂໍ້ມູນແອັບ"</string>
<string name="data_usage_template" msgid="6287906680674061783">"ໃຊ້ອິນເຕີເນັດໄປແລ້ວ <xliff:g id="ID_1">%1$s</xliff:g> ລະຫວ່າງ <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"ຂັ້ນສູງ"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"ລະຫັດຜ່ານບໍ່ຖືກຕ້ອງ. SIM ຖືກລັອກແລ້ວດຽວນີ້. ປ້ອນ PUK2 ເຂົ້າໄປ."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 ຖືກບລັອກຢ່າງຖາວອນແລ້ວ."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"ທ່ານຍັງພະຍາຍາມໄດ້ອີກ <xliff:g id="NUMBER">%d</xliff:g> ຄັ້ງ."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"ລັອກ PUK2 ແລ້ວ. ຕິດຕໍ່ຜູ້ໃຫ້ບໍລິການເພື່ອປົດລັອກ."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 ບໍ່ຖືກບລັອກອີກ"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"ເຄືອຂ່າຍ ຫຼື SIM card ຜິດພາດ"</string>
<string name="doneButton" msgid="7371209609238460207">"ແລ້ວໆ"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"ເປີດໃຊ້ເຄືອຂ່າຍມືຖື, ປິດໂໝດຢູ່ໃນຍົນ ຫຼື ປິດໂໝດຕົວປະຢັດແບັດເຕີຣີເພື່ອໂທ."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"ປິດໂໝດເຄື່ອງບິນເພື່ອເຮັດການໂທ."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"ປິດໂໝດເຄື່ອງບິນ ຫຼືເຊື່ອມຕໍ່ກັບເຄືອຂ່າຍໄຮ້ສາຍເພື່ອເຮັດການໂທ."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"ໂທລະສັບຮ້ອນເກີນໄປ"</b>\n\n"ບໍ່ສາມາດເຮັດການໂທນີ້ໄດ້. ກະລຸນາລອງອີກຄັ້ງເມື່ອໂທລະສັບຂອງທ່ານເຢັນລົງ.\n\nທ່ານຍັງສາມາດໂທສຸກເສີນໄດ້."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"ອອກຈາກໂໝດໂທກັບສຸກເສີນ ເພື່ອເຮັດການໂທປົກກະຕິ."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"ບໍ່ໄດ້ລົງທະບຽນໃນເຄືອຂ່າຍ."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"ເຄືອຂ່າຍມືຖືບໍ່ສາມາດໃຊ້ໄດ້."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId ຂອງຊິມອິນເຕີເນັດເລີ່ມຕົ້ນ:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"ແບນວິດ DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"ແບນວິດ UL (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"ຂໍ້ມູນສະຖານທີ່ມືຖື (ສິ້ນສຸດການສະໜັບສະໜຸນແລ້ວ):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"ການຕັ້ງຄ່າຊ່ອງ LTE ກາຍະພາບ:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"ອັດຕາການໂຫຼດຄືນໃໝ່ຂອງຂໍ້ມູນມືຖື:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"ຂໍ້ມູນການວັດແທກມືຖືທັງໝົດ:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"ຂໍ້ມູນໂທລະສັບ"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"ຂໍ້ມູນການເປີດນຳໃຊ້ເຄືອຂ່າຍ"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"ກະຕຸ້ນການຈັດຫາຜູ້ໃຫ້ບໍລິການ"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"ສັນຍານ Bluetooth ຂອງທ່ານອ່ອນ. ລອງສະຫຼັບລຳໂພງໂທລະສັບ."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"ການແຈ້ງເຕືອນຄຸນນະພາບການໂທ"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"ບັນຊີ SIP ທີ່ເຊົາສະໜັບສະໜູນ"</string>
</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 4760728..44e358f 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Balso pašto nustatymus gali keisti tik pagrindinis naudotojas."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Jūsų SIM kortelė buvo atblokuota. Atrakinamas telefonas..."</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM tinklo atrakinimo PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM kortelę užrakino operatorius"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Atblokuoti"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Atsisakyti"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Pateikiama užklausa atrakinti tinklą..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Peradresuojama į <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Išjungta"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Operatorius nepalaiko skambučių peradresavimo išjungimo, kai telefonas nepasiekiamas."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Operatorius nepalaiko skambučių peradresavimo."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Įjungti laukiamų skambučių paslaugą?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Per skambutį jums bus pranešta apie laukiamus skambučius"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Įjungti"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Atšaukti"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA laukiamų skambučių paslauga įjungus IMS įjungta"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA laukiamų skambučių paslauga įjungus IMS išjungta"</string>
<string name="updating_title" msgid="6130548922615719689">"Skambinimo nustatymai"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Skambučių nustatymus gali keisti tik administruojantis naudotojas."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Nustatymai (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Ribotos SIM kortelės funkcijos"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"„<xliff:g id="CARRIER_NAME">%1$s</xliff:g>“ skambučiai ir duomenų paslaugos gali būti užblokuotos naudojant numerį <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"„<xliff:g id="CARRIER_NAME">%1$s</xliff:g>“ duom. paslaug. ir skambučiai gali būti užblokuoti naudojant kitą SIM kortelę."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Nebenaudojamos SIP paskyros surastos ir pašalintos"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP skambinimas nebepalaikomas „Android“ platformoje.\nEsamos SIP paskyros <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> buvo pašalintos.\nPatvirtinkite savo numatytąjį skambinimo paskyros nustatymą."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Eiti į nustatymus"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Programos duomenų naudojimas"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g> išnaudota mobiliojo ryšio duomenų: <xliff:g id="ID_1">%1$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Išplėstiniai"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Netinkamas slaptažodis. SIM kortelė užrakinta. Įveskite PUK2 kodą."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 kodas visam laikui užblokuotas."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Liko <xliff:g id="NUMBER">%d</xliff:g> bandym."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 užrakintas. Kreipkitės į paslaugos teikėją, kad atrakintų."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 kodas atblokuotas"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Tinklo ar SIM kortelės klaida"</string>
<string name="doneButton" msgid="7371209609238460207">"Atlikta"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Įjunkite mobiliojo ryšio tinklą, išjunkite lėktuvo arba akumuliatoriaus tausojimo priemonės režimą, kad galėtumėte skambinti."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Išjunkite lėktuvo režimą, kad galėtumėte skambinti."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Išjunkite lėktuvo režimą arba prisijunkite prie belaidžio ryšio tinklo, kad galėtumėte skambinti."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefonas per karštas"</b>\n\n"Negalima užbaigti šio skambučio. Bandykite dar kartą, kai telefonas atvės.\n\nJūs vis dar galite skambinti pagalbos numeriais."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Jei norite skambinti ne pagalbos numeriu, išjunkite atgalinio skambinimo pagalbos numeriu režimą."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Neregistruota tinkle."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobilusis tinklas negalimas."</string>
@@ -873,7 +886,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Numatytųjų duomenų SIM kortelės papildomas ID:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL pralaidumas (Kb/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL pralaidumas (Kb/s):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Mobiliojo ryšio vietovės informacija (nebenaudojama):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE fizinio kanalo konfigūracija:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Mobiliojo ryšio informacijos atnaujinimo dažnis:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Visų mobiliųjų ryšių įvertinimo informacija:"</string>
@@ -918,4 +930,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Telefono informacija"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Operatoriaus aprūpinimo informacija"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Aktyviklio operatoriaus aprūpinimas"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Silpnas „Bluetooth“ signalas. Pabandykite perjungti garsiakalbį."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Pranešimas apie skambučio kokybę"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Nebenaudojamos SIP paskyros"</string>
</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 0ba2739..5fe13bb 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Tikai galvenais lietotājs var mainīt balss pasta iestatījumus."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"SIM karte ir atbloķēta. Notiek tālruņa atbloķēšana..."</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM tīkla atbloķēšanas PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM karte ir bloķēta operatoram"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Atbloķēt"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Noraidīt"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Notiek tīkla atbloķēšanas pieprasīšana..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Pāradresēšana uz: <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Izslēgts"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Jūsu mobilo sakaru operators neatbalsta zvanu pāradresācijas atspējošanu, ja tālrunis nav sasniedzams."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Jūsu mobilo sakaru operators neatbalsta zvanu pāradresāciju."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Vai ieslēgt zvanu gaidīšanu?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Zvana laikā saņemsiet paziņojumu par ienākošajiem zvaniem"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Ieslēgt"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Atcelt"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA zvanu gaidīšana, kad ir ieslēgts tūlītējās ziņojumapmaiņas pakalpojums"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA zvanu gaidīšana, kad ir izslēgts tūlītējās ziņojumapmaiņas pakalpojums"</string>
<string name="updating_title" msgid="6130548922615719689">"Zvanu iestatījumi"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Tikai lietotājs ar administratora tiesībām var mainīt zvanu iestatījumus."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Iestatījumi (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Ierobežotas SIM kartes funkcijas"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Izmantojot tālruņa numuru <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>, mobilo sakaru operatora <xliff:g id="CARRIER_NAME">%1$s</xliff:g> zvani un datu pakalpojumi var būt bloķēti."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Izmantojot citu SIM karti, operatora <xliff:g id="CARRIER_NAME">%1$s</xliff:g> zvani un datu pakalpojumi var būt bloķēti."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Tika konstatēti un noņemti SIP konti, kuru darbība ir pārtraukta"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP zvanu izmantošana vairs netiek atbalstīta Android platformā.\nJūsu esošie SIP konti <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> ir noņemti.\nLūdzu, apstipriniet konta iestatījumu, kas pēc noklusējuma tiks izmantots zvaniem."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Atvērt iestatījumus"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Lietotnes datu lietojums"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> mobilo datu izmantoti šādā laika periodā: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Papildu"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Parole nav pareiza. SIM bloķēta. Ievadiet PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 kods ir neatgriezeniski bloķēts."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Jums atlikuši <xliff:g id="NUMBER">%d</xliff:g> mēģinājumi."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 ir bloķēts. Lai atbloķētu, sazinieties ar pakalpojuma sniedzēju."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 vairs nav bloķēts"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Tīkla vai SIM kartes kļūda"</string>
<string name="doneButton" msgid="7371209609238460207">"Gatavs"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Lai veiktu zvanu, ieslēdziet mobilo tīklu, izslēdziet lidojuma režīmu vai izslēdziet akumulatora enerģijas taupīšanas režīmu."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Lai veiktu zvanu, izslēdziet lidojuma režīmu."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Lai veiktu zvanu, izslēdziet lidojuma režīmu vai izveidojiet savienojumu ar bezvadu tīklu."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Tālrunis ir pārkarsis"</b>\n\n"Nevar veikt šo zvanu. Kad tālrunis būs atdzisis, mēģiniet vēlreiz.\n\nJoprojām varat veikt ārkārtas izsaukumus."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Lai veiktu parastu zvanu, izejiet no ārkārtas atzvana režīma."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Tīklā nav reģistrēts."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobilais tīkls nav pieejams."</string>
@@ -870,7 +883,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Noklusējuma datu SIM kartes papildu ID:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL joslas platums (kb/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL joslas platums (kb/s):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Mobilā tālruņa atrašanās vietas informācija (novecojusi):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE fiziskā kanāla konfigurācija:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Mobilā tīkla informācijas atsvaidzināšanas biežums:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Visa mobilā tīkla mērījumu informācija:"</string>
@@ -915,4 +927,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Tālruņa informācija"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Informācija par mobilo sakaru operatora nodrošināšanu"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Aktivizēt operatora nodrošināšanu"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Bluetooth signāls ir vājš. Mēģiniet pārslēgties uz skaļruni."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Paziņojums par zvana kvalitāti"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"SIP konti, kuru darbība ir pārtraukta"</string>
</resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index ef71ae3..b8f98df 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Поставките за говорна пошта може да ги измени само примарниот корисник."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Вашата SIM картичка е одблокирана. Вашиот телефон се отклучува..."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN за отклучување мрежа на SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM-картичката е заклучена за операторот"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Отклучи"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Отфрли"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Барање за отклучување мрежа..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Проследување на <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Исклучено"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Вашиот оператор не поддржува оневозможување проследување повик кога вашиот телефон е недостапен."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Вашиот оператор не поддржува проследување повик."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Да се вклучи повик на чекање?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Во текот на повик ќе добиете известување за дојдовни повици"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Вклучи"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Откажи"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Повикот на чекање CDMA под IMS е вклучен"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Повикот на чекање CDMA под IMS е исклучен"</string>
<string name="updating_title" msgid="6130548922615719689">"Поставки за повици"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Поставките за повик може да ги измени само администраторскиот корисник."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Поставки (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Ограничена функционалност на SIM-картичка"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Повиците и услугите за интернет на <xliff:g id="CARRIER_NAME">%1$s</xliff:g> може да бидат блокирани додека се користи <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Повиците и услугите за интернет на <xliff:g id="CARRIER_NAME">%1$s</xliff:g> може да се блокирани со друга SIM-картичка."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Најдени и отстранети се неподдржани сметки на SIP"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Платформата Android веќе не поддржува повикување преку SIP.\nВашите постојни сметки на SIP <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> се отстранети.\nПотврдете ја вашата стандардна поставка за повикување."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Одете во „Поставки“"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Потрошен интернет од апликации"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> потрошен мобилен интернет во периодот <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Напредни"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Лозинката е неточна. SIM-картичката е сега заклучена. Внесете го ПУК2-кодот."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"ПУК2 кодот е трајно блокиран."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Имате уште <xliff:g id="NUMBER">%d</xliff:g> обиди."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2-кодот е заклучен. Контактирајте со операторот за отклучување."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2-кодот веќе не е блокиран"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Грешка со мрежа или SIM картичка"</string>
<string name="doneButton" msgid="7371209609238460207">"Готово"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Вклучете мобилна мрежа, исклучете го авионскиот режим или пак, штедачот на батеријата за да воспоставите повик."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Исклучете го авионскиот режим за да повикате."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Исклучете го авионскиот режим или поврзете се на безжична мрежа за да повикате."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Телефонот е премногу загреан"</b>\n\n"Повиков не може да се заврши. Обидете се повторно кога ќе се подизлади телефонот.\n\nСѐ уште може да воспоставувате итни повици."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Излезете од режимот на итен повратен повик за да направите обичен повик."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Не е регистриран на мрежа."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Не е достапна мобилна мрежа."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SUBID на стандардната SIM за мобилен интернет:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Брзина на пренос при преземање (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Брзина на пренос при прикачување (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Информации за локација на мобилен (неподдржано):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Конфигурација на физички канал на LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Стапка на освежување на информациите за мобилниот:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Сите информации за мерењата на мобилниот:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Информации за телефонот"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Информации за обезбедување оператор"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Активирајте обезбедување оператор"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Вашиот сигнал на Bluetooth е слаб. Обидете се со префрлање на интерфон."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Известување за квалитет на повик"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Неподдржани сметки на SIP"</string>
</resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 844d0b7..7963e41 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"പ്രാഥമിക ഉപയോക്താവിന് മാത്രമേ വോയ്സ്മെയിൽ ക്രമീകരണം പരിഷ്ക്കരിക്കാനാവൂ."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"സിം കാർഡ് തടഞ്ഞത് മാറ്റി. നിങ്ങളുടെ ഫോൺ അൺലോക്കുചെയ്യുന്നു…"</string>
<string name="label_ndp" msgid="7617392683877410341">"സിം നെറ്റ്വർക്ക് അൺലോക്ക് പിൻ"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"ഓപ്പറേറ്റർക്കായി SIM ലോക്ക് ചെയ്തിരിക്കുന്നു"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"അൺലോക്കുചെയ്യുക"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"നിരസിക്കുക"</string>
<string name="requesting_unlock" msgid="930512210309437741">"നെറ്റ്വർക്ക് അൺലോക്ക് അഭ്യർത്ഥിക്കുന്നു…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> എന്നതിലേക്ക് കൈമാറുന്നു"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"ഓഫ്"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"നിങ്ങളുടെ ഫോൺ പരിധിയ്ക്ക് പുറത്തായിരിക്കുമ്പോൾ കോൾ ഫോർവേഡിംഗ് പ്രവർത്തനരഹിതമാക്കുന്നതിനെ നിങ്ങളുടെ ഓപ്പറേറ്റർ പിന്തുണയ്ക്കുന്നില്ല."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"നിങ്ങളുടെ സേവനദാതാവ് കോൾ കൈമാറ്റം പിന്തുണയ്ക്കുന്നില്ല."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"കോൾ വെയ്റ്റിംഗ് ഓണാക്കണോ?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"കോളിനിടയ്ക്ക് വരുന്ന മറ്റ് ഇൻകമിംഗ് കോളുകളെക്കുറിച്ച് നിങ്ങളെ അറിയിക്കും"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"ഓണാക്കുക"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"റദ്ദാക്കുക"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS-ന് കീഴിലുള്ള CDMA കോൾ വെയ്റ്റിംഗ് ഓണാണ്"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS-ന് കീഴിലുള്ള CDMA കോൾ വെയ്റ്റിംഗ് ഓഫാണ്"</string>
<string name="updating_title" msgid="6130548922615719689">"കോൾ ക്രമീകരണങ്ങൾ"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"അഡ്മിൻ ഉപയോക്താവിന് മാത്രമേ കോൾ ക്രമീകരണം മാറ്റാൻ കഴിയൂ."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"ക്രമീകരണം (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"പരിമിതമായ സിം പ്രവർത്തനം"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g> ഉപയോഗിക്കുമ്പോൾ <xliff:g id="CARRIER_NAME">%1$s</xliff:g> കോളുകളും ഡാറ്റാ സേവനങ്ങളും ബ്ലോക്ക് ചെയ്തേക്കാം."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"മറ്റൊരു SIM ഉപയോഗിക്കുമ്പോൾ <xliff:g id="CARRIER_NAME">%1$s</xliff:g> കോളുകളും ഡാറ്റാ സേവനവും ബ്ലോക്ക് ചെയ്തേക്കാം."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"അവസാനിപ്പിച്ച SIP അക്കൗണ്ടുകൾ കണ്ടെത്തി നീക്കം ചെയ്തു"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP കോൾ ചെയ്യലിന് ഇനി Android പ്ലാറ്റ്ഫോമിൽ പിന്തുണയുണ്ടാകില്ല.\nനിലവിലെ നിങ്ങളുടെ SIP അക്കൗണ്ടുകളായ <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> എന്നിവ നീക്കം ചെയ്തിരിക്കുന്നു.\nനിങ്ങളുടെ ഡിഫോൾട്ട് കോൾ ചെയ്യൽ അക്കൗണ്ട് ക്രമീകരണം സ്ഥിരീകരിക്കുക."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"ക്രമീകരണത്തിലേക്ക് പോകുക"</string>
<string name="data_usage_title" msgid="8438592133893837464">"ആപ്പ് ഡാറ്റ ഉപയോഗം"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g> തീയതികൾക്കിടയിൽ <xliff:g id="ID_1">%1$s</xliff:g> മൊബൈൽ ഡാറ്റ ഉപയോഗിച്ചു"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"വിപുലമായത്"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"പാസ്വേഡ് തെറ്റാണ്. SIM ഇപ്പോൾ ലോക്കുചെയ്തിരിക്കുന്നു. PUK2 നൽകുക."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 ശാശ്വതമായി തടഞ്ഞിരിക്കുന്നു."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"നിങ്ങൾക്ക് <xliff:g id="NUMBER">%d</xliff:g> ശ്രമങ്ങൾ ശേഷിക്കുന്നു."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 ലോക്ക് ആണ്. അൺലോക്ക് ചെയ്യാൻ സേവന ദാതാവിനെ ബന്ധപ്പെടുക."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 ഇനി തടയില്ല"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"നെറ്റ്വർക്ക് അല്ലെങ്കിൽ സിം കാർഡ് പിശക്"</string>
<string name="doneButton" msgid="7371209609238460207">"പൂർത്തിയായി"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"ഒരു കോൾ വിളിക്കാൻ മൊബൈൽ നെറ്റ്വർക്ക് ഓണാക്കുകയോ ഫ്ലൈറ്റ് മോഡ് അല്ലെങ്കിൽ ബാറ്ററി ലാഭിക്കൽ മോഡ് ഓഫാക്കുകയോ ചെയ്യുക."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"ഒരു കോൾ വിളിക്കാൻ വിമാന മോഡ് ഓഫാക്കുക."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"ഒരു കോൾ വിളിക്കാൻ വിമാന മോഡ് ഓഫാക്കുക അല്ലെങ്കിൽ വയർലെസ്സ് നെറ്റ്വർക്കിലേക്ക് കണക്റ്റുചെയ്യുക."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"ഫോൺ വളരെയധികം ചൂടായിരിക്കുന്നു"</b>\n\n"ഈ കോൾ പൂർത്തിയാക്കാനാകില്ല. ഫോൺ തണുക്കുമ്പോൾ വീണ്ടും ശ്രമിക്കുക.\n\nഎമർജൻസി കോളുകൾ നിങ്ങൾക്ക് ഇപ്പോഴും ചെയ്യാവുന്നതാണ്."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"അടിയന്തിരമല്ലാത്ത കോൾ ചെയ്യാൻ അടിയന്തിര കോൾബാക്ക് മോഡിൽ നിന്ന് പുറത്തുകടക്കുക."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"നെറ്റ്വർക്കിൽ രജിസ്റ്റർ ചെയ്തിട്ടില്ല."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"മൊബൈൽ നെറ്റ്വർക്ക് ലഭ്യമല്ല."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ഡിഫോൾട്ട് ഡാറ്റാ സിമ്മിന്റെ ഉപഐഡി:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL ബാൻഡ്വിഡ്ത് (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL ബാൻഡ്വിഡ്ത് (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"സെൽ ലൊക്കേഷൻ വിവരങ്ങൾ (അവസാനിപ്പിച്ചു):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ഫിസിക്കൽ ചാനൽ കോൺഫിഗറേഷൻ:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"സെൽ വിവരങ്ങൾ പുതുക്കിയെടുക്കൽ നിരക്ക്:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"എല്ലാ സെൽ അളവ് വിവരങ്ങളും:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"ഫോൺ വിവരം"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"കാരിയർ പ്രൊവിഷനിംഗ് വിവരം"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"കാരിയർ പ്രൊവിഷനിംഗ് തുടങ്ങുക"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"നിങ്ങളുടെ Bluetooth സിഗ്നൽ ദുർബലമാണ്. സ്പീക്കർഫോണിലേക്ക് മാറ്റി നോക്കൂ."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"കോൾ നിലവാര അറിയിപ്പ്"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"അവസാനിപ്പിച്ച SIP അക്കൗണ്ടുകൾ"</string>
</resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index f59c83e..48ac8c0 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Дуут шуудангийн тохиргоог зөвхөн Үндсэн хэрэглэгч өөрчлөх боломжтой."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Таны SIM карт тайлагдлаа. Таны утас тайлагдаж байна…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM сүлжээ тайлах ПИН"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"Операторын SIM түгжигдсэн"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Тайлах"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Алгасах"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Сүлжээг тайлах хүсэлтийг илгээж байна…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> руу дамжуулж байна"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Идэвхгүй"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Таны үүрэн холбоо үзүүлэгчээс утас завгүй байхад дуудлагыг дамжуулахыг зогсоох үйлчилгээг дэмждэггүй."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Таны оператор компани дуудлага шилжүүлэхийг дэмждэггүй."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"\"Дуудлага хүлээлгэнд\" тохиргоог асаах уу?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Дуудлагын үеэр танд ирсэн дуудлагын талаар мэдэгдэнэ"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Асаах"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Болих"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS доорх CDMA-н \"Дуудлага хүлээлгэнд\" тохиргоо асаалттай байна"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS доорх CDMA-н \"Дуудлага хүлээлгэнд\" тохиргоо унтраалттай байна"</string>
<string name="updating_title" msgid="6130548922615719689">"Дуудлагын тохиргоо"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Дуудлагын тохиргоог зөвхөн админ хэрэглэгч солих боломжтой."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Тохиргоо (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Хязгаарлагдмал SIM-н функц"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g>-г ашиглах явцад <xliff:g id="CARRIER_NAME">%1$s</xliff:g>-н дуудлага болон дата үйлчилгээг блоклож болзошгүй."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Өөр SIM ашиглах явцад <xliff:g id="CARRIER_NAME">%1$s</xliff:g>-н дуудлага болон дата үйлчилгээг хориглож болзошгүй."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"SIP-н зогсоосон бүртгэлүүд олдсон бөгөөд тэдгээрийг хассан"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP дуудлагыг цаашид Android платформоос дэмжихгүй.\nТаны одоо байгаа <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> SIP-н бүртгэлүүдийг хассан.\nДуудлагын бүртгэлийн өгөгдмөл тохиргоогоо баталгаажуулна уу."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Тохиргоо руу очих"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Аппын дата ашиглалт"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> мобайл дата ашигласан <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Нарийвчилсан"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Нууц үг буруу байна. SIM карт одоо түгжигдсэн. PUK2 кодыг оруулна уу."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"ПҮК2 бүрмөсөн түгжигдсэн."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Танд <xliff:g id="NUMBER">%d</xliff:g> оролдлого үлдлээ."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2-г түгжсэн. Түгжээг тайлахын тулд үйлчилгээ үзүүлэгчтэй холбогдоно уу."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 түгжээгүй болсон"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Сүлжээ болон SIM картын алдаа"</string>
<string name="doneButton" msgid="7371209609238460207">"Дууссан"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Дуудлага хийхийн тулд мобайл сүлжээг асаах, нислэгийн горим буюу батерей хэмнэх горимыг идэвхгүй болгоно уу."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Дуудлага хийхийн тулд онгоцны горимыг унтраа."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Дуудлага хийхийн тулд онгоцны горимыг унтраа эсвэл утасгүй интернетэд холбогдоно уу."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Утас хэт халуун байна"</b>\n\n"Энэ дуудлагыг гүйцээх боломжгүй. Та утсаа хөрсөн үед дахин оролдоно уу.\n\nТа яаралтай дуудлага хийх боломжтой хэвээр байна."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Яаралтай түргэн тусламжийн бус дуудлага хийхийн тулд яаралтай түргэн тусламжийн callback горимоос гарна уу."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Сүлжээнд бүртгэгдээгүй."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Мобайль сүлжээ байхгүй."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Өгөгдмөл дата SIM-н SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DLзурвасын өргөн (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL Мессежийн өргөн (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Үүрэн байршлын мэдээлэл (цуцалсан):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE Сувгийн бодит тохиргоо:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Үүрэн мэдээлэл сэргээх тариф:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Бүх үүрэн хэмжилтийн мэдээлэл:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Утасны мэдээлэл"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Оператор компанийн хангалтын мэдээлэл"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Оператор компанийн хангалтыг эхлүүлэх"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Таны Bluetooth-н дохио сул байна. Чанга яригчтай утас руу сэлгэж үзнэ үү."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Дуудлагын чанарын мэдэгдэл"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"SIP-н зогсоосон бүртгэлүүд"</string>
</resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index bc59874..7c788fa 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"केवळ प्राथमिक वापरकर्ता व्हॉइसमेल सेटिंग्ज बदलू शकतो."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"तुमचे सिम कार्ड अवरोधित करणे रद्द केले गेले आहे. तुमचा फोन अनलॉक होत आहे…"</string>
<string name="label_ndp" msgid="7617392683877410341">"सिम नेटवर्क अनलॉक पिन"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"ऑपरेटरसाठी सिम लॉक केले आहे"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"अनलॉक करा"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"डिसमिस करा"</string>
<string name="requesting_unlock" msgid="930512210309437741">"नेटवर्क अनलॉकची विनंती करत आहे..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> वर फॉरवर्ड करत आहे"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"बंद"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"तुमचा फोन पोहचण्यायोग्य नसताना तुमचा वाहक कॉल अग्रेषण करणे अक्षम करण्यास समर्थन करीत नाही."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"तुमचा वाहक कॉल फॉरवर्डिंग करण्यास सपोर्ट करत नाही."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"कॉल वेटिंग सुरू करायचे आहे का?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"कॉल दरम्यान येणाऱ्या कॉलबद्दल तुम्हाला सूचित केले जाईल"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"सुरू करा"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"रद्द करा"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS मध्ये CDMA कॉल वेटिंग सुरू आहे"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS मध्ये CDMA कॉल वेटिंग बंद आहे"</string>
<string name="updating_title" msgid="6130548922615719689">"कॉल सेटिंग्ज"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"कॉल सेटिंग्ज केवळ प्रशासक वापरकर्त्याद्वारे बदलल्या जाऊ शकतात."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"सेटिंग्ज (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"मर्यादित सिम फंक्शनॅलिटी"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> चे कॉल आणि डेटा सेवा <xliff:g id="PHONE_NUMBER">%2$s</xliff:g> वापरताना ब्लॉक केले जाऊ शकतात."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> चे कॉल आणि डेटा सेवा दुसरे सिम वापरताना ब्लॉक केले जाऊ शकतात."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"आढळलेली आणि काढून टाकलेली कालबाह्य झालेली SIP खाती"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Android प्लॅटफॉर्मवर आता SIP कॉलिंगला सपोर्ट नाही.\nतुमची अस्तित्वात असलेली SIP खाती <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> काढून टाकली आहेत.\nकृपया तुमच्या कॉलिंगसंबंधित डीफॉल्ट खात्याचे सेटिंग कंफर्म करा."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"सेटिंग्जवर जा"</string>
<string name="data_usage_title" msgid="8438592133893837464">"अॅप डेटा वापर"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g> दरम्यान <xliff:g id="ID_1">%1$s</xliff:g> मोबाइल डेटा वापरला गेला"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"प्रगत"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"पासवर्ड चुकीचा आहे. सिम आता लॉक केले आहे. PUK2 प्रविष्ट करा."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 कायमचे अवरोधित केले आहे."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"आपल्याकडे <xliff:g id="NUMBER">%d</xliff:g> शिल्लक प्रयत्न आहेत."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 द्वारे लॉक केले आहे. ते अनलॉक करण्यासाठी सेवा पुरवठादाराशी संपर्क साधा."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"पिन2 अवरोधित केला नाही"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"नेटवर्क किंवा सिम कार्ड एरर"</string>
<string name="doneButton" msgid="7371209609238460207">"पूर्ण झाले"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"कॉल करण्यासाठी मोबाइल नेटवर्क सुरू करा, विमान मोड बंद करा किंवा बॅटरी बचकर्ता मोड बंद करा."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"कॉल करण्यासाठी विमान मोड बंद करा."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"कॉल करण्यासाठी विमान मोड बंद करा किंवा वायरलेस नेटवर्कशी कनेक्ट करा."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"फोन खूप गरम झाला आहे"</b>\n\n"हा कॉल पूर्ण करू शकत नाही. तुमचा फोन थंड झाल्यावर पुन्हा प्रयत्न करा.\n\nतुम्ही अजूनही आणीबाणी कॉल करू शकता."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"आणीबाणी नसलेला कॉल करण्यासाठी आणीबाणी कॉलबॅक मोडमधून बाहेर पडा."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"नेटवर्कवर नोंदणीकृत नाही."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"मोबाइल नेटवर्क उपलब्ध नाही."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"डीफॉल्ट डेटा सिम SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL बँडविड्थ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL बँडविड्थ (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"सेल स्थान माहिती (कालबाह्य झाली):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE फिजिकल चॅनल कॉंफिगरेशन:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"सेल माहिती रिफ्रेश रेट:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"सर्व सेल परिमाण माहिती:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"फोनची माहिती"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"वाहक सुविधा माहिती"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"वाहक सुविधा ट्रिगर करा"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"तुमचा ब्लूटूथ सिग्नल कमकुवत आहे. स्पीकरफोनवर स्विच करून पहा."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"कॉल गुणवत्ता सूचना"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"कालबाह्य झालेली SIP खाती"</string>
</resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 145a6ab..4ddb2e6 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Tetapan Mel Suara hanya boleh diubah suai oleh Pengguna Utama."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Kad SIM anda telah dinyahsekat. Telefon anda dibuka kunci..."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN buka kunci rangkaian SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM dikunci untuk pengendali"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Buka kunci"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Ketepikan"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Meminta dibukakan kunci rangkaian..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Memajukan ke <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Dimatikan"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Pembawa anda tidak menyokong pelumpuhan pemajuan panggilan semasa telefon anda tidak boleh dihubungi."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Pembawa anda tidak menyokong pemajuan panggilan."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Hidupkan panggilan menunggu?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Semasa panggilan, anda akan diberitahu tentang panggilan masuk"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Hidupkan"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Batal"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Panggilan Menunggu CDMA di bawah IMS Dihidupkan"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Panggilan Menunggu CDMA di bawah IMS Dimatikan"</string>
<string name="updating_title" msgid="6130548922615719689">"Tetapan panggilan"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Tetapan panggilan hanya boleh diubah oleh pengguna pentadbir."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Tetapan (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Fungsi SIM terhad"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Perkhidmatan panggilan dan data <xliff:g id="CARRIER_NAME">%1$s</xliff:g> mungkin disekat semasa menggunakan <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Perkhidmatan panggilan dan data <xliff:g id="CARRIER_NAME">%1$s</xliff:g> mungkin disekat semasa menggunakan SIM lain."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Akaun SIP yang ditamatkan telah ditemukan dan dialih keluar"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Panggilan SIP tidak lagi disokong oleh platform Android.\nAkaun SIP <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> anda yang sedia ada telah dialih keluar.\nSila sahkan tetapan akaun panggilan lalai anda."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Akses tetapan"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Penggunaan data apl"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> data mudah alih digunakan <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Terperinci"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Kata laluan tidak betul. SIM kini dikunci. Masukkan PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 disekat secara kekal."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Anda mempunyai <xliff:g id="NUMBER">%d</xliff:g> percubaan lagi."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 dikunci. Hubungi penyedia perkhidmatan untuk membuka kunci."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 tidak disekat lagi"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Ralat rangkaian atau kad SIM"</string>
<string name="doneButton" msgid="7371209609238460207">"Selesai"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Hidupkan rangkaian mudah alih, matikan mod pesawat atau matikan mod penjimat bateri untuk membuat panggilan."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Matikan mod pesawat untuk membuat panggilan."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Matikan mod pesawat atau sambungkan ke rangkaian wayarles untuk membuat panggilan."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefon terlalu panas"</b>\n\n"Tidak dapat melengkapkan panggilan ini. Cuba lagi apabila telefon anda menyejuk.\n\nAnda masih boleh membuat panggilan kecemasan."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Keluar daripada mod panggil balik kecemasan untuk membuat panggilan bukan kecemasan."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Tidak didaftarkan pada rangkaian."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Rangkaian mudah alih tidak tersedia."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId SIM data lalai:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Lebar Jalur DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Lebar Jalur UL (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Maklumat Lokasi Selular (ditamatkan):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfigurasi Saluran Fizikal LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Kadar Muat Semula Maklumat Selular:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Semua Maklumat Ukuran Selular:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Maklumat telefon"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Maklumat Peruntukan Pembawa"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Cetus Peruntukan Pembawa"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Isyarat bluetooth anda lemah. Cuba beralih kepada fon pembesar suara."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Pemberitahuan Kualiti Panggilan"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Akaun SIP ditamatkan"</string>
</resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 5d662ae..03aea11 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"အသံမေးလ်ဆက်တင်များကို အဓိကအသုံးပြုသူသာ ပြင်ဆင်နိုင်ပါသည်။"</string>
<string name="puk_unlocked" msgid="4627340655215746511">"သင့် ဆင်းမ်ကဒ်အား ပိတ်ဆို့မှုကို ဖယ်ရှားပြီးပါပြီ။ သင့်ဖုန်းဟာ သော့ဖွင့်နေပါသည်…"</string>
<string name="label_ndp" msgid="7617392683877410341">"ဆင်းမ်ကတ် ကွန်ရက် သော့ဖွင့်သော ပင်နံပါတ်"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"အော်ပရေတာအတွက် ဆင်းမ်ကတ်ကို လော့ခ်ချထားသည်"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"ဖွင့်ရန်"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"ပယ်ရန်"</string>
<string name="requesting_unlock" msgid="930512210309437741">"ကွန်ယက်သော့ဖွင့်ခြင်း တောင်းဆိုနေပါသည်…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> သို့ခေါ်ဆိုမှုအား တဆင့်ထပ်ပို့နေသည်"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"ပိတ်ထားသည်"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"သင့် ဖုန်း ဆက်သွယ်မှု လုပ်လို့မရစဉ် call forwarding ပြုလုပ်မှု ပယ်ဖျက်ရန် သင့် အော်ပရေတာမှ ခွင့်မပြုပါ"</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"သင်၏ ဝန်ဆောင်မှုပေးသူသည် အဝင်ဖုန်းကို ဆက်ပို့ပေးခြင်းအား ပံ့ပိုးမထားပါ။"</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"အဝင်ဖုန်း စောင့်ဆိုင်းခြင်းကို ဖွင့်မလား။"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"ဖုန်းပြောနေစဉ် အဝင်ခေါ်ဆိုမှုများအတွက် သင့်ထံ အကြောင်းကြားပါမည်"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"ဖွင့်ရန်"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"မလုပ်တော့"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS အောက်ရှိ CDMA အဝင်ဖုန်း စောင့်ဆိုင်းခြင်းကို ဖွင့်ထားသည်"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS အောက်ရှိ CDMA အဝင်ဖုန်း စောင့်ဆိုင်းခြင်းကို ပိတ်ထားသည်"</string>
<string name="updating_title" msgid="6130548922615719689">"ဖုန်းခေါ်ဆိုခြင်း ဆက်တင်များ"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"ခေါ်ဆိုမှုကြိုတင်ပြင်ဆင်ချက်များကို ကြီးကြပ်သူသာလျှင် ပြောင်းလဲနိုင်သည်။"</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"ဆက်တင်များ ( <xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g> )"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"ကန့်သတ်ထားသော ဆင်းမ်ကတ် လုပ်ဆောင်ချက်"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g>.အသုံးပြုနေစဉ် <xliff:g id="CARRIER_NAME">%1$s</xliff:g> ခေါ်ဆိုမှုများနှင့် ဒေတာဝန်ဆောင်မှုများကို ပိတ်ထားနိုင်သည်။"</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"အခြားဆင်းမ်ကတ် သုံးနေစဉ် <xliff:g id="CARRIER_NAME">%1$s</xliff:g> ခေါ်ဆိုမှုနှင့် ဒေတာဝန်ဆောင်မှုများ ပိတ်ထားနိုင်သည်။"</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"ရပ်ဆိုင်းထားသော SIP အကောင့်များ တွေ့ရှိပြီး ဖယ်ရှားလိုက်သည်"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP ခေါ်ဆိုမှုကို Android စနစ်က မပံ့ပိုးတော့ပါ။\nသင်၏လက်ရှိ SIP အကောင့် <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> ကို ဖယ်ရှားလိုက်သည်။\nသင့်မူရင်း ခေါ်ဆိုမှုအကောင့်ဆက်တင်ကို အတည်ပြုပါ။"</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"ဆက်တင်များသို့ သွားရန်"</string>
<string name="data_usage_title" msgid="8438592133893837464">"အက်ပ်ဒေတာ သုံးစွဲမှု"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g> အထိ မိုဘိုင်းဒေတာ <xliff:g id="ID_1">%1$s</xliff:g> ကို အသုံးပြုထားပါသည်"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"အဆင့်မြင့်"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"လျှို့ဝှက်စကားလုံး မမှန်ကန်ပါ။ SIM ယခုသော့ခတ်လိုက်ပါပြီ။ PUK2 ရိုက်ထည့်ပါ။"</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 ကို ထာ၀ရ ပိတ်ထားပါသည်"</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"သင့်တွင် လက်ကျန် ကြိုးစားခွင့် <xliff:g id="NUMBER">%d</xliff:g> ရှိသည်။"</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 လော့ခ်ချထားသည်။ ဖွင့်ရန် ဝန်ဆောင်မှုပေးသူကို ဆက်သွယ်ပါ။"</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"ပင်နံပါတ်2 အား အဆက်မဖြတ်တော့ပါ။"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"ကွန်ရက် သို့မဟုတ် ဆင်းမ်ကဒ်မှားယွင်းမှု"</string>
<string name="doneButton" msgid="7371209609238460207">"ပြီးပါပြီ"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"ဖုန်းခေါ်ရန် မိုဘိုင်းကွန်ရက်ဖွင့်ပြီး လေယာဉ်ပျံမုဒ် (သို့) ဘက်ထရီချွေတာရေးမုဒ်ကို ပိတ်ပါ။"</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"ဖုန်းခေါ်ရန် လေယာဉ်ပျံမုဒ် ပိတ်ထားပါ။"</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"ဖုန်းခေါ်ရန် လေယာဉ်ပျံမုဒ် ပိတ်ပါ သို့မဟုတ် ကြိုးမဲ့ကွန်ယက်သို့ ချိတ်ဆက်ပါ။"</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"ဖုန်းအလွန်ပူနေသည်"</b>\n\n"ဤဖုန်းကို ခေါ်ဆို၍မရပါ။ ဖုန်းအေးသွားသောအခါ ထပ်စမ်းကြည့်ပါ။\n\nအရေးပေါ်ဖုန်းခေါ်ခြင်းများ ပြုလုပ်နိုင်သေးသည်။"</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"အရေးပေါ် မဟုတ်သည့် ခေါ်ဆိုမှုကို ပြုလုပ်ရန် အရေးပေါ် ဖုန်းခေါ်မှုမှ ထွက်ပါ။"</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"ကွန်ယက်ပေါ်မှာ မှတ်ပုံတင်မှု မပြုလုပ်ထားပါ"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"မိုဘိုင်းကွန်ယက်များ မရှိပါ"</string>
@@ -661,7 +674,7 @@
<string name="preference_category_ringtone" msgid="8787281191375434976">"ဖုန်းမြည်သံ & တုန်ခါသံ"</string>
<string name="pstn_connection_service_label" msgid="9200102709997537069">"တပ်ဆင်ပြီး ဆင်းမ် ကဒ်များ"</string>
<string name="enable_video_calling_title" msgid="7246600931634161830">"ဗီဒီယို ခေါ်ဆိုမှုများကို ဖွင့်ထားရန်"</string>
- <string name="enable_video_calling_dialog_msg" msgid="7141478720386203540">"ဗွီဒီယို ခေါ်ဆိုမှု ဖွင့်ရန်၊ မြှင့်ထားသည့် 4G LTE မုဒ်ကို ကွန်ရက် ချိိန်ညှိချက်များတွင် ဖွင့်ပေးရပါမည်။"</string>
+ <string name="enable_video_calling_dialog_msg" msgid="7141478720386203540">"ဗီဒီယိုခေါ်ဆိုမှု ဖွင့်လိုပါက အဆင့်မြှင့်ထားသည့် 4G LTE မုဒ်ကို ကွန်ရက်ဆက်တင်များတွင် ဖွင့်ရပါမည်။"</string>
<string name="enable_video_calling_dialog_settings" msgid="8697890611305307110">"ကွန်ရက် ဆက်တင်များ"</string>
<string name="enable_video_calling_dialog_close" msgid="4298929725917045270">"ပိတ်ရန်"</string>
<string name="sim_label_emergency_calls" msgid="9078241989421522310">"အရေးပေါ်ခေါ်ဆိုမှုများ"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"မူရင်း ဒေတာဆင်းမ်ကဒ်အတွက် Id အခွဲ −"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL မြန်နှုန်း (kbps)−"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL မြန်နှုန်း (kbps)−"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"ဆဲလ်လူလာတည်နေရာ အချက်အလက် (ကန့်ကွက်ထားသည်) −"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ရုပ်ပိုင်းဆိုင်ရာ ချန်နယ်စီစဉ်သတ်မှတ်မှု−"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"ဆဲလ်လူလာ အချက်အလက် ရယူမှုနှုန်း −"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"ဆဲလ်လူလာတိုင်းတာမှု အချက်အလက် အားလုံး −"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"ဖုန်းအချက်အလက်"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"ဝန်ဆောင်မှုပေးသူက ပေးထားသည့် အချက်အလက်များ"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"အစပျိုးပေးသော ဖုန်းကုမ္ပဏီ ပံ့ပိုးမှု"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"သင်၏ ဘလူးတုသ်လိုင်းဆွဲအား မကောင်းပါ။ စပီကာဖုန်းသို့ ပြောင်းကြည့်ပါ။"</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"ခေါ်ဆိုမှုအရည်အသွေး အကြောင်းကြားချက်"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"ရပ်ဆိုင်းထားသော SIP အကောင့်များ"</string>
</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index aa66631..b956af6 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Talepost-innstillinger kan bare endres av hovedbrukeren."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"SIM-kortet er blitt avblokkert. Telefonen låses opp…"</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN-kode for å fjerne operatørlås"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM-kortet er operatørlåst"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Lås opp"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Skjul"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Spør om fjerning av operatørlås…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Viderekobler til <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Av"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Operatøren støtter ikke deaktivering av viderekobling når telefonen er utenfor dekning."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Operatøren støtter ikke viderekobling."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Vil du slå på «samtale venter»?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Du blir varslet om innkommende anrop under pågående samtaler"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Slå på"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Lukk"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA samtale venter under IMS er på"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA samtale venter under IMS er av"</string>
<string name="updating_title" msgid="6130548922615719689">"Samtaleinnstillinger"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Anropsinnstillinger kan bare endres av administratoren."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Innstillinger (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Begrensede SIM-kortfunksjoner"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Det kan hende <xliff:g id="CARRIER_NAME">%1$s</xliff:g>-anrop og -datatjenester blir blokkert mens du bruker <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>-anrop og -datatjenester kan bli blokkert mens du bruker et annet SIM-kort."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"De avviklede SIP-kontoene er funnet og fjernet"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP-anrop støttes ikke lenger på Android-plattformen.\nDe eksisterende SIP-kontoene dine (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>) er fjernet.\nBekreft innstillingen din for standardkonto for anrop."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Gå til innstillingene"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Appens databruk"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> mobildata er brukt i perioden <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Avansert"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Passordet er feil. PIN-koden er nå blokkert. Skriv inn en PUK2-kode."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 er permanent sperret."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Du har <xliff:g id="NUMBER">%d</xliff:g> forsøk igjen."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2-låst. Kontakt tjenesteleverandøren for å låse opp."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2-koden er ikke lenger blokkert"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Feil på nettverket eller SIM-kortet"</string>
<string name="doneButton" msgid="7371209609238460207">"Ferdig"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Slå på mobilnettverket, slå av flymodus eller slå av batterisparingsmodus for å ringe ut."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Slå av flymodus for å ringe."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Slå av flymodus eller koble til et trådløst nettverk for å ringe."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefonen er for varm"</b>\n\n"Kan ikke fullføre dette anropet. Prøv igjen når telefonen har kjølt seg ned.\n\nDu kan fortsatt foreta nødanrop."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Avslutt modusen for nødanrop for å gjøre et vanlig anrop."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Ikke registrert på nettverket."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobilnettverket er ikke tilgjengelig."</string>
@@ -583,7 +596,7 @@
<string name="hac_mode_summary" msgid="7774989500136009881">"Slå på kompatibilitet med høreapparater"</string>
<string name="rtt_mode_title" msgid="3075948111362818043">"Sanntidstekst-anrop (STT)"</string>
<string name="rtt_mode_summary" msgid="8631541375609989562">"Tillat meldingsutveksling i talesamtaler"</string>
- <string name="rtt_mode_more_information" msgid="587500128658756318">"STT er til hjelp for brukere som er døve, tunghørte, har talefunksjonshemning eller trenger mer enn bare tale.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>Finn ut mer</a>\n <br><br> – STT-anrop lagres som meldingstranskripsjoner\n <br> – STT er ikke tilgjengelig for videoanrop"</string>
+ <string name="rtt_mode_more_information" msgid="587500128658756318">"STT er til hjelp for brukere som er døve, hørselshemmede, har taleproblemer eller trenger mer enn bare tale.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>Finn ut mer</a>\n <br><br> – STT-anrop lagres som meldingstranskripsjoner\n <br> – STT er ikke tilgjengelig for videoanrop"</string>
<string name="no_rtt_when_roaming" msgid="5268008247378355389">"Merk: RTT er ikke tilgjengelig ved roaming"</string>
<string-array name="tty_mode_entries">
<item msgid="3238070884803849303">"TTY av"</item>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Under-ID for standard-SIM-kort for data:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Båndbredde for nedlasting (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Båndbredde for opplasting (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Informasjon om celleposisjonen (avviklet):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfigurering av fysisk LTE-kanal:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Oppdateringsfrekvens for celleinformasjon:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"All informasjon for cellemåling:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Telefoninformasjon"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Informasjon om operatørhåndtering"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Utløs operatørhåndtering"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Bluetooth-signalet er svakt. Prøv å bytte til høyttaleren."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Varsel om anropskvalitet"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Avviklede SIP-kontoer"</string>
</resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 5ceb661..e6d6abc 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"प्राथमिक प्रयोगकर्ताले मात्र भ्वाइस मेल सेटिङहरू परिमार्जन गर्न सक्नुहुन्छ।"</string>
<string name="puk_unlocked" msgid="4627340655215746511">"तपाईँको SIM कार्ड अनब्लक गरिएको छ। तपाईँको फोन अनलक हुँदैछ ..."</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM नेटवर्क अनलक PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"अपरेटरका लागि SIM लक गरिएको छ"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"अनलोक गर्नुहोस्"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"खारेज गर्नुहोस्"</string>
<string name="requesting_unlock" msgid="930512210309437741">"नेटवर्क अनलक गर्न अनुरोध गर्दै..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> मा फर्वाड गर्दै"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"बन्द गर्नुहोस्"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"तपाईंको फोन पहुँचयोग्य नहुँदा तपाईंको केरियरले कल-फर्वार्डिङ असक्षम बनाउँदा समर्थन गर्दैन।"</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"तपाईंको सेवा प्रदायकले कल फर्वार्ड गर्ने सुविधा उपलब्ध गराउँदैन।"</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"कल प्रतीक्षाको सुविधा सक्रिय गर्ने हो?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"कल हुँदै गर्दा, तपाईंलाई आगमन कलबारे जानकारी दिइने छ"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"सक्रिय गर्नुहोस्"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"रद्द गर्नुहोस्"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS अन्तर्गत CDMA कल प्रतीक्षाको सुविधा सक्रिय छ"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS अन्तर्गत CDMA कल प्रतीक्षाको सुविधा निष्क्रिय छ"</string>
<string name="updating_title" msgid="6130548922615719689">"कल सेटिङहरू"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"कल सेटिङहरू केवल प्रशासकीय प्रयोगकर्ताद्वारा परिवर्तन गर्न सकिन्छ।"</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"सेटिङहरू (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"SIM को सीमित सुविधा"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g>.प्रयोग गर्दा <xliff:g id="CARRIER_NAME">%1$s</xliff:g> का कल तथा डेटा सेवाहरूलाई रोक लगाइन सक्छ"</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"अर्को SIM प्रयोग गर्दा <xliff:g id="CARRIER_NAME">%1$s</xliff:g> कल तथा डेटा सेवहरूलाई रोक लगाइन सक्छ।"</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"चल्तीबाट हटाइएका SIP खाताहरू भेट्टाइयो र हटाइयो"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"अबदेखि Android प्लेटफर्ममा SIP कल गर्न मिल्दैन।\nतपाईंका हालका SIP खाताहरू <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> हटाइएका छन्।\nकृपया आफ्नो डिफल्ट कल गर्ने खाताको सेटिङ पुष्टि गर्नुहोस्।"</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"सेटिङमा जानुहोस्"</string>
<string name="data_usage_title" msgid="8438592133893837464">"अनुप्रयोगले गरेको डेटाको प्रयोग"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g> सम्म <xliff:g id="ID_1">%1$s</xliff:g> मोबाइल डेटा प्रयोग भयो"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"उन्नत"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"पासवर्ड गलत छ। सिम अब ब्लक गरिएको छ। PUK2 प्रविष्टि गर्नुहोस्।"</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 स्थायी रूपमा रोक्का गरियो।"</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n" तपाईंसँग <xliff:g id="NUMBER">%d</xliff:g> प्रयास बाँकी छन्।"</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"SIM कार्ड PUK2 बाट लक गरिएको छ। अनलक गर्न सेवा प्रदायकलाई सम्पर्क गर्नुहोस्।"</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 अब ब्लक गरिएको छैन"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"नेटवर्क वा SIM कार्ड त्रुटि।"</string>
<string name="doneButton" msgid="7371209609238460207">"सम्पन्न भयो"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"मोबाइल नेटवर्क सक्रिय पार्नुहोस्, कल गर्न हवाइजहाज मोड वा ब्याट्री सेवर मोड निष्क्रिय पार्नुहोस्।"</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"एक कल गर्न हवाइजहाज मोड बन्द गर्नुहोस्।"</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"एक कल गर्न हवाइजहाज मोड बन्द गर्नुहोस् वा एक ताररहितको सञ्जालमा कनेक्ट गर्नुहोस्।"</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"फोन ज्यादै तातेको छ"</b>\n\n"यो कल जारी राख्न सकिँदैन। फोन सेलाएपछि फेरि प्रयास गर्नुहोस्।\n\nतपाईं अझै पनि आपत्कालीन कलहरू गर्न सक्नुहुन्छ।"</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"गैर-आपत्कालीन कल गर्न आपत्कालीन कलब्याक मोडबाट निस्कनुहोस्।"</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"नेटवर्कमा दर्ता भएको छैन।"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"मोबाइल नेटवर्क उपलब्ध छैन।"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"डिफल्ट डेटा SIM को SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL ब्यान्डविथ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL व्यान्डविथ (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"सेलको स्थानबारे जानकारी (चल्तीबाट हटाइएको):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE को भौतिक च्यानलको कन्फिगरेसन:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"सेलसम्बन्धी जानकारीलाई पुनः ताजा गरिने दर:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"सेलको मापनसम्बन्धी सबै जानकारी:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"फोनसम्बन्धी जानकारी"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"सेवा प्रदायकको प्रावधानसम्बन्धी जानकारी"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"सेवा प्रदायकको प्रावधानलाई ट्रिगर गर्नुहोस्"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"ब्लुटुथको सिग्नल कमजोर छ। स्पिकरफोन प्रयोग गरी हेर्नुहोस्।"</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"कलको गुणस्तरसम्बन्धी सूचना"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"चल्तीबाट हटाइएका SIP खाताहरू"</string>
</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 0bd6a8a..b13d9c8 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Voicemailinstellingen kunnen alleen worden gewijzigd door de primaire gebruiker."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Je simkaart is gedeblokkeerd. Je telefoon wordt ontgrendeld..."</string>
<string name="label_ndp" msgid="7617392683877410341">"Pincode voor ontgrendelen SIM-netwerk"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"Simkaart vergrendeld voor provider"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Ontgrendelen"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Negeren"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Verzoek om ontgrendelen netwerk..."</string>
@@ -112,17 +113,24 @@
<string name="messageCFB" msgid="1958017270393563388">"Nummer indien in gesprek"</string>
<string name="sum_cfb_enabled" msgid="332037613072049492">"Doorschakelen naar <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfb_disabled" msgid="3589913334164866035">"Uit"</string>
- <string name="disable_cfb_forbidden" msgid="4831494744351633961">"Je provider biedt geen ondersteuning voor het uitschakelen van oproepdoorschakelingen wanneer je telefoon bezet is."</string>
+ <string name="disable_cfb_forbidden" msgid="4831494744351633961">"Je provider biedt geen ondersteuning voor het uitzetten van oproepdoorschakelingen wanneer je telefoon bezet is."</string>
<string name="labelCFNRy" msgid="3403533792248457946">"Indien niet opgenomen"</string>
<string name="messageCFNRy" msgid="7644434155765359009">"Nummer indien onbeantwoord"</string>
<string name="sum_cfnry_enabled" msgid="3000500837493854799">"Doorschakelen naar <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnry_disabled" msgid="1990563512406017880">"Uit"</string>
- <string name="disable_cfnry_forbidden" msgid="3174731413216550689">"Je provider biedt geen ondersteuning voor het uitschakelen van oproepdoorschakelingen wanneer je telefoon niet wordt opgenomen."</string>
+ <string name="disable_cfnry_forbidden" msgid="3174731413216550689">"Je provider biedt geen ondersteuning voor het uitzetten van oproepdoorschakelingen wanneer je telefoon niet wordt opgenomen."</string>
<string name="labelCFNRc" msgid="4163399350778066013">"Indien geen bereik"</string>
<string name="messageCFNRc" msgid="6980340731313007250">"Nummer indien onbereikbaar"</string>
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Doorschakelen naar <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Uit"</string>
- <string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Je provider biedt geen ondersteuning voor het uitschakelen van oproepdoorschakelingen wanneer je telefoon niet bereikbaar is."</string>
+ <string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Je provider biedt geen ondersteuning voor het uitzetten van oproepdoorschakelingen wanneer je telefoon niet bereikbaar is."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Je provider ondersteunt het doorschakelen van gesprekken niet."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Wisselgesprek aanzetten?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Tijdens een gesprek krijg je een melding over inkomende gesprekken"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Aanzetten"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Annuleren"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA-wisselgesprek onder IMS aan"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA-wisselgesprek onder IMS uit"</string>
<string name="updating_title" msgid="6130548922615719689">"Gespreksinstellingen"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Oproepinstellingen kunnen alleen worden gewijzigd door de beheerder."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Instellingen (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -136,16 +144,16 @@
<string name="stk_cc_ss_to_ussd_error" msgid="8330749347425752192">"SS-verzoek gewijzigd in USSD-verzoek"</string>
<string name="stk_cc_ss_to_ss_error" msgid="8297155544652134278">"Gewijzigd in nieuw SS-verzoek"</string>
<string name="stk_cc_ss_to_dial_video_error" msgid="4255261231466032505">"SS-verzoek gewijzigd in videogesprek"</string>
- <string name="fdn_check_failure" msgid="1833769746374185247">"De instelling \'Vaste nummers\' in je app Telefoon is ingeschakeld. Hierdoor werken sommige oproepgerelateerde functies niet."</string>
+ <string name="fdn_check_failure" msgid="1833769746374185247">"De instelling \'Vaste nummers\' in je app Telefoon staat aan. Hierdoor werken sommige oproepgerelateerde functies niet."</string>
<string name="radio_off_error" msgid="8321564164914232181">"Schakel de radio in voordat je deze instellingen bekijkt."</string>
<string name="close_dialog" msgid="1074977476136119408">"OK"</string>
- <string name="enable" msgid="2636552299455477603">"Inschakelen"</string>
+ <string name="enable" msgid="2636552299455477603">"Aanzetten"</string>
<string name="disable" msgid="1122698860799462116">"Uit"</string>
<string name="change_num" msgid="6982164494063109334">"Updaten"</string>
<string-array name="clir_display_values">
<item msgid="8477364191403806960">"Netwerkstandaard"</item>
<item msgid="6813323051965618926">"Nummer verbergen"</item>
- <item msgid="9150034130629852635">"Nummer weergeven"</item>
+ <item msgid="9150034130629852635">"Nummer tonen"</item>
</string-array>
<string name="vm_changed" msgid="4739599044379692505">"Voicemailnummer gewijzigd."</string>
<string name="vm_change_failed" msgid="7877733929455763566">"Kan het voicemailnummer niet wijzigen.\nNeem contact op met je provider als dit probleem zich blijft voordoen."</string>
@@ -276,16 +284,19 @@
<string name="roaming" msgid="1576180772877858949">"Roaming"</string>
<string name="roaming_enable" msgid="6853685214521494819">"Verbinding maken met dataservices tijdens roaming"</string>
<string name="roaming_disable" msgid="8856224638624592681">"Verbinding maken met dataservices tijdens roaming"</string>
- <string name="roaming_reenable_message" msgid="1951802463885727915">"Dataroaming is uitgeschakeld. Tik om in te schakelen."</string>
+ <string name="roaming_reenable_message" msgid="1951802463885727915">"Dataroaming staat uit. Tik om aan te zetten."</string>
<string name="roaming_enabled_message" msgid="9022249120750897">"Er kunnen kosten voor roaming in rekening worden gebracht. Tik om te wijzigen."</string>
<string name="roaming_notification_title" msgid="3590348480688047320">"Mobiele internetverbinding is verbroken"</string>
- <string name="roaming_on_notification_title" msgid="7451473196411559173">"Dataroaming is ingeschakeld"</string>
+ <string name="roaming_on_notification_title" msgid="7451473196411559173">"Dataroaming staat aan"</string>
<string name="roaming_warning" msgid="7855681468067171971">"Er kunnen hoge roamingkosten in rekening worden gebracht."</string>
<string name="roaming_check_price_warning" msgid="8212484083990570215">"Neem contact op met je netwerkprovider over de prijzen."</string>
<string name="roaming_alert_title" msgid="5689615818220960940">"Dataroaming toestaan?"</string>
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Beperkte simkaartfunctionaliteit"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Gesprekken en dataservices van <xliff:g id="CARRIER_NAME">%1$s</xliff:g> kunnen worden geblokkeerd terwijl je <xliff:g id="PHONE_NUMBER">%2$s</xliff:g> gebruikt."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>-gesprekken en -dataservices kunnen worden geblokkeerd voor een andere simkaart."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Beëindigde SIP-accounts gevonden en verwijderd"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP-gesprekken worden niet meer ondersteund door het Android-platform.\nJe bestaande SIP-accounts <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> zijn verwijderd.\nBevestig de instelling voor je standaard belaccount."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Naar instellingen"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Datagebruik van app"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> mobiele data gebruikt van <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Geavanceerd"</string>
@@ -294,7 +305,7 @@
<string name="carrier_settings_euicc_summary" msgid="2027941166597330117">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> — <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>"</string>
<string name="mobile_data_settings_title" msgid="7228249980933944101">"Mobiele data"</string>
<string name="mobile_data_settings_summary" msgid="5012570152029118471">"Datatoegang via mobiel netwerk"</string>
- <string name="data_usage_disable_mobile" msgid="5669109209055988308">"Mobiele data uitschakelen?"</string>
+ <string name="data_usage_disable_mobile" msgid="5669109209055988308">"Mobiele data uitzetten?"</string>
<string name="sim_selection_required_pref" msgid="6985901872978341314">"Selectie vereist"</string>
<string name="sim_change_data_title" msgid="9142726786345906606">"Sim voor gegevens wijzigen?"</string>
<string name="sim_change_data_message" msgid="3567358694255933280">"<xliff:g id="NEW_SIM">%1$s</xliff:g> in plaats van <xliff:g id="OLD_SIM">%2$s</xliff:g> gebruiken voor mobiele data?"</string>
@@ -316,33 +327,33 @@
<string name="cell_broadcast_sms" msgid="4053449797289031063">"Infodienstbericht"</string>
<string name="enable_disable_cell_bc_sms" msgid="4759958924031721350">"SMS Infodienstbericht"</string>
<string name="cell_bc_sms_enable" msgid="2019708772024632073">"SMS Infodienstbericht ingeschakeld"</string>
- <string name="cell_bc_sms_disable" msgid="1214238639910875347">"Infodienstbericht uitgeschakeld"</string>
+ <string name="cell_bc_sms_disable" msgid="1214238639910875347">"Infodienstbericht staat uit"</string>
<string name="cb_sms_settings" msgid="6858093721831312036">"Instellingen voor SMS infodienstberichten"</string>
<string name="enable_disable_emergency_broadcast" msgid="6325655044472196496">"Bericht bij noodsituatie"</string>
<string name="emergency_broadcast_enable" msgid="5759610647771102442">"Bericht bij noodsituatie ingeschakeld"</string>
- <string name="emergency_broadcast_disable" msgid="2844904734469323266">"Bericht bij noodsituatie uitgeschakeld"</string>
+ <string name="emergency_broadcast_disable" msgid="2844904734469323266">"Bericht bij noodsituatie staat uit"</string>
<string name="enable_disable_administrative" msgid="7825925366822117961">"Beheer"</string>
<string name="administrative_enable" msgid="5717963431079532028">"Beheer ingeschakeld"</string>
- <string name="administrative_disable" msgid="156796633660118691">"Beheer uitgeschakeld"</string>
+ <string name="administrative_disable" msgid="156796633660118691">"Beheer staat uit"</string>
<string name="enable_disable_maintenance" msgid="3824817751053035017">"Onderhoud"</string>
<string name="maintenance_enable" msgid="2646784483222342290">"Onderhoud ingeschakeld"</string>
- <string name="maintenance_disable" msgid="2121032601497725602">"Onderhoud uitgeschakeld"</string>
+ <string name="maintenance_disable" msgid="2121032601497725602">"Onderhoud staat uit"</string>
<string name="general_news_settings" msgid="2670499575962080411">"Algemeen nieuws"</string>
<string name="bf_news_settings" msgid="8571709425370794221">"Zakelijk en financieel nieuws"</string>
<string name="sports_news_settings" msgid="2684364556989168438">"Sportnieuws"</string>
<string name="entertainment_news_settings" msgid="4228527702346305543">"Amusementsnieuws"</string>
<string name="enable_disable_local" msgid="7654175079979415572">"Lokaal"</string>
<string name="local_enable" msgid="790606890868710629">"Lokaal nieuws ingeschakeld"</string>
- <string name="local_disable" msgid="7649945293198602877">"Lokaal nieuws uitgeschakeld"</string>
+ <string name="local_disable" msgid="7649945293198602877">"Lokaal nieuws staat uit"</string>
<string name="enable_disable_regional" msgid="5783403191376564638">"Regionaal"</string>
<string name="regional_enable" msgid="7730109417536296079">"Regionaal nieuws ingeschakeld"</string>
- <string name="regional_disable" msgid="3781951818157772545">"Regionaal nieuws uitgeschakeld"</string>
+ <string name="regional_disable" msgid="3781951818157772545">"Regionaal nieuws staat uit"</string>
<string name="enable_disable_national" msgid="6198481711934897632">"Binnenlands"</string>
<string name="national_enable" msgid="5159683504138239304">"Binnenlands nieuws ingeschakeld"</string>
- <string name="national_disable" msgid="8484356368757118987">"Binnenlands nieuws uitgeschakeld"</string>
+ <string name="national_disable" msgid="8484356368757118987">"Binnenlands nieuws staat uit"</string>
<string name="enable_disable_international" msgid="4204334217211198792">"Buitenlands"</string>
<string name="international_enable" msgid="8943466745792690340">"Buitenlands nieuws ingeschakeld"</string>
- <string name="international_disable" msgid="4803498658100318265">"Buitenlands nieuws uitgeschakeld"</string>
+ <string name="international_disable" msgid="4803498658100318265">"Buitenlands nieuws staat uit"</string>
<string name="list_language_title" msgid="1850167908665485738">"Taal"</string>
<string name="list_language_summary" msgid="7921756070782277559">"De taal voor het nieuws selecteren"</string>
<string-array name="list_language_entries">
@@ -366,7 +377,7 @@
<string name="list_language_dtitle" msgid="7457017255633587047">"Talen"</string>
<string name="enable_disable_local_weather" msgid="7734933941872511543">"Lokaal weerbericht"</string>
<string name="local_weather_enable" msgid="2143929735295254729">"Lokaal weerbericht ingeschakeld"</string>
- <string name="local_weather_disable" msgid="4209936355998349647">"Lokaal weerbericht uitgeschakeld"</string>
+ <string name="local_weather_disable" msgid="4209936355998349647">"Lokaal weerbericht staat uit"</string>
<string name="enable_disable_atr" msgid="821714821057385390">"Verkeersberichten"</string>
<string name="atr_enable" msgid="1799097759998768186">"Verkeersberichten ingeschakeld"</string>
<string name="atr_disable" msgid="6456758173289065766">"Verkeersberichten uitgeschakeld"</string>
@@ -381,7 +392,7 @@
<string name="lodgings_disable" msgid="5145649659459722661">"Overnachtingen uitgeschakeld"</string>
<string name="enable_disable_retail_directory" msgid="9095153426757081972">"Winkeloverzicht"</string>
<string name="retail_directory_enable" msgid="2532176568755452171">"Winkeloverzicht ingeschakeld"</string>
- <string name="retail_directory_disable" msgid="7242451210736299193">"Winkeloverzicht uitgeschakeld"</string>
+ <string name="retail_directory_disable" msgid="7242451210736299193">"Winkeloverzicht staat uit"</string>
<string name="enable_disable_advertisements" msgid="8704125663718797151">"Advertenties"</string>
<string name="advertisements_enable" msgid="8651409664217899145">"Advertenties ingeschakeld"</string>
<string name="advertisements_disable" msgid="2999957360508271492">"Advertenties uitgeschakeld"</string>
@@ -393,10 +404,10 @@
<string name="eo_disable" msgid="3955421278929647485">"Vacatures uitgeschakeld"</string>
<string name="enable_disable_mhh" msgid="715930476289202466">"Medisch, gezondheid en ziekenhuis"</string>
<string name="mhh_enable" msgid="7224396815285147008">"Medisch, gezondheid en ziekenhuis ingeschakeld"</string>
- <string name="mhh_disable" msgid="5503643028885686265">"Medisch, gezondheid en ziekenhuis uitgeschakeld"</string>
+ <string name="mhh_disable" msgid="5503643028885686265">"Medisch, gezondheid en ziekenhuis staat uit"</string>
<string name="enable_disable_technology_news" msgid="2794845609698078400">"Technologienieuws"</string>
<string name="technology_news_enable" msgid="1908991199492598311">"Technologienieuws ingeschakeld"</string>
- <string name="technology_news_disable" msgid="8388582607149800889">"Technologienieuws uitgeschakeld"</string>
+ <string name="technology_news_disable" msgid="8388582607149800889">"Technologienieuws staat uit"</string>
<string name="enable_disable_multi_category" msgid="5958248155437940625">"Meerdere categorieën"</string>
<string name="multi_category_enable" msgid="4531915767817483960">"Meerdere categorieën ingeschakeld"</string>
<string name="multi_category_disable" msgid="6325934413701238104">"Meerdere categorieën uitgeschakeld"</string>
@@ -430,22 +441,22 @@
<string name="manage_fdn_list" msgid="3341716430375195441">"FDN-lijst"</string>
<string name="fdn_list_with_label" msgid="1409655283510382556">"FDN-lijst (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="fdn_activation" msgid="2178637004710435895">"FDN-activering"</string>
- <string name="fdn_enabled" msgid="7017355494808056447">"Vaste nummers zijn ingeschakeld"</string>
- <string name="fdn_disabled" msgid="6696468878037736600">"Vaste nummers zijn uitgeschakeld"</string>
- <string name="enable_fdn" msgid="4830555730418033723">"FDN inschakelen"</string>
- <string name="disable_fdn" msgid="3918794950264647541">"FDN uitschakelen"</string>
+ <string name="fdn_enabled" msgid="7017355494808056447">"Vaste nummers staan aan"</string>
+ <string name="fdn_disabled" msgid="6696468878037736600">"Vaste nummers staan uit"</string>
+ <string name="enable_fdn" msgid="4830555730418033723">"FDN aanzetten"</string>
+ <string name="disable_fdn" msgid="3918794950264647541">"FDN uitzetten"</string>
<string name="change_pin2" msgid="3110844547237754871">"PIN2-code wijzigen"</string>
- <string name="enable_fdn_ok" msgid="5080925177369329827">"FDN uitschakelen"</string>
- <string name="disable_fdn_ok" msgid="3745475926874838676">"FDN inschakelen"</string>
+ <string name="enable_fdn_ok" msgid="5080925177369329827">"FDN uitzetten"</string>
+ <string name="disable_fdn_ok" msgid="3745475926874838676">"FDN aanzetten"</string>
<string name="sum_fdn" msgid="6152246141642323582">"Vaste nummers beheren"</string>
<string name="sum_fdn_change_pin" msgid="3510994280557335727">"Pincode voor FDN-toegang wijzigen"</string>
<string name="sum_fdn_manage_list" msgid="3311397063233992907">"Lijst met telefoonnummers beheren"</string>
<string name="voice_privacy" msgid="7346935172372181951">"Spraakprivacy"</string>
- <string name="voice_privacy_summary" msgid="3556460926168473346">"Geavanceerde privacymodus inschakelen"</string>
+ <string name="voice_privacy_summary" msgid="3556460926168473346">"Geavanceerde privacymodus aanzetten"</string>
<string name="tty_mode_option_title" msgid="3843817710032641703">"TTY-modus"</string>
<string name="tty_mode_option_summary" msgid="4770510287236494371">"TTY-modus instellen"</string>
<string name="auto_retry_mode_title" msgid="2985801935424422340">"Automatisch opnieuw proberen"</string>
- <string name="auto_retry_mode_summary" msgid="2863919925349511402">"Modus voor automatisch opnieuw proberen inschakelen"</string>
+ <string name="auto_retry_mode_summary" msgid="2863919925349511402">"Modus voor automatisch opnieuw proberen aanzetten"</string>
<string name="tty_mode_not_allowed_video_call" msgid="6551976083652752815">"De TTY-modus mag niet worden gewijzigd tijdens een videogesprek"</string>
<string name="menu_add" msgid="5616487894975773141">"Contact toevoegen"</string>
<string name="menu_edit" msgid="3593856941552460706">"Contact bewerken"</string>
@@ -472,7 +483,7 @@
<string name="simContacts_empty" msgid="1135632055473689521">"Geen contacten op je simkaart."</string>
<string name="simContacts_title" msgid="2714029230160136647">"Contacten selecteren om te importeren"</string>
<string name="simContacts_airplaneMode" msgid="4654884030631503808">"Schakel de vliegtuigmodus uit om contacten van de simkaart te importeren."</string>
- <string name="enable_pin" msgid="967674051730845376">"SIM pincode in-/uitschakelen"</string>
+ <string name="enable_pin" msgid="967674051730845376">"SIM pincode aan-/uitzetten"</string>
<string name="change_pin" msgid="3657869530942905790">"Pincode simkaart wijzigen"</string>
<string name="enter_pin_text" msgid="3182311451978663356">"Pincode simkaart:"</string>
<string name="oldPinLabel" msgid="8618515202411987721">"Oude pincode"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Wachtwoord onjuist. Simkaart nu vergrendeld. Geef PUK2 op."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2-code is definitief geblokkeerd."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Je hebt nog <xliff:g id="NUMBER">%d</xliff:g> pogingen."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 vergrendeld. Neem contact op met je serviceprovider om te ontgrendelen."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 niet langer geblokkeerd"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Netwerk- of simkaartfout"</string>
<string name="doneButton" msgid="7371209609238460207">"Klaar"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Schakel het mobiele netwerk in, schakel de vliegtuigmodus uit of schakel Batterijbesparing uit om te bellen."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Schakel de vliegtuigmodus uit om te bellen."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Schakel de vliegtuigmodus uit of maak verbinding met een draadloos netwerk om te bellen."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefoon te heet"</b>\n\n"Kan dit gesprek niet tot stand brengen. Probeer het opnieuw als je telefoon is afgekoeld.\n\nJe kunt nog steeds noodoproepen plaatsen."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Sluit de modus voor noodoproepen af om een niet-noodoproep te plaatsen."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Niet geregistreerd op netwerk."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobiel netwerk niet beschikbaar."</string>
@@ -580,7 +593,7 @@
<string name="singleContactImportedMsg" msgid="3619804066300998934">"Contact geïmporteerd"</string>
<string name="failedToImportSingleContactMsg" msgid="228095510489830266">"Kan contact niet importeren"</string>
<string name="hac_mode_title" msgid="4127986689621125468">"Hoortoestellen"</string>
- <string name="hac_mode_summary" msgid="7774989500136009881">"Compatibiliteit voor hoortoestel inschakelen"</string>
+ <string name="hac_mode_summary" msgid="7774989500136009881">"Compatibiliteit voor hoortoestel aanzetten"</string>
<string name="rtt_mode_title" msgid="3075948111362818043">"Realtime tekstoproep (RTT)"</string>
<string name="rtt_mode_summary" msgid="8631541375609989562">"Berichten in een audiogesprek toestaan"</string>
<string name="rtt_mode_more_information" msgid="587500128658756318">"RTT helpt bellers die doof of slechthorend zijn, een spraakbeperking hebben of meer dan alleen een stem nodig hebben.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>Meer informatie</a>\n <br><br> - RTT-gesprekken worden opgeslagen als berichttranscript.\n <br> - RTT is niet beschikbaar voor videogesprekken"</string>
@@ -604,7 +617,7 @@
<string name="ota_hfa_activation_title" msgid="3300556778212729671">"Activeren..."</string>
<string name="ota_hfa_activation_dialog_message" msgid="7921718445773342996">"De telefoon activeert je mobiele-dataservice.\n\nDit kan tot vijf minuten duren."</string>
<string name="ota_skip_activation_dialog_title" msgid="7666611236789203797">"Activering overslaan?"</string>
- <string name="ota_skip_activation_dialog_message" msgid="6691722887019708713">"Als je de activering overslaat, kun je niet bellen of verbinding maken met mobiele-datanetwerken (je kunt wel verbinding maken met wifi-netwerken). Tot je de telefoon activeert, wordt je gevraagd deze te activeren telkens wanneer je de telefoon inschakelt."</string>
+ <string name="ota_skip_activation_dialog_message" msgid="6691722887019708713">"Als je de activering overslaat, kun je niet bellen of verbinding maken met mobiele-datanetwerken (je kunt wel verbinding maken met wifi-netwerken). Tot je de telefoon activeert, wordt je gevraagd deze te activeren telkens wanneer je de telefoon aanzet."</string>
<string name="ota_skip_activation_dialog_skip_label" msgid="5908029466817825633">"Overslaan"</string>
<string name="ota_activate" msgid="7939695753665438357">"Activeren"</string>
<string name="ota_title_activate_success" msgid="1272135024761004889">"De telefoon is geactiveerd."</string>
@@ -622,7 +635,7 @@
<string name="ecm_exit_dialog" msgid="4200691880721429078">"EcmExitDialog"</string>
<string name="phone_entered_ecm_text" msgid="8431238297843035842">"Modus voor noodoproepen ingeschakeld"</string>
<string name="phone_in_ecm_notification_title" msgid="6825016389926367946">"Modus voor noodoproepen"</string>
- <string name="phone_in_ecm_call_notification_text" msgid="653972232922670335">"Gegevensverbinding uitgeschakeld"</string>
+ <string name="phone_in_ecm_call_notification_text" msgid="653972232922670335">"Gegevensverbinding staat uit"</string>
<string name="phone_in_ecm_notification_complete_time" msgid="7341624337163082759">"Geen dataverbinding tot <xliff:g id="COMPLETETIME">%s</xliff:g>"</string>
<plurals name="alert_dialog_exit_ecm" formatted="false" msgid="5425906903766466743">
<item quantity="other">De telefoon bevindt zich <xliff:g id="COUNT_1">%s</xliff:g> minuten in de modus voor noodoproepen. In deze modus kunnen geen apps worden gebruikt waarvoor een gegevensverbinding nodig is. Wil je nu afsluiten?</item>
@@ -653,15 +666,15 @@
<string name="selectContact" msgid="1527612842599767382">"contact selecteren"</string>
<string name="not_voice_capable" msgid="2819996734252084253">"Audiogesprekken worden niet ondersteund"</string>
<string name="description_dial_button" msgid="8614631902795087259">"bellen"</string>
- <string name="description_dialpad_button" msgid="7395114120463883623">"toetsenblok weergeven"</string>
+ <string name="description_dialpad_button" msgid="7395114120463883623">"toetsenblok tonen"</string>
<string name="pane_title_emergency_dialpad" msgid="3627372514638694401">"Noodtoetsenblok"</string>
<string name="voicemail_visual_voicemail_switch_title" msgid="6610414098912832120">"Visuele voicemail"</string>
<string name="voicemail_set_pin_dialog_title" msgid="7005128605986960003">"Pincode instellen"</string>
<string name="voicemail_change_pin_dialog_title" msgid="4633077715231764435">"Pincode wijzigen"</string>
<string name="preference_category_ringtone" msgid="8787281191375434976">"Ringtone en trillen"</string>
<string name="pstn_connection_service_label" msgid="9200102709997537069">"Ingebouwde simkaarten"</string>
- <string name="enable_video_calling_title" msgid="7246600931634161830">"Videogesprekken inschakelen"</string>
- <string name="enable_video_calling_dialog_msg" msgid="7141478720386203540">"Als je videogesprekken wilt inschakelen, moet je de geoptimaliseerde 4G LTE-modus inschakelen in de netwerkinstellingen."</string>
+ <string name="enable_video_calling_title" msgid="7246600931634161830">"Videogesprekken aanzetten"</string>
+ <string name="enable_video_calling_dialog_msg" msgid="7141478720386203540">"Als je videogesprekken wilt aanzetten, moet je de geoptimaliseerde 4G LTE-modus aanzetten in de netwerkinstellingen."</string>
<string name="enable_video_calling_dialog_settings" msgid="8697890611305307110">"Netwerkinstellingen"</string>
<string name="enable_video_calling_dialog_close" msgid="4298929725917045270">"Sluiten"</string>
<string name="sim_label_emergency_calls" msgid="9078241989421522310">"Noodoproepen"</string>
@@ -692,8 +705,8 @@
<string name="change_pin_confirm_pins_dont_match" msgid="305164501222587215">"Pincodes komen niet overeen"</string>
<string name="change_pin_succeeded" msgid="2504705600693014403">"Voicemailpincode geüpdatet"</string>
<string name="change_pin_system_error" msgid="7772788809875146873">"Kan pincode niet instellen"</string>
- <string name="mobile_data_status_roaming_turned_off_subtext" msgid="6840673347416227054">"Dataroaming is uitgeschakeld"</string>
- <string name="mobile_data_status_roaming_turned_on_subtext" msgid="5615757897768777865">"Dataroaming is ingeschakeld"</string>
+ <string name="mobile_data_status_roaming_turned_off_subtext" msgid="6840673347416227054">"Dataroaming staat uit"</string>
+ <string name="mobile_data_status_roaming_turned_on_subtext" msgid="5615757897768777865">"Dataroaming staat aan"</string>
<string name="mobile_data_status_roaming_without_plan_subtext" msgid="6536671968072284677">"Momenteel aan het roamen, data-abonnement nodig"</string>
<string name="mobile_data_status_roaming_with_plan_subtext" msgid="2576177169108123095">"Momenteel aan het roamen, data-abonnement is actief"</string>
<string name="mobile_data_status_no_plan_subtext" msgid="170331026419263657">"Geen mobiele data meer"</string>
@@ -813,29 +826,29 @@
<string name="callFailed_otasp_provisioning_in_process" msgid="3345666183602879326">"Kan geen gesprek plaatsen omdat het apparaat momenteel wordt geregistreerd."</string>
<string name="callFailed_already_dialing" msgid="7250591188960691086">"Kan gesprek niet plaatsen omdat er al een uitgaand gesprek is gestart."</string>
<string name="callFailed_already_ringing" msgid="2376603543544289303">"Kan gesprek niet plaatsen omdat er een niet-beantwoord inkomend gesprek is. Beantwoord of weiger het inkomende gesprek voordat je een nieuw gesprek plaatst."</string>
- <string name="callFailed_calling_disabled" msgid="5010992739401206283">"Kan gesprek niet plaatsen omdat bellen is uitgeschakeld via de systeemeigenschap ro.telephony.disable-call."</string>
+ <string name="callFailed_calling_disabled" msgid="5010992739401206283">"Kan gesprek niet plaatsen omdat bellen is uitgezet via de systeemeigenschap ro.telephony.disable-call."</string>
<string name="callFailed_too_many_calls" msgid="5379426826618582180">"Kan gesprek niet plaatsen omdat er al twee actieve gesprekken zijn. Verbreek de verbinding in een van de gesprekken of voeg ze samen tot een conferencecall voordat je een nieuw gesprek plaatst."</string>
- <string name="supp_service_over_ut_precautions" msgid="2145018231396701311">"Als je <xliff:g id="SUPP_SERVICE">%s</xliff:g> wilt gebruiken, zorg je dat je mobiele data hebt ingeschakeld. Je kunt dit wijzigen via de mobiele netwerkinstellingen."</string>
- <string name="supp_service_over_ut_precautions_roaming" msgid="670342104569972327">"Als je <xliff:g id="SUPP_SERVICE">%s</xliff:g> wilt gebruiken, zorg je dat je mobiele data en dataroaming hebt ingeschakeld. Je kunt dit wijzigen via de mobiele netwerkinstellingen."</string>
- <string name="supp_service_over_ut_precautions_dual_sim" msgid="5166866975550910474">"Als je <xliff:g id="SUPP_SERVICE">%1$s</xliff:g> wilt gebruiken, zorg je dat je mobiele data hebt ingeschakeld voor sim <xliff:g id="SIM_NUMBER">%2$d</xliff:g>. Je kunt dit wijzigen via de mobiele netwerkinstellingen."</string>
- <string name="supp_service_over_ut_precautions_roaming_dual_sim" msgid="6627654855191817965">"Als je <xliff:g id="SUPP_SERVICE">%1$s</xliff:g> wilt gebruiken, zorg je dat je mobiele data en dataroaming hebt ingeschakeld voor sim <xliff:g id="SIM_NUMBER">%2$d</xliff:g>. Je kunt dit wijzigen via de mobiele netwerkinstellingen."</string>
+ <string name="supp_service_over_ut_precautions" msgid="2145018231396701311">"Als je <xliff:g id="SUPP_SERVICE">%s</xliff:g> wilt gebruiken, zorg je dat je mobiele data hebt aangezet. Je kunt dit wijzigen via de mobiele netwerkinstellingen."</string>
+ <string name="supp_service_over_ut_precautions_roaming" msgid="670342104569972327">"Als je <xliff:g id="SUPP_SERVICE">%s</xliff:g> wilt gebruiken, zorg je dat je mobiele data en dataroaming hebt aangezet. Je kunt dit wijzigen via de mobiele netwerkinstellingen."</string>
+ <string name="supp_service_over_ut_precautions_dual_sim" msgid="5166866975550910474">"Als je <xliff:g id="SUPP_SERVICE">%1$s</xliff:g> wilt gebruiken, zorg je dat je mobiele data hebt aangezet voor sim <xliff:g id="SIM_NUMBER">%2$d</xliff:g>. Je kunt dit wijzigen via de mobiele netwerkinstellingen."</string>
+ <string name="supp_service_over_ut_precautions_roaming_dual_sim" msgid="6627654855191817965">"Als je <xliff:g id="SUPP_SERVICE">%1$s</xliff:g> wilt gebruiken, zorg je dat je mobiele data en dataroaming hebt aangezet voor sim <xliff:g id="SIM_NUMBER">%2$d</xliff:g>. Je kunt dit wijzigen via de mobiele netwerkinstellingen."</string>
<string name="supp_service_over_ut_precautions_dialog_dismiss" msgid="5934541487903081652">"Sluiten"</string>
- <string name="radio_info_data_connection_enable" msgid="6183729739783252840">"Dataverbinding inschakelen"</string>
- <string name="radio_info_data_connection_disable" msgid="6404751291511368706">"Dataverbinding uitschakelen"</string>
+ <string name="radio_info_data_connection_enable" msgid="6183729739783252840">"Dataverbinding aanzetten"</string>
+ <string name="radio_info_data_connection_disable" msgid="6404751291511368706">"Dataverbinding uitzetten"</string>
<string name="volte_provisioned_switch_string" msgid="4812874990480336178">"VoLTE-registratie"</string>
<string name="vt_provisioned_switch_string" msgid="8295542122512195979">"Videogesprekken geregistreerd"</string>
<string name="wfc_provisioned_switch_string" msgid="3835004640321078988">"Wifi-gesprekken geregistreerd"</string>
<string name="eab_provisioned_switch_string" msgid="4449676720736033035">"EAB/aanwezigheid geregistreerd"</string>
<string name="cbrs_data_switch_string" msgid="6060356430838077653">"CBRS-gegevens"</string>
- <string name="dsds_switch_string" msgid="7564769822086764796">"DSDS inschakelen"</string>
+ <string name="dsds_switch_string" msgid="7564769822086764796">"DSDS aanzetten"</string>
<string name="dsds_dialog_title" msgid="8494569893941847575">"Apparaat opnieuw opstarten?"</string>
<string name="dsds_dialog_message" msgid="4047480385678538850">"Start het apparaat opnieuw op om de instelling te wijzigen."</string>
<string name="dsds_dialog_confirm" msgid="9032004888134129885">"Opnieuw opstarten"</string>
<string name="dsds_dialog_cancel" msgid="3245958947099586655">"Annuleren"</string>
<string name="radio_info_radio_power" msgid="8805595022160471587">"Mobiel radiovermogen"</string>
- <string name="radioInfo_menu_viewADN" msgid="4533179730908559846">"Adresboek op simkaart weergeven"</string>
- <string name="radioInfo_menu_viewFDN" msgid="1847236480527032061">"Vaste nummers weergeven"</string>
- <string name="radioInfo_menu_viewSDN" msgid="2613431584522392842">"Servicenummers weergeven"</string>
+ <string name="radioInfo_menu_viewADN" msgid="4533179730908559846">"Adresboek op simkaart bekijken"</string>
+ <string name="radioInfo_menu_viewFDN" msgid="1847236480527032061">"Vaste nummers bekijken"</string>
+ <string name="radioInfo_menu_viewSDN" msgid="2613431584522392842">"Servicenummers bekijken"</string>
<string name="radioInfo_menu_getIMS" msgid="1950869267853198232">"IMS-servicestatus"</string>
<string name="radio_info_ims_reg_status_title" msgid="6875885401313992007">"IMS-status"</string>
<string name="radio_info_ims_reg_status_registered" msgid="7095182114078864326">"Geregistreerd"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId van standaard simkaart voor data:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL-bandbreedte (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL-bandbreedte (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Mobiele locatiegegevens (beëindigd):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Fysieke LTE-kanaalconfiguratie:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Vernieuwingsfrequentie van mobiele data:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Alle mobiele meetgegevens:"</string>
@@ -896,7 +908,7 @@
<string name="radio_info_smsc_label" msgid="3749927072726033763">"SMSC:"</string>
<string name="radio_info_smsc_update_label" msgid="5141996256097115753">"Updaten"</string>
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Vernieuwen"</string>
- <string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS-controle in-/uitschakelen"</string>
+ <string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS-controle aan-/uitzetten"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-specifieke gegevens/instellingen"</string>
<string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC beschikbaar:"</string>
<string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR beperkt:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Telefooninformatie"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Gegevens voor providerregistratie"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Providerregistratie activeren"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Het bluetooth-signaal is zwak. Schakel over naar bellen op luidspreker."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Melding over gesprekskwaliteit"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Beëindigde SIP-accounts"</string>
</resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 5885108..3ceb270 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"କେବଳ ପ୍ରାଥମିକ ୟୁଜରଙ୍କ ଦ୍ଵାରା ଭଏସମେଲ୍ ସେଟିଙ୍ଗକୁ ସଂଶୋଧିତ କରାଯାଇପାରିବ।"</string>
<string name="puk_unlocked" msgid="4627340655215746511">"ଆପଣଙ୍କର SIM କାର୍ଡକୁ ଅବରୋଧ କରିଦିଆଯାଇଛି। ଆପଣଙ୍କର ଫୋନ୍ ଅନଲକ୍ ହେଉଛି…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM ନେଟ୍ୱର୍କକୁ ଅନଲକ୍ କରିବା ପାଇଁ PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"ଅପରେଟର୍ ପାଇଁ SIM ଲକ୍ କରାଯାଇଛି"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"ଅନଲକ୍ କରନ୍ତୁ"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"ଖାରଜ କରନ୍ତୁ"</string>
<string name="requesting_unlock" msgid="930512210309437741">"ନେଟ୍ୱର୍କକୁ ଅନଲକ୍ କରିବା ପାଇଁ ଅନୁରୋଧ କରାଯାଉଛି…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g>କୁ ଫରୱାର୍ଡ କରାଯାଉଛି"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"ବନ୍ଦ ଅଛି"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"ଆପଣଙ୍କର ଫୋନ୍ ଅପହଞ୍ଚ ଦୂରତାରେ ଥିବାବେଳେ ଆପଣଙ୍କର କେରିଅର୍ କଲ୍ ଫର୍ୱାର୍ଡିଙ୍ଗକୁ ଅକ୍ଷମ କରିବାରେ ସପୋର୍ଟ କରିବେ ନାହିଁ।"</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"ଆପଣଙ୍କ ମୋବାଇଲ କମ୍ପାନୀ କଲ୍ ଫର୍ୱାର୍ଡିଂ ସୁବିଧାର ସମର୍ଥନ କରେ ନାହିଁ।"</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"\'କଲ୍ ୱେଟିଂ\' ସୁବିଧା ଚାଲୁ କରିବେ?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"କଲ୍ ସମୟରେ, ଇନକମିଂ କଲଗୁଡ଼ିକ ବିଷୟରେ ଆପଣଙ୍କୁ ସୂଚିତ କରାଯିବ"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"ଚାଲୁ କରନ୍ତୁ"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"ବାତିଲ୍ କରନ୍ତୁ"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMSରେ CDMA \'କଲ୍ ୱେଟିଂ\' ସୁବିଧା ଚାଲୁ ଅଛି"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMSରେ CDMA \'କଲ୍ ୱେଟିଂ\' ସୁବିଧା ବନ୍ଦ ଅଛି"</string>
<string name="updating_title" msgid="6130548922615719689">"କଲ୍ ସେଟିଂସ୍"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"କଲ୍ ସେଟିଙ୍ଗକୁ କେବଳ ଆଡମିନ୍ ୟୁଜର୍ ବଦଳାଇପାରିବେ।"</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"ସେଟିଙ୍ଗ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -174,7 +182,7 @@
<string name="connect_later" msgid="1950138106010005425">"ବର୍ତ୍ତମାନ ଏହି ନେଟ୍ୱର୍କରେ କନେକ୍ଟ ହୋଇପାରିବ ନାହିଁ। ପରେ ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
<string name="registration_done" msgid="5337407023566953292">"ଏହି ନେଟ୍ୱର୍କରେ ପଞ୍ଜୀକୃତ କରାଯାଇଛି।"</string>
<string name="already_auto" msgid="8607068290733079336">"ପୂର୍ବରୁ ସ୍ଵାଚାଳିତ ଚୟନରେ ଅଛି।"</string>
- <string name="select_automatically" msgid="779750291257872651">"ସ୍ଵଚାଳିତ ଭାବେ ନେଟ୍ୱର୍କ ଚୟନ କରନ୍ତୁ"</string>
+ <string name="select_automatically" msgid="779750291257872651">"ସ୍ଵଚାଳିତ ଭାବେ ନେଟୱାର୍କ ଚୟନ କରନ୍ତୁ"</string>
<string name="manual_mode_disallowed_summary" msgid="3970048592179890197">"%1$s ସହ କନେକ୍ଟ ହେଲେ ଉପଲବ୍ଧ ହେବ ନାହିଁ"</string>
<string name="network_select_title" msgid="4117305053881611988">"ନେଟ୍ୱାର୍କ"</string>
<string name="register_automatically" msgid="3907580547590554834">"ସ୍ଵଚାଳିତ ପଞ୍ଜୀକରଣ…"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"ସୀମିତ SIM ପ୍ରକାର୍ଯ୍ୟ"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g> ବ୍ୟବହାର କରିବା ସମୟରେ <xliff:g id="CARRIER_NAME">%1$s</xliff:g> କଲ୍ ଏବଂ ଡାଟା ସେବାଗୁଡ଼ିକ ବ୍ଲକ୍ କରାଯାଇପାରେ।"</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"ଅନ୍ୟ SIM ବ୍ୟବହାର କରିବା ସମୟରେ <xliff:g id="CARRIER_NAME">%1$s</xliff:g> କଲ୍ ଏବଂ ଡାଟା ସେବାଗୁଡ଼ିକ ବ୍ଲକ୍ କରାଯାଇପାରେ।"</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"ଅସମର୍ଥିତ SIP ଆକାଉଣ୍ଟଗୁଡ଼ିକ ମିଳିଛି ଏବଂ କାଢ଼ି ଦିଆଯାଇଛି"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP କଲିଂ ଆଉ Android ପ୍ଲାଟଫର୍ମ ଦ୍ୱାରା ସମର୍ଥିତ ନୁହେଁ।\nଆପଣଙ୍କର ପୂର୍ବରୁ ଥିବା SIP ଆକାଉଣ୍ଟ <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>କୁ କାଢ଼ି ଦିଆଯାଇଛି।\nଦୟାକରି ଆପଣଙ୍କ ଡିଫଲ୍ଟ କଲିଂ ଆକାଉଣ୍ଟ ସେଟିଂକୁ ସୁନିଶ୍ଚିତ କରନ୍ତୁ।"</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"ସେଟିଂସକୁ ଯାଆନ୍ତୁ"</string>
<string name="data_usage_title" msgid="8438592133893837464">"ଆପ୍ ଦ୍ୱାରା ଡାଟା ବ୍ୟବହାର"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g>ରେ <xliff:g id="ID_1">%1$s</xliff:g> ମୋବାଇଲ୍ ଡାଟା ବ୍ୟବହାର କରାଯାଇଛି"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"ଉନ୍ନତ"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"ଭୁଲ୍ ପାସ୍ୱର୍ଡ। ବର୍ତ୍ତମାନ SIM ଲକ୍ ହୋଇଗଲା। PUK2କୁ ପ୍ରବେଶ କରନ୍ତୁ।"</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2କୁ ସ୍ଥାୟୀରୂପେ ଅବରୋଧ କରାଯାଇଛି।"</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"ଆପଣଙ୍କର <xliff:g id="NUMBER">%d</xliff:g>ଟି ସୁଯୋଗ ବଳକା ଅଛି।"</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 ଲକ୍ କରାଯାଇଛି। ଅନଲକ୍ କରିବାକୁ ସେବା ପ୍ରଦାନକାରୀ ସହ ଯୋଗାଯୋଗ କରନ୍ତୁ।"</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2କୁ ଆଉ ଅବରୋଧ କରାଯାଇନାହିଁ"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"ନେଟ୍ୱର୍କ କିମ୍ବା SIM କାର୍ଡ ତ୍ରୁଟି"</string>
<string name="doneButton" msgid="7371209609238460207">"ହୋଇଗଲା"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"କଲ୍ କରିବା ପାଇଁ ମୋବାଇଲ୍ ନେଟ୍ୱର୍କକୁ ଚାଲୁ କରନ୍ତୁ, ଏୟାରପ୍ଲେନ୍ ମୋଡ୍କୁ ବନ୍ଦ କରନ୍ତୁ କିମ୍ବା ବ୍ୟାଟେରୀ ସେଭର୍କୁ ବନ୍ଦ କରନ୍ତୁ।"</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"ଗୋଟିଏ କଲ୍ କରିବା ପାଇଁ ଏୟାରପ୍ଲେନ୍ ମୋଡ୍କୁ ବନ୍ଦ କରନ୍ତୁ।"</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"ଗୋଟିଏ କଲ୍ କରିବା ପାଇଁ ଏୟାରପ୍ଲେନ୍ ମୋଡ୍କୁ ବନ୍ଦ କରନ୍ତୁ କିମ୍ବା ଏକ ତାରବିହୀନ ନେଟ୍ୱର୍କ ସହ କନେକ୍ଟ କରନ୍ତୁ।"</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"ଫୋନଟି ବହୁତ ଗରମ ହୋଇଯାଇଛି"</b>\n\n"ଏହି କଲକୁ ସମ୍ପୂର୍ଣ୍ଣ କରାଯାଇପାରିବ ନାହିଁ। ଆପଣଙ୍କ ଫୋନ୍ ଥଣ୍ଡା ହେବା ପରେ ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ। \n\nଆପଣ ଏବେ ବି ଜରୁରୀକାଳୀନ କଲ୍ କରିପାରିବେ।"</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"ଗୋଟିଏ ସାଧାରଣ କଲ୍ କରିବା ପାଇଁ ଜରୁରିକାଳୀନ କଲବ୍ୟାକ୍ ମୋଡ୍ରୁ ବାହାରି ଆସନ୍ତୁ।"</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"ନେଟ୍ୱର୍କରେ ପଞ୍ଜୀକୃତ କରାଯାଇନାହିଁ।"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"ମୋବାଇଲ୍ ନେଟ୍ୱର୍କ ଉପଲବ୍ଧ ନାହିଁ।"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ଡିଫଲ୍ଟ ଡାଟା SIMର SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL ବ୍ୟାଣ୍ଡୱିଡଥ୍ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL ବ୍ୟାଣ୍ଡୱିଡଥ୍ (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"ସେଲ୍ ଲୋକେସନ୍ ସୂଚନା (ଅନୁରୋଧ କରାଯାଇଛି):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ଫିଜିକାଲ୍ ଚ୍ୟାନେଲ୍ କନ୍ଫିଗରେସନ୍:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"ସେଲ୍ ସୂଚନା ରିଫ୍ରେସ୍ ଦର:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"ସମସ୍ତ ସେଲ୍ ପରିମାପ ସୂଚନା:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"ଫୋନ୍ ସୂଚନା"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"ମୋବାଇଲ୍ କମ୍ପାନୀ ପ୍ରାବଧାନ ସୂଚନା"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"କେରିଅର୍ ପ୍ରୋଭିଜନିଂ ଟ୍ରିଗର୍ କରନ୍ତୁ"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"ଆପଣଙ୍କ ବ୍ଲୁଟୁଥ୍ ସିଗନାଲ୍ ଦୁର୍ବଳ ଅଛି। ସ୍ପିକରଫୋନକୁ ସ୍ୱିଚ୍ କରିବା ପାଇଁ ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"କଲ୍ ଗୁଣବତ୍ତା ବିଜ୍ଞପ୍ତି"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"ଅସମର୍ଥିତ SIP ଆକାଉଣ୍ଟଗୁଡ଼ିକ"</string>
</resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 7e166eb..e4883ef 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"ਵੌਇਸਮੇਲ ਸੈਟਿੰਗਾਂ ਸਿਰਫ਼ ਪ੍ਰਾਇਮਰੀ ਵਰਤੋਂਕਾਰ ਦੁਆਰਾ ਹੀ ਸੰਸ਼ੋਧਿਤ ਕੀਤੀਆਂ ਜਾ ਸਕਦੀਆਂ ਹਨ।"</string>
<string name="puk_unlocked" msgid="4627340655215746511">"ਤੁਹਾਡਾ SIM ਕਾਰਡ ਅਨਬਲੌਕ ਕਰ ਦਿੱਤਾ ਗਿਆ ਹੈ। ਤੁਹਾਡਾ ਫੋਨ ਅਨੌਲਕ ਹੋ ਰਿਹਾ ਹੈ..."</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM ਨੈਟਵਰਕ ਅਣਲਾਕ PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"ਓਪਰੇਟਰ ਲਈ ਸਿਮ ਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"ਅਣਲਾਕ ਕਰੋ"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"ਖਾਰਜ ਕਰੋ"</string>
<string name="requesting_unlock" msgid="930512210309437741">"ਨੈਟਵਰਕ ਅਣਲਾਕ ਦੀ ਬੇਨਤੀ ਕਰ ਰਿਹਾ ਹੈ…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> ਨੂੰ ਫੌਰਵਾਰਡ ਕਰ ਰਿਹਾ ਹੈ"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"ਬੰਦ"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"ਜਦੋਂ ਤੁਹਾਡਾ ਫੋਨ ਨਾਪਹੁੰਚਯੋਗ ਹੁੰਦਾ ਹੈ ਤਾਂ ਤੁਹਾਡਾ ਕੈਰੀਅਰ ਕਾਲ ਫੌਰਵਾਰਡਿੰਗ ਨੂੰ ਅਸਮਰੱਥ ਬਣਾਉਣ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦਾ।"</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"ਤੁਹਾਡਾ ਕੈਰੀਅਰ ਕਾਲ ਫਾਰਵਰਡਿੰਗ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦਾ ਹੈ।"</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"ਕੀ ਕਾਲ ਦੀ ਉਡੀਕ ਸੈਟਿੰਗ ਨੂੰ ਚਾਲੂ ਕਰਨਾ ਹੈ?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"ਕਾਲ ਕਰਨ ਵੇਲੇ, ਤੁਹਾਨੂੰ ਇਨਕਮਿੰਗ ਕਾਲਾਂ ਬਾਰੇ ਸੂਚਿਤ ਕੀਤਾ ਜਾਵੇਗਾ"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"ਚਾਲੂ ਕਰੋ"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"ਰੱਦ ਕਰੋ"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS ਦੇ ਅਧੀਨ CDMA ਕਾਲ ਦੀ ਉਡੀਕ ਵਾਲੀ ਸੁਵਿਧਾ ਚਾਲੂ ਹੈ"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS ਦੇ ਅਧੀਨ CDMA ਕਾਲ ਦੀ ਉਡੀਕ ਵਾਲੀ ਸੁਵਿਧਾ ਬੰਦ ਹੈ"</string>
<string name="updating_title" msgid="6130548922615719689">"ਕਾਲ ਸੈਟਿੰਗਾਂ"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"ਕਾਲ ਸੈਟਿੰਗਾਂ ਸਿਰਫ਼ ਪ੍ਰਸ਼ਾਸਕ ਵਰਤੋਂਕਾਰ ਵੱਲੋਂ ਹੀ ਬਦਲੀਆਂ ਜਾ ਸਕਦੀਆਂ ਹਨ।"</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"ਸੈਟਿੰਗਾਂ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -265,7 +273,7 @@
<string name="network_operator_category" msgid="4992217193732304680">"ਨੈੱਟਵਰਕ"</string>
<string name="enhanced_4g_lte_mode_title" msgid="4213420368777080540">"ਵਿਸਤ੍ਰਿਤ 4G LTE ਮੋਡ"</string>
<!-- no translation found for enhanced_4g_lte_mode_title_variant:0 (7240155150166394308) -->
- <string name="enhanced_4g_lte_mode_summary" msgid="7725708511804143638">"ਵੌਇਸ ਅਤੇ ਹੋਰ ਸੰਚਾਰ ਬਿਹਤਰ ਬਣਾਉਣ ਲਈ LTE ਸੇਵਾਵਾਂ ਵਰਤੋ ( ਸਿਫ਼ਾਰਸ਼ ਕੀਤਾ)"</string>
+ <string name="enhanced_4g_lte_mode_summary" msgid="7725708511804143638">"ਅਵਾਜ਼ੀ ਕਾਲਾਂ ਅਤੇ ਹੋਰ ਸੰਚਾਰ ਬਿਹਤਰ ਬਣਾਉਣ ਲਈ LTE ਸੇਵਾਵਾਂ ਵਰਤੋ (ਸਿਫ਼ਾਰਸ਼ੀ)"</string>
<string name="enhanced_4g_lte_mode_summary_o2" msgid="2521108446409016542">"ਅਵਾਜ਼ ਅਤੇ ਹੋਰ ਸੰਚਾਰਾਂ (ਸਿਫ਼ਾਰਸ਼ ਕੀਤੇ) ਨੂੰ ਬਿਹਤਰ ਬਣਾਉਣ ਲਈ 4G ਸੇਵਾਵਾਂ ਵਰਤੋ"</string>
<!-- no translation found for enhanced_4g_lte_mode_sumary_variant:0 (2943982616649705147) -->
<!-- no translation found for enhanced_4g_lte_mode_sumary_variant:1 (5262249464504131443) -->
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"ਸੀਮਤ ਸਿਮ ਪ੍ਰਕਾਰਜਾਤਮਕਤਾ"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g> ਨੂੰ ਵਰਤਣ ਵੇਲੇ ਸ਼ਾਇਦ <xliff:g id="CARRIER_NAME">%1$s</xliff:g> ਦੀਆਂ ਕਾਲਾਂ ਅਤੇ ਡਾਟਾ ਸੇਵਾਵਾਂ ਨੂੰ ਬਲਾਕ ਕੀਤਾ ਜਾਵੇ।"</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"ਕੋਈ ਹੋਰ ਸਿਮ ਵਰਤਣ ਵੇਲੇ ਸ਼ਾਇਦ <xliff:g id="CARRIER_NAME">%1$s</xliff:g> ਦੀਆਂ ਕਾਲਾਂ ਅਤੇ ਡਾਟਾ ਸੇਵਾਵਾਂ ਨੂੰ ਬਲਾਕ ਕੀਤਾ ਜਾਵੇ।"</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"ਨਾਪਸੰਦ ਕੀਤੇ SIP ਖਾਤੇ ਮਿਲੇ ਅਤੇ ਹਟਾ ਦਿੱਤੇ ਗਏ"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP ਕਾਲਿੰਗ ਹੁਣ Android ਪਲੇਟਫਾਰਮ ਵੱਲੋਂ ਸਮਰਥਿਤ ਨਹੀਂ ਹੈ।\nਤੁਹਾਡੇ ਮੌਜੂਦਾ SIP ਖਾਤੇ <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> ਹਟਾ ਦਿੱਤੇ ਗਏ ਹਨ।\nਕਿਰਪਾ ਕਰਕੇ ਆਪਣੀ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਕਾਲਿੰਗ ਖਾਤਾ ਸੈਟਿੰਗ ਦੀ ਤਸਦੀਕ ਕਰੋ।"</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"ਸੈਟਿੰਗਾਂ \'ਤੇ ਜਾਓ"</string>
<string name="data_usage_title" msgid="8438592133893837464">"ਐਪ ਡਾਟਾ ਵਰਤੋਂ"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g> ਤੱਕ <xliff:g id="ID_1">%1$s</xliff:g> ਮੋਬਾਈਲ ਡਾਟਾ ਵਰਤਿਆ ਗਿਆ"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"ਉੱਨਤ"</string>
@@ -327,24 +338,24 @@
<string name="enable_disable_maintenance" msgid="3824817751053035017">"ਪ੍ਰਬੰਧਨ"</string>
<string name="maintenance_enable" msgid="2646784483222342290">"ਪ੍ਰਬੰਧਨ ਸਮਰਥਿਤ"</string>
<string name="maintenance_disable" msgid="2121032601497725602">"ਪ੍ਰਬੰਧਨ ਅਸਮਰਥਿਤ"</string>
- <string name="general_news_settings" msgid="2670499575962080411">"ਸਧਾਰਨ ਖ਼ਬਰਾਂ"</string>
- <string name="bf_news_settings" msgid="8571709425370794221">"ਵਪਾਰਕ ਅਤੇ ਵਿੱਤੀ ਖ਼ਬਰਾਂ"</string>
- <string name="sports_news_settings" msgid="2684364556989168438">"ਖੇਡ ਦੀਆਂ ਖ਼ਬਰਾਂ"</string>
- <string name="entertainment_news_settings" msgid="4228527702346305543">"ਮਨੋਰੰਜਕ ਖ਼ਬਰਾਂ"</string>
+ <string name="general_news_settings" msgid="2670499575962080411">"ਸਧਾਰਨ ਖਬਰਾਂ"</string>
+ <string name="bf_news_settings" msgid="8571709425370794221">"ਕਾਰੋਬਾਰੀ ਅਤੇ ਵਿੱਤੀ ਖਬਰਾਂ"</string>
+ <string name="sports_news_settings" msgid="2684364556989168438">"ਖੇਡਾਂ ਨਾਲ ਸੰਬੰਧਿਤ ਖਬਰਾਂ"</string>
+ <string name="entertainment_news_settings" msgid="4228527702346305543">"ਮਨੋਰੰਜਨ ਸੰਬੰਧੀ ਖਬਰਾਂ"</string>
<string name="enable_disable_local" msgid="7654175079979415572">"ਸਥਾਨਕ"</string>
- <string name="local_enable" msgid="790606890868710629">"ਸਥਾਨਕ ਖ਼ਬਰਾਂ ਸਮਰਥਿਤ"</string>
- <string name="local_disable" msgid="7649945293198602877">"ਸਥਾਨਕ ਖ਼ਬਰਾਂ ਅਸਮਰਥਿਤ"</string>
+ <string name="local_enable" msgid="790606890868710629">"ਸਥਾਨਕ ਖਬਰਾਂ ਚਾਲੂ ਕੀਤੀਆਂ ਗਈਆਂ"</string>
+ <string name="local_disable" msgid="7649945293198602877">"ਸਥਾਨਕ ਖਬਰਾਂ ਬੰਦ ਕੀਤੀਆਂ ਗਈਆਂ"</string>
<string name="enable_disable_regional" msgid="5783403191376564638">"ਖੇਤਰੀ"</string>
- <string name="regional_enable" msgid="7730109417536296079">"ਖੇਤਰੀ ਖ਼ਬਰਾਂ ਸਮਰਥਿਤ"</string>
- <string name="regional_disable" msgid="3781951818157772545">"ਖੇਤਰੀ ਖ਼ਬਰਾਂ ਅਸਮਰਥਿਤ"</string>
+ <string name="regional_enable" msgid="7730109417536296079">"ਖੇਤਰੀ ਖਬਰਾਂ ਚਾਲੂ ਕੀਤੀਆਂ ਗਈਆਂ"</string>
+ <string name="regional_disable" msgid="3781951818157772545">"ਖੇਤਰੀ ਖਬਰਾਂ ਬੰਦ ਕੀਤੀਆਂ ਗਈਆਂ"</string>
<string name="enable_disable_national" msgid="6198481711934897632">"ਰਾਸ਼ਟਰੀ"</string>
- <string name="national_enable" msgid="5159683504138239304">"ਰਾਸ਼ਟਰੀ ਖ਼ਬਰਾਂ ਸਮਰਥਿਤ"</string>
- <string name="national_disable" msgid="8484356368757118987">"ਰਾਸ਼ਟਰੀ ਖ਼ਬਰਾਂ ਅਸਮਰਥਿਤ"</string>
+ <string name="national_enable" msgid="5159683504138239304">"ਰਾਸ਼ਟਰੀ ਖਬਰਾਂ ਚਾਲੂ ਕੀਤੀਆਂ ਗਈਆਂ"</string>
+ <string name="national_disable" msgid="8484356368757118987">"ਰਾਸ਼ਟਰੀ ਖਬਰਾਂ ਬੰਦ ਕੀਤੀਆਂ ਗਈਆਂ"</string>
<string name="enable_disable_international" msgid="4204334217211198792">"ਅੰਤਰਰਾਸ਼ਟਰੀ"</string>
- <string name="international_enable" msgid="8943466745792690340">"ਅੰਤਰਰਾਸ਼ਟਰੀ ਖ਼ਬਰਾਂ ਸਮਰਥਿਤ"</string>
- <string name="international_disable" msgid="4803498658100318265">"ਅੰਤਰਰਾਸ਼ਟਰੀ ਖ਼ਬਰਾਂ ਅਸਮਰਥਿਤ"</string>
+ <string name="international_enable" msgid="8943466745792690340">"ਅੰਤਰਰਾਸ਼ਟਰੀ ਖਬਰਾਂ ਚਾਲੂ ਕੀਤੀਆਂ ਗਈਆਂ"</string>
+ <string name="international_disable" msgid="4803498658100318265">"ਅੰਤਰਰਾਸ਼ਟਰੀ ਖਬਰਾਂ ਬੰਦ ਕੀਤੀਆਂ ਗਈਆਂ"</string>
<string name="list_language_title" msgid="1850167908665485738">"ਭਾਸ਼ਾ"</string>
- <string name="list_language_summary" msgid="7921756070782277559">"ਖ਼ਬਰਾਂ ਦੀ ਭਾਸ਼ਾ ਚੁਣੋ"</string>
+ <string name="list_language_summary" msgid="7921756070782277559">"ਖਬਰਾਂ ਦੀ ਭਾਸ਼ਾ ਚੁਣੋ"</string>
<string-array name="list_language_entries">
<item msgid="2347238508726934281">"ਅੰਗ੍ਰੇਜ਼ੀ"</item>
<item msgid="5172468397620875174">"ਫਰਾਂਸੀਸੀ"</item>
@@ -394,9 +405,9 @@
<string name="enable_disable_mhh" msgid="715930476289202466">"ਚਿਕਿਤਸਾ, ਸਿਹਤ ਅਤੇ ਹਸਪਤਾਲ"</string>
<string name="mhh_enable" msgid="7224396815285147008">"ਚਿਕਿਤਸਾ, ਸਿਹਤ ਅਤੇ ਹਸਪਤਾਲ ਸਮਰਥਿਤ"</string>
<string name="mhh_disable" msgid="5503643028885686265">"ਚਿਕਿਤਸਾ, ਸਿਹਤ ਅਤੇ ਹਸਪਤਾਲ ਅਸਮਰਥਿਤ"</string>
- <string name="enable_disable_technology_news" msgid="2794845609698078400">"ਟੈਕਨਾਲੋਜੀ ਖ਼ਬਰਾਂ"</string>
- <string name="technology_news_enable" msgid="1908991199492598311">"ਟੈਕਨਾਲੋਜੀ ਖ਼ਬਰਾਂ ਸਮਰਥਿਤ"</string>
- <string name="technology_news_disable" msgid="8388582607149800889">"ਟੈਕਨਾਲੋਜੀ ਖ਼ਬਰਾਂ ਅਸਮਰਥਿਤ"</string>
+ <string name="enable_disable_technology_news" msgid="2794845609698078400">"ਤਕਨਾਲੋਜੀ ਖਬਰਾਂ"</string>
+ <string name="technology_news_enable" msgid="1908991199492598311">"ਤਕਨਾਲੋਜੀ ਖਬਰਾਂ ਚਾਲੂ ਕੀਤੀਆਂ ਗਈਆਂ"</string>
+ <string name="technology_news_disable" msgid="8388582607149800889">"ਤਕਨਾਲੋਜੀ ਖਬਰਾਂ ਬੰਦ ਕੀਤੀਆਂ ਗਈਆਂ"</string>
<string name="enable_disable_multi_category" msgid="5958248155437940625">"ਮਲਟੀ-ਸ਼੍ਰੇਣੀ"</string>
<string name="multi_category_enable" msgid="4531915767817483960">"ਮਲਟੀ-ਸ਼੍ਰੇਣੀ ਸਮਰਥਿਤ"</string>
<string name="multi_category_disable" msgid="6325934413701238104">"ਮਲਟੀ-ਸ਼੍ਰੇਣੀ ਅਸਮਰਥਿਤ"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"ਪਾਸਵਰਡ ਗ਼ਲਤ। SIM ਹੁਣ ਲੌਕ ਹੈ। PUK2 ਦਰਜ ਕਰੋ।"</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 ਸਥਾਈ ਤੌਰ ਤੇ ਬਲੌਕ ਕੀਤਾ ਹੋਇਆ ਹੈ।"</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"ਤੁਹਾਡੇ ਕੋਲ <xliff:g id="NUMBER">%d</xliff:g> ਕੋਸ਼ਿਸ਼ਾਂ ਬਾਕੀ ਹਨ।"</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 ਲਾਕ ਹੈ। ਅਣਲਾਕ ਕਰਨ ਲਈ ਸੇਵਾ ਪ੍ਰਦਾਨਕ ਨੂੰ ਸੰਪਰਕ ਕਰੋ।"</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 ਹੁਣ ਬਲੌਕ ਨਹੀਂ ਹੈ"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"ਨੈਟਵਰਕ ਜਾਂ SIM ਕਾਰਡ ਤਰੁੱਟੀ"</string>
<string name="doneButton" msgid="7371209609238460207">"ਹੋ ਗਿਆ"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"ਕਾਲ ਕਰਨ ਲਈ ਮੋਬਾਈਲ ਨੈੱਟਵਰਕ ਚਾਲੂ ਕਰੋ, ਜਹਾਜ਼ ਮੋਡ ਬੰਦ ਕਰੋ ਜਾਂ ਬੈਟਰੀ ਸੇਵਰ ਮੋਡ ਬੰਦ ਕਰੋ।"</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"ਇੱਕ ਕਾਲ ਕਰਨ ਲਈ ਏਅਰਪਲੇਨ ਮੋਡ ਬੰਦ ਕਰੋ।"</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"ਏਅਰਪਲੇਨ ਮੋਡ ਬੰਦ ਕਰੋ ਜਾਂ ਇੱਕ ਕਾਲ ਕਰਨ ਲਈ ਇੱਕ ਵਾਇਰਲੈਸ ਨੈਟਵਰਕ ਨਾਲ ਕਨੈਕਟ ਕਰੋ।"</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"ਫ਼ੋਨ ਬਹੁਤ ਗਰਮ ਹੋ ਗਿਆ ਹੈ"</b>\n\n"ਇਸ ਕਾਲ ਨੂੰ ਪੂਰਾ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ। ਫ਼ੋਨ ਦੇ ਠੰਡਾ ਹੋਣ ਜਾਣ \'ਤੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।\n\nਤੁਸੀਂ ਹਾਲੇ ਵੀ ਸੰਕਟਕਾਲੀਨ ਕਾਲਾਂ ਕਰ ਸਕਦੇ ਹੋ।"</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"ਇੱਕ ਗ਼ੈਰ-ਅਪਾਤਕਾਲ ਕਾਲ ਕਰਨ ਲਈ ਅਪਾਤਕਾਲ ਕਾਲਬੈਕ ਮੋਡ ਤੋਂ ਬਾਹਰ ਆਓ।"</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"ਨੈਟਵਰਕ ਤੇ ਰਜਿਸਟਰ ਨਹੀਂ ਕੀਤਾ।"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"ਮੋਬਾਈਲ ਨੈਟਵਰਕ ਉਪਲਬਧ ਨਹੀਂ।"</string>
@@ -582,7 +595,7 @@
<string name="hac_mode_title" msgid="4127986689621125468">"ਸੁਣਨ ਲਈ ਸਹਾਇਕ ਸਾਧਨ"</string>
<string name="hac_mode_summary" msgid="7774989500136009881">"ਸੁਣਨ ਲਈ ਸਹਾਇਕ ਸਾਧਨ ਅਨੁਕੂਲਤਾ ਚਾਲੂ ਕਰੋ"</string>
<string name="rtt_mode_title" msgid="3075948111362818043">"ਨਾਲ-ਦੀ-ਨਾਲ ਦਿਸਦੀ ਲਿਖਤ (RTT) ਵਾਲੀ ਕਾਲ"</string>
- <string name="rtt_mode_summary" msgid="8631541375609989562">"ਕਿਸੇ ਵੌਇਸ ਕਾਲ ਦੌਰਾਨ ਸੁਨੇਹਾ ਭੇਜਣ ਦਿਓ"</string>
+ <string name="rtt_mode_summary" msgid="8631541375609989562">"ਕਿਸੇ ਅਵਾਜ਼ੀ ਕਾਲ ਦੌਰਾਨ ਸੁਨੇਹਾ ਭੇਜਣ ਦਿਓ"</string>
<string name="rtt_mode_more_information" msgid="587500128658756318">"RTT ਅਜਿਹੇ ਕਾਲ ਕਰਨ ਵਾਲੇ ਲੋਕਾਂ ਦੀ ਸਹਾਇਤਾ ਕਰਦਾ ਹੈ ਜੋ ਬੋਲੇ, ਘੱਟ ਸੁਣਨ ਵਾਲੇ, ਬੋਲਣ ਵਿੱਚ ਅਸਮਰਥ ਜਾਂ ਜਿਨ੍ਹਾਂ ਲਈ ਸਿਰਫ਼ ਅਵਾਜ਼ ਕਾਫ਼ੀ ਨਹੀਂ ਹੁੰਦੀ।<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>ਹੋਰ ਜਾਣੋ</a>\n <br><br> - RTT ਕਾਲਾਂ ਨੂੰ ਇੱਕ ਪ੍ਰਤਿਲਿਪੀ ਸੁਨੇਹੇ ਵਜੋਂ ਰੱਖਿਅਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ\n <br> - RTT ਵੀਡੀਓ ਕਾਲਾਂ ਲਈ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
<string name="no_rtt_when_roaming" msgid="5268008247378355389">"ਨੋਟ ਕਥਨ:ਵੇਲੇ RTT ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
<string-array name="tty_mode_entries">
@@ -651,7 +664,7 @@
<string name="contactPhoto" msgid="7885089213135154834">"ਸੰਪਰਕ ਫ਼ੋਟੋ"</string>
<string name="goPrivate" msgid="4645108311382209551">"ਨਿੱਜੀ ਜਾਓ"</string>
<string name="selectContact" msgid="1527612842599767382">"ਸੰਪਰਕ ਚੁਣੋ"</string>
- <string name="not_voice_capable" msgid="2819996734252084253">"ਵੌਇਸ ਕਾਲਿੰਗ ਸਮਰਥਿਤ ਨਹੀਂ"</string>
+ <string name="not_voice_capable" msgid="2819996734252084253">"ਅਵਾਜ਼ੀ ਕਾਲਿੰਗ ਸਮਰਥਿਤ ਨਹੀਂ"</string>
<string name="description_dial_button" msgid="8614631902795087259">"ਡਾਇਲ ਕਰੋ"</string>
<string name="description_dialpad_button" msgid="7395114120463883623">"ਡਾਇਲਪੈਡ ਦਿਖਾਓ"</string>
<string name="pane_title_emergency_dialpad" msgid="3627372514638694401">"ਸੰਕਟਕਾਲੀਨ ਡਾਇਲਪੈਡ"</string>
@@ -842,7 +855,7 @@
<string name="radio_info_ims_reg_status_not_registered" msgid="8045821447288876085">"ਰਜਿਸਟਰ ਨਹੀਂ ਕੀਤੀ ਗਈ"</string>
<string name="radio_info_ims_feature_status_available" msgid="6493200914756969292">"ਉਪਲਬਧ"</string>
<string name="radio_info_ims_feature_status_unavailable" msgid="8930391136839759778">"ਅਣਉਪਲਬਧ"</string>
- <string name="radio_info_ims_reg_status" msgid="25582845222446390">"IMS ਰਜਿਸਟਰੇਸ਼ਨ: <xliff:g id="STATUS">%1$s</xliff:g>\nLTE \'ਤੇ ਵੌਇਸ: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nਵਾਈ-ਫਾਈ \'ਤੇ ਵੌਇਸ: <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nਵੀਡੀਓ ਕਾਲਿੰਗ: <xliff:g id="AVAILABILITY_2">%4$s</xliff:g>\nUT ਇੰਟਰਫੇਸ: <xliff:g id="AVAILABILITY_3">%5$s</xliff:g>"</string>
+ <string name="radio_info_ims_reg_status" msgid="25582845222446390">"IMS ਰਜਿਸਟਰੇਸ਼ਨ: <xliff:g id="STATUS">%1$s</xliff:g>\nLTE \'ਤੇ ਅਵਾਜ਼: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nਵਾਈ-ਫਾਈ \'ਤੇ ਅਵਾਜ਼ੀ ਕਾਲ: <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nਵੀਡੀਓ ਕਾਲਿੰਗ: <xliff:g id="AVAILABILITY_2">%4$s</xliff:g>\nUT ਇੰਟਰਫੇਸ: <xliff:g id="AVAILABILITY_3">%5$s</xliff:g>"</string>
<string name="radioInfo_service_in" msgid="45753418231446400">"ਸੇਵਾ ਵਿੱਚ"</string>
<string name="radioInfo_service_out" msgid="287972405416142312">"ਸੇਵਾ ਵਿੱਚ ਨਹੀਂ"</string>
<string name="radioInfo_service_emergency" msgid="4763879891415016848">"ਸਿਰਫ਼ ਸੰਕਟਕਾਲੀਨ ਕਾਲਾਂ"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ਪੂਰਵ-ਨਿਰਧਾਰਤ ਡਾਟਾ ਸਿਮ ਦਾ SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL ਬੈਂਡਵਿਡਥ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL ਬੈਂਡਵਿਡਥ (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"ਸੈੱਲ ਦੀ ਟਿਕਾਣਾ ਜਾਣਕਾਰੀ (ਨਾਪਸੰਦ ਕੀਤੀ):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ਭੌਤਿਕ ਚੈਨਲ ਸੰਰੂਪਣ:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"ਸੈੱਲ ਦੀ ਜਾਣਕਾਰੀ ਦੀ ਰਿਫ੍ਰੈਸ਼ ਦਰ:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"ਸਾਰੀ ਸੈੱਲ ਮਾਪ ਜਾਣਕਾਰੀ:"</string>
@@ -880,12 +892,12 @@
<string name="radio_info_ppp_received_label" msgid="5753592451640644889">"ਡਾਟਾ ਪ੍ਰਾਪਤ ਹੋਇਆ:"</string>
<string name="radio_info_gsm_service_label" msgid="6443348321714241328">"ਵੌਇਸ ਸੇਵਾ:"</string>
<string name="radio_info_signal_strength_label" msgid="5545444702102543260">"ਸਿਗਨਲ ਦੀ ਤੀਬਰਤਾ:"</string>
- <string name="radio_info_call_status_label" msgid="7693575431923095487">"ਵੌਇਸ ਕਾਲ ਸਥਿਤੀ:"</string>
+ <string name="radio_info_call_status_label" msgid="7693575431923095487">"ਅਵਾਜ਼ੀ ਕਾਲ ਸਥਿਤੀ:"</string>
<string name="radio_info_ppp_sent_label" msgid="6542208429356199695">"ਡਾਟਾ ਭੇਜਿਆ ਗਿਆ:"</string>
<string name="radio_info_message_waiting_label" msgid="1886549432566952078">"ਸੁਨੇਹੇ ਦੀ ਉਡੀਕ:"</string>
<string name="radio_info_phone_number_label" msgid="2533852539562512203">"ਫ਼ੋਨ ਨੰਬਰ:"</string>
<string name="radio_info_band_mode_label" msgid="23480556225515290">"ਰੇਡੀਓ ਬੈਂਡ ਚੁਣੋ"</string>
- <string name="radio_info_voice_network_type_label" msgid="2395347336419593265">"ਵੌਇਸ ਨੈੱਟਵਰਕ ਕਿਸਮ:"</string>
+ <string name="radio_info_voice_network_type_label" msgid="2395347336419593265">"ਅਵਾਜ਼ੀ ਨੈੱਟਵਰਕ ਦੀ ਕਿਸਮ:"</string>
<string name="radio_info_data_network_type_label" msgid="8886597029237501929">"ਡਾਟਾ ਨੈੱਟਵਰਕ ਕਿਸਮ:"</string>
<string name="phone_index_label" msgid="6222406512768964268">"ਫ਼ੋਨ ਕ੍ਰਮ-ਸੂਚੀ ਚੁਣੋ"</string>
<string name="radio_info_set_perferred_label" msgid="7408131389363136210">"ਤਰਜੀਹੀ ਨੈੱਟਵਰਕ ਕਿਸਮ ਸੈੱਟ ਕਰੋ:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"ਫ਼ੋਨ ਜਾਣਕਾਰੀ"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"ਕੈਰੀਅਰ ਪ੍ਰੋਵਿਜ਼ਨਿੰਗ ਜਾਣਕਾਰੀ"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"ਕੈਰੀਅਰ ਪ੍ਰਵਿਜ਼ਨਿੰਗ ਨੂੰ ਟ੍ਰਿਗਰ ਕਰੋ"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"ਤੁਹਾਡਾ ਬਲੂਟੁੱਥ ਸਿਗਨਲ ਕਮਜ਼ੋਰ ਹੈ। ਸਪੀਕਰਫ਼ੋਨ \'ਤੇ ਲਿਜਾ ਕੇ ਦੇਖੋ।"</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"ਕਾਲ ਦੀ ਕੁਆਲਿਟੀ ਸੰਬੰਧੀ ਸੂਚਨਾ"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"ਨਾਪਸੰਦ ਕੀਤੇ SIP ਖਾਤੇ"</string>
</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index b0044e4..006693b 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Ustawienia poczty głosowej może zmienić tylko użytkownik główny."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Karta SIM została odblokowana. Odblokowywanie telefonu..."</string>
<string name="label_ndp" msgid="7617392683877410341">"Kod PIN do karty SIM odblokowujący sieć"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"Urządzenie obsługuje tylko karty SIM operatora"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Odblokuj"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Zamknij"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Żądanie odblokowania sieci..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Przekierowanie na <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Wyłączone"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Twój operator nie umożliwia wyłączenia przekazywania połączeń, gdy numer jest nieosiągalny."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Twój operator nie obsługuje przekierowania połączeń."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Włączyć połączenie oczekujące?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Podczas rozmowy otrzymasz powiadomienie o połączeniach przychodzących"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Włącz"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Anuluj"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Połączenie oczekujące CDMA, gdy usługa IMS jest włączona"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Połączenie oczekujące CDMA, gdy usługa IMS jest wyłączona"</string>
<string name="updating_title" msgid="6130548922615719689">"Ustawienia połączeń"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Ustawienia połączeń może zmieniać tylko użytkownik będący administratorem."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Ustawienia (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -270,7 +278,7 @@
<!-- no translation found for enhanced_4g_lte_mode_sumary_variant:0 (2943982616649705147) -->
<!-- no translation found for enhanced_4g_lte_mode_sumary_variant:1 (5262249464504131443) -->
<!-- no translation found for enhanced_4g_lte_mode_sumary_variant:2 (6356974241850241718) -->
- <string name="data_enabled" msgid="22525832097434368">"Włączone przesyłanie danych"</string>
+ <string name="data_enabled" msgid="22525832097434368">"Przesyłanie danych włączone"</string>
<string name="data_enable_summary" msgid="696860063456536557">"Zezwalaj na użycie danych"</string>
<string name="dialog_alert_title" msgid="5260471806940268478">"Uwaga"</string>
<string name="roaming" msgid="1576180772877858949">"Roaming"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Ograniczona funkcjonalność karty SIM"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Połączenia i usługi transmisji danych w sieci operatora <xliff:g id="CARRIER_NAME">%1$s</xliff:g> mogą być zablokowane, gdy używasz numeru <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Połączenia i transmisja danych w <xliff:g id="CARRIER_NAME">%1$s</xliff:g> mogą być zablokowane przy innej karcie SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Wycofane konta SIP zostały znalezione i usunięte"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Połączenia SIP nie są już obsługiwane na platformie Android.\nTwoje konta SIP (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>) zostały usunięte.\nPotwierdź ustawienie domyślnego konta do połączeń."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Otwórz ustawienia"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Użycie danych przez aplikację"</string>
<string name="data_usage_template" msgid="6287906680674061783">"Wykorzystano <xliff:g id="ID_1">%1$s</xliff:g> mobilnej transmisji danych w okresie <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Zaawansowane"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Nieprawidłowe hasło. Karta SIM została zablokowana. Wpisz PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 został trwale zablokowany."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Masz jeszcze <xliff:g id="NUMBER">%d</xliff:g> prób(y)."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"Kod PUK2 zablokowany. Aby odblokować, skontaktuj się z dostawcą usług."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 nie jest już zablokowany"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Błąd sieci lub karty SIM"</string>
<string name="doneButton" msgid="7371209609238460207">"Gotowe"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Aby zadzwonić, włącz sieć komórkową, wyłącz tryb samolotowy lub wyłącz tryb oszczędzania baterii."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Wyłącz tryb samolotowy, by zadzwonić."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Wyłącz tryb samolotowy lub połącz się z siecią bezprzewodową, by zadzwonić."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefon jest zbyt gorący"</b>\n\n"Nie można nawiązać tego połączenia. Spróbuj ponownie, gdy telefon się ochłodzi.\n\nNadal możesz wykonywać połączenia alarmowe."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Aby zadzwonić normalnie, wyjdź z trybu alarmowego połączenia zwrotnego."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Nie zarejestrowano w sieci"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Sieć komórkowa jest niedostępna."</string>
@@ -873,7 +886,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Identyfikator domyślnej karty SIM do transmisji danych:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Przepustowość kanału DL (kb/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Przepustowość kanału UL (kb/s):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Informacje o lokalizacji z sieci komórkowej (wycofane):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfiguracja kanału fizycznego LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Częstotliwość odświeżania informacji o sieci komórkowej:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Wszystkie informacje pomiarowe z sieci komórkowej:"</string>
@@ -918,4 +930,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Informacje o telefonie"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Informacje o obsłudze administracyjnej operatora"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Uruchom obsługę administracyjną operatora"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Twój sygnał Bluetooth jest słaby. Spróbuj przełączyć na głośnik."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Powiadomienie o jakości połączenia"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Wycofane konta SIP"</string>
</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 8cbe585..89d2279 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"As definições do correio de voz só podem ser modificadas pelo utilizador principal."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"O cartão SIM foi desbloqueado. O telefone está a ser desbloqueado..."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN para desbloqueio de rede do cartão SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM bloqueado para o operador"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Desbloquear"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Ignorar"</string>
<string name="requesting_unlock" msgid="930512210309437741">"A pedir desbloqueio de rede..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"A encaminhar para <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Desativado"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"O seu operador não suporta a desativação do encaminhamento de chamadas quando o telemóvel não está acessível."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"O seu operador não suporta encaminhamento de chamadas."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Ativar chamada em espera?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Durante uma chamada, será notificado sobre as chamadas recebidas."</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Ativar"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Cancelar"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Chamada em espera CDMA em IMS ativada"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Chamada em espera CDMA em IMS desativada"</string>
<string name="updating_title" msgid="6130548922615719689">"Definições de chamadas"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"As definições de chamadas só podem ser alteradas pelo utilizador gestor."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Definições (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Funcionalidade de SIM limitada"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"As chamadas e os serviços de dados da <xliff:g id="CARRIER_NAME">%1$s</xliff:g> podem ficar bloqueados ao utilizar o número <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"As chamadas e serviços de dados da <xliff:g id="CARRIER_NAME">%1$s</xliff:g> podem ser bloqueados se utilizar outro SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Foram encontradas e removidas contas SIP descontinuadas"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"As chamadas SIP já não são suportadas pela plataforma Android.\nAs suas contas SIP existentes <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> foram removidas.\nConfirme a predefinição de conta de chamadas."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Aceder às definições"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Utilização de dados da app"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> de dados móveis utilizados entre <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Avançadas"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Palavra-passe incorreta. SIM bloqueado. Introduza o PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 bloqueado permanentemente."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Tem mais <xliff:g id="NUMBER">%d</xliff:g> tentativas."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 bloqueado. Contacte o fornecedor de serviços para desbloquear."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"O PIN2 deixou de estar bloqueado"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Erro do cartão SIM ou da rede"</string>
<string name="doneButton" msgid="7371209609238460207">"Concluído"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Ative a rede móvel, desative o modo de avião ou desative o modo de poupança de bateria para fazer uma chamada."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Desative o modo de avião para fazer uma chamada."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Desative o modo de avião ou ligue-se a uma rede sem fios para fazer uma chamada."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"O telemóvel está demasiado quente"</b>\n\n"Não é possível concluir esta chamada. Tente novamente depois de o telemóvel arrefecer.\n\nAinda pode efetuar chamadas de emergência."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Sair do modo de chamada de retorno de emergência para efetuar uma chamada que não é de emergência."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Sem registo na rede."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Rede móvel não disponível."</string>
@@ -625,12 +638,12 @@
<string name="phone_in_ecm_call_notification_text" msgid="653972232922670335">"Ligação de dados desativada"</string>
<string name="phone_in_ecm_notification_complete_time" msgid="7341624337163082759">"Sem ligação de dados até à(s) <xliff:g id="COMPLETETIME">%s</xliff:g>"</string>
<plurals name="alert_dialog_exit_ecm" formatted="false" msgid="5425906903766466743">
- <item quantity="other">O telemóvel fica no modo de Chamada de emergência durante <xliff:g id="COUNT_1">%s</xliff:g> minutos. Neste modo, não é possível utilizar aplicações com uma ligação de dados. Pretende sair agora?</item>
<item quantity="one">O telemóvel fica no modo de Chamada de emergência durante <xliff:g id="COUNT_0">%s</xliff:g> minuto. Neste modo, não é possível utilizar aplicações com uma ligação de dados. Pretende sair agora?</item>
+ <item quantity="other">O telemóvel fica no modo de Chamada de emergência durante <xliff:g id="COUNT_1">%s</xliff:g> minutos. Neste modo, não é possível utilizar aplicações com uma ligação de dados. Pretende sair agora?</item>
</plurals>
<plurals name="alert_dialog_not_avaialble_in_ecm" formatted="false" msgid="1152682528741457004">
- <item quantity="other">A ação selecionada não está disponível no modo de Chamada de emergência. O telemóvel fica neste modo durante <xliff:g id="COUNT_1">%s</xliff:g> minutos. Pretende sair agora?</item>
<item quantity="one">A ação selecionada não está disponível no modo de Chamada de emergência. O telemóvel fica neste modo durante <xliff:g id="COUNT_0">%s</xliff:g> minuto. Pretende sair agora?</item>
+ <item quantity="other">A ação selecionada não está disponível no modo de Chamada de emergência. O telemóvel fica neste modo durante <xliff:g id="COUNT_1">%s</xliff:g> minutos. Pretende sair agora?</item>
</plurals>
<string name="alert_dialog_in_ecm_call" msgid="1207545603149771978">"A ação selecionada não está disponível durante uma chamada de emergência."</string>
<string name="progress_dialog_exiting_ecm" msgid="9159080081676927217">"A sair do modo de chamada de retorno de emergência"</string>
@@ -640,8 +653,8 @@
<string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"O telemóvel está no modo de chamada de retorno de emergência."</string>
<string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Até à(s) <xliff:g id="COMPLETETIME">%s</xliff:g>."</string>
<plurals name="alert_dialog_exit_ecm_without_data_restriction_hint" formatted="false" msgid="6477733043040328640">
- <item quantity="other">O telemóvel ficará no modo de chamada de retorno de emergência durante <xliff:g id="COUNT_1">%s</xliff:g> minutos.\nPretende sair agora?</item>
<item quantity="one">O telemóvel ficará no modo de chamada de retorno de emergência durante <xliff:g id="COUNT_0">%s</xliff:g> minuto.\nPretende sair agora?</item>
+ <item quantity="other">O telemóvel ficará no modo de chamada de retorno de emergência durante <xliff:g id="COUNT_1">%s</xliff:g> minutos.\nPretende sair agora?</item>
</plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Serviço"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Configuração"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubID do SIM de dados predefinido:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Largura de banda de transferência (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Largura de banda de carregamento (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Informações da localização da célula (descontinuadas):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuração do canal físico LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Taxa de atualização das informações da célula:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Todas as informações de medição de células:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Informações do telefone"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Informações de fornecimento do operador"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Acionar fornecimento do operador"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"O seu sinal Bluetooth é fraco. Tente mudar para o altifalante."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Notificação de qualidade da chamada"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Contas SIP descontinuadas"</string>
</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 1c3ecd4..b1b1a2f 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"As configurações do correio de voz só podem ser modificadas pelo usuário principal."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"O seu chip foi desbloqueado. O seu telefone está desbloqueando…"</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN de desbloqueio da rede SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"Chip bloqueado para o operador"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Desbloquear"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Dispensar"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Solicitando o desbloqueio de rede…"</string>
@@ -85,11 +86,11 @@
<string name="voicemail_notifications_preference_title" msgid="7829238858063382977">"Notificações"</string>
<string name="cell_broadcast_settings" msgid="8135324242541809924">"Transmissões de emergência"</string>
<string name="call_settings" msgid="3677282690157603818">"Configurações de chamadas"</string>
- <string name="additional_gsm_call_settings" msgid="1561980168685658846">"Configurações adicionais"</string>
- <string name="additional_gsm_call_settings_with_label" msgid="7973920539979524908">"Configurações adicionais (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
- <string name="sum_gsm_call_settings" msgid="7964692601608878138">"Configurações adicionais somente de chamada GSM"</string>
+ <string name="additional_gsm_call_settings" msgid="1561980168685658846">"Configurações avançadas"</string>
+ <string name="additional_gsm_call_settings_with_label" msgid="7973920539979524908">"Configurações avançadas (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
+ <string name="sum_gsm_call_settings" msgid="7964692601608878138">"Configurações avançadas somente de chamada GSM"</string>
<string name="additional_cdma_call_settings" msgid="2178016561980611304">"Configurações de chamada CDMA adicionais"</string>
- <string name="sum_cdma_call_settings" msgid="3185825305136993636">"Configurações adicionais somente de chamada CDMA"</string>
+ <string name="sum_cdma_call_settings" msgid="3185825305136993636">"Configurações avançadas somente de chamada CDMA"</string>
<string name="labelNwService" msgid="6015891883487125120">"Configurações do serviço de rede"</string>
<string name="labelCallerId" msgid="2090540744550903172">"Identificador de chamadas"</string>
<string name="sum_loading_settings" msgid="434063780286688775">"Carregando configurações…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Encaminhar para <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Desativado"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Sua operadora não suporta a desativação do encaminhamento de chamada quando seu telefone não está acessível."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Sua operadora não é compatível com o encaminhamento de chamada."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Ativar chamada em espera?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Durante uma chamada, você receberá notificações das chamadas recebidas"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Ativar"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Cancelar"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"A chamada CDMA em espera está ativada no IMS"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"A chamada CDMA em espera está desativada no IMS"</string>
<string name="updating_title" msgid="6130548922615719689">"Configurações de chamadas"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"As configurações de chamada só podem ser alteradas pelo usuário administrador."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Configurações (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Funcionalidade limitada do chip"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Talvez os serviços de dados e as chamadas da operadora <xliff:g id="CARRIER_NAME">%1$s</xliff:g> sejam bloqueados se você usar <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Talvez os serviços de dados e as chamadas da <xliff:g id="CARRIER_NAME">%1$s</xliff:g> sejam bloqueados com outro chip."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Contas SIP suspensas encontradas e removidas"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"A chamada SIP não é mais compatível com a Plataforma Android.\nSuas contas SIP foram removidas: <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>.\nConfirme a configuração da conta padrão para chamadas."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Acessar configurações"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Uso de dados do app"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> de dados móveis usados em <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Avançadas"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Senha incorreta. Chip bloqueado. Insira o PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 permanentemente bloqueado."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Você tem <xliff:g id="NUMBER">%d</xliff:g> tentativas restantes."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 bloqueado. Entre em contato com o provedor de serviços para desbloqueá-lo."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 não mais bloqueado"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Erro de rede ou do chip"</string>
<string name="doneButton" msgid="7371209609238460207">"Concluído"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Ative a rede móvel e desative o modo avião ou o modo de economia de bateria para ligar."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Desative o modo avião para fazer uma chamada."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Desative o modo avião ou conecte-se a uma rede sem fio para fazer uma chamada."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"O smartphone está muito quente"</b>\n\n"Não é possível completar esta chamada. Tente de novo quando seu smartphone esfriar.\n\nVocê ainda pode fazer chamadas de emergência."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Saia do modo de retorno de chamada de emergência para fazer uma chamada que não seja de emergência."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Não registrado na rede."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Rede móvel não disponível."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Subcódigo do chip de dados padrão:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Largura de banda DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Largura de banda UL (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Informações de local do celular (uso suspenso):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuração do canal físico de LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Taxa de atualização das informações do celular:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Todas as informações de medição do celular:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Informações do telefone"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Informações de provisionamento da operadora"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Acionar provisionamento da operadora"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"O sinal do Bluetooth está fraco. Mude para o viva-voz."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Notificação sobre a qualidade da chamada"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Contas SIP suspensas"</string>
</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 35ee855..326cc52 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Setările pentru mesagerie vocală pot fi modificate numai de utilizatorul principal."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Cardul dvs. SIM a fost deblocat. Telefonul dvs. se deblochează..."</string>
<string name="label_ndp" msgid="7617392683877410341">"Codul PIN de deblocare a rețelei SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"Cardul SIM a fost blocat pentru operator"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Deblocați"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Renunțați"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Se solicită deblocarea rețelei..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Se redirecționează la <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Dezactivat"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Operatorul dvs. nu acceptă ca redirecționarea apelurilor să fie dezactivată atunci când telefonul nu este accesibil."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Operatorul dvs. nu acceptă redirecționarea apelurilor."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Activați apelurile în așteptare?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"În timpul unui apel, veți primi o notificare despre apelurile primite"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Activați"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Anulați"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Apelurile în așteptare CDMA din IMS sunt activate"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Apelurile în așteptare CDMA din IMS sunt dezactivate"</string>
<string name="updating_title" msgid="6130548922615719689">"Setări pentru apeluri"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Numai administratorul poate să modifice setările pentru apeluri."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Setări (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Funcționalitate SIM limitată"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Este posibil ca apelurile și serviciile de date de la <xliff:g id="CARRIER_NAME">%1$s</xliff:g> să fie blocate în timp ce se folosește <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Apelurile și serviciile de date de la <xliff:g id="CARRIER_NAME">%1$s</xliff:g> pot fi blocate când folosiți alt card SIM"</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Au fost găsite și eliminate conturi SIP învechite"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Platforma Android nu mai acceptă apelarea SIP.\nConturile dvs. SIP existente <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> au fost eliminate.\nConfirmați setarea prestabilită pentru apelare a contului."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Accesați setările"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Date utilizate de aplicație"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> de date mobile utilizate în intervalul <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Avansate"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Parola nu este corectă. Cardul SIM a fost blocat. Introduceți codul PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"Codul PUK2 este blocat definitiv."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"V-au rămas <xliff:g id="NUMBER">%d</xliff:g> (de) încercări."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"Blocat cu PUK2. Contactați furnizorul de servicii pentru a debloca."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"Codul PIN2 nu mai este blocat"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Eroare de rețea sau de card SIM"</string>
<string name="doneButton" msgid="7371209609238460207">"Terminat"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Activați rețeaua mobilă, dezactivați modul avion sau modul de economisire a bateriei pentru a iniția un apel."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Pentru a apela, dezactivați modul Avion."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Pentru a apela, dezactivați modul Avion sau conectați-vă la o rețea wireless."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefonul este prea fierbinte"</b>\n\n"Nu se poate iniția apelul. Încercați din nou după ce telefonul se răcește.\n\nPuteți, totuși, să inițiați apeluri de urgență."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Ieșiți din modul de apelare inversă de urgență pentru a efectua un apel care nu este de urgență."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Neînregistrat în rețea."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Rețeaua mobilă nu este disponibilă."</string>
@@ -870,7 +883,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId pentru SIM-ul de date prestabilit:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Lățime de bandă de descărcare (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Lățime de bandă de încărcare (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Informații despre localizarea celulei (învechite):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configurarea canalului fizic LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Rata de actualizare a informațiilor despre celulă:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Toate informațiile de măsurare despre celulă:"</string>
@@ -915,4 +927,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Informații despre telefon"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Informații despre provizionarea operatorului"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Declanșați provizionarea operatorului"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Semnalul Bluetooth este slab. Încercați să folosiți difuzorul."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Notificare privind calitatea apelului"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Conturi SIP învechite"</string>
</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index c0397e3..4a6ee96 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Настройки голосовой почты может изменить только основной пользователь"</string>
<string name="puk_unlocked" msgid="4627340655215746511">"SIM-карта разблокирована. Осуществляется разблокировка телефона..."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN для разблокировки сети SIM-карты"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM-карта заблокирована для оператора"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Разблокировать"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Закрыть"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Запрос разблокировки сети..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Переадресация на номер <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Отключено"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Ваш оператор не позволяет отключать переадресацию вызовов, если телефон находится вне зоны доступа."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Ваш оператор не поддерживает переадресацию вызовов."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Включить ожидание вызова?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Во время телефонного разговора вы будете получать уведомления о входящих вызовах"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Включить"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Отмена"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Ожидание вызова CDMA, если сервис IMS включен"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Ожидание вызова CDMA, если сервис IMS выключен"</string>
<string name="updating_title" msgid="6130548922615719689">"Настройки вызовов"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Настройки вызовов может изменить только основной пользователь"</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Настройки (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Ограниченная функциональность SIM-карты"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Звонки и мобильный Интернет через <xliff:g id="CARRIER_NAME">%1$s</xliff:g> могут блокироваться при использовании номера <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Звонки и Интернет через <xliff:g id="CARRIER_NAME">%1$s</xliff:g> могут блокироваться при использовании другой SIM-карты."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Найдены и удалены неподдерживаемые SIP-аккаунты"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"На платформе Android больше не поддерживаются звонки через протокол SIP.\nВаши SIP-аккаунты (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>) удалены.\nПодтвердите аккаунт, который будет по умолчанию использоваться для звонков."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Перейти в настройки"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Трафик приложения"</string>
<string name="data_usage_template" msgid="6287906680674061783">"Объем мобильного трафика за период <xliff:g id="ID_2">%2$s</xliff:g>: <xliff:g id="ID_1">%1$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Дополнительные настройки"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Неверный пароль. SIM-карта заблокирована. Введите код PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"Код PUK2 заблокирован навсегда"</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Осталось попыток: <xliff:g id="NUMBER">%d</xliff:g>."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"Код PUK2 заблокирован. Чтобы разблокировать его, обратитесь к оператору."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"Код PIN2 разблокирован."</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Ошибка сети или SIM-карты"</string>
<string name="doneButton" msgid="7371209609238460207">"Готово"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Чтобы позвонить, подключитесь к мобильной сети или отключите режим полета либо режим энергосбережения."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Чтобы позвонить, отключите режим полета."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Чтобы позвонить, отключите режим полета и подключитесь к Wi-Fi."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Телефон перегрелся."</b>\n\n"Не удалось совершить звонок. Повторите попытку, когда телефон остынет.\n\nЭто не касается экстренных вызовов."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Чтобы сделать обычный звонок, выйдите из режима экстренных обратных вызовов."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Нет регистрации в сети."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Мобильная сеть недоступна."</string>
@@ -873,7 +886,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Доп. идентификатор SIM-карты для мобильного Интернета по умолчанию:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Пропускная способность DL-канала (кбит/c):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Пропускная способность UL-канала (кбит/с):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Информация о местоположении сети (устаревшее):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Конфигурация физического канала LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Частота обновления данных о сетях:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Статистика сети:"</string>
@@ -918,4 +930,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Информация о телефоне"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Сведения о синхронизации оператора"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Запустить синхронизацию оператора"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Слабый сигнал Bluetooth. Попробуйте переключиться на громкую связь."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Уведомление о качестве связи"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Неподдерживаемые SIP-аккаунты"</string>
</resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 45850c8..7efc245 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"හඬ තැපැල් සැකසීම් මූලික පරිශීලක විසින් පමණක් වෙනස් කිරීමට හැකිය."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"ඔබගේ SIM කාඩ්පත අඟුළු හැර තිබේ. ඔබගේ දුරකතනයේ අඟුළු හරිමින්…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM ජාල අඟුළු හැරීමේ PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"මෙහෙයවන්නා සඳහා SIM අගුලු දමා ඇත"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"අඟුල අරින්න"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"ඉවතලන්න"</string>
<string name="requesting_unlock" msgid="930512210309437741">"ජාල අඟුළු ඇරීමක් ඉල්ලුම් කරමින්…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> වෙත ඉදිරියට යවමින්"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"අක්රියයි"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"ඔබගේ වාහකය ඔබගේ දුරකථනය ළඟා විය නොහැකි විට ඇමතුම් ඉදිරියට ගෙනයාම අබල කිරීමට සහාය නොදක්වයි."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"ඔබේ වාහකය ඇමතුම් යොමු කිරීමට සහාය නොදක්වයි."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"ඇමතුම රඳවා ගැනීම ක්රියාත්මක කරන්නද?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"ඇමතුමක් අතරතුර, ඔබට එන ඇමතුම් පිළිබඳ දැනුම් දෙනු ඇත"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"ක්රියාත්මක කරන්න"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"අවලංගු කරන්න"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS යටත් CDMA ඇමතුම රඳවා ගැනීම ක්රියාත්මකයි"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS යටත් CDMA ඇමතුම රඳවා ගැනීම ක්රියාවිරහිතයි"</string>
<string name="updating_title" msgid="6130548922615719689">"ඇමතුම් සැකසීම්"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"ඇමතුම් සැකසීම් වෙනස් කළ හැක්කේ පරිපාලක පරිශීලකයාට පමණි."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"සැකසීම් (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"සීමිත SIM ක්රියාකාරීත්වය"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g> භාවිත කරන අතරතුර <xliff:g id="CARRIER_NAME">%1$s</xliff:g> ඇමතුම් සහ දත්ත සේවා අවහිර විය හැකිය."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"වෙනත් SIM එකක් භාවිත කරන අතරතුර <xliff:g id="CARRIER_NAME">%1$s</xliff:g> ඇමතුම් සහ දත්ත සේවා අවහිර විය හැකිය."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"අතහැර දැමූ SIP ගිණුම් සොයා ගෙන ඉවත් කර ඇත"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP ඇමතීමට තවදුරටත් Android වේදිකාව මගින් සහාය නොදක්වයි.\nඔබේ පවතින SIP ගිණුම් <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> ඉවත් කර ඇත.\nඔබේ පෙරනිමි ඇමතීමේ ගිණුම් සැකසීම තහවුරු කරන්න."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"සැකසීම් වෙත යන්න"</string>
<string name="data_usage_title" msgid="8438592133893837464">"යෙදුම් දත්ත භාවිතය"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g> <xliff:g id="ID_1">%1$s</xliff:g>ක ජංගම දත්ත භාවිත කරන ලදී"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"උසස්"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"මුරපදය වැරදිය. SIM දැන් අගුළු දමා තිබේ. PUK2 ඇතුළු කරන්න."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 සදාකාලිකවම අවහිරයි."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"ඔබට නැවත උත්සාහ කිරීම් <xliff:g id="NUMBER">%d</xliff:g> ක් තිබේ."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 අගුළු දැමිණි. අගුළු ඇරීමට සේවා සපයන්නා සම්බන්ධ කර ගන්න."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"තව දුරටත් PIN2 අවහිර කර නොතිබේ"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"ජාලයේ හෝ SIM කාඩ්පතෙහි දෝෂයක්"</string>
<string name="doneButton" msgid="7371209609238460207">"හරි"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"ජංගම ජාලය ක්රියාත්මක කරන්න, ඇමතුමක් ගැනීමට ගුවන් යානා ප්රකාරය හෝ බැටරි සුරැකුම් ප්රකාරය ක්රියාවිරහිත කරන්න."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"ඇමතුමක් ගැනීමට ගුවන් යානා මාදිලිය අක්රිය කරන්න."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"ඇමතුමක් ලබා ගැනීමට ගුවන් යානා මෝඩය අක්රිය කරන්න හෝ රැහැන් රහිත ජාලයකට සම්බන්ධ වෙන්න."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"දුරකථනය ඉතා උණුසුම් ය"</b>\n\n"මෙම ඇමතුම සම්පූර්ණ කළ නොහැක. ඔබේ දුරකථනය සිසිල් වන විට නැවත උත්සාහ කරන්න.\n\nඔබට තවමත් හදිසි ඇමතුම් ලබා ගත හැක."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"හදිසි-නොවන ඇමතුමක් සිදු කිරීමට හදිසි අවස්ථා පසු ඇමතුම් ප්රකාරයෙන් ඉවත් වන්න."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"ජාලය මත ලියාපදිංචි වී නැත."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"ජංගම ජාලය නොමැත."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"පෙරනිමි දත්ත SIM පතේ උප හැඳුනුම:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL කලාප පළල (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL කලාප පළල (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"සෙල් ස්ථාන තොරතුරු (අත් හරින ලදී):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE භෞතික නාලිකා වින්යාසය:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"සෙල් තොරතුරු නැවුම් කිරීමේ අනුපාතය:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"සියලු සෙල් මිනුම් තොරතුරු:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"දුරකථන තොරතුරු"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"වාහක ප්රතිපාදන තතු"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"උත්ප්රේරක වාහක ප්රතිපාදන"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"ඔබේ බ්ලූටූත් සංඥාව දුර්වලයි. ස්පීකර් දුරකථනයට මාරු වීමට උත්සාහ කරන්න."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"ඇමතුම් ගුණත්ව දැනුම්දීම"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"අතහැර දැමූ SIP ගිණුම්"</string>
</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 5436158..7f9bc94 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Nastavenia hlasovej schránky môže zmeniť iba primárny používateľ."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"SIM karta bola odblokovaná. Prebieha odomykanie telefónu..."</string>
<string name="label_ndp" msgid="7617392683877410341">"Kód PIN odomknutia siete pre SIM kartu"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM karta je uzamknutá pre operátora"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Odomknúť"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Zatvoriť"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Žiadosť o odomknutie siete..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Presmerovanie na <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Vypnuté"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Váš operátor neumožňuje zakázanie presmerovaní, keď je telefón nedostupný."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Váš operátor nepodporuje presmerovanie hovorov."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Chcete zapnúť funkciu Čakajúci hovor?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Počas hovoru budete dostávať upozornenia na prichádzajúce hovory"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Zapnúť"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Zrušiť"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA – čakajúci hovor službe IMS – zapnuté"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA – čakajúci hovor v službe IMS – vypnuté"</string>
<string name="updating_title" msgid="6130548922615719689">"Nastavenia hovorov"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Nastavenia hovorov môže zmeniť iba používateľ s povoleniami správcu."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Nastavenia (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Obmedzená funkčnosť SIM karty"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Hovory a dátové služby operátora <xliff:g id="CARRIER_NAME">%1$s</xliff:g> môžu byť zablokované, keď používate číslo <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Hovory a dátové služby operátora <xliff:g id="CARRIER_NAME">%1$s</xliff:g> môžu byť s inou SIM kartou blokované."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Boli nájdené a odstránené účty SIP s ukončenou podporou"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Platforma Androidu už nepodporuje volanie SIP.\nVaše existujúce účty SIP <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> boli odstránené.\nPotvrďte svoje predvolené nastavenie účtu na volanie."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Prejsť do nastavení"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Spotreba dát aplikácií"</string>
<string name="data_usage_template" msgid="6287906680674061783">"Spotreba mobilných dát za obdobie <xliff:g id="ID_2">%2$s</xliff:g> je <xliff:g id="ID_1">%1$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Rozšírené"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Heslo je nesprávne. SIM karta bola uzamknutá. Zadajte kód PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"Kód PUK2 je natrvalo blokovaný."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Zostávajú vám pokusy (počet: <xliff:g id="NUMBER">%d</xliff:g>)"</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"SIM karta je blokovaná kódom PUK2. Ak ju chcete odblokovať, kontaktujte poskytovateľa služieb."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"Kód PIN2 už nie je blokovaný"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Došlo k chybe siete alebo SIM karty"</string>
<string name="doneButton" msgid="7371209609238460207">"Hotovo"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Ak chcete volať, zapnite mobilnú sieť a vypnite režim v lietadle alebo režim šetriča batérie."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Ak chcete volať, vypnite režim v lietadle"</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Ak chcete volať, vypnite režim v lietadle alebo sa pripojte k bezdrôtovej sieti"</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefón sa príliš zahrial"</b>\n\n"Tento hovor sa nedá uskutočniť. Skúste to znova, keď telefón vychladne.\n\nTiesňové volania môžete uskutočňovať aj naďalej."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Ak chcete volať štandardným spôsobom, ukončite režim tiesňového spätného volania."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Prihlásenie do siete nebolo úspešné."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobilná sieť nie je k dispozícii."</string>
@@ -873,7 +886,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Podradený identifikátor predvolenej dátovej SIM karty:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Rýchlosť pripojenia DL (kB/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Rýchlosť pripojenia UL (kB/s):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Informácie o polohe mobilnej siete (podpora bola ukončená):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfigurácia fyzického kanála LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Frekvencia obnovenia informácií o mobilnej sieti:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Všetky informácie o meraní mobilnej siete:"</string>
@@ -918,4 +930,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Informácie o telefóne"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Informácie o poskytovaní služieb operátorom"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Spustiť poskytovanie služieb operátorom"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Signál Bluetooth je slabý. Skúste prepnúť na reproduktor."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Upozornenie o kvalite hovoru"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Účty SIP s ukončenou podporou"</string>
</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 40a54b3..5feb01d 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Nastavitve odzivnika lahko spremeni samo primarni uporabnik."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Kartica SIM je bila deblokirana. Telefon se odklepa ..."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN za odklep omrežja kartice SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"Kartica SIM je zaklenjena za operaterja"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Odkleni"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Opusti"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Zahteva za odklepanje omrežja ..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Preusmerjanje na <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Izklopljeno"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Vaš operater ne podpira izklopa preusmeritve klicev, kadar je telefon nedosegljiv."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Vaš operater ne podpira posredovanja klicev."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Želite vklopiti čakajoči klic?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Med klicem boste obveščeni o dohodnih klicih"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Vklopi"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Prekliči"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Čakajoči klic CDMA v sistemu IMS je vklopljen"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Čakajoči klic CDMA v sistemu IMS je izklopljen"</string>
<string name="updating_title" msgid="6130548922615719689">"Nastavitve klicev"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Nastavitve klicanja lahko spremeni samo uporabnik s skrbniškim dostopom."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Nastavitve (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Omejena funkcionalnost kartice SIM"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Klici in podatkovne storitve prek operaterja <xliff:g id="CARRIER_NAME">%1$s</xliff:g> bodo pri uporabi številke <xliff:g id="PHONE_NUMBER">%2$s</xliff:g> morda blokirane."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Klici in podatk. storitve <xliff:g id="CARRIER_NAME">%1$s</xliff:g> bodo pri uporabi druge kartice SIM morda blokirane."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Opuščeni računi SIP so bili odkriti in odstranjeni"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Platforma Android ne podpira več klicanja SIP.\nObstoječi računi SIP so bili odstranjeni: <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>.\nPotrdite privzeto nastavitev računa za klicanje."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Odpri nastavitve"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Prenos podatkov aplikacije"</string>
<string name="data_usage_template" msgid="6287906680674061783">"V obdobju <xliff:g id="ID_2">%2$s</xliff:g> je bilo v mobilnem omrežju prenesenih <xliff:g id="ID_1">%1$s</xliff:g> podatkov"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Dodatno"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Geslo je napačno. Kartica SIM je zaklenjena. Vnesite kodo PUK 2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"Koda PUK2 je trajno blokirana."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Imate še toliko poskusov: <xliff:g id="NUMBER">%d</xliff:g>."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"Zaklenjeno s kodo PUK2. Za odklepanje se obrnite na ponudnika storitev."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"Koda PIN 2 ni več blokirana"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Napaka omrežja ali kartice SIM"</string>
<string name="doneButton" msgid="7371209609238460207">"Dokončano"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Vklopite mobilno omrežje, izklopite način za letalo ali način za varčevanje z energijo akumulatorja, če želite opraviti klic."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Izklopite način za letalo, če želite opraviti klic."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Izklopite način za letalo ali se povežite v omrežje Wi-Fi, če želite opraviti klic."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefon je prevroč"</b>\n\n"Tega klica ni mogoče dokončati. Poskusite znova, ko se telefon ohladi.\n\nŠe vedno lahko opravljate klice v sili."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Če ne gre za klic v sili, zaprite način za povratni klici v sili."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Ni registrirano v omrežju."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Omrežje prenosnega telefona ni na voljo."</string>
@@ -873,7 +886,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ID naročnine privzete kartice SIM za prenos podatkov:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Pasovna širina za prenos (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Pasovna širina za nalaganje (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Podatki o lokaciji celice (zastarelo):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfiguracija fizičnega kanala LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Frekvenca osveževanja podatkov o celici:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Vsi podatki o meritvah celice:"</string>
@@ -918,4 +930,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Informacije o telefonu"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Podatki o omogočanju uporabe operaterja"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Sproži omogočanje uporabe operaterja"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Signal povezave Bluetooth je šibek. Poskusite preklopiti na zvočnik."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Obvestilo o kakovosti klica"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Opuščeni računi SIP"</string>
</resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 9e37521..8ea15ab 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Cilësimet e postës zanore mund të modifikohen vetëm nga \"Përdoruesi kryesor\""</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Karta jote SIM u zhbllokua. Telefoni yt po shkyçet..."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN-i i shkyçjes së rrjetit të kartës SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"Karta SIM e kyçur për operatorin"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Shkyçe"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Injoro"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Po kërkon shkyçjen e rrjetit…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Transferim te <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Çaktivizuar"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Operatori yt nuk mbështet çaktivizimin e transferimit të telefonatës kur telefoni është i paarritshëm."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Operatori celular nuk e mbështet transferimin e telefonatave."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Do të aktivizosh telefonatat në pritje?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Gjatë një telefonate, do të njoftohesh për telefonatat hyrëse"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Aktivizo"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Anulo"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Telefonatat në pritje CDMA në sistemin IMS janë aktive"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Telefonatat në pritje CDMA në sistemin IMS janë joaktive"</string>
<string name="updating_title" msgid="6130548922615719689">"Cilësimet e telefonatës"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Cilësimet e telefonatës mund të ndryshohen vetëm nga administratori."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Cilësimet (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Funksionalitet i kufizuar i kartës SIM"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Shërbimet e telefonatave dhe të të dhënave të <xliff:g id="CARRIER_NAME">%1$s</xliff:g> mund të bllokohen kur përdoret <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Shërbimet e telefonatave dhe të dhënave të <xliff:g id="CARRIER_NAME">%1$s</xliff:g> bllokohen kur përdoret SIM tjetër."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Llogaritë e zhvlerësuara SIP janë gjetur dhe hequr"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Telefonatat me SIP nuk mbështeten më nga platforma Android.\nLlogaritë e tua ekzistuese SIP <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> janë hequr.\nKonfirmo cilësimin e parazgjedhur të llogarisë së telefonatave."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Shko te cilësimet"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Përdorimi i të dhënave të aplikacioneve"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> të dhëna celulare të përdorura mes datave <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Të përparuara"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Fjalëkalimi është i pasaktë. Karta SIM tani është e kyçur. Fut PUK2-shin."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2-shi është i bllokuar përgjithmonë."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Të kanë mbetur edhe <xliff:g id="NUMBER">%d</xliff:g> përpjekje."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"Kodi PUK2 i kyçur. Kontakto me ofruesin e shërbimit për ta shkyçur."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2-shi nuk është më i bllokuar"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Gabim në rrjet ose në kartën SIM"</string>
<string name="doneButton" msgid="7371209609238460207">"U krye"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Aktivizo rrjetin celular, çaktivizo modalitetin e aeroplanit ose modalitetin e kursyesit të baterisë për të bërë një telefonatë."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Çaktivizo modalitetin e aeroplanit për të bërë telefonata."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Çaktivizo modalitetin e aeroplanit ose lidhu me ndonjë rrjet interneti valor për të bërë telefonata."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefoni është shumë i nxehtë"</b>\n\n"Kjo telefonatë nuk mund të kryhet. Provo përsëri kur të ftohet telefoni.\n\nMund të bësh përsëri telefonata të urgjencës."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Dil nga modaliteti i kthimit të telefonatës së urgjencës për të bërë një telefonatë jo urgjente."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"I paregjistruar në rrjet."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Rrjeti celular nuk mundësohet."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ID-ja dytësore e kartës SIM të parazgjedhur të të dhënave:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Gjerësia e bandës DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Gjerësia e bandës UL (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Informacionet e vendndodhjes të rrjetit celular (e vjetruar):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfigurimi i kanalit fizik LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Shpejtësia e rifreskimit të informacioneve të rrjetit celular"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Të gjitha informacionet e matjes së rrjetit celular:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Informacioni i telefonit"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Informacionet e përgatitjes së operatorit celular"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Aktivizo përgatitjen e operatorit celular"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Sinjali i Bluetooth-it është i dobët. Provo të kalosh te altoparlanti."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Njoftim për cilësinë e telefonatës"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Llogaritë e zhvlerësuara SIP"</string>
</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 53dc93c..ecd1ff1 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Само примарни корисник може да мења подешавања говорне поште."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Ваша SIM картица је одблокирана. Телефон се откључава..."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN за откључавање мреже на SIM картици"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM картица је закључана за оператера"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Откључај"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Одбаци"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Тражење откључавања мреже"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Прослеђује се на <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Искључено"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Мобилни оператер не подржава онемогућавање преусмеравања позива када је телефон недоступан."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Ваш мобилни оператер не подржава преусмеравање позива."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Желите да укључите стављање позива на чекање?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Током позива ћете добијати обавештења о долазним позивима"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Укључи"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Откажи"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Стављање CDMA позива на чекање у IMS-у је укључено"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Стављање CDMA позива на чекање у IMS-у је искључено"</string>
<string name="updating_title" msgid="6130548922615719689">"Подешавања позива"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Само кориснику са администраторским правима је дозвољено да мења подешавања позива."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Подешавања (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Ограничена функционалност SIM-а"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> услуге за позиве и податке се блокирају док користите <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> услуге за позиве и податке се блокирају док користите други SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Застарели SIP налози су пронађени и уклоњени"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Android платформа више не подржава SIP позивање.\nПостојећи SIP налози <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> су уклоњени.\nПотврдите подразумевано подешавање налога позивања."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Иди у подешавања"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Потрошња података апликације"</string>
<string name="data_usage_template" msgid="6287906680674061783">"Потрошили сте <xliff:g id="ID_1">%1$s</xliff:g> мобилних података у периоду <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Напредно"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Лозинка није тачна. SIM је сада блокиран. Унесите PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 је трајно блокиран."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Имате још <xliff:g id="NUMBER">%d</xliff:g> покушаја."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 је закључан. За откључавање се обратите добављачу услуге."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 више није блокиран"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Грешка на мрежи или SIM картици"</string>
<string name="doneButton" msgid="7371209609238460207">"Готово"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Укључите мобилну мрежу и искључите режим рада у авиону или режим уштеде батерије да бисте упутили позив."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Искључите режим рада у авиону да бисте упутили позив."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Искључите режим рада у авиону или се повежите на бежичну мрежу да бисте упутили позив."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Телефон је превише врућ"</b>\n\n"Позив не може да се заврши. Пробајте поново када се телефон охлади.\n\nИ даље можете да упућујете хитне позиве."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Изађите из режима хитног повратног позива да бисте упутили позив који није хитан."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Није регистровано на мрежи."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Мобилна мрежа није доступна."</string>
@@ -602,7 +615,7 @@
<string name="ota_title_activate" msgid="4049645324841263423">"Активирајте телефон"</string>
<string name="ota_touch_activate" msgid="838764494319694754">"Потребно је да упутите посебан позив да бисте активирали услугу свој телефона. \n\nНакон што притиснете дугме „Активирај“, слушајте дата упутства да бисте да бисте активирали свој телефон."</string>
<string name="ota_hfa_activation_title" msgid="3300556778212729671">"Активирање..."</string>
- <string name="ota_hfa_activation_dialog_message" msgid="7921718445773342996">"Телефон активира услугу мобилног преноса података.\n\nТо може да потраје и до 5 минута."</string>
+ <string name="ota_hfa_activation_dialog_message" msgid="7921718445773342996">"Телефон активира услугу мобилних података.\n\nТо може да потраје и до 5 минута."</string>
<string name="ota_skip_activation_dialog_title" msgid="7666611236789203797">"Желите ли да прескочите активацију?"</string>
<string name="ota_skip_activation_dialog_message" msgid="6691722887019708713">"Ако прескочите активацију, не можете да упућујете позиве или да се повезујете са мрежама за мобилни пренос података (иако можете да се повежете са WiFi мрежама). Све док не активирате свој телефон, бићете упитани да то учините сваки пут када га укључите."</string>
<string name="ota_skip_activation_dialog_skip_label" msgid="5908029466817825633">"Прескочи"</string>
@@ -870,7 +883,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubID подразумеваног SIM-а за податке:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL пропусни опсег (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL пропусни опсег (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Информације о локацији предајника (застарело):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Конфигурација LTE физичког канала:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Учесталост освежавања информација о предајнику:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Све информације о мерењу за предајник:"</string>
@@ -915,4 +927,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Информације о телефону"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Информације о додели мобилног оператера"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Покретање доделе мобилног оператера"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Bluetooth сигнал је слаб. Пробајте да пређете на спикерфон."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Обавештење о квалитету позива"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Застарели SIP налози"</string>
</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index a31d849..4b112c4 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Endast den primära användaren får ändra röstbrevlådans inställningar."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"SIM-kortets spärr har tagits bort. Låser upp telefonen…"</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN-kod för upplåsning av SIM-nätverk"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"Operatörslåst SIM-kort"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Lås upp"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Ta bort permanent"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Försöker låsa upp nätverket…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Vidarebefordrar till <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Av"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Operatören stöder inte inaktivering av vidarebefordran av samtal när det inte går att nå telefonen."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Din operatör stöder inte vidarebefordran av samtal."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Aktivera Samtal väntar?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Du meddelas om inkommande samtal under samtal"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Aktivera"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Avbryt"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA samtal väntar under IMS på"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA samtal väntar under IMS av"</string>
<string name="updating_title" msgid="6130548922615719689">"Samtalsinställningar"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Det är bara administratören som kan ändra samtalsinställningar."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Inställningar (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Begränsad SIM-funktionalitet"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Samtals- och datatjänster från <xliff:g id="CARRIER_NAME">%1$s</xliff:g> kan vara blockerade medan du använder <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Samtals- och datatjänster från <xliff:g id="CARRIER_NAME">%1$s</xliff:g> kan vara blockerade när annat SIM-kort används."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Hittade och tog bort utfasade SIP-konton"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Android-plattformen stöder inte längre SIP-samtal.\nDitt befintliga SIP-konto <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> har tagits bort.\nBekräfta kontoinställningen för standardsamtal."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Öppna inställningarna"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Appars dataanvändning"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> mobildata använt mellan <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Avancerat"</string>
@@ -440,8 +451,8 @@
<string name="sum_fdn" msgid="6152246141642323582">"Hantera fasta nummer"</string>
<string name="sum_fdn_change_pin" msgid="3510994280557335727">"Ändra PIN-kod för FDN-åtkomst"</string>
<string name="sum_fdn_manage_list" msgid="3311397063233992907">"Hantera nummerlistan"</string>
- <string name="voice_privacy" msgid="7346935172372181951">"Sekretess för Voice"</string>
- <string name="voice_privacy_summary" msgid="3556460926168473346">"Aktivera avancerat sekretessläge"</string>
+ <string name="voice_privacy" msgid="7346935172372181951">"Integritet för Voice"</string>
+ <string name="voice_privacy_summary" msgid="3556460926168473346">"Aktivera avancerat integritetsläge"</string>
<string name="tty_mode_option_title" msgid="3843817710032641703">"TTY-läge"</string>
<string name="tty_mode_option_summary" msgid="4770510287236494371">"Ange TTY-läge"</string>
<string name="auto_retry_mode_title" msgid="2985801935424422340">"Försök igen automatiskt"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Felaktigt lösenord. SIM-kortet är nu låst. Ange PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK-kod 2 har blockerats permanent."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Du har <xliff:g id="NUMBER">%d</xliff:g> försök kvar."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 låst. Kontakta tjänsteleverantören för att låsa upp."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 är inte längre blockerad"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Nätverks- eller SIM-kortsfel"</string>
<string name="doneButton" msgid="7371209609238460207">"Färdig"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Aktivera mobilt nätverk, stäng av flygplansläget eller batterisparläget för att ringa ett samtal."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Inaktivera flygplansläget om du vill ringa."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Inaktivera flygplansläget eller anslut till ett trådlöst nätverk om du vill ringa."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefonen för varm"</b>\n\n"Det går inte att genomföra samtalet. Försök igen när telefonen svalnar.\n\nDet går fortfarande att ringa nödsamtal."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Avsluta läget för återuppringning vid nödsamtal om du vill ringa ett vanligt samtal."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Inte registrerat på nätverk."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Inget mobilt nätverk är tillgängligt."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId för standarddata på SIM-kortet:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Bandbredd för nedladdning (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Bandbredd för uppladdning (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Platsinformation för mast (utfasad):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Fysisk kanalkonfiguration för LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Frekvens för uppdatering av mastinformation:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Alla information om mastmätning:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Telefoninformation"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Information om operatörsadministration"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Aktivera operatörsadministration"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Svag Bluetooth-signal. Försök med att växla till högtalartelefon."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Avisering om samtalskvalitet"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Utfasade SIP-konton"</string>
</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 42bcd65..83727d0 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Mipangilio ya ujumbe wa sauti inaweza kubadilishwa na Mtumiaji wa Msingi Pekee."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Kadi yako ya simu imefunguliwa. Simu yangu inafungua…."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN ya kufungua mtandao wa SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM imefungwa kwa mtoa huduma"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Fungua"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Ondoa"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Inaomba mtandao ufunguliwe…."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Inasambaza kwa <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Imezimwa"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Mtoa huduma wako haauni ulemezaji wa kusambaza simu wakati simu yako haifikiwi."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Kampuni inayokupa huduma za simu haina huduma ya kusambaza simu."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Ungependa kuwasha kipengele cha simu inayosubiri kupokewa?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Simu inapoendelea, utaarifiwa kuhusu simu unazopigiwa"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Washa"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Ghairi"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Kipengele cha Simu Inayosubiri Kupokewa ya CDMA chini ya IMS Kimewashwa"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Kipengele cha Simu Inayosubiri Kupokewa ya CDMA chini ya IMS Kimezimwa"</string>
<string name="updating_title" msgid="6130548922615719689">"Mipangilio ya simu"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Mipangilio ya simu inaweza kubadilishwa na mtumiaji wa akaunti ya msimamizi."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Mipangilio (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Matumizi ya SIM yana kikomo"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Huenda huduma za simu na data za <xliff:g id="CARRIER_NAME">%1$s</xliff:g> zikazuiwa unapotumia <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Huenda huduma za simu na data za <xliff:g id="CARRIER_NAME">%1$s</xliff:g> zikazuiwa unapotumia SIM nyingine."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Akaunti za SIP ambazo zimefungwa zimepatikana na zikaondolewa"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Huduma ya kupiga simu kupitia SIP haipatikani tena kwenye mfumo wa Android.\nAkaunti zako za SIP zilizopo <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> zimeondolewa.\nTafadhali thibitisha mipangilio ya akaunti yako chaguomsingi ya kupiga simu."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Nenda kwenye mipangilio"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Programu inavyotumia data"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> za data ya mtandao wa simu zimetumika kuanzia <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Mipangilio ya Kina"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Nenosiri si sahihi. SIM sasa imefungwa. Weka PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 Imezuiwa kabisa."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Una majaribio <xliff:g id="NUMBER">%d</xliff:g> yaliyobaki."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"Imefungwa kwa sababu PUK2 si sahihi. Wasiliana na mtoa huduma ili aifungue."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 sasa haijazuiwa"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Hitilafu ya mtandao au SIM kadi"</string>
<string name="doneButton" msgid="7371209609238460207">"Kwisha"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Washa mtandao wa simu, zima hali ya ndegeni au uzime hali ya kiokoa betri ili upige simu."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Zima hali ya ndegeni ili upige simu."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Zima hali ya ndegeni au uunganishe kwenye mtandao pasiwaya ili upige simu."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Simu ina kiwango cha joto cha juu sana"</b>\n\n"Imeshindwa kupiga simu hii. Jaribu tena wakati kiwango cha joto cha simu yako kitapungua.\n\nBado unaweza kupiga simu za dharura."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Ondoka kwenye hali ya kupiga simu za dharura ili upige simu zisizokuwa za dharura."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Haijasajiliwa kwa mitandao"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mtandao wa simu haupatikani."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId ya SIM chaguomsingi ya data:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Kipimo Data cha DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Kipimo Data cha UL (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Maelezo ya Mahali Simu ya Mkononi Ilipo (huduma haipo tena):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Mipangilio ya Kituo Halisi cha LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Kasi ya Kuonyesha Upya Maelezo ya Simu:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Maelezo Yote ya Vipimo vya Simu:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Maelezo ya simu"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Maelezo ya Utaratibu wa Kutayarisha Mtandao"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Anzisha Utaratibu wa Kutayarisha Mtandao"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Muunganisho wako wa bluetooth ni dhaifu. Jaribu kubadilisha ili utumie spika ya simu."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Arifa ya Ubora wa Simu"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Akaunti za SIP ambazo zimefungwa"</string>
</resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 8d4e7ba..029c5bd 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"முதன்மை பயனர் மட்டுமே குரலஞ்சல் அமைப்புகளை மாற்ற முடியும்."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"சிம் கார்டின் தடுப்பு நீக்கப்பட்டது. மொபைல் திறக்கப்படுகிறது…"</string>
<string name="label_ndp" msgid="7617392683877410341">"சிம் நெட்வொர்க்கை அன்லாக் செய்யும் பின்"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"ஆப்பரேட்டருக்கு சிம் பூட்டப்பட்டுள்ளது"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"அன்லாக் செய்"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"விலக்கு"</string>
<string name="requesting_unlock" msgid="930512210309437741">"நெட்வொர்க்கை அன்லாக் செய்யக் கோருகிறது…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> க்குப் பகிர்"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"ஆஃப்"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"மொபைல் தொடர்புகொள்ள முடியாத இடத்தில் இருக்கும்போது, அழைப்பு பகிர்தலை முடக்குவதை ஆபரேட்டர் ஆதரிக்கவில்லை."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"உங்கள் மொபைல் நிறுவனம் அழைப்புப் பகிர்வை ஆதரிக்கவில்லை."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"அழைப்பு காத்திருப்பை இயக்கவா?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"ஒரு அழைப்பில் இருக்கும்போது, உள்வரும் அழைப்புகள் குறித்து உங்களுக்குத் தெரிவிக்கப்படும்"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"இயக்கு"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"ரத்துசெய்"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA அழைப்பு காத்திருப்பு IMS இயக்கத்தில்"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA அழைப்பு காத்திருப்பு IMS முடக்கத்தில்"</string>
<string name="updating_title" msgid="6130548922615719689">"அழைப்பு அமைப்பு"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"நிர்வாகிப் பயனர் மட்டுமே அழைப்பிற்கான அமைப்புகளை மாற்ற முடியும்."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"அமைப்புகள் (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"SIMமின் செயல்பாடு வரம்பிடப்பட்டது"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g> எண்ணைப் பயன்படுத்தும் போது <xliff:g id="CARRIER_NAME">%1$s</xliff:g> அழைப்புகளும் டேட்டா சேவைகளும் தடுக்கப்படக்கூடும்."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"வேறொரு SIMமில் <xliff:g id="CARRIER_NAME">%1$s</xliff:g> அழைப்புகளும் டேட்டா சேவைகளும் தடுக்கப்படக்கூடும்."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"நிறுத்தப்பட்ட SIP கணக்குகள் கண்டறியப்பட்டு அகற்றப்பட்டன"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Android பிளாட்ஃபார்மில் இனி SIP அழைப்பைச் செய்ய முடியாது.\nஏற்கெனவே உள்ள <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> SIP கணக்குகள் அகற்றப்பட்டன.\nஅழைப்பைச் செய்வதற்கான இயல்புக் கணக்கு அமைப்பை உறுதிசெய்யவும்."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"அமைப்புகளுக்குச் செல்"</string>
<string name="data_usage_title" msgid="8438592133893837464">"ஆப்ஸ் டேட்டா உபயோகம்"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g>: <xliff:g id="ID_1">%1$s</xliff:g> மொபைல் டேட்டா பயன்படுத்தப்பட்டது"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"மேம்பட்டவை"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"கடவுச்சொல் தவறானது. சிம் இப்போது பூட்டப்பட்டது. PUK2ஐ உள்ளிடவும்."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 தற்காலிகமாகத் தடுக்கப்பட்டது."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"நீங்கள் <xliff:g id="NUMBER">%d</xliff:g> முறை முயற்சிக்கலாம்."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 லாக் செய்யப்பட்டுள்ளது. அன்லாக் செய்ய சேவை வழங்குநரைத் தொடர்புகொள்ளவும்."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 இனி தடுக்கப்படாது"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"நெட்வொர்க் அல்லது சிம் கார்டு பிழை"</string>
<string name="doneButton" msgid="7371209609238460207">"முடிந்தது"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"அழைக்க, மொபைல் நெட்வொர்க்கை இயக்கவும், விமானப் பயன்முறை அல்லது பேட்டரி சேமிப்பான் பயன்முறையை முடக்கவும்."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"அழைக்க, விமானப் பயன்முறையை முடக்கவும்."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"அழைக்க, விமானப் பயன்முறையை முடக்கவும் அல்லது வயர்லெஸ் நெட்வொர்க்குடன் இணைக்கவும்."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"மொபைல் மிகச் சூடாக உள்ளது"</b>\n\n"இந்த அழைப்பைத் தொடர முடியவில்லை. மொபைலின் சூடு முழுமையாகத் தணிந்ததும் மீண்டும் அழைக்கவும்.\n\nஇருப்பினும் அவசர அழைப்புகளைச் செய்யலாம்."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"வழக்கமான அழைப்பிற்கு, அவசரகாலத் திரும்ப அழைக்கும் பயன்முறையிலிருந்து வெளியேறவும்."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"நெட்வொர்க்கில் பதிவுசெய்யப்படவில்லை."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"மொபைல் நெட்வொர்க் கிடைக்கவில்லை."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"இயல்பான டேட்டா சிம்மின் துணை ஐடி:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL இணைய வேகம் (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL இணைய வேகம் (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"மொபைலின் இருப்பிடத் தகவல் (நிறுத்தப்பட்டது):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ஃபிசிக்கல் சேனல் உள்ளமைவு:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"மொபைல் தகவலின் புதுப்பிப்பு விகிதம்:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"அனைத்து மொபைல் அளவீட்டுத் தகவல்:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"மொபைலின் தகவல்"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Carrier Provisioning Info"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Trigger Carrier Provisioning"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"புளூடூத் சிக்னல் வலுவற்றதாக உள்ளது. ஸ்பீக்கர் ஃபோனிற்கு மாற்றவும்."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"அழைப்பின் தரம் தொடர்பான அறிவிப்பு"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"நிறுத்தப்பட்ட SIP கணக்குகள்"</string>
</resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 1162141..1c232bf 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"వాయిస్ మెయిల్ సెట్టింగ్లను ప్రాథమిక వినియోగదారు మాత్రమే సవరించగలరు."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"మీ SIM కార్డు అన్బ్లాక్ చేయబడింది. మీ ఫోన్ అన్లాక్ చేయబడుతోంది…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM నెట్వర్క్ అన్లాక్ పిన్"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"ఈ ఆపరేటర్కు సంబంధించి SIM లాక్ చేయబడింది"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"అన్లాక్ చేయి"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"తీసివేయి"</string>
<string name="requesting_unlock" msgid="930512210309437741">"నెట్వర్క్ అన్లాక్ను అభ్యర్థిస్తోంది…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g>కి ఫార్వార్డ్ చేస్తోంది"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"ఆఫ్లో ఉంది"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"మీ ఫోన్ను చేరుకోవడం సాధ్యపడనప్పుడు కాల్ ఫార్వర్డింగ్ను నిలిపివేయడానికి మీ క్యారియర్ మద్దతు ఇవ్వదు."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"కాల్ ఫార్వర్డింగ్ను మీ క్యారియర్ సపోర్ట్ చేయదు."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"కాల్ వెయిటింగ్ను ఆన్ చేయాలా?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"మీరు కాల్స్లో ఉండగా, ఇన్కమింగ్ కాల్స్ల గురించి మీకు తెలియజేయబడుతుంది"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"ఆన్ చేయి"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"రద్దు చేయి"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMSలో CDMA కాల్ వెయిటింగ్ ఆన్లో ఉంది"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMSలో CDMA కాల్ వెయిటింగ్ ఆఫ్లో ఉంది"</string>
<string name="updating_title" msgid="6130548922615719689">"కాల్ సెట్టింగ్లు"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"కాల్ సెట్టింగ్లను నిర్వాహక వినియోగదారు మాత్రమే మార్చగలరు."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"సెట్టింగ్లు (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"SIM పనితీరు పరిమితంగా ఉంది"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g> ఉపయోగిస్తున్నప్పుడు <xliff:g id="CARRIER_NAME">%1$s</xliff:g> కాల్స్ మరియు డేటా సేవలు బ్లాక్ చేయబడవచ్చు."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"వేరొక SIMను ఉపయోగిస్తున్నప్పుడు <xliff:g id="CARRIER_NAME">%1$s</xliff:g> కాల్స్ మరియు డేటా సేవలు బ్లాక్ చేయబడవచ్చు."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"తీసివేయబడిన SIP ఖాతాలు కనుగొనబడ్డాయి, తీసివేయబడ్డాయి"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP కాలింగ్కు ఇకపై Android ప్లాట్ఫామ్లో సపోర్ట్ ఉండదు.\nఇప్పటికే ఉన్న మీ SIP ఖాతాలు<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> తీసివేయబడ్డాయి.\nదయచేసి మీ ఆటోమేటిక్ కాలింగ్ ఖాతా సెట్టింగ్ను ధృవీకరించండి."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"సెట్టింగ్లకు వెళ్లు"</string>
<string name="data_usage_title" msgid="8438592133893837464">"యాప్ డేటా వినియోగం"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g> మధ్య కాలంలో <xliff:g id="ID_1">%1$s</xliff:g> మొబైల్ డేటా ఉపయోగించబడింది"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"అధునాతన సెట్టింగ్లు"</string>
@@ -382,9 +393,9 @@
<string name="enable_disable_retail_directory" msgid="9095153426757081972">"రీటైల్ డైరెక్టరీ"</string>
<string name="retail_directory_enable" msgid="2532176568755452171">"రీటైల్ డైరెక్టరీ ప్రారంభించబడింది"</string>
<string name="retail_directory_disable" msgid="7242451210736299193">"రీటైల్ డైరెక్టరీ నిలిపివేయబడింది"</string>
- <string name="enable_disable_advertisements" msgid="8704125663718797151">"బిజినెస్ ప్రకటనలు"</string>
- <string name="advertisements_enable" msgid="8651409664217899145">"బిజినెస్ ప్రకటనలు ప్రారంభించబడ్డాయి"</string>
- <string name="advertisements_disable" msgid="2999957360508271492">"బిజినెస్ ప్రకటనలు నిలిపివేయబడ్డాయి"</string>
+ <string name="enable_disable_advertisements" msgid="8704125663718797151">"అడ్వర్టయిజ్మెంట్లు"</string>
+ <string name="advertisements_enable" msgid="8651409664217899145">"అడ్వర్టయిజ్మెంట్లు ఎనేబుల్ చేయబడ్డాయి"</string>
+ <string name="advertisements_disable" msgid="2999957360508271492">"అడ్వర్టయిజ్మెంట్లు డిజేబుల్ చేయబడ్డాయి"</string>
<string name="enable_disable_stock_quotes" msgid="631498357725951825">"స్టాక్ కోట్లు"</string>
<string name="stock_quotes_enable" msgid="5104318195579771947">"స్టాక్ కోట్లు ప్రారంభించబడ్డాయి"</string>
<string name="stock_quotes_disable" msgid="3126423481704498278">"స్టాక్ కోట్లు నిలిపివేయబడ్డాయి"</string>
@@ -459,7 +470,7 @@
<string name="adding_fdn_contact" msgid="3112531600824361259">"ఫిక్స్డ్ డయలింగ్ నంబర్ను జోడిస్తోంది..."</string>
<string name="fdn_contact_added" msgid="2840016151693394596">"ఫిక్స్డ్ డయలింగ్ నంబర్ జోడించబడింది."</string>
<string name="edit_fdn_contact" msgid="6030829994819587408">"ఫిక్స్డ్ డయలింగ్ నంబర్ను ఎడిట్ చేయండి"</string>
- <string name="updating_fdn_contact" msgid="6989341376868227150">"ఫిక్స్డ్ డయలింగ్ నంబర్ను నవీకరిస్తోంది..."</string>
+ <string name="updating_fdn_contact" msgid="6989341376868227150">"ఫిక్స్డ్ డయలింగ్ నంబర్ను అప్డేట్ చేస్తోంది..."</string>
<string name="fdn_contact_updated" msgid="6876330243323118937">"ఫిక్స్డ్ డయలింగ్ నంబర్ నవీకరించబడింది."</string>
<string name="delete_fdn_contact" msgid="7027405651994507077">"ఫిక్స్డ్ డయలింగ్ నంబర్ను తొలగించండి"</string>
<string name="deleting_fdn_contact" msgid="6872320570844460428">"ఫిక్స్డ్ డయలింగ్ నంబర్ను తొలగిస్తోంది..."</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"పాస్వర్డ్ చెల్లదు. SIM ఇప్పుడు లాక్ చేయబడింది. PUK2ని నమోదు చేయండి."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 శాశ్వతంగా బ్లాక్ చేయబడింది."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"మీకు <xliff:g id="NUMBER">%d</xliff:g> ప్రయత్నాలు మిగిలి ఉన్నాయి."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 లాక్ చేయబడింది. అన్లాక్ చేయడానికి సర్వీస్ ప్రొవైడర్ను కాంటాక్ట్ చేయండి."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 ఇప్పుడు బ్లాక్ అయ్యి లేదు"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"నెట్వర్క్ లేదా SIM కార్డ్ లోపం"</string>
<string name="doneButton" msgid="7371209609238460207">"పూర్తయింది"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"కాల్ చేయడానికి మొబైల్ నెట్వర్క్ను ఆన్ చేయండి, అలాగే ఎయిర్ప్లైన్ మోడ్ లేదా బ్యాటరీ సేవర్ మోడ్ ఆన్లో ఉంటే ఆఫ్ చేయండి."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"కాల్ చేయడానికి ఎయిర్ప్లైన్ మోడ్ను ఆఫ్ చేయండి."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"కాల్ చేయడానికి ఎయిర్ప్లైన్ మోడ్ను ఆఫ్ చేయండి లేదా వైర్లెస్ నెట్వర్క్కు కనెక్ట్ చేయండి."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"ఫోన్ చాలా వేడిగా ఉంది"</b>\n\n"ఈ కాల్ను పూర్తి చేయడం సాధ్యపడదు. మీ ఫోన్ చల్లబడినప్పుడు మళ్లీ ట్రై చేయండి.\n\nమీరు ఇప్పటికీ ఎమర్జెన్సీ కాల్స్ చేయవచ్చు."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"సాధారణ కాల్ చేయడానికి అత్యవసర కాల్బ్యాక్ మోడ్ నుండి నిష్క్రమించండి."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"నెట్వర్క్లో నమోదు కాలేదు."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"మొబైల్ నెట్వర్క్ అందుబాటులో లేదు."</string>
@@ -576,7 +589,7 @@
<string name="importSimEntry" msgid="3892354284082689894">"దిగుమతి చేయి"</string>
<string name="importAllSimEntries" msgid="2628391505643564007">"అన్నింటినీ దిగుమతి చేయి"</string>
<string name="importingSimContacts" msgid="4995457122107888932">"SIM కాంటాక్ట్లను దిగుమతి చేస్తోంది"</string>
- <string name="importToFDNfromContacts" msgid="5068664870738407341">"పరిచయాల నుండి దిగుమతి చేయి"</string>
+ <string name="importToFDNfromContacts" msgid="5068664870738407341">"కాంటాక్ట్ల నుండి దిగుమతి చేయి"</string>
<string name="singleContactImportedMsg" msgid="3619804066300998934">"కాంటాక్ట్ దిగుమతి చేయబడింది"</string>
<string name="failedToImportSingleContactMsg" msgid="228095510489830266">"కాంటాక్ట్ను దిగుమతి చేయడంలో విఫలమైంది"</string>
<string name="hac_mode_title" msgid="4127986689621125468">"వినికిడి సహాయక సాధనాలు"</string>
@@ -606,7 +619,7 @@
<string name="ota_skip_activation_dialog_title" msgid="7666611236789203797">"సక్రియం చేయడాన్ని దాటవేయాలా?"</string>
<string name="ota_skip_activation_dialog_message" msgid="6691722887019708713">"మీరు సక్రియం చేయడాన్ని దాటవేస్తే, కాల్స్ చేయలేరు లేదా మొబైల్ డేటా నెట్వర్క్లకు కనెక్ట్ చేయలేరు (మీరు Wi-Fi నెట్వర్క్లకు కనెక్ట్ చేయగలిగినప్పటికీ). మీరు మీ ఫోన్ను సక్రియం చేసేవరకు, దాన్ని ప్రారంభించే ప్రతిసారీ సక్రియం చేయమని మిమ్మల్ని అడుగుతుంది."</string>
<string name="ota_skip_activation_dialog_skip_label" msgid="5908029466817825633">"దాటవేయి"</string>
- <string name="ota_activate" msgid="7939695753665438357">"సక్రియం చేయి"</string>
+ <string name="ota_activate" msgid="7939695753665438357">"యాక్టివేట్ చేయండి"</string>
<string name="ota_title_activate_success" msgid="1272135024761004889">"ఫోన్ సక్రియం చేయబడింది."</string>
<string name="ota_title_problem_with_activation" msgid="7019745985413368726">"సక్రియం చేయడంలో సమస్య"</string>
<string name="ota_listen" msgid="2772252405488894280">"సక్రియం చేయడం పూర్తయిందని మీకు వినిపించేవరకు చదివి వినిపించే సూచనలను అనుసరించండి."</string>
@@ -651,7 +664,7 @@
<string name="contactPhoto" msgid="7885089213135154834">"కాంటాక్ట్ ఫోటో"</string>
<string name="goPrivate" msgid="4645108311382209551">"ప్రైవేట్గా వెళ్లు"</string>
<string name="selectContact" msgid="1527612842599767382">"కాంటాక్ట్ను ఎంచుకోండి"</string>
- <string name="not_voice_capable" msgid="2819996734252084253">"వాయిస్ కాలింగ్కు మద్దతు లేదు"</string>
+ <string name="not_voice_capable" msgid="2819996734252084253">"వాయిస్ కాలింగ్కు సపోర్ట్ లేదు"</string>
<string name="description_dial_button" msgid="8614631902795087259">"డయల్ చేయి"</string>
<string name="description_dialpad_button" msgid="7395114120463883623">"డయల్ప్యాడ్ను చూపు"</string>
<string name="pane_title_emergency_dialpad" msgid="3627372514638694401">"అత్యవసర డయల్ప్యాడ్"</string>
@@ -836,8 +849,8 @@
<string name="radioInfo_menu_viewADN" msgid="4533179730908559846">"SIM అడ్రస్ పుస్తకాన్ని చూడండి"</string>
<string name="radioInfo_menu_viewFDN" msgid="1847236480527032061">"ఫిక్స్డ్ డయలింగ్ నంబర్లను చూడండి"</string>
<string name="radioInfo_menu_viewSDN" msgid="2613431584522392842">"సర్వీస్ డయలింగ్ నంబర్లను చూడండి"</string>
- <string name="radioInfo_menu_getIMS" msgid="1950869267853198232">"IMS సేవ స్థితి"</string>
- <string name="radio_info_ims_reg_status_title" msgid="6875885401313992007">"IMS స్థితి"</string>
+ <string name="radioInfo_menu_getIMS" msgid="1950869267853198232">"IMS సేవ స్టేటస్"</string>
+ <string name="radio_info_ims_reg_status_title" msgid="6875885401313992007">"IMS స్టేటస్"</string>
<string name="radio_info_ims_reg_status_registered" msgid="7095182114078864326">"నమోదు చేయబడింది"</string>
<string name="radio_info_ims_reg_status_not_registered" msgid="8045821447288876085">"నమోదు కాలేదు"</string>
<string name="radio_info_ims_feature_status_available" msgid="6493200914756969292">"అందుబాటులో ఉన్నారు"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ఆటోమేటిక్ డేటా SIM యొక్క SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL బ్యాండ్విడ్త్ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL బ్యాండ్విడ్త్ (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"సెల్ లొకేషన్ సమాచారం (విస్మరించబడింది):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE భౌతిక ఛానెల్ కాన్ఫిగరేషన్:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"సెల్ సమాచార రిఫ్రెష్ సగటు:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"మొత్తం సెల్ పరిమాణ సమాచారం:"</string>
@@ -878,9 +890,9 @@
<string name="radio_info_ppp_resets_label" msgid="9131901102339077661">"బూట్ చేసినప్పటి నుండి PPP రీసెట్ సంఖ్య:"</string>
<string name="radio_info_current_network_label" msgid="3052098695239642450">"ప్రస్తుత నెట్వర్క్:"</string>
<string name="radio_info_ppp_received_label" msgid="5753592451640644889">"స్వీకరించిన డేటా:"</string>
- <string name="radio_info_gsm_service_label" msgid="6443348321714241328">"వాయిస్ సేవ:"</string>
+ <string name="radio_info_gsm_service_label" msgid="6443348321714241328">"వాయిస్ సర్వీస్:"</string>
<string name="radio_info_signal_strength_label" msgid="5545444702102543260">"సిగ్నల్ సామర్థ్యం:"</string>
- <string name="radio_info_call_status_label" msgid="7693575431923095487">"వాయిస్ కాల్ స్థితి:"</string>
+ <string name="radio_info_call_status_label" msgid="7693575431923095487">"వాయిస్ కాల్ స్టేటస్:"</string>
<string name="radio_info_ppp_sent_label" msgid="6542208429356199695">"పంపిన డేటా:"</string>
<string name="radio_info_message_waiting_label" msgid="1886549432566952078">"సందేశ నిరీక్షణ:"</string>
<string name="radio_info_phone_number_label" msgid="2533852539562512203">"ఫోన్ నంబర్:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"ఫోన్ సమాచారం"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"క్యారియర్ కేటాయింపు సమాచారం"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"క్యారియర్ కేటాయింపు సక్రియం చేయండి"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"మీ బ్లూటూత్ సిగ్నల్ బలహీనంగా ఉంది. స్పీకర్ఫోన్కు స్విచ్ అవ్వడానికి ట్రై చేయండి."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"కాల్ క్వాలిటీ నోటిఫికేషన్"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"తీసివేయబడిన SIP ఖాతాలు"</string>
</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index e527e89..f2e94dd 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"ผู้ใช้หลักเท่านั้นที่สามารถเปลี่ยนการตั้งค่าข้อความเสียงได้"</string>
<string name="puk_unlocked" msgid="4627340655215746511">"ยกเลิกการปิดกั้นซิมการ์ดแล้ว กำลังปลดล็อกโทรศัพท์ของคุณ..."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN ปลดล็อกเครือข่ายซิม"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"ล็อก SIM สำหรับโอเปอเรเตอร์แล้ว"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"ปลดล็อก"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"เลิกแสดง"</string>
<string name="requesting_unlock" msgid="930512210309437741">"กำลังขอปลดล็อกเครือข่าย..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"โอนสายไปที่ <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"ปิด"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"ผู้ให้บริการของคุณไม่สนับสนุนการปิดใช้งานการโอนสายเมื่อติดต่อไม่ได้"</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"ผู้ให้บริการของคุณไม่รองรับการโอนสาย"</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"เปิดใช้สายเรียกซ้อนไหม"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"ระบบจะแจ้งเตือนหากมีสายเรียกเข้าซ้อนระหว่างการโทร"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"เปิด"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"ยกเลิก"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"เปิดใช้สายเรียกซ้อนระบบ CDMA ภายใต้ IMS"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"ปิดใช้สายเรียกซ้อนระบบ CDMA ภายใต้ IMS"</string>
<string name="updating_title" msgid="6130548922615719689">"การตั้งค่าการโทร"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"ผู้ใช้ระดับผู้ดูแลระบบเท่านั้นที่สามารถเปลี่ยนแปลงการตั้งค่าการโทรได้"</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"การตั้งค่า (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"การใช้งาน SIM ถูกจำกัด"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"บริการโทรออกและอินเทอร์เน็ตของ <xliff:g id="CARRIER_NAME">%1$s</xliff:g> อาจถูกบล็อกขณะใช้ <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>"</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"บริการโทรออกและอินเทอร์เน็ตของ <xliff:g id="CARRIER_NAME">%1$s</xliff:g> อาจถูกบล็อกขณะใช้ SIM อื่น"</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"พบบัญชี SIP ที่เลิกใช้งานและนำออกแล้ว"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"แพลตฟอร์ม Android จะไม่รองรับการโทร SIP อีกต่อไป\nระบบนำบัญชี SIP ที่มีอยู่ของคุณ<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>ออกแล้ว\nโปรดยืนยันการตั้งค่าบัญชีการโทรเริ่มต้น"</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"ไปที่การตั้งค่า"</string>
<string name="data_usage_title" msgid="8438592133893837464">"การใช้อินเทอร์เน็ตของแอป"</string>
<string name="data_usage_template" msgid="6287906680674061783">"เน็ตมือถือ <xliff:g id="ID_1">%1$s</xliff:g> ที่ใช้ระหว่าง <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"ขั้นสูง"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"รหัสผ่านไม่ถูกต้อง ระบบล็อกซิมแล้ว โปรดป้อน PUK2"</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 ถูกบล็อกอย่างถาวร"</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"คุณพยายามได้อีก <xliff:g id="NUMBER">%d</xliff:g> ครั้ง"</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 ล็อกอยู่ ติดต่อผู้ให้บริการเพื่อปลดล็อก"</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"ไม่มีการบล็อก PIN2 อีกต่อไป"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"เครือข่ายหรือซิมการ์ดเกิดข้อผิดพลาด"</string>
<string name="doneButton" msgid="7371209609238460207">"เสร็จสิ้น"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"เปิดเครือข่ายมือถือ ปิดโหมดบนเครื่องบิน หรือปิดโหมดประหยัดแบตเตอรี่เพื่อโทรออก"</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"ปิดโหมดบนเครื่องบินเพื่อโทรออก"</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"ปิดโหมดบนเครื่องบินหรือเชื่อมต่อเครือข่ายไร้สายเพื่อโทรออก"</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"โทรศัพท์ร้อนเกินไป"</b>\n\n"ไม่สามารถโทรได้ โปรดลองอีกครั้งเมื่อโทรศัพท์เย็นลงแล้ว\n\nคุณยังสามารถโทรหมายเลขฉุกเฉินได้อยู่"</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"โปรดออกจากโหมดการโทรกลับกรณีฉุกเฉินเพื่อโทรไปยังหมายเลขที่ไม่ใช่หมายเลขฉุกเฉิน"</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"ยังไม่ได้ลงทะเบียนบนเครือข่าย"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"เครือข่ายมือถือใช้งานไม่ได้"</string>
@@ -566,7 +579,7 @@
<string name="onscreenEndCallText" msgid="6138725377654842757">"สิ้นสุด"</string>
<string name="onscreenShowDialpadText" msgid="658465753816164079">"แป้นหมายเลข"</string>
<string name="onscreenMuteText" msgid="5470306116733843621">"ปิดเสียง"</string>
- <string name="onscreenAddCallText" msgid="9075675082903611677">"เพิ่มการโทร"</string>
+ <string name="onscreenAddCallText" msgid="9075675082903611677">"เพิ่มสาย"</string>
<string name="onscreenMergeCallsText" msgid="3692389519611225407">"รวมสาย"</string>
<string name="onscreenSwapCallsText" msgid="2682542150803377991">"สลับ"</string>
<string name="onscreenManageCallsText" msgid="1162047856081836469">"จัดการการโทร"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId ของซิมอินเทอร์เน็ตเริ่มต้น:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"แบนด์วิดท์ดาวน์โหลด (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"แบนด์วิดท์อัปโหลด (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"ข้อมูลตำแหน่งของเครือข่ายมือถือ (เลิกใช้งานแล้ว):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"การกำหนดค่าแชเนลทางกายภาพของ LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"อัตราการรีเฟรชข้อมูลมือถือ:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"ข้อมูลการวัดเครือข่ายมือถือทั้งหมด:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"ข้อมูลโทรศัพท์"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"ข้อมูลการจัดสรรผู้ให้บริการ"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"ทริกเกอร์การจัดสรรผู้ให้บริการ"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"สัญญาณบลูทูธอ่อน ลองเปลี่ยนไปใช้ลำโพงแทน"</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"การแจ้งเตือนคุณภาพการโทร"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"บัญชี SIP ที่เลิกใช้งาน"</string>
</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 83b4585..495807b 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Maaari lang baguhin ng Pangunahing User ang Mga Setting ng Voicemail."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Na-unblock ang iyong SIM card. Nag-a-unlock ang telepono mo…"</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN na pang-unlock ng SIM network"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"Na-lock para sa operator ang SIM"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"I-unlock"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Balewalain"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Humihiling ng pag-unlock sa network…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Pinapasa sa <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Naka-off"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Hindi sinusuportahan ng iyong carrier ang hindi pagpapagana ng pagpasa ng tawag kapag hindi maabot ang iyong telepono."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Hindi sinusuportahan ng iyong carrier ang pagpasa ng tawag."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"I-on ang call waiting?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Habang tumatawag, aabisuhan ka sa mga papasok na tawag"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"I-on"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Kanselahin"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Naka-on ang CDMA Call Waiting sa IMS"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Naka-off ang CDMA Call Waiting sa IMS"</string>
<string name="updating_title" msgid="6130548922615719689">"Mga setting ng tawag"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Ang admin user lang ang maaaring magbago sa mga setting ng tawag."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Mga Setting (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Limitadong functionality ng SIM"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Ang mga tawag at serbisyo ng data ng <xliff:g id="CARRIER_NAME">%1$s</xliff:g> ay puwedeng ma-block habang ginagamit ang <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Ang tawag at serbisyo ng data ng <xliff:g id="CARRIER_NAME">%1$s</xliff:g> ay maba-block habang ginagamit ang isa pang SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Nakita at naalis ang mga hindi na ginagamit na SIP account"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Hindi na sinusuportahan ng Android Platform ang pagtawag sa pamamagitan ng SIP.\nNaalis na ang iyong mga kasalukuyang SIP account na <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>.\nPakikumpirma ang iyong default na setting ng account para sa pagtawag."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Pumunta sa mga setting"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Paggamit ng data ng app"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> na mobile data ang nagamit noong <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Advanced"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Mali ang password. Naka-lock na ngayon ang SIM. Ilagay ang PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"Permanenteng na-block ang PUK2."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Mayroon ka na lang <xliff:g id="NUMBER">%d</xliff:g> (na) natitirang pagsubok."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"Na-lock ang PUK2. Makipag-ugnayan sa service provider para i-unlock."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"Hindi na naka-block ang PIN2"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Error sa network o SIM card"</string>
<string name="doneButton" msgid="7371209609238460207">"Tapos na"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"I-on ang mobile network, i-off ang airplane mode o i-off ang battery saver mode upang tumawag."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"I-off ang airplane mode upang makatawag."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"I-off ang airplane mode o kumonekta sa isang wireless network upang makatawag."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Masyadong mainit ang telepono"</b>\n\n"Hindi makumpleto ang tawag na ito. Subukan ulit kapag lumamig ang iyong telepono.\n\nMakakagawa ka pa rin ng mga emergency na tawag."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Lumabas sa emergency callback mode upang makapagsagawa ng hindi pang-emergency na pagtawag."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Hindi nakarehistro sa network."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Hindi available ang mobile network."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId ng default na data SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL Bandwidth (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL Bandwidth (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Impormasyon ng Lokasyon ng Cell (hindi na ginagamit):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuration ng LTE Physical Channel:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Rate ng Pag-refresh ng Impormasyon ng Cell:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Impormasyon ng Pagsukat sa Lahat ng Cell:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Impormasyon ng telepono"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Impormasyon sa Provisioning ng Carrier"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"I-trigger ang Provisioning ng Carrier"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Mahina ang signal ng iyong bluetooth. Subukang lumipat sa speakerphone."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Notification sa Kalidad ng Tawag"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Mga hindi na ginagamit na SIP account"</string>
</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 3a9a099..74bb70b 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Sesli Mesaj Ayarları yalnızca Birincil Kullanıcı tarafından değiştirilebilir."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"SIM kartınızın engellemesi kaldırıldı. Telefonunuzun kilidi açılıyor..."</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM ağı kilit açma PIN kodu"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM şu operatör için kilitlendi"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Kilit Aç"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Kapat"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Ağ kilidini açma isteği yapılıyor..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Şuraya yönlendiriliyor: <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Kapalı"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Operatörünüz, telefonunuza ulaşılamadığında çağrı yönlendirmenin devre dışı bırakılmasını desteklemiyor."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Operatörünüz arama yönlendirmeyi desteklemiyor."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Arama bekletme etkinleştirilsin mi?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Bir arama sırasında gelen aramalarla ilgili olarak bilgilendirilirsiniz"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Etkinleştir"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"İptal"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS altında CDMA Arama Bekletme Etkin"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS altında CDMA Arama Bekletme Devre Dışı"</string>
<string name="updating_title" msgid="6130548922615719689">"Çağrı ayarları"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Çağrı ayarları sadece yönetici tarafından değiştirilebilir."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Ayarlar (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Sınırlı SIM işlevselliği"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g> kullanılırken <xliff:g id="CARRIER_NAME">%1$s</xliff:g> aramaları ve veri hizmetleri engellenebilir."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Başka bir SIM kullanılırken <xliff:g id="CARRIER_NAME">%1$s</xliff:g> aramaları ve veri hizmetleri engellenebilir."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Kullanımdan kaldırılmış SIP hesapları bulundu ve kaldırıldı"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP araması, Android platformunda artık desteklenmemektedir.\nMevcut SIP hesaplarınız <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> kaldırıldı.\nLütfen varsayılan arama hesabı ayarınızı onaylayın."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Ayarlara git"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Uygulama veri kullanımı"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g> arasında <xliff:g id="ID_1">%1$s</xliff:g> mobil veri kullanıldı"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Gelişmiş"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Şifre hatalı. SIM kilitlendi. PUK2\'yi girin."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 kalıcı olarak engellendi."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"<xliff:g id="NUMBER">%d</xliff:g> deneme hakkınız kaldı."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 kilitlendi. Kilidi açmak için servis sağlayıcıyla iletişime geçin."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 artık engellenmiş durumda değil"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Ağ veya SIM kart hatası"</string>
<string name="doneButton" msgid="7371209609238460207">"Bitti"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Telefon etmek için mobil ağı açın, uçak modunu veya pil tasarrufu modunu kapatın."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Telefon etmek için uçak modunu kapatın."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Telefon etmek için uçak modunu kapatın veya kablosuz ağa bağlanın."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefon çok ısındı"</b>\n\n"Bu arama tamamlanamıyor. Telefonunuz soğuyana kadar bekleyip tekrar deneyin.\n\nBu süre zarfında acil durum aramaları yapabilirsiniz."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Acil durum çağrısı dışında bir çağrı yapmak için acil durumda geri aranma modundan çıkın."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Ağda kayıtlı değil."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobil ağ kullanılamıyor."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Varsayılan veri SIM\'inin alt kimliği:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"İndirme Bant Genişliği (kb/sn.):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Yükleme Bant Genişliği (kb/sn.):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Hücre Konumu Bilgisi (kullanımdan kaldırıldı):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE Fiziksel Kanal Yapılandırması:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Hücre Bilgilerini Yenileme Hızı:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Tüm Hücre Ölçümü Bilgileri:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Telefon bilgileri"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Operatör Temel Hazırlık Bilgileri"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Tetikleyici Operatör Temel Hazırlığı"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Bluetooth sinyaliniz zayıf. Hoparlöre geçmeyi deneyin."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Arama Kalitesiyle İlgili Bildirim"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Kullanımdan kaldırılan SIP hesapları"</string>
</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 5cdd5fb..92aa4d3 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Налаштування голосової пошти може змінювати лише основний користувач."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Вашу SIM-карту розблоковано. Ваш телефон розблоковується..."</string>
<string name="label_ndp" msgid="7617392683877410341">"Розбл. PIN-код мережі SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"Заблокована SIM-карта оператора"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Розблок."</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Відхилити"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Запитув. розблок. мережі..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Переадресація на <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Вимкнено"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Ваш оператор не підтримує вимкнення переадресації викликів, коли телефон недосяжний."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Ваш оператор не підтримує переадресацію викликів."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Увімкнути паралельний виклик?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Під час дзвінків ви отримуватимете сповіщення про інші вхідні виклики"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Увімкнути"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Скасувати"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Паралельний виклик CDMA на платформі IMS увімкнено"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Паралельний виклик CDMA на платформі IMS вимкнено"</string>
<string name="updating_title" msgid="6130548922615719689">"Налаштування викликів"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Налаштування викликів може змінювати лише адміністратор."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Налаштування (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Обмежені функції SIM-карти"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Виклики та сервіси даних оператора <xliff:g id="CARRIER_NAME">%1$s</xliff:g> може бути заблоковано, коли ви використовуєте <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Коли працює інша SIM-карта, виклики й сервіси даних <xliff:g id="CARRIER_NAME">%1$s</xliff:g> може бути заблоковано."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Знайдено та вилучено облікові записи SIP, що не підтримуються"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Платформа Android більше не підтримує виклики SIP.\nНаявні облікові записи SIP (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>) вилучено. \nПеревірте налаштування для викликів за умовчанням в обліковому записі."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Перейти в налаштування"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Використання трафіку додатками"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> мобільного трафіку використано в період <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Додатково"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Неправильний пароль. SIM-карту заблоковано. Введіть PUK2-код."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2-код назавжди заблоковано."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"У вас залишилося стільки спроб: <xliff:g id="NUMBER">%d</xliff:g>."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"Заблоковано кодом PUK2. Щоб розблокувати, зв’яжіться з оператором."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2-код більше не заблоковано"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Помилка мережі або SIM-карти"</string>
<string name="doneButton" msgid="7371209609238460207">"Готово"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Щоб зателефонувати, увімкніть мобільну мережу, вимкніть режим польоту або режим економії заряду."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Щоб зателефонувати, вимкніть режим польоту."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Щоб зателефонувати, вимкніть режим польоту або під’єднайтеся до бездротової мережі."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Телефон занадто нагрівся"</b>\n\n"Не вдалося завершити виклик. Зачекайте, доки телефон охолоне, і повторіть спробу.\n\nЕкстрені виклики доступні."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Щоб зателефонувати на звичайний номер, вимкніть режим екстрених викликів."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Не зареєстровано в мережі."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Мобільна мережа недоступна."</string>
@@ -873,7 +886,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Субідентифікатор SIM-карти для даних за умовчанням:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Пропускна спроможність DL (кбіт/с):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Пропускна спроможність UL (кбіт/с):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Геодані телефона (більше не підтримуються):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Конфігурація фізичного каналу LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Частота оновлення даних про мобільний зв\'язок:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Усі дані про показники мобільного зв\'язку:"</string>
@@ -918,4 +930,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Інформація про телефон"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Інформація про ініціалізацію оператора"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Запустити ініціалізацію оператора"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Сигнал Bluetooth заслабкий. Спробуйте переключитися на гучний зв\'язок."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Сповіщення про якість виклику"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Облікові записи SIP, що не підтримуються"</string>
</resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 4209b1e..7a5e676 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"صرف بنیادی صارف ہی صوتی میل کی ترتیبات تبدیل کر سکتا ہے۔"</string>
<string name="puk_unlocked" msgid="4627340655215746511">"آپ کا SIM کارڈ غیر مسدود ہوگیا ہے۔ آپ کا فون غیر مقفل ہو رہا ہے…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM نیٹ ورک غیر مقفل کرنے کا PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"آپریٹر کے لیے SIM مقفل ہے"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"غیر مقفل کریں"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"برخاست کریں"</string>
<string name="requesting_unlock" msgid="930512210309437741">"نیٹ ورک غیر مقفل کرنے کی درخواست کر رہا ہے…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> کو آگے بھیج رہا ہے"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"آف"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"آپ کا کیریئر آپ کا فون ناقابل رسائی ہونے پر کال آگے منتقل کرنے کو غیر فعال کرنے کا تعاون نہیں کرتا ہے۔"</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"آپ کا کیریئر کال فارورڈنگ کو سپورٹ نہیں کرتا ہے۔"</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"\'کال کا انتظار کرنا\' آن کریں؟"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"کال کے دوران، آپ کو موصول ہونے والی کالز کے بارے میں مطلع کیا جائے گا"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"آن کریں"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"منسوخ کریں"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS آن کے تحت CDMA کال کا انتظار کرنا"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS آف کے تحت CDMA کال کا انتظار کرنا"</string>
<string name="updating_title" msgid="6130548922615719689">"کال کی ترتیبات"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"صرف منتظم صارف ہی کال کی ترتیبات تبدیل کر سکتا ہے۔"</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"ترتیبات (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"SIM کے محدود فنکشن"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g> کے استعمال کے دوران <xliff:g id="CARRIER_NAME">%1$s</xliff:g> سے کالز اور ڈیٹا سروسز کو مسدود کیا جا سکتا ہے۔"</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"دوسرے SIM کے استعمال کے دوران <xliff:g id="CARRIER_NAME">%1$s</xliff:g> سے کالز اور ڈیٹا سروسز کو مسدود کیا جا سکتا ہے۔"</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"فرسودہ SIP اکاؤنٹس ملیں اور انہیں ہٹا دیا گیا"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Android پلیٹ فارم کے ذریعے SIP کالنگ اب تعاون یافتہ نہیں ہے۔\nآپ کے موجودہ SIP اکاؤنٹس <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> ہٹا دیے گئے ہیں۔\nبراہ کرم اپنے ڈیفالٹ کالنگ اکاؤنٹ کی ترتیب کی تصدیق کریں۔"</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"ترتیبات پر جائیں"</string>
<string name="data_usage_title" msgid="8438592133893837464">"ایپ ڈیٹا کا استعمال"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> موبائل ڈیٹا <xliff:g id="ID_2">%2$s</xliff:g> میں استعمال ہوا"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"جدید ترین"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"پاس ورڈ غلط ہے۔ SIM اب مقفل ہے۔ PUK2 درج کریں۔"</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 مستقل طور پر مسدود ہے۔"</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"آپ کے پاس <xliff:g id="NUMBER">%d</xliff:g> کوششیں بچی ہیں۔"</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 مقفل ہے۔ غیر مقفل کرنے کے ليے سروس فراہم کنندہ سے رابطہ کریں۔"</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 اب مسدود نہیں ہے"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"نیٹ ورک یا SIM کارڈ کی خرابی"</string>
<string name="doneButton" msgid="7371209609238460207">"ہوگیا"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"کال کرنے کیلئے موبائل نیٹ ورک آن کریں، ہوائی جہاز موڈ یا بیٹری سیور موڈ آف کریں۔"</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"کال کرنے کیلئے ہوائی جہاز وضع آف کریں۔"</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"کال کرنے کیلئے ہوائی جہاز وضع آف کریں یا کسی وائرلیس نیٹ ورک سے منسلک ہوں۔"</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"فون بہت گرم ہے"</b>\n\n"اس کال کو مکمل نہیں کیا جا سکتا۔ جب آپ کا فون ٹھنڈا ہو جائے تو دوبارہ کوشش کریں۔\n\nآپ اب بھی ہنگامی کالز کر سکتے ہیں۔"</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"غیر ہنگامی کال کرنے کیلئے ہنگامی کال بیک موڈ سے اخراج کریں۔"</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"نیٹ ورک پر رجسٹرڈ نہیں ہے۔"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"موبائل نیٹ ورک دستیاب نہیں ہے۔"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ڈیفالٹ ڈیٹا SIM کی SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL بینڈ وڈتھ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL بینڈ وڈتھ (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"سیل کے مقام کی معلومات (فرسودہ):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE فزيکل چینل کنفیگریشن:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"سیل کی معلومات ریفریش کرنے کی شرح:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"سیل پیمائش کی تمام معلومات:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"فون کی معلومات"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"کیرئیر فراہمی معلومات"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"ٹرگر کیرئیر فراہمی"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"آپ کا بلوٹوتھ سگنل کمزور ہے۔ اسپیکر فون پر سوئچ کر کے آزمائیں۔"</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"کال کی کوالٹی کی اطلاع"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"فرسودہ SIP اکاؤنٹس"</string>
</resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 982bef7..c86c5ee 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Ovozli pochta sozlamalarini faqat bosh foydalanuvchi o‘zgartira oladi."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"To‘sib qo‘yilgan SIM kartangiz ochildi. Telefoningiz qulfdan chiqarilmoqda…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM kartani tarmoqdagi qulfidan chiqarish PIN kodi"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"SIM karta operator uchun bloklangan"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Qulfdan chiqarish"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Rad etish"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Tarmoqni qulfdan chiqarish so‘ralmoqda…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> raqamiga yo‘naltiriladi"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Yoqilmagan"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Telefoningiz o‘chiq bo‘lganida qo‘ng‘iroqlarni boshqa raqamga yo‘naltirishni o‘chirish xususiyati tarmoq operatoringizda yo‘q."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Operatoringiz tarmog‘i orqali chaqiruvlarni uzatib bo‘lmaydi"</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Chaqiruvdagi kutish rejimi yoqilsinmi?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Suhbat paytida boshqa kiruvchi chaqiruvlar haqida sizga bildirishnoma keladi"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Yoqish"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Bekor qilish"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS asosidagi CDMA chaqiruvlari uchun kutish rejimi yoqildi"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS asosidagi CDMA chaqiruvlari uchun kutish rejimi faolsizlantirildi"</string>
<string name="updating_title" msgid="6130548922615719689">"Chaqiruv sozlamalari"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Faqat administrator qo‘ng‘iroq sozlamalarini o‘zgartirishi mumkin."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Sozlamalar (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"SIM kartada ayrim funksiyalar ishlamaydi"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"<xliff:g id="PHONE_NUMBER">%2$s</xliff:g> raqami ishlatilsa, <xliff:g id="CARRIER_NAME">%1$s</xliff:g> orqali chaqiruvlar va mobil internet bloklanishi mumkin."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Boshqa SIM karta ishlatilsa, <xliff:g id="CARRIER_NAME">%1$s</xliff:g> orqali chaqiruvlar va internet bloklanishi mumkin."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"To‘xtatilgan SIP hisoblar aniqlandi va olib tashlandi"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Endi Android platformasida SIP orqali chaqiruvlarni amalga oshirish imkonsiz.\nSIP hisoblaringiz (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>) olib tashlandi.\nChaqiruvlar uchun standart holatda ishlatiladigan hisobingizni tasdiqlang."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Sozlamalarni ochish"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Ilova sarflagan trafik"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g> davomida sarflangan mobil trafik: <xliff:g id="ID_1">%1$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Kengaytirilgan sozlamalar"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Parol noto‘g‘ri. SIM karta qulflandi. PUK2 kodini kiriting."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 kod butunlay to‘sib qo‘yildi."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Sizda <xliff:g id="NUMBER">%d</xliff:g> ta urinish qoldi."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 bloklangan. Uni blokdan chiqarish uchun xizmat ta’minotchisiga murojaat qiling."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 kodi blokdan chiqarildi"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Tarmoq yoki SIM kartada xato"</string>
<string name="doneButton" msgid="7371209609238460207">"Tayyor"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Telefon qilish uchun mobil tarmoqni yoqing, parvoz yoki quvvat tejash rejimini oʻchiring."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Qo‘ng‘iroq qilish uchun parvoz rejimini o‘chiring"</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Qo‘ng‘iroq qilish uchun parvoz rejimini o‘chiring yoki simsiz tarmoqqa ulaning."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Telefon qizib ketdi"</b>\n\n"Chaqiruvni amalga oshirish imkonsiz. Telefoningiz soviganida qayta urining.\n\nFavqulodda chaqiruvlarni har qanday holatda ham amalga oshira olasiz."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Odatiy qo‘ng‘iroq qilish uchun favqulodda qayta qo‘ng‘iroq rejimidan chiqing."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Tarmoqda ro‘yxatdan o‘tmagan."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Uyali tarmoq mavjud emas."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Mobil internet uchun birlamchi SIM kartaning qoʻshimcha identifikatori:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Axborot uzatish tezligi (kbit/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL kanalining axborot uzatish tezligi (kbit/s):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Tarmoq joylashuvi haqidagi axborot (eskirgan):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE jismoniy kanal konfiguratsiyasi:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Tarmoq haqidagi axborotning yangilanish darajasi:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Tarmoq statistikasi:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Telefon haqida"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Operator sinxronizatsiyasi haqida"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Operator sinxronizatsiyasini yoqish/o‘chirish"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Bluetooth signali kuchsiz. Baland ovoz rejimini yoqish tavsiya etiladi."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Chaqiruv sifati haqida bildirishnoma"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"To‘xtatilgan SIP hisoblar"</string>
</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index d34d544..a6d8d20 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Chỉ người dùng chính mới có thể sửa đổi Cài đặt thư thoại."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Thẻ SIM của bạn đã được bỏ chặn. Điện thoại của bạn đang mở khóa…"</string>
<string name="label_ndp" msgid="7617392683877410341">"Mã PIM mở khóa mạng SIM"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"Đã khóa SIM đối với nhà cung cấp dịch vụ"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Mở khóa"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Loại bỏ"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Đang yêu cầu mở khóa mạng..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Chuyển tiếp tới <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Tắt"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Nhà cung cấp dịch vụ của bạn không hỗ trợ vô hiệu hóa chuyển tiếp cuộc gọi khi điện thoại của bạn không thể truy cập được."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Nhà mạng của bạn không hỗ trợ tính năng chuyển tiếp cuộc gọi."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Bật chế độ cuộc gọi chờ?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Trong suốt cuộc gọi, bạn sẽ nhận được thông báo về các cuộc gọi đến"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Bật"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Hủy"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Cuộc gọi chờ CDMA trong IMS đang bật"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Cuộc gọi chờ CDMA trong IMS đang tắt"</string>
<string name="updating_title" msgid="6130548922615719689">"Cài đặt cuộc gọi"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Chỉ người dùng quản trị mới có thể thay đổi cài đặt cuộc gọi."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Cài đặt (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Chức năng SIM bị giới hạn"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Các cuộc gọi và dịch vụ dữ liệu của nhà mạng <xliff:g id="CARRIER_NAME">%1$s</xliff:g> có thể bị chặn khi bạn sử dụng số <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Các cuộc gọi và dịch vụ dữ liệu của nhà mạng <xliff:g id="CARRIER_NAME">%1$s</xliff:g> có thể bị chặn khi bạn sử dụng một SIM khác."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Đã tìm thấy và xóa các tài khoản SIP không dùng nữa"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Nền tảng Android không còn hỗ trợ tính năng gọi điện qua SIP.\nCác tài khoản SIP hiện có của bạn <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> đã bị xóa.\nVui lòng xác nhận chế độ cài đặt tài khoản gọi mặc định của bạn."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Chuyển đến phần Cài đặt"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Mức sử dụng dữ liệu của ứng dụng"</string>
<string name="data_usage_template" msgid="6287906680674061783">"Đã dùng <xliff:g id="ID_1">%1$s</xliff:g> dữ liệu di động: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Nâng cao"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Mật khẩu không đúng. SIM hiện đã bị chặn. Hãy nhập PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 bị chặn vĩnh viễn."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Bạn còn <xliff:g id="NUMBER">%d</xliff:g> lần thử."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 đang bị khóa. Hãy liên hệ với nhà cung cấp dịch vụ để mở khóa."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 không còn bị chặn"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Lỗi mạng hoặc thẻ SIM"</string>
<string name="doneButton" msgid="7371209609238460207">"Xong"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Bật mạng di động, tắt chế độ trên máy bay hoặc tắt chế độ trình tiết kiệm pin để thực hiện cuộc gọi."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Tắt chế độ trên máy bay để thực hiện cuộc gọi."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Tắt chế độ trên máy bay hoặc kết nối với mạng không dây để thực hiện cuộc gọi."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Điện thoại quá nóng"</b>\n\n"Không thể hoàn tất cuộc gọi này. Hãy thử lại khi điện thoại nguội bớt.\n\nBạn vẫn có thể thực hiện cuộc gọi khẩn cấp."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Thoát khỏi chế độ gọi lại khẩn cấp để thực hiện cuộc gọi không khẩn cấp."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Chưa được đăng ký trên mạng."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mạng di động không khả dụng."</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Mã phụ của SIM dữ liệu mặc định:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Băng thông DL (kb/giây):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Băng thông UL (kb/giây):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Thông tin vị trí mạng di động (không dùng nữa):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Cấu hình kênh LTE thực:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Tốc độ làm mới thông tin mạng di động:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Toàn bộ thông tin về số đo mạng di động:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Thông tin điện thoại"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Thông tin cấp phép của nhà mạng"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Kích hoạt dịch vụ cấp phép của nhà mạng"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Tín hiệu Bluetooth của bạn đang yếu. Hãy thử chuyển sang loa ngoài."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Thông báo về chất lượng cuộc gọi"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Tài khoản SIP không dùng nữa"</string>
</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index d0d079d..6bcb0a5 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"只有主用户才能修改语音信箱设置。"</string>
<string name="puk_unlocked" msgid="4627340655215746511">"您的SIM卡已解锁。正在解锁您的手机..."</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM 网络解锁 PIN 码"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"已锁定以下运营商的 SIM 卡:"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"解锁"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"关闭"</string>
<string name="requesting_unlock" msgid="930512210309437741">"正在请求网络解锁..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"转接到 <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"关闭"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"您的运营商不支持在手机无法接通时停用来电转接功能。"</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"您的运营商不支持来电转接功能。"</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"要启用来电等待吗?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"如果您在通话期间接到来电,则会收到通知"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"开启"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"取消"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"基于 IMS 的 CDMA 来电等待功能已开启"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"基于 IMS 的 CDMA 来电等待功能已关闭"</string>
<string name="updating_title" msgid="6130548922615719689">"通话设置"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"只有管理员用户才能更改通话设置。"</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"设置(<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"SIM 卡功能受限"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"使用 <xliff:g id="PHONE_NUMBER">%2$s</xliff:g> 时,系统可能会屏蔽<xliff:g id="CARRIER_NAME">%1$s</xliff:g>通话和数据服务。"</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"使用另一张 SIM 卡时,系统可能会屏蔽<xliff:g id="CARRIER_NAME">%1$s</xliff:g>通话和数据服务。"</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"系统已找到并移除已弃用的 SIP 帐号"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"SIP 通话已不再受 Android 平台支持。\n您现有的 SIP 帐号 <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> 已被移除。\n请确认您的默认通话帐号设置。"</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"转到设置"</string>
<string name="data_usage_title" msgid="8438592133893837464">"应用的流量使用情况"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g>期间使用的移动数据流量为:<xliff:g id="ID_1">%1$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"高级"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"密码不正确。SIM 卡已锁定。请输入 PUK2 码。"</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2码已被永远锁定。"</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"您还可以尝试输入 <xliff:g id="NUMBER">%d</xliff:g> 次。"</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"SIM 卡已通过 PUK2 进行锁定。请联系服务提供商来解锁。"</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 码已解除锁定"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"网络或 SIM 卡错误"</string>
<string name="doneButton" msgid="7371209609238460207">"完成"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"需开启移动网络,并关闭飞行模式或省电模式才能拨打电话。"</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"需关闭飞行模式才能拨打电话。"</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"需关闭飞行模式或连接至无线网络才能拨打电话。"</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"手机过热"</b>\n\n"无法完成此通话。请先让手机降温,然后再重试。\n\n您仍然可以拨打紧急呼救电话。"</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"要拨打非紧急电话,请先退出紧急回拨模式。"</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"尚未注册网络。"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"无法连接到移动网络"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"默认数据 SIM 卡的 SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL 带宽 (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL 带宽 (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"移动网络位置信息(已弃用):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE 物理信道配置:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"移动网络信息刷新频率:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"所有移动网络测量信息:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"手机信息"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"运营商配置信息"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"触发运营商配置"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"您的蓝牙信号较弱。请尝试切换为扬声器模式。"</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"通话质量通知"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"已弃用的 SIP 帐号"</string>
</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index d60fdcd..85cedaa 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"只有主要使用者可以修改留言設定。"</string>
<string name="puk_unlocked" msgid="4627340655215746511">"您的 SIM 卡已解除封鎖。您的手機正在解除鎖定..."</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM 網絡解除鎖定 PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"流動網絡供應商的 SIM 卡已上鎖"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"解除鎖定"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"關閉"</string>
<string name="requesting_unlock" msgid="930512210309437741">"正在要求解除網絡鎖定..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"轉接至 <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"關閉"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"您的流動網絡供應商不支援在手機無法接通時停用轉接功能。"</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"您的流動網絡供應商不支援來電轉駁。"</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"要開啟來電等候功能嗎?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"您會在通話期間收到來電通知"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"開啟"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"取消"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS 下的 CDMA 來電等候功能已開啟"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS 下的 CDMA 來電等候功能已關閉"</string>
<string name="updating_title" msgid="6130548922615719689">"通話設定"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"只有管理員可以變更通話設定。"</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"設定 (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"SIM 卡功能受限"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"「<xliff:g id="CARRIER_NAME">%1$s</xliff:g>」通話和數據服務在使用 <xliff:g id="PHONE_NUMBER">%2$s</xliff:g> 卡時可能遭到封鎖。"</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"「<xliff:g id="CARRIER_NAME">%1$s</xliff:g>」通話和數據服務在使用其他 SIM 卡時可能遭到封鎖。"</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"已找到並移除已停用的 SIP 帳戶"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Android 平台不再支援 SIP 通話。\n您現有的 SIP 帳戶 (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>) 已移除。\n請確認您的預設通話帳戶設定。"</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"前往設定"</string>
<string name="data_usage_title" msgid="8438592133893837464">"應用程式數據用量"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g>期間使用了 <xliff:g id="ID_1">%1$s</xliff:g>流動數據"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"進階設定"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"密碼不正確。SIM 現在已被封鎖,請輸入 PUK2。"</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 碼已被永久封鎖。"</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"您還有 <xliff:g id="NUMBER">%d</xliff:g> 次嘗試機會。"</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"PUK2 已鎖定。請通知服務供應商解鎖。"</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 目前沒有封鎖"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"網絡或 SIM 卡錯誤。"</string>
<string name="doneButton" msgid="7371209609238460207">"完成"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"開啟流動網絡、關閉飛行模式或關閉省電模式,以撥打電話。"</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"關閉飛行模式以撥打電話。"</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"關閉飛行模式或連接無線網絡,以撥打電話。"</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"電話過熱"</b>\n\n"無法完成此通話。請在電話冷卻後再試一次。\n\n您仍可進行緊急電話。"</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"離開緊急回撥模式即可撥打非緊急電話。"</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"未在網絡上完成註冊。"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"無法使用流動網絡。"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"預設數據 SIM 卡的子 ID:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"下載頻寬 (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"上載頻寬 (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"發射站位置資料 (已淘汰):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE 實體渠道設定:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"發射站資料重新整理頻率:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"所有發射站量度資料:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"手機資訊"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"流動網絡供應商佈建資料"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"觸發流動網絡供應商佈建程序"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"您的藍牙訊號微弱。請改用擴音器。"</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"通話品質通知"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"已停用的 SIP 帳戶"</string>
</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 5d9de3c..64a54cd 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"只有主要使用者可以修改語音信箱設定。"</string>
<string name="puk_unlocked" msgid="4627340655215746511">"您的 SIM 卡已解鎖。正在解鎖手機中…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM 網路解鎖 PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"以下電信業者的 SIM 卡已鎖定"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"解除鎖定"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"關閉"</string>
<string name="requesting_unlock" msgid="930512210309437741">"正在要求解除網路鎖定…"</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"轉接至 <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"關閉"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"您的電信業者不支援手機無收訊時停用轉接功能。"</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"你的電信業者不支援來電轉接功能。"</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"要開啟來電等候功能嗎?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"如果通話期間有來電,系統會通知你"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"開啟"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"取消"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMS 的 CDMA 來電等候功能已開啟"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMS 的 CDMA 來電等候功能已關閉"</string>
<string name="updating_title" msgid="6130548922615719689">"通話設定"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"只有管理員使用者可以變更通話設定。"</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"設定 (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"SIM 卡功能受限"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"使用 <xliff:g id="PHONE_NUMBER">%2$s</xliff:g> 時,「<xliff:g id="CARRIER_NAME">%1$s</xliff:g>」的通話和數據服務可能會遭到封鎖。"</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"當你使用另一張 SIM 卡時,「<xliff:g id="CARRIER_NAME">%1$s</xliff:g>」的通話和數據服務可能會遭到封鎖。"</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"系統找到不適用的 SIP 帳戶,並已將這些帳戶移除"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Android 平台不再支援 SIP 通話功能。\n系統已移除你現有的 SIP 帳戶 (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>)。\n請確認你的預設通話帳戶設定。"</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"前往設定"</string>
<string name="data_usage_title" msgid="8438592133893837464">"應用程式數據用量"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g>期間使用了 <xliff:g id="ID_1">%1$s</xliff:g> 的行動數據"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"進階"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"密碼不正確,SIM 卡現已遭到封鎖。請輸入 PUK2。"</string>
<string name="puk2_blocked" msgid="3131139031779319911">"PUK2 遭到永久封鎖。"</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"您還可以嘗試 <xliff:g id="NUMBER">%d</xliff:g> 次。"</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"SIM 卡已遭 PUK2 碼鎖定,請要求服務供應商協助解鎖。"</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"PIN2 已解除封鎖"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"網路或 SIM 卡錯誤"</string>
<string name="doneButton" msgid="7371209609238460207">"完成"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"如要撥打電話,請開啟行動網路,並關閉飛航模式或省電模式。"</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"關閉飛航模式即可撥打電話。"</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"關閉飛航模式或連上無線網路即可撥打電話。"</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"手機過熱"</b>\n\n"無法撥打這通電話。請等到手機降溫後再試一次。\n\n在這段期間內仍可撥打緊急電話。"</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"結束緊急回撥模式,以便撥打非緊急電話。"</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"尚未註冊網路。"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"無法使用 Google 行動服務網路。"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"預設資料 SIM 卡的子 ID:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"下行頻寬 (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"上行頻寬 (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"行動網路位置資訊 (已不適用):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE 實體通道設定:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"行動網路資訊重新整理頻率:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"所有行動網路測量資訊:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"手機資訊"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"電信業者佈建資訊"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"觸發電信業者佈建程序"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"你的藍牙訊號微弱,建議你改用擴音模式。"</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"通話品質通知"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"不適用的 SIP 帳戶"</string>
</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index f5f5681..bcc1562 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -50,6 +50,7 @@
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Izilungiselelo zevoyisimeyili zingashintshwa kuphela umsebenzisi oyinhloko."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Ikhadi lakho le-SIM livuliwe. Ifoni yakho iyavula..."</string>
<string name="label_ndp" msgid="7617392683877410341">"Inethiwekhi ye-SIM yokuvula i-PIN"</string>
+ <string name="label_phoneid" msgid="8775611434123577808">"I-SIM ivalelwe umsebenzisi"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Vula"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Cashisa"</string>
<string name="requesting_unlock" msgid="930512210309437741">"Icela ukuvula inethiwekhi..."</string>
@@ -123,6 +124,13 @@
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Idlulisela ku-<xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Valiwe"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Isiphathi sakho asisekeli ukuvimbela ukudlulisela ucingo lapho ifoni yakho ingafinyeleleki."</string>
+ <string name="registration_cf_forbidden" msgid="4386482610771190420">"Inkampani yakho yenethiwekhi ayisekeli ukudlulisela phambili ikholi."</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Vula ukulinda ikholi?"</string>
+ <string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Phakathi nekholi, uzokwaziswa ngamakholi angenayo"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Vula"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"Hoxa"</string>
+ <string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"Ukulinda kwekholi ye-CDMA ngaphansi kwe-IMS kuvuliwe"</string>
+ <string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"Ukulinda kwekholi ye-CDMA ngaphansi kwe-IMS kuvaliwe"</string>
<string name="updating_title" msgid="6130548922615719689">"Izilungiselelo zekholi"</string>
<string name="call_settings_admin_user_only" msgid="7238947387649986286">"Izilungiselelo zekholi zingaguqulwa kuphela ngumsebenzisi oyinhloko."</string>
<string name="call_settings_with_label" msgid="8460230435361579511">"Izilungiselelo (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -286,6 +294,9 @@
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Ukusebenza okukhawulelwe kwe-SIM"</string>
<string name="limited_sim_function_with_phone_num_notification_message" msgid="5928988883403677610">"Amakholi e-<xliff:g id="CARRIER_NAME">%1$s</xliff:g> namasevisi edatha angavinjelwa kusetshenziswa u-<xliff:g id="PHONE_NUMBER">%2$s</xliff:g>."</string>
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"Amakholi e-<xliff:g id="CARRIER_NAME">%1$s</xliff:g> namasevisi edatha angavinjelwa ngenkathi kusetshenziswa enye i-SIM."</string>
+ <string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Ama-akhawunti we-SIP eshlisiwe atholakele futhi asusiwe"</string>
+ <string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Ukufona kwe-SIP akusasekelwa ingxenyekazi ye-Android.\nAma-akhawunti akho e-SIP akhona <xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g> ahanjisiwe.\nSicela uqinisekise isethingi le-akhawunti yakho lokufona elizenzakalelayo."</string>
+ <string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Iya kumasethingi"</string>
<string name="data_usage_title" msgid="8438592133893837464">"Ukusetshenziswa kwedatha yohlelo lokusebenza"</string>
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_1">%1$s</xliff:g> kusetshenziswe idatha yeselula engu-<xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Okuthuthukisiwe"</string>
@@ -504,6 +515,7 @@
<string name="puk2_requested" msgid="6992374450720307514">"Iphasiwedi ayilungile. I-SIM manje ikhiyiwe. Faka i-PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"I-PUK2 ivinjiwe unaphakade."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Unemizamo engu-<xliff:g id="NUMBER">%d</xliff:g> esele."</string>
+ <string name="puk2_locked" msgid="6497760825455461057">"I-PUK2 ikhiyiwe. Xhumana nomhlinzeki wesevisi ukuze uvule."</string>
<string name="pin2_unblocked" msgid="4481107908727789303">"I-PIN2 ayisavinjiwe"</string>
<string name="pin2_error_exception" msgid="8116103864600823641">"Iphutha lenethiwekhi noma lekhadi le-SIM"</string>
<string name="doneButton" msgid="7371209609238460207">"Kwenziwe"</string>
@@ -525,6 +537,7 @@
<string name="incall_error_power_off" product="watch" msgid="7191184639454113633">"Vula inethiwekhi yeselula, vala imodi yendiza noma vala imodi yokulondoloza ibhethri ukuze wenze ikholi."</string>
<string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Vala imodi yendiza ukuze wenze ikholi."</string>
<string name="incall_error_power_off_wfc" msgid="9125661184694727052">"Vala imodi yendiza noma xhumeka kunethiwekhi engenantambo ukuze wenze ikholi."</string>
+ <string name="incall_error_power_off_thermal" product="default" msgid="8695809601655300168"><b>"Ifoni ishisa kakhulu"</b>\n\n"Ayikwazi ukwenza le kholi. Zama futhi lapho ifoni iphola.\n\nUsengakwazi ukwenza amakholi aphuthumayo."</string>
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Phuma kwimodi yokushayela emuva yesiko esiphuthumayo ukuze wenze ikholi yemodi engaphuthumi."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Ayibhalisiwe kwinethiwekhi."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Inethiwekhi yefoni ayitholakali"</string>
@@ -867,7 +880,6 @@
<string name="radio_info_dds" msgid="1122593144425697126">"I-SubId ye-SIM yedatha yokuzenzakalela:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Umkhawulokudonsa we-DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Umkhawulokudonsa we-UL (kbps):"</string>
- <string name="radio_info_signal_location_label" msgid="6188435197086550049">"Ulwazi lwendawo lweseli (kwehlisiwe):"</string>
<string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Ukulungiselelwa okuphathekayo kwesiteshi se-LTE:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Isilinganiso sokuqalisa kabusha solwazi lweseli:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Ulwazi lwesilinganiso seseli:"</string>
@@ -912,4 +924,7 @@
<string name="phone_info_label" product="default" msgid="1784175881556791433">"Ulwazi lwefoni"</string>
<string name="carrier_provisioning" msgid="2668065041869578376">"Ulwazi lokuhlinzeka lenkampani yenethiwekhi"</string>
<string name="trigger_carrier_provisioning" msgid="1301829588620638234">"Cupha ukunikezwa kwenkampani yenethiwekhi"</string>
+ <string name="call_quality_notification_bluetooth_details" msgid="8348950331707346711">"Isignali yakho ye-bluetooth ayiqinile. Zama ukushintshela kusipikhasefoni."</string>
+ <string name="call_quality_notification_name" msgid="3476828289553948830">"Isaziso Sekhwalithi"</string>
+ <string name="notification_channel_sip_account" msgid="1261816025156179637">"Ama-akhawunti we-SIP ehlisiwe"</string>
</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 7e71068..a296254 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -211,9 +211,6 @@
CarrierConfigManager.KEY_CARRIER_VVM_PACKAGE_NAME_STRING does not handle it -->
<string name="system_visual_voicemail_client" translatable="false"/>
- <!-- Flag to enable VVM3 visual voicemail. VVM3 is used by Verizon Wireless. -->
- <bool name="vvm3_enabled">false</bool>
-
<!-- Flag indicating whether to allow pstn phone accounts [DO NOT TRANSLATE] -->
<bool name="config_pstn_phone_accounts_enabled">true</bool>
@@ -299,4 +296,29 @@
<item>250</item>
<item>350</item>
</integer-array>
+
+ <!-- String indicating the package name of the device GbaService implementation. -->
+ <string name="config_gba_package" translatable="false"/>
+ <!-- The interval to release/unbind GbaService after the authentication request
+ by millisecond. -1 - no release, 0 - release immediately,
+ positive n - release in n milliseconds -->
+ <integer name="config_gba_release_time">0</integer>
+
+ <!-- Whether or not to support RCS User Capability Exchange -->
+ <bool name="config_rcs_user_capability_exchange_enabled">true</bool>
+
+ <!-- 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>
+
+ <!-- The package names which can request thermal mitigation. -->
+ <string-array name="thermal_mitigation_allowlisted_packages" translatable="false">
+ </string-array>
+
+ <!-- The package name of the app which hosts the
+ {@link TelecomManager#ACTION_SHOW_CALL_SETTINGS} settings screen. -->
+ <string name="call_settings_package_name">com.android.phone</string>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6509f13..2077530 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -107,6 +107,8 @@
<!-- network depersonalization -->
<!-- Label text for PIN entry widget on SIM Network Depersonalization panel -->
<string name="label_ndp">SIM network unlock PIN</string>
+ <!-- Label text for Operator displayName on SIM Network Depersonalization panel -->
+ <string name="label_phoneid">SIM locked for operator</string>
<!-- Button label on SIM Network Depersonalization panel -->
<string name="sim_ndp_unlock_text">Unlock</string>
<!-- Button label on SIM Network Depersonalization panel -->
@@ -273,6 +275,15 @@
<string name="sum_cfnrc_disabled">Off</string>
<!-- Error message displayed after failing to disable forwarding calls when the phone is unreachable -->
<string name="disable_cfnrc_forbidden">Your carrier doesn\'t support disabling call forwarding when your phone is unreachable.</string>
+ <string name="registration_cf_forbidden">Your carrier doesn\'t support call forwarding.</string>
+
+ <!-- Cdma Call waiting settings screen, setting option name -->
+ <string name="cdma_call_waiting">Turn on call waiting?</string>
+ <string name="enable_cdma_call_waiting_setting">During a call, you\'ll be notified about incoming calls</string>
+ <string name="enable_cdma_cw">Turn on</string>
+ <string name="disable_cdma_cw">Cancel</string>
+ <string name="cdma_call_waiting_in_ims_on">CDMA Call Waiting under IMS On</string>
+ <string name="cdma_call_waiting_in_ims_off">CDMA Call Waiting under IMS Off</string>
<!-- Title of the progress dialog displayed while updating Call settings -->
<string name="updating_title">Call settings</string>
@@ -633,6 +644,13 @@
<string name="limited_sim_function_with_phone_num_notification_message"><xliff:g id="carrier_name">%1$s</xliff:g> calls and data services may be blocked while using <xliff:g id="phone_number">%2$s</xliff:g>.</string>
<!-- Notification message for limited sim function during dual sim [CHAR LIMIT=80]-->
<string name="limited_sim_function_notification_message"><xliff:g id="carrier_name">%1$s</xliff:g> calls and data services may be blocked while using another SIM.</string>
+ <!-- Notification title for SIP accounts removed -->
+ <string name="sip_accounts_removed_notification_title">Deprecated SIP accounts found and removed</string>
+ <!-- Notification message for SIP accoutns removed -->
+ <string name="sip_accounts_removed_notification_message">
+ SIP calling is no longer supported by Android platform.\nYour existing SIP accounts <xliff:g id="removed_sip_accounts">%s</xliff:g> have been removed.\nPlease confirm your default calling account setting.
+ </string>
+ <string name="sip_accounts_removed_notification_action">Go to settings</string>
<!-- Mobile network settings screen, data usage setting check box name -->
<string name="data_usage_title">App data usage</string>
<!-- Summary about how much data has been used in a date range [CHAR LIMIT=100] -->
@@ -1114,6 +1132,8 @@
<string name="puk2_blocked">PUK2 is permanently blocked.</string>
<!-- SIM PIN2 screen: error message -->
<string name="pin2_attempts">\nYou have <xliff:g id="number">%d</xliff:g> remaining attempts.</string>
+ <!-- SIM PIN2 screen: error message displayed in a dialog -->
+ <string name="puk2_locked">PUK2 locked. Contact service provider to unlock.</string>
<!-- SIM PIN screen: status message displayed in a popup (toast) -->
<string name="pin2_unblocked">PIN2 no longer blocked</string>
<!-- SIM PIN screen: error message shown in dialog when there is a network or sim error.
@@ -1175,6 +1195,8 @@
<string name="incall_error_power_off" product="default">Turn off airplane mode to make a call.</string>
<!-- In-call screen: call failure message displayed in an error dialog when in airplane mode, WFC is enabled, not wifi-only, and not connected to wireless networks. [CHAR_LIMIT=NONE] -->
<string name="incall_error_power_off_wfc">Turn off airplane mode or connect to a wireless network to make a call.</string>
+ <!-- In-call screen: call failure message displayed in an error dialog when radio off due to thermal mitigation. [CHAR_LIMIT=NONE] -->
+ <string name="incall_error_power_off_thermal" product="default"><b>Phone too hot</b>\n\nCan\'t complete this call. Try again when your phone cools down.\n\nYou can still make emergency calls.</string>
<!-- In-call screen: call failure message displayed when the user attempts to make a non-emergency call while the phone is in emergency callback mode. -->
<string name="incall_error_ecm_emergency_only">Exit emergency callback mode to make a non-emergency call.</string>
<!-- In-call screen: call failure message displayed in an error dialog.
@@ -2068,8 +2090,6 @@
<!-- Radio Info screen. Label for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="radio_info_ul_kbps">UL Bandwidth (kbps):</string>
<!-- Radio Info screen. Label for a status item. Used for diagnostic info screens, precise translation isn't needed -->
- <string name="radio_info_signal_location_label">Cell Location Info (deprecated):</string>
- <!-- Radio Info screen. Label for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="radio_info_phy_chan_config">LTE Physical Channel Configuration:</string>
<!-- Radio Info screen. Label for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="radio_info_cell_info_refresh_rate">Cell Info Refresh Rate:</string>
@@ -2141,6 +2161,8 @@
<string name="radio_info_nr_state">NR State:</string>
<!-- Radio Info screen. Label for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="radio_info_nr_frequency">NR Frequency:</string>
+ <!-- Radio Info screen. Label for a status item. Used for diagnostic info screens, precise translation isn't needed -->
+ <string name="radio_info_network_slicing_config" translatable="false">Network slicing config:</string>
<!-- Band Mode Selection -->
<!-- Band mode screen. Title of activity. -->
@@ -2164,4 +2186,14 @@
<!-- Trigger Carrier Provisioning [CHAR LIMIT=NONE] -->
<string name="trigger_carrier_provisioning">Trigger Carrier Provisioning</string>
+ <!-- details of the message popped up when there is
+ bad call quality caused by bluetooth connection-->
+ <string name="call_quality_notification_bluetooth_details">
+ Your bluetooth signal is weak. Try switching to speakerphone.</string>
+ <!-- name of the notification that pops up during
+ a phone call when there is bad call quality -->
+ <string name="call_quality_notification_name">Call Quality Notification</string>
+ <!-- Telephony notification channel name for a channel containing SIP accounts removed
+ notificatios -->
+ <string name="notification_channel_sip_account">Deprecated SIP accounts</string>
</resources>
diff --git a/res/xml/carrier_ss_string.xml b/res/xml/carrier_ss_string.xml
index 29de13a..b7771cd 100644
--- a/res/xml/carrier_ss_string.xml
+++ b/res/xml/carrier_ss_string.xml
@@ -146,4 +146,32 @@
<entry key="status_code" definition="ok">7</entry>
</command_result>
</feature>
+ <feature name="callwaiting">
+ <command name="query"><!--For example: *#102#-->
+ <service_code>102</service_code>
+ <action_code>*#</action_code>
+ <response_format number ="1"><!--For example: 120*4#-->
+ <entry position="4" key="status_code"/>
+ </response_format>
+ </command>
+ <command name="activate"><!--For example: *102#-->
+ <service_code>102</service_code>
+ <action_code>*</action_code>
+ <response_format number="1"><!--For example: 102*5#-->
+ <entry position="4" key="status_code"/>
+ </response_format>
+ </command>
+ <command name="deactivate"><!--For example: #102#-->
+ <service_code>102</service_code>
+ <action_code>#</action_code>
+ <response_format number="1"><!--For example: 148*4#-->
+ <entry position="4" key="status_code"/>
+ </response_format>
+ </command>
+ <command_result number="3">
+ <entry key="status_code" definition="activate">5</entry>
+ <entry key="status_code" definition="deactivate">4</entry>
+ <entry key="status_code" definition="unregister">0</entry>
+ </command_result>
+ </feature>
</resources>
diff --git a/res/xml/carrier_ss_string_850.xml b/res/xml/carrier_ss_string_850.xml
index 01eeee5..ed31fae 100644
--- a/res/xml/carrier_ss_string_850.xml
+++ b/res/xml/carrier_ss_string_850.xml
@@ -94,4 +94,32 @@
<entry key="status_code" definition="ok">7</entry>
</command_result>
</feature>
+ <feature name="callwaiting">
+ <command name="query"><!--For example: *#102#-->
+ <service_code>102</service_code>
+ <action_code>*#</action_code>
+ <response_format number ="1"><!--For example: 120*4#-->
+ <entry position="4" key="status_code"/>
+ </response_format>
+ </command>
+ <command name="activate"><!--For example: *102#-->
+ <service_code>102</service_code>
+ <action_code>*</action_code>
+ <response_format number="1"><!--For example: 102*5#-->
+ <entry position="4" key="status_code"/>
+ </response_format>
+ </command>
+ <command name="deactivate"><!--For example: #102#-->
+ <service_code>102</service_code>
+ <action_code>#</action_code>
+ <response_format number="1"><!--For example: 148*4#-->
+ <entry position="4" key="status_code"/>
+ </response_format>
+ </command>
+ <command_result number="3">
+ <entry key="status_code" definition="activate">5</entry>
+ <entry key="status_code" definition="deactivate">4</entry>
+ <entry key="status_code" definition="unregister">0</entry>
+ </command_result>
+ </feature>
</resources>
diff --git a/res/xml/cdma_call_privacy.xml b/res/xml/cdma_call_privacy.xml
index 1aeeefe..a16a504 100644
--- a/res/xml/cdma_call_privacy.xml
+++ b/res/xml/cdma_call_privacy.xml
@@ -7,4 +7,14 @@
android:title="@string/voice_privacy"
android:persistent="false"
android:summary="@string/voice_privacy_summary"/>
+
+ <PreferenceScreen
+ android:key="call_forwarding_key"
+ android:title="@string/labelCF"
+ android:persistent="false" />
+
+ <com.android.phone.CdmaCallWaitingPreference
+ android:key="call_waiting_key"
+ android:title="@string/labelCW"
+ android:persistent="false" />
</PreferenceScreen>
diff --git a/res/xml/phone_account_settings.xml b/res/xml/phone_account_settings.xml
index a243a65..8722761 100644
--- a/res/xml/phone_account_settings.xml
+++ b/res/xml/phone_account_settings.xml
@@ -55,34 +55,4 @@
</PreferenceCategory>
- <PreferenceCategory
- android:key="phone_accounts_sip_settings_category_key"
- android:title="@string/sip_settings"
- android:persistent="false">
-
- <PreferenceScreen
- android:title="@string/sip_accounts"
- android:persistent="false">
-
- <intent android:action="android.intent.action.MAIN"
- android:targetPackage="com.android.phone"
- android:targetClass="com.android.services.telephony.sip.SipSettings" />
-
- </PreferenceScreen>
-
- <ListPreference
- android:key="use_sip_calling_options_key"
- android:title="@string/sip_call_options_title"
- android:persistent="true"
- android:entries="@array/sip_call_options_entries"
- android:entryValues="@array/sip_call_options_values"/>
-
- <SwitchPreference
- android:key="sip_receive_calls_key"
- android:title="@string/sip_receive_calls"
- android:summary="@string/sip_receive_calls_summary"
- android:persistent="true"/>
-
- </PreferenceCategory>
-
</PreferenceScreen>
diff --git a/sip/res/values-as/strings.xml b/sip/res/values-as/strings.xml
index 76a0b5f..13043e3 100644
--- a/sip/res/values-as/strings.xml
+++ b/sip/res/values-as/strings.xml
@@ -20,7 +20,7 @@
<string name="sip_accounts" msgid="7297896885665783239">"SIP একাউণ্টসমূহ"</string>
<string name="sip_accounts_title" msgid="3061686404598143943">"একাউণ্টসমূহ"</string>
<string name="sip_receive_calls" msgid="3403644006618369349">"অন্তৰ্গামী কল লাভ কৰি থকা হৈছে"</string>
- <string name="sip_receive_calls_summary" msgid="5306603671778761443">"বেটাৰি অধিক ব্যৱহাৰ কৰে"</string>
+ <string name="sip_receive_calls_summary" msgid="5306603671778761443">"বেটাৰী অধিক ব্যৱহাৰ কৰে"</string>
<string name="sip_call_options_title" msgid="5027066677561068192">"SIP কলিং ব্যৱহাৰ কৰক"</string>
<string name="sip_call_options_wifi_only_title" msgid="6663105297927456484">"SIP কলিং ব্যৱহাৰ কৰক (কেৱল ৱাই-ফাই)"</string>
<string name="sip_call_options_entry_1" msgid="4722647332760934261">"ডেটা নেটৱৰ্ক উপলব্ধ থাকোঁতে সকলো কলৰ বাবে"</string>
diff --git a/sip/res/values-te/strings.xml b/sip/res/values-te/strings.xml
index b646f9d..b821a60 100644
--- a/sip/res/values-te/strings.xml
+++ b/sip/res/values-te/strings.xml
@@ -36,7 +36,7 @@
<string name="alert_dialog_close" msgid="1734746505531110706">"ప్రొఫైల్ను మూసివేయండి"</string>
<string name="alert_dialog_ok" msgid="7806760618798687406">"సరే"</string>
<string name="close_profile" msgid="3756064641769751774">"మూసివేయి"</string>
- <string name="registration_status_checking_status" msgid="884179594507591180">"స్థితిని తనిఖీ చేస్తోంది..."</string>
+ <string name="registration_status_checking_status" msgid="884179594507591180">"స్టేటస్ని తనిఖీ చేస్తోంది..."</string>
<string name="registration_status_registering" msgid="7986331597809521791">"నమోదు చేస్తోంది…"</string>
<string name="registration_status_still_trying" msgid="7178623685868766282">"ఇప్పటికీ ప్రయత్నిస్తోంది..."</string>
<string name="registration_status_not_receiving" msgid="3873074208531938401">"కాల్స్ను స్వీకరించడం లేదు."</string>
diff --git a/sip/src/com/android/services/telephony/sip/SipAccountRegistry.java b/sip/src/com/android/services/telephony/sip/SipAccountRegistry.java
index 1cf7f4b..6b34f00 100644
--- a/sip/src/com/android/services/telephony/sip/SipAccountRegistry.java
+++ b/sip/src/com/android/services/telephony/sip/SipAccountRegistry.java
@@ -16,8 +16,12 @@
package com.android.services.telephony.sip;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.Context;
-import android.net.sip.SipException;
+import android.content.Intent;
import android.net.sip.SipManager;
import android.net.sip.SipProfile;
import android.telecom.PhoneAccount;
@@ -25,9 +29,13 @@
import android.telecom.TelecomManager;
import android.util.Log;
+import com.android.phone.R;
+
+import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.stream.Collectors;
/**
* Manages the {@link PhoneAccount} entries for SIP calling.
@@ -45,41 +53,6 @@
}
/**
- * Starts the SIP service associated with the SIP profile.
- *
- * @param sipManager The SIP manager.
- * @param context The context.
- * @param isReceivingCalls {@code True} if the sip service is being started to make and
- * receive calls. {@code False} if the sip service is being started only for
- * outgoing calls.
- * @return {@code True} if the service started successfully.
- */
- boolean startSipService(SipManager sipManager, Context context, boolean isReceivingCalls) {
- if (VERBOSE) log("startSipService, profile: " + mProfile);
- try {
- // Stop the Sip service for the profile if it is already running. This is important
- // if we are changing the state of the "receive calls" option.
- sipManager.close(mProfile.getUriString());
-
- // Start the sip service for the profile.
- if (isReceivingCalls) {
- sipManager.open(
- mProfile,
- SipUtil.createIncomingCallPendingIntent(context,
- mProfile.getProfileName()),
- null);
- } else {
- sipManager.open(mProfile);
- }
- return true;
- } catch (SipException e) {
- log("startSipService, profile: " + mProfile.getProfileName() +
- ", exception: " + e);
- }
- return false;
- }
-
- /**
* Stops the SIP service associated with the SIP profile. The {@code SipAccountRegistry} is
* informed when the service has been stopped via an intent which triggers
* {@link SipAccountRegistry#removeSipProfile(String)}.
@@ -102,9 +75,16 @@
private static final String PREFIX = "[SipAccountRegistry] ";
private static final boolean VERBOSE = false; /* STOP SHIP if true */
private static final SipAccountRegistry INSTANCE = new SipAccountRegistry();
+ private static final String NOTIFICATION_TAG = SipAccountRegistry.class.getSimpleName();
+ private static final int SIP_ACCOUNTS_REMOVED_NOTIFICATION_ID = 1;
+
+ private static final String CHANNEL_ID_SIP_ACCOUNTS_REMOVED = "sipAccountsRemoved";
private final List<AccountEntry> mAccounts = new CopyOnWriteArrayList<>();
+ private NotificationChannel mNotificationChannel;
+ private NotificationManager mNm;
+
private SipAccountRegistry() {}
public static SipAccountRegistry getInstance() {
@@ -115,8 +95,20 @@
* Sets up the Account registry and performs any upgrade operations before it is used.
*/
public void setup(Context context) {
+ setupNotificationChannel(context);
verifyAndPurgeInvalidPhoneAccounts(context);
- startSipProfilesAsync(context, (String) null, false);
+ startSipProfilesAsync(context);
+ }
+
+ private void setupNotificationChannel(Context context) {
+ mNotificationChannel = new NotificationChannel(
+ CHANNEL_ID_SIP_ACCOUNTS_REMOVED,
+ context.getText(R.string.notification_channel_sip_account),
+ NotificationManager.IMPORTANCE_HIGH);
+ mNm = context.getSystemService(NotificationManager.class);
+ if (mNm != null) {
+ mNm.createNotificationChannel(mNotificationChannel);
+ }
}
/**
@@ -149,8 +141,8 @@
* @param sipProfileName The name of the {@link SipProfile} to start, or {@code null} for all.
* @param enableProfile Sip account should be enabled
*/
- void startSipService(Context context, String sipProfileName, boolean enableProfile) {
- startSipProfilesAsync(context, sipProfileName, enableProfile);
+ void startSipService(Context context, String sipProfileName, boolean enabledProfile) {
+ startSipProfilesAsync(context);
}
/**
@@ -193,33 +185,20 @@
}
/**
- * Causes the SIP service to be restarted for all {@link SipProfile}s. For example, if the user
- * toggles the "receive calls" option for SIP, this method handles restarting the SIP services
- * in the new mode.
- *
- * @param context The context.
- */
- public void restartSipService(Context context) {
- startSipProfiles(context, null, false);
- }
-
- /**
* Performs an asynchronous call to
* {@link SipAccountRegistry#startSipProfiles(android.content.Context, String)}, starting the
* specified SIP profile and registering its {@link android.telecom.PhoneAccount}.
*
* @param context The context.
- * @param sipProfileName Name of the SIP profile.
- * @param enableProfile Sip account should be enabled.
*/
private void startSipProfilesAsync(
- final Context context, final String sipProfileName, final boolean enableProfile) {
+ final Context context) {
if (VERBOSE) log("startSipProfiles, start auto registration");
new Thread(new Runnable() {
@Override
public void run() {
- startSipProfiles(context, sipProfileName, enableProfile);
+ startSipProfiles(context);
}}
).start();
}
@@ -230,48 +209,55 @@
* register the associated SIP account.
*
* @param context The context.
- * @param sipProfileName A specific SIP profile Name to start, or {@code null} to start all.
- * @param enableProfile Sip account should be enabled.
*/
- private void startSipProfiles(Context context, String sipProfileName, boolean enableProfile) {
- final SipPreferences sipPreferences = new SipPreferences(context);
- boolean isReceivingCalls = sipPreferences.isReceivingCallsEnabled();
- TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
- SipManager sipManager = SipManager.newInstance(context);
+ private void startSipProfiles(Context context) {
SipProfileDb profileDb = new SipProfileDb(context);
List<SipProfile> sipProfileList = profileDb.retrieveSipProfileList();
- for (SipProfile profile : sipProfileList) {
- // Register a PhoneAccount for the profile and optionally enable the primary
- // profile.
- if (sipProfileName == null || sipProfileName.equals(profile.getProfileName())) {
- PhoneAccount phoneAccount = SipUtil.createPhoneAccount(context, profile);
- telecomManager.registerPhoneAccount(phoneAccount);
- if (enableProfile) {
- telecomManager.enablePhoneAccount(phoneAccount.getAccountHandle(), true);
+ // If there're SIP profiles existing in DB, display a notification and delete all these
+ // profiles.
+ if (!sipProfileList.isEmpty()) {
+ for (SipProfile profile : sipProfileList) {
+ stopSipService(context, profile.getProfileName());
+ removeSipProfile(profile.getProfileName());
+ try {
+ profileDb.deleteProfile(profile);
+ } catch (IOException e) {
+ // Ignore
}
- startSipServiceForProfile(profile, sipManager, context, isReceivingCalls);
}
+ sendSipAccountsRemovedNotification(context, sipProfileList);
}
}
- /**
- * Starts the SIP service for a sip profile and saves a new {@code AccountEntry} in the
- * registry.
- *
- * @param profile The {@link SipProfile} to start.
- * @param sipManager The SIP manager.
- * @param context The context.
- * @param isReceivingCalls {@code True} if the profile should be started such that it can
- * receive incoming calls.
- */
- private void startSipServiceForProfile(SipProfile profile, SipManager sipManager,
- Context context, boolean isReceivingCalls) {
- removeSipProfile(profile.getUriString());
+ private void sendSipAccountsRemovedNotification(Context context, List<SipProfile> profiles) {
+ String sipAccounts = profiles.stream().map(p -> p.getProfileName())
+ .collect(Collectors.joining(","));
- AccountEntry entry = new AccountEntry(profile);
- if (entry.startSipService(sipManager, context, isReceivingCalls)) {
- mAccounts.add(entry);
+ Intent intent = new Intent(TelecomManager.ACTION_CHANGE_PHONE_ACCOUNTS);
+ intent.setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent,
+ PendingIntent.FLAG_IMMUTABLE);
+
+ Notification.Action action = new Notification.Action.Builder(R.drawable.ic_sim_card,
+ context.getString(R.string.sip_accounts_removed_notification_action),
+ pendingIntent).build();
+ Notification.Builder builder = new Notification.Builder(context)
+ .setSmallIcon(R.drawable.ic_sim_card)
+ .setChannelId(CHANNEL_ID_SIP_ACCOUNTS_REMOVED)
+ .setContentTitle(context.getText(R.string.sip_accounts_removed_notification_title))
+ .setStyle(new Notification.BigTextStyle()
+ .bigText(context.getString(
+ R.string.sip_accounts_removed_notification_message,
+ sipAccounts)))
+ .setAutoCancel(true)
+ .addAction(action);
+ Notification notification = builder.build();
+ if (mNm != null) {
+ mNm.notify(NOTIFICATION_TAG, SIP_ACCOUNTS_REMOVED_NOTIFICATION_ID,
+ notification);
+ } else {
+ log("NotificationManager is null when send the notification of removed SIP accounts");
}
}
diff --git a/sip/src/com/android/services/telephony/sip/SipPhoneAccountSettingsActivity.java b/sip/src/com/android/services/telephony/sip/SipPhoneAccountSettingsActivity.java
deleted file mode 100644
index a6f6381..0000000
--- a/sip/src/com/android/services/telephony/sip/SipPhoneAccountSettingsActivity.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2014 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.services.telephony.sip;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.net.sip.SipProfile;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.telecom.PhoneAccountHandle;
-import android.telecom.TelecomManager;
-import android.util.Log;
-
-/**
- * This activity receives the standard telecom intent to open settings for a PhoneAccount. It
- * translates the incoming phone account to a SIP profile and opens the corresponding
- * PreferenceActivity for said profile.
- */
-public final class SipPhoneAccountSettingsActivity extends Activity {
- private static final String TAG = "SipSettingsActivity";
-
- /** ${inheritDoc} */
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- Intent intent = getIntent();
- Log.i(TAG, "" + intent);
- if (intent != null) {
- PhoneAccountHandle accountHandle = (PhoneAccountHandle)
- intent.getParcelableExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
- Log.i(TAG, "" + accountHandle);
-
- if (accountHandle != null) {
- SipProfileDb profileDb = new SipProfileDb(this);
- String profileName = SipUtil.getSipProfileNameFromPhoneAccount(accountHandle);
- SipProfile profile = profileDb.retrieveSipProfileFromName(profileName);
- if (profile != null) {
- Intent settingsIntent = new Intent(this, SipEditor.class);
- settingsIntent.putExtra(SipSettings.KEY_SIP_PROFILE, (Parcelable) profile);
- startActivity(settingsIntent);
- }
- }
- }
-
- finish();
- }
-}
diff --git a/sip/src/com/android/services/telephony/sip/SipUtil.java b/sip/src/com/android/services/telephony/sip/SipUtil.java
index 5c7409f..a5793d0 100644
--- a/sip/src/com/android/services/telephony/sip/SipUtil.java
+++ b/sip/src/com/android/services/telephony/sip/SipUtil.java
@@ -63,7 +63,9 @@
Intent intent = new Intent(context, SipIncomingCallReceiver.class);
intent.setAction(SipManager.ACTION_SIP_INCOMING_CALL);
intent.putExtra(EXTRA_PHONE_ACCOUNT, SipUtil.createAccountHandle(context, sipProfileName));
- return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ return PendingIntent.getBroadcast(context, 0, intent,
+ // Mutable because information associated with the call is passed back here.
+ PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
}
public static boolean isPhoneIdle(Context context) {
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index 54acaad..ec6ea2b 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -30,6 +30,9 @@
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
import android.os.PersistableBundle;
import android.os.UserManager;
import android.preference.Preference;
@@ -40,8 +43,8 @@
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.CarrierConfigManager;
-import android.telephony.PhoneStateListener;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.telephony.ims.ProvisioningManager;
import android.telephony.ims.feature.ImsFeature;
@@ -103,6 +106,7 @@
private ImsManager mImsMgr;
private SubscriptionInfoHelper mSubscriptionInfoHelper;
private TelecomManager mTelecomManager;
+ private TelephonyCallback mTelephonyCallback;
private SwitchPreference mButtonAutoRetry;
private PreferenceScreen mVoicemailSettingsScreen;
@@ -263,6 +267,7 @@
mSubscriptionInfoHelper.setActionBarTitle(
getActionBar(), getResourcesForSubId(), R.string.call_settings_with_label);
mTelecomManager = getSystemService(TelecomManager.class);
+ mTelephonyCallback = new CallFeaturesTelephonyCallback();
}
private void updateImsManager(Phone phone) {
@@ -279,13 +284,18 @@
private void listenPhoneState(boolean listen) {
TelephonyManager telephonyManager = getSystemService(TelephonyManager.class)
.createForSubscriptionId(mPhone.getSubId());
- telephonyManager.listen(mPhoneStateListener, listen
- ? PhoneStateListener.LISTEN_CALL_STATE : PhoneStateListener.LISTEN_NONE);
+ if (listen) {
+ telephonyManager.registerTelephonyCallback(
+ new HandlerExecutor(new Handler(Looper.getMainLooper())), mTelephonyCallback);
+ } else {
+ telephonyManager.unregisterTelephonyCallback(mTelephonyCallback);
+ }
}
- private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ private final class CallFeaturesTelephonyCallback extends TelephonyCallback implements
+ TelephonyCallback.CallStateListener {
@Override
- public void onCallStateChanged(int state, String incomingNumber) {
+ public void onCallStateChanged(int state) {
if (DBG) log("PhoneStateListener onCallStateChanged: state is " + state);
boolean isCallStateIdle = state == TelephonyManager.CALL_STATE_IDLE;
if (mEnableVideoCalling != null) {
@@ -295,7 +305,7 @@
mButtonWifiCalling.setEnabled(isCallStateIdle);
}
}
- };
+ }
private final ProvisioningManager.Callback mProvisioningCallback =
new ProvisioningManager.Callback() {
@@ -388,7 +398,8 @@
cdmaOptions.setIntent(mSubscriptionInfoHelper.getIntent(CdmaCallOptions.class));
gsmOptions.setIntent(mSubscriptionInfoHelper.getIntent(GsmUmtsCallOptions.class));
} else {
- prefSet.removePreference(cdmaOptions);
+ // Remove GSM options and repopulate the preferences in this Activity if phone type is
+ // GSM.
prefSet.removePreference(gsmOptions);
int phoneType = mPhone.getPhoneType();
@@ -396,16 +407,16 @@
prefSet.removePreference(fdnButton);
} else {
if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+ // For now, just keep CdmaCallOptions as one entity. Eventually CDMA should
+ // follow the same pattern as GSM below, where VP and Call forwarding are
+ // populated here and Call waiting is populated in another "Additional Settings"
+ // submenu for CDMA.
prefSet.removePreference(fdnButton);
-
- if (!carrierConfig.getBoolean(
- CarrierConfigManager.KEY_VOICE_PRIVACY_DISABLE_UI_BOOL)) {
- addPreferencesFromResource(R.xml.cdma_call_privacy);
- CdmaVoicePrivacySwitchPreference buttonVoicePrivacy =
- (CdmaVoicePrivacySwitchPreference) findPreference(BUTTON_VP_KEY);
- buttonVoicePrivacy.setPhone(mPhone);
- }
+ cdmaOptions.setSummary(null);
+ cdmaOptions.setTitle(R.string.additional_gsm_call_settings);
+ cdmaOptions.setIntent(mSubscriptionInfoHelper.getIntent(CdmaCallOptions.class));
} else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+ prefSet.removePreference(cdmaOptions);
if (mPhone.getIccCard() == null || !mPhone.getIccCard().getIccFdnAvailable()) {
prefSet.removePreference(fdnButton);
}
diff --git a/src/com/android/phone/CallForwardEditPreference.java b/src/com/android/phone/CallForwardEditPreference.java
index e8cf0d1..bf296f9 100644
--- a/src/com/android/phone/CallForwardEditPreference.java
+++ b/src/com/android/phone/CallForwardEditPreference.java
@@ -122,7 +122,27 @@
Log.d(LOG_TAG, "mButtonClicked=" + mButtonClicked + ", positiveResult=" + positiveResult);
// Ignore this event if the user clicked the cancel button, or if the dialog is dismissed
// without any button being pressed (back button press or click event outside the dialog).
- if (this.mButtonClicked != DialogInterface.BUTTON_NEGATIVE) {
+ if (isUnknownStatus() && this.mButtonClicked != DialogInterface.BUTTON_NEGATIVE) {
+ int action = (mButtonClicked == DialogInterface.BUTTON_POSITIVE) ?
+ CommandsInterface.CF_ACTION_REGISTRATION :
+ CommandsInterface.CF_ACTION_DISABLE;
+ final String number = (action == CommandsInterface.CF_ACTION_DISABLE) ?
+ "" : getPhoneNumber();
+
+ Log.d(LOG_TAG, "reason=" + reason + ", action=" + action + ", number=" + number);
+
+ // Display no forwarding number while we're waiting for confirmation.
+ setSummaryOff("");
+
+ mPhone.setCallForwardingOption(action,
+ reason,
+ number,
+ mServiceClass,
+ 0,
+ mHandler.obtainMessage(MyHandler.MESSAGE_SET_CF,
+ action,
+ MyHandler.MESSAGE_SET_CF));
+ } else if (this.mButtonClicked != DialogInterface.BUTTON_NEGATIVE) {
int action = (isToggled() || (mButtonClicked == DialogInterface.BUTTON_POSITIVE)) ?
CommandsInterface.CF_ACTION_REGISTRATION :
CommandsInterface.CF_ACTION_DISABLE;
@@ -194,6 +214,7 @@
Log.i(LOG_TAG, "handleGetCFResponse: Overridding CF number");
}
+ setUnknownStatus(callForwardInfo.status == CommandsInterface.SS_STATUS_UNKNOWN);
setToggled(callForwardInfo.status == 1);
boolean displayVoicemailNumber = false;
if (TextUtils.isEmpty(callForwardInfo.number)) {
@@ -343,6 +364,7 @@
AsyncResult ar = (AsyncResult) msg.obj;
callForwardInfo = null;
+ boolean summaryOff = false;
if (ar.exception != null) {
Log.d(LOG_TAG, "handleGetCFResponse: ar.exception=" + ar.exception);
if (ar.exception instanceof CommandException) {
@@ -362,7 +384,9 @@
CallForwardInfo cfInfoArray[] = (CallForwardInfo[]) ar.result;
if (cfInfoArray == null || cfInfoArray.length == 0) {
Log.d(LOG_TAG, "handleGetCFResponse: cfInfoArray.length==0");
- mTcpListener.onError(CallForwardEditPreference.this, RESPONSE_ERROR);
+ if (!(ar.userObj instanceof Throwable)) {
+ mTcpListener.onError(CallForwardEditPreference.this, RESPONSE_ERROR);
+ }
} else {
for (int i = 0, length = cfInfoArray.length; i < length; i++) {
Log.d(LOG_TAG, "handleGetCFResponse, cfInfoArray[" + i + "]="
@@ -372,6 +396,8 @@
CallForwardInfo info = cfInfoArray[i];
handleCallForwardResult(info);
+ summaryOff = (info.status == CommandsInterface.SS_STATUS_UNKNOWN);
+
if (ar.userObj instanceof Throwable) {
Log.d(LOG_TAG, "Skipped duplicated error dialog");
continue;
@@ -379,8 +405,7 @@
// Show an alert if we got a success response but
// with unexpected values.
- // Currently only handle the fail-to-disable case
- // since we haven't observed fail-to-enable.
+ // Handle the fail-to-disable case.
if (msg.arg2 == MESSAGE_SET_CF &&
msg.arg1 == CommandsInterface.CF_ACTION_DISABLE &&
info.status == 1) {
@@ -404,7 +429,21 @@
}
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setNeutralButton(R.string.close_dialog, null);
- builder.setTitle(getContext().getText(R.string.error_updating_title));
+ builder.setTitle(getContext()
+ .getText(R.string.error_updating_title));
+ builder.setMessage(s);
+ builder.setCancelable(true);
+ builder.create().show();
+ } else if (msg.arg2 == MESSAGE_SET_CF &&
+ msg.arg1 == CommandsInterface.CF_ACTION_REGISTRATION &&
+ info.status == 0) {
+ // Handle the fail-to-enable case.
+ CharSequence s = getContext()
+ .getText(R.string.registration_cf_forbidden);
+ AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+ builder.setNeutralButton(R.string.close_dialog, null);
+ builder.setTitle(getContext()
+ .getText(R.string.error_updating_title));
builder.setMessage(s);
builder.setCancelable(true);
builder.create().show();
@@ -417,7 +456,15 @@
// Now whether or not we got a new number, reset our enabled
// summary text since it may have been replaced by an empty
// placeholder.
- updateSummaryText();
+ // for CDMA, doesn't display summary.
+ if (summaryOff) {
+ setSummaryOff("");
+ } else {
+ // Now whether or not we got a new number, reset our enabled
+ // summary text since it may have been replaced by an empty
+ // placeholder.
+ updateSummaryText();
+ }
}
private void handleSetCFResponse(Message msg) {
@@ -426,6 +473,16 @@
Log.d(LOG_TAG, "handleSetCFResponse: ar.exception=" + ar.exception);
// setEnabled(false);
}
+
+ if (ar.result != null) {
+ int arr = (int)ar.result;
+ if (arr == CommandsInterface.SS_STATUS_UNKNOWN) {
+ Log.d(LOG_TAG, "handleSetCFResponse: no need to re get in CDMA");
+ mTcpListener.onFinished(CallForwardEditPreference.this, false);
+ return;
+ }
+ }
+
Log.d(LOG_TAG, "handleSetCFResponse: re get");
if (!mCallForwardByUssd) {
mPhone.getCallForwardingOption(reason, mServiceClass,
diff --git a/src/com/android/phone/CallNotifier.java b/src/com/android/phone/CallNotifier.java
index 6c18623..a8ef220 100644
--- a/src/com/android/phone/CallNotifier.java
+++ b/src/com/android/phone/CallNotifier.java
@@ -25,13 +25,14 @@
import android.media.ToneGenerator;
import android.os.AsyncResult;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Message;
import android.os.SystemProperties;
import android.telecom.TelecomManager;
-import android.telephony.PhoneStateListener;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.Log;
@@ -68,8 +69,8 @@
/** The singleton instance. */
private static CallNotifier sInstance;
- private Map<Integer, CallNotifierPhoneStateListener> mPhoneStateListeners =
- new ArrayMap<Integer, CallNotifierPhoneStateListener>();
+ private Map<Integer, CallNotifierTelephonyCallback> mTelephonyCallback =
+ new ArrayMap<Integer, CallNotifierTelephonyCallback>();
private Map<Integer, Boolean> mCFIStatus = new ArrayMap<Integer, Boolean>();
private Map<Integer, Boolean> mMWIStatus = new ArrayMap<Integer, Boolean>();
private PhoneGlobals mApplication;
@@ -566,7 +567,7 @@
// slot 0 first then slot 1. This is needed to ensure that when CFI or MWI is enabled for
// both slots, user always sees icon related to slot 0 on left side followed by that of
// slot 1.
- List<Integer> subIdList = new ArrayList<Integer>(mPhoneStateListeners.keySet());
+ List<Integer> subIdList = new ArrayList<Integer>(mTelephonyCallback.keySet());
Collections.sort(subIdList, new Comparator<Integer>() {
public int compare(Integer sub1, Integer sub2) {
int slotId1 = SubscriptionController.getInstance().getSlotIndex(sub1);
@@ -583,10 +584,9 @@
mApplication.notificationMgr.updateMwi(subId, false);
mApplication.notificationMgr.updateCfi(subId, false);
- // Listening to LISTEN_NONE removes the listener.
- mTelephonyManager.listen(
- mPhoneStateListeners.get(subId), PhoneStateListener.LISTEN_NONE);
- mPhoneStateListeners.remove(subId);
+ // Unregister the listener.
+ mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback.get(subId));
+ mTelephonyCallback.remove(subId);
} else {
Log.d(LOG_TAG, "updatePhoneStateListeners: update CF notifications.");
@@ -616,12 +616,11 @@
// Register new phone listeners for active subscriptions.
for (int i = 0; i < subInfos.size(); i++) {
int subId = subInfos.get(i).getSubscriptionId();
- if (!mPhoneStateListeners.containsKey(subId)) {
- CallNotifierPhoneStateListener listener = new CallNotifierPhoneStateListener(subId);
- mTelephonyManager.createForSubscriptionId(subId).listen(listener,
- PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
- | PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR);
- mPhoneStateListeners.put(subId, listener);
+ if (!mTelephonyCallback.containsKey(subId)) {
+ CallNotifierTelephonyCallback listener = new CallNotifierTelephonyCallback(subId);
+ mTelephonyManager.createForSubscriptionId(subId).registerTelephonyCallback(
+ new HandlerExecutor(this), listener);
+ mTelephonyCallback.put(subId, listener);
}
}
}
@@ -768,10 +767,13 @@
}
};
- private class CallNotifierPhoneStateListener extends PhoneStateListener {
+ private class CallNotifierTelephonyCallback extends TelephonyCallback implements
+ TelephonyCallback.MessageWaitingIndicatorListener,
+ TelephonyCallback.CallForwardingIndicatorListener {
+
private final int mSubId;
- CallNotifierPhoneStateListener(int subId) {
+ CallNotifierTelephonyCallback(int subId) {
super();
this.mSubId = subId;
}
@@ -790,7 +792,7 @@
mCFIStatus.put(this.mSubId, visible);
updatePhoneStateListeners(false, UPDATE_TYPE_CFI, this.mSubId);
}
- };
+ }
private void log(String msg) {
Log.d(LOG_TAG, msg);
diff --git a/src/com/android/phone/CallWaitingSwitchPreference.java b/src/com/android/phone/CallWaitingSwitchPreference.java
index 41442fe..01dd3b2 100644
--- a/src/com/android/phone/CallWaitingSwitchPreference.java
+++ b/src/com/android/phone/CallWaitingSwitchPreference.java
@@ -1,18 +1,21 @@
package com.android.phone;
+import static com.android.phone.TimeConsumingPreferenceActivity.EXCEPTION_ERROR;
import static com.android.phone.TimeConsumingPreferenceActivity.RESPONSE_ERROR;
import android.content.Context;
-import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.preference.SwitchPreference;
+import android.telephony.TelephonyManager;
import android.util.AttributeSet;
import android.util.Log;
-import com.android.internal.telephony.CommandException;
import com.android.internal.telephony.Phone;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
public class CallWaitingSwitchPreference extends SwitchPreference {
private static final String LOG_TAG = "CallWaitingSwitchPreference";
private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
@@ -20,6 +23,11 @@
private final MyHandler mHandler = new MyHandler();
private Phone mPhone;
private TimeConsumingPreferenceListener mTcpListener;
+ private Executor mExecutor;
+ private TelephonyManager mTelephonyManager;
+ private boolean mIsDuringUpdateProcess = false;
+ private int mUpdateStatus = TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR;
+ private int mQueryStatus = TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR;
public CallWaitingSwitchPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
@@ -37,101 +45,84 @@
TimeConsumingPreferenceListener listener, boolean skipReading, Phone phone) {
mPhone = phone;
mTcpListener = listener;
+ mExecutor = Executors.newSingleThreadExecutor();
+ mTelephonyManager = getContext().getSystemService(
+ TelephonyManager.class).createForSubscriptionId(phone.getSubId());
if (!skipReading) {
- mPhone.getCallWaiting(mHandler.obtainMessage(MyHandler.MESSAGE_GET_CALL_WAITING,
- MyHandler.MESSAGE_GET_CALL_WAITING, MyHandler.MESSAGE_GET_CALL_WAITING));
+ Log.d(LOG_TAG, "init getCallWaitingStatus");
+ mTelephonyManager.getCallWaitingStatus(mExecutor, this::queryStatusCallBack);
if (mTcpListener != null) {
mTcpListener.onStarted(this, true);
}
}
}
+ private void queryStatusCallBack(int result) {
+ Log.d(LOG_TAG, "queryStatusCallBack: CW state " + result);
+ mQueryStatus = result;
+ mHandler.sendMessage(mHandler.obtainMessage(MyHandler.MESSAGE_UPDATE_CALL_WAITING));
+ }
+
+ private void updateStatusCallBack(int result) {
+ Log.d(LOG_TAG, "updateStatusCallBack: CW state " + result + ", and re get");
+ mUpdateStatus = result;
+ mTelephonyManager.getCallWaitingStatus(mExecutor, this::queryStatusCallBack);
+ }
+
@Override
protected void onClick() {
super.onClick();
-
- mPhone.setCallWaiting(isChecked(),
- mHandler.obtainMessage(MyHandler.MESSAGE_SET_CALL_WAITING));
+ mTelephonyManager.setCallWaitingEnabled(isChecked(), mExecutor, this::updateStatusCallBack);
if (mTcpListener != null) {
+ mIsDuringUpdateProcess = true;
mTcpListener.onStarted(this, false);
}
}
private class MyHandler extends Handler {
- static final int MESSAGE_GET_CALL_WAITING = 0;
- static final int MESSAGE_SET_CALL_WAITING = 1;
+ static final int MESSAGE_UPDATE_CALL_WAITING = 0;
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MESSAGE_GET_CALL_WAITING:
- handleGetCallWaitingResponse(msg);
- break;
- case MESSAGE_SET_CALL_WAITING:
- handleSetCallWaitingResponse(msg);
+ case MESSAGE_UPDATE_CALL_WAITING:
+ updateUi();
break;
}
}
- private void handleGetCallWaitingResponse(Message msg) {
- AsyncResult ar = (AsyncResult) msg.obj;
-
+ private void updateUi() {
if (mTcpListener != null) {
- if (msg.arg2 == MESSAGE_SET_CALL_WAITING) {
+ if (mIsDuringUpdateProcess) {
mTcpListener.onFinished(CallWaitingSwitchPreference.this, false);
} else {
mTcpListener.onFinished(CallWaitingSwitchPreference.this, true);
}
}
- if (ar.exception instanceof CommandException) {
- if (DBG) {
- Log.d(LOG_TAG, "handleGetCallWaitingResponse: CommandException=" +
- ar.exception);
- }
+ if (mIsDuringUpdateProcess && (
+ mUpdateStatus == TelephonyManager.CALL_WAITING_STATUS_NOT_SUPPORTED
+ || mUpdateStatus
+ == TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR)) {
+ Log.d(LOG_TAG, "handleSetCallWaitingResponse: Exception");
if (mTcpListener != null) {
- mTcpListener.onException(CallWaitingSwitchPreference.this,
- (CommandException)ar.exception);
+ mTcpListener.onError(CallWaitingSwitchPreference.this, EXCEPTION_ERROR);
}
- } else if (ar.userObj instanceof Throwable || ar.exception != null) {
- // Still an error case but just not a CommandException.
- if (DBG) {
- Log.d(LOG_TAG, "handleGetCallWaitingResponse: Exception" + ar.exception);
- }
+ } else if (mQueryStatus == TelephonyManager.CALL_WAITING_STATUS_NOT_SUPPORTED
+ || mQueryStatus == TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR) {
+ Log.d(LOG_TAG, "handleGetCallWaitingResponse: Exception");
if (mTcpListener != null) {
mTcpListener.onError(CallWaitingSwitchPreference.this, RESPONSE_ERROR);
}
} else {
- if (DBG) {
- Log.d(LOG_TAG, "handleGetCallWaitingResponse: CW state successfully queried.");
- }
- int[] cwArray = (int[])ar.result;
- // If cwArray[0] is = 1, then cwArray[1] must follow,
- // with the TS 27.007 service class bit vector of services
- // for which call waiting is enabled.
- try {
- setChecked(((cwArray[0] == 1) && ((cwArray[1] & 0x01) == 0x01)));
- } catch (ArrayIndexOutOfBoundsException e) {
- Log.e(LOG_TAG, "handleGetCallWaitingResponse: improper result: err ="
- + e.getMessage());
+ if (mQueryStatus == TelephonyManager.CALL_WAITING_STATUS_ENABLED) {
+ setChecked(true);
+ } else {
+ setChecked(false);
}
}
- }
-
- private void handleSetCallWaitingResponse(Message msg) {
- AsyncResult ar = (AsyncResult) msg.obj;
-
- if (ar.exception != null) {
- if (DBG) {
- Log.d(LOG_TAG, "handleSetCallWaitingResponse: ar.exception=" + ar.exception);
- }
- //setEnabled(false);
- }
- if (DBG) Log.d(LOG_TAG, "handleSetCallWaitingResponse: re get");
-
- mPhone.getCallWaiting(obtainMessage(MESSAGE_GET_CALL_WAITING,
- MESSAGE_SET_CALL_WAITING, MESSAGE_SET_CALL_WAITING, ar.exception));
+ mIsDuringUpdateProcess = false;
}
}
}
diff --git a/src/com/android/phone/CallWaitingUssdResultReceiver.java b/src/com/android/phone/CallWaitingUssdResultReceiver.java
new file mode 100644
index 0000000..b9049e9
--- /dev/null
+++ b/src/com/android/phone/CallWaitingUssdResultReceiver.java
@@ -0,0 +1,112 @@
+/*
+ * 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.phone;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.telephony.TelephonyManager;
+import android.telephony.UssdResponse;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.IIntegerConsumer;
+
+import java.util.HashMap;
+
+/**
+ * Handling the call waiting USSD result.
+ */
+public class CallWaitingUssdResultReceiver extends ResultReceiver {
+ private static final String LOG_TAG = "CwUssdResultReceiver";
+
+ private IIntegerConsumer mCallback;
+ private CarrierXmlParser mCarrierXmlParser;
+ private CarrierXmlParser.SsEntry.SSAction mSsAction;
+
+ CallWaitingUssdResultReceiver(Handler handler, IIntegerConsumer callback,
+ CarrierXmlParser carrierXmlParser, CarrierXmlParser.SsEntry.SSAction action) {
+ super(handler);
+ mCallback = callback;
+ mCarrierXmlParser = carrierXmlParser;
+ mSsAction = action;
+ }
+
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle ussdResponse) {
+ log("USSD:" + resultCode);
+ checkNotNull(ussdResponse, "ussdResponse cannot be null.");
+ UssdResponse response = ussdResponse.getParcelable(
+ TelephonyManager.USSD_RESPONSE);
+
+ if (resultCode == TelephonyManager.USSD_RETURN_SUCCESS) {
+ int callWaitingStatus = getStatusFromResponse(response);
+ try {
+ mCallback.accept(callWaitingStatus);
+ } catch (RemoteException e) {
+ log("Fail to notify getCallWaitingStatus due to " + e);
+ }
+ } else {
+ try {
+ mCallback.accept(TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR);
+ } catch (RemoteException e) {
+ log("Fail to notify getCallWaitingStatus due to " + e);
+ }
+ }
+ }
+
+ private int getStatusFromResponse(UssdResponse response) {
+ int callWaitingStatus = TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR;
+
+ CarrierXmlParser.SsFeature callWaitingFeature = mCarrierXmlParser.getFeature(
+ CarrierXmlParser.FEATURE_CALL_WAITING);
+ if (callWaitingFeature == null) {
+ return callWaitingStatus;
+ }
+
+ HashMap<String, String> analysisResult = callWaitingFeature
+ .getResponseSet(mSsAction, response.getReturnMessage().toString());
+ if (analysisResult.get(CarrierXmlParser.TAG_RESPONSE_STATUS_ERROR) != null) {
+ return callWaitingStatus;
+ }
+
+ if (analysisResult != null && analysisResult.size() != 0) {
+ String tmpStatusStr = analysisResult.get(
+ CarrierXmlParser.TAG_RESPONSE_STATUS);
+
+ if (!TextUtils.isEmpty(tmpStatusStr)) {
+ if (tmpStatusStr.equals(
+ CarrierXmlParser.TAG_COMMAND_RESULT_DEFINITION_ACTIVATE)) {
+ callWaitingStatus =
+ TelephonyManager.CALL_WAITING_STATUS_ENABLED;
+ } else if (tmpStatusStr.equals(
+ CarrierXmlParser.TAG_COMMAND_RESULT_DEFINITION_DEACTIVATE)) {
+ callWaitingStatus =
+ TelephonyManager.CALL_WAITING_STATUS_DISABLED;
+ }
+ }
+ }
+ return callWaitingStatus;
+ }
+
+ private static void log(String msg) {
+ Log.d(LOG_TAG, msg);
+ }
+}
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index 37a0618..a463243 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -36,6 +36,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.Process;
@@ -55,6 +56,7 @@
import android.util.LocalLog;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.ICarrierConfigLoader;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.Phone;
@@ -166,6 +168,7 @@
private static final int EVENT_BIND_DEFAULT_FOR_NO_SIM_CONFIG_TIMEOUT = 21;
// Fetching config timed out from the default app for no SIM config.
private static final int EVENT_FETCH_DEFAULT_FOR_NO_SIM_CONFIG_TIMEOUT = 22;
+ // NOTE: any new EVENT_* values must be added to method eventToString().
private static final int BIND_TIMEOUT_MILLIS = 30000;
@@ -201,10 +204,14 @@
// 3. clearing config (e.g. due to sim removal)
// 4. encountering bind or IPC error
private class ConfigHandler extends Handler {
+ ConfigHandler(@NonNull Looper looper) {
+ super(looper);
+ }
+
@Override
public void handleMessage(Message msg) {
final int phoneId = msg.arg1;
- logdWithLocalLog("mHandler: " + msg.what + " phoneId: " + phoneId);
+ logdWithLocalLog("mHandler: " + eventToString(msg.what) + " phoneId: " + phoneId);
if (!SubscriptionManager.isValidPhoneId(phoneId)
&& msg.what != EVENT_MULTI_SIM_CONFIG_CHANGED) {
return;
@@ -247,6 +254,12 @@
}
case EVENT_DO_FETCH_DEFAULT: {
+ // Clear in-memory cache for carrier app config, so when carrier app gets
+ // uninstalled, no stale config is left.
+ if (mConfigFromCarrierApp[phoneId] != null
+ && getCarrierPackageForPhoneId(phoneId) == null) {
+ mConfigFromCarrierApp[phoneId] = null;
+ }
// Restore persistent override values.
PersistableBundle config = restoreConfigFromXml(
mPlatformCarrierConfigPackage, OVERRIDE_PACKAGE_ADDITION, phoneId);
@@ -275,9 +288,13 @@
phoneId,
EVENT_CONNECTED_TO_DEFAULT)) {
sendMessageDelayed(
- obtainMessage(EVENT_BIND_DEFAULT_TIMEOUT, phoneId, -1),
+ obtainMessage(EVENT_BIND_DEFAULT_TIMEOUT, phoneId, -1 /*arg2*/,
+ getMessageToken(phoneId)),
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.
@@ -290,7 +307,7 @@
}
case EVENT_CONNECTED_TO_DEFAULT: {
- removeMessages(EVENT_BIND_DEFAULT_TIMEOUT);
+ removeMessages(EVENT_BIND_DEFAULT_TIMEOUT, getMessageToken(phoneId));
final CarrierServiceConnection conn = (CarrierServiceConnection) msg.obj;
// If new service connection has been created, unbind.
if (mServiceConnection[phoneId] != conn || conn.service == null) {
@@ -304,12 +321,13 @@
@Override
public void onReceiveResult(int resultCode, Bundle resultData) {
unbindIfBound(mContext, conn, phoneId);
+ removeMessages(EVENT_FETCH_DEFAULT_TIMEOUT,
+ getMessageToken(phoneId));
// If new service connection has been created, this is stale.
if (mServiceConnection[phoneId] != conn) {
loge("Received response for stale request.");
return;
}
- removeMessages(EVENT_FETCH_DEFAULT_TIMEOUT);
if (resultCode == RESULT_ERROR || resultData == null) {
// On error, abort config fetching.
loge("Failed to get carrier config");
@@ -341,7 +359,8 @@
break; // So we don't set a timeout.
}
sendMessageDelayed(
- obtainMessage(EVENT_FETCH_DEFAULT_TIMEOUT, phoneId, -1),
+ obtainMessage(EVENT_FETCH_DEFAULT_TIMEOUT, phoneId, -1 /*arg2*/,
+ getMessageToken(phoneId)),
BIND_TIMEOUT_MILLIS);
break;
}
@@ -349,7 +368,7 @@
case EVENT_BIND_DEFAULT_TIMEOUT:
case EVENT_FETCH_DEFAULT_TIMEOUT: {
loge("Bind/fetch time out from " + mPlatformCarrierConfigPackage);
- removeMessages(EVENT_FETCH_DEFAULT_TIMEOUT);
+ removeMessages(EVENT_FETCH_DEFAULT_TIMEOUT, getMessageToken(phoneId));
// 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
// then config was cleared while we were waiting and we should not continue.
@@ -359,6 +378,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;
}
@@ -399,9 +420,13 @@
if (carrierPackageName != null && bindToConfigPackage(carrierPackageName,
phoneId, EVENT_CONNECTED_TO_CARRIER)) {
sendMessageDelayed(
- obtainMessage(EVENT_BIND_CARRIER_TIMEOUT, phoneId, -1),
+ obtainMessage(EVENT_BIND_CARRIER_TIMEOUT, phoneId, -1 /*arg2*/,
+ getMessageToken(phoneId)),
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");
@@ -412,7 +437,7 @@
}
case EVENT_CONNECTED_TO_CARRIER: {
- removeMessages(EVENT_BIND_CARRIER_TIMEOUT);
+ removeMessages(EVENT_BIND_CARRIER_TIMEOUT, getMessageToken(phoneId));
final CarrierServiceConnection conn = (CarrierServiceConnection) msg.obj;
// If new service connection has been created, unbind.
if (mServiceConnection[phoneId] != conn || conn.service == null) {
@@ -426,12 +451,13 @@
@Override
public void onReceiveResult(int resultCode, Bundle resultData) {
unbindIfBound(mContext, conn, phoneId);
+ removeMessages(EVENT_FETCH_CARRIER_TIMEOUT,
+ getMessageToken(phoneId));
// If new service connection has been created, this is stale.
if (mServiceConnection[phoneId] != conn) {
loge("Received response for stale request.");
return;
}
- removeMessages(EVENT_FETCH_CARRIER_TIMEOUT);
if (resultCode == RESULT_ERROR || resultData == null) {
// On error, abort config fetching.
loge("Failed to get carrier config from carrier app: "
@@ -444,7 +470,15 @@
resultData.getParcelable(KEY_CONFIG_BUNDLE);
saveConfigToXml(getCarrierPackageForPhoneId(phoneId), "",
phoneId, carrierId, config);
- mConfigFromCarrierApp[phoneId] = config;
+ if (config != null) {
+ mConfigFromCarrierApp[phoneId] = config;
+ } else {
+ logdWithLocalLog("Config from carrier app is null "
+ + "for phoneId " + phoneId);
+ // Put a stub bundle in place so that the rest of the logic
+ // continues smoothly.
+ mConfigFromCarrierApp[phoneId] = new PersistableBundle();
+ }
sendMessage(
obtainMessage(
EVENT_FETCH_CARRIER_DONE, phoneId, -1));
@@ -464,15 +498,17 @@
break; // So we don't set a timeout.
}
sendMessageDelayed(
- obtainMessage(EVENT_FETCH_CARRIER_TIMEOUT, phoneId, -1),
+ obtainMessage(EVENT_FETCH_CARRIER_TIMEOUT, phoneId, -1 /*arg2*/,
+ getMessageToken(phoneId)),
BIND_TIMEOUT_MILLIS);
break;
}
case EVENT_BIND_CARRIER_TIMEOUT:
case EVENT_FETCH_CARRIER_TIMEOUT: {
- loge("Bind/fetch from carrier app timeout");
- removeMessages(EVENT_FETCH_CARRIER_TIMEOUT);
+ loge("Bind/fetch from carrier app timeout, package="
+ + getCarrierPackageForPhoneId(phoneId));
+ removeMessages(EVENT_FETCH_CARRIER_TIMEOUT, getMessageToken(phoneId));
// 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
// then config was cleared while we were waiting and we should not continue.
@@ -482,6 +518,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;
}
@@ -643,11 +681,13 @@
* Constructs a CarrierConfigLoader, registers it as a service, and registers a broadcast
* receiver for relevant events.
*/
- private CarrierConfigLoader(Context context) {
+ @VisibleForTesting
+ /* package */ CarrierConfigLoader(Context context,
+ SubscriptionInfoUpdater subscriptionInfoUpdater, @NonNull Looper looper) {
mContext = context;
mPlatformCarrierConfigPackage =
mContext.getString(R.string.platform_carrier_config_package);
- mHandler = new ConfigHandler();
+ mHandler = new ConfigHandler(looper);
IntentFilter bootFilter = new IntentFilter();
bootFilter.addAction(Intent.ACTION_BOOT_COMPLETED);
@@ -674,11 +714,8 @@
mFromSystemUnlocked = new boolean[numPhones];
mServiceConnectionForNoSimConfig = new CarrierServiceConnection[numPhones];
mServiceBoundForNoSimConfig = new boolean[numPhones];
- // Make this service available through ServiceManager.
- TelephonyFrameworkInitializer
- .getTelephonyServiceManager().getCarrierConfigServiceRegisterer().register(this);
logd("CarrierConfigLoader has started");
- mSubscriptionInfoUpdater = PhoneFactory.getSubscriptionInfoUpdater();
+ mSubscriptionInfoUpdater = subscriptionInfoUpdater;
mHandler.sendEmptyMessage(EVENT_CHECK_SYSTEM_UPDATE);
}
@@ -687,11 +724,14 @@
*
* This is only done once, at startup, from {@link com.android.phone.PhoneApp#onCreate}.
*/
- /* package */
- static CarrierConfigLoader init(Context context) {
+ /* package */ static CarrierConfigLoader init(Context context) {
synchronized (CarrierConfigLoader.class) {
if (sInstance == null) {
- sInstance = new CarrierConfigLoader(context);
+ sInstance = new CarrierConfigLoader(context,
+ PhoneFactory.getSubscriptionInfoUpdater(), Looper.myLooper());
+ // Make this service available through ServiceManager.
+ TelephonyFrameworkInitializer.getTelephonyServiceManager()
+ .getCarrierConfigServiceRegisterer().register(sInstance);
} else {
Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance);
}
@@ -699,7 +739,8 @@
}
}
- private void clearConfigForPhone(int phoneId, boolean fetchNoSimConfig) {
+ @VisibleForTesting
+ /* package */ void clearConfigForPhone(int phoneId, boolean fetchNoSimConfig) {
/* Ignore clear configuration request if device is being shutdown. */
Phone phone = PhoneFactory.getPhone(phoneId);
if (phone != null) {
@@ -821,7 +862,8 @@
}
}
- private CarrierIdentifier getCarrierIdentifierForPhoneId(int phoneId) {
+ @VisibleForTesting
+ /* package */ CarrierIdentifier getCarrierIdentifierForPhoneId(int phoneId) {
String mcc = "";
String mnc = "";
String imsi = "";
@@ -848,10 +890,17 @@
}
/** 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)
+ List<String> carrierPackageNames;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ carrierPackageNames = TelephonyManager.from(mContext)
.getCarrierPackageNamesForIntentAndPhone(
- new Intent(CarrierService.CARRIER_SERVICE_INTERFACE), phoneId);
+ new Intent(CarrierService.CARRIER_SERVICE_INTERFACE), phoneId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
if (carrierPackageNames != null && carrierPackageNames.size() > 0) {
return carrierPackageNames.get(0);
} else {
@@ -976,12 +1025,14 @@
}
}
- private void saveConfigToXml(String packageName, @NonNull String extraString, int phoneId,
+ @VisibleForTesting
+ /* package */ void saveConfigToXml(String packageName, @NonNull String extraString, int phoneId,
CarrierIdentifier carrierId, PersistableBundle config) {
saveConfigToXml(packageName, extraString, phoneId, carrierId, config, false);
}
- private void saveNoSimConfigToXml(String packageName, PersistableBundle config) {
+ @VisibleForTesting
+ /* package */ void saveNoSimConfigToXml(String packageName, PersistableBundle config) {
saveConfigToXml(packageName, "", -1, null, config, true);
}
@@ -1130,12 +1181,6 @@
* have a saved config file to use instead.
*/
private void updateConfigForPhoneId(int phoneId) {
- // Clear in-memory cache for carrier app config, so when carrier app gets uninstalled, no
- // stale config is left.
- if (mConfigFromCarrierApp[phoneId] != null &&
- getCarrierPackageForPhoneId(phoneId) == null) {
- mConfigFromCarrierApp[phoneId] = null;
- }
mHandler.sendMessage(mHandler.obtainMessage(EVENT_DO_FETCH_DEFAULT, phoneId, -1));
}
@@ -1148,44 +1193,46 @@
@Override
@NonNull
- public PersistableBundle getConfigForSubId(int subId, String callingPackage) {
- return getConfigForSubIdWithFeature(subId, callingPackage, null);
+ public PersistableBundle getConfigForSubId(int subscriptionId, String callingPackage) {
+ return getConfigForSubIdWithFeature(subscriptionId, callingPackage, null);
}
@Override
@NonNull
- public PersistableBundle getConfigForSubIdWithFeature(int subId, String callingPackage,
+ public PersistableBundle getConfigForSubIdWithFeature(int subscriptionId, String callingPackage,
String callingFeatureId) {
- if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId, callingPackage,
- callingFeatureId, "getCarrierConfig")) {
+ if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subscriptionId,
+ callingPackage, callingFeatureId, "getCarrierConfig")) {
return new PersistableBundle();
}
- int phoneId = SubscriptionManager.getPhoneId(subId);
+ int phoneId = SubscriptionManager.getPhoneId(subscriptionId);
PersistableBundle retConfig = CarrierConfigManager.getDefaultConfig();
if (SubscriptionManager.isValidPhoneId(phoneId)) {
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);
@@ -1203,27 +1250,32 @@
int phoneId = SubscriptionManager.getPhoneId(subscriptionId);
if (!SubscriptionManager.isValidPhoneId(phoneId)) {
logd("Ignore invalid phoneId: " + phoneId + " for subId: " + subscriptionId);
- return;
+ throw new IllegalArgumentException(
+ "Invalid phoneId " + phoneId + " for subId " + subscriptionId);
}
- overrideConfig(mOverrideConfigs, phoneId, overrides);
+ // Post to run on handler thread on which all states should be confined.
+ mHandler.post(() -> {
+ overrideConfig(mOverrideConfigs, phoneId, overrides);
- if (persistent) {
- overrideConfig(mPersistentOverrideConfigs, phoneId, overrides);
+ if (persistent) {
+ overrideConfig(mPersistentOverrideConfigs, phoneId, overrides);
- if (overrides != null) {
- final CarrierIdentifier carrierId = getCarrierIdentifierForPhoneId(phoneId);
- saveConfigToXml(mPlatformCarrierConfigPackage, OVERRIDE_PACKAGE_ADDITION, phoneId,
- carrierId, mPersistentOverrideConfigs[phoneId]);
- } else {
- final String iccid = getIccIdForPhoneId(phoneId);
- final int cid = getSpecificCarrierIdForPhoneId(phoneId);
- String fileName = getFilenameForConfig(mPlatformCarrierConfigPackage,
- OVERRIDE_PACKAGE_ADDITION, iccid, cid);
- File fileToDelete = new File(mContext.getFilesDir(), fileName);
- fileToDelete.delete();
+ if (overrides != null) {
+ final CarrierIdentifier carrierId = getCarrierIdentifierForPhoneId(phoneId);
+ saveConfigToXml(mPlatformCarrierConfigPackage, OVERRIDE_PACKAGE_ADDITION,
+ phoneId,
+ carrierId, mPersistentOverrideConfigs[phoneId]);
+ } else {
+ final String iccid = getIccIdForPhoneId(phoneId);
+ final int cid = getSpecificCarrierIdForPhoneId(phoneId);
+ String fileName = getFilenameForConfig(mPlatformCarrierConfigPackage,
+ OVERRIDE_PACKAGE_ADDITION, iccid, cid);
+ File fileToDelete = new File(mContext.getFilesDir(), fileName);
+ fileToDelete.delete();
+ }
}
- }
- notifySubscriptionInfoUpdater(phoneId);
+ notifySubscriptionInfoUpdater(phoneId);
+ });
}
private void overrideConfig(@NonNull PersistableBundle[] currentOverrides, int phoneId,
@@ -1238,17 +1290,18 @@
}
@Override
- public void notifyConfigChangedForSubId(int subId) {
- int phoneId = SubscriptionManager.getPhoneId(subId);
- if (!SubscriptionManager.isValidPhoneId(phoneId)) {
- logd("Ignore invalid phoneId: " + phoneId + " for subId: " + subId);
- return;
- }
-
+ public void notifyConfigChangedForSubId(int subscriptionId) {
// Requires the calling app to be either a carrier privileged app for this subId or
// system privileged app with MODIFY_PHONE_STATE permission.
- TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mContext, subId,
- "Require carrier privileges or MODIFY_PHONE_STATE permission.");
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mContext,
+ subscriptionId, "Require carrier privileges or MODIFY_PHONE_STATE permission.");
+
+ int phoneId = SubscriptionManager.getPhoneId(subscriptionId);
+ if (!SubscriptionManager.isValidPhoneId(phoneId)) {
+ logd("Ignore invalid phoneId: " + phoneId + " for subId: " + subscriptionId);
+ throw new IllegalArgumentException(
+ "Invalid phoneId " + phoneId + " for subId " + subscriptionId);
+ }
// This method should block until deleting has completed, so that an error which prevents us
// from clearing the cache is passed back to the carrier app. With the files successfully
@@ -1265,7 +1318,7 @@
android.Manifest.permission.MODIFY_PHONE_STATE, null);
logdWithLocalLog("Update config for phoneId: " + phoneId + " simState: " + simState);
if (!SubscriptionManager.isValidPhoneId(phoneId)) {
- return;
+ throw new IllegalArgumentException("Invalid phoneId: " + phoneId);
}
// requires Java 7 for switch on string.
switch (simState) {
@@ -1291,6 +1344,31 @@
return mPlatformCarrierConfigPackage;
}
+ @VisibleForTesting
+ /* package */ Handler getHandler() {
+ return mHandler;
+ }
+
+ @VisibleForTesting
+ /* package */ PersistableBundle getConfigFromDefaultApp(int phoneId) {
+ return mConfigFromDefaultApp[phoneId];
+ }
+
+ @VisibleForTesting
+ /* package */ PersistableBundle getConfigFromCarrierApp(int phoneId) {
+ return mConfigFromCarrierApp[phoneId];
+ }
+
+ @VisibleForTesting
+ /* package */ PersistableBundle getNoSimConfig() {
+ return mNoSimConfig;
+ }
+
+ @VisibleForTesting
+ /* package */ PersistableBundle getOverrideConfig(int phoneId) {
+ return mOverrideConfigs[phoneId];
+ }
+
private void unbindIfBound(Context context, CarrierServiceConnection conn,
int phoneId) {
if (mServiceBound[phoneId]) {
@@ -1308,6 +1386,19 @@
}
/**
+ * Returns a boxed Integer object for phoneId, services as message token to distinguish messages
+ * with same code when calling {@link Handler#removeMessages(int, Object)}.
+ */
+ private Integer getMessageToken(int phoneId) {
+ if (phoneId < -128 || phoneId > 127) {
+ throw new IllegalArgumentException("phoneId should be in range [-128, 127], inclusive");
+ }
+ // Integer#valueOf guarantees the integers within [-128, 127] are cached and thus memory
+ // comparison (==) returns true for the same integer.
+ return Integer.valueOf(phoneId);
+ }
+
+ /**
* If {@code args} contains {@link #DUMP_ARG_REQUESTING_PACKAGE} and a following package name,
* we'll also call {@link IBinder#dump} on the default carrier service (if bound) and the
* specified carrier service (if bound). Typically, this is done for connectivity bug reports
@@ -1577,6 +1668,56 @@
}
}
+ // Get readable string for the message code supported in this class.
+ private static String eventToString(int code) {
+ switch (code) {
+ case EVENT_CLEAR_CONFIG:
+ return "EVENT_CLEAR_CONFIG";
+ case EVENT_CONNECTED_TO_DEFAULT:
+ return "EVENT_CONNECTED_TO_DEFAULT";
+ case EVENT_CONNECTED_TO_CARRIER:
+ return "EVENT_CONNECTED_TO_CARRIER";
+ case EVENT_FETCH_DEFAULT_DONE:
+ return "EVENT_FETCH_DEFAULT_DONE";
+ case EVENT_FETCH_CARRIER_DONE:
+ return "EVENT_FETCH_CARRIER_DONE";
+ case EVENT_DO_FETCH_DEFAULT:
+ return "EVENT_DO_FETCH_DEFAULT";
+ case EVENT_DO_FETCH_CARRIER:
+ return "EVENT_DO_FETCH_CARRIER";
+ case EVENT_PACKAGE_CHANGED:
+ return "EVENT_PACKAGE_CHANGED";
+ case EVENT_BIND_DEFAULT_TIMEOUT:
+ return "EVENT_BIND_DEFAULT_TIMEOUT";
+ case EVENT_BIND_CARRIER_TIMEOUT:
+ return "EVENT_BIND_CARRIER_TIMEOUT";
+ case EVENT_CHECK_SYSTEM_UPDATE:
+ return "EVENT_CHECK_SYSTEM_UPDATE";
+ case EVENT_SYSTEM_UNLOCKED:
+ return "EVENT_SYSTEM_UNLOCKED";
+ case EVENT_FETCH_DEFAULT_TIMEOUT:
+ return "EVENT_FETCH_DEFAULT_TIMEOUT";
+ case EVENT_FETCH_CARRIER_TIMEOUT:
+ return "EVENT_FETCH_CARRIER_TIMEOUT";
+ case EVENT_SUBSCRIPTION_INFO_UPDATED:
+ return "EVENT_SUBSCRIPTION_INFO_UPDATED";
+ case EVENT_MULTI_SIM_CONFIG_CHANGED:
+ return "EVENT_MULTI_SIM_CONFIG_CHANGED";
+ case EVENT_DO_FETCH_DEFAULT_FOR_NO_SIM_CONFIG:
+ return "EVENT_DO_FETCH_DEFAULT_FOR_NO_SIM_CONFIG";
+ case EVENT_FETCH_DEFAULT_FOR_NO_SIM_CONFIG_DONE:
+ return "EVENT_FETCH_DEFAULT_FOR_NO_SIM_CONFIG_DONE";
+ case EVENT_CONNECTED_TO_DEFAULT_FOR_NO_SIM_CONFIG:
+ return "EVENT_CONNECTED_TO_DEFAULT_FOR_NO_SIM_CONFIG";
+ case EVENT_BIND_DEFAULT_FOR_NO_SIM_CONFIG_TIMEOUT:
+ return "EVENT_BIND_DEFAULT_FOR_NO_SIM_CONFIG_TIMEOUT";
+ case EVENT_FETCH_DEFAULT_FOR_NO_SIM_CONFIG_TIMEOUT:
+ return "EVENT_FETCH_DEFAULT_FOR_NO_SIM_CONFIG_TIMEOUT";
+ default:
+ return "UNKNOWN(" + code + ")";
+ }
+ }
+
private void logd(String msg) {
Log.d(LOG_TAG, msg);
}
diff --git a/src/com/android/phone/CarrierXmlParser.java b/src/com/android/phone/CarrierXmlParser.java
index 18602c9..6e01b43 100644
--- a/src/com/android/phone/CarrierXmlParser.java
+++ b/src/com/android/phone/CarrierXmlParser.java
@@ -67,6 +67,7 @@
// To define feature's item name in xml
public static final String FEATURE_CALL_FORWARDING = "callforwarding";
+ public static final String FEATURE_CALL_WAITING = "callwaiting";
public static final String FEATURE_CALLER_ID = "callerid";
// COMMAND_NAME is xml's command name.
diff --git a/src/com/android/phone/CdmaCallForwardOptions.java b/src/com/android/phone/CdmaCallForwardOptions.java
new file mode 100644
index 0000000..d70e709
--- /dev/null
+++ b/src/com/android/phone/CdmaCallForwardOptions.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2020 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.phone;
+
+import android.app.ActionBar;
+import android.content.ContentProvider;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.os.Process;
+import android.os.UserHandle;
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+import android.telephony.CarrierConfigManager;
+import android.util.Log;
+import android.view.MenuItem;
+
+import com.android.internal.telephony.CallForwardInfo;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Phone;
+
+import java.util.ArrayList;
+
+public class CdmaCallForwardOptions extends TimeConsumingPreferenceActivity {
+ private static final String LOG_TAG = "CdmaCallForwardOptions";
+
+ private static final String NUM_PROJECTION[] = {
+ android.provider.ContactsContract.CommonDataKinds.Phone.NUMBER
+ };
+
+ private static final String BUTTON_CFU_KEY = "button_cfu_key";
+ private static final String BUTTON_CFB_KEY = "button_cfb_key";
+ private static final String BUTTON_CFNRY_KEY = "button_cfnry_key";
+ private static final String BUTTON_CFNRC_KEY = "button_cfnrc_key";
+
+ private static final String KEY_TOGGLE = "toggle";
+ private static final String KEY_STATUS = "status";
+ private static final String KEY_NUMBER = "number";
+ private static final String KEY_ENABLE = "enable";
+
+ private CallForwardEditPreference mButtonCFU;
+ private CallForwardEditPreference mButtonCFB;
+ private CallForwardEditPreference mButtonCFNRy;
+ private CallForwardEditPreference mButtonCFNRc;
+
+ private final ArrayList<CallForwardEditPreference> mPreferences =
+ new ArrayList<CallForwardEditPreference> ();
+ private int mInitIndex= 0;
+
+ private boolean mFirstResume;
+ private Bundle mIcicle;
+ private Phone mPhone;
+ private SubscriptionInfoHelper mSubscriptionInfoHelper;
+ private boolean mReplaceInvalidCFNumbers;
+ private boolean mCallForwardByUssd;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ addPreferencesFromResource(R.xml.callforward_options);
+
+ mSubscriptionInfoHelper = new SubscriptionInfoHelper(this, getIntent());
+ mSubscriptionInfoHelper.setActionBarTitle(
+ getActionBar(), getResources(), R.string.call_forwarding_settings_with_label);
+ mPhone = mSubscriptionInfoHelper.getPhone();
+
+ PersistableBundle b = null;
+ boolean supportCFNRc = true;
+ if (mSubscriptionInfoHelper.hasSubId()) {
+ b = PhoneGlobals.getInstance().getCarrierConfigForSubId(
+ mSubscriptionInfoHelper.getSubId());
+ } else {
+ b = PhoneGlobals.getInstance().getCarrierConfig();
+ }
+ if (b != null) {
+ mReplaceInvalidCFNumbers = b.getBoolean(
+ CarrierConfigManager.KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL);
+ mCallForwardByUssd = b.getBoolean(
+ CarrierConfigManager.KEY_USE_CALL_FORWARDING_USSD_BOOL);
+ supportCFNRc = b.getBoolean(
+ CarrierConfigManager.KEY_CALL_FORWARDING_WHEN_UNREACHABLE_SUPPORTED_BOOL);
+ }
+
+ PreferenceScreen prefSet = getPreferenceScreen();
+ mButtonCFU = (CallForwardEditPreference) prefSet.findPreference(BUTTON_CFU_KEY);
+ mButtonCFB = (CallForwardEditPreference) prefSet.findPreference(BUTTON_CFB_KEY);
+ mButtonCFNRy = (CallForwardEditPreference) prefSet.findPreference(BUTTON_CFNRY_KEY);
+ mButtonCFNRc = (CallForwardEditPreference) prefSet.findPreference(BUTTON_CFNRC_KEY);
+
+ mButtonCFU.setParentActivity(this, mButtonCFU.reason);
+ mButtonCFB.setParentActivity(this, mButtonCFB.reason);
+ mButtonCFNRy.setParentActivity(this, mButtonCFNRy.reason);
+ mButtonCFNRc.setParentActivity(this, mButtonCFNRc.reason);
+
+ mPreferences.add(mButtonCFU);
+ mPreferences.add(mButtonCFB);
+ mPreferences.add(mButtonCFNRy);
+
+ if (supportCFNRc) {
+ mPreferences.add(mButtonCFNRc);
+ } else {
+ // When CFNRc is not supported, mButtonCFNRc is grayed out from the menu.
+ // Default state for the preferences in this PreferenceScreen is disabled.
+ // Only preferences listed in the ArrayList mPreferences will be enabled.
+ // By not adding mButtonCFNRc to mPreferences it will be kept disabled.
+ Log.d(LOG_TAG, "onCreate: CFNRc is not supported, grey out the item.");
+ }
+
+ if (mCallForwardByUssd) {
+ //the call forwarding ussd command's behavior is similar to the call forwarding when
+ //unanswered,so only display the call forwarding when unanswered item.
+ prefSet.removePreference(mButtonCFU);
+ prefSet.removePreference(mButtonCFB);
+ prefSet.removePreference(mButtonCFNRc);
+ mPreferences.remove(mButtonCFU);
+ mPreferences.remove(mButtonCFB);
+ mPreferences.remove(mButtonCFNRc);
+ mButtonCFNRy.setDependency(null);
+ }
+
+ // we wait to do the initialization until onResume so that the
+ // TimeConsumingPreferenceActivity dialog can display as it
+ // relies on onResume / onPause to maintain its foreground state.
+
+ mFirstResume = true;
+ mIcicle = icicle;
+
+ ActionBar actionBar = getActionBar();
+ if (actionBar != null) {
+ // android.R.id.home will be triggered in onOptionsItemSelected()
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ if (mFirstResume) {
+ if (mIcicle == null) {
+ Log.d(LOG_TAG, "start to init ");
+ CallForwardEditPreference pref = mPreferences.get(mInitIndex);
+ pref.init(this, mPhone, mReplaceInvalidCFNumbers, mCallForwardByUssd);
+ pref.startCallForwardOptionsQuery();
+
+ } else {
+ mInitIndex = mPreferences.size();
+
+ for (CallForwardEditPreference pref : mPreferences) {
+ Bundle bundle = mIcicle.getParcelable(pref.getKey());
+ pref.setToggled(bundle.getBoolean(KEY_TOGGLE));
+ pref.setEnabled(bundle.getBoolean(KEY_ENABLE));
+ CallForwardInfo cf = new CallForwardInfo();
+ cf.number = bundle.getString(KEY_NUMBER);
+ cf.status = bundle.getInt(KEY_STATUS);
+ pref.init(this, mPhone, mReplaceInvalidCFNumbers, mCallForwardByUssd);
+ pref.restoreCallForwardInfo(cf);
+ }
+ }
+ mFirstResume = false;
+ mIcicle = null;
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ for (CallForwardEditPreference pref : mPreferences) {
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(KEY_TOGGLE, pref.isToggled());
+ bundle.putBoolean(KEY_ENABLE, pref.isEnabled());
+ if (pref.callForwardInfo != null) {
+ bundle.putString(KEY_NUMBER, pref.callForwardInfo.number);
+ bundle.putInt(KEY_STATUS, pref.callForwardInfo.status);
+ }
+ outState.putParcelable(pref.getKey(), bundle);
+ }
+ }
+
+ @Override
+ public void onFinished(Preference preference, boolean reading) {
+ if (mInitIndex < mPreferences.size()-1 && !isFinishing()) {
+ mInitIndex++;
+ CallForwardEditPreference pref = mPreferences.get(mInitIndex);
+ pref.init(this, mPhone, mReplaceInvalidCFNumbers, mCallForwardByUssd);
+ pref.startCallForwardOptionsQuery();
+ }
+
+ super.onFinished(preference, reading);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ Log.d(LOG_TAG, "onActivityResult: done");
+ if (resultCode != RESULT_OK) {
+ Log.d(LOG_TAG, "onActivityResult: contact picker result not OK.");
+ return;
+ }
+ Cursor cursor = null;
+ try {
+ // check if the URI returned by the user belongs to the user
+ final int currentUser = UserHandle.getUserId(Process.myUid());
+ if (currentUser
+ != ContentProvider.getUserIdFromUri(data.getData(), currentUser)) {
+
+ Log.w(LOG_TAG, "onActivityResult: Contact data of different user, "
+ + "cannot access");
+ return;
+ }
+ cursor = getContentResolver().query(data.getData(),
+ NUM_PROJECTION, null, null, null);
+ if ((cursor == null) || (!cursor.moveToFirst())) {
+ Log.d(LOG_TAG, "onActivityResult: bad contact data, no results found.");
+ return;
+ }
+
+ switch (requestCode) {
+ case CommandsInterface.CF_REASON_UNCONDITIONAL:
+ mButtonCFU.onPickActivityResult(cursor.getString(0));
+ break;
+ case CommandsInterface.CF_REASON_BUSY:
+ mButtonCFB.onPickActivityResult(cursor.getString(0));
+ break;
+ case CommandsInterface.CF_REASON_NO_REPLY:
+ mButtonCFNRy.onPickActivityResult(cursor.getString(0));
+ break;
+ case CommandsInterface.CF_REASON_NOT_REACHABLE:
+ mButtonCFNRc.onPickActivityResult(cursor.getString(0));
+ break;
+ default:
+ // TODO: may need exception here.
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ final int itemId = item.getItemId();
+ if (itemId == android.R.id.home) { // See ActionBar#setDisplayHomeAsUpEnabled()
+ CallFeaturesSetting.goUpToTopLevelSetting(this, mSubscriptionInfoHelper);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/src/com/android/phone/CdmaCallOptions.java b/src/com/android/phone/CdmaCallOptions.java
index 8513664..6145870 100644
--- a/src/com/android/phone/CdmaCallOptions.java
+++ b/src/com/android/phone/CdmaCallOptions.java
@@ -19,19 +19,38 @@
import android.os.Bundle;
import android.os.PersistableBundle;
import android.preference.Preference;
-import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsManager;
+import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.feature.MmTelFeature;
+import android.util.Log;
import android.view.MenuItem;
import com.android.internal.telephony.PhoneConstants;
-public class CdmaCallOptions extends PreferenceActivity {
+public class CdmaCallOptions extends TimeConsumingPreferenceActivity {
private static final String LOG_TAG = "CdmaCallOptions";
- private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
private static final String BUTTON_VP_KEY = "button_voice_privacy_key";
- private CdmaVoicePrivacySwitchPreference mButtonVoicePrivacy;
+ private static final String CALL_FORWARDING_KEY = "call_forwarding_key";
+ private static final String CALL_WAITING_KEY = "call_waiting_key";
+
+ private class UtCallback extends ImsMmTelManager.CapabilityCallback {
+ @Override
+ public void onCapabilitiesStatusChanged(MmTelFeature.MmTelCapabilities capabilities) {
+ boolean isUtAvailable = capabilities.isCapable(
+ MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT);
+ updatePreferencesEnabled(isUtAvailable);
+ }
+ }
+
+ private Preference mCallForwardingPref;
+ private CdmaCallWaitingPreference mCallWaitingPref;
+ private UtCallback mUtCallback;
+ private ImsMmTelManager mMmTelManager;
@Override
protected void onCreate(Bundle icicle) {
@@ -43,19 +62,111 @@
subInfoHelper.setActionBarTitle(
getActionBar(), getResources(), R.string.labelCdmaMore_with_label);
- mButtonVoicePrivacy = (CdmaVoicePrivacySwitchPreference) findPreference(BUTTON_VP_KEY);
- mButtonVoicePrivacy.setPhone(subInfoHelper.getPhone());
+ CdmaVoicePrivacySwitchPreference buttonVoicePrivacy =
+ (CdmaVoicePrivacySwitchPreference) findPreference(BUTTON_VP_KEY);
+ buttonVoicePrivacy.setPhone(subInfoHelper.getPhone());
PersistableBundle carrierConfig;
+ int subId;
if (subInfoHelper.hasSubId()) {
- carrierConfig = PhoneGlobals.getInstance().getCarrierConfigForSubId(
- subInfoHelper.getSubId());
+ subId = subInfoHelper.getSubId();
} else {
- carrierConfig = PhoneGlobals.getInstance().getCarrierConfig();
+ subId = SubscriptionManager.getDefaultSubscriptionId();
}
+ carrierConfig = PhoneGlobals.getInstance().getCarrierConfigForSubId(subId);
if (subInfoHelper.getPhone().getPhoneType() != PhoneConstants.PHONE_TYPE_CDMA
|| carrierConfig.getBoolean(CarrierConfigManager.KEY_VOICE_PRIVACY_DISABLE_UI_BOOL)) {
- // disable the entire screen
- getPreferenceScreen().setEnabled(false);
+ buttonVoicePrivacy.setEnabled(false);
+ }
+
+ mCallForwardingPref = getPreferenceScreen().findPreference(CALL_FORWARDING_KEY);
+ if (carrierConfig != null && carrierConfig.getBoolean(
+ CarrierConfigManager.KEY_CALL_FORWARDING_VISIBILITY_BOOL)) {
+ mCallForwardingPref.setIntent(
+ subInfoHelper.getIntent(CdmaCallForwardOptions.class));
+ } else {
+ getPreferenceScreen().removePreference(mCallForwardingPref);
+ mCallForwardingPref = null;
+ }
+
+ mCallWaitingPref = (CdmaCallWaitingPreference) getPreferenceScreen()
+ .findPreference(CALL_WAITING_KEY);
+ if (carrierConfig == null || !carrierConfig.getBoolean(
+ CarrierConfigManager.KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL)) {
+ getPreferenceScreen().removePreference(mCallWaitingPref);
+ mCallWaitingPref = null;
+ }
+ // Do not go further if the preferences are removed.
+ if (mCallForwardingPref == null && mCallWaitingPref == null) return;
+
+ boolean isSsOverCdmaEnabled = carrierConfig != null && carrierConfig.getBoolean(
+ CarrierConfigManager.KEY_SUPPORT_SS_OVER_CDMA_BOOL);
+ boolean isSsOverUtEnabled = carrierConfig != null && carrierConfig.getBoolean(
+ CarrierConfigManager.KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL);
+
+ if (isSsOverCdmaEnabled && mCallWaitingPref != null) {
+ // If SS over CDMA is enabled, then the preference will always be enabled,
+ // independent of SS over UT status. Initialize it now.
+ mCallWaitingPref.init(this, subInfoHelper.getPhone());
+ return;
+ }
+ // Since SS over UT availability can change, first disable the preferences that rely on it
+ // and only enable it if UT is available.
+ updatePreferencesEnabled(false);
+ if (isSsOverUtEnabled) {
+ // Register a callback to listen to SS over UT state. This will enable the preferences
+ // once the callback notifies settings that UT is enabled.
+ registerMmTelCapsCallback(subId);
+ } else {
+ Log.w(LOG_TAG, "SS over UT and CDMA disabled, but preferences are visible.");
+ }
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ unregisterMmTelCapsCallback();
+ }
+
+ private void unregisterMmTelCapsCallback() {
+ if (mMmTelManager == null || mUtCallback == null) return;
+ mMmTelManager.unregisterMmTelCapabilityCallback(mUtCallback);
+ mUtCallback = null;
+ Log.d(LOG_TAG, "unregisterMmTelCapsCallback: UT availability callback unregistered");
+ }
+
+ private void registerMmTelCapsCallback(int subId) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) return;
+ ImsManager imsManager = getSystemService(ImsManager.class);
+ try {
+ if (imsManager != null) {
+ mUtCallback = new UtCallback();
+ mMmTelManager = imsManager.getImsMmTelManager(subId);
+ // Callback will call back with the state as soon as it is available.
+ mMmTelManager.registerMmTelCapabilityCallback(getMainExecutor(), mUtCallback);
+ Log.d(LOG_TAG, "registerMmTelCapsCallback: UT availability callback "
+ + "registered");
+ } else {
+ Log.w(LOG_TAG, "registerMmTelCapsCallback: couldn't get ImsManager, assuming "
+ + "UT is not available: ");
+ updatePreferencesEnabled(false);
+ }
+ } catch (IllegalArgumentException | ImsException e) {
+ Log.w(LOG_TAG, "registerMmTelCapsCallback: couldn't register callback, assuming "
+ + "UT is not available: " + e);
+ updatePreferencesEnabled(false);
+ }
+ }
+
+ private void updatePreferencesEnabled(boolean isEnabled) {
+ Log.d(LOG_TAG, "updatePreferencesEnabled: " + isEnabled);
+ if (mCallForwardingPref != null) mCallForwardingPref.setEnabled(isEnabled);
+
+ if (mCallWaitingPref == null || mCallWaitingPref.isEnabled() == isEnabled) return;
+ mCallWaitingPref.setActionAvailable(isEnabled);
+ if (isEnabled) {
+ SubscriptionInfoHelper subInfoHelper = new SubscriptionInfoHelper(this, getIntent());
+ // kick off the normal process to populate the Call Waiting status.
+ mCallWaitingPref.init(this, subInfoHelper.getPhone());
}
}
@@ -76,5 +187,4 @@
}
return false;
}
-
}
diff --git a/src/com/android/phone/CdmaCallWaitingPreference.java b/src/com/android/phone/CdmaCallWaitingPreference.java
new file mode 100644
index 0000000..3713b19
--- /dev/null
+++ b/src/com/android/phone/CdmaCallWaitingPreference.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2020 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.phone;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.Preference;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Phone;
+
+public class CdmaCallWaitingPreference extends Preference {
+ private static final String LOG_TAG = "CdmaCallWaitingPreference";
+ private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+ private Context mContext;
+ private Phone mPhone;
+ private TimeConsumingPreferenceListener mTcpListener;
+ private MyHandler mHandler = new MyHandler();
+ private boolean mIsActionAvailable = true;
+
+ public CdmaCallWaitingPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mContext = context;
+ }
+
+ public CdmaCallWaitingPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.preferenceStyle);
+ }
+
+ public CdmaCallWaitingPreference(Context context) {
+ this(context, null);
+ }
+
+ public void init(TimeConsumingPreferenceListener listener, Phone phone) {
+ mPhone = phone;
+ mTcpListener = listener;
+ Log.d(LOG_TAG, "phone id= " + mPhone.getPhoneId());
+ mPhone.getCallWaiting(mHandler.obtainMessage(MyHandler.MESSAGE_GET_CALL_WAITING,
+ MyHandler.MESSAGE_GET_CALL_WAITING, MyHandler.MESSAGE_GET_CALL_WAITING));
+ if (mTcpListener != null) {
+ mTcpListener.onStarted(this, true);
+ }
+ }
+
+ /**
+ * Enables this preference if Call waiting is available in the platform. If not, this will
+ * override all attempts to enable the preference from the associated
+ * TimeConsumingPreferenceActivity.
+ */
+ public void setActionAvailable(boolean isAvailable) {
+ mIsActionAvailable = isAvailable;
+ super.setEnabled(mIsActionAvailable);
+ }
+
+ @Override
+ public void onClick() {
+ super.onClick();
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+ builder.setTitle(mContext.getText(R.string.cdma_call_waiting));
+ builder.setMessage(mContext.getText(R.string.enable_cdma_call_waiting_setting));
+ builder.setPositiveButton(R.string.enable_cdma_cw, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ mPhone.setCallWaiting(true,
+ mHandler.obtainMessage(MyHandler.MESSAGE_SET_CALL_WAITING));
+ if (mTcpListener != null) {
+ mTcpListener.onStarted(CdmaCallWaitingPreference.this, false);
+ }
+ }
+ });
+ builder.setNegativeButton(R.string.disable_cdma_cw, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ mPhone.setCallWaiting(false,
+ mHandler.obtainMessage(MyHandler.MESSAGE_SET_CALL_WAITING));
+ if (mTcpListener != null) {
+ mTcpListener.onStarted(CdmaCallWaitingPreference.this, false);
+ }
+ }
+ });
+ builder.create().show();
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ // If this action is currently disabled due to configuration changes, do not allow anything
+ // to enable it.
+ if (!mIsActionAvailable) return;
+ super.setEnabled(enabled);
+ }
+
+ private class MyHandler extends Handler {
+ static final int MESSAGE_GET_CALL_WAITING = 0;
+ static final int MESSAGE_SET_CALL_WAITING = 1;
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_GET_CALL_WAITING:
+ handleGetCallWaitingResponse(msg);
+ break;
+ case MESSAGE_SET_CALL_WAITING:
+ handleSetCallWaitingResponse(msg);
+ break;
+ }
+ }
+
+ private void handleGetCallWaitingResponse(Message msg) {
+ AsyncResult ar = (AsyncResult) msg.obj;
+
+ if (mTcpListener != null) {
+ if (msg.arg2 == MESSAGE_SET_CALL_WAITING) {
+ mTcpListener.onFinished(CdmaCallWaitingPreference.this, false);
+ } else {
+ mTcpListener.onFinished(CdmaCallWaitingPreference.this, true);
+ }
+ }
+
+ if (ar.exception instanceof CommandException) {
+ if (DBG) {
+ Log.d(LOG_TAG, "handleGetCallWaitingResponse: CommandException=" +
+ ar.exception);
+ }
+ if (mTcpListener != null) {
+ mTcpListener.onException(CdmaCallWaitingPreference.this,
+ (CommandException)ar.exception);
+ }
+ } else if (ar.userObj instanceof Throwable || ar.exception != null) {
+ if (DBG) {
+ Log.d(LOG_TAG, "handleGetCallWaitingResponse: Exception" + ar.exception);
+ }
+ if (mTcpListener != null) {
+ mTcpListener.onError(CdmaCallWaitingPreference.this,
+ TimeConsumingPreferenceActivity.RESPONSE_ERROR);
+ }
+ } else {
+ if (DBG) {
+ Log.d(LOG_TAG, "handleGetCallWaitingResponse: CW state successfully queried.");
+ }
+ int[] cwArray = (int[])ar.result;
+ if (cwArray == null) {
+ if (mTcpListener != null) {
+ mTcpListener.onError(CdmaCallWaitingPreference.this,
+ TimeConsumingPreferenceActivity.RESPONSE_ERROR);
+ }
+ return;
+ }
+
+ try {
+ if (cwArray[0] == CommandsInterface.SS_STATUS_UNKNOWN) {
+ setSummary("");
+ } else if(cwArray[0] == 1) {
+ setSummary(mContext.getString(R.string.cdma_call_waiting_in_ims_on));
+ } else if(cwArray[0] == 0) {
+ setSummary(mContext.getString(R.string.cdma_call_waiting_in_ims_off));
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ setSummary("");
+ Log.e(LOG_TAG, "handleGetCallWaitingResponse: improper result: err ="
+ + e.getMessage());
+ }
+ }
+ }
+
+ private void handleSetCallWaitingResponse(Message msg) {
+ AsyncResult ar = (AsyncResult) msg.obj;
+
+ if (ar.exception != null) {
+ if (DBG) {
+ Log.d(LOG_TAG, "handleSetCallWaitingResponse: ar.exception=" + ar.exception);
+ }
+ }
+
+ if (ar.result != null) {
+ int arr = (int)ar.result;
+ if (arr == CommandsInterface.SS_STATUS_UNKNOWN) {
+ Log.d(LOG_TAG, "handleSetCallWaitingResponse: no need to re get in CDMA");
+ mTcpListener.onFinished(CdmaCallWaitingPreference.this, false);
+ return;
+ }
+ }
+
+ if (DBG) Log.d(LOG_TAG, "handleSetCallWaitingResponse: re get");
+ mPhone.getCallWaiting(obtainMessage(MESSAGE_GET_CALL_WAITING,
+ MESSAGE_SET_CALL_WAITING, MESSAGE_SET_CALL_WAITING, ar.exception));
+ }
+ }
+}
diff --git a/src/com/android/phone/EditPhoneNumberPreference.java b/src/com/android/phone/EditPhoneNumberPreference.java
index 74b8a45..505c284 100644
--- a/src/com/android/phone/EditPhoneNumberPreference.java
+++ b/src/com/android/phone/EditPhoneNumberPreference.java
@@ -16,6 +16,9 @@
package com.android.phone;
+import static android.view.View.LAYOUT_DIRECTION_LOCALE;
+import static android.view.View.TEXT_DIRECTION_LOCALE;
+
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
@@ -37,6 +40,8 @@
import android.widget.ImageButton;
import android.widget.TextView;
+import com.android.internal.telephony.CommandsInterface;
+
public class EditPhoneNumberPreference extends EditTextPreference {
//allowed modes for this preference.
@@ -90,6 +95,7 @@
private String mPhoneNumber;
private boolean mChecked;
+ private boolean mIsUnknownStatus;
/**
* Interface for the dialog closed listener, related to
@@ -209,7 +215,9 @@
}
}
editText.setText(BidiFormatter.getInstance().unicodeWrap(
- mPhoneNumber, TextDirectionHeuristics.LTR));
+ mPhoneNumber, TextDirectionHeuristics.LOCALE));
+ editText.setTextDirection(TEXT_DIRECTION_LOCALE);
+ editText.setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
editText.setMovementMethod(ArrowKeyMovementMethod.getInstance());
editText.setKeyListener(DialerKeyListener.getInstance());
editText.setOnFocusChangeListener(mDialogFocusChangeListener);
@@ -254,7 +262,13 @@
// displayed, since there is no need to hide the edittext
// field anymore.
if (mConfirmationMode == CM_ACTIVATION) {
- if (mChecked) {
+ if (mIsUnknownStatus) {
+ builder.setPositiveButton(mEnableText, this);
+ builder.setNeutralButton(mDisableText, this);
+ if (mPrefId == CommandsInterface.CF_REASON_ALL) {
+ builder.setPositiveButton(null, null);
+ }
+ } else if (mChecked) {
builder.setPositiveButton(mChangeNumberText, this);
builder.setNeutralButton(mDisableText, this);
} else {
@@ -310,7 +324,8 @@
@Override
public void onClick(DialogInterface dialog, int which) {
// The neutral button (button3) is always the toggle.
- if ((mConfirmationMode == CM_ACTIVATION) && (which == DialogInterface.BUTTON_NEUTRAL)) {
+ if ((mConfirmationMode == CM_ACTIVATION) && (which == DialogInterface.BUTTON_NEUTRAL)
+ && !mIsUnknownStatus) {
//flip the toggle if we are in the correct mode.
setToggled(!isToggled());
}
@@ -499,4 +514,12 @@
public void showPhoneNumberDialog() {
showDialog(null);
}
+
+ public void setUnknownStatus(boolean isUnknown) {
+ mIsUnknownStatus = isUnknown;
+ }
+
+ public boolean isUnknownStatus() {
+ return mIsUnknownStatus;
+ }
}
diff --git a/src/com/android/phone/IccNetworkDepersonalizationPanel.java b/src/com/android/phone/IccNetworkDepersonalizationPanel.java
index a26225e..a4ec8a4 100644
--- a/src/com/android/phone/IccNetworkDepersonalizationPanel.java
+++ b/src/com/android/phone/IccNetworkDepersonalizationPanel.java
@@ -23,6 +23,8 @@
import android.os.Message;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.Editable;
import android.text.Spannable;
@@ -68,6 +70,7 @@
private static IccNetworkDepersonalizationPanel [] sNdpPanel =
new IccNetworkDepersonalizationPanel[
TelephonyManager.getDefault().getSupportedModemCount()];
+ private SubscriptionInfo mSir;
//UI elements
private EditText mPinEntry;
@@ -75,6 +78,7 @@
private LinearLayout mStatusPanel;
private TextView mPersoSubtypeText;
private PersoSubState mPersoSubState;
+ private TextView mPhoneIdText;
private TextView mStatusText;
private Button mUnlockButton;
@@ -167,6 +171,8 @@
super(context);
mPhone = PhoneGlobals.getPhone();
mPersoSubtype = PersoSubState.PERSOSUBSTATE_SIM_NETWORK.ordinal();
+ mSir = SubscriptionManager.from(context)
+ .getActiveSubscriptionInfoForSimSlotIndex(mPhone.getPhoneId());
}
//constructor
@@ -175,6 +181,8 @@
super(context);
mPhone = phone == null ? PhoneGlobals.getPhone() : phone;
mPersoSubtype = subtype;
+ mSir = SubscriptionManager.from(context)
+ .getActiveSubscriptionInfoForSimSlotIndex(mPhone.getPhoneId());
}
@Override
@@ -194,6 +202,7 @@
mEntryPanel = (LinearLayout) findViewById(R.id.entry_panel);
mPersoSubtypeText = (TextView) findViewById(R.id.perso_subtype_text);
+ mPhoneIdText = (TextView) findViewById(R.id.perso_phoneid_text);
displayStatus(statusType.ENTRY.name());
mUnlockButton = (Button) findViewById(R.id.ndp_unlock);
@@ -282,6 +291,17 @@
log ("Unsupported Perso Subtype :" + mPersoSubState.name());
return;
}
+ if(mSir != null) {
+ CharSequence displayName = mSir.getDisplayName();
+ log("Operator displayName is: " + displayName + "phoneId: " + mPhone.getPhoneId());
+
+ if(displayName != null && displayName != "") {
+ // Displaying Operator displayName on UI
+ String phoneIdText = getContext().getString(R.string.label_phoneid)
+ + ": " + displayName;
+ mPhoneIdText.setText(phoneIdText);
+ }
+ }
if (type == statusType.ENTRY.name()) {
String displayText = getContext().getString(label);
diff --git a/src/com/android/phone/IccPanel.java b/src/com/android/phone/IccPanel.java
index 73dd8bc..4018e40 100644
--- a/src/com/android/phone/IccPanel.java
+++ b/src/com/android/phone/IccPanel.java
@@ -74,13 +74,13 @@
@Override
protected void onStart() {
super.onStart();
- mStatusBarManager.setDisabledForSimNetworkLock(true);
+ mStatusBarManager.setExpansionDisabledForSimNetworkLock(true);
}
@Override
public void onStop() {
super.onStop();
- mStatusBarManager.setDisabledForSimNetworkLock(false);
+ mStatusBarManager.setExpansionDisabledForSimNetworkLock(false);
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
diff --git a/src/com/android/phone/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java
index dcae24b..7d594d1 100644
--- a/src/com/android/phone/ImsRcsController.java
+++ b/src/com/android/phone/ImsRcsController.java
@@ -16,32 +16,46 @@
package com.android.phone;
+import android.Manifest;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.os.UserHandle;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyFrameworkInitializer;
+import android.telephony.ims.DelegateRequest;
import android.telephony.ims.ImsException;
+import android.telephony.ims.RcsContactUceCapability;
+import android.telephony.ims.RcsUceAdapter.PublishState;
import android.telephony.ims.RegistrationManager;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.IImsRcsController;
import android.telephony.ims.aidl.IImsRegistrationCallback;
import android.telephony.ims.aidl.IRcsUceControllerCallback;
+import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
+import android.telephony.ims.aidl.ISipDelegate;
+import android.telephony.ims.aidl.ISipDelegateConnectionStateCallback;
+import android.telephony.ims.aidl.ISipDelegateMessageCallback;
+import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.RcsFeature;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.Log;
import com.android.ims.ImsManager;
+import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.TelephonyPermissions;
-import com.android.internal.telephony.imsphone.ImsPhone;
+import com.android.internal.telephony.ims.ImsResolver;
import com.android.services.telephony.rcs.RcsFeatureController;
+import com.android.services.telephony.rcs.SipTransportController;
import com.android.services.telephony.rcs.TelephonyRcsService;
-import com.android.services.telephony.rcs.UserCapabilityExchangeImpl;
+import com.android.services.telephony.rcs.UceControllerManager;
import java.util.List;
+import java.util.Set;
/**
* Implementation of the IImsRcsController interface.
@@ -54,6 +68,9 @@
private PhoneGlobals mApp;
private TelephonyRcsService mRcsService;
+ private ImsResolver mImsResolver;
+ // set by shell cmd phone src set-device-enabled true/false
+ private Boolean mSingleRegistrationOverride;
/**
* Initialize the singleton ImsRcsController instance.
@@ -76,6 +93,7 @@
mApp = app;
TelephonyFrameworkInitializer
.getTelephonyServiceManager().getTelephonyImsServiceRegisterer().register(this);
+ mImsResolver = ImsResolver.getInstance();
}
/**
@@ -84,7 +102,8 @@
*/
@Override
public void registerImsRegistrationCallback(int subId, IImsRegistrationCallback callback) {
- enforceReadPrivilegedPermission("registerImsRegistrationCallback");
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ mApp, subId, "registerImsRegistrationCallback");
final long token = Binder.clearCallingIdentity();
try {
getRcsFeatureController(subId).registerImsRegistrationCallback(subId, callback);
@@ -101,7 +120,8 @@
*/
@Override
public void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback callback) {
- enforceReadPrivilegedPermission("unregisterImsRegistrationCallback");
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ mApp, subId, "unregisterImsRegistrationCallback");
final long token = Binder.clearCallingIdentity();
try {
getRcsFeatureController(subId).unregisterImsRegistrationCallback(subId, callback);
@@ -117,7 +137,8 @@
*/
@Override
public void getImsRcsRegistrationState(int subId, IIntegerConsumer consumer) {
- enforceReadPrivilegedPermission("getImsRcsRegistrationState");
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ mApp, subId, "getImsRcsRegistrationState");
final long token = Binder.clearCallingIdentity();
try {
getRcsFeatureController(subId).getRegistrationState(regState -> {
@@ -138,7 +159,8 @@
*/
@Override
public void getImsRcsRegistrationTransportType(int subId, IIntegerConsumer consumer) {
- enforceReadPrivilegedPermission("getImsRcsRegistrationTransportType");
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ mApp, subId, "getImsRcsRegistrationTransportType");
final long token = Binder.clearCallingIdentity();
try {
getRcsFeatureController(subId).getRegistrationTech(regTech -> {
@@ -191,6 +213,8 @@
final long token = Binder.clearCallingIdentity();
try {
getRcsFeatureController(subId).unregisterRcsAvailabilityCallback(subId, callback);
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG, "unregisterRcsAvailabilityCallback: error=" + e.errorCode);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -201,7 +225,7 @@
*
* @param subId the subscription ID
* @param capability the RCS capability to query.
- * @param radioTech the radio tech that this capability failed for
+ * @param radioTech the radio technology type that we are querying.
* @return true if the RCS capability is capable for this subscription, false otherwise.
*/
@Override
@@ -227,15 +251,17 @@
* @param subId the subscription ID
* @param capability the RCS capability to query.
* @return true if the RCS capability is currently available for the associated subscription,
+ * @param radioTech the radio technology type that we are querying.
* false otherwise.
*/
@Override
public boolean isAvailable(int subId,
- @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability) {
+ @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
+ @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) {
enforceReadPrivilegedPermission("isAvailable");
final long token = Binder.clearCallingIdentity();
try {
- return getRcsFeatureController(subId).isAvailable(capability);
+ return getRcsFeatureController(subId).isAvailable(capability, radioTech);
} catch (ImsException e) {
Log.e(TAG, "isAvailable: sudId=" + subId
+ ", capability=" + capability + ", " + e.getMessage());
@@ -248,37 +274,228 @@
@Override
public void requestCapabilities(int subId, String callingPackage, String callingFeatureId,
List<Uri> contactNumbers, IRcsUceControllerCallback c) {
- enforceReadPrivilegedPermission("requestCapabilities");
- if (!isUceSettingEnabled(subId, callingPackage, callingFeatureId)) {
- throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
- "The user has not enabled UCE for this subscription.");
- }
+ enforceAccessUserCapabilityExchangePermission("requestCapabilities");
+ enforceReadContactsPermission("requestCapabilities");
final long token = Binder.clearCallingIdentity();
try {
- UserCapabilityExchangeImpl uce = getRcsFeatureController(subId).getFeature(
- UserCapabilityExchangeImpl.class);
- if (uce == null) {
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
"This subscription does not support UCE.");
}
- uce.requestCapabilities(contactNumbers, c);
+ uceCtrlManager.requestCapabilities(contactNumbers, c);
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode(), e.getMessage());
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
- public int getUcePublishState(int subId) {
- enforceReadPrivilegedPermission("getUcePublishState");
+ public void requestAvailability(int subId, String callingPackage,
+ String callingFeatureId, Uri contactNumber, IRcsUceControllerCallback c) {
+ enforceAccessUserCapabilityExchangePermission("requestAvailability");
+ enforceReadContactsPermission("requestAvailability");
final long token = Binder.clearCallingIdentity();
try {
- UserCapabilityExchangeImpl uce = getRcsFeatureController(subId).getFeature(
- UserCapabilityExchangeImpl.class);
- if (uce == null) {
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
"This subscription does not support UCE.");
}
- return uce.getUcePublishState();
+ uceCtrlManager.requestNetworkAvailability(contactNumber, c);
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode(), e.getMessage());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public @PublishState int getUcePublishState(int subId) {
+ enforceReadPrivilegedPermission("getUcePublishState");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "This subscription does not support UCE.");
+ }
+ return uceCtrlManager.getUcePublishState();
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode(), e.getMessage());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Add new feature tags to the Set used to calculate the capabilities in PUBLISH.
+ */
+ // Used for SHELL command only right now.
+ public RcsContactUceCapability addUceRegistrationOverrideShell(int subId,
+ Set<String> featureTags) throws ImsException {
+ // Permission check happening in PhoneInterfaceManager.
+ try {
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ return null;
+ }
+ return uceCtrlManager.addUceRegistrationOverride(featureTags);
+ } catch (ServiceSpecificException e) {
+ throw new ImsException(e.getMessage(), e.errorCode);
+ }
+ }
+
+ /**
+ * Remove existing feature tags to the Set used to calculate the capabilities in PUBLISH.
+ */
+ // Used for SHELL command only right now.
+ public RcsContactUceCapability removeUceRegistrationOverrideShell(int subId,
+ Set<String> featureTags) throws ImsException {
+ // Permission check happening in PhoneInterfaceManager.
+ try {
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ return null;
+ }
+ return uceCtrlManager.removeUceRegistrationOverride(featureTags);
+ } catch (ServiceSpecificException e) {
+ throw new ImsException(e.getMessage(), e.errorCode);
+ }
+ }
+
+ /**
+ * Clear all overrides in the Set used to calculate the capabilities in PUBLISH.
+ */
+ // Used for SHELL command only right now.
+ public RcsContactUceCapability clearUceRegistrationOverrideShell(int subId)
+ throws ImsException {
+ try {
+ // Permission check happening in PhoneInterfaceManager.
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ return null;
+ }
+ return uceCtrlManager.clearUceRegistrationOverride();
+ } catch (ServiceSpecificException e) {
+ throw new ImsException(e.getMessage(), e.errorCode);
+ }
+ }
+
+ /**
+ * @return current RcsContactUceCapability instance that will be used for PUBLISH.
+ */
+ // Used for SHELL command only right now.
+ public RcsContactUceCapability getLatestRcsContactUceCapabilityShell(int subId)
+ throws ImsException {
+ try {
+ // Permission check happening in PhoneInterfaceManager.
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ return null;
+ }
+ return uceCtrlManager.getLatestRcsContactUceCapability();
+ } catch (ServiceSpecificException e) {
+ throw new ImsException(e.getMessage(), e.errorCode);
+ }
+ }
+
+ /**
+ * @return the PIDf XML used in the last PUBLISH procedure or "none" if the device is not
+ * published. Returns {@code null} if the operation failed due to an error.
+ */
+ // Used for SHELL command only right now.
+ public String getLastUcePidfXmlShell(int subId) throws ImsException {
+ try {
+ // Permission check happening in PhoneInterfaceManager.
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ return null;
+ }
+ String pidfXml = uceCtrlManager.getLastPidfXml();
+ return pidfXml == null ? "none" : pidfXml;
+ } catch (ServiceSpecificException e) {
+ throw new ImsException(e.getMessage(), e.errorCode);
+ }
+ }
+
+ /**
+ * Remove UCE requests cannot be sent to the network status.
+ * @return true if this command is successful.
+ */
+ // Used for SHELL command only right now.
+ public boolean removeUceRequestDisallowedStatus(int subId) throws ImsException {
+ try {
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId, true).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ return false;
+ }
+ return uceCtrlManager.removeUceRequestDisallowedStatus();
+ } catch (ServiceSpecificException e) {
+ throw new ImsException(e.getMessage(), e.errorCode);
+ }
+ }
+
+ /**
+ * Set the timeout for contact capabilities request.
+ */
+ // Used for SHELL command only right now.
+ public boolean setCapabilitiesRequestTimeout(int subId, long timeoutAfter) throws ImsException {
+ try {
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId, true).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ return false;
+ }
+ return uceCtrlManager.setCapabilitiesRequestTimeout(timeoutAfter);
+ } catch (ServiceSpecificException e) {
+ throw new ImsException(e.getMessage(), e.errorCode);
+ }
+ }
+
+ @Override
+ public void registerUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c) {
+ enforceReadPrivilegedPermission("registerUcePublishStateCallback");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "This subscription does not support UCE.");
+ }
+ uceCtrlManager.registerPublishStateCallback(c);
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode(), e.getMessage());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void unregisterUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c) {
+ enforceReadPrivilegedPermission("unregisterUcePublishStateCallback");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "This subscription does not support UCE.");
+ }
+ uceCtrlManager.unregisterPublishStateCallback(c);
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG, "unregisterUcePublishStateCallback: error=" + e.errorCode);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -313,6 +530,153 @@
}
}
+ @Override
+ public boolean isSipDelegateSupported(int subId) {
+ TelephonyPermissions.enforceAnyPermissionGranted(mApp, Binder.getCallingUid(),
+ "isSipDelegateSupported",
+ Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION,
+ Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+ if (!isImsSingleRegistrationSupportedOnDevice()) {
+ return false;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ SipTransportController transport = getRcsFeatureController(subId).getFeature(
+ SipTransportController.class);
+ if (transport == null) {
+ return false;
+ }
+ return transport.isSupported(subId);
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode(), e.getMessage());
+ } catch (ServiceSpecificException e) {
+ if (e.errorCode == ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
+ return false;
+ }
+ throw e;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void createSipDelegate(int subId, DelegateRequest request, String packageName,
+ ISipDelegateConnectionStateCallback delegateState,
+ ISipDelegateMessageCallback delegateMessage) {
+ enforceImsSingleRegistrationPermission("createSipDelegate");
+ if (!isImsSingleRegistrationSupportedOnDevice()) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "SipDelegate creation is only supported for devices supporting IMS single "
+ + "registration");
+ }
+ if (!UserHandle.getUserHandleForUid(Binder.getCallingUid()).isSystem()) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "SipDelegate creation is only available to primary user.");
+ }
+ try {
+ int remoteUid = mApp.getPackageManager().getPackageUid(packageName, 0 /*flags*/);
+ if (Binder.getCallingUid() != remoteUid) {
+ throw new SecurityException("passed in packageName does not match the caller");
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new SecurityException("Passed in PackageName can not be found on device");
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ SipTransportController transport = getRcsFeatureController(subId).getFeature(
+ SipTransportController.class);
+ if (transport == null) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "This subscription does not support the creation of SIP delegates");
+ }
+ try {
+ transport.createSipDelegate(subId, request, packageName, delegateState,
+ delegateMessage);
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode(), e.getMessage());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void destroySipDelegate(int subId, ISipDelegate connection, int reason) {
+ // Do not check permissions here - the caller needs to have a connection already from the
+ // create method to call this method.
+ if (connection == null) {
+ return;
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ SipTransportController transport = getRcsFeatureController(subId).getFeature(
+ SipTransportController.class);
+ if (transport == null) {
+ return;
+ }
+ transport.destroySipDelegate(subId, connection, reason);
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG, "destroySipDelegate: error=" + e.errorCode);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void triggerNetworkRegistration(int subId, ISipDelegate connection, int sipCode,
+ String sipReason) {
+ enforceImsSingleRegistrationPermission("triggerNetworkRegistration");
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ SipTransportController transport = getRcsFeatureController(subId).getFeature(
+ SipTransportController.class);
+ if (transport == null) {
+ return;
+ }
+ transport.triggerFullNetworkRegistration(subId, connection, sipCode, sipReason);
+ } catch (ServiceSpecificException e) {
+ Log.e(TAG, "triggerNetworkRegistration: error=" + e.errorCode);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Registers for updates to the RcsFeature connection through the IImsServiceFeatureCallback
+ * callback.
+ */
+ @Override
+ public void registerRcsFeatureCallback(int slotId, IImsServiceFeatureCallback callback) {
+ enforceModifyPermission();
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (mImsResolver == null) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "Device does not support IMS");
+ }
+ mImsResolver.listenForFeature(slotId, ImsFeature.FEATURE_RCS, callback);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Unregister a previously registered IImsServiceFeatureCallback associated with an ImsFeature.
+ */
+ @Override
+ public void unregisterImsFeatureCallback(IImsServiceFeatureCallback callback) {
+ enforceModifyPermission();
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (mImsResolver == null) return;
+ mImsResolver.unregisterImsFeatureCallback(callback);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
/**
* Make sure either called from same process as self (phone) or IPC caller has read privilege.
*
@@ -324,6 +688,15 @@
}
/**
+ * @throws SecurityException if the caller does not have the required
+ * PERFORM_IMS_SINGLE_REGISTRATION permission.
+ */
+ private void enforceImsSingleRegistrationPermission(String message) {
+ mApp.enforceCallingOrSelfPermission(
+ Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION, message);
+ }
+
+ /**
* Make sure the caller has the MODIFY_PHONE_STATE permission.
*
* @throws SecurityException if the caller does not have the required permission
@@ -333,28 +706,23 @@
}
/**
- * Retrieve ImsPhone instance.
+ * Make sure the caller has the ACCESS_RCS_USER_CAPABILITY_EXCHANGE permission.
*
- * @param subId the subscription ID
- * @return The ImsPhone instance
- * @throws ServiceSpecificException if getting ImsPhone instance failed.
+ * @throws SecurityException if the caller does not have the required permission.
*/
- private ImsPhone getImsPhone(int subId) {
- if (!ImsManager.isImsSupportedOnDevice(mApp)) {
- throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
- "IMS is not available on device.");
- }
- Phone phone = PhoneGlobals.getPhone(subId);
- if (phone == null) {
- throw new ServiceSpecificException(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION,
- "Invalid subscription Id: " + subId);
- }
- ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
- if (imsPhone == null) {
- throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
- "Cannot find ImsPhone instance: " + subId);
- }
- return imsPhone;
+ 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);
}
/**
@@ -365,6 +733,18 @@
* @throws ServiceSpecificException if getting RcsFeatureManager instance failed.
*/
private RcsFeatureController getRcsFeatureController(int subId) {
+ return getRcsFeatureController(subId, false /* skipVerifyingConfig */);
+ }
+
+ /**
+ * Retrieve RcsFeatureManager instance.
+ *
+ * @param subId the subscription ID
+ * @param skipVerifyingConfig If the RCS configuration can be skip.
+ * @return The RcsFeatureManager instance
+ * @throws ServiceSpecificException if getting RcsFeatureManager instance failed.
+ */
+ private RcsFeatureController getRcsFeatureController(int subId, boolean skipVerifyingConfig) {
if (!ImsManager.isImsSupportedOnDevice(mApp)) {
throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
"IMS is not available on device.");
@@ -379,6 +759,9 @@
"Invalid subscription Id: " + subId);
}
int slotId = phone.getPhoneId();
+ if (!skipVerifyingConfig) {
+ verifyImsRcsConfiguredOrThrow(slotId);
+ }
RcsFeatureController c = mRcsService.getFeatureController(slotId);
if (c == null) {
throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
@@ -387,7 +770,35 @@
return c;
}
+ /**
+ * Throw an ImsException if the IMS resolver does not have an ImsService configured for RCS
+ * for the given slot ID or no ImsResolver instance has been created.
+ * @param slotId The slot ID that the IMS service is created for.
+ * @throws ServiceSpecificException If there is no ImsService configured for this slot.
+ */
+ private void verifyImsRcsConfiguredOrThrow(int slotId) {
+ if (mImsResolver == null
+ || !mImsResolver.isImsServiceConfiguredForFeature(slotId, ImsFeature.FEATURE_RCS)) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "This subscription does not support RCS");
+ }
+ }
+
+ private boolean isImsSingleRegistrationSupportedOnDevice() {
+ return mSingleRegistrationOverride != null ? mSingleRegistrationOverride
+ : mApp.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION);
+ }
+
void setRcsService(TelephonyRcsService rcsService) {
mRcsService = rcsService;
}
+
+ /**
+ * Override device RCS single registration support check for CTS testing or remove override
+ * if the Boolean is set to null.
+ */
+ void setDeviceSingleRegistrationSupportOverride(Boolean deviceOverrideValue) {
+ mSingleRegistrationOverride = deviceOverrideValue;
+ }
}
diff --git a/src/com/android/phone/ImsUtil.java b/src/com/android/phone/ImsUtil.java
index 38936ec..ba4ad38 100644
--- a/src/com/android/phone/ImsUtil.java
+++ b/src/com/android/phone/ImsUtil.java
@@ -25,6 +25,8 @@
import com.android.ims.ImsConfig;
import com.android.ims.ImsManager;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.imsphone.ImsPhone;
public class ImsUtil {
private static final String LOG_TAG = ImsUtil.class.getSimpleName();
@@ -128,6 +130,13 @@
return false;
}
+ // Do not promote WFC if in roaming and WFC roaming not allowed.
+ // WFC roaming setting is not modifiable, so its value is decided by the default value
+ // chosen by the carrier, hence it really means if the carrier supports WFC roaming.
+ if (getLastKnownRoamingState(phoneId) && !imsManager.isWfcRoamingEnabledByUser()) {
+ return false;
+ }
+
ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm != null) {
@@ -152,4 +161,13 @@
}
return subId;
}
+
+ private static boolean getLastKnownRoamingState(int phoneId) {
+ try {
+ ImsPhone imsPhone = (ImsPhone) (PhoneFactory.getPhone(phoneId).getImsPhone());
+ return imsPhone.getLastKnownRoamingState();
+ } catch (NullPointerException | ClassCastException e) {
+ return false;
+ }
+ }
}
diff --git a/src/com/android/phone/LocalConnectionImpl.java b/src/com/android/phone/LocalConnectionImpl.java
new file mode 100644
index 0000000..c2630ef
--- /dev/null
+++ b/src/com/android/phone/LocalConnectionImpl.java
@@ -0,0 +1,38 @@
+/*
+ * 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.phone;
+
+import android.content.Context;
+import android.telephony.TelephonyLocalConnection;
+
+import com.android.phone.callcomposer.CallComposerPictureManager;
+
+import java.util.UUID;
+
+public class LocalConnectionImpl implements TelephonyLocalConnection.ConnectionImpl {
+ private Context mContext;
+
+ public LocalConnectionImpl(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public String getCallComposerServerUrlForHandle(int subscriptionId, UUID uuid) {
+ return CallComposerPictureManager.getInstance(mContext, subscriptionId)
+ .getServerUrlForImageId(uuid);
+ }
+}
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index 1be7273..fb45f4c 100644
--- a/src/com/android/phone/NotificationMgr.java
+++ b/src/com/android/phone/NotificationMgr.java
@@ -546,7 +546,11 @@
int slotId = SubscriptionManager.getSlotIndex(subId);
resId = (slotId == 0) ? R.drawable.stat_sys_phone_call_forward_sub1
: R.drawable.stat_sys_phone_call_forward_sub2;
- notificationTitle = subInfo.getDisplayName().toString();
+ if (subInfo.getDisplayName() != null) {
+ notificationTitle = subInfo.getDisplayName().toString();
+ } else {
+ notificationTitle = mContext.getString(R.string.labelCF);
+ }
} else {
notificationTitle = mContext.getString(R.string.labelCF);
}
@@ -561,9 +565,10 @@
.setChannelId(NotificationChannelController.CHANNEL_ID_CALL_FORWARD)
.setOnlyAlertOnce(isRefresh);
- Intent intent = new Intent(Intent.ACTION_MAIN);
+ Intent intent = new Intent(TelecomManager.ACTION_SHOW_CALL_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- intent.setClassName("com.android.phone", "com.android.phone.CallFeaturesSetting");
+ intent.setPackage(mContext.getResources().getString(
+ R.string.call_settings_package_name));
SubscriptionInfoHelper.addExtrasToIntent(
intent, mSubscriptionManager.getActiveSubscriptionInfo(subId));
builder.setContentIntent(PendingIntent.getActivity(mContext, subId /* requestCode */,
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 0971754..2535365 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -49,12 +49,15 @@
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyLocalConnection;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
import android.util.LocalLog;
import android.util.Log;
import android.widget.Toast;
+import com.android.ims.ImsFeatureBinderRepository;
+import com.android.internal.os.BinderCallsStats;
import com.android.internal.telephony.CallManager;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.MmiCode;
@@ -68,6 +71,10 @@
import com.android.internal.telephony.dataconnection.DataConnectionReasons;
import com.android.internal.telephony.dataconnection.DataConnectionReasons.DataDisallowedReasonType;
import com.android.internal.telephony.ims.ImsResolver;
+import com.android.internal.telephony.imsphone.ImsPhone;
+import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
+import com.android.internal.telephony.uicc.UiccCard;
+import com.android.internal.telephony.uicc.UiccProfile;
import com.android.internal.util.IndentingPrintWriter;
import com.android.phone.settings.SettingsConstants;
import com.android.phone.vvm.CarrierVvmPackageInstalledReceiver;
@@ -150,7 +157,6 @@
CallNotifier notifier;
CallerInfoCache callerInfoCache;
NotificationMgr notificationMgr;
- ImsResolver mImsResolver;
TelephonyRcsService mTelephonyRcsService;
public PhoneInterfaceManager phoneMgr;
public ImsRcsController imsRcsController;
@@ -205,6 +211,7 @@
new CarrierVvmPackageInstalledReceiver();
private final SettingsObserver mSettingsObserver;
+ private BinderCallsStats.SettingsObserver mBinderCallsSettingsObserver;
private static class EventSimStateChangedBag {
final int mPhoneId;
@@ -216,6 +223,53 @@
}
}
+ // Some carrier config settings disable the network lock screen, so we call handleSimLock
+ // when either SIM_LOCK or CARRIER_CONFIG changes so that no matter which one happens first,
+ // we still do the right thing
+ private void handleSimLock(int subType, Phone phone) {
+ PersistableBundle cc = getCarrierConfigForSubId(phone.getSubId());
+ if (!CarrierConfigManager.isConfigForIdentifiedCarrier(cc)) {
+ // If we only have the default carrier config just return, to avoid popping up the
+ // the SIM lock screen when it's disabled by the carrier.
+ Log.i(LOG_TAG, "Not showing 'SIM network unlock' screen. Carrier config not loaded");
+ return;
+ }
+ if (cc.getBoolean(CarrierConfigManager.KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL)) {
+ // Some products don't have the concept of a "SIM network lock"
+ Log.i(LOG_TAG, "Not showing 'SIM network unlock' screen. Disabled by carrier config");
+ return;
+ }
+
+ // if passed in subType is unknown, retrieve it here.
+ if (subType == -1) {
+ final UiccCard uiccCard = phone.getUiccCard();
+ if (uiccCard == null) {
+ Log.e(LOG_TAG,
+ "handleSimLock: uiccCard for phone " + phone.getPhoneId() + " is null");
+ return;
+ }
+ final UiccProfile uiccProfile = uiccCard.getUiccProfile();
+ if (uiccProfile == null) {
+ Log.e(LOG_TAG,
+ "handleSimLock: uiccProfile for phone " + phone.getPhoneId() + " is null");
+ return;
+ }
+ subType = uiccProfile.getApplication(
+ uiccProfile.mCurrentAppType).getPersoSubState().ordinal();
+ }
+ // Normal case: show the "SIM network unlock" PIN entry screen.
+ // The user won't be able to do anything else until
+ // they enter a valid SIM network PIN.
+ Log.i(LOG_TAG, "show sim depersonal panel");
+ IccNetworkDepersonalizationPanel.showDialog(phone, subType);
+ }
+
+ private boolean isSimLocked(Phone phone) {
+ TelephonyManager tm = getSystemService(TelephonyManager.class);
+ return tm.createForSubscriptionId(phone.getSubId()).getSimState()
+ == TelephonyManager.SIM_STATE_NETWORK_LOCKED;
+ }
+
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
@@ -225,20 +279,9 @@
// TODO: This event should be handled by the lock screen, just
// like the "SIM missing" and "Sim locked" cases (bug 1804111).
case EVENT_SIM_NETWORK_LOCKED:
- if (getCarrierConfig().getBoolean(
- CarrierConfigManager.KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL)) {
- // Some products don't have the concept of a "SIM network lock"
- Log.i(LOG_TAG, "Ignoring EVENT_SIM_NETWORK_LOCKED event; "
- + "not showing 'SIM network unlock' PIN entry screen");
- } else {
- // Normal case: show the "SIM network unlock" PIN entry screen.
- // The user won't be able to do anything else until
- // they enter a valid SIM network PIN.
- Log.i(LOG_TAG, "show sim depersonal panel");
- Phone phone = (Phone) ((AsyncResult) msg.obj).userObj;
- int subType = (Integer)((AsyncResult)msg.obj).result;
- IccNetworkDepersonalizationPanel.showDialog(phone, subType);
- }
+ int subType = (Integer) ((AsyncResult) msg.obj).result;
+ Phone phone = (Phone) ((AsyncResult) msg.obj).userObj;
+ handleSimLock(subType, phone);
break;
case EVENT_DATA_ROAMING_DISCONNECTED:
@@ -267,7 +310,9 @@
// process.
EventSimStateChangedBag bag = (EventSimStateChangedBag)msg.obj;
if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(bag.mIccStatus)
- || IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(bag.mIccStatus)) {
+ || IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(bag.mIccStatus)
+ || IccCardConstants.INTENT_VALUE_ICC_NOT_READY.equals(bag.mIccStatus)
+ || IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(bag.mIccStatus)) {
// when the right event is triggered and there
// are UI objects in the foreground, we close
// them to display the lock panel.
@@ -281,7 +326,7 @@
mPUKEntryProgressDialog.dismiss();
mPUKEntryProgressDialog = null;
}
- Log.i(LOG_TAG, "Dismissing depersonal panel");
+ Log.i(LOG_TAG, "Dismissing depersonal panel" + (bag.mIccStatus));
IccNetworkDepersonalizationPanel.dialogDismiss(bag.mPhoneId);
}
break;
@@ -309,6 +354,12 @@
// The voicemail number could be overridden by carrier config, so need to
// refresh the message waiting (voicemail) indicator.
refreshMwiIndicator(subId);
+ phone = getPhone(subId);
+ if (phone != null && isSimLocked(phone)) {
+ // pass in subType=-1 so handleSimLock can find the actual subType if
+ // needed. This is safe as valid values for subType are >= 0
+ handleSimLock(-1, phone);
+ }
break;
}
}
@@ -325,6 +376,9 @@
ContentResolver resolver = getContentResolver();
+ // Initialize the shim from frameworks/opt/telephony into packages/services/Telephony.
+ TelephonyLocalConnection.setInstance(new LocalConnectionImpl(this));
+
// Cache the "voice capable" flag.
// This flag currently comes from a resource (which is
// overrideable on a per-product basis):
@@ -353,9 +407,29 @@
R.string.config_ims_mmtel_package);
String defaultImsRcsPackage = getResources().getString(
R.string.config_ims_rcs_package);
- mImsResolver = new ImsResolver(this, defaultImsMmtelPackage,
- defaultImsRcsPackage, PhoneFactory.getPhones().length);
- mImsResolver.initialize();
+ ImsResolver.make(this, defaultImsMmtelPackage,
+ defaultImsRcsPackage, PhoneFactory.getPhones().length,
+ new ImsFeatureBinderRepository());
+ ImsResolver.getInstance().initialize();
+
+ // With the IMS phone created, load static config.xml values from the phone process
+ // so that it can be provided to the ImsPhoneCallTracker.
+ for (Phone p : PhoneFactory.getPhones()) {
+ Phone imsPhone = p.getImsPhone();
+ if (imsPhone != null && imsPhone instanceof ImsPhone) {
+ ImsPhone theImsPhone = (ImsPhone) imsPhone;
+ if (theImsPhone.getCallTracker() instanceof ImsPhoneCallTracker) {
+ ImsPhoneCallTracker ict = (ImsPhoneCallTracker)
+ theImsPhone.getCallTracker();
+
+ ImsPhoneCallTracker.Config config = new ImsPhoneCallTracker.Config();
+ config.isD2DCommunicationSupported = getResources().getBoolean(
+ R.bool.config_use_device_to_device_communication);
+ ict.setConfig(config);
+ }
+ }
+ }
+ RcsProvisioningMonitor.make(this);
}
// Start TelephonyDebugService After the default phone is created.
@@ -458,6 +532,13 @@
SettingsConstants.HAC_KEY + "=" + (hac == SettingsConstants.HAC_ENABLED
? SettingsConstants.HAC_VAL_ON : SettingsConstants.HAC_VAL_OFF));
}
+
+ // Start tracking Binder latency for the phone process.
+ mBinderCallsSettingsObserver = new BinderCallsStats.SettingsObserver(
+ getApplicationContext(),
+ new BinderCallsStats(
+ new BinderCallsStats.Injector(),
+ com.android.internal.os.BinderLatencyProto.Dims.TELEPHONY));
}
/**
@@ -483,10 +564,6 @@
return PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId));
}
- public ImsResolver getImsResolver() {
- return mImsResolver;
- }
-
/* package */ CallManager getCallManager() {
return mCM;
}
@@ -915,6 +992,23 @@
}
/**
+ * @return whether the device supports RCS User Capability Exchange or not.
+ */
+ public boolean getDeviceUceEnabled() {
+ return (mTelephonyRcsService == null) ? false : mTelephonyRcsService.isDeviceUceEnabled();
+ }
+
+ /**
+ * Set the device supports RCS User Capability Exchange.
+ * @param isEnabled true if the device supports UCE.
+ */
+ public void setDeviceUceEnabled(boolean isEnabled) {
+ if (mTelephonyRcsService != null) {
+ mTelephonyRcsService.setDeviceUceEnabled(isEnabled);
+ }
+ }
+
+ /**
* Dump the state of the object, add calls to other objects as desired.
*
* @param fd File descriptor
@@ -935,7 +1029,7 @@
pw.println("ImsResolver:");
pw.increaseIndent();
try {
- if (mImsResolver != null) mImsResolver.dump(fd, pw, args);
+ if (ImsResolver.getInstance() != null) ImsResolver.getInstance().dump(fd, pw, args);
} catch (Exception e) {
e.printStackTrace();
}
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 244d214..2ac3d2c 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -23,17 +23,19 @@
import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_IMS;
import static com.android.internal.telephony.PhoneConstants.SUBSCRIPTION_KEY;
+import android.Manifest;
import android.Manifest.permission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.PendingIntent;
+import android.app.compat.CompatChanges;
+import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -54,6 +56,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;
@@ -66,6 +69,7 @@
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.Annotation.ApnType;
+import android.telephony.Annotation.ThermalMitigationResult;
import android.telephony.CallForwardingInfo;
import android.telephony.CarrierConfigManager;
import android.telephony.CarrierRestrictionRules;
@@ -76,6 +80,8 @@
import android.telephony.CellInfoGsm;
import android.telephony.CellInfoWcdma;
import android.telephony.ClientRequestStats;
+import android.telephony.DataThrottlingRequest;
+import android.telephony.IBootstrapAuthenticationCallback;
import android.telephony.ICellInfoCallback;
import android.telephony.IccOpenLogicalChannelResponse;
import android.telephony.LocationAccessPolicy;
@@ -88,29 +94,36 @@
import android.telephony.RadioAccessSpecifier;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
+import android.telephony.SignalStrengthUpdateRequest;
+import android.telephony.SignalThresholdInfo;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.TelephonyHistogram;
import android.telephony.TelephonyManager;
import android.telephony.TelephonyScanManager;
+import android.telephony.ThermalMitigationRequest;
import android.telephony.UiccAccessRule;
import android.telephony.UiccCardInfo;
import android.telephony.UiccSlotInfo;
import android.telephony.UssdResponse;
import android.telephony.VisualVoicemailSmsFilterSettings;
import android.telephony.data.ApnSetting;
+import android.telephony.data.NetworkSlicingConfig;
import android.telephony.emergency.EmergencyNumber;
+import android.telephony.gba.GbaAuthRequest;
+import android.telephony.gba.UaSecurityProtocolIdentifier;
import android.telephony.ims.ImsException;
import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.RcsClientConfiguration;
+import android.telephony.ims.RcsContactUceCapability;
import android.telephony.ims.RegistrationManager;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.IImsConfig;
import android.telephony.ims.aidl.IImsConfigCallback;
-import android.telephony.ims.aidl.IImsMmTelFeature;
-import android.telephony.ims.aidl.IImsRcsFeature;
import android.telephony.ims.aidl.IImsRegistration;
import android.telephony.ims.aidl.IImsRegistrationCallback;
+import android.telephony.ims.aidl.IRcsConfigCallback;
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.feature.RcsFeature;
@@ -124,18 +137,22 @@
import com.android.ims.ImsManager;
import com.android.ims.internal.IImsServiceFeatureCallback;
+import com.android.ims.rcs.uce.eab.EabUtil;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.CallForwardInfo;
import com.android.internal.telephony.CallManager;
import com.android.internal.telephony.CallStateException;
-import com.android.internal.telephony.CarrierInfoManager;
+import com.android.internal.telephony.CallTracker;
import com.android.internal.telephony.CarrierResolver;
import com.android.internal.telephony.CellNetworkScanResult;
import com.android.internal.telephony.CommandException;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.DefaultPhoneNotifier;
+import com.android.internal.telephony.GbaManager;
import com.android.internal.telephony.GsmCdmaPhone;
import com.android.internal.telephony.HalVersion;
import com.android.internal.telephony.IBooleanConsumer;
+import com.android.internal.telephony.ICallForwardingInfoCallback;
import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.INumberVerificationCallback;
import com.android.internal.telephony.ITelephony;
@@ -151,6 +168,7 @@
import com.android.internal.telephony.ProxyController;
import com.android.internal.telephony.RIL;
import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.RadioInterfaceCapabilityController;
import com.android.internal.telephony.ServiceStateTracker;
import com.android.internal.telephony.SmsController;
import com.android.internal.telephony.SmsPermissions;
@@ -176,15 +194,24 @@
import com.android.internal.telephony.uicc.UiccSlot;
import com.android.internal.telephony.util.LocaleUtils;
import com.android.internal.telephony.util.VoicemailNotificationSettingsUtil;
+import com.android.internal.util.FunctionalUtils;
import com.android.internal.util.HexDump;
+import com.android.phone.callcomposer.CallComposerPictureManager;
+import com.android.phone.callcomposer.CallComposerPictureTransfer;
+import com.android.phone.callcomposer.ImageData;
import com.android.phone.settings.PickSmsSubscriptionActivity;
import com.android.phone.vvm.PhoneAccountHandleConverter;
import com.android.phone.vvm.RemoteVvmTaskManager;
import com.android.phone.vvm.VisualVoicemailSettingsUtil;
import com.android.phone.vvm.VisualVoicemailSmsFilterConfig;
+import com.android.services.telephony.TelecomAccountRegistry;
+import com.android.services.telephony.TelephonyConnectionService;
import com.android.telephony.Rlog;
+import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -194,7 +221,9 @@
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
@@ -223,10 +252,8 @@
private static final int EVENT_NV_WRITE_CDMA_PRL_DONE = 18;
private static final int CMD_RESET_MODEM_CONFIG = 19;
private static final int EVENT_RESET_MODEM_CONFIG_DONE = 20;
- private static final int CMD_GET_PREFERRED_NETWORK_TYPE = 21;
- private static final int EVENT_GET_PREFERRED_NETWORK_TYPE_DONE = 22;
- private static final int CMD_SET_PREFERRED_NETWORK_TYPE = 23;
- private static final int EVENT_SET_PREFERRED_NETWORK_TYPE_DONE = 24;
+ private static final int CMD_GET_ALLOWED_NETWORK_TYPES_BITMASK = 21;
+ private static final int EVENT_GET_ALLOWED_NETWORK_TYPES_BITMASK_DONE = 22;
private static final int CMD_SEND_ENVELOPE = 25;
private static final int EVENT_SEND_ENVELOPE_DONE = 26;
private static final int CMD_INVOKE_OEM_RIL_REQUEST_RAW = 27;
@@ -293,6 +320,28 @@
private static final int EVENT_GET_CALL_WAITING_DONE = 88;
private static final int CMD_SET_CALL_WAITING = 89;
private static final int EVENT_SET_CALL_WAITING_DONE = 90;
+ private static final int CMD_ENABLE_NR_DUAL_CONNECTIVITY = 91;
+ private static final int EVENT_ENABLE_NR_DUAL_CONNECTIVITY_DONE = 92;
+ private static final int CMD_IS_NR_DUAL_CONNECTIVITY_ENABLED = 93;
+ private static final int EVENT_IS_NR_DUAL_CONNECTIVITY_ENABLED_DONE = 94;
+ private static final int CMD_GET_CDMA_SUBSCRIPTION_MODE = 95;
+ private static final int EVENT_GET_CDMA_SUBSCRIPTION_MODE_DONE = 96;
+ private static final int CMD_GET_SYSTEM_SELECTION_CHANNELS = 97;
+ private static final int EVENT_GET_SYSTEM_SELECTION_CHANNELS_DONE = 98;
+ private static final int CMD_SET_DATA_THROTTLING = 99;
+ private static final int EVENT_SET_DATA_THROTTLING_DONE = 100;
+ private static final int CMD_SET_SIM_POWER = 101;
+ private static final int EVENT_SET_SIM_POWER_DONE = 102;
+ private static final int CMD_SET_SIGNAL_STRENGTH_UPDATE_REQUEST = 103;
+ 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_SET_ALLOWED_NETWORK_TYPES_FOR_REASON = 107;
+ private static final int EVENT_SET_ALLOWED_NETWORK_TYPES_FOR_REASON_DONE = 108;
+ private static final int CMD_PREPARE_UNATTENDED_REBOOT = 109;
+ private static final int CMD_GET_SLICING_CONFIG = 110;
+ private static final int EVENT_GET_SLICING_CONFIG_DONE = 111;
+ private static final int CMD_ERASE_DATA_SHARED_PREFERENCES = 112;
// Parameters of select command.
private static final int SELECT_COMMAND = 0xA4;
@@ -302,21 +351,26 @@
/** The singleton instance. */
private static PhoneInterfaceManager sInstance;
+ private static List<String> sThermalMitigationAllowlistedPackages = new ArrayList<>();
private PhoneGlobals mApp;
private CallManager mCM;
private ImsResolver mImsResolver;
private UserManager mUserManager;
private AppOpsManager mAppOps;
+ private PackageManager mPm;
private MainThreadHandler mMainThreadHandler;
private SubscriptionController mSubscriptionController;
private SharedPreferences mTelephonySharedPreferences;
private PhoneConfigurationManager mPhoneConfigurationManager;
+ private final RadioInterfaceCapabilityController mRadioInterfaceCapabilities;
/** User Activity */
private AtomicBoolean mNotifyUserActivity;
private static final int USER_ACTIVITY_NOTIFICATION_DELAY = 200;
+ private Set<Integer> mCarrierPrivilegeTestOverrideSubIds = new ArraySet<>();
+
private static final String PREF_CARRIERS_ALPHATAG_PREFIX = "carrier_alphtag_";
private static final String PREF_CARRIERS_NUMBER_PREFIX = "carrier_number_";
private static final String PREF_CARRIERS_SUBSCRIBER_PREFIX = "carrier_subscriber_";
@@ -333,12 +387,17 @@
private static final int TYPE_ALLOCATION_CODE_LENGTH = 8;
private static final int MANUFACTURER_CODE_LENGTH = 8;
+ private static final int SET_DATA_THROTTLING_MODEM_THREW_INVALID_PARAMS = -1;
+ private static final int MODEM_DOES_NOT_SUPPORT_DATA_THROTTLING_ERROR_CODE = -2;
+
/**
* Experiment flag to enable erase modem config on reset network, default value is false
*/
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.
*/
@@ -753,40 +812,136 @@
handleNullReturnEvent(msg, "resetModemConfig");
break;
- case CMD_GET_PREFERRED_NETWORK_TYPE:
+ case CMD_IS_NR_DUAL_CONNECTIVITY_ENABLED: {
request = (MainThreadRequest) msg.obj;
- onCompleted = obtainMessage(EVENT_GET_PREFERRED_NETWORK_TYPE_DONE, request);
- getPhoneFromRequest(request).getPreferredNetworkType(onCompleted);
+ onCompleted = obtainMessage(EVENT_IS_NR_DUAL_CONNECTIVITY_ENABLED_DONE,
+ request);
+ Phone phone = getPhoneFromRequest(request);
+ if (phone != null) {
+ phone.isNrDualConnectivityEnabled(onCompleted, request.workSource);
+ } else {
+ loge("isNRDualConnectivityEnabled: No phone object");
+ request.result = false;
+ notifyRequester(request);
+ }
break;
+ }
- case EVENT_GET_PREFERRED_NETWORK_TYPE_DONE:
+ case EVENT_IS_NR_DUAL_CONNECTIVITY_ENABLED_DONE:
ar = (AsyncResult) msg.obj;
request = (MainThreadRequest) ar.userObj;
if (ar.exception == null && ar.result != null) {
- request.result = ar.result; // Integer
+ request.result = ar.result;
} else {
- request.result = null;
- if (ar.result == null) {
- loge("getPreferredNetworkType: Empty response");
- } else if (ar.exception instanceof CommandException) {
- loge("getPreferredNetworkType: CommandException: " +
- ar.exception);
+ // request.result must be set to something non-null
+ // for the calling thread to unblock
+ if (request.result != null) {
+ request.result = ar.result;
} else {
- loge("getPreferredNetworkType: Unknown exception");
+ request.result = false;
+ }
+ if (ar.result == null) {
+ loge("isNRDualConnectivityEnabled: Empty response");
+ } else if (ar.exception instanceof CommandException) {
+ loge("isNRDualConnectivityEnabled: CommandException: "
+ + ar.exception);
+ } else {
+ loge("isNRDualConnectivityEnabled: Unknown exception");
}
}
notifyRequester(request);
break;
- case CMD_SET_PREFERRED_NETWORK_TYPE:
+ case CMD_ENABLE_NR_DUAL_CONNECTIVITY: {
request = (MainThreadRequest) msg.obj;
- onCompleted = obtainMessage(EVENT_SET_PREFERRED_NETWORK_TYPE_DONE, request);
- int networkType = (Integer) request.argument;
- getPhoneFromRequest(request).setPreferredNetworkType(networkType, onCompleted);
+ onCompleted = obtainMessage(EVENT_ENABLE_NR_DUAL_CONNECTIVITY_DONE, request);
+ Phone phone = getPhoneFromRequest(request);
+ if (phone != null) {
+ phone.setNrDualConnectivityState((int) request.argument, onCompleted,
+ request.workSource);
+ } else {
+ loge("enableNrDualConnectivity: No phone object");
+ request.result =
+ TelephonyManager.ENABLE_NR_DUAL_CONNECTIVITY_RADIO_NOT_AVAILABLE;
+ notifyRequester(request);
+ }
+ break;
+ }
+
+ case EVENT_ENABLE_NR_DUAL_CONNECTIVITY_DONE: {
+ ar = (AsyncResult) msg.obj;
+ request = (MainThreadRequest) ar.userObj;
+ if (ar.exception == null) {
+ request.result =
+ TelephonyManager.ENABLE_NR_DUAL_CONNECTIVITY_SUCCESS;
+ } else {
+ request.result =
+ TelephonyManager
+ .ENABLE_NR_DUAL_CONNECTIVITY_RADIO_ERROR;
+ if (ar.exception instanceof CommandException) {
+ CommandException.Error error =
+ ((CommandException) (ar.exception)).getCommandError();
+ if (error == CommandException.Error.RADIO_NOT_AVAILABLE) {
+ request.result =
+ TelephonyManager
+ .ENABLE_NR_DUAL_CONNECTIVITY_RADIO_NOT_AVAILABLE;
+ } else if (error == CommandException.Error.REQUEST_NOT_SUPPORTED) {
+ request.result =
+ TelephonyManager
+ .ENABLE_NR_DUAL_CONNECTIVITY_NOT_SUPPORTED;
+ }
+ loge("enableNrDualConnectivity" + ": CommandException: "
+ + ar.exception);
+ } else {
+ loge("enableNrDualConnectivity" + ": Unknown exception");
+ }
+ }
+ notifyRequester(request);
+ break;
+ }
+
+ case CMD_GET_ALLOWED_NETWORK_TYPES_BITMASK:
+ request = (MainThreadRequest) msg.obj;
+ onCompleted = obtainMessage(EVENT_GET_ALLOWED_NETWORK_TYPES_BITMASK_DONE,
+ request);
+ getPhoneFromRequest(request).getAllowedNetworkTypesBitmask(onCompleted);
break;
- case EVENT_SET_PREFERRED_NETWORK_TYPE_DONE:
- handleNullReturnEvent(msg, "setPreferredNetworkType");
+ case EVENT_GET_ALLOWED_NETWORK_TYPES_BITMASK_DONE:
+ ar = (AsyncResult) msg.obj;
+ request = (MainThreadRequest) ar.userObj;
+ if (ar.exception == null && ar.result != null) {
+ request.result = ar.result; // Integer
+ } else {
+ // request.result must be set to something non-null
+ // for the calling thread to unblock
+ request.result = new int[]{-1};
+ if (ar.result == null) {
+ loge("getAllowedNetworkTypesBitmask: Empty response");
+ } else if (ar.exception instanceof CommandException) {
+ loge("getAllowedNetworkTypesBitmask: CommandException: "
+ + ar.exception);
+ } else {
+ loge("getAllowedNetworkTypesBitmask: Unknown exception");
+ }
+ }
+ notifyRequester(request);
+ break;
+
+ case CMD_SET_ALLOWED_NETWORK_TYPES_FOR_REASON:
+ request = (MainThreadRequest) msg.obj;
+ onCompleted = obtainMessage(EVENT_SET_ALLOWED_NETWORK_TYPES_FOR_REASON_DONE,
+ request);
+ Pair<Integer, Long> reasonWithNetworkTypes =
+ (Pair<Integer, Long>) request.argument;
+ getPhoneFromRequest(request).setAllowedNetworkTypes(
+ reasonWithNetworkTypes.first,
+ reasonWithNetworkTypes.second,
+ onCompleted);
+ break;
+
+ case EVENT_SET_ALLOWED_NETWORK_TYPES_FOR_REASON_DONE:
+ handleNullReturnEvent(msg, "setAllowedNetworkTypesForReason");
break;
case CMD_INVOKE_OEM_RIL_REQUEST_RAW:
@@ -831,38 +986,43 @@
getPhoneFromRequest(request).getAvailableNetworks(onCompleted);
break;
- case CMD_GET_CALL_FORWARDING:
+ case CMD_GET_CALL_FORWARDING: {
request = (MainThreadRequest) msg.obj;
onCompleted = obtainMessage(EVENT_GET_CALL_FORWARDING_DONE, request);
- int callForwardingReason = (Integer) request.argument;
- getPhoneFromRequest(request).getCallForwardingOption(
- callForwardingReason, onCompleted);
+ Pair<Integer, TelephonyManager.CallForwardingInfoCallback> args =
+ (Pair<Integer, TelephonyManager.CallForwardingInfoCallback>)
+ request.argument;
+ int callForwardingReason = args.first;
+ request.phone.getCallForwardingOption(callForwardingReason, onCompleted);
break;
-
- case EVENT_GET_CALL_FORWARDING_DONE:
+ }
+ case EVENT_GET_CALL_FORWARDING_DONE: {
ar = (AsyncResult) msg.obj;
request = (MainThreadRequest) ar.userObj;
- CallForwardingInfo callForwardingInfo = null;
+ TelephonyManager.CallForwardingInfoCallback callback =
+ ((Pair<Integer, TelephonyManager.CallForwardingInfoCallback>)
+ request.argument).second;
if (ar.exception == null && ar.result != null) {
+ CallForwardingInfo callForwardingInfo = null;
CallForwardInfo[] callForwardInfos = (CallForwardInfo[]) ar.result;
for (CallForwardInfo callForwardInfo : callForwardInfos) {
// Service Class is a bit mask per 3gpp 27.007. Search for
// any service for voice call.
if ((callForwardInfo.serviceClass
& CommandsInterface.SERVICE_CLASS_VOICE) > 0) {
- callForwardingInfo = new CallForwardingInfo(
- callForwardInfo.serviceClass, callForwardInfo.reason,
- callForwardInfo.number,
- callForwardInfo.timeSeconds);
+ callForwardingInfo = new CallForwardingInfo(true,
+ callForwardInfo.reason,
+ callForwardInfo.number,
+ callForwardInfo.timeSeconds);
break;
}
}
// Didn't find a call forward info for voice call.
if (callForwardingInfo == null) {
- callForwardingInfo = new CallForwardingInfo(
- CallForwardingInfo.STATUS_UNKNOWN_ERROR,
- 0 /* reason */, null /* number */, 0 /* timeout */);
+ callForwardingInfo = new CallForwardingInfo(false /* enabled */,
+ 0 /* reason */, null /* number */, 0 /* timeout */);
}
+ callback.onCallForwardingInfoAvailable(callForwardingInfo);
} else {
if (ar.result == null) {
loge("EVENT_GET_CALL_FORWARDING_DONE: Empty response");
@@ -870,56 +1030,80 @@
if (ar.exception != null) {
loge("EVENT_GET_CALL_FORWARDING_DONE: Exception: " + ar.exception);
}
- int errorCode = CallForwardingInfo.STATUS_UNKNOWN_ERROR;
+ int errorCode = TelephonyManager
+ .CallForwardingInfoCallback.RESULT_ERROR_UNKNOWN;
if (ar.exception instanceof CommandException) {
CommandException.Error error =
((CommandException) (ar.exception)).getCommandError();
if (error == CommandException.Error.FDN_CHECK_FAILURE) {
- errorCode = CallForwardingInfo.STATUS_FDN_CHECK_FAILURE;
+ errorCode = TelephonyManager
+ .CallForwardingInfoCallback.RESULT_ERROR_FDN_CHECK_FAILURE;
} else if (error == CommandException.Error.REQUEST_NOT_SUPPORTED) {
- errorCode = CallForwardingInfo.STATUS_NOT_SUPPORTED;
+ errorCode = TelephonyManager
+ .CallForwardingInfoCallback.RESULT_ERROR_NOT_SUPPORTED;
}
}
- callForwardingInfo = new CallForwardingInfo(
- errorCode, 0 /* reason */, null /* number */, 0 /* timeout */);
+ callback.onError(errorCode);
}
- request.result = callForwardingInfo;
- notifyRequester(request);
break;
+ }
- case CMD_SET_CALL_FORWARDING:
+ case CMD_SET_CALL_FORWARDING: {
request = (MainThreadRequest) msg.obj;
onCompleted = obtainMessage(EVENT_SET_CALL_FORWARDING_DONE, request);
+ request = (MainThreadRequest) msg.obj;
CallForwardingInfo callForwardingInfoToSet =
- (CallForwardingInfo) request.argument;
- getPhoneFromRequest(request).setCallForwardingOption(
- callForwardingInfoToSet.getStatus(),
+ ((Pair<CallForwardingInfo, Consumer<Integer>>)
+ request.argument).first;
+ request.phone.setCallForwardingOption(
+ callForwardingInfoToSet.isEnabled()
+ ? CommandsInterface.CF_ACTION_ENABLE
+ : CommandsInterface.CF_ACTION_DISABLE,
callForwardingInfoToSet.getReason(),
callForwardingInfoToSet.getNumber(),
callForwardingInfoToSet.getTimeoutSeconds(), onCompleted);
break;
+ }
- case EVENT_SET_CALL_FORWARDING_DONE:
+ case EVENT_SET_CALL_FORWARDING_DONE: {
ar = (AsyncResult) msg.obj;
request = (MainThreadRequest) ar.userObj;
- if (ar.exception == null) {
- request.result = true;
- } else {
- request.result = false;
+ Consumer<Integer> callback =
+ ((Pair<CallForwardingInfo, Consumer<Integer>>)
+ request.argument).second;
+ if (ar.exception != null) {
loge("setCallForwarding exception: " + ar.exception);
+ int errorCode = TelephonyManager.CallForwardingInfoCallback
+ .RESULT_ERROR_UNKNOWN;
+ if (ar.exception instanceof CommandException) {
+ CommandException.Error error =
+ ((CommandException) (ar.exception)).getCommandError();
+ if (error == CommandException.Error.FDN_CHECK_FAILURE) {
+ errorCode = TelephonyManager.CallForwardingInfoCallback
+ .RESULT_ERROR_FDN_CHECK_FAILURE;
+ } else if (error == CommandException.Error.REQUEST_NOT_SUPPORTED) {
+ errorCode = TelephonyManager.CallForwardingInfoCallback
+ .RESULT_ERROR_NOT_SUPPORTED;
+ }
+ }
+ callback.accept(errorCode);
+ } else {
+ callback.accept(TelephonyManager.CallForwardingInfoCallback.RESULT_SUCCESS);
}
- notifyRequester(request);
break;
+ }
- case CMD_GET_CALL_WAITING:
+ case CMD_GET_CALL_WAITING: {
request = (MainThreadRequest) msg.obj;
onCompleted = obtainMessage(EVENT_GET_CALL_WAITING_DONE, request);
getPhoneFromRequest(request).getCallWaiting(onCompleted);
break;
+ }
- case EVENT_GET_CALL_WAITING_DONE:
+ case EVENT_GET_CALL_WAITING_DONE: {
ar = (AsyncResult) msg.obj;
request = (MainThreadRequest) ar.userObj;
+ Consumer<Integer> callback = (Consumer<Integer>) request.argument;
int callForwardingStatus = TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR;
if (ar.exception == null && ar.result != null) {
int[] callForwardResults = (int[]) ar.result;
@@ -927,12 +1111,12 @@
// Search for any service for voice call.
if (callForwardResults.length > 1
&& ((callForwardResults[1]
- & CommandsInterface.SERVICE_CLASS_VOICE) > 0)) {
+ & CommandsInterface.SERVICE_CLASS_VOICE) > 0)) {
callForwardingStatus = callForwardResults[0] == 0
- ? TelephonyManager.CALL_WAITING_STATUS_INACTIVE
- : TelephonyManager.CALL_WAITING_STATUS_ACTIVE;
+ ? TelephonyManager.CALL_WAITING_STATUS_DISABLED
+ : TelephonyManager.CALL_WAITING_STATUS_ENABLED;
} else {
- callForwardingStatus = TelephonyManager.CALL_WAITING_STATUS_INACTIVE;
+ callForwardingStatus = TelephonyManager.CALL_WAITING_STATUS_DISABLED;
}
} else {
if (ar.result == null) {
@@ -950,29 +1134,43 @@
}
}
}
- request.result = callForwardingStatus;
- notifyRequester(request);
+ callback.accept(callForwardingStatus);
break;
+ }
- case CMD_SET_CALL_WAITING:
+ case CMD_SET_CALL_WAITING: {
request = (MainThreadRequest) msg.obj;
onCompleted = obtainMessage(EVENT_SET_CALL_WAITING_DONE, request);
- boolean isEnable = (Boolean) request.argument;
- getPhoneFromRequest(request).setCallWaiting(isEnable, onCompleted);
+ boolean enable = ((Pair<Boolean, Consumer<Integer>>) request.argument).first;
+ getPhoneFromRequest(request).setCallWaiting(enable, onCompleted);
break;
+ }
- case EVENT_SET_CALL_WAITING_DONE:
+ case EVENT_SET_CALL_WAITING_DONE: {
ar = (AsyncResult) msg.obj;
request = (MainThreadRequest) ar.userObj;
- if (ar.exception == null) {
- request.result = true;
- } else {
- request.result = false;
+ boolean enable = ((Pair<Boolean, Consumer<Integer>>) request.argument).first;
+ Consumer<Integer> callback =
+ ((Pair<Boolean, Consumer<Integer>>) request.argument).second;
+ if (ar.exception != null) {
loge("setCallWaiting exception: " + ar.exception);
+ if (ar.exception instanceof CommandException) {
+ CommandException.Error error =
+ ((CommandException) (ar.exception)).getCommandError();
+ if (error == CommandException.Error.REQUEST_NOT_SUPPORTED) {
+ callback.accept(TelephonyManager.CALL_WAITING_STATUS_NOT_SUPPORTED);
+ } else {
+ callback.accept(TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR);
+ }
+ } else {
+ callback.accept(TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR);
+ }
+ } else {
+ callback.accept(enable ? TelephonyManager.CALL_WAITING_STATUS_ENABLED
+ : TelephonyManager.CALL_WAITING_STATUS_DISABLED);
}
- notifyRequester(request);
break;
-
+ }
case EVENT_PERFORM_NETWORK_SCAN_DONE:
ar = (AsyncResult) msg.obj;
request = (MainThreadRequest) ar.userObj;
@@ -1036,29 +1234,31 @@
ResultReceiver result = (ResultReceiver) request.argument;
Bundle bundle = new Bundle();
bundle.putParcelable(TelephonyManager.MODEM_ACTIVITY_RESULT_KEY,
- new ModemActivityInfo(0, 0, 0, new int[0], 0));
+ new ModemActivityInfo(0, 0, 0,
+ new int[ModemActivityInfo.getNumTxPowerLevels()], 0));
result.send(0, bundle);
}
break;
- case EVENT_GET_MODEM_ACTIVITY_INFO_DONE:
+ case EVENT_GET_MODEM_ACTIVITY_INFO_DONE: {
ar = (AsyncResult) msg.obj;
request = (MainThreadRequest) ar.userObj;
ResultReceiver result = (ResultReceiver) request.argument;
- ModemActivityInfo ret = new ModemActivityInfo(0, 0, 0, new int[0], 0);
+ ModemActivityInfo ret = null;
+ int error = 0;
if (ar.exception == null && ar.result != null) {
// Update the last modem activity info and the result of the request.
ModemActivityInfo info = (ModemActivityInfo) ar.result;
if (isModemActivityInfoValid(info)) {
- int[] mergedTxTimeMs = new int[ModemActivityInfo.TX_POWER_LEVELS];
+ int[] mergedTxTimeMs = new int[ModemActivityInfo.getNumTxPowerLevels()];
int[] txTimeMs = info.getTransmitTimeMillis();
int[] lastModemTxTimeMs = mLastModemActivityInfo
.getTransmitTimeMillis();
for (int i = 0; i < mergedTxTimeMs.length; i++) {
mergedTxTimeMs[i] = txTimeMs[i] + lastModemTxTimeMs[i];
}
- mLastModemActivityInfo.setTimestamp(info.getTimestamp());
+ mLastModemActivityInfo.setTimestamp(info.getTimestampMillis());
mLastModemActivityInfo.setSleepTimeMillis(info.getSleepTimeMillis()
+ mLastModemActivityInfo.getSleepTimeMillis());
mLastModemActivityInfo.setIdleTimeMillis(info.getIdleTimeMillis()
@@ -1068,7 +1268,7 @@
info.getReceiveTimeMillis()
+ mLastModemActivityInfo.getReceiveTimeMillis());
}
- ret = new ModemActivityInfo(mLastModemActivityInfo.getTimestamp(),
+ ret = new ModemActivityInfo(mLastModemActivityInfo.getTimestampMillis(),
mLastModemActivityInfo.getSleepTimeMillis(),
mLastModemActivityInfo.getIdleTimeMillis(),
mLastModemActivityInfo.getTransmitTimeMillis(),
@@ -1076,18 +1276,29 @@
} else {
if (ar.result == null) {
loge("queryModemActivityInfo: Empty response");
+ error = TelephonyManager.ModemActivityInfoException
+ .ERROR_INVALID_INFO_RECEIVED;
} else if (ar.exception instanceof CommandException) {
loge("queryModemActivityInfo: CommandException: " +
ar.exception);
+ error = TelephonyManager.ModemActivityInfoException
+ .ERROR_MODEM_RESPONSE_ERROR;
} else {
loge("queryModemActivityInfo: Unknown exception");
+ error = TelephonyManager.ModemActivityInfoException
+ .ERROR_UNKNOWN;
}
}
Bundle bundle = new Bundle();
- bundle.putParcelable(TelephonyManager.MODEM_ACTIVITY_RESULT_KEY, ret);
+ if (ret != null) {
+ bundle.putParcelable(TelephonyManager.MODEM_ACTIVITY_RESULT_KEY, ret);
+ } else {
+ bundle.putInt(TelephonyManager.EXCEPTION_RESULT_KEY, error);
+ }
result.send(0, bundle);
notifyRequester(request);
break;
+ }
case CMD_SET_ALLOWED_CARRIERS:
request = (MainThreadRequest) msg.obj;
@@ -1250,11 +1461,27 @@
request.result = ar.exception == null;
notifyRequester(request);
break;
+ case CMD_GET_CDMA_SUBSCRIPTION_MODE:
+ request = (MainThreadRequest) msg.obj;
+ onCompleted = obtainMessage(EVENT_GET_CDMA_SUBSCRIPTION_MODE_DONE, request);
+ getPhoneFromRequest(request).queryCdmaSubscriptionMode(onCompleted);
+ break;
+ case EVENT_GET_CDMA_SUBSCRIPTION_MODE_DONE:
+ ar = (AsyncResult) msg.obj;
+ request = (MainThreadRequest) ar.userObj;
+ if (ar.exception != null) {
+ request.result = TelephonyManager.CDMA_SUBSCRIPTION_RUIM_SIM;
+ } else {
+ request.result = ((int[]) ar.result)[0];
+ }
+ notifyRequester(request);
+ break;
case CMD_SET_CDMA_SUBSCRIPTION_MODE:
request = (MainThreadRequest) msg.obj;
onCompleted = obtainMessage(EVENT_SET_CDMA_SUBSCRIPTION_MODE_DONE, request);
int subscriptionMode = (int) request.argument;
- getPhoneFromRequest(request).setCdmaSubscription(subscriptionMode, onCompleted);
+ getPhoneFromRequest(request).setCdmaSubscriptionMode(
+ subscriptionMode, onCompleted);
break;
case EVENT_SET_CDMA_SUBSCRIPTION_MODE_DONE:
ar = (AsyncResult) msg.obj;
@@ -1304,19 +1531,20 @@
Log.w(LOG_TAG, "Discarded CellInfo due to Callback RemoteException");
}
break;
- case CMD_GET_CELL_LOCATION:
+ case CMD_GET_CELL_LOCATION: {
request = (MainThreadRequest) msg.obj;
WorkSource ws = (WorkSource) request.argument;
Phone phone = getPhoneFromRequest(request);
phone.getCellIdentity(ws, obtainMessage(EVENT_GET_CELL_LOCATION_DONE, request));
break;
- case EVENT_GET_CELL_LOCATION_DONE:
+ }
+ case EVENT_GET_CELL_LOCATION_DONE: {
ar = (AsyncResult) msg.obj;
request = (MainThreadRequest) ar.userObj;
if (ar.exception == null) {
request.result = ar.result;
} else {
- phone = getPhoneFromRequest(request);
+ Phone phone = getPhoneFromRequest(request);
request.result = (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA)
? new CellIdentityCdma() : new CellIdentityGsm();
}
@@ -1325,6 +1553,7 @@
request.notifyAll();
}
break;
+ }
case CMD_MODEM_REBOOT:
request = (MainThreadRequest) msg.obj;
onCompleted = obtainMessage(EVENT_RESET_MODEM_CONFIG_DONE, request);
@@ -1341,7 +1570,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);
@@ -1356,6 +1585,7 @@
}
notifyRequester(request);
break;
+ }
case CMD_GET_MODEM_STATUS:
request = (MainThreadRequest) msg.obj;
onCompleted = obtainMessage(EVENT_GET_MODEM_STATUS_DONE, request);
@@ -1400,6 +1630,35 @@
notifyRequester(request);
break;
}
+ case CMD_GET_SYSTEM_SELECTION_CHANNELS: {
+ request = (MainThreadRequest) msg.obj;
+ onCompleted = obtainMessage(EVENT_GET_SYSTEM_SELECTION_CHANNELS_DONE, request);
+ Phone phone = getPhoneFromRequest(request);
+ if (phone != null) {
+ phone.getSystemSelectionChannels(onCompleted);
+ } else {
+ loge("getSystemSelectionChannels: No phone object");
+ request.result = new ArrayList<RadioAccessSpecifier>();
+ notifyRequester(request);
+ }
+ break;
+ }
+ case EVENT_GET_SYSTEM_SELECTION_CHANNELS_DONE:
+ ar = (AsyncResult) msg.obj;
+ request = (MainThreadRequest) ar.userObj;
+ if (ar.exception == null && ar.result != null) {
+ request.result = ar.result;
+ } else {
+ request.result = new IllegalStateException(
+ "Failed to retrieve system selecton channels");
+ if (ar.result == null) {
+ loge("getSystemSelectionChannels: Empty response");
+ } else {
+ loge("getSystemSelectionChannels: Unknown exception");
+ }
+ }
+ notifyRequester(request);
+ break;
case EVENT_SET_FORBIDDEN_PLMNS_DONE:
ar = (AsyncResult) msg.obj;
request = (MainThreadRequest) ar.userObj;
@@ -1453,6 +1712,12 @@
handleNullReturnEvent(msg, "eraseModemConfig");
break;
+ case CMD_ERASE_DATA_SHARED_PREFERENCES:
+ request = (MainThreadRequest) msg.obj;
+ request.result = defaultPhone.eraseDataInSharedPreferences();
+ notifyRequester(request);
+ break;
+
case CMD_CHANGE_ICC_LOCK_PASSWORD:
request = (MainThreadRequest) msg.obj;
onCompleted = obtainMessage(EVENT_CHANGE_ICC_LOCK_PASSWORD_DONE, request);
@@ -1465,27 +1730,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;
@@ -1496,6 +1778,201 @@
getDefaultPhone().getContext().sendBroadcastAsUser(
intent, UserHandle.ALL, permission.USER_ACTIVITY);
break;
+
+ case CMD_SET_DATA_THROTTLING: {
+ request = (MainThreadRequest) msg.obj;
+ onCompleted = obtainMessage(EVENT_SET_DATA_THROTTLING_DONE, request);
+ DataThrottlingRequest dataThrottlingRequest =
+ (DataThrottlingRequest) request.argument;
+ Phone phone = getPhoneFromRequest(request);
+ if (phone != null) {
+ phone.setDataThrottling(onCompleted,
+ request.workSource, dataThrottlingRequest.getDataThrottlingAction(),
+ dataThrottlingRequest.getCompletionDurationMillis());
+ } else {
+ loge("setDataThrottling: No phone object");
+ request.result =
+ TelephonyManager.THERMAL_MITIGATION_RESULT_MODEM_NOT_AVAILABLE;
+ notifyRequester(request);
+ }
+
+ break;
+ }
+ case EVENT_SET_DATA_THROTTLING_DONE:
+ ar = (AsyncResult) msg.obj;
+ request = (MainThreadRequest) ar.userObj;
+
+ if (ar.exception == null) {
+ request.result = TelephonyManager.THERMAL_MITIGATION_RESULT_SUCCESS;
+ } else if (ar.exception instanceof CommandException) {
+ loge("setDataThrottling: CommandException: " + ar.exception);
+ CommandException.Error error =
+ ((CommandException) (ar.exception)).getCommandError();
+
+ if (error == CommandException.Error.RADIO_NOT_AVAILABLE) {
+ request.result = TelephonyManager
+ .THERMAL_MITIGATION_RESULT_MODEM_NOT_AVAILABLE;
+ } else if (error == CommandException.Error.INVALID_ARGUMENTS) {
+ request.result = SET_DATA_THROTTLING_MODEM_THREW_INVALID_PARAMS;
+ } else if (error == CommandException.Error.REQUEST_NOT_SUPPORTED) {
+ request.result = MODEM_DOES_NOT_SUPPORT_DATA_THROTTLING_ERROR_CODE;
+ } else {
+ request.result =
+ TelephonyManager.THERMAL_MITIGATION_RESULT_MODEM_ERROR;
+ }
+ } else {
+ request.result = TelephonyManager.THERMAL_MITIGATION_RESULT_MODEM_ERROR;
+ }
+ Log.w(LOG_TAG, "DataThrottlingResult = " + request.result);
+ notifyRequester(request);
+ break;
+
+ case CMD_SET_SIM_POWER: {
+ request = (MainThreadRequest) msg.obj;
+ onCompleted = obtainMessage(EVENT_SET_SIM_POWER_DONE, request);
+ request = (MainThreadRequest) msg.obj;
+ int stateToSet =
+ ((Pair<Integer, IIntegerConsumer>)
+ request.argument).first;
+ request.phone.setSimPowerState(stateToSet, onCompleted, request.workSource);
+ break;
+ }
+ case EVENT_SET_SIM_POWER_DONE: {
+ ar = (AsyncResult) msg.obj;
+ request = (MainThreadRequest) ar.userObj;
+ IIntegerConsumer callback =
+ ((Pair<Integer, IIntegerConsumer>) request.argument).second;
+ if (ar.exception != null) {
+ loge("setSimPower exception: " + ar.exception);
+ int errorCode = TelephonyManager.CallForwardingInfoCallback
+ .RESULT_ERROR_UNKNOWN;
+ if (ar.exception instanceof CommandException) {
+ CommandException.Error error =
+ ((CommandException) (ar.exception)).getCommandError();
+ if (error == CommandException.Error.SIM_ERR) {
+ errorCode = TelephonyManager.SET_SIM_POWER_STATE_SIM_ERROR;
+ } else if (error == CommandException.Error.INVALID_ARGUMENTS) {
+ errorCode = TelephonyManager.SET_SIM_POWER_STATE_ALREADY_IN_STATE;
+ } else if (error == CommandException.Error.REQUEST_NOT_SUPPORTED) {
+ errorCode = TelephonyManager.SET_SIM_POWER_STATE_NOT_SUPPORTED;
+ } else {
+ errorCode = TelephonyManager.SET_SIM_POWER_STATE_MODEM_ERROR;
+ }
+ }
+ try {
+ callback.accept(errorCode);
+ } catch (RemoteException e) {
+ // Ignore if the remote process is no longer available to call back.
+ Log.w(LOG_TAG, "setSimPower: callback not available.");
+ }
+ } else {
+ try {
+ callback.accept(TelephonyManager.SET_SIM_POWER_STATE_SUCCESS);
+ } catch (RemoteException e) {
+ // Ignore if the remote process is no longer available to call back.
+ Log.w(LOG_TAG, "setSimPower: callback not available.");
+ }
+ }
+ break;
+ }
+ case CMD_SET_SIGNAL_STRENGTH_UPDATE_REQUEST: {
+ request = (MainThreadRequest) msg.obj;
+
+ final Phone phone = getPhoneFromRequest(request);
+ if (phone == null || phone.getServiceStateTracker() == null) {
+ request.result = new IllegalStateException("Phone or SST is null");
+ notifyRequester(request);
+ break;
+ }
+
+ Pair<Integer, SignalStrengthUpdateRequest> pair =
+ (Pair<Integer, SignalStrengthUpdateRequest>) request.argument;
+ onCompleted = obtainMessage(EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST_DONE,
+ request);
+ phone.getServiceStateTracker().setSignalStrengthUpdateRequest(
+ request.subId, pair.first /*callingUid*/,
+ pair.second /*request*/, onCompleted);
+ break;
+ }
+ case EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST_DONE: {
+ ar = (AsyncResult) msg.obj;
+ request = (MainThreadRequest) ar.userObj;
+ // request.result will be the exception of ar if present, true otherwise.
+ // Be cautious not to leave result null which will wait() forever
+ request.result = ar.exception != null ? ar.exception : true;
+ notifyRequester(request);
+ break;
+ }
+ case CMD_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST: {
+ request = (MainThreadRequest) msg.obj;
+
+ Phone phone = getPhoneFromRequest(request);
+ if (phone == null || phone.getServiceStateTracker() == null) {
+ request.result = new IllegalStateException("Phone or SST is null");
+ notifyRequester(request);
+ break;
+ }
+
+ Pair<Integer, SignalStrengthUpdateRequest> pair =
+ (Pair<Integer, SignalStrengthUpdateRequest>) request.argument;
+ onCompleted = obtainMessage(EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST_DONE,
+ request);
+ phone.getServiceStateTracker().clearSignalStrengthUpdateRequest(
+ request.subId, pair.first /*callingUid*/,
+ pair.second /*request*/, onCompleted);
+ break;
+ }
+ case EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST_DONE: {
+ ar = (AsyncResult) msg.obj;
+ request = (MainThreadRequest) ar.userObj;
+ request.result = ar.exception != null ? ar.exception : true;
+ notifyRequester(request);
+ break;
+ }
+
+ case CMD_GET_SLICING_CONFIG: {
+ request = (MainThreadRequest) msg.obj;
+ onCompleted = obtainMessage(EVENT_GET_SLICING_CONFIG_DONE, request);
+ request.phone.getSlicingConfig(onCompleted);
+ break;
+ }
+ case EVENT_GET_SLICING_CONFIG_DONE: {
+ ar = (AsyncResult) msg.obj;
+ request = (MainThreadRequest) ar.userObj;
+ ResultReceiver result = (ResultReceiver) request.argument;
+
+ NetworkSlicingConfig slicingConfig = null;
+ Bundle bundle = new Bundle();
+ int resultCode = 0;
+ if (ar.exception != null) {
+ Log.e(LOG_TAG, "Exception retrieving slicing configuration="
+ + ar.exception);
+ resultCode = TelephonyManager.NetworkSlicingException.ERROR_MODEM_ERROR;
+ } else if (ar.result == null) {
+ Log.w(LOG_TAG, "Timeout Waiting for slicing configuration!");
+ resultCode = TelephonyManager.NetworkSlicingException.ERROR_TIMEOUT;
+ } else {
+ // use the result as returned
+ resultCode = TelephonyManager.NetworkSlicingException.SUCCESS;
+ slicingConfig = (NetworkSlicingConfig) ar.result;
+ }
+
+ if (slicingConfig == null) {
+ slicingConfig = new NetworkSlicingConfig();
+ }
+ bundle.putParcelable(TelephonyManager.KEY_SLICING_CONFIG_HANDLE, slicingConfig);
+ result.send(resultCode, bundle);
+ notifyRequester(request);
+ 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;
@@ -1531,8 +2008,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*/);
}
/**
@@ -1542,7 +2019,7 @@
*/
private Object sendRequest(int command, Object argument, WorkSource workSource) {
return sendRequest(command, argument, SubscriptionManager.INVALID_SUBSCRIPTION_ID,
- null, workSource);
+ null, workSource, -1 /*timeoutInMs*/);
}
/**
@@ -1551,7 +2028,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);
}
/**
@@ -1560,7 +2048,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*/);
}
/**
@@ -1569,17 +2057,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.");
}
@@ -1596,16 +2085,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.result == 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;
}
@@ -1657,15 +2166,17 @@
private PhoneInterfaceManager(PhoneGlobals app) {
mApp = app;
mCM = PhoneGlobals.getInstance().mCM;
- mImsResolver = PhoneGlobals.getInstance().getImsResolver();
+ mImsResolver = ImsResolver.getInstance();
mUserManager = (UserManager) app.getSystemService(Context.USER_SERVICE);
mAppOps = (AppOpsManager)app.getSystemService(Context.APP_OPS_SERVICE);
+ mPm = app.getSystemService(PackageManager.class);
mMainThreadHandler = new MainThreadHandler();
mSubscriptionController = SubscriptionController.getInstance();
mTelephonySharedPreferences =
PreferenceManager.getDefaultSharedPreferences(mApp);
mNetworkScanRequestTracker = new NetworkScanRequestTracker();
mPhoneConfigurationManager = PhoneConfigurationManager.getInstance();
+ mRadioInterfaceCapabilities = RadioInterfaceCapabilityController.getInstance();
mNotifyUserActivity = new AtomicBoolean(false);
publish();
@@ -1709,18 +2220,14 @@
return PhoneFactory.getPhone(mSubscriptionController.getPhoneId(subId));
}
- private void sendEraseModemConfig(Phone phone) {
- if (phone != null) {
- TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
- mApp, phone.getSubId(), "eraseModemConfig");
- final long identity = Binder.clearCallingIdentity();
- try {
- Boolean success = (Boolean) sendRequest(CMD_ERASE_MODEM_CONFIG, null);
- if (DBG) log("eraseModemConfig:" + ' ' + (success ? "ok" : "fail"));
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
+ private void sendEraseModemConfig(@NonNull Phone phone) {
+ Boolean success = (Boolean) sendRequest(CMD_ERASE_MODEM_CONFIG, null);
+ if (DBG) log("eraseModemConfig:" + ' ' + (success ? "ok" : "fail"));
+ }
+
+ private void sendEraseDataInSharedPreferences(@NonNull Phone phone) {
+ Boolean success = (Boolean) sendRequest(CMD_ERASE_DATA_SHARED_PREFERENCES, null);
+ if (DBG) log("eraseDataInSharedPreferences:" + ' ' + (success ? "ok" : "fail"));
}
private boolean isImsAvailableOnDevice() {
@@ -1824,7 +2331,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 {
@@ -1837,7 +2345,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 {
@@ -1852,6 +2361,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;
@@ -1863,7 +2373,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;
}
@@ -1885,6 +2396,11 @@
((CommandException)(ar.exception)).getCommandError()
== CommandException.Error.PASSWORD_INCORRECT) {
mResult = PhoneConstants.PIN_PASSWORD_INCORRECT;
+ } //When UiccCardApp dispose,handle message and return exception
+ else if (ar.exception instanceof CommandException &&
+ ((CommandException) (ar.exception)).getCommandError()
+ == CommandException.Error.ABORTED) {
+ mResult = PhoneConstants.PIN_OPERATION_ABORTED;
} else {
mResult = PhoneConstants.PIN_GENERAL_FAILURE;
}
@@ -1940,24 +2456,61 @@
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;
}
}
+ /**
+ * This method has been removed due to privacy and stability concerns.
+ */
+ @Override
public void updateServiceLocation() {
- updateServiceLocationForSubscriber(getDefaultSubscription());
-
+ Log.e(LOG_TAG, "Call to unsupported method updateServiceLocation()");
+ return;
}
- public void updateServiceLocationForSubscriber(int subId) {
- // No permission check needed here: this call is harmless, and it's
- // needed for the ServiceState.requestStateUpdate() call (which is
- // already intentionally exposed to 3rd parties.)
+ @Override
+ public void updateServiceLocationWithPackageName(String callingPackage) {
+ mApp.getSystemService(AppOpsManager.class)
+ .checkPackage(Binder.getCallingUid(), callingPackage);
+
+ final int targetSdk = TelephonyPermissions.getTargetSdk(mApp, callingPackage);
+ if (targetSdk > android.os.Build.VERSION_CODES.R) {
+ // Callers targeting S have no business invoking this method.
+ return;
+ }
+
+ LocationAccessPolicy.LocationPermissionResult locationResult =
+ LocationAccessPolicy.checkLocationPermission(mApp,
+ new LocationAccessPolicy.LocationPermissionQuery.Builder()
+ .setCallingPackage(callingPackage)
+ .setCallingFeatureId(null)
+ .setCallingPid(Binder.getCallingPid())
+ .setCallingUid(Binder.getCallingUid())
+ .setMethod("updateServiceLocation")
+ .setMinSdkVersionForCoarse(Build.VERSION_CODES.BASE)
+ .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+ .build());
+ // Apps that lack location permission have no business calling this method;
+ // however, because no permission was declared in the public API, denials must
+ // all be "soft".
+ switch (locationResult) {
+ case DENIED_HARD: /* fall through */
+ case DENIED_SOFT:
+ return;
+ }
+
+ WorkSource workSource = getWorkSource(Binder.getCallingUid());
final long identity = Binder.clearCallingIdentity();
try {
- final Phone phone = getPhone(subId);
+ final Phone phone = getPhone(getDefaultSubscription());
if (phone != null) {
- phone.updateServiceLocation();
+ phone.updateServiceLocation(workSource);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -2139,7 +2692,8 @@
int subId = mSubscriptionController.getDefaultDataSubId();
final Phone phone = getPhone(subId);
if (phone != null) {
- phone.getDataEnabledSettings().setUserDataEnabled(true);
+ phone.getDataEnabledSettings().setDataEnabled(
+ TelephonyManager.DATA_ENABLED_REASON_USER, true);
return true;
} else {
return false;
@@ -2159,7 +2713,8 @@
int subId = mSubscriptionController.getDefaultDataSubId();
final Phone phone = getPhone(subId);
if (phone != null) {
- phone.getDataEnabledSettings().setUserDataEnabled(false);
+ phone.getDataEnabledSettings().setDataEnabled(
+ TelephonyManager.DATA_ENABLED_REASON_USER, false);
return true;
} else {
return false;
@@ -2217,14 +2772,46 @@
}
}
+ /**
+ * @deprecated This method is deprecated and is only being kept due to an UnsupportedAppUsage
+ * tag on getCallState Binder call.
+ */
+ @Deprecated
+ @Override
public int getCallState() {
- return getCallStateForSlot(getSlotForDefaultSubscription());
- }
-
- public int getCallStateForSlot(int slotIndex) {
+ if (CompatChanges.isChangeEnabled(
+ TelecomManager.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION,
+ Binder.getCallingUid())) {
+ // Do not allow this API to be called on API version 31+, it should only be
+ // called on old apps using this Binder call directly.
+ throw new SecurityException("This method can only be used for applications "
+ + "targeting API version 30 or less.");
+ }
final long identity = Binder.clearCallingIdentity();
try {
- Phone phone = PhoneFactory.getPhone(slotIndex);
+ Phone phone = getPhone(getDefaultSubscription());
+ return phone == null ? TelephonyManager.CALL_STATE_IDLE :
+ PhoneConstantConversions.convertCallState(phone.getState());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public int getCallStateForSubscription(int subId, String callingPackage, String featureId) {
+ if (CompatChanges.isChangeEnabled(
+ TelecomManager.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION,
+ Binder.getCallingUid())) {
+ // Check READ_PHONE_STATE for API version 31+
+ if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mApp, subId, callingPackage,
+ featureId, "getCallStateForSubscription")) {
+ throw new SecurityException("getCallState requires READ_PHONE_STATE for apps "
+ + "targeting API level 31+.");
+ }
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Phone phone = getPhone(subId);
return phone == null ? TelephonyManager.CALL_STATE_IDLE :
PhoneConstantConversions.convertCallState(phone.getState());
} finally {
@@ -2325,80 +2912,43 @@
if (sst == null) return "";
LocaleTracker lt = sst.getLocaleTracker();
if (lt == null) return "";
- if (!TextUtils.isEmpty(lt.getCurrentCountry())) return lt.getCurrentCountry();
- EmergencyNumberTracker ent = phone.getEmergencyNumberTracker();
- return (ent == null) ? "" : ent.getEmergencyCountryIso();
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
- public void enableLocationUpdates() {
- enableLocationUpdatesForSubscriber(getDefaultSubscription());
- }
-
- @Override
- public void enableLocationUpdatesForSubscriber(int subId) {
- mApp.enforceCallingOrSelfPermission(
- android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
-
- final long identity = Binder.clearCallingIdentity();
- try {
- final Phone phone = getPhone(subId);
- if (phone != null) {
- phone.enableLocationUpdates();
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
- public void disableLocationUpdates() {
- disableLocationUpdatesForSubscriber(getDefaultSubscription());
- }
-
- @Override
- public void disableLocationUpdatesForSubscriber(int subId) {
- mApp.enforceCallingOrSelfPermission(
- android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
-
- final long identity = Binder.clearCallingIdentity();
- try {
- final Phone phone = getPhone(subId);
- if (phone != null) {
- phone.disableLocationUpdates();
- }
+ return lt.getCurrentCountry();
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
- * Returns the target SDK version number for a given package name.
+ * This method was removed due to potential issues caused by performing partial
+ * updates of service state, and lack of a credible use case.
*
- * This call MUST be invoked before clearing the calling UID.
- *
- * @return target SDK if the package is found or INT_MAX.
+ * This has the ability to break the telephony implementation by disabling notification of
+ * changes in device connectivity. DO NOT USE THIS!
*/
- private int getTargetSdk(String packageName) {
- try {
- final ApplicationInfo ai = mApp.getPackageManager().getApplicationInfoAsUser(
- packageName, 0, UserHandle.getUserHandleForUid(Binder.getCallingUid()));
- if (ai != null) return ai.targetSdkVersion;
- } catch (PackageManager.NameNotFoundException unexpected) {
- loge("Failed to get package info for pkg="
- + packageName + ", uid=" + Binder.getCallingUid());
- }
- return Integer.MAX_VALUE;
+ @Override
+ public void enableLocationUpdates() {
+ mApp.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
+ }
+
+ /**
+ * This method was removed due to potential issues caused by performing partial
+ * updates of service state, and lack of a credible use case.
+ *
+ * This has the ability to break the telephony implementation by disabling notification of
+ * changes in device connectivity. DO NOT USE THIS!
+ */
+ @Override
+ public void disableLocationUpdates() {
+ mApp.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
}
@Override
@SuppressWarnings("unchecked")
public List<NeighboringCellInfo> getNeighboringCellInfo(String callingPackage,
String callingFeatureId) {
- final int targetSdk = getTargetSdk(callingPackage);
+ final int targetSdk = TelephonyPermissions.getTargetSdk(mApp, callingPackage);
if (targetSdk >= android.os.Build.VERSION_CODES.Q) {
throw new SecurityException(
"getNeighboringCellInfo() is unavailable to callers targeting Q+ SDK levels.");
@@ -2457,7 +3007,7 @@
return new ArrayList<>();
}
- final int targetSdk = getTargetSdk(callingPackage);
+ final int targetSdk = TelephonyPermissions.getTargetSdk(mApp, callingPackage);
if (targetSdk >= android.os.Build.VERSION_CODES.Q) {
return getCachedCellInfo();
}
@@ -2510,13 +3060,15 @@
.build());
switch (locationResult) {
case DENIED_HARD:
- if (getTargetSdk(callingPackage) < Build.VERSION_CODES.Q) {
+ if (TelephonyPermissions
+ .getTargetSdk(mApp, callingPackage) < Build.VERSION_CODES.Q) {
// Safetynet logging for b/154934934
EventLog.writeEvent(0x534e4554, "154934934", Binder.getCallingUid());
}
throw new SecurityException("Not allowed to access cell info");
case DENIED_SOFT:
- if (getTargetSdk(callingPackage) < Build.VERSION_CODES.Q) {
+ if (TelephonyPermissions
+ .getTargetSdk(mApp, callingPackage) < Build.VERSION_CODES.Q) {
// Safetynet logging for b/154934934
EventLog.writeEvent(0x534e4554, "154934934", Binder.getCallingUid());
}
@@ -2555,6 +3107,7 @@
return null;
}
int subId = phone.getSubId();
+ enforceCallingPackage(callingPackage, Binder.getCallingUid(), "getImeiForSlot");
if (!TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mApp, subId,
callingPackage, callingFeatureId, "getImeiForSlot")) {
return null;
@@ -2581,6 +3134,13 @@
@Override
public String getMeidForSlot(int slotIndex, String callingPackage, String callingFeatureId) {
+ try {
+ mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+ } catch (SecurityException se) {
+ EventLog.writeEvent(0x534e4554, "186530496", Binder.getCallingUid());
+ throw new SecurityException("Package " + callingPackage + " does not belong to "
+ + Binder.getCallingUid());
+ }
Phone phone = PhoneFactory.getPhone(slotIndex);
if (phone == null) {
return null;
@@ -2700,6 +3260,26 @@
//
/**
+ * Make sure the caller is the calling package itself
+ *
+ * @throws SecurityException if the caller is not the calling package
+ */
+ private void enforceCallingPackage(String callingPackage, int callingUid, String message) {
+ int packageUid = -1;
+ PackageManager pm = mApp.getBaseContext().createContextAsUser(
+ UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager();
+ try {
+ packageUid = pm.getPackageUid(callingPackage, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ // packageUid is -1
+ }
+ if (packageUid != callingUid) {
+ throw new SecurityException(message + ": Package " + callingPackage
+ + " does not belong to " + callingUid);
+ }
+ }
+
+ /**
* Make sure the caller has the MODIFY_PHONE_STATE permission.
*
* @throws SecurityException if the caller does not have the required permission
@@ -2713,7 +3293,7 @@
*
* @throws SecurityException if the caller is not system.
*/
- private void enforceSystemCaller() {
+ private static void enforceSystemCaller() {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Caller must be system");
}
@@ -2737,6 +3317,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;
@@ -2926,7 +3510,9 @@
String authorizedPackage = NumberVerificationManager.getAuthorizedPackage(mApp);
if (!TextUtils.equals(callingPackage, authorizedPackage)) {
- throw new SecurityException("Calling package must be configured in the device config");
+ throw new SecurityException("Calling package must be configured in the device config: "
+ + "calling package: " + callingPackage
+ + ", configured package: " + authorizedPackage);
}
if (range == null) {
@@ -3222,7 +3808,7 @@
@Override
public int getNetworkSelectionMode(int subId) {
TelephonyPermissions
- .enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ .enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getNetworkSelectionMode");
final long identity = Binder.clearCallingIdentity();
try {
@@ -3259,7 +3845,7 @@
@Override
public void registerImsRegistrationCallback(int subId, IImsRegistrationCallback c)
throws RemoteException {
- TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "registerImsRegistrationCallback");
if (!ImsManager.isImsSupportedOnDevice(mApp)) {
@@ -3268,9 +3854,9 @@
}
final long token = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
- .addRegistrationCallbackForSubscription(c, subId);
+ int slotId = getSlotIndexOrException(subId);
+ verifyImsMmTelConfiguredOrThrow(slotId);
+ ImsManager.getInstance(mApp, slotId).addRegistrationCallbackForSubscription(c, subId);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -3285,14 +3871,13 @@
*/
@Override
public void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback c) {
- TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "unregisterImsRegistrationCallback");
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
}
final long token = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone.
ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
.removeRegistrationCallbackForSubscription(c, subId);
} catch (ImsException e) {
@@ -3342,7 +3927,7 @@
*/
@Override
public void getImsMmTelRegistrationTransportType(int subId, IIntegerConsumer consumer) {
- TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getImsMmTelRegistrationTransportType");
if (!ImsManager.isImsSupportedOnDevice(mApp)) {
throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
@@ -3382,17 +3967,17 @@
@Override
public void registerMmTelCapabilityCallback(int subId, IImsCapabilityCallback c)
throws RemoteException {
- TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "registerMmTelCapabilityCallback");
if (!ImsManager.isImsSupportedOnDevice(mApp)) {
throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
"IMS not available on device.");
}
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
final long token = Binder.clearCallingIdentity();
try {
- ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
- .addCapabilitiesCallbackForSubscription(c, subId);
+ int slotId = getSlotIndexOrException(subId);
+ verifyImsMmTelConfiguredOrThrow(slotId);
+ ImsManager.getInstance(mApp, slotId).addCapabilitiesCallbackForSubscription(c, subId);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -3407,7 +3992,7 @@
*/
@Override
public void unregisterMmTelCapabilityCallback(int subId, IImsCapabilityCallback c) {
- TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "unregisterMmTelCapabilityCallback");
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
@@ -3415,7 +4000,6 @@
final long token = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone.
ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
.removeCapabilitiesCallbackForSubscription(c, subId);
} catch (ImsException e) {
@@ -3431,11 +4015,11 @@
@Override
public boolean isCapable(int subId, int capability, int regTech) {
enforceReadPrivilegedPermission("isCapable");
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
final long token = Binder.clearCallingIdentity();
try {
- return ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).queryMmTelCapability(capability, regTech);
+ int slotId = getSlotIndexOrException(subId);
+ verifyImsMmTelConfiguredOrThrow(slotId);
+ return ImsManager.getInstance(mApp, slotId).queryMmTelCapability(capability, regTech);
} catch (com.android.ims.ImsException e) {
Log.w(LOG_TAG, "IMS isCapable - service unavailable: " + e.getMessage());
return false;
@@ -3487,6 +4071,7 @@
+ subId + "'");
throw new ServiceSpecificException(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION);
}
+ verifyImsMmTelConfiguredOrThrow(slotId);
ImsManager.getInstance(mApp, slotId).isSupported(capability,
transportType, aBoolean -> {
try {
@@ -3496,6 +4081,8 @@
+ "running. Ignore");
}
});
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode());
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -3507,14 +4094,14 @@
*/
@Override
public boolean isAdvancedCallingSettingEnabled(int subId) {
- TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isAdvancedCallingSettingEnabled");
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
final long token = Binder.clearCallingIdentity();
try {
- return ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).isEnhanced4gLteModeSettingEnabledByUser();
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify.
+ return ImsManager.getInstance(mApp, slotId).isEnhanced4gLteModeSettingEnabledByUser();
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -3528,9 +4115,10 @@
"setAdvancedCallingSettingEnabled");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).setEnhanced4gLteModeSetting(isEnabled);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify. The
+ // new setting will be picked up when the ImsService comes up next if it isn't up.
+ ImsManager.getInstance(mApp, slotId).setEnhanced4gLteModeSetting(isEnabled);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -3544,12 +4132,13 @@
*/
@Override
public boolean isVtSettingEnabled(int subId) {
- TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isVtSettingEnabled");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- return ImsManager.getInstance(mApp, getSlotIndexOrException(subId)).isVtEnabledByUser();
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify.
+ return ImsManager.getInstance(mApp, slotId).isVtEnabledByUser();
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -3563,8 +4152,10 @@
"setVtSettingEnabled");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp, getSlotIndexOrException(subId)).setVtSetting(isEnabled);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify. The
+ // new setting will be picked up when the ImsService comes up next if it isn't up.
+ ImsManager.getInstance(mApp, slotId).setVtSetting(isEnabled);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -3578,13 +4169,13 @@
*/
@Override
public boolean isVoWiFiSettingEnabled(int subId) {
- TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isVoWiFiSettingEnabled");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- return ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).isWfcEnabledByUser();
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify.
+ return ImsManager.getInstance(mApp, slotId).isWfcEnabledByUser();
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -3598,8 +4189,55 @@
"setVoWiFiSettingEnabled");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp, getSlotIndexOrException(subId)).setWfcSetting(isEnabled);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify. The
+ // new setting will be picked up when the ImsService comes up next if it isn't up.
+ ImsManager.getInstance(mApp, slotId).setWfcSetting(isEnabled);
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * @return true if the user's setting for Voice over Cross SIM is enabled and false if it is not
+ * Requires carrier privileges or READ_PRECISE_PHONE_STATE permission.
+ * @param subId The subscription to use to check the configuration.
+ */
+ @Override
+ public boolean isCrossSimCallingEnabledByUser(int subId) {
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ mApp, subId, "isCrossSimCallingEnabledByUser");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify.
+ return ImsManager.getInstance(mApp, slotId).isCrossSimCallingEnabledByUser();
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Sets the user's setting for whether or not Voice over Cross SIM is enabled.
+ * Requires MODIFY_PHONE_STATE permission.
+ * @param subId The subscription to use to check the configuration.
+ * @param isEnabled true if the user's setting for Voice over Cross SIM is enabled,
+ * false otherwise
+ */
+ @Override
+ public void setCrossSimCallingEnabled(int subId, boolean isEnabled) {
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp, subId,
+ "setCrossSimCallingEnabled");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify. The
+ // new setting will be picked up when the ImsService comes up next if it isn't up.
+ ImsManager.getInstance(mApp, slotId).setCrossSimCallingEnabled(isEnabled);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -3612,14 +4250,15 @@
* @param subId The subscription to use to check the configuration.
*/
@Override
+
public boolean isVoWiFiRoamingSettingEnabled(int subId) {
- TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isVoWiFiRoamingSettingEnabled");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- return ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).isWfcRoamingEnabledByUser();
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify.
+ return ImsManager.getInstance(mApp, slotId).isWfcRoamingEnabledByUser();
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -3633,9 +4272,10 @@
"setVoWiFiRoamingSettingEnabled");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).setWfcRoamingSetting(isEnabled);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify. The
+ // new setting will be picked up when the ImsService comes up next if it isn't up.
+ ImsManager.getInstance(mApp, slotId).setWfcRoamingSetting(isEnabled);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -3649,9 +4289,9 @@
"setVoWiFiNonPersistent");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).setWfcNonPersistent(isCapable, mode);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting will be ignored if the ImsService isn't up.
+ ImsManager.getInstance(mApp, slotId).setWfcNonPersistent(isCapable, mode);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -3665,13 +4305,13 @@
*/
@Override
public int getVoWiFiModeSetting(int subId) {
- TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getVoWiFiModeSetting");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- return ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).getWfcMode(false /*isRoaming*/);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify.
+ return ImsManager.getInstance(mApp, slotId).getWfcMode(false /*isRoaming*/);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -3685,9 +4325,10 @@
"setVoWiFiModeSetting");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).setWfcMode(mode, false /*isRoaming*/);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify. The
+ // new setting will be picked up when the ImsService comes up next if it isn't up.
+ ImsManager.getInstance(mApp, slotId).setWfcMode(mode, false /*isRoaming*/);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -3700,9 +4341,9 @@
enforceReadPrivilegedPermission("getVoWiFiRoamingModeSetting");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- return ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).getWfcMode(true /*isRoaming*/);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify.
+ return ImsManager.getInstance(mApp, slotId).getWfcMode(true /*isRoaming*/);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -3716,9 +4357,10 @@
"setVoWiFiRoamingModeSetting");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).setWfcMode(mode, true /*isRoaming*/);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify. The
+ // new setting will be picked up when the ImsService comes up next if it isn't up.
+ ImsManager.getInstance(mApp, slotId).setWfcMode(mode, true /*isRoaming*/);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -3732,8 +4374,10 @@
"setRttCapabilityEnabled");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp, getSlotIndexOrException(subId)).setRttEnabled(isEnabled);
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify. The
+ // new setting will be picked up when the ImsService comes up next if it isn't up.
+ ImsManager.getInstance(mApp, slotId).setRttEnabled(isEnabled);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -3747,13 +4391,13 @@
*/
@Override
public boolean isTtyOverVolteEnabled(int subId) {
- TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isTtyOverVolteEnabled");
final long identity = Binder.clearCallingIdentity();
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- return ImsManager.getInstance(mApp,
- getSlotIndexOrException(subId)).isTtyOnVoLteCapable();
+ int slotId = getSlotIndexOrException(subId);
+ // This setting doesn't require an active ImsService connection, so do not verify.
+ return ImsManager.getInstance(mApp, slotId).isTtyOnVoLteCapable();
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
} finally {
@@ -3770,8 +4414,9 @@
throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
"IMS not available on device.");
}
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
- ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
+ int slotId = getSlotIndexOrException(subId);
+ verifyImsMmTelConfiguredOrThrow(slotId);
+ ImsManager.getInstance(mApp, slotId)
.addProvisioningCallbackForSubscription(callback, subId);
} catch (ImsException e) {
throw new ServiceSpecificException(e.getCode());
@@ -3788,7 +4433,6 @@
throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
}
try {
- // TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
.removeProvisioningCallbackForSubscription(callback, subId);
} catch (ImsException e) {
@@ -3890,7 +4534,9 @@
public void setImsProvisioningStatusForCapability(int subId, int capability, int tech,
boolean isProvisioned) {
if (tech != ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN
- && tech != ImsRegistrationImplBase.REGISTRATION_TECH_LTE) {
+ && tech != ImsRegistrationImplBase.REGISTRATION_TECH_LTE
+ && tech != ImsRegistrationImplBase.REGISTRATION_TECH_NR
+ && tech != ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
throw new IllegalArgumentException("Registration technology '" + tech + "' is invalid");
}
checkModifyPhoneStatePermission(subId, "setImsProvisioningStatusForCapability");
@@ -3900,6 +4546,12 @@
if (!isImsProvisioningRequired(subId, capability, true)) {
return;
}
+ if (tech == ImsRegistrationImplBase.REGISTRATION_TECH_NR
+ || tech == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
+ loge("setImsProvisioningStatusForCapability: called for technology that does "
+ + "not support provisioning - " + tech);
+ return;
+ }
// this capability requires provisioning, route to the correct API.
ImsManager ims = ImsManager.getInstance(mApp, getSlotIndex(subId));
@@ -3927,7 +4579,7 @@
}
cacheMmTelCapabilityProvisioning(subId, capability, tech, isProvisioned);
try {
- ims.changeMmTelCapability(capability, tech, isProvisioned);
+ ims.changeMmTelCapability(isProvisioned, capability, tech);
} catch (com.android.ims.ImsException e) {
loge("setImsProvisioningStatusForCapability: couldn't change UT capability"
+ ", Exception" + e.getMessage());
@@ -3949,7 +4601,9 @@
@Override
public boolean getImsProvisioningStatusForCapability(int subId, int capability, int tech) {
if (tech != ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN
- && tech != ImsRegistrationImplBase.REGISTRATION_TECH_LTE) {
+ && tech != ImsRegistrationImplBase.REGISTRATION_TECH_LTE
+ && tech != ImsRegistrationImplBase.REGISTRATION_TECH_NR
+ && tech != ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
throw new IllegalArgumentException("Registration technology '" + tech + "' is invalid");
}
enforceReadPrivilegedPermission("getProvisioningStatusForCapability");
@@ -3960,6 +4614,13 @@
return true;
}
+ if (tech == ImsRegistrationImplBase.REGISTRATION_TECH_NR
+ || tech == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
+ loge("getImsProvisioningStatusForCapability: called for technology that does "
+ + "not support provisioning - " + tech);
+ return true;
+ }
+
ImsManager ims = ImsManager.getInstance(mApp, getSlotIndex(subId));
switch (capability) {
case MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE: {
@@ -4131,7 +4792,7 @@
+ subId + "' for key:" + key);
return ImsConfigImplBase.CONFIG_RESULT_UNKNOWN;
}
- return ImsManager.getInstance(mApp, slotId).getConfigInterface().getConfigInt(key);
+ return ImsManager.getInstance(mApp, slotId).getConfigInt(key);
} catch (com.android.ims.ImsException e) {
Log.w(LOG_TAG, "getImsProvisioningInt: ImsService is not available for subscription '"
+ subId + "' for key:" + key);
@@ -4156,7 +4817,7 @@
+ subId + "' for key:" + key);
return ProvisioningManager.STRING_QUERY_RESULT_ERROR_GENERIC;
}
- return ImsManager.getInstance(mApp, slotId).getConfigInterface().getConfigString(key);
+ return ImsManager.getInstance(mApp, slotId).getConfigString(key);
} catch (com.android.ims.ImsException e) {
Log.w(LOG_TAG, "getImsProvisioningString: ImsService is not available for sub '"
+ subId + "' for key:" + key);
@@ -4182,10 +4843,10 @@
+ subId + "' for key:" + key);
return ImsConfigImplBase.CONFIG_RESULT_FAILED;
}
- return ImsManager.getInstance(mApp, slotId).getConfigInterface().setConfig(key, value);
- } catch (com.android.ims.ImsException e) {
+ return ImsManager.getInstance(mApp, slotId).setConfig(key, value);
+ } catch (com.android.ims.ImsException | RemoteException e) {
Log.w(LOG_TAG, "setImsProvisioningInt: ImsService unavailable for sub '" + subId
- + "' for key:" + key);
+ + "' for key:" + key, e);
return ImsConfigImplBase.CONFIG_RESULT_FAILED;
} finally {
Binder.restoreCallingIdentity(identity);
@@ -4208,16 +4869,30 @@
+ subId + "' for key:" + key);
return ImsConfigImplBase.CONFIG_RESULT_FAILED;
}
- return ImsManager.getInstance(mApp, slotId).getConfigInterface().setConfig(key, value);
- } catch (com.android.ims.ImsException e) {
+ return ImsManager.getInstance(mApp, slotId).setConfig(key, value);
+ } catch (com.android.ims.ImsException | RemoteException e) {
Log.w(LOG_TAG, "setImsProvisioningString: ImsService unavailable for sub '" + subId
- + "' for key:" + key);
+ + "' for key:" + key, e);
return ImsConfigImplBase.CONFIG_RESULT_FAILED;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
+ /**
+ * Throw an ImsException if the IMS resolver does not have an ImsService configured for MMTEL
+ * for the given slot ID or no ImsResolver instance has been created.
+ * @param slotId The slot ID that the IMS service is created for.
+ * @throws ImsException If there is no ImsService configured for this slot.
+ */
+ private void verifyImsMmTelConfiguredOrThrow(int slotId) throws ImsException {
+ if (mImsResolver == null || !mImsResolver.isImsServiceConfiguredForFeature(slotId,
+ ImsFeature.FEATURE_MMTEL)) {
+ throw new ImsException("This subscription does not support MMTEL over IMS",
+ ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
+ }
+ }
+
private int getSlotIndexOrException(int subId) throws ImsException {
int slotId = SubscriptionManager.getSlotIndex(subId);
if (!SubscriptionManager.isValidSlotIndex(slotId)) {
@@ -4241,7 +4916,14 @@
@Override
public int getNetworkTypeForSubscriber(int subId, String callingPackage,
String callingFeatureId) {
- final int targetSdk = getTargetSdk(callingPackage);
+ try {
+ mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+ } catch (SecurityException se) {
+ EventLog.writeEvent(0x534e4554, "186776740", Binder.getCallingUid());
+ throw new SecurityException("Package " + callingPackage + " does not belong to "
+ + Binder.getCallingUid());
+ }
+ final int targetSdk = TelephonyPermissions.getTargetSdk(mApp, callingPackage);
if (targetSdk > android.os.Build.VERSION_CODES.Q) {
return getDataNetworkTypeForSubscriber(subId, callingPackage, callingFeatureId);
} else if (targetSdk == android.os.Build.VERSION_CODES.Q
@@ -4269,8 +4951,8 @@
*/
@Override
public int getDataNetworkType(String callingPackage, String callingFeatureId) {
- return getDataNetworkTypeForSubscriber(getDefaultSubscription(), callingPackage,
- callingFeatureId);
+ return getDataNetworkTypeForSubscriber(mSubscriptionController.getDefaultDataSubId(),
+ callingPackage, callingFeatureId);
}
/**
@@ -4380,7 +5062,8 @@
if (phone == null) {
return PhoneConstants.LTE_ON_CDMA_UNKNOWN;
} else {
- return phone.getLteOnCdmaMode();
+ return TelephonyProperties.lte_on_cdma_device()
+ .orElse(PhoneConstants.LTE_ON_CDMA_FALSE);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -4987,58 +5670,35 @@
}
/**
- * Returns the {@link IImsMmTelFeature} that corresponds to the given slot Id for the MMTel
- * feature or {@link null} if the service is not available. If the feature is available, the
- * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates.
+ * Registers for updates to the MmTelFeature connection through the IImsServiceFeatureCallback
+ * callback.
*/
- public IImsMmTelFeature getMmTelFeatureAndListen(int slotId,
- IImsServiceFeatureCallback callback) {
+ @Override
+ public void registerMmTelFeatureCallback(int slotId, IImsServiceFeatureCallback callback) {
enforceModifyPermission();
final long identity = Binder.clearCallingIdentity();
try {
if (mImsResolver == null) {
- // may happen if the device does not support IMS.
- return null;
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "Device does not support IMS");
}
- return mImsResolver.getMmTelFeatureAndListen(slotId, callback);
+ mImsResolver.listenForFeature(slotId, ImsFeature.FEATURE_MMTEL, callback);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
-
- /**
- * Returns the {@link IImsRcsFeature} that corresponds to the given slot Id for the RCS
- * feature during emergency calling or {@link null} if the service is not available. If the
- * feature is available, the {@link IImsServiceFeatureCallback} callback is registered as a
- * listener for feature updates.
- */
- public IImsRcsFeature getRcsFeatureAndListen(int slotId, IImsServiceFeatureCallback callback) {
- enforceModifyPermission();
-
- final long identity = Binder.clearCallingIdentity();
- try {
- if (mImsResolver == null) {
- // may happen if the device does not support IMS.
- return null;
- }
- return mImsResolver.getRcsFeatureAndListen(slotId, callback);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
/**
* Unregister a previously registered IImsServiceFeatureCallback associated with an ImsFeature.
*/
- public void unregisterImsFeatureCallback(int slotId, int featureType,
- IImsServiceFeatureCallback callback) {
+ @Override
+ public void unregisterImsFeatureCallback(IImsServiceFeatureCallback callback) {
enforceModifyPermission();
final long identity = Binder.clearCallingIdentity();
try {
if (mImsResolver == null) return;
- mImsResolver.unregisterImsFeatureCallback(slotId, featureType, callback);
+ mImsResolver.unregisterImsFeatureCallback(callback);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -5119,6 +5779,36 @@
}
/**
+ * Clears any carrier ImsService overrides for the slot index specified that were previously
+ * set with {@link #setBoundImsServiceOverride(int, boolean, int[], String)}.
+ *
+ * This should only be used for testing.
+ *
+ * @param slotIndex the slot ID that the ImsService should bind for.
+ * @return true if clearing the carrier ImsService override succeeded or false if it did not.
+ */
+ @Override
+ public boolean clearCarrierImsServiceOverride(int slotIndex) {
+ int[] subIds = SubscriptionManager.getSubId(slotIndex);
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "clearCarrierImsServiceOverride");
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp,
+ (subIds != null ? subIds[0] : SubscriptionManager.INVALID_SUBSCRIPTION_ID),
+ "clearCarrierImsServiceOverride");
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (mImsResolver == null) {
+ // may happen if the device does not support IMS.
+ return false;
+ }
+ return mImsResolver.clearCarrierImsServiceConfiguration(slotIndex);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
* Return the package name of the currently bound ImsService.
*
* @param slotId The slot that the ImsService is associated with.
@@ -5131,7 +5821,7 @@
@ImsFeature.FeatureType int featureType) {
int[] subIds = SubscriptionManager.getSubId(slotId);
TelephonyPermissions
- .enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+ .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, (subIds != null ? subIds[0] : SubscriptionManager.INVALID_SUBSCRIPTION_ID),
"getBoundImsServicePackage");
@@ -5170,6 +5860,7 @@
+ subId + "'");
throw new ServiceSpecificException(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION);
}
+ verifyImsMmTelConfiguredOrThrow(slotId);
ImsManager.getInstance(mApp, slotId).getImsServiceState(anInteger -> {
try {
callback.accept(anInteger == null ? ImsFeature.STATE_UNAVAILABLE : anInteger);
@@ -5178,17 +5869,27 @@
+ "Ignore");
}
});
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode());
} finally {
Binder.restoreCallingIdentity(token);
}
}
- public void setImsRegistrationState(boolean registered) {
+ /**
+ * Sets the ims registration state on all valid {@link Phone}s.
+ */
+ public void setImsRegistrationState(final boolean registered) {
enforceModifyPermission();
final long identity = Binder.clearCallingIdentity();
try {
- getDefaultPhone().setImsRegistrationState(registered);
+ // NOTE: Before S, this method only set the default phone.
+ for (final Phone phone : PhoneFactory.getPhones()) {
+ if (SubscriptionManager.isValidSubscriptionId(phone.getSubId())) {
+ phone.setImsRegistrationState(registered);
+ }
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -5209,13 +5910,14 @@
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);
}
}
- /**
+ /**
* Ask the radio to connect to the input network and change selection mode to manual.
*
* @param subId the id of the subscription.
@@ -5259,18 +5961,18 @@
@Override
public String getManualNetworkSelectionPlmn(int subId) {
TelephonyPermissions
- .enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ .enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getManualNetworkSelectionPlmn");
final long identity = Binder.clearCallingIdentity();
try {
if (!isActiveSubscription(subId)) {
- return "";
+ throw new IllegalArgumentException("Invalid Subscription Id: " + subId);
}
final Phone phone = getPhone(subId);
if (phone == null) {
- return "";
+ throw new IllegalArgumentException("Invalid Subscription Id: " + subId);
}
OperatorInfo networkSelection = phone.getSavedNetworkSelection();
return TextUtils.isEmpty(networkSelection.getOperatorNumeric())
@@ -5297,6 +5999,8 @@
.setCallingUid(Binder.getCallingUid())
.setMethod("getCellNetworkScanResults")
.setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+ .setMinSdkVersionForCoarse(Build.VERSION_CODES.Q)
+ .setMinSdkVersionForEnforcement(Build.VERSION_CODES.Q)
.build());
switch (locationResult) {
case DENIED_HARD:
@@ -5319,7 +6023,8 @@
* Get the call forwarding info, given the call forwarding reason.
*/
@Override
- public CallForwardingInfo getCallForwarding(int subId, int callForwardingReason) {
+ public void getCallForwarding(int subId, int callForwardingReason,
+ ICallForwardingInfoCallback callback) {
enforceReadPrivilegedPermission("getCallForwarding");
long identity = Binder.clearCallingIdentity();
try {
@@ -5327,8 +6032,39 @@
log("getCallForwarding: subId " + subId
+ " callForwardingReason" + callForwardingReason);
}
- return (CallForwardingInfo) sendRequest(
- CMD_GET_CALL_FORWARDING, callForwardingReason, subId);
+
+ Phone phone = getPhone(subId);
+ if (phone == null) {
+ try {
+ callback.onError(
+ TelephonyManager.CallForwardingInfoCallback.RESULT_ERROR_UNKNOWN);
+ } catch (RemoteException e) {
+ // ignore
+ }
+ return;
+ }
+
+ Pair<Integer, TelephonyManager.CallForwardingInfoCallback> argument = Pair.create(
+ callForwardingReason, new TelephonyManager.CallForwardingInfoCallback() {
+ @Override
+ public void onCallForwardingInfoAvailable(CallForwardingInfo info) {
+ try {
+ callback.onCallForwardingInfoAvailable(info);
+ } catch (RemoteException e) {
+ // ignore
+ }
+ }
+
+ @Override
+ public void onError(int error) {
+ try {
+ callback.onError(error);
+ } catch (RemoteException e) {
+ // ignore
+ }
+ }
+ });
+ sendRequestAsync(CMD_GET_CALL_FORWARDING, argument, phone, null);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -5339,7 +6075,8 @@
* reason, the number to forward, and the timeout before the forwarding is attempted.
*/
@Override
- public boolean setCallForwarding(int subId, CallForwardingInfo callForwardingInfo) {
+ public void setCallForwarding(int subId, CallForwardingInfo callForwardingInfo,
+ IIntegerConsumer callback) {
enforceModifyPermission();
long identity = Binder.clearCallingIdentity();
try {
@@ -5347,38 +6084,130 @@
log("setCallForwarding: subId " + subId
+ " callForwardingInfo" + callForwardingInfo);
}
- return (Boolean) sendRequest(CMD_SET_CALL_FORWARDING, callForwardingInfo, subId);
+
+ Phone phone = getPhone(subId);
+ if (phone == null) {
+ try {
+ callback.accept(
+ TelephonyManager.CallForwardingInfoCallback.RESULT_ERROR_UNKNOWN);
+ } catch (RemoteException e) {
+ // ignore
+ }
+ return;
+ }
+
+ Pair<CallForwardingInfo, Consumer<Integer>> arguments = Pair.create(callForwardingInfo,
+ FunctionalUtils.ignoreRemoteException(callback::accept));
+
+ sendRequestAsync(CMD_SET_CALL_FORWARDING, arguments, phone, null);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
- * Get the call forwarding info, given the call forwarding reason.
+ * Get the call waiting status for a subId.
*/
@Override
- public int getCallWaitingStatus(int subId) {
- enforceReadPrivilegedPermission("getCallForwarding");
+ public void getCallWaitingStatus(int subId, IIntegerConsumer callback) {
+ enforceReadPrivilegedPermission("getCallWaitingStatus");
long identity = Binder.clearCallingIdentity();
try {
+ Phone phone = getPhone(subId);
+ if (phone == null) {
+ try {
+ callback.accept(TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR);
+ } catch (RemoteException e) {
+ // ignore
+ }
+ return;
+ }
+ CarrierConfigManager configManager = new CarrierConfigManager(phone.getContext());
+ PersistableBundle c = configManager.getConfigForSubId(subId);
+ boolean requireUssd = c.getBoolean(
+ CarrierConfigManager.KEY_USE_CALL_WAITING_USSD_BOOL, false);
+
if (DBG) log("getCallWaitingStatus: subId " + subId);
- return (Integer) sendRequest(CMD_GET_CALL_WAITING, null, subId);
+ if (requireUssd) {
+ CarrierXmlParser carrierXmlParser = new CarrierXmlParser(phone.getContext(),
+ getSubscriptionCarrierId(subId));
+ String newUssdCommand = "";
+ try {
+ newUssdCommand = carrierXmlParser.getFeature(
+ CarrierXmlParser.FEATURE_CALL_WAITING)
+ .makeCommand(CarrierXmlParser.SsEntry.SSAction.QUERY, null);
+ } catch (NullPointerException e) {
+ loge("Failed to generate USSD number" + e);
+ }
+ ResultReceiver wrappedCallback = new CallWaitingUssdResultReceiver(
+ mMainThreadHandler, callback, carrierXmlParser,
+ CarrierXmlParser.SsEntry.SSAction.QUERY);
+ final String ussdCommand = newUssdCommand;
+ Executors.newSingleThreadExecutor().execute(() -> {
+ handleUssdRequest(subId, ussdCommand, wrappedCallback);
+ });
+ } else {
+ Consumer<Integer> argument = FunctionalUtils.ignoreRemoteException(
+ callback::accept);
+ sendRequestAsync(CMD_GET_CALL_WAITING, argument, phone, null);
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
- * Sets the voice call forwarding info including status (enable/disable), call forwarding
- * reason, the number to forward, and the timeout before the forwarding is attempted.
+ * Sets whether call waiting is enabled for a given subId.
*/
@Override
- public boolean setCallWaitingStatus(int subId, boolean isEnable) {
+ public void setCallWaitingStatus(int subId, boolean enable, IIntegerConsumer callback) {
enforceModifyPermission();
long identity = Binder.clearCallingIdentity();
try {
- if (DBG) log("setCallWaitingStatus: subId " + subId + " isEnable: " + isEnable);
- return (Boolean) sendRequest(CMD_SET_CALL_WAITING, isEnable, subId);
+ if (DBG) log("setCallWaitingStatus: subId " + subId + " enable: " + enable);
+
+ Phone phone = getPhone(subId);
+ if (phone == null) {
+ try {
+ callback.accept(TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR);
+ } catch (RemoteException e) {
+ // ignore
+ }
+ return;
+ }
+
+ CarrierConfigManager configManager = new CarrierConfigManager(phone.getContext());
+ PersistableBundle c = configManager.getConfigForSubId(subId);
+ boolean requireUssd = c.getBoolean(
+ CarrierConfigManager.KEY_USE_CALL_WAITING_USSD_BOOL, false);
+
+ if (DBG) log("getCallWaitingStatus: subId " + subId);
+ if (requireUssd) {
+ CarrierXmlParser carrierXmlParser = new CarrierXmlParser(phone.getContext(),
+ getSubscriptionCarrierId(subId));
+ CarrierXmlParser.SsEntry.SSAction ssAction =
+ enable ? CarrierXmlParser.SsEntry.SSAction.UPDATE_ACTIVATE
+ : CarrierXmlParser.SsEntry.SSAction.UPDATE_DEACTIVATE;
+ String newUssdCommand = "";
+ try {
+ newUssdCommand = carrierXmlParser.getFeature(
+ CarrierXmlParser.FEATURE_CALL_WAITING)
+ .makeCommand(ssAction, null);
+ } catch (NullPointerException e) {
+ loge("Failed to generate USSD number" + e);
+ }
+ ResultReceiver wrappedCallback = new CallWaitingUssdResultReceiver(
+ mMainThreadHandler, callback, carrierXmlParser, ssAction);
+ final String ussdCommand = newUssdCommand;
+ Executors.newSingleThreadExecutor().execute(() -> {
+ handleUssdRequest(subId, ussdCommand, wrappedCallback);
+ });
+ } else {
+ Pair<Boolean, Consumer<Integer>> arguments = Pair.create(enable,
+ FunctionalUtils.ignoreRemoteException(callback::accept));
+
+ sendRequestAsync(CMD_SET_CALL_WAITING, arguments, phone, null);
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -5407,6 +6236,8 @@
.setCallingUid(Binder.getCallingUid())
.setMethod("requestNetworkScan")
.setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+ .setMinSdkVersionForCoarse(Build.VERSION_CODES.Q)
+ .setMinSdkVersionForEnforcement(Build.VERSION_CODES.Q)
.build());
if (locationResult != LocationAccessPolicy.LocationPermissionResult.ALLOWED) {
SecurityException e = checkNetworkRequestForSanitizedLocationAccess(
@@ -5478,123 +6309,29 @@
}
/**
- * Get the calculated preferred network type.
- * Used for debugging incorrect network type.
+ * Get the allowed network types bitmask.
*
- * @return the preferred network type, defined in RILConstants.java.
+ * @return the allowed network types bitmask, defined in RILConstants.java.
*/
@Override
- public int getCalculatedPreferredNetworkType(String callingPackage, String callingFeatureId) {
- final Phone defaultPhone = getDefaultPhone();
- if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mApp, defaultPhone.getSubId(),
- callingPackage, callingFeatureId, "getCalculatedPreferredNetworkType")) {
- return RILConstants.PREFERRED_NETWORK_MODE;
- }
-
- final long identity = Binder.clearCallingIdentity();
- try {
- // FIXME: need to get SubId from somewhere.
- return PhoneFactory.calculatePreferredNetworkType(defaultPhone.getContext(), 0);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- /**
- * Get the preferred network type.
- * Used for device configuration by some CDMA operators.
- *
- * @return the preferred network type, defined in RILConstants.java.
- */
- @Override
- public int getPreferredNetworkType(int subId) {
+ public int getAllowedNetworkTypesBitmask(int subId) {
TelephonyPermissions
- .enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
- mApp, subId, "getPreferredNetworkType");
+ .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+ mApp, subId, "getAllowedNetworkTypesBitmask");
final long identity = Binder.clearCallingIdentity();
try {
- if (DBG) log("getPreferredNetworkType");
- int[] result = (int[]) sendRequest(CMD_GET_PREFERRED_NETWORK_TYPE, null, subId);
- int networkType = (result != null ? result[0] : -1);
- if (DBG) log("getPreferredNetworkType: " + networkType);
- return networkType;
+ if (DBG) log("getAllowedNetworkTypesBitmask");
+ int[] result = (int[]) sendRequest(CMD_GET_ALLOWED_NETWORK_TYPES_BITMASK, null, subId);
+ int networkTypesBitmask = (result != null ? result[0] : -1);
+ if (DBG) log("getAllowedNetworkTypesBitmask: " + networkTypesBitmask);
+ return networkTypesBitmask;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
- * Set the preferred network type.
- *
- * @param networkType the preferred network type, defined in RILConstants.java.
- * @return true on success; false on any failure.
- */
- @Override
- public boolean setPreferredNetworkType(int subId, int networkType) {
- TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
- mApp, subId, "setPreferredNetworkType");
-
- final long identity = Binder.clearCallingIdentity();
- try {
- Boolean success = (Boolean) sendRequest(
- CMD_SET_PREFERRED_NETWORK_TYPE, networkType, subId);
-
- if (success) {
- Settings.Global.putInt(mApp.getContentResolver(),
- Settings.Global.PREFERRED_NETWORK_MODE + subId, networkType);
- }
- if (DBG) log("setPreferredNetworkType: " + (success ? "ok" : "fail"));
- return success;
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- /**
- * Get the allowed network types that store in the telephony provider.
- *
- * @param subId the id of the subscription.
- * @return allowedNetworkTypes the allowed network types.
- */
- @Override
- public long getAllowedNetworkTypes(int subId) {
- TelephonyPermissions
- .enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
- mApp, subId, "getAllowedNetworkTypes");
-
- final long identity = Binder.clearCallingIdentity();
- try {
- return SubscriptionManager.getLongSubscriptionProperty(
- subId, SubscriptionManager.ALLOWED_NETWORK_TYPES, -1, mApp);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- /**
- * Set the allowed network types.
- *
- * @param subId the id of the subscription.
- * @param allowedNetworkTypes the allowed network types.
- * @return true on success; false on any failure.
- */
- @Override
- public boolean setAllowedNetworkTypes(int subId, long allowedNetworkTypes) {
- TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
- mApp, subId, "setAllowedNetworkTypes");
-
- SubscriptionManager.setSubscriptionProperty(subId,
- SubscriptionManager.ALLOWED_NETWORK_TYPES,
- String.valueOf(allowedNetworkTypes));
-
- int preferredNetworkMode = Settings.Global.getInt(mApp.getContentResolver(),
- Settings.Global.PREFERRED_NETWORK_MODE + subId,
- RILConstants.PREFERRED_NETWORK_MODE);
- return setPreferredNetworkType(subId, preferredNetworkMode);
- }
-
- /**
* Get the allowed network types for certain reason.
*
* @param subId the id of the subscription.
@@ -5604,9 +6341,8 @@
@Override
public long getAllowedNetworkTypesForReason(int subId,
@TelephonyManager.AllowedNetworkTypesReason int reason) {
- TelephonyPermissions
- .enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
- mApp, subId, "getAllowedNetworkTypesForReason");
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ mApp, subId, "getAllowedNetworkTypesForReason");
final long identity = Binder.clearCallingIdentity();
try {
return getPhoneFromSubId(subId).getAllowedNetworkTypes(reason);
@@ -5616,21 +6352,61 @@
}
/**
- * Get the effective allowed network types on the device.
- * This API will return an intersection of allowed network types for all reasons,
- * including the configuration done through setAllowedNetworkTypes
- *
- * @param subId the id of the subscription.
- * @return the allowed network types
+ * Enable/Disable E-UTRA-NR Dual Connectivity
+ * @param subId subscription id of the sim card
+ * @param nrDualConnectivityState expected NR dual connectivity state
+ * This can be passed following states
+ * <ol>
+ * <li>Enable NR dual connectivity {@link TelephonyManager#NR_DUAL_CONNECTIVITY_ENABLE}
+ * <li>Disable NR dual connectivity {@link TelephonyManager#NR_DUAL_CONNECTIVITY_DISABLE}
+ * <li>Disable NR dual connectivity and force secondary cell to be released
+ * {@link TelephonyManager#NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE}
+ * </ol>
+ * @return operation result.
*/
@Override
- public long getEffectiveAllowedNetworkTypes(int subId) {
- TelephonyPermissions
- .enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
- mApp, subId, "getEffectiveAllowedNetworkTypes");
+ public int setNrDualConnectivityState(int subId,
+ @TelephonyManager.NrDualConnectivityState int nrDualConnectivityState) {
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
+ mApp, subId, "enableNRDualConnectivity");
+ if (!isRadioInterfaceCapabilitySupported(
+ TelephonyManager.CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE)) {
+ return TelephonyManager.ENABLE_NR_DUAL_CONNECTIVITY_NOT_SUPPORTED;
+ }
+
+ WorkSource workSource = getWorkSource(Binder.getCallingUid());
final long identity = Binder.clearCallingIdentity();
try {
- return getPhoneFromSubId(subId).getEffectiveAllowedNetworkTypes();
+ int result = (int) sendRequest(CMD_ENABLE_NR_DUAL_CONNECTIVITY,
+ nrDualConnectivityState, subId,
+ workSource);
+ if (DBG) log("enableNRDualConnectivity result: " + result);
+ return result;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Is E-UTRA-NR Dual Connectivity enabled
+ * @return true if dual connectivity is enabled else false
+ */
+ @Override
+ public boolean isNrDualConnectivityEnabled(int subId) {
+ TelephonyPermissions
+ .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+ mApp, subId, "isNRDualConnectivityEnabled");
+ if (!isRadioInterfaceCapabilitySupported(
+ TelephonyManager.CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE)) {
+ return false;
+ }
+ WorkSource workSource = getWorkSource(Binder.getCallingUid());
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ boolean isEnabled = (boolean) sendRequest(CMD_IS_NR_DUAL_CONNECTIVITY_ENABLED,
+ null, subId, workSource);
+ if (DBG) log("isNRDualConnectivityEnabled: " + isEnabled);
+ return isEnabled;
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -5647,16 +6423,36 @@
*/
@Override
public boolean setAllowedNetworkTypesForReason(int subId,
- @TelephonyManager.AllowedNetworkTypesReason int reason, long allowedNetworkTypes) {
+ @TelephonyManager.AllowedNetworkTypesReason int reason,
+ @TelephonyManager.NetworkTypeBitMask long allowedNetworkTypes) {
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "setAllowedNetworkTypesForReason");
+ if (!TelephonyManager.isValidAllowedNetworkTypesReason(reason)) {
+ loge("setAllowedNetworkTypesForReason: Invalid allowed network type reason: " + reason);
+ return false;
+ }
+ if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
+ loge("setAllowedNetworkTypesForReason: Invalid subscriptionId:" + subId);
+ return false;
+ }
+
+ log("setAllowedNetworkTypesForReason: " + reason + " value: "
+ + TelephonyManager.convertNetworkTypeBitmaskToString(allowedNetworkTypes));
+
+
+ if (allowedNetworkTypes == getPhoneFromSubId(subId).getAllowedNetworkTypes(reason)) {
+ log("setAllowedNetworkTypesForReason: " + reason + "does not change value");
+ return true;
+ }
+
final long identity = Binder.clearCallingIdentity();
try {
- getPhoneFromSubId(subId).setAllowedNetworkTypes(reason, allowedNetworkTypes);
- int preferredNetworkMode = Settings.Global.getInt(mApp.getContentResolver(),
- Settings.Global.PREFERRED_NETWORK_MODE + subId,
- RILConstants.PREFERRED_NETWORK_MODE);
- return setPreferredNetworkType(subId, preferredNetworkMode);
+ Boolean success = (Boolean) sendRequest(
+ CMD_SET_ALLOWED_NETWORK_TYPES_FOR_REASON,
+ new Pair<Integer, Long>(reason, allowedNetworkTypes), subId);
+
+ if (DBG) log("setAllowedNetworkTypesForReason: " + (success ? "ok" : "fail"));
+ return success;
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -5686,33 +6482,6 @@
}
/**
- * Set mobile data enabled
- * Used by the user through settings etc to turn on/off mobile data
- *
- * @param enable {@code true} turn turn data on, else {@code false}
- */
- @Override
- public void setUserDataEnabled(int subId, boolean enable) {
- TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
- mApp, subId, "setUserDataEnabled");
-
- final long identity = Binder.clearCallingIdentity();
- try {
- int phoneId = mSubscriptionController.getPhoneId(subId);
- if (DBG) log("setUserDataEnabled: subId=" + subId + " phoneId=" + phoneId);
- Phone phone = PhoneFactory.getPhone(phoneId);
- if (phone != null) {
- if (DBG) log("setUserDataEnabled: subId=" + subId + " enable=" + enable);
- phone.getDataEnabledSettings().setUserDataEnabled(enable);
- } else {
- loge("setUserDataEnabled: no phone found. Invalid subId=" + subId);
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- /**
* Enable or disable always reporting signal strength changes from radio.
*
* @param isEnable {@code true} for enabling; {@code false} for disabling.
@@ -5802,7 +6571,18 @@
*/
@Override
public boolean isDataEnabled(int subId) {
- enforceReadPrivilegedPermission("isDataEnabled");
+ try {
+ try {
+ mApp.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_NETWORK_STATE,
+ null);
+ } catch (Exception e) {
+ mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE,
+ "isDataEnabled");
+ }
+ } catch (Exception e) {
+ enforceReadPrivilegedPermission("isDataEnabled");
+ }
final long identity = Binder.clearCallingIdentity();
try {
@@ -5822,6 +6602,53 @@
}
}
+ /**
+ * Check if data is enabled for a specific reason
+ * @param subId Subscription index
+ * @param reason the reason the data enable change is taking place
+ * @return {@code true} if the overall data is enabled; {@code false} if not.
+ */
+ @Override
+ public boolean isDataEnabledForReason(int subId,
+ @TelephonyManager.DataEnabledReason int reason) {
+ try {
+ mApp.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NETWORK_STATE,
+ null);
+ } catch (Exception e) {
+ mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE,
+ "isDataEnabledForReason");
+ }
+
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ int phoneId = mSubscriptionController.getPhoneId(subId);
+ if (DBG) {
+ log("isDataEnabledForReason: subId=" + subId + " phoneId=" + phoneId
+ + " reason=" + reason);
+ }
+ Phone phone = PhoneFactory.getPhone(phoneId);
+ if (phone != null) {
+ boolean retVal;
+ if (reason == TelephonyManager.DATA_ENABLED_REASON_USER) {
+ retVal = phone.isUserDataEnabled();
+ } else {
+ retVal = phone.getDataEnabledSettings().isDataEnabledForReason(reason);
+ }
+ if (DBG) log("isDataEnabledForReason: retVal=" + retVal);
+ return retVal;
+ } else {
+ if (DBG) {
+ loge("isDataEnabledForReason: no phone subId="
+ + subId + " retVal=false");
+ }
+ return false;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
private int getCarrierPrivilegeStatusFromCarrierConfigRules(int privilegeFromSim, int uid,
Phone phone) {
if (uid == Process.PHONE_UID) {
@@ -5844,6 +6671,11 @@
final long identity = Binder.clearCallingIdentity();
try {
int subId = phone.getSubId();
+ if (mCarrierPrivilegeTestOverrideSubIds.contains(subId)) {
+ // A test override is in place for the privileges for this subId, so don't try to
+ // read the subscription privileges.
+ return privilegeFromSim;
+ }
SubscriptionInfo subInfo = subController.getSubscriptionInfo(subId);
List<UiccAccessRule> carrierConfigAccessRules = subInfo.getCarrierConfigAccessRules();
@@ -5868,6 +6700,11 @@
final long identity = Binder.clearCallingIdentity();
try {
int subId = phone.getSubId();
+ if (mCarrierPrivilegeTestOverrideSubIds.contains(subId)) {
+ // A test override is in place for the privileges for this subId, so don't try to
+ // read the subscription privileges.
+ return privilegeFromSim;
+ }
SubscriptionInfo subInfo = subController.getSubscriptionInfo(subId);
List<UiccAccessRule> carrierConfigAccessRules = subInfo.getCarrierConfigAccessRules();
@@ -5951,6 +6788,7 @@
@Override
public int checkCarrierPrivilegesForPackage(int subId, String pkgName) {
+ enforceReadPrivilegedPermission("checkCarrierPrivilegesForPackage");
if (TextUtils.isEmpty(pkgName)) {
return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
}
@@ -5968,6 +6806,7 @@
@Override
public int checkCarrierPrivilegesForPackageAnyPhone(String pkgName) {
+ // TODO(b/186774706): Remove @RequiresPermission from TelephonyManager API
if (TextUtils.isEmpty(pkgName))
return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
int result = TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED;
@@ -5991,6 +6830,7 @@
@Override
public List<String> getCarrierPackageNamesForIntentAndPhone(Intent intent, int phoneId) {
+ enforceReadPrivilegedPermission("getCarrierPackageNamesForIntentAndPhone");
if (!SubscriptionManager.isValidPhoneId(phoneId)) {
loge("phoneId " + phoneId + " is not valid.");
return null;
@@ -6005,6 +6845,7 @@
@Override
public List<String> getPackagesWithCarrierPrivileges(int phoneId) {
+ enforceReadPrivilegedPermission("getPackagesWithCarrierPrivileges");
PackageManager pm = mApp.getPackageManager();
List<String> privilegedPackages = new ArrayList<>();
List<PackageInfo> packages = null;
@@ -6054,18 +6895,59 @@
final Phone phone = getPhone(subId);
UiccCard card = phone == null ? null : phone.getUiccCard();
if (card == null) {
- loge("getIccId: No UICC");
return null;
}
String iccId = card.getIccId();
if (TextUtils.isEmpty(iccId)) {
- loge("getIccId: ICC ID is null or empty.");
return null;
}
return iccId;
}
@Override
+ public void setCallComposerStatus(int subId, int status) {
+ enforceModifyPermission();
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Phone phone = getPhone(subId);
+ if (phone != null) {
+ Phone defaultPhone = phone.getImsPhone();
+ if (defaultPhone != null && defaultPhone.getPhoneType() == PHONE_TYPE_IMS) {
+ ImsPhone imsPhone = (ImsPhone) defaultPhone;
+ imsPhone.setCallComposerStatus(status);
+ ImsManager.getInstance(mApp, getSlotIndexOrException(subId))
+ .updateImsServiceConfig();
+ }
+ }
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public int getCallComposerStatus(int subId) {
+ enforceReadPrivilegedPermission("getCallComposerStatus");
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Phone phone = getPhone(subId);
+ if (phone != null) {
+ Phone defaultPhone = phone.getImsPhone();
+ if (defaultPhone != null && defaultPhone.getPhoneType() == PHONE_TYPE_IMS) {
+ ImsPhone imsPhone = (ImsPhone) defaultPhone;
+ return imsPhone.getCallComposerStatus();
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return TelephonyManager.CALL_COMPOSER_STATUS_OFF;
+ }
+
+ @Override
public boolean setLine1NumberForDisplayForSubscriber(int subId, String alphaTag,
String number) {
TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(mApp,
@@ -6362,20 +7244,11 @@
}
@Override
- public void setRadioCapability(RadioAccessFamily[] rafs) {
- try {
- ProxyController.getInstance().setRadioCapability(rafs);
- } catch (RuntimeException e) {
- Log.w(LOG_TAG, "setRadioCapability: Runtime Exception");
- }
- }
-
- @Override
public int getRadioAccessFamily(int phoneId, String callingPackage) {
Phone phone = PhoneFactory.getPhone(phoneId);
try {
TelephonyPermissions
- .enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+ .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, phone.getSubId(), "getRadioAccessFamily");
} catch (SecurityException e) {
EventLog.writeEvent(0x534e4554, "150857259", -1, "Missing Permission");
@@ -6388,7 +7261,7 @@
final long identity = Binder.clearCallingIdentity();
try {
TelephonyPermissions
- .enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+ .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, phone.getSubId(), "getRadioAccessFamily");
raf = ProxyController.getInstance().getRadioAccessFamily(phoneId);
} finally {
@@ -6398,6 +7271,98 @@
}
@Override
+ public void uploadCallComposerPicture(int subscriptionId, String callingPackage,
+ String contentType, ParcelFileDescriptor fd, ResultReceiver callback) {
+ try {
+ if (!Objects.equals(mApp.getPackageManager().getPackageUid(callingPackage, 0),
+ Binder.getCallingUid())) {
+ throw new SecurityException("Invalid package:" + callingPackage);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new SecurityException("Invalid package:" + callingPackage);
+ }
+ RoleManager rm = mApp.getSystemService(RoleManager.class);
+ List<String> dialerRoleHolders = rm.getRoleHolders(RoleManager.ROLE_DIALER);
+ if (!dialerRoleHolders.contains(callingPackage)) {
+ throw new SecurityException("App must be the dialer role holder to"
+ + " upload a call composer pic");
+ }
+
+ Executors.newSingleThreadExecutor().execute(() -> {
+ ByteArrayOutputStream output = new ByteArrayOutputStream(
+ (int) TelephonyManager.getMaximumCallComposerPictureSize());
+ InputStream input = new ParcelFileDescriptor.AutoCloseInputStream(fd);
+ boolean readUntilEnd = false;
+ int totalBytesRead = 0;
+ byte[] buffer = new byte[16 * 1024];
+ while (true) {
+ int numRead;
+ try {
+ numRead = input.read(buffer);
+ } catch (IOException e) {
+ try {
+ fd.checkError();
+ callback.send(TelephonyManager.CallComposerException.ERROR_INPUT_CLOSED,
+ null);
+ } catch (IOException e1) {
+ // This means that the other side closed explicitly with an error. If this
+ // happens, log and ignore.
+ loge("Remote end of call composer picture pipe closed: " + e1);
+ }
+ break;
+ }
+ if (numRead == -1) {
+ readUntilEnd = true;
+ break;
+ }
+ totalBytesRead += numRead;
+ if (totalBytesRead > TelephonyManager.getMaximumCallComposerPictureSize()) {
+ loge("Too many bytes read for call composer picture: " + totalBytesRead);
+ try {
+ input.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ break;
+ }
+ output.write(buffer, 0, numRead);
+ }
+ // Generally, the remote end will close the file descriptors. The only case where we
+ // close is above, where the picture size is too big.
+
+ try {
+ fd.checkError();
+ } catch (IOException e) {
+ loge("Remote end for call composer closed with an error: " + e);
+ return;
+ }
+
+ if (!readUntilEnd) {
+ loge("Did not finish reading entire image; aborting");
+ return;
+ }
+
+ ImageData imageData = new ImageData(output.toByteArray(), contentType, null);
+ CallComposerPictureManager.getInstance(mApp, subscriptionId).handleUploadToServer(
+ new CallComposerPictureTransfer.Factory() {},
+ imageData,
+ (result) -> {
+ if (result.first != null) {
+ ParcelUuid parcelUuid = new ParcelUuid(result.first);
+ Bundle outputResult = new Bundle();
+ outputResult.putParcelable(
+ TelephonyManager.KEY_CALL_COMPOSER_PICTURE_HANDLE, parcelUuid);
+ callback.send(TelephonyManager.CallComposerException.SUCCESS,
+ outputResult);
+ } else {
+ callback.send(result.second, null);
+ }
+ }
+ );
+ });
+ }
+
+ @Override
public void enableVideoCalling(boolean enable) {
final Phone defaultPhone = getDefaultPhone();
enforceModifyPermission();
@@ -6548,6 +7513,13 @@
*/
@Override
public String getDeviceIdWithFeature(String callingPackage, String callingFeatureId) {
+ try {
+ mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+ } catch (SecurityException se) {
+ EventLog.writeEvent(0x534e4554, "186530889", Binder.getCallingUid());
+ throw new SecurityException("Package " + callingPackage + " does not belong to "
+ + Binder.getCallingUid());
+ }
final Phone phone = PhoneFactory.getPhone(0);
if (phone == null) {
return null;
@@ -6608,6 +7580,11 @@
@Override
public @Nullable PhoneAccountHandle getPhoneAccountHandleForSubscriptionId(int subscriptionId) {
+ TelephonyPermissions
+ .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+ mApp,
+ subscriptionId,
+ "getPhoneAccountHandleForSubscriptionId, " + "subscriptionId: " + subscriptionId);
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subscriptionId);
@@ -6678,17 +7655,23 @@
if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)) {
return;
}
-
+ Phone defaultPhone = getDefaultPhone();
+ if (defaultPhone != null) {
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
+ mApp, getDefaultPhone().getSubId(), "factoryReset");
+ }
final long identity = Binder.clearCallingIdentity();
try {
if (SubscriptionManager.isUsableSubIdValue(subId) && !mUserManager.hasUserRestriction(
UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
- setUserDataEnabled(subId, getDefaultDataEnabled());
+ setDataEnabledForReason(subId, TelephonyManager.DATA_ENABLED_REASON_USER,
+ getDefaultDataEnabled());
setNetworkSelectionModeAutomatic(subId);
- setPreferredNetworkType(subId, getDefaultNetworkType(subId));
+ Phone phone = getPhone(subId);
+ cleanUpAllowedNetworkTypes(phone, subId);
setDataRoamingEnabled(subId, getDefaultDataRoamingEnabled(subId));
- CarrierInfoManager.deleteAllCarrierKeysForImsiEncryption(mApp);
+ getPhone(subId).resetCarrierKeysForImsiEncryption();
}
// There has been issues when Sms raw table somehow stores orphan
// fragments. They lead to garbled message when new fragments come
@@ -6701,17 +7684,37 @@
ImsManager.getInstance(mApp, slotId).factoryReset();
}
+ if (defaultPhone == null) {
+ return;
+ }
// Erase modem config if erase modem on network setting is enabled.
String configValue = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TELEPHONY,
RESET_NETWORK_ERASE_MODEM_CONFIG_ENABLED);
if (configValue != null && Boolean.parseBoolean(configValue)) {
- sendEraseModemConfig(getDefaultPhone());
+ sendEraseModemConfig(defaultPhone);
}
+
+ sendEraseDataInSharedPreferences(defaultPhone);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
+ @VisibleForTesting
+ void cleanUpAllowedNetworkTypes(Phone phone, int subId) {
+ if (phone == null || !SubscriptionManager.isUsableSubscriptionId(subId)) {
+ return;
+ }
+ long defaultNetworkType = RadioAccessFamily.getRafFromNetworkType(
+ RILConstants.PREFERRED_NETWORK_MODE);
+ SubscriptionManager.setSubscriptionProperty(subId,
+ SubscriptionManager.ALLOWED_NETWORK_TYPES,
+ "user=" + defaultNetworkType);
+ phone.loadAllowedNetworksFromSubscriptionDatabase();
+ phone.setAllowedNetworkTypes(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER,
+ defaultNetworkType, null);
+ }
+
private void cleanUpSmsRawTable(Context context) {
ContentResolver resolver = context.getContentResolver();
Uri uri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw/permanentDelete");
@@ -6780,7 +7783,7 @@
}
private final ModemActivityInfo mLastModemActivityInfo =
- new ModemActivityInfo(0, 0, 0, new int[0], 0);
+ new ModemActivityInfo(0, 0, 0, new int[ModemActivityInfo.getNumTxPowerLevels()], 0);
/**
* Responds to the ResultReceiver with the {@link android.telephony.ModemActivityInfo} object
@@ -6810,12 +7813,9 @@
return false;
}
int activityDurationMs =
- (int) (info.getTimestamp() - mLastModemActivityInfo.getTimestamp());
- int totalTxTimeMs = 0;
- int[] txTimeMs = info.getTransmitTimeMillis();
- for (int i = 0; i < info.getTransmitPowerInfo().size(); i++) {
- totalTxTimeMs += txTimeMs[i];
- }
+ (int) (info.getTimestampMillis() - mLastModemActivityInfo.getTimestampMillis());
+ int totalTxTimeMs = Arrays.stream(info.getTransmitTimeMillis()).sum();
+
return (info.isValid()
&& (info.getSleepTimeMillis() <= activityDurationMs)
&& (info.getIdleTimeMillis() <= activityDurationMs)
@@ -6845,6 +7845,8 @@
.setMethod("getServiceStateForSubscriber")
.setLogAsInfo(true)
.setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+ .setMinSdkVersionForCoarse(Build.VERSION_CODES.Q)
+ .setMinSdkVersionForEnforcement(Build.VERSION_CODES.Q)
.build());
LocationAccessPolicy.LocationPermissionResult coarseLocationResult =
@@ -6857,6 +7859,8 @@
.setMethod("getServiceStateForSubscriber")
.setLogAsInfo(true)
.setMinSdkVersionForCoarse(Build.VERSION_CODES.Q)
+ .setMinSdkVersionForFine(Integer.MAX_VALUE)
+ .setMinSdkVersionForEnforcement(Build.VERSION_CODES.Q)
.build());
// We don't care about hard or soft here -- all we need to know is how much info to scrub.
boolean hasFinePermission =
@@ -6864,7 +7868,15 @@
boolean hasCoarsePermission =
coarseLocationResult == LocationAccessPolicy.LocationPermissionResult.ALLOWED;
+ final Phone phone = getPhone(subId);
+ if (phone == null) {
+ return null;
+ }
+
final long identity = Binder.clearCallingIdentity();
+
+ boolean isCallingPackageDataService = phone.getDataServicePackages()
+ .contains(callingPackage);
try {
// isActiveSubId requires READ_PHONE_STATE, which we already check for above
if (!mSubscriptionController.isActiveSubId(subId, callingPackage, callingFeatureId)) {
@@ -6873,16 +7885,11 @@
return null;
}
- final Phone phone = getPhone(subId);
- if (phone == null) {
- return null;
- }
-
ServiceState ss = phone.getServiceState();
// Scrub out the location info in ServiceState depending on what level of access
// the caller has.
- if (hasFinePermission) return ss;
+ if (hasFinePermission || isCallingPackageDataService) return ss;
if (hasCoarsePermission) return ss.createLocationInfoSanitizedCopy(false);
return ss.createLocationInfoSanitizedCopy(true);
} finally {
@@ -7211,31 +8218,6 @@
}
/**
- * Action set from carrier signalling broadcast receivers to enable/disable metered apns
- * @param subId the subscription ID that this action applies to.
- * @param enabled control enable or disable metered apns.
- * {@hide}
- */
- @Override
- public void carrierActionSetMeteredApnsEnabled(int subId, boolean enabled) {
- enforceModifyPermission();
- final Phone phone = getPhone(subId);
-
- final long identity = Binder.clearCallingIdentity();
- if (phone == null) {
- loge("carrierAction: SetMeteredApnsEnabled fails with invalid subId: " + subId);
- return;
- }
- try {
- phone.carrierActionSetMeteredApnsEnabled(enabled);
- } catch (Exception e) {
- Log.e(LOG_TAG, "carrierAction: SetMeteredApnsEnabled fails. Exception ex=" + e);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- /**
* Action set from carrier signalling broadcast receivers to enable/disable radio
* @param subId the subscription ID that this action applies to.
* @param enabled control enable or disable radio.
@@ -7336,20 +8318,36 @@
}
/**
- * Policy control of data connection. Usually used when data limit is passed.
- * @param enabled True if enabling the data, otherwise disabling.
+ * Policy control of data connection with reason {@@TelephonyManager.DataEnabledReason}
* @param subId Subscription index
- * {@hide}
+ * @param reason the reason the data enable change is taking place
+ * @param enabled True if enabling the data, otherwise disabling.
+ * @hide
*/
@Override
- public void setPolicyDataEnabled(boolean enabled, int subId) {
- enforceModifyPermission();
+ public void setDataEnabledForReason(int subId, @TelephonyManager.DataEnabledReason int reason,
+ boolean enabled) {
+ if (reason == TelephonyManager.DATA_ENABLED_REASON_USER
+ || reason == TelephonyManager.DATA_ENABLED_REASON_CARRIER) {
+ try {
+ TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(
+ mApp, subId, "setDataEnabledForReason");
+ } catch (SecurityException se) {
+ enforceModifyPermission();
+ }
+ } else {
+ enforceModifyPermission();
+ }
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subId);
if (phone != null) {
- phone.getDataEnabledSettings().setPolicyDataEnabled(enabled);
+ if (reason == TelephonyManager.DATA_ENABLED_REASON_CARRIER) {
+ phone.carrierActionSetMeteredApnsEnabled(enabled);
+ } else {
+ phone.getDataEnabledSettings().setDataEnabled(reason, enabled);
+ }
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -7407,7 +8405,37 @@
final long identity = Binder.clearCallingIdentity();
try {
if (phone != null) {
- phone.setSimPowerState(state, workSource);
+ phone.setSimPowerState(state, null, workSource);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Set SIM card power state.
+ *
+ * @param slotIndex SIM slot id.
+ * @param state State of SIM (power down, power up, pass through)
+ * @param callback callback to trigger after success or failure
+ * - {@link android.telephony.TelephonyManager#CARD_POWER_DOWN}
+ * - {@link android.telephony.TelephonyManager#CARD_POWER_UP}
+ * - {@link android.telephony.TelephonyManager#CARD_POWER_UP_PASS_THROUGH}
+ *
+ **/
+ @Override
+ public void setSimPowerStateForSlotWithCallback(int slotIndex, int state,
+ IIntegerConsumer callback) {
+ enforceModifyPermission();
+ Phone phone = PhoneFactory.getPhone(slotIndex);
+
+ WorkSource workSource = getWorkSource(Binder.getCallingUid());
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (phone != null) {
+ Pair<Integer, IIntegerConsumer> arguments = Pair.create(state, callback);
+ sendRequestAsync(CMD_SET_SIM_POWER, arguments, phone, workSource);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -7512,17 +8540,19 @@
*/
@Override
public boolean isDataRoamingEnabled(int subId) {
- mApp.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NETWORK_STATE,
- null /* message */);
+ try {
+ mApp.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NETWORK_STATE,
+ null);
+ } catch (Exception e) {
+ TelephonyPermissions.enforceCallingOrSelfReadPhoneStatePermissionOrCarrierPrivilege(
+ mApp, subId, "isDataRoamingEnabled");
+ }
boolean isEnabled = false;
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subId);
isEnabled = phone != null ? phone.getDataRoamingEnabled() : false;
- } catch (Exception e) {
- TelephonyPermissions.enforeceCallingOrSelfReadPhoneStatePermissionOrCarrierPrivilege(
- mApp, subId, "isDataRoamingEnabled");
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -7559,7 +8589,7 @@
@Override
public boolean isManualNetworkSelectionAllowed(int subId) {
TelephonyPermissions
- .enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+ .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isManualNetworkSelectionAllowed");
boolean isAllowed = true;
@@ -7798,6 +8828,11 @@
}
phone.setCarrierTestOverride(mccmnc, imsi, iccid, gid1, gid2, plmn, spn,
carrierPrivilegeRules, apn);
+ if (carrierPrivilegeRules == null) {
+ mCarrierPrivilegeTestOverrideSubIds.remove(subId);
+ } else {
+ mCarrierPrivilegeTestOverrideSubIds.add(subId);
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -7840,7 +8875,7 @@
@Override
public int getCdmaRoamingMode(int subId) {
TelephonyPermissions
- .enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+ .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getCdmaRoamingMode");
final long identity = Binder.clearCallingIdentity();
@@ -7865,6 +8900,20 @@
}
@Override
+ public int getCdmaSubscriptionMode(int subId) {
+ TelephonyPermissions
+ .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+ mApp, subId, "getCdmaSubscriptionMode");
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return (int) sendRequest(CMD_GET_CDMA_SUBSCRIPTION_MODE, null /* argument */, subId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public boolean setCdmaSubscriptionMode(int subId, int mode) {
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "setCdmaSubscriptionMode");
@@ -7907,18 +8956,16 @@
final Phone defaultPhone = getDefaultPhone();
if (!exactMatch) {
TelephonyPermissions
- .enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+ .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, defaultPhone.getSubId(), "isEmergencyNumber(Potential)");
}
final long identity = Binder.clearCallingIdentity();
try {
for (Phone phone: PhoneFactory.getPhones()) {
if (phone.getEmergencyNumberTracker() != null
- && phone.getEmergencyNumberTracker() != null) {
- if (phone.getEmergencyNumberTracker().isEmergencyNumber(
- number, exactMatch)) {
- return true;
- }
+ && phone.getEmergencyNumberTracker()
+ .isEmergencyNumber(number, exactMatch)) {
+ return true;
}
}
return false;
@@ -8180,7 +9227,7 @@
loge("isMultiSimSupportedInternal: no static configuration available");
return TelephonyManager.MULTISIM_NOT_SUPPORTED_BY_HARDWARE;
}
- if (staticCapability.logicalModemList.size() < 2) {
+ if (staticCapability.getLogicalModemList().size() < 2) {
loge("isMultiSimSupportedInternal: maximum number of modem is < 2");
return TelephonyManager.MULTISIM_NOT_SUPPORTED_BY_HARDWARE;
}
@@ -8324,7 +9371,7 @@
* 1) user data is turned on, or
* 2) APN is un-metered for this subscription, or
* 3) APN type is whitelisted. E.g. MMS is whitelisted if
- * {@link TelephonyManager#setAlwaysAllowMmsData} is turned on.
+ * {@link TelephonyManager#MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED} is enabled.
*
* @return whether data is allowed for a apn type.
*
@@ -8398,6 +9445,26 @@
}
@Override
+ public List<RadioAccessSpecifier> getSystemSelectionChannels(int subId) {
+ TelephonyPermissions
+ .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+ mApp, subId, "getSystemSelectionChannels");
+ WorkSource workSource = getWorkSource(Binder.getCallingUid());
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Object result = sendRequest(CMD_GET_SYSTEM_SELECTION_CHANNELS, null, subId, workSource);
+ if (result instanceof IllegalStateException) {
+ throw (IllegalStateException) result;
+ }
+ List<RadioAccessSpecifier> specifiers = (List<RadioAccessSpecifier>) result;
+ if (DBG) log("getSystemSelectionChannels: " + specifiers);
+ return specifiers;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public boolean isMvnoMatched(int subId, int mvnoType, @NonNull String mvnoMatchData) {
enforceReadPrivilegedPermission("isMvnoMatched");
IccRecords iccRecords = UiccController.getInstance().getIccRecords(
@@ -8436,6 +9503,11 @@
//TODO investigate if this API should require proper permission check in R b/133791609
final long identity = Binder.clearCallingIdentity();
try {
+ String carrierUAProfUrl = mApp.getCarrierConfigForSubId(subId).getString(
+ CarrierConfigManager.KEY_MMS_UA_PROF_URL_STRING);
+ if (!TextUtils.isEmpty(carrierUAProfUrl)) {
+ return carrierUAProfUrl;
+ }
return SubscriptionManager.getResourcesForSubId(getDefaultPhone().getContext(), subId)
.getString(com.android.internal.R.string.config_mms_user_agent_profile_url);
} finally {
@@ -8448,6 +9520,11 @@
//TODO investigate if this API should require proper permission check in R b/133791609
final long identity = Binder.clearCallingIdentity();
try {
+ String carrierUserAgent = mApp.getCarrierConfigForSubId(subId).getString(
+ CarrierConfigManager.KEY_MMS_USER_AGENT_STRING);
+ if (!TextUtils.isEmpty(carrierUserAgent)) {
+ return carrierUserAgent;
+ }
return SubscriptionManager.getResourcesForSubId(getDefaultPhone().getContext(), subId)
.getString(com.android.internal.R.string.config_mms_user_agent);
} finally {
@@ -8456,55 +9533,54 @@
}
@Override
- public boolean setDataAllowedDuringVoiceCall(int subId, boolean allow) {
- enforceModifyPermission();
+ public boolean isMobileDataPolicyEnabled(int subscriptionId, int policy) {
+ enforceReadPrivilegedPermission("isMobileDataPolicyEnabled");
- // Now that all security checks passes, perform the operation as ourselves.
final long identity = Binder.clearCallingIdentity();
try {
- Phone phone = getPhone(subId);
+ Phone phone = getPhone(subscriptionId);
if (phone == null) return false;
- return phone.getDataEnabledSettings().setAllowDataDuringVoiceCall(allow);
+ switch (policy) {
+ case TelephonyManager.MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL:
+ return phone.getDataEnabledSettings().isDataAllowedInVoiceCall();
+ case TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED:
+ return phone.getDataEnabledSettings().isMmsAlwaysAllowed();
+ default:
+ throw new IllegalArgumentException(policy + " is not a valid policy");
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
- public boolean isDataAllowedInVoiceCall(int subId) {
- enforceReadPrivilegedPermission("isDataAllowedInVoiceCall");
-
- // Now that all security checks passes, perform the operation as ourselves.
- final long identity = Binder.clearCallingIdentity();
- try {
- Phone phone = getPhone(subId);
- if (phone == null) return false;
-
- return phone.getDataEnabledSettings().isDataAllowedInVoiceCall();
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
- public boolean setAlwaysAllowMmsData(int subId, boolean alwaysAllow) {
+ public void setMobileDataPolicyEnabled(int subscriptionId, int policy,
+ boolean enabled) {
enforceModifyPermission();
- // Now that all security checks passes, perform the operation as ourselves.
final long identity = Binder.clearCallingIdentity();
try {
- Phone phone = getPhone(subId);
- if (phone == null) return false;
+ Phone phone = getPhone(subscriptionId);
+ if (phone == null) return;
- return phone.getDataEnabledSettings().setAlwaysAllowMmsData(alwaysAllow);
+ switch (policy) {
+ case TelephonyManager.MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL:
+ phone.getDataEnabledSettings().setAllowDataDuringVoiceCall(enabled);
+ break;
+ case TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED:
+ phone.getDataEnabledSettings().setAlwaysAllowMmsData(enabled);
+ break;
+ default:
+ throw new IllegalArgumentException(policy + " is not a valid policy");
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
- * Updates whether conference event pacakge handling is enabled.
+ * Updates whether conference event package handling is enabled.
* @param isCepEnabled {@code true} if CEP handling is enabled (default), or {@code false}
* otherwise.
*/
@@ -8543,15 +9619,19 @@
isCompressed) {
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "notifyRcsAutoConfigurationReceived");
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
+ }
+ if (!isImsAvailableOnDevice()) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "IMS not available on device.");
+ }
+
+ final long identity = Binder.clearCallingIdentity();
try {
- IImsConfig configBinder = getImsConfig(getSlotIndex(subId), ImsFeature.FEATURE_RCS);
- if (configBinder == null) {
- Rlog.e(LOG_TAG, "null result for getImsConfig");
- } else {
- configBinder.notifyRcsAutoConfigurationReceived(config, isCompressed);
- }
- } catch (RemoteException e) {
- Rlog.e(LOG_TAG, "fail to getImsConfig " + e.getMessage());
+ RcsProvisioningMonitor.getInstance().updateConfig(subId, config, isCompressed);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
@@ -8681,4 +9761,1061 @@
public boolean canConnectTo5GInDsdsMode() {
return mApp.getResources().getBoolean(R.bool.config_5g_connection_in_dsds_mode);
}
+
+ @Override
+ public @NonNull List<String> getEquivalentHomePlmns(int subId, String callingPackage,
+ String callingFeatureId) {
+ if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
+ mApp, subId, callingPackage, callingFeatureId, "getEquivalentHomePlmns")) {
+ throw new SecurityException("Requires READ_PHONE_STATE permission.");
+ }
+
+ Phone phone = getPhone(subId);
+ if (phone == null) {
+ throw new RuntimeException("phone is not available");
+ }
+ // Now that all security checks passes, perform the operation as ourselves.
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return phone.getEquivalentHomePlmns();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public boolean isRadioInterfaceCapabilitySupported(
+ final @NonNull @TelephonyManager.RadioInterfaceCapability String capability) {
+ Set<String> radioInterfaceCapabilities =
+ mRadioInterfaceCapabilities.getCapabilities();
+ if (radioInterfaceCapabilities == null) {
+ throw new RuntimeException("radio interface capabilities are not available");
+ }
+ return radioInterfaceCapabilities.contains(capability);
+ }
+
+ @Override
+ public void bootstrapAuthenticationRequest(int subId, int appType, Uri nafUrl,
+ UaSecurityProtocolIdentifier securityProtocol,
+ boolean forceBootStrapping, IBootstrapAuthenticationCallback callback) {
+ TelephonyPermissions.enforceAnyPermissionGrantedOrCarrierPrivileges(mApp, subId,
+ Binder.getCallingUid(), "bootstrapAuthenticationRequest",
+ Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION,
+ Manifest.permission.MODIFY_PHONE_STATE);
+ if (DBG) {
+ log("bootstrapAuthenticationRequest, subId:" + subId + ", appType:"
+ + appType + ", NAF:" + nafUrl + ", sp:" + securityProtocol
+ + ", forceBootStrapping:" + forceBootStrapping + ", callback:" + callback);
+ }
+
+ if (!SubscriptionManager.isValidSubscriptionId(subId)
+ || appType < TelephonyManager.APPTYPE_UNKNOWN
+ || appType > TelephonyManager.APPTYPE_ISIM
+ || nafUrl == null || securityProtocol == null || callback == null) {
+ Log.d(LOG_TAG, "bootstrapAuthenticationRequest failed due to invalid parameters");
+ if (callback != null) {
+ try {
+ callback.onAuthenticationFailure(
+ 0, TelephonyManager.GBA_FAILURE_REASON_FEATURE_NOT_SUPPORTED);
+ } catch (RemoteException exception) {
+ log("Fail to notify onAuthenticationFailure due to " + exception);
+ }
+ return;
+ }
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ getGbaManager(subId).bootstrapAuthenticationRequest(
+ new GbaAuthRequest(subId, appType, nafUrl, securityProtocol.toByteArray(),
+ forceBootStrapping, callback));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Attempts to set the radio power state for all phones for thermal reason.
+ * This does not guarantee that the
+ * requested radio power state will actually be set. See {@link
+ * PhoneInternalInterface#setRadioPowerForReason} for more details.
+ *
+ * @param enable {@code true} if trying to turn radio on.
+ * @return {@code true} if phone setRadioPowerForReason was called. Otherwise, returns {@code
+ * false}.
+ */
+ private boolean setRadioPowerForThermal(boolean enable) {
+ boolean isPhoneAvailable = false;
+ for (int i = 0; i < TelephonyManager.getDefault().getActiveModemCount(); i++) {
+ Phone phone = PhoneFactory.getPhone(i);
+ if (phone != null) {
+ phone.setRadioPowerForReason(enable, Phone.RADIO_POWER_REASON_THERMAL);
+ isPhoneAvailable = true;
+ }
+ }
+
+ // return true if successfully informed the phone object about the thermal radio power
+ // request.
+ return isPhoneAvailable;
+ }
+
+ private int handleDataThrottlingRequest(int subId,
+ DataThrottlingRequest dataThrottlingRequest) {
+ boolean isDataThrottlingSupported = isRadioInterfaceCapabilitySupported(
+ TelephonyManager.CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING);
+ if (!isDataThrottlingSupported && dataThrottlingRequest.getDataThrottlingAction()
+ != DataThrottlingRequest.DATA_THROTTLING_ACTION_NO_DATA_THROTTLING) {
+ throw new IllegalArgumentException("modem does not support data throttling");
+ }
+
+ // Ensure that radio is on. If not able to power on due to phone being unavailable, return
+ // THERMAL_MITIGATION_RESULT_MODEM_NOT_AVAILABLE.
+ if (!setRadioPowerForThermal(true)) {
+ return TelephonyManager.THERMAL_MITIGATION_RESULT_MODEM_NOT_AVAILABLE;
+ }
+
+ setDataEnabledForReason(subId, TelephonyManager.DATA_ENABLED_REASON_THERMAL, true);
+
+ if (isDataThrottlingSupported) {
+ int thermalMitigationResult =
+ (int) sendRequest(CMD_SET_DATA_THROTTLING, dataThrottlingRequest, subId);
+ if (thermalMitigationResult == SET_DATA_THROTTLING_MODEM_THREW_INVALID_PARAMS) {
+ throw new IllegalArgumentException("modem returned INVALID_ARGUMENTS");
+ } else if (thermalMitigationResult
+ == MODEM_DOES_NOT_SUPPORT_DATA_THROTTLING_ERROR_CODE) {
+ log("Modem likely does not support data throttling on secondary carrier. Data " +
+ "throttling action = " + dataThrottlingRequest.getDataThrottlingAction());
+ return TelephonyManager.THERMAL_MITIGATION_RESULT_MODEM_ERROR;
+ }
+ return thermalMitigationResult;
+ }
+
+ return TelephonyManager.THERMAL_MITIGATION_RESULT_SUCCESS;
+ }
+
+ private static List<String> getThermalMitigationAllowlist(Context context) {
+ if (sThermalMitigationAllowlistedPackages.isEmpty()) {
+ for (String pckg : context.getResources()
+ .getStringArray(R.array.thermal_mitigation_allowlisted_packages)) {
+ sThermalMitigationAllowlistedPackages.add(pckg);
+ }
+ }
+
+ return sThermalMitigationAllowlistedPackages;
+ }
+
+ private boolean isAnyPhoneInEmergencyState() {
+ TelecomManager tm = mApp.getSystemService(TelecomManager.class);
+ if (tm.isInEmergencyCall()) {
+ Log.e(LOG_TAG , "Phone state is not valid. One of the phones is in an emergency call");
+ return true;
+ }
+ for (Phone phone : PhoneFactory.getPhones()) {
+ if (phone.isInEmergencySmsMode() || phone.isInEcm()) {
+ Log.e(LOG_TAG, "Phone state is not valid. isInEmergencySmsMode = "
+ + phone.isInEmergencySmsMode() + " isInEmergencyCallbackMode = "
+ + phone.isInEcm());
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Used by shell commands to add an authorized package name for thermal mitigation.
+ * @param packageName name of package to be allowlisted
+ * @param context
+ */
+ static void addPackageToThermalMitigationAllowlist(String packageName, Context context) {
+ sThermalMitigationAllowlistedPackages = getThermalMitigationAllowlist(context);
+ sThermalMitigationAllowlistedPackages.add(packageName);
+ }
+
+ /**
+ * Used by shell commands to remove an authorized package name for thermal mitigation.
+ * @param packageName name of package to remove from allowlist
+ * @param context
+ */
+ static void removePackageFromThermalMitigationAllowlist(String packageName, Context context) {
+ sThermalMitigationAllowlistedPackages = getThermalMitigationAllowlist(context);
+ sThermalMitigationAllowlistedPackages.remove(packageName);
+ }
+
+ /**
+ * Thermal mitigation request to control functionalities at modem.
+ *
+ * @param subId the id of the subscription.
+ * @param thermalMitigationRequest holds all necessary information to be passed down to modem.
+ * @param callingPackage the package name of the calling package.
+ *
+ * @return thermalMitigationResult enum as defined in android.telephony.Annotation.
+ */
+ @Override
+ @ThermalMitigationResult
+ public int sendThermalMitigationRequest(
+ int subId,
+ ThermalMitigationRequest thermalMitigationRequest,
+ String callingPackage) throws IllegalArgumentException {
+ enforceModifyPermission();
+
+ mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+ if (!getThermalMitigationAllowlist(getDefaultPhone().getContext())
+ .contains(callingPackage)) {
+ throw new SecurityException("Calling package must be configured in the device config. "
+ + "calling package: " + callingPackage);
+ }
+
+ WorkSource workSource = getWorkSource(Binder.getCallingUid());
+ final long identity = Binder.clearCallingIdentity();
+
+ int thermalMitigationResult = TelephonyManager.THERMAL_MITIGATION_RESULT_UNKNOWN_ERROR;
+ try {
+ int thermalMitigationAction = thermalMitigationRequest.getThermalMitigationAction();
+ switch (thermalMitigationAction) {
+ case ThermalMitigationRequest.THERMAL_MITIGATION_ACTION_DATA_THROTTLING:
+ thermalMitigationResult =
+ handleDataThrottlingRequest(subId,
+ thermalMitigationRequest.getDataThrottlingRequest());
+ break;
+ case ThermalMitigationRequest.THERMAL_MITIGATION_ACTION_VOICE_ONLY:
+ if (thermalMitigationRequest.getDataThrottlingRequest() != null) {
+ throw new IllegalArgumentException("dataThrottlingRequest must be null for "
+ + "ThermalMitigationRequest.THERMAL_MITIGATION_ACTION_VOICE_ONLY");
+ }
+
+ // Ensure that radio is on. If not able to power on due to phone being
+ // unavailable, return THERMAL_MITIGATION_RESULT_MODEM_NOT_AVAILABLE.
+ if (!setRadioPowerForThermal(true)) {
+ thermalMitigationResult =
+ TelephonyManager.THERMAL_MITIGATION_RESULT_MODEM_NOT_AVAILABLE;
+ break;
+ }
+
+ setDataEnabledForReason(subId, TelephonyManager.DATA_ENABLED_REASON_THERMAL,
+ false);
+ thermalMitigationResult = TelephonyManager.THERMAL_MITIGATION_RESULT_SUCCESS;
+ break;
+ case ThermalMitigationRequest.THERMAL_MITIGATION_ACTION_RADIO_OFF:
+ if (thermalMitigationRequest.getDataThrottlingRequest() != null) {
+ throw new IllegalArgumentException("dataThrottlingRequest must be null for"
+ + " ThermalMitigationRequest.THERMAL_MITIGATION_ACTION_RADIO_OFF");
+ }
+
+ TelecomAccountRegistry registry = TelecomAccountRegistry.getInstance(null);
+ if (registry != null) {
+ Phone phone = getPhone(subId);
+ if (phone == null) {
+ thermalMitigationResult =
+ TelephonyManager.THERMAL_MITIGATION_RESULT_MODEM_NOT_AVAILABLE;
+ break;
+ }
+
+ TelephonyConnectionService service =
+ registry.getTelephonyConnectionService();
+ if (service != null && service.isEmergencyCallPending()) {
+ Log.e(LOG_TAG, "An emergency call is pending");
+ thermalMitigationResult =
+ TelephonyManager.THERMAL_MITIGATION_RESULT_INVALID_STATE;
+ break;
+ } else if (isAnyPhoneInEmergencyState()) {
+ thermalMitigationResult =
+ TelephonyManager.THERMAL_MITIGATION_RESULT_INVALID_STATE;
+ break;
+ }
+ } else {
+ thermalMitigationResult =
+ TelephonyManager.THERMAL_MITIGATION_RESULT_MODEM_NOT_AVAILABLE;
+ break;
+ }
+
+ // Turn radio off. If not able to power off due to phone being unavailable,
+ // return THERMAL_MITIGATION_RESULT_MODEM_NOT_AVAILABLE.
+ if (!setRadioPowerForThermal(false)) {
+ thermalMitigationResult =
+ TelephonyManager.THERMAL_MITIGATION_RESULT_MODEM_NOT_AVAILABLE;
+ break;
+ }
+ thermalMitigationResult =
+ TelephonyManager.THERMAL_MITIGATION_RESULT_SUCCESS;
+ break;
+ default:
+ throw new IllegalArgumentException("the requested thermalMitigationAction does "
+ + "not exist. Requested action: " + thermalMitigationAction);
+ }
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "thermalMitigationRequest. Exception e =" + e);
+ thermalMitigationResult = TelephonyManager.THERMAL_MITIGATION_RESULT_MODEM_ERROR;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ if (DBG) {
+ log("thermalMitigationRequest returning with thermalMitigationResult: "
+ + thermalMitigationResult);
+ }
+
+ return thermalMitigationResult;
+ }
+
+ /**
+ * Set the GbaService Package Name that Telephony will bind to.
+ *
+ * @param subId The sim that the GbaService is associated with.
+ * @param packageName The name of the package to be replaced with.
+ * @return true if setting the GbaService to bind to succeeded, false if it did not.
+ */
+ @Override
+ public boolean setBoundGbaServiceOverride(int subId, String packageName) {
+ enforceModifyPermission();
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return getGbaManager(subId).overrideServicePackage(packageName);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Return the package name of the currently bound GbaService.
+ *
+ * @param subId The sim that the GbaService is associated with.
+ * @return the package name of the GbaService configuration, null if GBA is not supported.
+ */
+ @Override
+ public String getBoundGbaService(int subId) {
+ enforceReadPrivilegedPermission("getBoundGbaServicePackage");
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return getGbaManager(subId).getServicePackage();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Set the release time for telephony to unbind GbaService.
+ *
+ * @param subId The sim that the GbaService is associated with.
+ * @param interval The release time to unbind GbaService by millisecond.
+ * @return true if setting the GbaService to bind to succeeded, false if it did not.
+ */
+ @Override
+ public boolean setGbaReleaseTimeOverride(int subId, int interval) {
+ enforceModifyPermission();
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return getGbaManager(subId).overrideReleaseTime(interval);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Return the release time for telephony to unbind GbaService.
+ *
+ * @param subId The sim that the GbaService is associated with.
+ * @return The release time to unbind GbaService by millisecond.
+ */
+ @Override
+ public int getGbaReleaseTime(int subId) {
+ enforceReadPrivilegedPermission("getGbaReleaseTime");
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return getGbaManager(subId).getReleaseTime();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private GbaManager getGbaManager(int subId) {
+ GbaManager instance = GbaManager.getInstance(subId);
+ if (instance == null) {
+ String packageName = mApp.getResources().getString(R.string.config_gba_package);
+ int releaseTime = mApp.getResources().getInteger(R.integer.config_gba_release_time);
+ instance = GbaManager.make(mApp, subId, packageName, releaseTime);
+ }
+ return instance;
+ }
+
+ /**
+ * indicate whether the device and the carrier can support
+ * RCS VoLTE single registration.
+ */
+ @Override
+ public boolean isRcsVolteSingleRegistrationCapable(int subId) {
+ TelephonyPermissions.enforceAnyPermissionGrantedOrCarrierPrivileges(mApp, subId,
+ Binder.getCallingUid(), "isRcsVolteSingleRegistrationCapable",
+ Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION,
+ permission.READ_PRIVILEGED_PHONE_STATE);
+
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ RcsProvisioningMonitor rpm = RcsProvisioningMonitor.getInstance();
+ if (rpm != null) {
+ Boolean isCapable = rpm.isRcsVolteSingleRegistrationEnabled(subId);
+ if (isCapable != null) {
+ return isCapable;
+ }
+ }
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
+ "service is temporarily unavailable.");
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Register RCS provisioning callback.
+ */
+ @Override
+ public void registerRcsProvisioningCallback(int subId,
+ IRcsConfigCallback callback) {
+ TelephonyPermissions.enforceAnyPermissionGrantedOrCarrierPrivileges(mApp, subId,
+ Binder.getCallingUid(), "registerRcsProvisioningCallback",
+ Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION,
+ permission.READ_PRIVILEGED_PHONE_STATE);
+
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
+ }
+ if (!isImsAvailableOnDevice()) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "IMS not available on device.");
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (!RcsProvisioningMonitor.getInstance()
+ .registerRcsProvisioningCallback(subId, callback)) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION,
+ "Active subscription not found.");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Unregister RCS provisioning callback.
+ */
+ @Override
+ public void unregisterRcsProvisioningCallback(int subId,
+ IRcsConfigCallback callback) {
+ TelephonyPermissions.enforceAnyPermissionGrantedOrCarrierPrivileges(mApp, subId,
+ Binder.getCallingUid(), "unregisterRcsProvisioningCallback",
+ Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION,
+ permission.READ_PRIVILEGED_PHONE_STATE);
+
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
+ }
+ if (!isImsAvailableOnDevice()) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "IMS not available on device.");
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ RcsProvisioningMonitor.getInstance()
+ .unregisterRcsProvisioningCallback(subId, callback);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * trigger RCS reconfiguration.
+ */
+ public void triggerRcsReconfiguration(int subId) {
+ TelephonyPermissions.enforceAnyPermissionGranted(mApp, Binder.getCallingUid(),
+ "triggerRcsReconfiguration",
+ Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION);
+
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
+ }
+ if (!isImsAvailableOnDevice()) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "IMS not available on device.");
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ RcsProvisioningMonitor.getInstance().requestReconfig(subId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Provide the client configuration parameters of the RCS application.
+ */
+ public void setRcsClientConfiguration(int subId, RcsClientConfiguration rcc) {
+ TelephonyPermissions.enforceAnyPermissionGranted(mApp, Binder.getCallingUid(),
+ "setRcsClientConfiguration",
+ Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION);
+
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
+ }
+ if (!isImsAvailableOnDevice()) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "IMS not available on device.");
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+
+ try {
+ IImsConfig configBinder = getImsConfig(getSlotIndex(subId), ImsFeature.FEATURE_RCS);
+ if (configBinder == null) {
+ Rlog.e(LOG_TAG, "null result for setRcsClientConfiguration");
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION,
+ "could not find the requested subscription");
+ } else {
+ configBinder.setRcsClientConfiguration(rcc);
+ }
+ } catch (RemoteException e) {
+ Rlog.e(LOG_TAG, "fail to setRcsClientConfiguration " + e.getMessage());
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
+ "service is temporarily unavailable.");
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * 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
+ public void setDeviceSingleRegistrationEnabledOverride(String enabledStr) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "setDeviceSingleRegistrationEnabledOverride");
+ enforceModifyPermission();
+
+ Boolean enabled = "NULL".equalsIgnoreCase(enabledStr) ? null
+ : Boolean.parseBoolean(enabledStr);
+ RcsProvisioningMonitor.getInstance().overrideDeviceSingleRegistrationEnabled(enabled);
+ mApp.imsRcsController.setDeviceSingleRegistrationSupportOverride(enabled);
+ }
+
+ /**
+ * Sends a device to device communication message. Only usable via shell.
+ * @param message message to send.
+ * @param value message value.
+ */
+ @Override
+ public void sendDeviceToDeviceMessage(int message, int value) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "sendDeviceToDeviceMessage");
+ enforceModifyPermission();
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ TelephonyConnectionService service =
+ TelecomAccountRegistry.getInstance(null).getTelephonyConnectionService();
+ if (service == null) {
+ Rlog.e(LOG_TAG, "sendDeviceToDeviceMessage: not in a call.");
+ return;
+ }
+ service.sendTestDeviceToDeviceMessage(message, value);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Sets the specified device to device transport active.
+ * @param transport The transport to set active.
+ */
+ @Override
+ public void setActiveDeviceToDeviceTransport(@NonNull String transport) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "setActiveDeviceToDeviceTransport");
+ enforceModifyPermission();
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ TelephonyConnectionService service =
+ TelecomAccountRegistry.getInstance(null).getTelephonyConnectionService();
+ if (service == null) {
+ Rlog.e(LOG_TAG, "setActiveDeviceToDeviceTransport: not in a call.");
+ return;
+ }
+ service.setActiveDeviceToDeviceTransport(transport);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void setDeviceToDeviceForceEnabled(boolean isForceEnabled) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "setDeviceToDeviceForceEnabled");
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Arrays.stream(PhoneFactory.getPhones()).forEach(
+ p -> {
+ Phone thePhone = p.getImsPhone();
+ if (thePhone != null && thePhone instanceof ImsPhone) {
+ ImsPhone imsPhone = (ImsPhone) thePhone;
+ CallTracker tracker = imsPhone.getCallTracker();
+ if (tracker != null && tracker instanceof ImsPhoneCallTracker) {
+ ImsPhoneCallTracker imsPhoneCallTracker =
+ (ImsPhoneCallTracker) tracker;
+ imsPhoneCallTracker.setDeviceToDeviceForceEnabled(isForceEnabled);
+ }
+ }
+ }
+ );
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Gets the config of RCS VoLTE single registration enabled for the device.
+ */
+ @Override
+ public boolean getDeviceSingleRegistrationEnabled() {
+ enforceReadPrivilegedPermission("getDeviceSingleRegistrationEnabled");
+ return RcsProvisioningMonitor.getInstance().getDeviceSingleRegistrationEnabled();
+ }
+
+ /**
+ * Overrides the config of RCS VoLTE single registration enabled for the carrier/subscription.
+ */
+ @Override
+ public boolean setCarrierSingleRegistrationEnabledOverride(int subId, String enabledStr) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "setCarrierSingleRegistrationEnabledOverride");
+ enforceModifyPermission();
+
+ Boolean enabled = "NULL".equalsIgnoreCase(enabledStr) ? null
+ : Boolean.parseBoolean(enabledStr);
+ return RcsProvisioningMonitor.getInstance().overrideCarrierSingleRegistrationEnabled(
+ subId, enabled);
+ }
+
+ /**
+ * Gets the config of RCS VoLTE single registration enabled for the carrier/subscription.
+ */
+ @Override
+ public boolean getCarrierSingleRegistrationEnabled(int subId) {
+ enforceReadPrivilegedPermission("getCarrierSingleRegistrationEnabled");
+ return RcsProvisioningMonitor.getInstance().getCarrierSingleRegistrationEnabled(subId);
+ }
+
+ /**
+ * Overrides the ims feature validation result
+ */
+ @Override
+ public boolean setImsFeatureValidationOverride(int subId, String enabledStr) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "setImsFeatureValidationOverride");
+
+ Boolean enabled = "NULL".equalsIgnoreCase(enabledStr) ? null
+ : Boolean.parseBoolean(enabledStr);
+ return RcsProvisioningMonitor.getInstance().overrideImsFeatureValidation(
+ subId, enabled);
+ }
+
+ /**
+ * Gets the ims feature validation override value
+ */
+ @Override
+ public boolean getImsFeatureValidationOverride(int subId) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "getImsFeatureValidationOverride");
+ return RcsProvisioningMonitor.getInstance().getImsFeatureValidationOverride(subId);
+ }
+
+ /**
+ * Get the mobile provisioning url that is used to launch a browser to allow users to manage
+ * their mobile plan.
+ */
+ @Override
+ public String getMobileProvisioningUrl() {
+ enforceReadPrivilegedPermission("getMobileProvisioningUrl");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return getDefaultPhone().getMobileProvisioningUrl();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Get the EAB contact from the EAB database.
+ */
+ @Override
+ public String getContactFromEab(String contact) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "getContactFromEab");
+ enforceModifyPermission();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return EabUtil.getContactFromEab(getDefaultPhone().getContext(), contact);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Get the EAB capability from the EAB database.
+ */
+ @Override
+ public String getCapabilityFromEab(String contact) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "getCapabilityFromEab");
+ enforceModifyPermission();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return EabUtil.getCapabilityFromEab(getDefaultPhone().getContext(), contact);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Remove the EAB contacts from the EAB database.
+ */
+ @Override
+ public int removeContactFromEab(int subId, String contacts) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "removeCapabilitiesFromEab");
+ enforceModifyPermission();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return EabUtil.removeContactFromEab(subId, contacts, getDefaultPhone().getContext());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public boolean getDeviceUceEnabled() {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "getDeviceUceEnabled");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mApp.getDeviceUceEnabled();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void setDeviceUceEnabled(boolean isEnabled) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "setDeviceUceEnabled");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mApp.setDeviceUceEnabled(isEnabled);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Add new feature tags to the Set used to calculate the capabilities in PUBLISH.
+ * @return current RcsContactUceCapability instance that will be used for PUBLISH.
+ */
+ // Used for SHELL command only right now.
+ @Override
+ public RcsContactUceCapability addUceRegistrationOverrideShell(int subId,
+ List<String> featureTags) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "addUceRegistrationOverrideShell");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mApp.imsRcsController.addUceRegistrationOverrideShell(subId,
+ new ArraySet<>(featureTags));
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode(), e.getMessage());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Remove existing feature tags to the Set used to calculate the capabilities in PUBLISH.
+ * @return current RcsContactUceCapability instance that will be used for PUBLISH.
+ */
+ // Used for SHELL command only right now.
+ @Override
+ public RcsContactUceCapability removeUceRegistrationOverrideShell(int subId,
+ List<String> featureTags) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "removeUceRegistrationOverrideShell");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mApp.imsRcsController.removeUceRegistrationOverrideShell(subId,
+ new ArraySet<>(featureTags));
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode(), e.getMessage());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Clear all overrides in the Set used to calculate the capabilities in PUBLISH.
+ * @return current RcsContactUceCapability instance that will be used for PUBLISH.
+ */
+ // Used for SHELL command only right now.
+ @Override
+ public RcsContactUceCapability clearUceRegistrationOverrideShell(int subId) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "clearUceRegistrationOverrideShell");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mApp.imsRcsController.clearUceRegistrationOverrideShell(subId);
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode(), e.getMessage());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * @return current RcsContactUceCapability instance that will be used for PUBLISH.
+ */
+ // Used for SHELL command only right now.
+ @Override
+ public RcsContactUceCapability getLatestRcsContactUceCapabilityShell(int subId) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "getLatestRcsContactUceCapabilityShell");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mApp.imsRcsController.getLatestRcsContactUceCapabilityShell(subId);
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode(), e.getMessage());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Returns the last PIDF XML sent to the network during the last PUBLISH or "none" if the
+ * device does not have an active PUBLISH.
+ */
+ // Used for SHELL command only right now.
+ @Override
+ public String getLastUcePidfXmlShell(int subId) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "uceGetLastPidfXml");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mApp.imsRcsController.getLastUcePidfXmlShell(subId);
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode(), e.getMessage());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Remove UCE requests cannot be sent to the network status.
+ */
+ // Used for SHELL command only right now.
+ @Override
+ public boolean removeUceRequestDisallowedStatus(int subId) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "uceRemoveDisallowedStatus");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mApp.imsRcsController.removeUceRequestDisallowedStatus(subId);
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode(), e.getMessage());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Remove UCE requests cannot be sent to the network status.
+ */
+ // Used for SHELL command only.
+ @Override
+ public boolean setCapabilitiesRequestTimeout(int subId, long timeoutAfterMs) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "setCapRequestTimeout");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mApp.imsRcsController.setCapabilitiesRequestTimeout(subId, timeoutAfterMs);
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode(), e.getMessage());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void setSignalStrengthUpdateRequest(int subId, SignalStrengthUpdateRequest request,
+ String callingPackage) {
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
+ mApp, subId, "setSignalStrengthUpdateRequest");
+
+ final int callingUid = Binder.getCallingUid();
+ // Verify that tha callingPackage belongs to the calling UID
+ mApp.getSystemService(AppOpsManager.class)
+ .checkPackage(callingUid, callingPackage);
+
+ validateSignalStrengthUpdateRequest(request, callingUid);
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Object result = sendRequest(CMD_SET_SIGNAL_STRENGTH_UPDATE_REQUEST,
+ new Pair<Integer, SignalStrengthUpdateRequest>(callingUid, request), subId);
+
+ if (result instanceof IllegalStateException) {
+ throw (IllegalStateException) result;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void clearSignalStrengthUpdateRequest(int subId, SignalStrengthUpdateRequest request,
+ String callingPackage) {
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
+ mApp, subId, "clearSignalStrengthUpdateRequest");
+
+ final int callingUid = Binder.getCallingUid();
+ // Verify that tha callingPackage belongs to the calling UID
+ mApp.getSystemService(AppOpsManager.class)
+ .checkPackage(callingUid, callingPackage);
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Object result = sendRequest(CMD_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST,
+ new Pair<Integer, SignalStrengthUpdateRequest>(callingUid, request), subId);
+
+ if (result instanceof IllegalStateException) {
+ throw (IllegalStateException) result;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private static void validateSignalStrengthUpdateRequest(SignalStrengthUpdateRequest request,
+ int callingUid) {
+ if (callingUid == Process.PHONE_UID || callingUid == Process.SYSTEM_UID) {
+ // phone/system process do not have further restriction on request
+ return;
+ }
+
+ // Applications has restrictions on how to use the request:
+ // Only system caller can set mIsSystemThresholdReportingRequestedWhileIdle
+ if (request.isSystemThresholdReportingRequestedWhileIdle()) {
+ // This is not system caller which has been checked above
+ throw new IllegalArgumentException(
+ "Only system can set isSystemThresholdReportingRequestedWhileIdle");
+ }
+
+ for (SignalThresholdInfo info : request.getSignalThresholdInfos()) {
+ // Only system caller can set mHysteresisMs/mHysteresisDb/mIsEnabled.
+ if (info.getHysteresisMs() != SignalThresholdInfo.HYSTERESIS_MS_DISABLED
+ || info.getHysteresisDb() != SignalThresholdInfo.HYSTERESIS_DB_DISABLED
+ || info.isEnabled()) {
+ throw new IllegalArgumentException(
+ "Only system can set hide fields in SignalThresholdInfo");
+ }
+
+ // Thresholds length for each RAN need in range. This has been validated in
+ // SignalThresholdInfo#Builder#setThreshold. Here we prevent apps calling hide method
+ // setThresholdUnlimited (e.g. through reflection) with too short or too long thresholds
+ final int[] thresholds = info.getThresholds();
+ Objects.requireNonNull(thresholds);
+ if (thresholds.length < SignalThresholdInfo.getMinimumNumberOfThresholdsAllowed()
+ || thresholds.length
+ > SignalThresholdInfo.getMaximumNumberOfThresholdsAllowed()) {
+ throw new IllegalArgumentException(
+ "thresholds length is out of range: " + thresholds.length);
+ }
+ }
+ }
+
+ /**
+ * Gets the current phone capability.
+ *
+ * Requires carrier privileges or READ_PRECISE_PHONE_STATE permission.
+ * @return the PhoneCapability which describes the data connection capability of modem.
+ * It's used to evaluate possible phone config change, for example from single
+ * SIM device to multi-SIM device.
+ */
+ @Override
+ public PhoneCapability getPhoneCapability() {
+ enforceReadPrivilegedPermission("getPhoneCapability");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mPhoneConfigurationManager.getCurrentPhoneCapability();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ /**
+ * Request to get the current slicing configuration including URSP rules and
+ * NSSAIs (configured, allowed and rejected).
+ *
+ * Requires carrier privileges or READ_PRIVILEGED_PHONE_STATE permission.
+ */
+ @Override
+ public void getSlicingConfig(ResultReceiver callback) {
+ enforceReadPrivilegedPermission("getSlicingConfig");
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Phone phone = getDefaultPhone();
+ sendRequestAsync(CMD_GET_SLICING_CONFIG, callback, phone, null);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
diff --git a/src/com/android/phone/RcsProvisioningMonitor.java b/src/com/android/phone/RcsProvisioningMonitor.java
new file mode 100644
index 0000000..23c4c5a
--- /dev/null
+++ b/src/com/android/phone/RcsProvisioningMonitor.java
@@ -0,0 +1,902 @@
+/*
+ * Copyright 2020 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.phone;
+
+import android.Manifest;
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyRegistryManager;
+import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.RcsConfig;
+import android.telephony.ims.aidl.IImsConfig;
+import android.telephony.ims.aidl.IRcsConfigCallback;
+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.Iterator;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+
+/**
+ * Class to monitor RCS Provisioning Status
+ */
+public class RcsProvisioningMonitor {
+ private static final String TAG = "RcsProvisioningMonitor";
+ private static final boolean DBG = Build.IS_ENG;
+
+ private static final int EVENT_SUB_CHANGED = 1;
+ private static final int EVENT_DMA_CHANGED = 2;
+ private static final int EVENT_CC_CHANGED = 3;
+ private static final int EVENT_CONFIG_RECEIVED = 4;
+ 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 static final int EVENT_FEATURE_ENABLED_OVERRIDE = 9;
+
+ private final PhoneGlobals mPhone;
+ private final Handler mHandler;
+ // Cache the RCS provsioning info and related sub id
+ private final ConcurrentHashMap<Integer, RcsProvisioningInfo> mRcsProvisioningInfos =
+ new ConcurrentHashMap<>();
+ private Boolean mDeviceSingleRegistrationEnabledOverride;
+ private final HashMap<Integer, Boolean> mCarrierSingleRegistrationEnabledOverride =
+ new HashMap<>();
+ private final ConcurrentHashMap<Integer, Boolean> mImsFeatureValidationOverride =
+ new ConcurrentHashMap<>();
+ 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;
+
+ private final SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener =
+ new SubscriptionManager.OnSubscriptionsChangedListener() {
+ @Override
+ public void onSubscriptionsChanged() {
+ if (!mHandler.hasMessages(EVENT_SUB_CHANGED)) {
+ mHandler.sendEmptyMessage(EVENT_SUB_CHANGED);
+ }
+ }
+ };
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(
+ intent.getAction())) {
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ logv("Carrier-config changed for sub : " + subId);
+ if (SubscriptionManager.isValidSubscriptionId(subId)
+ && !mHandler.hasMessages(EVENT_CC_CHANGED)) {
+ mHandler.sendEmptyMessage(EVENT_CC_CHANGED);
+ }
+ }
+ }
+ };
+
+ private final class DmaChangedListener implements OnRoleHoldersChangedListener {
+ @Override
+ public void onRoleHoldersChanged(String role, UserHandle user) {
+ if (RoleManager.ROLE_SMS.equals(role)) {
+ logv("default messaging application changed.");
+ mHandler.sendEmptyMessage(EVENT_DMA_CHANGED);
+ }
+ }
+
+ public void register() {
+ try {
+ mRoleManager.addOnRoleHoldersChangedListenerAsUser(
+ mPhone.getMainExecutor(), this, UserHandle.SYSTEM);
+ } catch (RuntimeException e) {
+ loge("Could not register dma change listener due to " + e);
+ }
+ }
+
+ public void unregister() {
+ try {
+ mRoleManager.removeOnRoleHoldersChangedListenerAsUser(this, UserHandle.SYSTEM);
+ } catch (RuntimeException e) {
+ loge("Could not unregister dma change listener due to " + e);
+ }
+ }
+ }
+
+ private final class MyHandler extends Handler {
+ MyHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ logv("handleMessage: " + msg);
+ switch (msg.what) {
+ case EVENT_SUB_CHANGED:
+ onSubChanged();
+ break;
+ case EVENT_DMA_CHANGED:
+ onDefaultMessagingApplicationChanged();
+ break;
+ case EVENT_CC_CHANGED:
+ onCarrierConfigChange();
+ break;
+ case EVENT_CONFIG_RECEIVED:
+ onConfigReceived(msg.arg1, (byte[]) msg.obj, msg.arg2 == 1);
+ break;
+ case EVENT_RECONFIG_REQUEST:
+ onReconfigRequest(msg.arg1);
+ break;
+ case EVENT_DEVICE_CONFIG_OVERRIDE:
+ Boolean deviceEnabled = (Boolean) msg.obj;
+ if (!booleanEquals(deviceEnabled, mDeviceSingleRegistrationEnabledOverride)) {
+ mDeviceSingleRegistrationEnabledOverride = deviceEnabled;
+ onCarrierConfigChange();
+ }
+ break;
+ case EVENT_CARRIER_CONFIG_OVERRIDE:
+ Boolean carrierEnabledOverride = (Boolean) msg.obj;
+ Boolean carrierEnabled = mCarrierSingleRegistrationEnabledOverride.put(
+ msg.arg1, carrierEnabledOverride);
+ if (!booleanEquals(carrierEnabledOverride, carrierEnabled)) {
+ onCarrierConfigChange();
+ }
+ break;
+ case EVENT_RESET:
+ reset();
+ break;
+ default:
+ loge("Unhandled event " + msg.what);
+ }
+ }
+ }
+
+ private final class RcsProvisioningInfo {
+ private int mSubId;
+ private volatile int mSingleRegistrationCapability;
+ private volatile byte[] mConfig;
+ 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 ArraySet<>();
+ registerRcsFeatureListener(this);
+ }
+
+ int getSubId() {
+ return mSubId;
+ }
+
+ void setSingleRegistrationCapability(int singleRegistrationCapability) {
+ mSingleRegistrationCapability = singleRegistrationCapability;
+ }
+
+ int getSingleRegistrationCapability() {
+ return mSingleRegistrationCapability;
+ }
+
+ void setConfig(byte[] config) {
+ if (!Arrays.equals(mConfig, config)) {
+ mConfig = config;
+ if (mConfig != null) {
+ notifyRcsAutoConfigurationReceived();
+ } else {
+ notifyRcsAutoConfigurationRemoved();
+ }
+ }
+ }
+
+ byte[] getConfig() {
+ return mConfig;
+ }
+
+ boolean addRcsConfigCallback(IRcsConfigCallback cb) {
+ if (mIImsConfig == null) {
+ logd("fail to addRcsConfigCallback as imsConfig is null");
+ return false;
+ }
+
+ synchronized (mRcsConfigCallbacks) {
+ try {
+ mIImsConfig.addRcsConfigCallback(cb);
+ } catch (RemoteException e) {
+ loge("fail to addRcsConfigCallback due to " + e);
+ return false;
+ }
+ mRcsConfigCallbacks.add(cb);
+ }
+ return true;
+ }
+
+ boolean removeRcsConfigCallback(IRcsConfigCallback cb) {
+ boolean result = true;
+
+ synchronized (mRcsConfigCallbacks) {
+ if (mIImsConfig != null) {
+ try {
+ mIImsConfig.removeRcsConfigCallback(cb);
+ } catch (RemoteException e) {
+ loge("fail to removeRcsConfigCallback due to " + e);
+ }
+ } else {
+ // Return false but continue to remove the callback
+ result = false;
+ }
+
+ try {
+ cb.onRemoved();
+ } catch (RemoteException e) {
+ logd("Failed to notify onRemoved due to dead binder of " + cb);
+ }
+ mRcsConfigCallbacks.remove(cb);
+ }
+ 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) {
+ Iterator<IRcsConfigCallback> it = mRcsConfigCallbacks.iterator();
+ while (it.hasNext()) {
+ IRcsConfigCallback cb = it.next();
+ if (mIImsConfig != null) {
+ try {
+ mIImsConfig.removeRcsConfigCallback(cb);
+ } catch (RemoteException e) {
+ loge("fail to removeRcsConfigCallback due to " + e);
+ }
+ }
+ try {
+ cb.onRemoved();
+ } catch (RemoteException e) {
+ logd("Failed to notify onRemoved due to dead binder of " + cb);
+ }
+ it.remove();
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ 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);
+ mSubscriptionManager = mPhone.getSystemService(SubscriptionManager.class);
+ mTelephonyRegistryManager = mPhone.getSystemService(TelephonyRegistryManager.class);
+ mRoleManager = roleManager;
+ mDmaPackageName = getDmaPackageName();
+ logv("DMA is " + mDmaPackageName);
+ mDmaChangedListener = new DmaChangedListener();
+ mFeatureFactory = factory;
+ init();
+ }
+
+ /**
+ * create an instance
+ */
+ public static RcsProvisioningMonitor make(PhoneGlobals app) {
+ if (sInstance == null) {
+ logd("RcsProvisioningMonitor created.");
+ HandlerThread handlerThread = new HandlerThread(TAG);
+ handlerThread.start();
+ sInstance = new RcsProvisioningMonitor(app, handlerThread.getLooper(),
+ new RoleManagerAdapterImpl(app), RcsFeatureManager::getConnector);
+ }
+ return sInstance;
+ }
+
+ /**
+ * get the instance
+ */
+ public static RcsProvisioningMonitor getInstance() {
+ 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.");
+ release();
+ mHandler.getLooper().quit();
+ }
+
+ /**
+ * get the handler
+ */
+ @VisibleForTesting
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ /**
+ * Gets the config for a subscription
+ */
+ @VisibleForTesting
+ public byte[] getConfig(int subId) {
+ if (mRcsProvisioningInfos.containsKey(subId)) {
+ return mRcsProvisioningInfos.get(subId).getConfig();
+ }
+ return null;
+ }
+
+ /**
+ * Returns whether Rcs Volte single registration is enabled for the sub.
+ */
+ public Boolean isRcsVolteSingleRegistrationEnabled(int subId) {
+ if (mRcsProvisioningInfos.containsKey(subId)) {
+ return mRcsProvisioningInfos.get(subId).getSingleRegistrationCapability()
+ == ProvisioningManager.STATUS_CAPABLE;
+ }
+ return null;
+ }
+
+ /**
+ * Called when the new rcs config is received
+ */
+ public void updateConfig(int subId, byte[] config, boolean isCompressed) {
+ mHandler.sendMessage(mHandler.obtainMessage(
+ EVENT_CONFIG_RECEIVED, subId, isCompressed ? 1 : 0, config));
+ }
+
+ /**
+ * Called when the application needs rcs re-config
+ */
+ public void requestReconfig(int subId) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_RECONFIG_REQUEST, subId, 0));
+ }
+
+ /**
+ * Called when the application registers rcs provisioning callback
+ */
+ public boolean registerRcsProvisioningCallback(int subId, IRcsConfigCallback cb) {
+ RcsProvisioningInfo info = mRcsProvisioningInfos.get(subId);
+ // should not happen in normal case
+ if (info == null) {
+ logd("fail to register rcs provisioning callback due to subscription unavailable");
+ return false;
+ }
+
+ return info.addRcsConfigCallback(cb);
+ }
+
+ /**
+ * Called when the application unregisters rcs provisioning callback
+ */
+ public boolean unregisterRcsProvisioningCallback(int subId, IRcsConfigCallback cb) {
+ RcsProvisioningInfo info = mRcsProvisioningInfos.get(subId);
+ // should not happen in normal case
+ if (info == null) {
+ logd("fail to unregister rcs provisioning changed due to subscription unavailable");
+ return false;
+ }
+
+ return info.removeRcsConfigCallback(cb);
+ }
+
+ /**
+ * 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) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_DEVICE_CONFIG_OVERRIDE, enabled));
+ }
+
+ /**
+ * Overrides the carrier config whether single registration is enabled
+ */
+ public boolean overrideCarrierSingleRegistrationEnabled(int subId, Boolean enabled) {
+ if (!mRcsProvisioningInfos.containsKey(subId)) {
+ return false;
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(
+ EVENT_CARRIER_CONFIG_OVERRIDE, subId, 0, enabled));
+ return true;
+ }
+
+ /**
+ * override the rcs feature validation result for a subscription
+ */
+ public boolean overrideImsFeatureValidation(int subId, Boolean enabled) {
+ if (enabled == null) {
+ mImsFeatureValidationOverride.remove(subId);
+ } else {
+ mImsFeatureValidationOverride.put(subId, enabled);
+ }
+ return true;
+ }
+
+ /**
+ * Returns the device config whether single registration is enabled
+ */
+ public boolean getDeviceSingleRegistrationEnabled() {
+ for (RcsProvisioningInfo info : mRcsProvisioningInfos.values()) {
+ return (info.getSingleRegistrationCapability()
+ & ProvisioningManager.STATUS_DEVICE_NOT_CAPABLE) == 0;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the carrier config whether single registration is enabled
+ */
+ public boolean getCarrierSingleRegistrationEnabled(int subId) {
+ if (mRcsProvisioningInfos.containsKey(subId)) {
+ return (mRcsProvisioningInfos.get(subId).getSingleRegistrationCapability()
+ & ProvisioningManager.STATUS_CARRIER_NOT_CAPABLE) == 0;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the rcs feature validation override value, null if it is not set.
+ */
+ public Boolean getImsFeatureValidationOverride(int subId) {
+ return mImsFeatureValidationOverride.get(subId);
+ }
+
+ private void onDefaultMessagingApplicationChanged() {
+ final String packageName = getDmaPackageName();
+ if (!TextUtils.equals(mDmaPackageName, packageName)) {
+ mDmaPackageName = packageName;
+ logv("new default messaging application " + mDmaPackageName);
+
+ mRcsProvisioningInfos.forEach((k, v) -> {
+ notifyDmaForSub(k, v.getSingleRegistrationCapability());
+
+ byte[] cachedConfig = v.getConfig();
+ //clear old callbacks
+ v.clear();
+ if (isAcsUsed(k)) {
+ logv("acs used, trigger to re-configure.");
+ updateConfigForSub(k, null, true);
+ v.triggerRcsReconfiguration();
+ } else {
+ logv("acs not used, set cached config and notify.");
+ v.setConfig(cachedConfig);
+ }
+ });
+ }
+ }
+
+ private void updateConfigForSub(int subId, byte[] config, boolean isCompressed) {
+ logv("updateConfigForSub, subId:" + subId + ", mTestModeEnabled:" + mTestModeEnabled);
+ if (!mTestModeEnabled) {
+ RcsConfig.updateConfigForSub(mPhone, subId, config, isCompressed);
+ }
+ }
+
+ private byte[] loadConfigForSub(int subId) {
+ logv("loadConfigForSub, subId:" + subId + ", mTestModeEnabled:" + mTestModeEnabled);
+ if (!mTestModeEnabled) {
+ return RcsConfig.loadRcsConfigForSub(mPhone, subId, false);
+ }
+ return null;
+ }
+
+ private boolean isAcsUsed(int subId) {
+ PersistableBundle b = mCarrierConfigManager.getConfigForSubId(subId);
+ if (b == null) {
+ return false;
+ }
+ return b.getBoolean(CarrierConfigManager.KEY_USE_ACS_FOR_RCS_BOOL);
+ }
+
+ private boolean isSingleRegistrationRequiredByCarrier(int subId) {
+ Boolean enabledByOverride = mCarrierSingleRegistrationEnabledOverride.get(subId);
+ if (enabledByOverride != null) {
+ return enabledByOverride;
+ }
+
+ PersistableBundle b = mCarrierConfigManager.getConfigForSubId(subId);
+ if (b == null) {
+ return false;
+ }
+ return b.getBoolean(CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL);
+ }
+
+ private int getSingleRegistrationCapableValue(int subId) {
+ boolean isSingleRegistrationEnabledOnDevice =
+ mDeviceSingleRegistrationEnabledOverride != null
+ ? mDeviceSingleRegistrationEnabledOverride
+ : mPhone.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION);
+
+ int value = (isSingleRegistrationEnabledOnDevice ? 0
+ : ProvisioningManager.STATUS_DEVICE_NOT_CAPABLE) | (
+ isSingleRegistrationRequiredByCarrier(subId) ? 0
+ : ProvisioningManager.STATUS_CARRIER_NOT_CAPABLE);
+ logv("SingleRegistrationCapableValue : " + value);
+ return value;
+ }
+
+ private void onCarrierConfigChange() {
+ logv("onCarrierConfigChange");
+ mRcsProvisioningInfos.forEach((subId, info) -> {
+ int value = getSingleRegistrationCapableValue(subId);
+ if (value != info.getSingleRegistrationCapability()) {
+ info.setSingleRegistrationCapability(value);
+ notifyDmaForSub(subId, value);
+ }
+ });
+ }
+
+ private void onSubChanged() {
+ final int[] activeSubs = mSubscriptionManager.getActiveSubscriptionIdList();
+ final ArraySet<Integer> subsToBeDeactivated =
+ new ArraySet<>(mRcsProvisioningInfos.keySet());
+
+ for (int i : activeSubs) {
+ subsToBeDeactivated.remove(i);
+ if (!mRcsProvisioningInfos.containsKey(i)) {
+ 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));
+ notifyDmaForSub(i, capability);
+ }
+ }
+
+ subsToBeDeactivated.forEach(i -> {
+ RcsProvisioningInfo info = mRcsProvisioningInfos.remove(i);
+ if (info != null) {
+ info.destroy();
+ }
+ });
+ }
+
+ private void onConfigReceived(int subId, byte[] config, boolean isCompressed) {
+ logv("onConfigReceived, subId:" + subId + ", config:"
+ + config + ", isCompressed:" + isCompressed);
+ RcsProvisioningInfo info = mRcsProvisioningInfos.get(subId);
+ info.setConfig(isCompressed ? RcsConfig.decompressGzip(config) : config);
+ updateConfigForSub(subId, config, isCompressed);
+ }
+
+ private void onReconfigRequest(int subId) {
+ logv("onReconfigRequest, subId:" + subId);
+ RcsProvisioningInfo info = mRcsProvisioningInfos.get(subId);
+ if (info != null) {
+ info.setConfig(null);
+ // clear rcs config stored in db
+ updateConfigForSub(subId, null, true);
+ info.triggerRcsReconfiguration();
+ }
+ }
+
+ private void notifyDmaForSub(int subId, int capability) {
+ final Intent intent = new Intent(
+ ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE);
+ intent.setPackage(mDmaPackageName);
+ intent.putExtra(ProvisioningManager.EXTRA_SUBSCRIPTION_ID, subId);
+ intent.putExtra(ProvisioningManager.EXTRA_STATUS, capability);
+ logv("notify " + intent + ", sub:" + subId + ", capability:" + capability);
+ // Only send permission to the default sms app if it has the correct permissions
+ // except test mode enabled
+ if (!mTestModeEnabled) {
+ mPhone.sendBroadcast(intent, Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION);
+ } else {
+ mPhone.sendBroadcast(intent);
+ }
+ }
+
+ private String getDmaPackageName() {
+ try {
+ return CollectionUtils.firstOrNull(mRoleManager.getRoleHolders(RoleManager.ROLE_SMS));
+ } catch (RuntimeException e) {
+ loge("Could not get dma name due to " + e);
+ return null;
+ }
+ }
+
+ 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) {
+ // make sure the info to be removed in any case, even the slotId changed or invalid.
+ for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
+ mRcsFeatureListeners.valueAt(i).removeRcsProvisioningInfo(info);
+ }
+ }
+
+ private static boolean booleanEquals(Boolean val1, Boolean val2) {
+ return (val1 == null && val2 == null)
+ || (Boolean.TRUE.equals(val1) && Boolean.TRUE.equals(val2))
+ || (Boolean.FALSE.equals(val1) && Boolean.FALSE.equals(val2));
+ }
+
+ private static void logv(String msg) {
+ if (DBG) {
+ Rlog.d(TAG, msg);
+ }
+ }
+
+ private static void logd(String msg) {
+ Rlog.d(TAG, msg);
+ }
+
+ private static void loge(String msg) {
+ Rlog.e(TAG, msg);
+ }
+
+ /**
+ * {@link RoleManager} is final so we have to wrap the implementation for testing.
+ */
+ @VisibleForTesting
+ public interface RoleManagerAdapter {
+ /** See {@link RoleManager#getRoleHolders(String)} */
+ List<String> getRoleHolders(String roleName);
+ /** See {@link RoleManager#addOnRoleHoldersChangedListenerAsUser} */
+ void addOnRoleHoldersChangedListenerAsUser(Executor executor,
+ OnRoleHoldersChangedListener listener, UserHandle user);
+ /** See {@link RoleManager#removeOnRoleHoldersChangedListenerAsUser} */
+ void removeOnRoleHoldersChangedListenerAsUser(OnRoleHoldersChangedListener listener,
+ UserHandle user);
+ }
+
+ private static class RoleManagerAdapterImpl implements RoleManagerAdapter {
+ private final RoleManager mRoleManager;
+
+ private RoleManagerAdapterImpl(Context context) {
+ mRoleManager = context.getSystemService(RoleManager.class);
+ }
+
+ @Override
+ public List<String> getRoleHolders(String roleName) {
+ return mRoleManager.getRoleHolders(roleName);
+ }
+
+ @Override
+ public void addOnRoleHoldersChangedListenerAsUser(Executor executor,
+ OnRoleHoldersChangedListener listener, UserHandle user) {
+ mRoleManager.addOnRoleHoldersChangedListenerAsUser(executor, listener, user);
+ }
+
+ @Override
+ public void removeOnRoleHoldersChangedListenerAsUser(OnRoleHoldersChangedListener listener,
+ UserHandle user) {
+ mRoleManager.removeOnRoleHoldersChangedListenerAsUser(listener, user);
+ }
+ }
+}
diff --git a/src/com/android/phone/ServiceStateProvider.java b/src/com/android/phone/ServiceStateProvider.java
index a7d27d5..56786f9 100644
--- a/src/com/android/phone/ServiceStateProvider.java
+++ b/src/com/android/phone/ServiceStateProvider.java
@@ -16,13 +16,18 @@
package com.android.phone;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.provider.Telephony.ServiceStateTable;
import static android.provider.Telephony.ServiceStateTable.CONTENT_URI;
+import static android.provider.Telephony.ServiceStateTable.DATA_NETWORK_TYPE;
+import static android.provider.Telephony.ServiceStateTable.DATA_REG_STATE;
+import static android.provider.Telephony.ServiceStateTable.DUPLEX_MODE;
import static android.provider.Telephony.ServiceStateTable.IS_MANUAL_NETWORK_SELECTION;
import static android.provider.Telephony.ServiceStateTable.VOICE_REG_STATE;
import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionId;
import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionIdAndField;
+import android.Manifest;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
@@ -30,16 +35,22 @@
import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder;
import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
import android.os.Parcel;
+import android.telephony.LocationAccessPolicy;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.TelephonyPermissions;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* The class to provide base facility to access ServiceState related content,
@@ -61,18 +72,6 @@
public static final String SERVICE_STATE = "service_state";
/**
- * An integer value indicating the current data service state.
- * <p>
- * Valid values: {@link ServiceState#STATE_IN_SERVICE},
- * {@link ServiceState#STATE_OUT_OF_SERVICE}, {@link ServiceState#STATE_EMERGENCY_ONLY},
- * {@link ServiceState#STATE_POWER_OFF}.
- * <p>
- * This is the same as {@link ServiceState#getDataRegState()}.
- * @hide
- */
- public static final String DATA_REG_STATE = "data_reg_state";
-
- /**
* An integer value indicating the current voice roaming type.
* <p>
* This is the same as {@link ServiceState#getVoiceRoamingType()}.
@@ -232,7 +231,9 @@
public static final String OPERATOR_ALPHA_SHORT_RAW = "operator_alpha_short_raw";
private final HashMap<Integer, ServiceState> mServiceStates = new HashMap<>();
- private static final String[] sColumns = {
+
+ @VisibleForTesting
+ /* package */ static final String[] ALL_COLUMNS = {
VOICE_REG_STATE,
DATA_REG_STATE,
VOICE_ROAMING_TYPE,
@@ -257,8 +258,38 @@
IS_USING_CARRIER_AGGREGATION,
OPERATOR_ALPHA_LONG_RAW,
OPERATOR_ALPHA_SHORT_RAW,
+ DATA_NETWORK_TYPE,
+ DUPLEX_MODE,
};
+ /**
+ * Columns that are exposed to public surface.
+ * These are the columns accessible to apps target S+ and lack
+ * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE} permission.
+ */
+ @VisibleForTesting
+ /* package */ static final String[] PUBLIC_COLUMNS = {
+ VOICE_REG_STATE,
+ DATA_REG_STATE,
+ VOICE_OPERATOR_NUMERIC,
+ IS_MANUAL_NETWORK_SELECTION,
+ DATA_NETWORK_TYPE,
+ DUPLEX_MODE
+ };
+
+ /**
+ * Columns protected by location permissions (either FINE or COARSE).
+ * SecurityException will throw if applications without location permissions try to put those
+ * columns explicitly into cursor (e.g. through {@code projection} parameter in
+ * {@link #query(Uri, String[], String, String[], String)} method).
+ * Default (scrub-out) value will return if applications try to put all columns into cursor by
+ * specifying null of {@code projection} parameter and get values through the returned cursor.
+ */
+ private static final Set<String> LOCATION_PROTECTED_COLUMNS_SET = Set.of(
+ NETWORK_ID,
+ SYSTEM_ID
+ );
+
@Override
public boolean onCreate() {
return true;
@@ -361,12 +392,54 @@
}
// Get the service state
- ServiceState ss = getServiceState(subId);
- if (ss == null) {
+ ServiceState unredactedServiceState = getServiceState(subId);
+ if (unredactedServiceState == null) {
Log.d(TAG, "returning null");
return null;
}
+ // TODO(b/182384053): replace targetSdk check with CompatChanges#isChangeEnabled
+ final boolean targetingAtLeastS = TelephonyPermissions.getTargetSdk(getContext(),
+ getCallingPackage()) >= Build.VERSION_CODES.S;
+ final boolean canReadPrivilegedPhoneState = getContext().checkCallingOrSelfPermission(
+ Manifest.permission.READ_PRIVILEGED_PHONE_STATE) == PERMISSION_GRANTED;
+
+ final String[] availableColumns;
+ final ServiceState ss;
+ if (targetingAtLeastS && !canReadPrivilegedPhoneState) {
+ // targetSdkVersion S+ without read privileged phone state permission can only
+ // access public columns which have no location sensitive info.
+ availableColumns = PUBLIC_COLUMNS;
+ ss = unredactedServiceState;
+ } else {
+ availableColumns = ALL_COLUMNS;
+
+ final boolean hasLocationPermission = hasLocationPermission();
+ if (hasLocationPermission) {
+ // No matter the targetSdkVersion, return unredacted ServiceState if caller does
+ // have location permission.
+ ss = unredactedServiceState;
+ } else {
+ // The caller has targetSdkVersion S+ but no location permission. It explicitly
+ // requires location protected columns. Throw SecurityException to fail loudly.
+ if (targetingAtLeastS && projection != null) {
+ for (String requiredColumn : projection) {
+ if (LOCATION_PROTECTED_COLUMNS_SET.contains(requiredColumn)) {
+ throw new SecurityException("Column " + requiredColumn
+ + "requires location permissions to access.");
+ }
+ }
+ }
+
+ // In all other cases, return the redacted ServiceState.
+ // The caller has no location permission but only requires columns without
+ // location sensitive info or "all" columns, return result that scrub out all
+ // sensitive info. In later case, we will not know which columns will be fetched
+ // from the returned cursor until the result has been returned.
+ ss = getLocationRedactedServiceState(unredactedServiceState);
+ }
+ }
+
// Build the result
final int voice_reg_state = ss.getState();
final int data_reg_state = ss.getDataRegistrationState();
@@ -392,8 +465,11 @@
final int is_using_carrier_aggregation = (ss.isUsingCarrierAggregation()) ? 1 : 0;
final String operator_alpha_long_raw = ss.getOperatorAlphaLongRaw();
final String operator_alpha_short_raw = ss.getOperatorAlphaShortRaw();
+ final int data_network_type = ss.getDataNetworkType();
+ final int duplex_mode = ss.getDuplexMode();
- return buildSingleRowResult(projection, sColumns, new Object[] {
+ Object[] data = availableColumns == ALL_COLUMNS ? new Object[]{
+ // data for all columns
voice_reg_state,
data_reg_state,
voice_roaming_type,
@@ -418,7 +494,19 @@
is_using_carrier_aggregation,
operator_alpha_long_raw,
operator_alpha_short_raw,
- });
+ data_network_type,
+ duplex_mode,
+ } : new Object[]{
+ // data for public columns only
+ voice_reg_state,
+ data_reg_state,
+ voice_operator_numeric,
+ is_manual_network_selection,
+ data_network_type,
+ duplex_mode,
+ };
+
+ return buildSingleRowResult(projection, availableColumns, data);
}
}
@@ -480,6 +568,10 @@
context.getContentResolver().notifyChange(
getUriForSubscriptionIdAndField(subId, DATA_ROAMING_TYPE), null, false);
}
+ if (firstUpdate || dataNetworkTypeChanged(oldSS, newSS)) {
+ context.getContentResolver().notifyChange(
+ getUriForSubscriptionIdAndField(subId, DATA_NETWORK_TYPE), null, false);
+ }
}
private static boolean voiceRegStateChanged(ServiceState oldSS, ServiceState newSS) {
@@ -498,6 +590,10 @@
return oldSS.getDataRoamingType() != newSS.getDataRoamingType();
}
+ private static boolean dataNetworkTypeChanged(ServiceState oldSS, ServiceState newSS) {
+ return oldSS.getDataNetworkType() != newSS.getDataNetworkType();
+ }
+
/**
* Notify interested apps that the ServiceState has changed.
*
@@ -517,7 +613,8 @@
// If oldSS is null and newSS is not (e.g. first update of service state) this will also
// notify
if (oldSS == null || voiceRegStateChanged(oldSS, newSS) || dataRegStateChanged(oldSS, newSS)
- || voiceRoamingTypeChanged(oldSS, newSS) || dataRoamingTypeChanged(oldSS, newSS)) {
+ || voiceRoamingTypeChanged(oldSS, newSS) || dataRoamingTypeChanged(oldSS, newSS)
+ || dataNetworkTypeChanged(oldSS, newSS)) {
context.getContentResolver().notifyChange(getUriForSubscriptionId(subId), null, false);
}
}
@@ -563,4 +660,35 @@
values.put(SERVICE_STATE, p.marshall());
return values;
}
+
+ /**
+ * Check location permission with same policy as {@link TelephonyManager#getServiceState()}
+ * which enforces location permission check starting from Q.
+ */
+ private boolean hasLocationPermission() {
+ LocationAccessPolicy.LocationPermissionResult locationPermissionResult =
+ LocationAccessPolicy.checkLocationPermission(getContext(),
+ new LocationAccessPolicy.LocationPermissionQuery.Builder()
+ .setCallingPackage(getCallingPackage())
+ .setCallingFeatureId(getCallingAttributionTag())
+ .setCallingPid(Binder.getCallingPid())
+ .setCallingUid(Binder.getCallingUid())
+ .setMethod("ServiceStateProvider#query")
+ .setLogAsInfo(true)
+ .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+ .setMinSdkVersionForCoarse(Build.VERSION_CODES.Q)
+ .setMinSdkVersionForEnforcement(Build.VERSION_CODES.Q)
+ .build());
+ return locationPermissionResult == LocationAccessPolicy.LocationPermissionResult.ALLOWED;
+ }
+
+ // Return a copy of ServiceState with all sensitive info redacted.
+ @VisibleForTesting
+ /* package */ static ServiceState getLocationRedactedServiceState(ServiceState serviceState) {
+ ServiceState ss =
+ serviceState.createLocationInfoSanitizedCopy(true /*removeCoarseLocation*/);
+ // TODO(b/188061647): remove the additional redaction once it is fixed in SS
+ ss.setCdmaSystemAndNetworkId(ServiceState.UNKNOWN_ID, ServiceState.UNKNOWN_ID);
+ return ss;
+ }
}
diff --git a/src/com/android/phone/SimPhonebookProvider.java b/src/com/android/phone/SimPhonebookProvider.java
new file mode 100644
index 0000000..b921398
--- /dev/null
+++ b/src/com/android/phone/SimPhonebookProvider.java
@@ -0,0 +1,917 @@
+/*
+ * Copyright (C) 2020 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.phone;
+
+import static com.android.internal.telephony.IccProvider.STR_NEW_TAG;
+import static com.android.internal.telephony.IccProvider.STR_NEW_NUMBER;
+
+import android.Manifest;
+import android.annotation.TestApi;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.RemoteException;
+import android.provider.SimPhonebookContract;
+import android.provider.SimPhonebookContract.ElementaryFiles;
+import android.provider.SimPhonebookContract.SimRecords;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.IIccPhoneBook;
+import com.android.internal.telephony.uicc.AdnRecord;
+import com.android.internal.telephony.uicc.IccConstants;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import java.util.ArrayList;
+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;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Supplier;
+
+/**
+ * Provider for contact records stored on the SIM card.
+ *
+ * @see SimPhonebookContract
+ */
+public class SimPhonebookProvider extends ContentProvider {
+
+ @VisibleForTesting
+ static final String[] ELEMENTARY_FILES_ALL_COLUMNS = {
+ ElementaryFiles.SLOT_INDEX,
+ ElementaryFiles.SUBSCRIPTION_ID,
+ ElementaryFiles.EF_TYPE,
+ ElementaryFiles.MAX_RECORDS,
+ ElementaryFiles.RECORD_COUNT,
+ ElementaryFiles.NAME_MAX_LENGTH,
+ ElementaryFiles.PHONE_NUMBER_MAX_LENGTH
+ };
+ @VisibleForTesting
+ static final String[] SIM_RECORDS_ALL_COLUMNS = {
+ SimRecords.SUBSCRIPTION_ID,
+ SimRecords.ELEMENTARY_FILE_TYPE,
+ SimRecords.RECORD_NUMBER,
+ SimRecords.NAME,
+ SimRecords.PHONE_NUMBER
+ };
+ private static final String TAG = "SimPhonebookProvider";
+ private static final Set<String> ELEMENTARY_FILES_COLUMNS_SET =
+ ImmutableSet.copyOf(ELEMENTARY_FILES_ALL_COLUMNS);
+ private static final Set<String> SIM_RECORDS_WRITABLE_COLUMNS = ImmutableSet.of(
+ SimRecords.NAME, SimRecords.PHONE_NUMBER
+ );
+
+ private static final int WRITE_TIMEOUT_SECONDS = 30;
+
+ private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+
+ private static final int ELEMENTARY_FILES = 100;
+ private static final int ELEMENTARY_FILES_ITEM = 101;
+ private static final int SIM_RECORDS = 200;
+ private static final int SIM_RECORDS_ITEM = 201;
+
+ static {
+ URI_MATCHER.addURI(SimPhonebookContract.AUTHORITY,
+ ElementaryFiles.ELEMENTARY_FILES_PATH_SEGMENT, ELEMENTARY_FILES);
+ URI_MATCHER.addURI(
+ SimPhonebookContract.AUTHORITY,
+ ElementaryFiles.ELEMENTARY_FILES_PATH_SEGMENT + "/"
+ + SimPhonebookContract.SUBSCRIPTION_ID_PATH_SEGMENT + "/#/*",
+ ELEMENTARY_FILES_ITEM);
+ URI_MATCHER.addURI(SimPhonebookContract.AUTHORITY,
+ SimPhonebookContract.SUBSCRIPTION_ID_PATH_SEGMENT + "/#/*", SIM_RECORDS);
+ URI_MATCHER.addURI(SimPhonebookContract.AUTHORITY,
+ SimPhonebookContract.SUBSCRIPTION_ID_PATH_SEGMENT + "/#/*/#", SIM_RECORDS_ITEM);
+ }
+
+ // Only allow 1 write at a time to prevent races; the mutations are based on reads of the
+ // existing list of records which means concurrent writes would be problematic.
+ private final Lock mWriteLock = new ReentrantLock(true);
+ private SubscriptionManager mSubscriptionManager;
+ private Supplier<IIccPhoneBook> mIccPhoneBookSupplier;
+ private ContentNotifier mContentNotifier;
+
+ static int efIdForEfType(@ElementaryFiles.EfType int efType) {
+ switch (efType) {
+ case ElementaryFiles.EF_ADN:
+ return IccConstants.EF_ADN;
+ case ElementaryFiles.EF_FDN:
+ return IccConstants.EF_FDN;
+ case ElementaryFiles.EF_SDN:
+ return IccConstants.EF_SDN;
+ default:
+ return 0;
+ }
+ }
+
+ private static void validateProjection(Set<String> allowed, String[] projection) {
+ if (projection == null || allowed.containsAll(Arrays.asList(projection))) {
+ return;
+ }
+ Set<String> invalidColumns = new LinkedHashSet<>(Arrays.asList(projection));
+ invalidColumns.removeAll(allowed);
+ throw new IllegalArgumentException(
+ "Unsupported columns: " + Joiner.on(",").join(invalidColumns));
+ }
+
+ private static int getRecordSize(int[] recordsSize) {
+ return recordsSize[0];
+ }
+
+ private static int getRecordCount(int[] recordsSize) {
+ return recordsSize[2];
+ }
+
+ /** Returns the IccPhoneBook used to load the AdnRecords. */
+ private static IIccPhoneBook getIccPhoneBook() {
+ return IIccPhoneBook.Stub.asInterface(TelephonyFrameworkInitializer
+ .getTelephonyServiceManager().getIccPhoneBookServiceRegisterer().get());
+ }
+
+ @Override
+ public boolean onCreate() {
+ ContentResolver resolver = getContext().getContentResolver();
+ return onCreate(getContext().getSystemService(SubscriptionManager.class),
+ SimPhonebookProvider::getIccPhoneBook,
+ uri -> resolver.notifyChange(uri, null));
+ }
+
+ @TestApi
+ boolean onCreate(SubscriptionManager subscriptionManager,
+ Supplier<IIccPhoneBook> iccPhoneBookSupplier, ContentNotifier notifier) {
+ if (subscriptionManager == null) {
+ return false;
+ }
+ mSubscriptionManager = subscriptionManager;
+ mIccPhoneBookSupplier = iccPhoneBookSupplier;
+ mContentNotifier = notifier;
+
+ mSubscriptionManager.addOnSubscriptionsChangedListener(MoreExecutors.directExecutor(),
+ new SubscriptionManager.OnSubscriptionsChangedListener() {
+ boolean mFirstCallback = true;
+ private int[] mNotifiedSubIds = {};
+
+ @Override
+ public void onSubscriptionsChanged() {
+ if (mFirstCallback) {
+ mFirstCallback = false;
+ return;
+ }
+ int[] activeSubIds = mSubscriptionManager.getActiveSubscriptionIdList();
+ if (!Arrays.equals(mNotifiedSubIds, activeSubIds)) {
+ notifier.notifyChange(SimPhonebookContract.AUTHORITY_URI);
+ mNotifiedSubIds = Arrays.copyOf(activeSubIds, activeSubIds.length);
+ }
+ }
+ });
+ 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
+ public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable Bundle queryArgs,
+ @Nullable CancellationSignal cancellationSignal) {
+ if (queryArgs != null && (queryArgs.containsKey(ContentResolver.QUERY_ARG_SQL_SELECTION)
+ || queryArgs.containsKey(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS)
+ || queryArgs.containsKey(ContentResolver.QUERY_ARG_SQL_LIMIT))) {
+ throw new IllegalArgumentException(
+ "A SQL selection was provided but it is not supported by this provider.");
+ }
+ switch (URI_MATCHER.match(uri)) {
+ case ELEMENTARY_FILES:
+ return queryElementaryFiles(projection);
+ case ELEMENTARY_FILES_ITEM:
+ return queryElementaryFilesItem(PhonebookArgs.forElementaryFilesItem(uri),
+ projection);
+ case SIM_RECORDS:
+ return querySimRecords(PhonebookArgs.forSimRecords(uri, queryArgs), projection);
+ case SIM_RECORDS_ITEM:
+ return querySimRecordsItem(PhonebookArgs.forSimRecordsItem(uri, queryArgs),
+ projection);
+ default:
+ throw new IllegalArgumentException("Unsupported Uri " + uri);
+ }
+ }
+
+ @Nullable
+ @Override
+ public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
+ @Nullable String[] selectionArgs, @Nullable String sortOrder,
+ @Nullable CancellationSignal cancellationSignal) {
+ throw new UnsupportedOperationException("Only query with Bundle is supported");
+ }
+
+ @Nullable
+ @Override
+ public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
+ @Nullable String[] selectionArgs, @Nullable String sortOrder) {
+ throw new UnsupportedOperationException("Only query with Bundle is supported");
+ }
+
+ private Cursor queryElementaryFiles(String[] projection) {
+ validateProjection(ELEMENTARY_FILES_COLUMNS_SET, projection);
+ if (projection == null) {
+ projection = ELEMENTARY_FILES_ALL_COLUMNS;
+ }
+
+ MatrixCursor result = new MatrixCursor(projection);
+
+ List<SubscriptionInfo> activeSubscriptions = getActiveSubscriptionInfoList();
+ for (SubscriptionInfo subInfo : activeSubscriptions) {
+ try {
+ addEfToCursor(result, subInfo, ElementaryFiles.EF_ADN);
+ addEfToCursor(result, subInfo, ElementaryFiles.EF_FDN);
+ addEfToCursor(result, subInfo, ElementaryFiles.EF_SDN);
+ } catch (RemoteException e) {
+ // Return an empty cursor. If service to access it is throwing remote
+ // exceptions then it's basically the same as not having a SIM.
+ return new MatrixCursor(projection, 0);
+ }
+ }
+ return result;
+ }
+
+ 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 {
+ addEfToCursor(
+ result, getActiveSubscriptionInfo(args.subscriptionId), args.efType);
+ } catch (RemoteException e) {
+ // Return an empty cursor. If service to access it is throwing remote
+ // exceptions then it's basically the same as not having a SIM.
+ return new MatrixCursor(projection, 0);
+ }
+ return result;
+ }
+
+ private void addEfToCursor(MatrixCursor result, SubscriptionInfo subscriptionInfo,
+ int efType) throws RemoteException {
+ int[] recordsSize = mIccPhoneBookSupplier.get().getAdnRecordsSizeForSubscriber(
+ subscriptionInfo.getSubscriptionId(), efIdForEfType(efType));
+ addEfToCursor(result, subscriptionInfo, efType, recordsSize);
+ }
+
+ private void addEfToCursor(MatrixCursor result, SubscriptionInfo subscriptionInfo,
+ int efType, int[] recordsSize) throws RemoteException {
+ // If the record count is zero then the SIM doesn't support the elementary file so just
+ // omit it.
+ if (recordsSize == null || getRecordCount(recordsSize) == 0) {
+ return;
+ }
+ MatrixCursor.RowBuilder row = result.newRow()
+ .add(ElementaryFiles.SLOT_INDEX, subscriptionInfo.getSimSlotIndex())
+ .add(ElementaryFiles.SUBSCRIPTION_ID, subscriptionInfo.getSubscriptionId())
+ .add(ElementaryFiles.EF_TYPE, efType)
+ .add(ElementaryFiles.MAX_RECORDS, getRecordCount(recordsSize))
+ .add(ElementaryFiles.NAME_MAX_LENGTH,
+ AdnRecord.getMaxAlphaTagBytes(getRecordSize(recordsSize)))
+ .add(ElementaryFiles.PHONE_NUMBER_MAX_LENGTH,
+ AdnRecord.getMaxPhoneNumberDigits());
+ if (result.getColumnIndex(ElementaryFiles.RECORD_COUNT) != -1) {
+ int efid = efIdForEfType(efType);
+ List<AdnRecord> existingRecords = mIccPhoneBookSupplier.get()
+ .getAdnRecordsInEfForSubscriber(subscriptionInfo.getSubscriptionId(), efid);
+ int nonEmptyCount = 0;
+ for (AdnRecord record : existingRecords) {
+ if (!record.isEmpty()) {
+ nonEmptyCount++;
+ }
+ }
+ row.add(ElementaryFiles.RECORD_COUNT, nonEmptyCount);
+ }
+ }
+
+ private Cursor querySimRecords(PhonebookArgs args, String[] projection) {
+ validateSubscriptionAndEf(args);
+ if (projection == null) {
+ projection = SIM_RECORDS_ALL_COLUMNS;
+ }
+
+ List<AdnRecord> records = loadRecordsForEf(args);
+ if (records == null) {
+ return new MatrixCursor(projection, 0);
+ }
+ MatrixCursor result = new MatrixCursor(projection, records.size());
+ List<Pair<AdnRecord, MatrixCursor.RowBuilder>> rowBuilders = new ArrayList<>(
+ records.size());
+ for (AdnRecord record : records) {
+ if (!record.isEmpty()) {
+ rowBuilders.add(Pair.create(record, result.newRow()));
+ }
+ }
+ // This is kind of ugly but avoids looking up columns in an inner loop.
+ for (String column : projection) {
+ switch (column) {
+ case SimRecords.SUBSCRIPTION_ID:
+ for (Pair<AdnRecord, MatrixCursor.RowBuilder> row : rowBuilders) {
+ row.second.add(args.subscriptionId);
+ }
+ break;
+ case SimRecords.ELEMENTARY_FILE_TYPE:
+ for (Pair<AdnRecord, MatrixCursor.RowBuilder> row : rowBuilders) {
+ row.second.add(args.efType);
+ }
+ break;
+ case SimRecords.RECORD_NUMBER:
+ for (Pair<AdnRecord, MatrixCursor.RowBuilder> row : rowBuilders) {
+ row.second.add(row.first.getRecId());
+ }
+ break;
+ case SimRecords.NAME:
+ for (Pair<AdnRecord, MatrixCursor.RowBuilder> row : rowBuilders) {
+ row.second.add(row.first.getAlphaTag());
+ }
+ break;
+ case SimRecords.PHONE_NUMBER:
+ for (Pair<AdnRecord, MatrixCursor.RowBuilder> row : rowBuilders) {
+ row.second.add(row.first.getNumber());
+ }
+ break;
+ default:
+ Rlog.w(TAG, "Column " + column + " is unsupported for " + args.uri);
+ break;
+ }
+ }
+ return result;
+ }
+
+ private Cursor querySimRecordsItem(PhonebookArgs args, String[] projection) {
+ if (projection == null) {
+ projection = SIM_RECORDS_ALL_COLUMNS;
+ }
+ validateSubscriptionAndEf(args);
+ AdnRecord record = loadRecord(args);
+
+ MatrixCursor result = new MatrixCursor(projection, 1);
+ if (record == null || record.isEmpty()) {
+ return result;
+ }
+ result.newRow()
+ .add(SimRecords.SUBSCRIPTION_ID, args.subscriptionId)
+ .add(SimRecords.ELEMENTARY_FILE_TYPE, args.efType)
+ .add(SimRecords.RECORD_NUMBER, record.getRecId())
+ .add(SimRecords.NAME, record.getAlphaTag())
+ .add(SimRecords.PHONE_NUMBER, record.getNumber());
+ return result;
+ }
+
+ @Nullable
+ @Override
+ public String getType(@NonNull Uri uri) {
+ switch (URI_MATCHER.match(uri)) {
+ case ELEMENTARY_FILES:
+ return ElementaryFiles.CONTENT_TYPE;
+ case ELEMENTARY_FILES_ITEM:
+ return ElementaryFiles.CONTENT_ITEM_TYPE;
+ case SIM_RECORDS:
+ return SimRecords.CONTENT_TYPE;
+ case SIM_RECORDS_ITEM:
+ return SimRecords.CONTENT_ITEM_TYPE;
+ default:
+ throw new IllegalArgumentException("Unsupported Uri " + uri);
+ }
+ }
+
+ @Nullable
+ @Override
+ public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
+ return insert(uri, values, null);
+ }
+
+ @Nullable
+ @Override
+ public Uri insert(@NonNull Uri uri, @Nullable ContentValues values, @Nullable Bundle extras) {
+ switch (URI_MATCHER.match(uri)) {
+ case SIM_RECORDS:
+ return insertSimRecord(PhonebookArgs.forSimRecords(uri, extras), values);
+ case ELEMENTARY_FILES:
+ case ELEMENTARY_FILES_ITEM:
+ case SIM_RECORDS_ITEM:
+ throw new UnsupportedOperationException(uri + " does not support insert");
+ default:
+ throw new IllegalArgumentException("Unsupported Uri " + uri);
+ }
+ }
+
+ private Uri insertSimRecord(PhonebookArgs args, ContentValues values) {
+ validateWritableEf(args, "insert");
+ validateSubscriptionAndEf(args);
+
+ if (values == null || values.isEmpty()) {
+ return null;
+ }
+ validateValues(args, values);
+ String newName = Strings.nullToEmpty(values.getAsString(SimRecords.NAME));
+ String newPhoneNumber = Strings.nullToEmpty(values.getAsString(SimRecords.PHONE_NUMBER));
+
+ acquireWriteLockOrThrow();
+ try {
+ List<AdnRecord> records = loadRecordsForEf(args);
+ if (records == null) {
+ Rlog.e(TAG, "Failed to load existing records for " + args.uri);
+ return null;
+ }
+ AdnRecord emptyRecord = null;
+ for (AdnRecord record : records) {
+ if (record.isEmpty()) {
+ emptyRecord = record;
+ break;
+ }
+ }
+ if (emptyRecord == null) {
+ // When there are no empty records that means the EF is full.
+ throw new IllegalStateException(
+ args.uri + " is full. Please delete records to add new ones.");
+ }
+ boolean success = updateRecord(args, emptyRecord, args.pin2, newName, newPhoneNumber);
+ if (!success) {
+ Rlog.e(TAG, "Insert failed for " + args.uri);
+ // Something didn't work but since we don't have any more specific
+ // information to provide to the caller it's better to just return null
+ // rather than throwing and possibly crashing their process.
+ return null;
+ }
+ notifyChange();
+ return SimRecords.getItemUri(args.subscriptionId, args.efType, emptyRecord.getRecId());
+ } finally {
+ releaseWriteLock();
+ }
+ }
+
+ @Override
+ public int delete(@NonNull Uri uri, @Nullable String selection,
+ @Nullable String[] selectionArgs) {
+ throw new UnsupportedOperationException("Only delete with Bundle is supported");
+ }
+
+ @Override
+ public int delete(@NonNull Uri uri, @Nullable Bundle extras) {
+ switch (URI_MATCHER.match(uri)) {
+ case SIM_RECORDS_ITEM:
+ return deleteSimRecordsItem(PhonebookArgs.forSimRecordsItem(uri, extras));
+ case ELEMENTARY_FILES:
+ case ELEMENTARY_FILES_ITEM:
+ case SIM_RECORDS:
+ throw new UnsupportedOperationException(uri + " does not support delete");
+ default:
+ throw new IllegalArgumentException("Unsupported Uri " + uri);
+ }
+ }
+
+ private int deleteSimRecordsItem(PhonebookArgs args) {
+ validateWritableEf(args, "delete");
+ validateSubscriptionAndEf(args);
+
+ acquireWriteLockOrThrow();
+ try {
+ AdnRecord record = loadRecord(args);
+ if (record == null || record.isEmpty()) {
+ return 0;
+ }
+ if (!updateRecord(args, record, args.pin2, "", "")) {
+ Rlog.e(TAG, "Failed to delete " + args.uri);
+ }
+ notifyChange();
+ } finally {
+ releaseWriteLock();
+ }
+ return 1;
+ }
+
+
+ @Override
+ public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable Bundle extras) {
+ switch (URI_MATCHER.match(uri)) {
+ case SIM_RECORDS_ITEM:
+ return updateSimRecordsItem(PhonebookArgs.forSimRecordsItem(uri, extras), values);
+ case ELEMENTARY_FILES:
+ case ELEMENTARY_FILES_ITEM:
+ case SIM_RECORDS:
+ throw new UnsupportedOperationException(uri + " does not support update");
+ default:
+ throw new IllegalArgumentException("Unsupported Uri " + uri);
+ }
+ }
+
+ @Override
+ public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
+ @Nullable String[] selectionArgs) {
+ throw new UnsupportedOperationException("Only Update with bundle is supported");
+ }
+
+ private int updateSimRecordsItem(PhonebookArgs args, ContentValues values) {
+ validateWritableEf(args, "update");
+ validateSubscriptionAndEf(args);
+
+ if (values == null || values.isEmpty()) {
+ return 0;
+ }
+ validateValues(args, values);
+ String newName = Strings.nullToEmpty(values.getAsString(SimRecords.NAME));
+ String newPhoneNumber = Strings.nullToEmpty(values.getAsString(SimRecords.PHONE_NUMBER));
+
+ acquireWriteLockOrThrow();
+
+ try {
+ AdnRecord record = loadRecord(args);
+
+ // Note we allow empty records to be updated. This is a bit weird because they are
+ // not returned by query methods but this allows a client application assign a name
+ // to a specific record number. This may be desirable in some phone app use cases since
+ // the record number is often used as a quick dial index.
+ if (record == null) {
+ return 0;
+ }
+ if (!updateRecord(args, record, args.pin2, newName, newPhoneNumber)) {
+ Rlog.e(TAG, "Failed to update " + args.uri);
+ return 0;
+ }
+ notifyChange();
+ } finally {
+ releaseWriteLock();
+ }
+ return 1;
+ }
+
+ void validateSubscriptionAndEf(PhonebookArgs args) {
+ SubscriptionInfo info =
+ args.subscriptionId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ ? getActiveSubscriptionInfo(args.subscriptionId)
+ : null;
+ if (info == null) {
+ throw new IllegalArgumentException("No active SIM with subscription ID "
+ + args.subscriptionId);
+ }
+
+ int[] recordsSize = getRecordsSizeForEf(args);
+ if (recordsSize == null || recordsSize[1] == 0) {
+ throw new IllegalArgumentException(args.efName
+ + " is not supported for SIM with subscription ID " + args.subscriptionId);
+ }
+ }
+
+ private void acquireWriteLockOrThrow() {
+ try {
+ if (!mWriteLock.tryLock(WRITE_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
+ throw new IllegalStateException("Timeout waiting to write");
+ }
+ } catch (InterruptedException e) {
+ throw new IllegalStateException("Write failed");
+ }
+ }
+
+ private void releaseWriteLock() {
+ mWriteLock.unlock();
+ }
+
+ private void validateWritableEf(PhonebookArgs args, String operationName) {
+ if (args.efType == ElementaryFiles.EF_FDN) {
+ if (hasPermissionsForFdnWrite(args)) {
+ return;
+ }
+ }
+ if (args.efType != ElementaryFiles.EF_ADN) {
+ throw new UnsupportedOperationException(
+ args.uri + " does not support " + operationName);
+ }
+ }
+
+ private boolean hasPermissionsForFdnWrite(PhonebookArgs args) {
+ TelephonyManager telephonyManager = Objects.requireNonNull(
+ getContext().getSystemService(TelephonyManager.class));
+ String callingPackage = getCallingPackage();
+ int granted = PackageManager.PERMISSION_DENIED;
+ if (callingPackage != null) {
+ granted = getContext().getPackageManager().checkPermission(
+ Manifest.permission.MODIFY_PHONE_STATE, callingPackage);
+ }
+ return granted == PackageManager.PERMISSION_GRANTED
+ || telephonyManager.hasCarrierPrivileges(args.subscriptionId);
+
+ }
+
+
+ private boolean updateRecord(PhonebookArgs args, AdnRecord existingRecord, String pin2,
+ String newName, String newPhone) {
+ try {
+ ContentValues values = new ContentValues();
+ values.put(STR_NEW_TAG, newName);
+ values.put(STR_NEW_NUMBER, newPhone);
+ return mIccPhoneBookSupplier.get().updateAdnRecordsInEfByIndexForSubscriber(
+ args.subscriptionId, existingRecord.getEfid(), values,
+ existingRecord.getRecId(),
+ pin2);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ private void validatePhoneNumber(@Nullable String phoneNumber) {
+ if (phoneNumber == null || phoneNumber.isEmpty()) {
+ throw new IllegalArgumentException(SimRecords.PHONE_NUMBER + " is required.");
+ }
+ int actualLength = phoneNumber.length();
+ // When encoded the "+" prefix sets a bit and so doesn't count against the maximum length
+ if (phoneNumber.startsWith("+")) {
+ actualLength--;
+ }
+ if (actualLength > AdnRecord.getMaxPhoneNumberDigits()) {
+ throw new IllegalArgumentException(SimRecords.PHONE_NUMBER + " is too long.");
+ }
+ for (int i = 0; i < phoneNumber.length(); i++) {
+ char c = phoneNumber.charAt(i);
+ if (!PhoneNumberUtils.isNonSeparator(c)) {
+ throw new IllegalArgumentException(
+ SimRecords.PHONE_NUMBER + " contains unsupported characters.");
+ }
+ }
+ }
+
+ private void validateValues(PhonebookArgs args, ContentValues values) {
+ if (!SIM_RECORDS_WRITABLE_COLUMNS.containsAll(values.keySet())) {
+ Set<String> unsupportedColumns = new ArraySet<>(values.keySet());
+ unsupportedColumns.removeAll(SIM_RECORDS_WRITABLE_COLUMNS);
+ throw new IllegalArgumentException("Unsupported columns: " + Joiner.on(',')
+ .join(unsupportedColumns));
+ }
+
+ String phoneNumber = values.getAsString(SimRecords.PHONE_NUMBER);
+ validatePhoneNumber(phoneNumber);
+
+ String name = values.getAsString(SimRecords.NAME);
+ int length = getEncodedNameLength(name);
+ 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.");
+ }
+ }
+
+ private List<SubscriptionInfo> getActiveSubscriptionInfoList() {
+ // Getting the SubscriptionInfo requires READ_PHONE_STATE but we're only returning
+ // the subscription ID and slot index which are not sensitive information.
+ CallingIdentity identity = clearCallingIdentity();
+ try {
+ return mSubscriptionManager.getActiveSubscriptionInfoList();
+ } finally {
+ restoreCallingIdentity(identity);
+ }
+ }
+
+ private SubscriptionInfo getActiveSubscriptionInfo(int subId) {
+ // Getting the SubscriptionInfo requires READ_PHONE_STATE.
+ CallingIdentity identity = clearCallingIdentity();
+ try {
+ return mSubscriptionManager.getActiveSubscriptionInfo(subId);
+ } finally {
+ restoreCallingIdentity(identity);
+ }
+ }
+
+ private List<AdnRecord> loadRecordsForEf(PhonebookArgs args) {
+ try {
+ return mIccPhoneBookSupplier.get().getAdnRecordsInEfForSubscriber(
+ args.subscriptionId, args.efid);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ private AdnRecord loadRecord(PhonebookArgs args) {
+ List<AdnRecord> records = loadRecordsForEf(args);
+ if (records == null || args.recordNumber > records.size()) {
+ return null;
+ }
+ AdnRecord result = records.get(args.recordNumber - 1);
+ // This should be true but the service could have a different implementation.
+ if (result.getRecId() == args.recordNumber) {
+ return result;
+ }
+ for (AdnRecord record : records) {
+ if (record.getRecId() == args.recordNumber) {
+ return result;
+ }
+ }
+ return null;
+ }
+
+
+ private int[] getRecordsSizeForEf(PhonebookArgs args) {
+ try {
+ return mIccPhoneBookSupplier.get().getAdnRecordsSizeForSubscriber(
+ args.subscriptionId, args.efid);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ void notifyChange() {
+ mContentNotifier.notifyChange(SimPhonebookContract.AUTHORITY_URI);
+ }
+
+ /** Testable wrapper around {@link ContentResolver#notifyChange(Uri, ContentObserver)} */
+ @TestApi
+ interface ContentNotifier {
+ void notifyChange(Uri uri);
+ }
+
+ /**
+ * Holds the arguments extracted from the Uri and query args for accessing the referenced
+ * phonebook data on a SIM.
+ */
+ private static class PhonebookArgs {
+ public final Uri uri;
+ public final int subscriptionId;
+ public final String efName;
+ public final int efType;
+ public final int efid;
+ public final int recordNumber;
+ public final String pin2;
+
+ PhonebookArgs(Uri uri, int subscriptionId, String efName,
+ @ElementaryFiles.EfType int efType, int efid, int recordNumber,
+ @Nullable Bundle queryArgs) {
+ this.uri = uri;
+ this.subscriptionId = subscriptionId;
+ this.efName = efName;
+ this.efType = efType;
+ this.efid = efid;
+ this.recordNumber = recordNumber;
+ pin2 = efType == ElementaryFiles.EF_FDN && queryArgs != null
+ ? queryArgs.getString(SimRecords.QUERY_ARG_PIN2)
+ : null;
+ }
+
+ static PhonebookArgs createFromEfName(Uri uri, int subscriptionId,
+ String efName, int recordNumber, @Nullable Bundle queryArgs) {
+ int efType;
+ int efid;
+ if (efName != null) {
+ switch (efName) {
+ case ElementaryFiles.PATH_SEGMENT_EF_ADN:
+ efType = ElementaryFiles.EF_ADN;
+ efid = IccConstants.EF_ADN;
+ break;
+ case ElementaryFiles.PATH_SEGMENT_EF_FDN:
+ efType = ElementaryFiles.EF_FDN;
+ efid = IccConstants.EF_FDN;
+ break;
+ case ElementaryFiles.PATH_SEGMENT_EF_SDN:
+ efType = ElementaryFiles.EF_SDN;
+ efid = IccConstants.EF_SDN;
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unrecognized elementary file " + efName);
+ }
+ } else {
+ efType = ElementaryFiles.EF_UNKNOWN;
+ efid = 0;
+ }
+ return new PhonebookArgs(uri, subscriptionId, efName, efType, efid, recordNumber,
+ queryArgs);
+ }
+
+ /**
+ * Pattern: elementary_files/subid/${subscriptionId}/${efName}
+ *
+ * e.g. elementary_files/subid/1/adn
+ *
+ * @see ElementaryFiles#getItemUri(int, int)
+ * @see #ELEMENTARY_FILES_ITEM
+ */
+ static PhonebookArgs forElementaryFilesItem(Uri uri) {
+ int subscriptionId = parseSubscriptionIdFromUri(uri, 2);
+ String efName = uri.getPathSegments().get(3);
+ return PhonebookArgs.createFromEfName(
+ uri, subscriptionId, efName, -1, null);
+ }
+
+ /**
+ * Pattern: subid/${subscriptionId}/${efName}
+ *
+ * <p>e.g. subid/1/adn
+ *
+ * @see SimRecords#getContentUri(int, int)
+ * @see #SIM_RECORDS
+ */
+ static PhonebookArgs forSimRecords(Uri uri, Bundle queryArgs) {
+ int subscriptionId = parseSubscriptionIdFromUri(uri, 1);
+ String efName = uri.getPathSegments().get(2);
+ return PhonebookArgs.createFromEfName(uri, subscriptionId, efName, -1, queryArgs);
+ }
+
+ /**
+ * Pattern: subid/${subscriptionId}/${efName}/${recordNumber}
+ *
+ * <p>e.g. subid/1/adn/10
+ *
+ * @see SimRecords#getItemUri(int, int, int)
+ * @see #SIM_RECORDS_ITEM
+ */
+ static PhonebookArgs forSimRecordsItem(Uri uri, Bundle queryArgs) {
+ int subscriptionId = parseSubscriptionIdFromUri(uri, 1);
+ String efName = uri.getPathSegments().get(2);
+ int recordNumber = parseRecordNumberFromUri(uri, 3);
+ return PhonebookArgs.createFromEfName(uri, subscriptionId, efName, recordNumber,
+ queryArgs);
+ }
+
+ private static int parseSubscriptionIdFromUri(Uri uri, int pathIndex) {
+ if (pathIndex == -1) {
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
+ String segment = uri.getPathSegments().get(pathIndex);
+ try {
+ return Integer.parseInt(segment);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid subscription ID: " + segment);
+ }
+ }
+
+ private static int parseRecordNumberFromUri(Uri uri, int pathIndex) {
+ try {
+ return Integer.parseInt(uri.getPathSegments().get(pathIndex));
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(
+ "Invalid record index: " + uri.getLastPathSegment());
+ }
+ }
+ }
+}
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index 2255138..dbeb7ce 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -16,31 +16,57 @@
package com.android.phone;
+import static com.android.internal.telephony.d2d.Communicator.MESSAGE_CALL_AUDIO_CODEC;
+import static com.android.internal.telephony.d2d.Communicator.MESSAGE_CALL_RADIO_ACCESS_TYPE;
+import static com.android.internal.telephony.d2d.Communicator.MESSAGE_DEVICE_BATTERY_STATE;
+import static com.android.internal.telephony.d2d.Communicator.MESSAGE_DEVICE_NETWORK_COVERAGE;
+
+import android.Manifest;
import android.content.Context;
-import android.os.BasicShellCommandHandler;
+import android.net.Uri;
import android.os.Binder;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.provider.BlockedNumberContract;
+import android.telephony.BarringInfo;
import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.TelephonyRegistryManager;
import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.RcsContactUceCapability;
import android.telephony.ims.feature.ImsFeature;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
+import android.util.SparseArray;
+import com.android.ims.rcs.uce.util.FeatureTags;
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.d2d.Communicator;
import com.android.internal.telephony.emergency.EmergencyNumberTracker;
import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.phone.callcomposer.CallComposerPictureManager;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.TreeSet;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
/**
* Takes actions based on the adb commands given by "adb shell cmd phone ...". Be careful, no
@@ -55,20 +81,27 @@
private static final boolean VDBG = true;
private static final int DEFAULT_PHONE_ID = 0;
+ private static final String CALL_COMPOSER_SUBCOMMAND = "callcomposer";
private static final String IMS_SUBCOMMAND = "ims";
private static final String NUMBER_VERIFICATION_SUBCOMMAND = "numverify";
private static final String EMERGENCY_CALLBACK_MODE = "emergency-callback-mode";
private static final String EMERGENCY_NUMBER_TEST_MODE = "emergency-number-test-mode";
private static final String END_BLOCK_SUPPRESSION = "end-block-suppression";
+ private static final String RESTART_MODEM = "restart-modem";
+ 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";
- private static final String DATA_DISABLE = "disable";
+ private static final String ENABLE = "enable";
+ private static final String DISABLE = "disable";
+ private static final String QUERY = "query";
- private static final String IMS_SET_CARRIER_SERVICE = "set-ims-service";
- private static final String IMS_GET_CARRIER_SERVICE = "get-ims-service";
- private static final String IMS_ENABLE = "enable";
- private static final String IMS_DISABLE = "disable";
+ private static final String CALL_COMPOSER_TEST_MODE = "test-mode";
+ private static final String CALL_COMPOSER_SIMULATE_CALL = "simulate-outgoing-call";
+ private static final String CALL_COMPOSER_USER_SETTING = "user-setting";
+
+ private static final String IMS_SET_IMS_SERVICE = "set-ims-service";
+ private static final String IMS_GET_IMS_SERVICE = "get-ims-service";
+ private static final String IMS_CLEAR_SERVICE_OVERRIDE = "clear-ims-service-override";
// Used to disable or enable processing of conference event package data from the network.
// This is handy for testing scenarios where CEP data does not exist on a network which does
// support CEP data.
@@ -81,11 +114,63 @@
private static final String CC_SET_VALUE = "set-value";
private static final String CC_CLEAR_VALUES = "clear-values";
+ private static final String GBA_SUBCOMMAND = "gba";
+ private static final String GBA_SET_SERVICE = "set-service";
+ private static final String GBA_GET_SERVICE = "get-service";
+ private static final String GBA_SET_RELEASE_TIME = "set-release";
+ private static final String GBA_GET_RELEASE_TIME = "get-release";
+
+ private static final String SINGLE_REGISTATION_CONFIG = "src";
+ private static final String SRC_SET_DEVICE_ENABLED = "set-device-enabled";
+ 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 SRC_SET_FEATURE_ENABLED = "set-feature-validation";
+ private static final String SRC_GET_FEATURE_ENABLED = "get-feature-validation";
+
+ private static final String D2D_SUBCOMMAND = "d2d";
+ private static final String D2D_SEND = "send";
+ private static final String D2D_TRANSPORT = "transport";
+ private static final String D2D_SET_DEVICE_SUPPORT = "set-device-support";
+
+ private static final String BARRING_SUBCOMMAND = "barring";
+ private static final String BARRING_SEND_INFO = "send";
+
+ private static final String RCS_UCE_COMMAND = "uce";
+ private static final String UCE_GET_EAB_CONTACT = "get-eab-contact";
+ private static final String UCE_GET_EAB_CAPABILITY = "get-eab-capability";
+ private static final String UCE_REMOVE_EAB_CONTACT = "remove-eab-contact";
+ private static final String UCE_GET_DEVICE_ENABLED = "get-device-enabled";
+ private static final String UCE_SET_DEVICE_ENABLED = "set-device-enabled";
+ private static final String UCE_OVERRIDE_PUBLISH_CAPS = "override-published-caps";
+ private static final String UCE_GET_LAST_PIDF_XML = "get-last-publish-pidf";
+ private static final String UCE_REMOVE_REQUEST_DISALLOWED_STATUS =
+ "remove-request-disallowed-status";
+ private static final String UCE_SET_CAPABILITY_REQUEST_TIMEOUT =
+ "set-capabilities-request-timeout";
+
+ // Check if a package has carrier privileges on any SIM, regardless of subId/phoneId.
+ private static final String HAS_CARRIER_PRIVILEGES_COMMAND = "has-carrier-privileges";
+
+ private static final String DISABLE_PHYSICAL_SUBSCRIPTION = "disable-physical-subscription";
+ private static final String ENABLE_PHYSICAL_SUBSCRIPTION = "enable-physical-subscription";
+
+ private static final String THERMAL_MITIGATION_COMMAND = "thermal-mitigation";
+ private static final String ALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND = "allow-package";
+ private static final String DISALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND = "disallow-package";
+
+ private static final String GET_ALLOWED_NETWORK_TYPES_FOR_USER =
+ "get-allowed-network-types-for-users";
+ private static final String SET_ALLOWED_NETWORK_TYPES_FOR_USER =
+ "set-allowed-network-types-for-users";
// Take advantage of existing methods that already contain permissions checks when possible.
private final ITelephony mInterface;
private SubscriptionManager mSubscriptionManager;
private CarrierConfigManager mCarrierConfigManager;
+ private TelephonyRegistryManager mTelephonyRegistryManager;
private Context mContext;
private enum CcType {
@@ -131,12 +216,56 @@
}
};
+ /**
+ * Map from a shorthand string to the feature tags required in registration required in order
+ * for the RCS feature to be considered "capable".
+ */
+ private static final Map<String, Set<String>> TEST_FEATURE_TAG_MAP;
+ static {
+ ArrayMap<String, Set<String>> map = new ArrayMap<>(18);
+ map.put("chat_v1", Collections.singleton(FeatureTags.FEATURE_TAG_CHAT_IM));
+ map.put("chat_v2", Collections.singleton(FeatureTags.FEATURE_TAG_CHAT_SESSION));
+ map.put("ft", Collections.singleton(FeatureTags.FEATURE_TAG_FILE_TRANSFER));
+ map.put("ft_sms", Collections.singleton(FeatureTags.FEATURE_TAG_FILE_TRANSFER_VIA_SMS));
+ map.put("mmtel", Collections.singleton(FeatureTags.FEATURE_TAG_MMTEL));
+ map.put("mmtel_vt", new ArraySet<>(Arrays.asList(FeatureTags.FEATURE_TAG_MMTEL,
+ FeatureTags.FEATURE_TAG_VIDEO)));
+ map.put("geo_push", Collections.singleton(FeatureTags.FEATURE_TAG_GEO_PUSH));
+ map.put("geo_push_sms", Collections.singleton(FeatureTags.FEATURE_TAG_GEO_PUSH_VIA_SMS));
+ map.put("call_comp",
+ Collections.singleton(FeatureTags.FEATURE_TAG_CALL_COMPOSER_ENRICHED_CALLING));
+ map.put("call_comp_mmtel",
+ Collections.singleton(FeatureTags.FEATURE_TAG_CALL_COMPOSER_VIA_TELEPHONY));
+ map.put("call_post", Collections.singleton(FeatureTags.FEATURE_TAG_POST_CALL));
+ map.put("map", Collections.singleton(FeatureTags.FEATURE_TAG_SHARED_MAP));
+ map.put("sketch", Collections.singleton(FeatureTags.FEATURE_TAG_SHARED_SKETCH));
+ // Feature tags defined twice for chatbot session because we want v1 and v2 based on bot
+ // version
+ map.put("chatbot", new ArraySet<>(Arrays.asList(
+ FeatureTags.FEATURE_TAG_CHATBOT_COMMUNICATION_USING_SESSION,
+ FeatureTags.FEATURE_TAG_CHATBOT_VERSION_SUPPORTED)));
+ map.put("chatbot_v2", new ArraySet<>(Arrays.asList(
+ FeatureTags.FEATURE_TAG_CHATBOT_COMMUNICATION_USING_SESSION,
+ FeatureTags.FEATURE_TAG_CHATBOT_VERSION_SUPPORTED)));
+ map.put("chatbot_sa", new ArraySet<>(Arrays.asList(
+ FeatureTags.FEATURE_TAG_CHATBOT_COMMUNICATION_USING_STANDALONE_MSG,
+ FeatureTags.FEATURE_TAG_CHATBOT_VERSION_SUPPORTED)));
+ map.put("chatbot_sa_v2", new ArraySet<>(Arrays.asList(
+ FeatureTags.FEATURE_TAG_CHATBOT_COMMUNICATION_USING_STANDALONE_MSG,
+ FeatureTags.FEATURE_TAG_CHATBOT_VERSION_SUPPORTED)));
+ map.put("chatbot_role", Collections.singleton(FeatureTags.FEATURE_TAG_CHATBOT_ROLE));
+ TEST_FEATURE_TAG_MAP = Collections.unmodifiableMap(map);
+ }
+
+
public TelephonyShellCommand(ITelephony binder, Context context) {
mInterface = binder;
mCarrierConfigManager =
(CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
mSubscriptionManager = (SubscriptionManager)
context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ mTelephonyRegistryManager = (TelephonyRegistryManager)
+ context.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
mContext = context;
}
@@ -150,6 +279,8 @@
case IMS_SUBCOMMAND: {
return handleImsCommand();
}
+ case RCS_UCE_COMMAND:
+ return handleRcsUceCommand();
case NUMBER_VERIFICATION_SUBCOMMAND:
return handleNumberVerificationCommand();
case EMERGENCY_CALLBACK_MODE:
@@ -163,6 +294,31 @@
return handleDataTestModeCommand();
case END_BLOCK_SUPPRESSION:
return handleEndBlockSuppressionCommand();
+ case GBA_SUBCOMMAND:
+ return handleGbaCommand();
+ case D2D_SUBCOMMAND:
+ return handleD2dCommand();
+ case BARRING_SUBCOMMAND:
+ return handleBarringCommand();
+ case SINGLE_REGISTATION_CONFIG:
+ return handleSingleRegistrationConfigCommand();
+ case RESTART_MODEM:
+ return handleRestartModemCommand();
+ case CALL_COMPOSER_SUBCOMMAND:
+ return handleCallComposerCommand();
+ case UNATTENDED_REBOOT:
+ return handleUnattendedReboot();
+ case HAS_CARRIER_PRIVILEGES_COMMAND:
+ return handleHasCarrierPrivilegesCommand();
+ case THERMAL_MITIGATION_COMMAND:
+ return handleThermalMitigationCommand();
+ case DISABLE_PHYSICAL_SUBSCRIPTION:
+ return handleEnablePhysicalSubscription(false);
+ case ENABLE_PHYSICAL_SUBSCRIPTION:
+ return handleEnablePhysicalSubscription(true);
+ case GET_ALLOWED_NETWORK_TYPES_FOR_USER:
+ case SET_ALLOWED_NETWORK_TYPES_FOR_USER:
+ return handleAllowedNetworkTypesCommand(cmd);
default: {
return handleDefaultCommands(cmd);
}
@@ -177,6 +333,8 @@
pw.println(" Print this help text.");
pw.println(" ims");
pw.println(" IMS Commands.");
+ pw.println(" uce");
+ pw.println(" RCS User Capability Exchange Commands.");
pw.println(" emergency-number-test-mode");
pw.println(" Emergency Number Test Mode Commands.");
pw.println(" end-block-suppression");
@@ -185,11 +343,66 @@
pw.println(" Data Test Mode Commands.");
pw.println(" cc");
pw.println(" Carrier Config Commands.");
+ pw.println(" gba");
+ pw.println(" GBA Commands.");
+ pw.println(" src");
+ 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.");
+ pw.println(" has-carrier-privileges [package]");
+ pw.println(" Query carrier privilege status for a package. Prints true or false.");
+ pw.println(" get-allowed-network-types-for-users");
+ pw.println(" Get the Allowed Network Types.");
+ pw.println(" set-allowed-network-types-for-users");
+ pw.println(" Set the Allowed Network Types.");
onHelpIms();
+ onHelpUce();
onHelpEmergencyNumber();
onHelpEndBlockSupperssion();
onHelpDataTestMode();
onHelpCc();
+ onHelpGba();
+ onHelpSrc();
+ onHelpD2D();
+ onHelpDisableOrEnablePhysicalSubscription();
+ onHelpAllowedNetworkTypes();
+ }
+
+ private void onHelpD2D() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("D2D Comms Commands:");
+ pw.println(" d2d send TYPE VALUE");
+ pw.println(" Sends a D2D message of specified type and value.");
+ pw.println(" Type: " + MESSAGE_CALL_RADIO_ACCESS_TYPE + " - "
+ + Communicator.messageToString(MESSAGE_CALL_RADIO_ACCESS_TYPE));
+ pw.println(" Type: " + MESSAGE_CALL_AUDIO_CODEC + " - " + Communicator.messageToString(
+ MESSAGE_CALL_AUDIO_CODEC));
+ pw.println(" Type: " + MESSAGE_DEVICE_BATTERY_STATE + " - "
+ + Communicator.messageToString(
+ MESSAGE_DEVICE_BATTERY_STATE));
+ pw.println(" Type: " + MESSAGE_DEVICE_NETWORK_COVERAGE + " - "
+ + Communicator.messageToString(MESSAGE_DEVICE_NETWORK_COVERAGE));
+ pw.println(" d2d transport TYPE");
+ pw.println(" Forces the specified D2D transport TYPE to be active. Use the");
+ pw.println(" short class name of the transport; i.e. DtmfTransport or RtpTransport.");
+ pw.println(" d2d set-device-support true/default");
+ pw.println(" true - forces device support to be enabled for D2D.");
+ pw.println(" default - clear any previously set force-enable of D2D, reverting to ");
+ pw.println(" the current device's configuration.");
+ }
+
+ private void onHelpBarring() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Barring Commands:");
+ pw.println(" barring send -s SLOT_ID -b BARRING_TYPE -c IS_CONDITIONALLY_BARRED"
+ + " -t CONDITIONAL_BARRING_TIME_SECS");
+ pw.println(" Notifies of a barring info change for the specified slot id.");
+ pw.println(" BARRING_TYPE: 0 for BARRING_TYPE_NONE");
+ pw.println(" BARRING_TYPE: 1 for BARRING_TYPE_UNCONDITIONAL");
+ pw.println(" BARRING_TYPE: 2 for BARRING_TYPE_CONDITIONAL");
+ pw.println(" BARRING_TYPE: -1 for BARRING_TYPE_UNKNOWN");
}
private void onHelpIms() {
@@ -210,9 +423,14 @@
pw.println(" -s: The SIM slot ID for the registered ImsService. If no option");
pw.println(" is specified, it will choose the default voice SIM slot.");
pw.println(" -c: The ImsService defined as the carrier configured ImsService.");
- pw.println(" -c: The ImsService defined as the device default ImsService.");
+ pw.println(" -d: The ImsService defined as the device default ImsService.");
pw.println(" -f: The feature type that the query will be requested for. If none is");
pw.println(" specified, the returned package name will correspond to MMTEL.");
+ pw.println(" ims clear-ims-service-override [-s SLOT_ID]");
+ pw.println(" Clear all carrier ImsService overrides. This does not work for device ");
+ pw.println(" configuration overrides. Options are:");
+ pw.println(" -s: The SIM slot ID for the registered ImsService. If no option");
+ pw.println(" is specified, it will choose the default voice SIM slot.");
pw.println(" ims enable [-s SLOT_ID]");
pw.println(" enables IMS for the SIM slot specified, or for the default voice SIM slot");
pw.println(" if none is specified.");
@@ -223,6 +441,48 @@
pw.println(" enables or disables handling or network conference event package data.");
}
+ private void onHelpUce() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("User Capability Exchange Commands:");
+ pw.println(" uce get-eab-contact [PHONE_NUMBER]");
+ pw.println(" Get the EAB contacts from the EAB database.");
+ pw.println(" Options are:");
+ pw.println(" PHONE_NUMBER: The phone numbers to be removed from the EAB databases");
+ pw.println(" Expected output format :");
+ pw.println(" [PHONE_NUMBER],[RAW_CONTACT_ID],[CONTACT_ID],[DATA_ID]");
+ pw.println(" uce remove-eab-contact [-s SLOT_ID] [PHONE_NUMBER]");
+ pw.println(" Remove the EAB contacts from the EAB database.");
+ pw.println(" Options are:");
+ pw.println(" -s: The SIM slot ID to read carrier config value for. If no option");
+ pw.println(" is specified, it will choose the default voice SIM slot.");
+ pw.println(" PHONE_NUMBER: The phone numbers to be removed from the EAB databases");
+ pw.println(" uce get-device-enabled");
+ pw.println(" Get the config to check whether the device supports RCS UCE or not.");
+ pw.println(" uce set-device-enabled true|false");
+ pw.println(" Set the device config for RCS User Capability Exchange to the value.");
+ pw.println(" The value could be true, false.");
+ pw.println(" uce override-published-caps [-s SLOT_ID] add|remove|clear [CAPABILITIES]");
+ pw.println(" Override the existing SIP PUBLISH with different capabilities.");
+ pw.println(" Options are:");
+ pw.println(" -s: The SIM slot ID to read carrier config value for. If no option");
+ pw.println(" is specified, it will choose the default voice SIM slot.");
+ pw.println(" add [CAPABILITY]: add a new capability");
+ pw.println(" remove [CAPABILITY]: remove a capability");
+ pw.println(" clear: clear all capability overrides");
+ pw.println(" CAPABILITY: \":\" separated list of capabilities.");
+ pw.println(" Valid options are: [mmtel(_vt), chat_v1, chat_v2, ft, ft_sms,");
+ pw.println(" geo_push, geo_push_sms, call_comp, call_post, map, sketch, chatbot,");
+ pw.println(" chatbot_sa, chatbot_role] as well as full length");
+ pw.println(" featureTag=\"featureValue\" feature tags that are not defined here.");
+ pw.println(" uce get-last-publish-pidf [-s SLOT_ID]");
+ pw.println(" Get the PIDF XML included in the last SIP PUBLISH, or \"none\" if no ");
+ pw.println(" PUBLISH is active");
+ pw.println(" uce remove-request-disallowed-status [-s SLOT_ID]");
+ pw.println(" Remove the UCE is disallowed to execute UCE requests status");
+ pw.println(" uce set-capabilities-request-timeout [-s SLOT_ID] [REQUEST_TIMEOUT_MS]");
+ pw.println(" Set the timeout for contact capabilities request.");
+ }
+
private void onHelpNumberVerification() {
PrintWriter pw = getOutPrintWriter();
pw.println("Number verification commands");
@@ -234,6 +494,25 @@
pw.println(" 1 if the call would have been intercepted, 0 otherwise.");
}
+ private void onHelpThermalMitigation() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Thermal mitigation commands");
+ pw.println(" thermal-mitigation allow-package PACKAGE_NAME");
+ pw.println(" Set the package as one of authorized packages for thermal mitigation.");
+ pw.println(" thermal-mitigation disallow-package PACKAGE_NAME");
+ pw.println(" Remove the package from one of the authorized packages for thermal "
+ + "mitigation.");
+ }
+
+ private void onHelpDisableOrEnablePhysicalSubscription() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Disable or enable a physical subscription");
+ pw.println(" disable-physical-subscription SUB_ID");
+ pw.println(" Disable the physical subscription with the provided subId, if allowed.");
+ pw.println(" enable-physical-subscription SUB_ID");
+ pw.println(" Enable the physical subscription with the provided subId, if allowed.");
+ }
+
private void onHelpDataTestMode() {
PrintWriter pw = getOutPrintWriter();
pw.println("Mobile Data Test Mode Commands:");
@@ -290,6 +569,93 @@
pw.println(" is specified, it will choose the default voice SIM slot.");
}
+ private void onHelpGba() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Gba Commands:");
+ pw.println(" gba set-service [-s SLOT_ID] PACKAGE_NAME");
+ pw.println(" Sets the GbaService defined in PACKAGE_NAME to to be the bound.");
+ pw.println(" Options are:");
+ pw.println(" -s: The SIM slot ID to read carrier config value for. If no option");
+ pw.println(" is specified, it will choose the default voice SIM slot.");
+ pw.println(" gba get-service [-s SLOT_ID]");
+ pw.println(" Gets the package name of the currently defined GbaService.");
+ pw.println(" Options are:");
+ pw.println(" -s: The SIM slot ID to read carrier config value for. If no option");
+ pw.println(" is specified, it will choose the default voice SIM slot.");
+ pw.println(" gba set-release [-s SLOT_ID] n");
+ pw.println(" Sets the time to release/unbind GbaService in n milli-second.");
+ pw.println(" Do not release/unbind if n is -1.");
+ pw.println(" Options are:");
+ pw.println(" -s: The SIM slot ID to read carrier config value for. If no option");
+ pw.println(" is specified, it will choose the default voice SIM slot.");
+ pw.println(" gba get-release [-s SLOT_ID]");
+ pw.println(" Gets the time to release/unbind GbaService in n milli-sencond.");
+ pw.println(" Options are:");
+ pw.println(" -s: The SIM slot ID to read carrier config value for. If no option");
+ pw.println(" is specified, it will choose the default voice SIM slot.");
+ }
+
+ 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).");
+ pw.println(" src get-device-enabled");
+ pw.println(" Gets the device config for RCS VoLTE single registration.");
+ pw.println(" src set-carrier-enabled [-s SLOT_ID] true|false|null");
+ pw.println(" Sets the carrier config for RCS VoLTE single registration to the value.");
+ pw.println(" The value could be true, false, or null(undefined).");
+ pw.println(" Options are:");
+ pw.println(" -s: The SIM slot ID to set the config value for. If no option");
+ pw.println(" is specified, it will choose the default voice SIM slot.");
+ pw.println(" src get-carrier-enabled [-s SLOT_ID]");
+ pw.println(" Gets the carrier config for RCS VoLTE single registration.");
+ pw.println(" Options are:");
+ pw.println(" -s: The SIM slot ID to read the config value for. If no option");
+ pw.println(" is specified, it will choose the default voice SIM slot.");
+ pw.println(" src set-feature-validation [-s SLOT_ID] true|false|null");
+ pw.println(" Sets ims feature validation result.");
+ pw.println(" The value could be true, false, or null(undefined).");
+ pw.println(" Options are:");
+ pw.println(" -s: The SIM slot ID to set the config value for. If no option");
+ pw.println(" is specified, it will choose the default voice SIM slot.");
+ pw.println(" src get-feature-validation [-s SLOT_ID]");
+ pw.println(" Gets ims feature validation override value.");
+ pw.println(" Options are:");
+ pw.println(" -s: The SIM slot ID to read the config value for. If no option");
+ pw.println(" is specified, it will choose the default voice SIM slot.");
+ }
+
+ private void onHelpAllowedNetworkTypes() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Allowed Network Types Commands:");
+ pw.println(" get-allowed-network-types-for-users [-s SLOT_ID]");
+ pw.println(" Print allowed network types value.");
+ pw.println(" Options are:");
+ pw.println(" -s: The SIM slot ID to read allowed network types value for. If no");
+ pw.println(" option is specified, it will choose the default voice SIM slot.");
+ pw.println(" set-allowed-network-types-for-users [-s SLOT_ID] [NETWORK_TYPES_BITMASK]");
+ pw.println(" Sets allowed network types to NETWORK_TYPES_BITMASK.");
+ pw.println(" Options are:");
+ pw.println(" -s: The SIM slot ID to set allowed network types value for. If no");
+ pw.println(" option is specified, it will choose the default voice SIM slot.");
+ pw.println(" NETWORK_TYPES_BITMASK specifies the new network types value and this type");
+ pw.println(" is bitmask in binary format. Reference the NetworkTypeBitMask");
+ pw.println(" at TelephonyManager.java");
+ pw.println(" For example:");
+ pw.println(" NR only : 10000000000000000000");
+ pw.println(" NR|LTE : 11000001000000000000");
+ pw.println(" NR|LTE|CDMA|EVDO|GSM|WCDMA : 11001111101111111111");
+ pw.println(" LTE|CDMA|EVDO|GSM|WCDMA : 01001111101111111111");
+ pw.println(" LTE only : 01000001000000000000");
+ }
+
private int handleImsCommand() {
String arg = getNextArg();
if (arg == null) {
@@ -298,16 +664,19 @@
}
switch (arg) {
- case IMS_SET_CARRIER_SERVICE: {
+ case IMS_SET_IMS_SERVICE: {
return handleImsSetServiceCommand();
}
- case IMS_GET_CARRIER_SERVICE: {
+ case IMS_GET_IMS_SERVICE: {
return handleImsGetServiceCommand();
}
- case IMS_ENABLE: {
+ case IMS_CLEAR_SERVICE_OVERRIDE: {
+ return handleImsClearCarrierServiceCommand();
+ }
+ case ENABLE: {
return handleEnableIms();
}
- case IMS_DISABLE: {
+ case DISABLE: {
return handleDisableIms();
}
case IMS_CEP: {
@@ -326,7 +695,7 @@
return 0;
}
switch (arg) {
- case DATA_ENABLE: {
+ case ENABLE: {
try {
mInterface.enableDataConnectivity();
} catch (RemoteException ex) {
@@ -336,7 +705,7 @@
}
break;
}
- case DATA_DISABLE: {
+ case DISABLE: {
try {
mInterface.disableDataConnectivity();
} catch (RemoteException ex) {
@@ -478,6 +847,259 @@
return -1;
}
+ private boolean subIsEsim(int subId) {
+ SubscriptionInfo info = mSubscriptionManager.getActiveSubscriptionInfo(subId);
+ if (info != null) {
+ return info.isEmbedded();
+ }
+ return false;
+ }
+
+ private int handleEnablePhysicalSubscription(boolean enable) {
+ PrintWriter errPw = getErrPrintWriter();
+ int subId = 0;
+ try {
+ subId = Integer.parseInt(getNextArgRequired());
+ } catch (NumberFormatException e) {
+ errPw.println((enable ? "enable" : "disable")
+ + "-physical-subscription requires an integer as a subId.");
+ return -1;
+ }
+ // 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) {
+ errPw.println("cc: Permission denied.");
+ return -1;
+ }
+ // Verify that the subId represents a physical sub
+ if (subIsEsim(subId)) {
+ errPw.println("SubId " + subId + " is not for a physical subscription");
+ return -1;
+ }
+ Log.d(LOG_TAG, (enable ? "Enabling" : "Disabling")
+ + " physical subscription with subId=" + subId);
+ mSubscriptionManager.setUiccApplicationsEnabled(subId, enable);
+ return 0;
+ }
+
+ private int handleThermalMitigationCommand() {
+ String arg = getNextArg();
+ String packageName = getNextArg();
+ if (arg == null || packageName == null) {
+ onHelpThermalMitigation();
+ return 0;
+ }
+
+ if (!checkShellUid()) {
+ return -1;
+ }
+
+ switch (arg) {
+ case ALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND: {
+ PhoneInterfaceManager.addPackageToThermalMitigationAllowlist(packageName, mContext);
+ return 0;
+ }
+ case DISALLOW_THERMAL_MITIGATION_PACKAGE_SUBCOMMAND: {
+ PhoneInterfaceManager.removePackageFromThermalMitigationAllowlist(packageName,
+ mContext);
+ return 0;
+ }
+ default:
+ onHelpThermalMitigation();
+ }
+
+ return -1;
+
+ }
+
+ private int handleD2dCommand() {
+ String arg = getNextArg();
+ if (arg == null) {
+ onHelpD2D();
+ return 0;
+ }
+
+ switch (arg) {
+ case D2D_SEND: {
+ return handleD2dSendCommand();
+ }
+ case D2D_TRANSPORT: {
+ return handleD2dTransportCommand();
+ }
+ case D2D_SET_DEVICE_SUPPORT: {
+ return handleD2dDeviceSupportedCommand();
+ }
+ }
+
+ return -1;
+ }
+
+ private int handleD2dSendCommand() {
+ PrintWriter errPw = getErrPrintWriter();
+ int messageType = -1;
+ int messageValue = -1;
+
+ String arg = getNextArg();
+ if (arg == null) {
+ onHelpD2D();
+ return 0;
+ }
+ try {
+ messageType = Integer.parseInt(arg);
+ } catch (NumberFormatException e) {
+ errPw.println("message type must be a valid integer");
+ return -1;
+ }
+
+ arg = getNextArg();
+ if (arg == null) {
+ onHelpD2D();
+ return 0;
+ }
+ try {
+ messageValue = Integer.parseInt(arg);
+ } catch (NumberFormatException e) {
+ errPw.println("message value must be a valid integer");
+ return -1;
+ }
+
+ try {
+ mInterface.sendDeviceToDeviceMessage(messageType, messageValue);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "d2d send error: " + e.getMessage());
+ errPw.println("Exception: " + e.getMessage());
+ return -1;
+ }
+
+ return 0;
+ }
+
+ private int handleD2dTransportCommand() {
+ PrintWriter errPw = getErrPrintWriter();
+
+ String arg = getNextArg();
+ if (arg == null) {
+ onHelpD2D();
+ return 0;
+ }
+
+ try {
+ mInterface.setActiveDeviceToDeviceTransport(arg);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "d2d transport error: " + e.getMessage());
+ errPw.println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+ private int handleBarringCommand() {
+ String arg = getNextArg();
+ if (arg == null) {
+ onHelpBarring();
+ return 0;
+ }
+
+ switch (arg) {
+ case BARRING_SEND_INFO: {
+ return handleBarringSendCommand();
+ }
+ }
+ return -1;
+ }
+
+ private int handleBarringSendCommand() {
+ PrintWriter errPw = getErrPrintWriter();
+ int slotId = getDefaultSlot();
+ int subId = SubscriptionManager.getSubId(slotId)[0];
+ @BarringInfo.BarringServiceInfo.BarringType int barringType =
+ BarringInfo.BarringServiceInfo.BARRING_TYPE_UNCONDITIONAL;
+ boolean isConditionallyBarred = false;
+ int conditionalBarringTimeSeconds = 0;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-s": {
+ try {
+ slotId = Integer.parseInt(getNextArgRequired());
+ subId = SubscriptionManager.getSubId(slotId)[0];
+ } catch (NumberFormatException e) {
+ errPw.println("barring send requires an integer as a SLOT_ID.");
+ return -1;
+ }
+ break;
+ }
+ case "-b": {
+ try {
+ barringType = Integer.parseInt(getNextArgRequired());
+ if (barringType < -1 || barringType > 2) {
+ throw new NumberFormatException();
+ }
+
+ } catch (NumberFormatException e) {
+ errPw.println("barring send requires an integer in range [-1,2] as "
+ + "a BARRING_TYPE.");
+ return -1;
+ }
+ break;
+ }
+ case "-c": {
+ try {
+ isConditionallyBarred = Boolean.parseBoolean(getNextArgRequired());
+ } catch (Exception e) {
+ errPw.println("barring send requires a boolean after -c indicating"
+ + " conditional barring");
+ return -1;
+ }
+ break;
+ }
+ case "-t": {
+ try {
+ conditionalBarringTimeSeconds = Integer.parseInt(getNextArgRequired());
+ } catch (NumberFormatException e) {
+ errPw.println("barring send requires an integer for time of barring"
+ + " in seconds after -t for conditional barring");
+ return -1;
+ }
+ break;
+ }
+ }
+ }
+ SparseArray<BarringInfo.BarringServiceInfo> barringServiceInfos = new SparseArray<>();
+ BarringInfo.BarringServiceInfo bsi = new BarringInfo.BarringServiceInfo(
+ barringType, isConditionallyBarred, 0, conditionalBarringTimeSeconds);
+ barringServiceInfos.append(0, bsi);
+ BarringInfo barringInfo = new BarringInfo(null, barringServiceInfos);
+ try {
+ mTelephonyRegistryManager.notifyBarringInfoChanged(slotId, subId, barringInfo);
+ } catch (Exception e) {
+ Log.w(LOG_TAG, "barring send error: " + e.getMessage());
+ errPw.println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
+ private int handleD2dDeviceSupportedCommand() {
+ PrintWriter errPw = getErrPrintWriter();
+
+ String arg = getNextArg();
+ if (arg == null) {
+ onHelpD2D();
+ return 0;
+ }
+
+ boolean isEnabled = "true".equals(arg.toLowerCase());
+ try {
+ mInterface.setDeviceToDeviceForceEnabled(isEnabled);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Error forcing D2D enabled: " + e.getMessage());
+ errPw.println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
// ims set-ims-service
private int handleImsSetServiceCommand() {
PrintWriter errPw = getErrPrintWriter();
@@ -563,6 +1185,42 @@
return 0;
}
+ // ims clear-ims-service-override
+ private int handleImsClearCarrierServiceCommand() {
+ PrintWriter errPw = getErrPrintWriter();
+ int slotId = getDefaultSlot();
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-s": {
+ try {
+ slotId = Integer.parseInt(getNextArgRequired());
+ } catch (NumberFormatException e) {
+ errPw.println("ims set-ims-service requires an integer as a SLOT_ID.");
+ return -1;
+ }
+ break;
+ }
+ }
+ }
+
+ try {
+ boolean result = mInterface.clearCarrierImsServiceOverride(slotId);
+ if (VDBG) {
+ Log.v(LOG_TAG, "ims clear-ims-service-override -s " + slotId
+ + ", result=" + result);
+ }
+ getOutPrintWriter().println(result);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "ims clear-ims-service-override -s " + slotId
+ + ", error" + e.getMessage());
+ errPw.println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
// ims get-ims-service
private int handleImsGetServiceCommand() {
PrintWriter errPw = getErrPrintWriter();
@@ -1217,4 +1875,854 @@
}
return 0;
}
+
+ private int handleRestartModemCommand() {
+ // 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("RestartModem: Permission denied.");
+ return -1;
+ }
+
+ boolean result = TelephonyManager.getDefault().rebootRadio();
+ getOutPrintWriter().println(result);
+
+ 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) {
+ onHelpGba();
+ return 0;
+ }
+
+ switch (arg) {
+ case GBA_SET_SERVICE: {
+ return handleGbaSetServiceCommand();
+ }
+ case GBA_GET_SERVICE: {
+ return handleGbaGetServiceCommand();
+ }
+ case GBA_SET_RELEASE_TIME: {
+ return handleGbaSetReleaseCommand();
+ }
+ case GBA_GET_RELEASE_TIME: {
+ return handleGbaGetReleaseCommand();
+ }
+ }
+
+ return -1;
+ }
+
+ private int getSubId(String cmd) {
+ int slotId = getDefaultSlot();
+ String opt = getNextOption();
+ if (opt != null && opt.equals("-s")) {
+ try {
+ slotId = Integer.parseInt(getNextArgRequired());
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println(cmd + " requires an integer as a SLOT_ID.");
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
+ }
+ int[] subIds = SubscriptionManager.getSubId(slotId);
+ return subIds[0];
+ }
+
+ private int handleGbaSetServiceCommand() {
+ int subId = getSubId("gba set-service");
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ return -1;
+ }
+
+ String packageName = getNextArg();
+ try {
+ if (packageName == null) {
+ packageName = "";
+ }
+ boolean result = mInterface.setBoundGbaServiceOverride(subId, packageName);
+ if (VDBG) {
+ Log.v(LOG_TAG, "gba set-service -s " + subId + " "
+ + packageName + ", result=" + result);
+ }
+ getOutPrintWriter().println(result);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "gba set-service " + subId + " "
+ + packageName + ", error" + e.getMessage());
+ getErrPrintWriter().println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
+ private int handleGbaGetServiceCommand() {
+ String result;
+
+ int subId = getSubId("gba get-service");
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ return -1;
+ }
+
+ try {
+ result = mInterface.getBoundGbaService(subId);
+ } catch (RemoteException e) {
+ return -1;
+ }
+ if (VDBG) {
+ Log.v(LOG_TAG, "gba get-service -s " + subId + ", returned: " + result);
+ }
+ getOutPrintWriter().println(result);
+ return 0;
+ }
+
+ private int handleGbaSetReleaseCommand() {
+ //the release time value could be -1
+ int subId = getRemainingArgsCount() > 1 ? getSubId("gba set-release")
+ : SubscriptionManager.getDefaultSubscriptionId();
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ return -1;
+ }
+
+ String intervalStr = getNextArg();
+ if (intervalStr == null) {
+ return -1;
+ }
+
+ try {
+ int interval = Integer.parseInt(intervalStr);
+ boolean result = mInterface.setGbaReleaseTimeOverride(subId, interval);
+ if (VDBG) {
+ Log.v(LOG_TAG, "gba set-release -s " + subId + " "
+ + intervalStr + ", result=" + result);
+ }
+ getOutPrintWriter().println(result);
+ } catch (NumberFormatException | RemoteException e) {
+ Log.w(LOG_TAG, "gba set-release -s " + subId + " "
+ + intervalStr + ", error" + e.getMessage());
+ getErrPrintWriter().println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
+ private int handleGbaGetReleaseCommand() {
+ int subId = getSubId("gba get-release");
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ return -1;
+ }
+
+ int result = 0;
+ try {
+ result = mInterface.getGbaReleaseTime(subId);
+ } catch (RemoteException e) {
+ return -1;
+ }
+ if (VDBG) {
+ Log.v(LOG_TAG, "gba get-release -s " + subId + ", returned: " + result);
+ }
+ getOutPrintWriter().println(result);
+ return 0;
+ }
+
+ private int handleSingleRegistrationConfigCommand() {
+ String arg = getNextArg();
+ if (arg == null) {
+ onHelpSrc();
+ return 0;
+ }
+
+ switch (arg) {
+ case SRC_SET_TEST_ENABLED: {
+ return handleSrcSetTestEnabledCommand();
+ }
+ case SRC_GET_TEST_ENABLED: {
+ return handleSrcGetTestEnabledCommand();
+ }
+ case SRC_SET_DEVICE_ENABLED: {
+ return handleSrcSetDeviceEnabledCommand();
+ }
+ case SRC_GET_DEVICE_ENABLED: {
+ return handleSrcGetDeviceEnabledCommand();
+ }
+ case SRC_SET_CARRIER_ENABLED: {
+ return handleSrcSetCarrierEnabledCommand();
+ }
+ case SRC_GET_CARRIER_ENABLED: {
+ return handleSrcGetCarrierEnabledCommand();
+ }
+ case SRC_SET_FEATURE_ENABLED: {
+ return handleSrcSetFeatureValidationCommand();
+ }
+ case SRC_GET_FEATURE_ENABLED: {
+ return handleSrcGetFeatureValidationCommand();
+ }
+ }
+
+ return -1;
+ }
+
+ private int handleRcsUceCommand() {
+ String arg = getNextArg();
+ if (arg == null) {
+ onHelpUce();
+ return 0;
+ }
+
+ switch (arg) {
+ case UCE_REMOVE_EAB_CONTACT:
+ return handleRemovingEabContactCommand();
+ case UCE_GET_EAB_CONTACT:
+ return handleGettingEabContactCommand();
+ case UCE_GET_EAB_CAPABILITY:
+ return handleGettingEabCapabilityCommand();
+ case UCE_GET_DEVICE_ENABLED:
+ return handleUceGetDeviceEnabledCommand();
+ case UCE_SET_DEVICE_ENABLED:
+ return handleUceSetDeviceEnabledCommand();
+ case UCE_OVERRIDE_PUBLISH_CAPS:
+ return handleUceOverridePublishCaps();
+ case UCE_GET_LAST_PIDF_XML:
+ return handleUceGetPidfXml();
+ case UCE_REMOVE_REQUEST_DISALLOWED_STATUS:
+ return handleUceRemoveRequestDisallowedStatus();
+ case UCE_SET_CAPABILITY_REQUEST_TIMEOUT:
+ return handleUceSetCapRequestTimeout();
+ }
+ return -1;
+ }
+
+ private int handleRemovingEabContactCommand() {
+ int subId = getSubId("uce remove-eab-contact");
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ return -1;
+ }
+
+ String phoneNumber = getNextArgRequired();
+ if (TextUtils.isEmpty(phoneNumber)) {
+ return -1;
+ }
+ int result = 0;
+ try {
+ result = mInterface.removeContactFromEab(subId, phoneNumber);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "uce remove-eab-contact -s " + subId + ", error " + e.getMessage());
+ getErrPrintWriter().println("Exception: " + e.getMessage());
+ return -1;
+ }
+
+ if (VDBG) {
+ Log.v(LOG_TAG, "uce remove-eab-contact -s " + subId + ", result: " + result);
+ }
+ return 0;
+ }
+
+ private int handleGettingEabContactCommand() {
+ String phoneNumber = getNextArgRequired();
+ if (TextUtils.isEmpty(phoneNumber)) {
+ return -1;
+ }
+ String result = "";
+ try {
+ result = mInterface.getContactFromEab(phoneNumber);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "uce get-eab-contact, error " + e.getMessage());
+ getErrPrintWriter().println("Exception: " + e.getMessage());
+ return -1;
+ }
+
+ if (VDBG) {
+ Log.v(LOG_TAG, "uce get-eab-contact, result: " + result);
+ }
+ getOutPrintWriter().println(result);
+ return 0;
+ }
+
+ private int handleGettingEabCapabilityCommand() {
+ String phoneNumber = getNextArgRequired();
+ if (TextUtils.isEmpty(phoneNumber)) {
+ return -1;
+ }
+ String result = "";
+ try {
+ result = mInterface.getCapabilityFromEab(phoneNumber);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "uce get-eab-capability, error " + e.getMessage());
+ getErrPrintWriter().println("Exception: " + e.getMessage());
+ return -1;
+ }
+
+ if (VDBG) {
+ Log.v(LOG_TAG, "uce get-eab-capability, result: " + result);
+ }
+ getOutPrintWriter().println(result);
+ return 0;
+ }
+
+ private int handleUceGetDeviceEnabledCommand() {
+ boolean result = false;
+ try {
+ result = mInterface.getDeviceUceEnabled();
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "uce get-device-enabled, error " + e.getMessage());
+ return -1;
+ }
+ if (VDBG) {
+ Log.v(LOG_TAG, "uce get-device-enabled, returned: " + result);
+ }
+ getOutPrintWriter().println(result);
+ return 0;
+ }
+
+ private int handleUceSetDeviceEnabledCommand() {
+ String enabledStr = getNextArg();
+ if (TextUtils.isEmpty(enabledStr)) {
+ return -1;
+ }
+
+ try {
+ boolean isEnabled = Boolean.parseBoolean(enabledStr);
+ mInterface.setDeviceUceEnabled(isEnabled);
+ if (VDBG) {
+ Log.v(LOG_TAG, "uce set-device-enabled " + enabledStr + ", done");
+ }
+ } catch (NumberFormatException | RemoteException e) {
+ Log.w(LOG_TAG, "uce set-device-enabled " + enabledStr + ", error " + e.getMessage());
+ getErrPrintWriter().println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
+ private int handleUceRemoveRequestDisallowedStatus() {
+ int subId = getSubId("uce remove-request-disallowed-status");
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ Log.w(LOG_TAG, "uce remove-request-disallowed-status, Invalid subscription ID");
+ return -1;
+ }
+ boolean result;
+ try {
+ result = mInterface.removeUceRequestDisallowedStatus(subId);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "uce remove-request-disallowed-status, error " + e.getMessage());
+ return -1;
+ }
+ if (VDBG) {
+ Log.v(LOG_TAG, "uce remove-request-disallowed-status, returned: " + result);
+ }
+ getOutPrintWriter().println(result);
+ return 0;
+ }
+
+ private int handleUceSetCapRequestTimeout() {
+ int subId = getSubId("uce set-capabilities-request-timeout");
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ Log.w(LOG_TAG, "uce set-capabilities-request-timeout, Invalid subscription ID");
+ return -1;
+ }
+ long timeoutAfterMs = Long.valueOf(getNextArg());
+ boolean result;
+ try {
+ result = mInterface.setCapabilitiesRequestTimeout(subId, timeoutAfterMs);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "uce set-capabilities-request-timeout, error " + e.getMessage());
+ return -1;
+ }
+ if (VDBG) {
+ Log.v(LOG_TAG, "uce set-capabilities-request-timeout, returned: " + result);
+ }
+ getOutPrintWriter().println(result);
+ 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 handleUceOverridePublishCaps() {
+ int subId = getSubId("uce override-published-caps");
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ return -1;
+ }
+ //uce override-published-caps [-s SLOT_ID] add|remove|clear|list [CAPABILITIES]
+ String operation = getNextArgRequired();
+ String caps = getNextArg();
+ if (!"add".equals(operation) && !"remove".equals(operation) && !"clear".equals(operation)
+ && !"list".equals(operation)) {
+ getErrPrintWriter().println("Invalid operation: " + operation);
+ return -1;
+ }
+
+ // add/remove requires capabilities to be specified.
+ if ((!"clear".equals(operation) && !"list".equals(operation)) && TextUtils.isEmpty(caps)) {
+ getErrPrintWriter().println("\"" + operation + "\" requires capabilities to be "
+ + "specified");
+ return -1;
+ }
+
+ ArraySet<String> capSet = new ArraySet<>();
+ if (!TextUtils.isEmpty(caps)) {
+ String[] capArray = caps.split(":");
+ for (String cap : capArray) {
+ // Allow unknown tags to be passed in as well.
+ capSet.addAll(TEST_FEATURE_TAG_MAP.getOrDefault(cap, Collections.singleton(cap)));
+ }
+ }
+
+ RcsContactUceCapability result = null;
+ try {
+ switch (operation) {
+ case "add":
+ result = mInterface.addUceRegistrationOverrideShell(subId,
+ new ArrayList<>(capSet));
+ break;
+ case "remove":
+ result = mInterface.removeUceRegistrationOverrideShell(subId,
+ new ArrayList<>(capSet));
+ break;
+ case "clear":
+ result = mInterface.clearUceRegistrationOverrideShell(subId);
+ break;
+ case "list":
+ result = mInterface.getLatestRcsContactUceCapabilityShell(subId);
+ break;
+ }
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "uce override-published-caps, error " + e.getMessage());
+ getErrPrintWriter().println("Exception: " + e.getMessage());
+ return -1;
+ } catch (ServiceSpecificException sse) {
+ // Reconstruct ImsException
+ ImsException imsException = new ImsException(sse.getMessage(), sse.errorCode);
+ Log.w(LOG_TAG, "uce override-published-caps, error " + imsException);
+ getErrPrintWriter().println("Exception: " + imsException);
+ return -1;
+ }
+ if (result == null) {
+ getErrPrintWriter().println("Service not available");
+ return -1;
+ }
+ getOutPrintWriter().println(result);
+ return 0;
+ }
+
+ private int handleUceGetPidfXml() {
+ int subId = getSubId("uce get-last-publish-pidf");
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ return -1;
+ }
+
+ String result;
+ try {
+ result = mInterface.getLastUcePidfXmlShell(subId);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "uce get-last-publish-pidf, error " + e.getMessage());
+ getErrPrintWriter().println("Exception: " + e.getMessage());
+ return -1;
+ } catch (ServiceSpecificException sse) {
+ // Reconstruct ImsException
+ ImsException imsException = new ImsException(sse.getMessage(), sse.errorCode);
+ Log.w(LOG_TAG, "uce get-last-publish-pidf error " + imsException);
+ getErrPrintWriter().println("Exception: " + imsException);
+ return -1;
+ }
+ if (result == null) {
+ getErrPrintWriter().println("Service not available");
+ return -1;
+ }
+ getOutPrintWriter().println(result);
+ return 0;
+ }
+
+ private int handleSrcSetDeviceEnabledCommand() {
+ String enabledStr = getNextArg();
+ if (enabledStr == null) {
+ return -1;
+ }
+
+ try {
+ mInterface.setDeviceSingleRegistrationEnabledOverride(enabledStr);
+ if (VDBG) {
+ Log.v(LOG_TAG, "src set-device-enabled " + enabledStr + ", done");
+ }
+ getOutPrintWriter().println("Done");
+ } catch (NumberFormatException | RemoteException e) {
+ Log.w(LOG_TAG, "src set-device-enabled " + enabledStr + ", error" + e.getMessage());
+ getErrPrintWriter().println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
+ private int handleSrcGetDeviceEnabledCommand() {
+ boolean result = false;
+ try {
+ result = mInterface.getDeviceSingleRegistrationEnabled();
+ } catch (RemoteException e) {
+ return -1;
+ }
+ if (VDBG) {
+ Log.v(LOG_TAG, "src get-device-enabled, returned: " + result);
+ }
+ getOutPrintWriter().println(result);
+ return 0;
+ }
+
+ private int handleSrcSetCarrierEnabledCommand() {
+ //the release time value could be -1
+ int subId = getRemainingArgsCount() > 1 ? getSubId("src set-carrier-enabled")
+ : SubscriptionManager.getDefaultSubscriptionId();
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ return -1;
+ }
+
+ String enabledStr = getNextArg();
+ if (enabledStr == null) {
+ return -1;
+ }
+
+ try {
+ boolean result =
+ mInterface.setCarrierSingleRegistrationEnabledOverride(subId, enabledStr);
+ if (VDBG) {
+ Log.v(LOG_TAG, "src set-carrier-enabled -s " + subId + " "
+ + enabledStr + ", result=" + result);
+ }
+ getOutPrintWriter().println(result);
+ } catch (NumberFormatException | RemoteException e) {
+ Log.w(LOG_TAG, "src set-carrier-enabled -s " + subId + " "
+ + enabledStr + ", error" + e.getMessage());
+ getErrPrintWriter().println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
+ private int handleSrcGetCarrierEnabledCommand() {
+ int subId = getSubId("src get-carrier-enabled");
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ return -1;
+ }
+
+ boolean result = false;
+ try {
+ result = mInterface.getCarrierSingleRegistrationEnabled(subId);
+ } catch (RemoteException e) {
+ return -1;
+ }
+ if (VDBG) {
+ Log.v(LOG_TAG, "src get-carrier-enabled -s " + subId + ", returned: " + result);
+ }
+ getOutPrintWriter().println(result);
+ return 0;
+ }
+
+ private int handleSrcSetFeatureValidationCommand() {
+ //the release time value could be -1
+ int subId = getRemainingArgsCount() > 1 ? getSubId("src set-feature-validation")
+ : SubscriptionManager.getDefaultSubscriptionId();
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ return -1;
+ }
+
+ String enabledStr = getNextArg();
+ if (enabledStr == null) {
+ return -1;
+ }
+
+ try {
+ boolean result =
+ mInterface.setImsFeatureValidationOverride(subId, enabledStr);
+ if (VDBG) {
+ Log.v(LOG_TAG, "src set-feature-validation -s " + subId + " "
+ + enabledStr + ", result=" + result);
+ }
+ getOutPrintWriter().println(result);
+ } catch (NumberFormatException | RemoteException e) {
+ Log.w(LOG_TAG, "src set-feature-validation -s " + subId + " "
+ + enabledStr + ", error" + e.getMessage());
+ getErrPrintWriter().println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
+ private int handleSrcGetFeatureValidationCommand() {
+ int subId = getSubId("src get-feature-validation");
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ return -1;
+ }
+
+ Boolean result = false;
+ try {
+ result = mInterface.getImsFeatureValidationOverride(subId);
+ } catch (RemoteException e) {
+ return -1;
+ }
+ if (VDBG) {
+ Log.v(LOG_TAG, "src get-feature-validation -s " + subId + ", returned: " + result);
+ }
+ getOutPrintWriter().println(result);
+ return 0;
+ }
+
+
+ private void onHelpCallComposer() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Call composer commands");
+ pw.println(" callcomposer test-mode enable|disable|query");
+ pw.println(" Enables or disables test mode for call composer. In test mode, picture");
+ pw.println(" upload/download from carrier servers is disabled, and operations are");
+ pw.println(" performed using emulated local files instead.");
+ pw.println(" callcomposer simulate-outgoing-call [subId] [UUID]");
+ pw.println(" Simulates an outgoing call being placed with the picture ID as");
+ pw.println(" the provided UUID. This triggers storage to the call log.");
+ pw.println(" callcomposer user-setting [subId] enable|disable|query");
+ pw.println(" Enables or disables the user setting for call composer, as set by");
+ pw.println(" TelephonyManager#setCallComposerStatus.");
+ }
+
+ private int handleCallComposerCommand() {
+ String arg = getNextArg();
+ if (arg == null) {
+ onHelpCallComposer();
+ return 0;
+ }
+
+ mContext.enforceCallingPermission(Manifest.permission.MODIFY_PHONE_STATE,
+ "MODIFY_PHONE_STATE required for call composer shell cmds");
+ switch (arg) {
+ case CALL_COMPOSER_TEST_MODE: {
+ String enabledStr = getNextArg();
+ if (ENABLE.equals(enabledStr)) {
+ CallComposerPictureManager.sTestMode = true;
+ } else if (DISABLE.equals(enabledStr)) {
+ CallComposerPictureManager.sTestMode = false;
+ } else if (QUERY.equals(enabledStr)) {
+ getOutPrintWriter().println(CallComposerPictureManager.sTestMode);
+ } else {
+ onHelpCallComposer();
+ return 1;
+ }
+ break;
+ }
+ case CALL_COMPOSER_SIMULATE_CALL: {
+ int subscriptionId = Integer.valueOf(getNextArg());
+ String uuidString = getNextArg();
+ UUID uuid = UUID.fromString(uuidString);
+ CompletableFuture<Uri> storageUriFuture = new CompletableFuture<>();
+ Binder.withCleanCallingIdentity(() -> {
+ CallComposerPictureManager.getInstance(mContext, subscriptionId)
+ .storeUploadedPictureToCallLog(uuid, storageUriFuture::complete);
+ });
+ try {
+ Uri uri = storageUriFuture.get();
+ getOutPrintWriter().println(String.valueOf(uri));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ break;
+ }
+ case CALL_COMPOSER_USER_SETTING: {
+ try {
+ int subscriptionId = Integer.valueOf(getNextArg());
+ String enabledStr = getNextArg();
+ if (ENABLE.equals(enabledStr)) {
+ mInterface.setCallComposerStatus(subscriptionId,
+ TelephonyManager.CALL_COMPOSER_STATUS_ON);
+ } else if (DISABLE.equals(enabledStr)) {
+ mInterface.setCallComposerStatus(subscriptionId,
+ TelephonyManager.CALL_COMPOSER_STATUS_OFF);
+ } else if (QUERY.equals(enabledStr)) {
+ getOutPrintWriter().println(mInterface.getCallComposerStatus(subscriptionId)
+ == TelephonyManager.CALL_COMPOSER_STATUS_ON);
+ } else {
+ onHelpCallComposer();
+ return 1;
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace(getOutPrintWriter());
+ return 1;
+ }
+ break;
+ }
+ }
+ return 0;
+ }
+
+ private int handleHasCarrierPrivilegesCommand() {
+ String packageName = getNextArgRequired();
+
+ boolean hasCarrierPrivileges;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ hasCarrierPrivileges =
+ mInterface.checkCarrierPrivilegesForPackageAnyPhone(packageName)
+ == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, HAS_CARRIER_PRIVILEGES_COMMAND + " exception", e);
+ getErrPrintWriter().println("Exception: " + e.getMessage());
+ return -1;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ getOutPrintWriter().println(hasCarrierPrivileges);
+ return 0;
+ }
+
+ private int handleAllowedNetworkTypesCommand(String command) {
+ if (!checkShellUid()) {
+ return -1;
+ }
+
+ PrintWriter errPw = getErrPrintWriter();
+ String tag = command + ": ";
+ String opt;
+ int subId = -1;
+ Log.v(LOG_TAG, command + " start");
+
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("-s")) {
+ try {
+ subId = slotStringToSubId(tag, getNextArgRequired());
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ errPw.println(tag + "No valid subscription found.");
+ return -1;
+ }
+ } catch (IllegalArgumentException e) {
+ // Missing slot id
+ errPw.println(tag + "SLOT_ID expected after -s.");
+ return -1;
+ }
+ } else {
+ errPw.println(tag + "Unknown option " + opt);
+ return -1;
+ }
+ }
+
+ if (GET_ALLOWED_NETWORK_TYPES_FOR_USER.equals(command)) {
+ return handleGetAllowedNetworkTypesCommand(subId);
+ }
+ if (SET_ALLOWED_NETWORK_TYPES_FOR_USER.equals(command)) {
+ return handleSetAllowedNetworkTypesCommand(subId);
+ }
+ return -1;
+ }
+
+ private int handleGetAllowedNetworkTypesCommand(int subId) {
+ PrintWriter errPw = getErrPrintWriter();
+
+ long result = -1;
+ try {
+ if (mInterface != null) {
+ result = mInterface.getAllowedNetworkTypesForReason(subId,
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "getAllowedNetworkTypesForReason RemoteException" + e);
+ errPw.println(GET_ALLOWED_NETWORK_TYPES_FOR_USER + "RemoteException " + e);
+ return -1;
+ }
+
+ getOutPrintWriter().println(TelephonyManager.convertNetworkTypeBitmaskToString(result));
+ return 0;
+ }
+
+ private int handleSetAllowedNetworkTypesCommand(int subId) {
+ PrintWriter errPw = getErrPrintWriter();
+
+ String bitmaskString = getNextArg();
+ if (TextUtils.isEmpty(bitmaskString)) {
+ errPw.println(SET_ALLOWED_NETWORK_TYPES_FOR_USER + " No NETWORK_TYPES_BITMASK");
+ return -1;
+ }
+ long allowedNetworkTypes = convertNetworkTypeBitmaskFromStringToLong(bitmaskString);
+ if (allowedNetworkTypes < 0) {
+ errPw.println(SET_ALLOWED_NETWORK_TYPES_FOR_USER + " No valid NETWORK_TYPES_BITMASK");
+ return -1;
+ }
+ boolean result = false;
+ try {
+ if (mInterface != null) {
+ result = mInterface.setAllowedNetworkTypesForReason(subId,
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER, allowedNetworkTypes);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "setAllowedNetworkTypesForReason RemoteException" + e);
+ errPw.println(SET_ALLOWED_NETWORK_TYPES_FOR_USER + " RemoteException " + e);
+ return -1;
+ }
+
+ String resultMessage = SET_ALLOWED_NETWORK_TYPES_FOR_USER + " failed";
+ if (result) {
+ resultMessage = SET_ALLOWED_NETWORK_TYPES_FOR_USER + " completed";
+ }
+ getOutPrintWriter().println(resultMessage);
+ return 0;
+ }
+
+ private long convertNetworkTypeBitmaskFromStringToLong(String bitmaskString) {
+ if (TextUtils.isEmpty(bitmaskString)) {
+ return -1;
+ }
+ if (VDBG) {
+ Log.v(LOG_TAG, "AllowedNetworkTypes:" + bitmaskString
+ + ", length: " + bitmaskString.length());
+ }
+ try {
+ return Long.parseLong(bitmaskString, 2);
+ } catch (NumberFormatException e) {
+ Log.e(LOG_TAG, "AllowedNetworkTypes: " + e);
+ return -1;
+ }
+ }
}
diff --git a/src/com/android/phone/TimeConsumingPreferenceActivity.java b/src/com/android/phone/TimeConsumingPreferenceActivity.java
index 3b5fe21..8c5ae6d 100644
--- a/src/com/android/phone/TimeConsumingPreferenceActivity.java
+++ b/src/com/android/phone/TimeConsumingPreferenceActivity.java
@@ -187,11 +187,6 @@
@Override
public void onError(Preference preference, int error) {
if (DBG) dumpState();
- if (!preference.isEnabled()) {
- Log.i(LOG_TAG, "onError, skipped duplicated error popup");
- return;
- }
-
Log.i(LOG_TAG, "onError, preference=" + preference.getKey() + ", error=" + error);
if (mIsForeground) {
diff --git a/src/com/android/phone/callcomposer/CallComposerPictureManager.java b/src/com/android/phone/callcomposer/CallComposerPictureManager.java
new file mode 100644
index 0000000..efb149e
--- /dev/null
+++ b/src/com/android/phone/callcomposer/CallComposerPictureManager.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2020 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.phone.callcomposer;
+
+import android.content.Context;
+import android.location.Location;
+import android.net.Uri;
+import android.os.OutcomeReceiver;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.provider.CallLog;
+import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
+import android.telephony.gba.UaSecurityProtocolIdentifier;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.phone.R;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+public class CallComposerPictureManager {
+ private static final String TAG = CallComposerPictureManager.class.getSimpleName();
+ private static final SparseArray<CallComposerPictureManager> sInstances = new SparseArray<>();
+ private static final String THREE_GPP_BOOTSTRAPPING = "3GPP-bootstrapping";
+
+ public static CallComposerPictureManager getInstance(Context context, int subscriptionId) {
+ synchronized (sInstances) {
+ if (sExecutorService == null) {
+ sExecutorService = Executors.newSingleThreadScheduledExecutor();
+ }
+ if (!sInstances.contains(subscriptionId)) {
+ sInstances.put(subscriptionId,
+ new CallComposerPictureManager(context, subscriptionId));
+ }
+ return sInstances.get(subscriptionId);
+ }
+ }
+
+ @VisibleForTesting
+ public static void clearInstances() {
+ synchronized (sInstances) {
+ sInstances.clear();
+ if (sExecutorService != null) {
+ sExecutorService.shutdown();
+ sExecutorService = null;
+ }
+ }
+ }
+
+ // disabled provisionally until the auth stack is fully operational
+ @VisibleForTesting
+ public static boolean sTestMode = false;
+ public static final String FAKE_SERVER_URL = "https://example.com/FAKE.png";
+ public static final String FAKE_SUBJECT = "This is a test call subject";
+ public static final Location FAKE_LOCATION = new Location("");
+ static {
+ // Meteor Crater, AZ
+ FAKE_LOCATION.setLatitude(35.027526);
+ FAKE_LOCATION.setLongitude(-111.021696);
+ }
+
+ public interface CallLogProxy {
+ default void storeCallComposerPictureAsUser(Context context,
+ UserHandle user,
+ InputStream input,
+ Executor executor,
+ OutcomeReceiver<Uri, CallLog.CallComposerLoggingException> callback) {
+ CallLog.storeCallComposerPicture(context.createContextAsUser(user, 0),
+ input, executor, callback);
+ }
+ }
+
+ private static ScheduledExecutorService sExecutorService = null;
+
+ private final HashMap<UUID, String> mCachedServerUrls = new HashMap<>();
+ private final HashMap<UUID, ImageData> mCachedImages = new HashMap<>();
+ private GbaCredentials mCachedCredentials = null;
+ private final int mSubscriptionId;
+ private final TelephonyManager mTelephonyManager;
+ private final Context mContext;
+ private CallLogProxy mCallLogProxy = new CallLogProxy() {};
+
+ private CallComposerPictureManager(Context context, int subscriptionId) {
+ mContext = context;
+ mSubscriptionId = subscriptionId;
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class)
+ .createForSubscriptionId(mSubscriptionId);
+ }
+
+ public void handleUploadToServer(CallComposerPictureTransfer.Factory transferFactory,
+ ImageData imageData, Consumer<Pair<UUID, Integer>> callback) {
+ if (sTestMode) {
+ UUID id = UUID.randomUUID();
+ mCachedImages.put(id, imageData);
+ mCachedServerUrls.put(id, FAKE_SERVER_URL);
+ callback.accept(Pair.create(id, TelephonyManager.CallComposerException.SUCCESS));
+ return;
+ }
+
+ PersistableBundle carrierConfig = mTelephonyManager.getCarrierConfig();
+ String uploadUrl = carrierConfig.getString(
+ CarrierConfigManager.KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING);
+ if (TextUtils.isEmpty(uploadUrl)) {
+ Log.e(TAG, "Call composer upload URL not configured in carrier config");
+ callback.accept(Pair.create(null,
+ TelephonyManager.CallComposerException.ERROR_UNKNOWN));
+ }
+ UUID id = UUID.randomUUID();
+ imageData.setId(id.toString());
+
+ CallComposerPictureTransfer transfer = transferFactory.create(mContext,
+ mSubscriptionId, uploadUrl, sExecutorService);
+
+ AtomicBoolean hasRetried = new AtomicBoolean(false);
+ transfer.setCallback(new CallComposerPictureTransfer.PictureCallback() {
+ @Override
+ public void onError(int error) {
+ callback.accept(Pair.create(null, error));
+ }
+
+ @Override
+ public void onRetryNeeded(boolean credentialRefresh, long backoffMillis) {
+ if (hasRetried.getAndSet(true)) {
+ Log.e(TAG, "Giving up on image upload after one retry.");
+ callback.accept(Pair.create(null,
+ TelephonyManager.CallComposerException.ERROR_NETWORK_UNAVAILABLE));
+ return;
+ }
+ GbaCredentialsSupplier supplier =
+ (realm, executor) ->
+ getGbaCredentials(credentialRefresh, carrierConfig, executor);
+
+ sExecutorService.schedule(() -> transfer.uploadPicture(imageData, supplier),
+ backoffMillis, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void onUploadSuccessful(String serverUrl) {
+ mCachedServerUrls.put(id, serverUrl);
+ mCachedImages.put(id, imageData);
+ Log.i(TAG, "Successfully received url: " + serverUrl + " associated with "
+ + id.toString());
+ callback.accept(Pair.create(id, TelephonyManager.CallComposerException.SUCCESS));
+ }
+ });
+
+ transfer.uploadPicture(imageData,
+ (realm, executor) -> getGbaCredentials(false, carrierConfig, executor));
+ }
+
+ public void handleDownloadFromServer(CallComposerPictureTransfer.Factory transferFactory,
+ String remoteUrl, Consumer<Pair<Uri, Integer>> callback) {
+ if (sTestMode) {
+ ImageData imageData = new ImageData(getPlaceholderPictureAsBytes(), "image/png", null);
+ UUID id = UUID.randomUUID();
+ mCachedImages.put(id, imageData);
+ storeUploadedPictureToCallLog(id, uri -> callback.accept(Pair.create(uri, -1)));
+ return;
+ }
+
+ PersistableBundle carrierConfig = mTelephonyManager.getCarrierConfig();
+ CallComposerPictureTransfer transfer = transferFactory.create(mContext,
+ mSubscriptionId, remoteUrl, sExecutorService);
+
+ AtomicBoolean hasRetried = new AtomicBoolean(false);
+ transfer.setCallback(new CallComposerPictureTransfer.PictureCallback() {
+ @Override
+ public void onError(int error) {
+ callback.accept(Pair.create(null, error));
+ }
+
+ @Override
+ public void onRetryNeeded(boolean credentialRefresh, long backoffMillis) {
+ if (hasRetried.getAndSet(true)) {
+ Log.e(TAG, "Giving up on image download after one retry.");
+ callback.accept(Pair.create(null,
+ TelephonyManager.CallComposerException.ERROR_NETWORK_UNAVAILABLE));
+ return;
+ }
+ GbaCredentialsSupplier supplier =
+ (realm, executor) ->
+ getGbaCredentials(credentialRefresh, carrierConfig, executor);
+
+ sExecutorService.schedule(() -> transfer.downloadPicture(supplier),
+ backoffMillis, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void onDownloadSuccessful(ImageData data) {
+ ByteArrayInputStream imageDataInput =
+ new ByteArrayInputStream(data.getImageBytes());
+ mCallLogProxy.storeCallComposerPictureAsUser(
+ mContext, UserHandle.CURRENT, imageDataInput,
+ sExecutorService,
+ new OutcomeReceiver<Uri, CallLog.CallComposerLoggingException>() {
+ @Override
+ public void onResult(@NonNull Uri result) {
+ callback.accept(Pair.create(
+ result, TelephonyManager.CallComposerException.SUCCESS));
+ }
+
+ @Override
+ public void onError(CallLog.CallComposerLoggingException e) {
+ // Just report an error to the client for now.
+ callback.accept(Pair.create(null,
+ TelephonyManager.CallComposerException.ERROR_UNKNOWN));
+ }
+ });
+ }
+ });
+
+ transfer.downloadPicture(((realm, executor) ->
+ getGbaCredentials(false, carrierConfig, executor)));
+ }
+
+ public void storeUploadedPictureToCallLog(UUID id, Consumer<Uri> callback) {
+ ImageData data = mCachedImages.get(id);
+ if (data == null) {
+ Log.e(TAG, "No picture associated with uuid " + id);
+ callback.accept(null);
+ return;
+ }
+ ByteArrayInputStream imageDataInput =
+ new ByteArrayInputStream(data.getImageBytes());
+ mCallLogProxy.storeCallComposerPictureAsUser(mContext, UserHandle.CURRENT, imageDataInput,
+ sExecutorService,
+ new OutcomeReceiver<Uri, CallLog.CallComposerLoggingException>() {
+ @Override
+ public void onResult(@NonNull Uri result) {
+ callback.accept(result);
+ clearCachedData();
+ }
+
+ @Override
+ public void onError(CallLog.CallComposerLoggingException e) {
+ // Just report an error to the client for now.
+ Log.e(TAG, "Error logging uploaded image: " + e.getErrorCode());
+ callback.accept(null);
+ clearCachedData();
+ }
+ });
+ }
+
+ public String getServerUrlForImageId(UUID id) {
+ return mCachedServerUrls.get(id);
+ }
+
+ public void clearCachedData() {
+ mCachedServerUrls.clear();
+ mCachedImages.clear();
+ }
+
+ private byte[] getPlaceholderPictureAsBytes() {
+ InputStream resourceInput = mContext.getResources().openRawResource(R.drawable.cupcake);
+ try {
+ return readBytes(resourceInput);
+ } catch (Exception e) {
+ return new byte[] {};
+ }
+ }
+
+ private static byte[] readBytes(InputStream inputStream) throws Exception {
+ byte[] buffer = new byte[1024];
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ int numRead;
+ do {
+ numRead = inputStream.read(buffer);
+ if (numRead > 0) output.write(buffer, 0, numRead);
+ } while (numRead > 0);
+ return output.toByteArray();
+ }
+
+ private CompletableFuture<GbaCredentials> getGbaCredentials(
+ boolean forceRefresh, PersistableBundle config, Executor executor) {
+ synchronized (this) {
+ if (!forceRefresh && mCachedCredentials != null) {
+ return CompletableFuture.completedFuture(mCachedCredentials);
+ }
+
+ if (forceRefresh) {
+ mCachedCredentials = null;
+ }
+ }
+
+ UaSecurityProtocolIdentifier securityProtocolIdentifier =
+ new UaSecurityProtocolIdentifier.Builder()
+ .setOrg(config.getInt(
+ CarrierConfigManager.KEY_GBA_UA_SECURITY_ORGANIZATION_INT))
+ .setProtocol(config.getInt(
+ CarrierConfigManager.KEY_GBA_UA_SECURITY_PROTOCOL_INT))
+ .setTlsCipherSuite(config.getInt(
+ CarrierConfigManager.KEY_GBA_UA_TLS_CIPHER_SUITE_INT))
+ .build();
+ CompletableFuture<GbaCredentials> resultFuture = new CompletableFuture<>();
+
+ mTelephonyManager.bootstrapAuthenticationRequest(TelephonyManager.APPTYPE_ISIM,
+ getNafUri(config), securityProtocolIdentifier, forceRefresh, executor,
+ new TelephonyManager.BootstrapAuthenticationCallback() {
+ @Override
+ public void onKeysAvailable(byte[] gbaKey, String transactionId) {
+ GbaCredentials creds = new GbaCredentials(transactionId, gbaKey);
+ synchronized (CallComposerPictureManager.this) {
+ mCachedCredentials = creds;
+ }
+ resultFuture.complete(creds);
+ }
+
+ @Override
+ public void onAuthenticationFailure(int reason) {
+ Log.e(TAG, "GBA auth failed: reason=" + reason);
+ resultFuture.complete(null);
+ }
+ });
+
+ return resultFuture;
+ }
+
+ private static Uri getNafUri(PersistableBundle carrierConfig) {
+ String uploadUriString = carrierConfig.getString(
+ CarrierConfigManager.KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING);
+ Uri uploadUri = Uri.parse(uploadUriString);
+ String nafPrefix;
+ switch (carrierConfig.getInt(CarrierConfigManager.KEY_GBA_MODE_INT)) {
+ case CarrierConfigManager.GBA_U:
+ nafPrefix = THREE_GPP_BOOTSTRAPPING + "-uicc";
+ break;
+ case CarrierConfigManager.GBA_DIGEST:
+ nafPrefix = THREE_GPP_BOOTSTRAPPING + "-digest";
+ break;
+ case CarrierConfigManager.GBA_ME:
+ default:
+ nafPrefix = THREE_GPP_BOOTSTRAPPING;
+ }
+ String newAuthority = nafPrefix + "@" + uploadUri.getAuthority();
+ Uri nafUri = new Uri.Builder().scheme(uploadUri.getScheme())
+ .encodedAuthority(newAuthority)
+ .build();
+ Log.i(TAG, "using NAF uri " + nafUri + " for GBA");
+ return nafUri;
+ }
+
+ @VisibleForTesting
+ static ScheduledExecutorService getExecutor() {
+ return sExecutorService;
+ }
+
+ @VisibleForTesting
+ void setCallLogProxy(CallLogProxy proxy) {
+ mCallLogProxy = proxy;
+ }
+}
diff --git a/src/com/android/phone/callcomposer/CallComposerPictureTransfer.java b/src/com/android/phone/callcomposer/CallComposerPictureTransfer.java
new file mode 100644
index 0000000..e4458cd
--- /dev/null
+++ b/src/com/android/phone/callcomposer/CallComposerPictureTransfer.java
@@ -0,0 +1,538 @@
+/*
+ * Copyright (C) 2020 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.phone.callcomposer;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.Build;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.http.multipart.MultipartEntity;
+import com.android.internal.http.multipart.Part;
+
+import com.google.common.net.MediaType;
+
+import gov.nist.javax.sip.header.WWWAuthenticate;
+
+import org.xml.sax.InputSource;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Iterator;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+public class CallComposerPictureTransfer {
+ private static final String TAG = CallComposerPictureTransfer.class.getSimpleName();
+ private static final int HTTP_TIMEOUT_MILLIS = 20000;
+ private static final int DEFAULT_BACKOFF_MILLIS = 1000;
+ private static final String THREE_GPP_GBA = "3gpp-gba";
+
+ private static final int ERROR_UNKNOWN = 0;
+ private static final int ERROR_HTTP_TIMEOUT = 1;
+ private static final int ERROR_NO_AUTH_REQUIRED = 2;
+ private static final int ERROR_FORBIDDEN = 3;
+
+ public interface Factory {
+ default CallComposerPictureTransfer create(Context context, int subscriptionId, String url,
+ ExecutorService executorService) {
+ return new CallComposerPictureTransfer(context, subscriptionId, url, executorService);
+ }
+ }
+
+ public interface PictureCallback {
+ default void onError(@TelephonyManager.CallComposerException.CallComposerError int error) {}
+ default void onRetryNeeded(boolean credentialRefresh, long backoffMillis) {}
+ default void onUploadSuccessful(String serverUrl) {}
+ default void onDownloadSuccessful(ImageData data) {}
+ }
+
+ private static class NetworkAccessException extends RuntimeException {
+ final int errorCode;
+
+ NetworkAccessException(int errorCode) {
+ this.errorCode = errorCode;
+ }
+ }
+
+ private final Context mContext;
+ private final int mSubscriptionId;
+ private final String mUrl;
+ private final ExecutorService mExecutorService;
+
+ private PictureCallback mCallback;
+
+ private CallComposerPictureTransfer(Context context, int subscriptionId, String url,
+ ExecutorService executorService) {
+ mContext = context;
+ mSubscriptionId = subscriptionId;
+ mExecutorService = executorService;
+ mUrl = url;
+ }
+
+ @VisibleForTesting
+ public void setCallback(PictureCallback callback) {
+ mCallback = callback;
+ }
+
+ public void uploadPicture(ImageData image,
+ GbaCredentialsSupplier credentialsSupplier) {
+ CompletableFuture<Network> networkFuture = getNetworkForCallComposer();
+ CompletableFuture<WWWAuthenticate> authorizationHeaderFuture = networkFuture
+ .thenApplyAsync((network) -> prepareInitialPost(network, mUrl), mExecutorService)
+ .thenComposeAsync(this::obtainAuthenticateHeader, mExecutorService)
+ .thenApplyAsync(DigestAuthUtils::parseAuthenticateHeader);
+ CompletableFuture<GbaCredentials> credsFuture = authorizationHeaderFuture
+ .thenComposeAsync((header) ->
+ credentialsSupplier.getCredentials(header.getRealm(), mExecutorService),
+ mExecutorService);
+
+ CompletableFuture<String> authorizationFuture =
+ authorizationHeaderFuture.thenCombineAsync(credsFuture,
+ (authHeader, credentials) ->
+ DigestAuthUtils.generateAuthorizationHeader(
+ authHeader, credentials, "POST", mUrl),
+ mExecutorService)
+ .whenCompleteAsync(
+ (authorization, error) -> handleExceptionalCompletion(error),
+ mExecutorService);
+
+ CompletableFuture<String> networkUrlFuture =
+ networkFuture.thenCombineAsync(authorizationFuture,
+ (network, auth) -> sendActualImageUpload(network, auth, image),
+ mExecutorService);
+ networkUrlFuture.thenAcceptAsync((result) -> {
+ if (result != null) mCallback.onUploadSuccessful(result);
+ }, mExecutorService).exceptionally((ex) -> {
+ logException("Exception uploading image" , ex);
+ return null;
+ });
+ }
+
+ public void downloadPicture(GbaCredentialsSupplier credentialsSupplier) {
+ CompletableFuture<Network> networkFuture = getNetworkForCallComposer();
+ CompletableFuture<HttpURLConnection> getConnectionFuture =
+ networkFuture.thenApplyAsync((network) ->
+ prepareImageDownloadRequest(network, mUrl), mExecutorService);
+
+ CompletableFuture<ImageData> immediatelyDownloadableImage = getConnectionFuture
+ .thenComposeAsync((conn) -> {
+ try {
+ if (conn.getResponseCode() != 200) {
+ return CompletableFuture.completedFuture(null);
+ }
+ } catch (IOException e) {
+ logException("IOException obtaining return code: ", e);
+ throw new NetworkAccessException(ERROR_HTTP_TIMEOUT);
+ }
+ return CompletableFuture.completedFuture(downloadImageFromConnection(conn));
+ }, mExecutorService);
+
+ CompletableFuture<ImageData> authRequiredImage = getConnectionFuture
+ .thenComposeAsync((conn) -> {
+ try {
+ if (conn.getResponseCode() == 200) {
+ // handled by above case
+ return CompletableFuture.completedFuture(null);
+ }
+ } catch (IOException e) {
+ logException("IOException obtaining return code: ", e);
+ throw new NetworkAccessException(ERROR_HTTP_TIMEOUT);
+ }
+ CompletableFuture<WWWAuthenticate> authenticateHeaderFuture =
+ obtainAuthenticateHeader(conn)
+ .thenApply(DigestAuthUtils::parseAuthenticateHeader);
+ CompletableFuture<GbaCredentials> credsFuture = authenticateHeaderFuture
+ .thenComposeAsync((header) ->
+ credentialsSupplier.getCredentials(header.getRealm(),
+ mExecutorService), mExecutorService);
+
+ CompletableFuture<String> authorizationFuture = authenticateHeaderFuture
+ .thenCombineAsync(credsFuture, (authHeader, credentials) ->
+ DigestAuthUtils.generateAuthorizationHeader(
+ authHeader, credentials, "GET", mUrl),
+ mExecutorService)
+ .whenCompleteAsync((authorization, error) ->
+ handleExceptionalCompletion(error), mExecutorService);
+
+ return networkFuture.thenCombineAsync(authorizationFuture,
+ this::downloadImageWithAuth, mExecutorService);
+ }, mExecutorService);
+
+ CompletableFuture.allOf(immediatelyDownloadableImage, authRequiredImage).thenRun(() -> {
+ ImageData fromImmediate = immediatelyDownloadableImage.getNow(null);
+ ImageData fromAuth = authRequiredImage.getNow(null);
+ // If both of these are null, that means an error happened somewhere in the chain.
+ // in that case, the error has already been transmitted to the callback, so ignore it.
+ if (fromAuth == null && fromImmediate == null) {
+ Log.w(TAG, "No result from download -- error happened sometime earlier");
+ }
+ if (fromAuth != null) mCallback.onDownloadSuccessful(fromAuth);
+ mCallback.onDownloadSuccessful(fromImmediate);
+ }).exceptionally((ex) -> {
+ logException("Exception downloading image" , ex);
+ return null;
+ });
+ }
+
+ private CompletableFuture<Network> getNetworkForCallComposer() {
+ ConnectivityManager connectivityManager =
+ mContext.getSystemService(ConnectivityManager.class);
+ NetworkRequest pictureNetworkRequest = new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build();
+ CompletableFuture<Network> resultFuture = new CompletableFuture<>();
+ connectivityManager.requestNetwork(pictureNetworkRequest,
+ new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onAvailable(@NonNull Network network) {
+ resultFuture.complete(network);
+ }
+ });
+ return resultFuture;
+ }
+
+ private HttpURLConnection prepareInitialPost(Network network, String uploadUrl) {
+ try {
+ HttpURLConnection connection =
+ (HttpURLConnection) network.openConnection(new URL(uploadUrl));
+ connection.setRequestMethod("POST");
+ connection.setInstanceFollowRedirects(false);
+ connection.setConnectTimeout(HTTP_TIMEOUT_MILLIS);
+ connection.setReadTimeout(HTTP_TIMEOUT_MILLIS);
+ connection.setRequestProperty("User-Agent", getUserAgent());
+ return connection;
+ } catch (MalformedURLException e) {
+ Log.e(TAG, "Malformed URL: " + uploadUrl);
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ logException("IOException opening network: ", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private HttpURLConnection prepareImageDownloadRequest(Network network, String imageUrl) {
+ try {
+ HttpURLConnection connection =
+ (HttpURLConnection) network.openConnection(new URL(imageUrl));
+ connection.setRequestMethod("GET");
+ connection.setConnectTimeout(HTTP_TIMEOUT_MILLIS);
+ connection.setReadTimeout(HTTP_TIMEOUT_MILLIS);
+ connection.setRequestProperty("User-Agent", getUserAgent());
+ return connection;
+ } catch (MalformedURLException e) {
+ Log.e(TAG, "Malformed URL: " + imageUrl);
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ logException("IOException opening network: ", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ // Attempts to connect via the supplied connection, expecting a HTTP 401 in response. Throws
+ // an IOException if the connection times out.
+ // After the response is received, returns the WWW-Authenticate header in the following form:
+ // "WWW-Authenticate:<method> <params>"
+ private CompletableFuture<String> obtainAuthenticateHeader(
+ HttpURLConnection connection) {
+ return CompletableFuture.supplyAsync(() -> {
+ int responseCode;
+ try {
+ responseCode = connection.getResponseCode();
+ } catch (IOException e) {
+ logException("IOException obtaining auth header: ", e);
+ throw new NetworkAccessException(ERROR_HTTP_TIMEOUT);
+ }
+ if (responseCode == 204) {
+ throw new NetworkAccessException(ERROR_NO_AUTH_REQUIRED);
+ } else if (responseCode == 403) {
+ throw new NetworkAccessException(ERROR_FORBIDDEN);
+ } else if (responseCode != 401) {
+ Log.w(TAG, "Received unexpected response in auth request, code= "
+ + responseCode);
+ throw new NetworkAccessException(ERROR_UNKNOWN);
+ }
+
+ return connection.getHeaderField(DigestAuthUtils.WWW_AUTHENTICATE);
+ }, mExecutorService);
+ }
+
+ private ImageData downloadImageWithAuth(Network network, String authorization) {
+ HttpURLConnection connection = prepareImageDownloadRequest(network, mUrl);
+ connection.addRequestProperty("Authorization", authorization);
+ return downloadImageFromConnection(connection);
+ }
+
+ private ImageData downloadImageFromConnection(HttpURLConnection conn) {
+ try {
+ if (conn.getResponseCode() != 200) {
+ Log.w(TAG, "Got response code " + conn.getResponseCode() + " when trying"
+ + " to download image");
+ if (conn.getResponseCode() == 401) {
+ Log.i(TAG, "Got 401 even with auth -- key refresh needed?");
+ mCallback.onRetryNeeded(true, 0);
+ }
+ return null;
+ }
+ } catch (IOException e) {
+ logException("IOException obtaining return code: ", e);
+ throw new NetworkAccessException(ERROR_HTTP_TIMEOUT);
+ }
+
+ String contentType = conn.getContentType();
+ ByteArrayOutputStream imageDataOut = new ByteArrayOutputStream();
+ byte[] buffer = new byte[4096];
+ int numRead;
+ try {
+ InputStream is = conn.getInputStream();
+ while (true) {
+ numRead = is.read(buffer);
+ if (numRead < 0) break;
+ imageDataOut.write(buffer, 0, numRead);
+ }
+ } catch (IOException e) {
+ logException("IOException reading from image body: ", e);
+ return null;
+ }
+
+ return new ImageData(imageDataOut.toByteArray(), contentType, null);
+ }
+
+ private void handleExceptionalCompletion(Throwable error) {
+ if (error != null) {
+ if (error.getCause() instanceof NetworkAccessException) {
+ int code = ((NetworkAccessException) error.getCause()).errorCode;
+ if (code == ERROR_UNKNOWN || code == ERROR_HTTP_TIMEOUT) {
+ scheduleRetry();
+ } else {
+ int failureCode;
+ if (code == ERROR_FORBIDDEN) {
+ failureCode = TelephonyManager.CallComposerException
+ .ERROR_AUTHENTICATION_FAILED;
+ } else {
+ failureCode = TelephonyManager.CallComposerException
+ .ERROR_UNKNOWN;
+ }
+ deliverFailure(failureCode);
+ }
+ } else {
+ deliverFailure(TelephonyManager.CallComposerException.ERROR_UNKNOWN);
+ }
+ }
+ }
+
+ private void scheduleRetry() {
+ mCallback.onRetryNeeded(false, DEFAULT_BACKOFF_MILLIS);
+ }
+
+ private void deliverFailure(int code) {
+ mCallback.onError(code);
+ }
+
+ private static Part makeUploadPart(String name, String contentType, String filename,
+ byte[] data) {
+ return new Part() {
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getContentType() {
+ return contentType;
+ }
+
+ @Override
+ public String getCharSet() {
+ return null;
+ }
+
+ @Override
+ public String getTransferEncoding() {
+ return null;
+ }
+
+ @Override
+ public void sendDispositionHeader(OutputStream out) throws IOException {
+ super.sendDispositionHeader(out);
+ if (filename != null) {
+ String fileNameSuffix = "; filename=\"" + filename + "\"";
+ out.write(fileNameSuffix.getBytes());
+ }
+ }
+
+ @Override
+ protected void sendData(OutputStream out) throws IOException {
+ out.write(data);
+ }
+
+ @Override
+ protected long lengthOfData() throws IOException {
+ return data.length;
+ }
+ };
+ }
+
+ private String sendActualImageUpload(Network network, String authHeader, ImageData image) {
+ Part transactionIdPart = makeUploadPart("tid", "text/plain",
+ null, image.getId().getBytes());
+ Part imageDataPart = makeUploadPart("File", image.getMimeType(),
+ image.getId(), image.getImageBytes());
+
+ MultipartEntity multipartEntity =
+ new MultipartEntity(new Part[] {transactionIdPart, imageDataPart});
+
+ HttpURLConnection connection = prepareInitialPost(network, mUrl);
+ connection.setDoOutput(true);
+ connection.addRequestProperty("Authorization", authHeader);
+ connection.addRequestProperty("Content-Length",
+ String.valueOf(multipartEntity.getContentLength()));
+ connection.addRequestProperty("Content-Type", multipartEntity.getContentType().getValue());
+ connection.addRequestProperty("Accept-Encoding", "*");
+
+ try (OutputStream requestBodyOut = connection.getOutputStream()) {
+ multipartEntity.writeTo(requestBodyOut);
+ } catch (IOException e) {
+ logException("IOException making request to upload image: ", e);
+ throw new RuntimeException(e);
+ }
+
+ try {
+ int response = connection.getResponseCode();
+ Log.i(TAG, "Received response code: " + response
+ + ", message=" + connection.getResponseMessage());
+ if (response == 401 || response == 403) {
+ deliverFailure(TelephonyManager.CallComposerException.ERROR_AUTHENTICATION_FAILED);
+ return null;
+ }
+ if (response == 503) {
+ // TODO: implement parsing of retry-after and schedule a retry with that time
+ scheduleRetry();
+ return null;
+ }
+ if (response != 200) {
+ scheduleRetry();
+ return null;
+ }
+ String responseBody = readResponseBody(connection);
+ String parsedUrl = parseImageUploadResponseXmlForUrl(responseBody);
+ Log.i(TAG, "Parsed URL as upload result: " + parsedUrl);
+ return parsedUrl;
+ } catch (IOException e) {
+ logException("IOException getting response to image upload: ", e);
+ deliverFailure(TelephonyManager.CallComposerException.ERROR_UNKNOWN);
+ return null;
+ }
+ }
+
+ private static String parseImageUploadResponseXmlForUrl(String xmlData) {
+ NamespaceContext ns = new NamespaceContext() {
+ public String getNamespaceURI(String prefix) {
+ return "urn:gsma:params:xml:ns:rcs:rcs:fthttp";
+ }
+
+ public String getPrefix(String uri) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Iterator getPrefixes(String uri) {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+ XPath xPath = XPathFactory.newInstance().newXPath();
+ xPath.setNamespaceContext(ns);
+ StringReader reader = new StringReader(xmlData);
+ try {
+ return (String) xPath.evaluate("/a:file/a:file-info[@type='file']/a:data/@url",
+ new InputSource(reader), XPathConstants.STRING);
+ } catch (XPathExpressionException e) {
+ logException("Error parsing response XML:", e);
+ return null;
+ }
+ }
+
+ private static String readResponseBody(HttpURLConnection connection) {
+ Charset charset = MediaType.parse(connection.getContentType())
+ .charset().or(Charset.defaultCharset());
+ StringBuilder sb = new StringBuilder();
+ try (InputStream inputStream = connection.getInputStream()) {
+ String outLine;
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, charset));
+ while ((outLine = reader.readLine()) != null) {
+ sb.append(outLine);
+ }
+ } catch (IOException e) {
+ logException("IOException reading request body: ", e);
+ return null;
+ }
+ return sb.toString();
+ }
+
+ private String getUserAgent() {
+ String carrierName = mContext.getSystemService(TelephonyManager.class)
+ .createForSubscriptionId(mSubscriptionId)
+ .getSimOperatorName();
+ String buildId = Build.ID;
+ String buildDate = DateTimeFormatter.ofPattern("yyyy-MM-dd")
+ .withZone(ZoneId.systemDefault())
+ .format(Instant.ofEpochMilli(Build.TIME));
+ String buildVersion = Build.VERSION.RELEASE_OR_CODENAME;
+ String deviceName = Build.DEVICE;
+ return String.format("%s %s %s %s %s %s %s",
+ carrierName, buildId, buildDate, "Android", buildVersion,
+ deviceName, THREE_GPP_GBA);
+
+ }
+
+ private static void logException(String message, Throwable e) {
+ StringWriter log = new StringWriter();
+ log.append(message);
+ log.append(":\n");
+ log.append(e.getMessage());
+ PrintWriter pw = new PrintWriter(log);
+ e.printStackTrace(pw);
+ Log.e(TAG, log.toString());
+ }
+}
diff --git a/src/com/android/phone/callcomposer/DigestAuthUtils.java b/src/com/android/phone/callcomposer/DigestAuthUtils.java
new file mode 100644
index 0000000..2f081f7
--- /dev/null
+++ b/src/com/android/phone/callcomposer/DigestAuthUtils.java
@@ -0,0 +1,157 @@
+/*
+ * 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.phone.callcomposer;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.google.common.io.BaseEncoding;
+
+import gov.nist.javax.sip.address.GenericURI;
+import gov.nist.javax.sip.header.Authorization;
+import gov.nist.javax.sip.header.WWWAuthenticate;
+import gov.nist.javax.sip.parser.WWWAuthenticateParser;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.text.ParseException;
+
+public class DigestAuthUtils {
+ private static final String TAG = DigestAuthUtils.class.getSimpleName();
+
+ public static final String WWW_AUTHENTICATE = "www-authenticate";
+ private static final String MD5_ALGORITHM = "md5";
+ private static final int CNONCE_LENGTH_BYTES = 16;
+ private static final String AUTH_QOP = "auth";
+
+ public static WWWAuthenticate parseAuthenticateHeader(String header) {
+ String reconstitutedHeader = WWW_AUTHENTICATE + ": " + header;
+ WWWAuthenticate parsedHeader;
+ try {
+ return (WWWAuthenticate) (new WWWAuthenticateParser(reconstitutedHeader).parse());
+ } catch (ParseException e) {
+ Log.e(TAG, "Error parsing received auth header: " + e);
+ return null;
+ }
+ }
+
+ // Generates the Authorization header for use in future requests to the call composer server.
+ public static String generateAuthorizationHeader(WWWAuthenticate parsedHeader,
+ GbaCredentials credentials, String method, String uri) {
+ if (!TextUtils.isEmpty(parsedHeader.getAlgorithm())
+ && !MD5_ALGORITHM.equals(parsedHeader.getAlgorithm().toLowerCase())) {
+ Log.e(TAG, "This client only supports MD5 auth");
+ return "";
+ }
+ if (!TextUtils.isEmpty(parsedHeader.getQop())
+ && !AUTH_QOP.equals(parsedHeader.getQop().toLowerCase())) {
+ Log.e(TAG, "This client only supports the auth qop");
+ return "";
+ }
+
+ String clientNonce = makeClientNonce();
+
+ String response = computeResponse(parsedHeader.getNonce(), clientNonce, AUTH_QOP,
+ credentials.getTransactionId(), parsedHeader.getRealm(), credentials.getKey(),
+ method, uri);
+
+ Authorization replyHeader = new Authorization();
+ try {
+ replyHeader.setScheme(parsedHeader.getScheme());
+ replyHeader.setUsername(credentials.getTransactionId());
+ replyHeader.setURI(new WorkaroundURI(uri));
+ replyHeader.setRealm(parsedHeader.getRealm());
+ replyHeader.setQop(AUTH_QOP);
+ replyHeader.setNonce(parsedHeader.getNonce());
+ replyHeader.setCNonce(clientNonce);
+ replyHeader.setNonceCount(1);
+ replyHeader.setResponse(response);
+ replyHeader.setOpaque(parsedHeader.getOpaque());
+ replyHeader.setAlgorithm(parsedHeader.getAlgorithm());
+
+ } catch (ParseException e) {
+ Log.e(TAG, "Error parsing while constructing reply header: " + e);
+ return null;
+ }
+
+ return replyHeader.encodeBody();
+ }
+
+ public static String computeResponse(String serverNonce, String clientNonce, String qop,
+ String username, String realm, byte[] password, String method, String uri) {
+ String a1Hash = generateA1Hash(username, realm, password);
+ String a2Hash = generateA2Hash(method, uri);
+
+ // this is the nonce-count; since we don't reuse, it's always 1
+ String nonceCount = "00000001";
+ MessageDigest md5Digest = getMd5Digest();
+
+ String hashInput = String.join(":",
+ a1Hash,
+ serverNonce,
+ nonceCount,
+ clientNonce,
+ qop,
+ a2Hash);
+ md5Digest.update(hashInput.getBytes());
+ return base16(md5Digest.digest());
+ }
+
+ private static String makeClientNonce() {
+ SecureRandom rand = new SecureRandom();
+ byte[] clientNonceBytes = new byte[CNONCE_LENGTH_BYTES];
+ rand.nextBytes(clientNonceBytes);
+ return base16(clientNonceBytes);
+ }
+
+ private static String generateA1Hash(
+ String bootstrapTransactionId, String realm, byte[] gbaKey) {
+ MessageDigest md5Digest = getMd5Digest();
+
+ String gbaKeyBase64 = BaseEncoding.base64().encode(gbaKey);
+ String hashInput = String.join(":", bootstrapTransactionId, realm, gbaKeyBase64);
+ md5Digest.update(hashInput.getBytes());
+
+ return base16(md5Digest.digest());
+ }
+
+ private static String generateA2Hash(String method, String requestUri) {
+ MessageDigest md5Digest = getMd5Digest();
+ md5Digest.update(String.join(":", method, requestUri).getBytes());
+ return base16(md5Digest.digest());
+ }
+
+ private static String base16(byte[] input) {
+ return BaseEncoding.base16().encode(input).toLowerCase();
+ }
+
+ private static MessageDigest getMd5Digest() {
+ try {
+ return MessageDigest.getInstance("MD5");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("Couldn't find MD5 algorithm: " + e);
+ }
+ }
+
+ private static class WorkaroundURI extends GenericURI {
+ public WorkaroundURI(String uriString) {
+ this.uriString = uriString;
+ this.scheme = "";
+ }
+ }
+}
diff --git a/src/com/android/phone/callcomposer/GbaCredentials.java b/src/com/android/phone/callcomposer/GbaCredentials.java
new file mode 100644
index 0000000..25a0cd5
--- /dev/null
+++ b/src/com/android/phone/callcomposer/GbaCredentials.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 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.phone.callcomposer;
+
+public class GbaCredentials {
+ private final String mTransactionId;
+ private final byte[] mKey;
+
+ public GbaCredentials(String transactionId, byte[] key) {
+ mTransactionId = transactionId;
+ mKey = key;
+ }
+
+ public String getTransactionId() {
+ return mTransactionId;
+ }
+
+ public byte[] getKey() {
+ return mKey;
+ }
+}
diff --git a/src/com/android/phone/callcomposer/GbaCredentialsSupplier.java b/src/com/android/phone/callcomposer/GbaCredentialsSupplier.java
new file mode 100644
index 0000000..9e5bb6a
--- /dev/null
+++ b/src/com/android/phone/callcomposer/GbaCredentialsSupplier.java
@@ -0,0 +1,24 @@
+/*
+ * 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.phone.callcomposer;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+
+public interface GbaCredentialsSupplier {
+ CompletableFuture<GbaCredentials> getCredentials(String realm, Executor executor);
+}
diff --git a/src/com/android/phone/callcomposer/ImageData.java b/src/com/android/phone/callcomposer/ImageData.java
new file mode 100644
index 0000000..fc93485
--- /dev/null
+++ b/src/com/android/phone/callcomposer/ImageData.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 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.phone.callcomposer;
+
+public class ImageData {
+ private final byte[] mImageBytes;
+ private final String mMimeType;
+
+ private String mId;
+
+ public ImageData(byte[] imageBytes, String mimeType, String id) {
+ mImageBytes = imageBytes;
+ mMimeType = mimeType;
+ mId = id;
+ }
+
+ public byte[] getImageBytes() {
+ return mImageBytes;
+ }
+
+ public String getMimeType() {
+ return mMimeType;
+ }
+
+ public String getId() {
+ return mId;
+ }
+
+ public void setId(String id) {
+ mId = id;
+ }
+}
diff --git a/src/com/android/phone/euicc/EuiccUiDispatcherActivity.java b/src/com/android/phone/euicc/EuiccUiDispatcherActivity.java
index 57caede..f24e7d6 100644
--- a/src/com/android/phone/euicc/EuiccUiDispatcherActivity.java
+++ b/src/com/android/phone/euicc/EuiccUiDispatcherActivity.java
@@ -26,7 +26,7 @@
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.UserHandle;
-import android.permission.PermissionManager;
+import android.permission.LegacyPermissionManager;
import android.service.euicc.EuiccService;
import android.telephony.euicc.EuiccManager;
import android.util.Log;
@@ -55,14 +55,15 @@
PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
| PackageManager.GET_RESOLVED_FILTER;
- private PermissionManager mPermissionManager;
+ private LegacyPermissionManager mPermissionManager;
private boolean mGrantPermissionDone = false;
private ThreadPoolExecutor mExecutor;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mPermissionManager = (PermissionManager) getSystemService(Context.PERMISSION_SERVICE);
+ mPermissionManager = (LegacyPermissionManager) getSystemService(
+ Context.LEGACY_PERMISSION_SERVICE);
mExecutor = new ThreadPoolExecutor(
1 /* corePoolSize */,
1 /* maxPoolSize */,
diff --git a/src/com/android/phone/settings/AccessibilitySettingsFragment.java b/src/com/android/phone/settings/AccessibilitySettingsFragment.java
index ad3f133..475d878 100644
--- a/src/com/android/phone/settings/AccessibilitySettingsFragment.java
+++ b/src/com/android/phone/settings/AccessibilitySettingsFragment.java
@@ -19,6 +19,9 @@
import android.content.Context;
import android.media.AudioManager;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
import android.os.PersistableBundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
@@ -27,8 +30,8 @@
import android.provider.Settings;
import android.telephony.AccessNetworkConstants;
import android.telephony.CarrierConfigManager;
-import android.telephony.PhoneStateListener;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
@@ -56,15 +59,12 @@
private static final int WFC_QUERY_TIMEOUT_MILLIS = 20;
- private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
- /**
- * Disable the TTY setting when in/out of a call (and if carrier doesn't
- * support VoLTE with TTY).
- * @see android.telephony.PhoneStateListener#onCallStateChanged(int,
- * java.lang.String)
- */
+ private final TelephonyCallback mTelephonyCallback = new AccessibilityTelephonyCallback();
+
+ private final class AccessibilityTelephonyCallback extends TelephonyCallback implements
+ TelephonyCallback.CallStateListener {
@Override
- public void onCallStateChanged(int state, String incomingNumber) {
+ public void onCallStateChanged(int state) {
if (DBG) Log.d(LOG_TAG, "PhoneStateListener.onCallStateChanged: state=" + state);
Preference pref = getPreferenceScreen().findPreference(BUTTON_TTY_KEY);
if (pref != null) {
@@ -78,7 +78,7 @@
|| (telephonyManager.getCallState() == TelephonyManager.CALL_STATE_IDLE));
}
}
- };
+ }
private Context mContext;
private AudioManager mAudioManager;
@@ -148,7 +148,8 @@
super.onResume();
TelephonyManager tm =
(TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
+ tm.registerTelephonyCallback(new HandlerExecutor(new Handler(Looper.getMainLooper())),
+ mTelephonyCallback);
}
@Override
@@ -156,7 +157,7 @@
super.onPause();
TelephonyManager tm =
(TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+ tm.unregisterTelephonyCallback(mTelephonyCallback);
}
@Override
diff --git a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
index 3811a77..224a1f9 100644
--- a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
+++ b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
@@ -6,14 +6,11 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Icon;
-import android.net.sip.SipManager;
import android.os.Bundle;
import android.os.UserManager;
-import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
-import android.preference.SwitchPreference;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
@@ -28,9 +25,6 @@
import com.android.phone.PhoneUtils;
import com.android.phone.R;
import com.android.phone.SubscriptionInfoHelper;
-import com.android.services.telephony.sip.SipAccountRegistry;
-import com.android.services.telephony.sip.SipPreferences;
-import com.android.services.telephony.sip.SipUtil;
import java.util.ArrayList;
import java.util.Collections;
@@ -48,11 +42,6 @@
private static final String ALL_CALLING_ACCOUNTS_KEY = "phone_accounts_all_calling_accounts";
- private static final String SIP_SETTINGS_CATEGORY_PREF_KEY =
- "phone_accounts_sip_settings_category_key";
- private static final String USE_SIP_PREF_KEY = "use_sip_calling_options_key";
- private static final String SIP_RECEIVE_CALLS_PREF_KEY = "sip_receive_calls_key";
-
private static final String MAKE_AND_RECEIVE_CALLS_CATEGORY_KEY =
"make_and_receive_calls_settings_category_key";
private static final String DEFAULT_OUTGOING_ACCOUNT_KEY = "default_outgoing_account";
@@ -84,10 +73,6 @@
private PreferenceCategory mMakeAndReceiveCallsCategory;
private boolean mMakeAndReceiveCallsCategoryPresent;
- private ListPreference mUseSipCalling;
- private SwitchPreference mSipReceiveCallsPreference;
- private SipPreferences mSipPreferences;
-
private final SubscriptionManager.OnSubscriptionsChangedListener
mOnSubscriptionsChangeListener =
new SubscriptionManager.OnSubscriptionsChangedListener() {
@@ -154,39 +139,6 @@
updateAccounts();
updateMakeCallsOptions();
- if (isPrimaryUser() && SipUtil.isVoipSupported(getActivity())) {
- mSipPreferences = new SipPreferences(getActivity());
-
- mUseSipCalling = (ListPreference)
- getPreferenceScreen().findPreference(USE_SIP_PREF_KEY);
- mUseSipCalling.setEntries(!SipManager.isSipWifiOnly(getActivity())
- ? R.array.sip_call_options_wifi_only_entries
- : R.array.sip_call_options_entries);
- mUseSipCalling.setOnPreferenceChangeListener(this);
-
- int optionsValueIndex =
- mUseSipCalling.findIndexOfValue(mSipPreferences.getSipCallOption());
- if (optionsValueIndex == -1) {
- // If the option is invalid (eg. deprecated value), default to SIP_ADDRESS_ONLY.
- mSipPreferences.setSipCallOption(
- getResources().getString(R.string.sip_address_only));
- optionsValueIndex =
- mUseSipCalling.findIndexOfValue(mSipPreferences.getSipCallOption());
- }
- mUseSipCalling.setValueIndex(optionsValueIndex);
- mUseSipCalling.setSummary(mUseSipCalling.getEntry());
-
- mSipReceiveCallsPreference = (SwitchPreference)
- getPreferenceScreen().findPreference(SIP_RECEIVE_CALLS_PREF_KEY);
- mSipReceiveCallsPreference.setEnabled(SipUtil.isPhoneIdle(getActivity()));
- mSipReceiveCallsPreference.setChecked(
- mSipPreferences.isReceivingCallsEnabled());
- mSipReceiveCallsPreference.setOnPreferenceChangeListener(this);
- } else {
- getPreferenceScreen().removePreference(
- getPreferenceScreen().findPreference(SIP_SETTINGS_CATEGORY_PREF_KEY));
- }
-
SubscriptionManager.from(getActivity()).addOnSubscriptionsChangedListener(
mOnSubscriptionsChangeListener);
}
@@ -207,21 +159,6 @@
*/
@Override
public boolean onPreferenceChange(Preference pref, Object objValue) {
- if (pref == mUseSipCalling) {
- String option = objValue.toString();
- mSipPreferences.setSipCallOption(option);
- mUseSipCalling.setValueIndex(mUseSipCalling.findIndexOfValue(option));
- mUseSipCalling.setSummary(mUseSipCalling.getEntry());
- return true;
- } else if (pref == mSipReceiveCallsPreference) {
- final boolean isEnabled = !mSipReceiveCallsPreference.isChecked();
- new Thread(new Runnable() {
- public void run() {
- handleSipReceiveCallsOption(isEnabled);
- }
- }).start();
- return true;
- }
return false;
}
@@ -256,22 +193,6 @@
@Override
public void onAccountChanged(AccountSelectionPreference pref) {}
- private synchronized void handleSipReceiveCallsOption(boolean isEnabled) {
- Context context = getActivity();
- if (context == null) {
- // Return if the fragment is detached from parent activity before executed by thread.
- return;
- }
-
- mSipPreferences.setReceivingCallsEnabled(isEnabled);
-
- SipUtil.useSipToReceiveIncomingCalls(context, isEnabled);
-
- // Restart all Sip services to ensure we reflect whether we are receiving calls.
- SipAccountRegistry sipAccountRegistry = SipAccountRegistry.getInstance();
- sipAccountRegistry.restartSipService(context);
- }
-
/**
* Queries the telcomm manager to update the default outgoing account selection preference
* with the list of outgoing accounts and the current default outgoing account.
@@ -409,32 +330,24 @@
mAccountList.removeAll();
List<PhoneAccountHandle> allNonSimAccounts =
getCallingAccounts(false /* includeSims */, true /* includeDisabled */);
- // Check to see if we should show the entire section at all.
- if (shouldShowConnectionServiceList(allNonSimAccounts)) {
- List<PhoneAccountHandle> enabledAccounts =
- getCallingAccounts(true /* includeSims */, false /* includeDisabled */);
- // Initialize the account list with the set of enabled & SIM accounts.
- initAccountList(enabledAccounts);
- // Only show the 'Make Calls With..." option if there are multiple accounts.
- if (enabledAccounts.size() > 1) {
- mMakeAndReceiveCallsCategory.addPreference(mDefaultOutgoingAccount);
- mMakeAndReceiveCallsCategoryPresent = true;
- mDefaultOutgoingAccount.setListener(this);
- updateDefaultOutgoingAccountsModel();
- } else {
- mMakeAndReceiveCallsCategory.removePreference(mDefaultOutgoingAccount);
- }
+ List<PhoneAccountHandle> enabledAccounts =
+ getCallingAccounts(true /* includeSims */, false /* includeDisabled */);
+ // Initialize the account list with the set of enabled & SIM accounts.
+ initAccountList(enabledAccounts);
- // If there are no third party (nonSim) accounts,
- // then don't show enable/disable dialog.
- if (!allNonSimAccounts.isEmpty()) {
- mAccountList.addPreference(mAllCallingAccounts);
- } else {
- mAccountList.removePreference(mAllCallingAccounts);
- }
+ // Always show the 'Make Calls With..." option
+ mMakeAndReceiveCallsCategory.addPreference(mDefaultOutgoingAccount);
+ mMakeAndReceiveCallsCategoryPresent = true;
+ mDefaultOutgoingAccount.setListener(this);
+ updateDefaultOutgoingAccountsModel();
+
+ // If there are no third party (nonSim) accounts,
+ // then don't show enable/disable dialog.
+ if (!allNonSimAccounts.isEmpty()) {
+ mAccountList.addPreference(mAllCallingAccounts);
} else {
- getPreferenceScreen().removePreference(mAccountList);
+ mAccountList.removePreference(mAllCallingAccounts);
}
}
}
diff --git a/src/com/android/phone/settings/RadioInfo.java b/src/com/android/phone/settings/RadioInfo.java
index 568adb8..b1ab413 100644
--- a/src/com/android/phone/settings/RadioInfo.java
+++ b/src/com/android/phone/settings/RadioInfo.java
@@ -17,7 +17,7 @@
package com.android.phone.settings;
import static android.net.ConnectivityManager.NetworkCallback;
-import static android.provider.Settings.Global.PREFERRED_NETWORK_MODE;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.ComponentName;
import android.content.Context;
@@ -37,9 +37,10 @@
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Message;
+import android.os.PersistableBundle;
import android.os.SystemProperties;
-import android.provider.Settings;
import android.telephony.AccessNetworkConstants;
import android.telephony.CarrierConfigManager;
import android.telephony.CellIdentityCdma;
@@ -51,22 +52,20 @@
import android.telephony.CellInfoGsm;
import android.telephony.CellInfoLte;
import android.telephony.CellInfoWcdma;
-import android.telephony.CellLocation;
import android.telephony.CellSignalStrengthCdma;
import android.telephony.CellSignalStrengthGsm;
import android.telephony.CellSignalStrengthLte;
import android.telephony.CellSignalStrengthWcdma;
import android.telephony.DataSpecificRegistrationInfo;
+import android.telephony.data.NetworkSlicingConfig;
import android.telephony.NetworkRegistrationInfo;
-import android.telephony.PhoneStateListener;
import android.telephony.PhysicalChannelConfig;
-import android.telephony.PreciseCallState;
+import android.telephony.RadioAccessFamily;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
-import android.telephony.cdma.CdmaCellLocation;
-import android.telephony.gsm.GsmCellLocation;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
@@ -98,8 +97,12 @@
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.TimeUnit;
/**
@@ -202,9 +205,6 @@
}
private static final int EVENT_CFI_CHANGED = 302;
-
- private static final int EVENT_QUERY_PREFERRED_TYPE_DONE = 1000;
- private static final int EVENT_SET_PREFERRED_TYPE_DONE = 1001;
private static final int EVENT_QUERY_SMSC_DONE = 1005;
private static final int EVENT_UPDATE_SMSC_DONE = 1006;
private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED = 1007;
@@ -216,6 +216,11 @@
private static final int MENU_ITEM_GET_IMS_STATUS = 4;
private static final int MENU_ITEM_TOGGLE_DATA = 5;
+ private static final String CARRIER_PROVISIONING_ACTION =
+ "com.android.phone.settings.CARRIER_PROVISIONING";
+ private static final String TRIGGER_CARRIER_PROVISIONING_ACTION =
+ "com.android.phone.settings.TRIGGER_CARRIER_PROVISIONING";
+
private TextView mDeviceId; //DeviceId is the IMEI in GSM and the MEID in CDMA
private TextView mLine1Number;
private TextView mSubscriptionId;
@@ -231,7 +236,6 @@
private TextView mDBm;
private TextView mMwi;
private TextView mCfi;
- private TextView mLocation;
private TextView mCellInfo;
private TextView mSent;
private TextView mReceived;
@@ -247,6 +251,7 @@
private TextView mNrAvailable;
private TextView mNrState;
private TextView mNrFrequency;
+ private TextView mNetworkSlicingConfig;
private EditText mSmsc;
private Switch mRadioPowerOnSwitch;
private Button mCellInfoRefreshRateButton;
@@ -283,7 +288,6 @@
private boolean mCfiValue = false;
private List<CellInfo> mCellInfoResult = null;
- private CellLocation mCellLocationResult = null;
private int mPreferredNetworkTypeResult;
private int mCellInfoRefreshRateIndex;
@@ -302,11 +306,22 @@
}
};
+ private static final int DEFAULT_TIMEOUT_MS = 1000;
+
// not final because we need to recreate this object to register on a new subId (b/117555407)
- private PhoneStateListener mPhoneStateListener = new RadioInfoPhoneStateListener();
- private class RadioInfoPhoneStateListener extends PhoneStateListener {
+ private TelephonyCallback mTelephonyCallback = new RadioInfoTelephonyCallback();
+ private class RadioInfoTelephonyCallback extends TelephonyCallback implements
+ TelephonyCallback.DataConnectionStateListener,
+ TelephonyCallback.DataActivityListener,
+ TelephonyCallback.CallStateListener,
+ TelephonyCallback.MessageWaitingIndicatorListener,
+ TelephonyCallback.CallForwardingIndicatorListener,
+ TelephonyCallback.CellInfoListener,
+ TelephonyCallback.SignalStrengthsListener,
+ TelephonyCallback.ServiceStateListener {
+
@Override
- public void onDataConnectionStateChanged(int state) {
+ public void onDataConnectionStateChanged(int state, int networkType) {
updateDataState();
updateNetworkType();
}
@@ -317,22 +332,12 @@
}
@Override
- public void onCallStateChanged(int state, String incomingNumber) {
+ public void onCallStateChanged(int state) {
updateNetworkType();
updatePhoneState(state);
}
@Override
- public void onPreciseCallStateChanged(PreciseCallState preciseState) {
- updateNetworkType();
- }
-
- @Override
- public void onCellLocationChanged(CellLocation location) {
- updateLocation(location);
- }
-
- @Override
public void onMessageWaitingIndicatorChanged(boolean mwi) {
mMwiValue = mwi;
updateMessageWaiting();
@@ -385,8 +390,7 @@
private void updatePreferredNetworkType(int type) {
if (type >= PREFERRED_NETWORK_LABELS.length || type < 0) {
- log("EVENT_QUERY_PREFERRED_TYPE_DONE: unknown "
- + "type=" + type);
+ log("Network type: unknown type value=" + type);
type = PREFERRED_NETWORK_LABELS.length - 1; //set to Unknown
}
mPreferredNetworkTypeResult = type;
@@ -414,21 +418,6 @@
public void handleMessage(Message msg) {
AsyncResult ar;
switch (msg.what) {
- case EVENT_QUERY_PREFERRED_TYPE_DONE:
- ar = (AsyncResult) msg.obj;
- if (ar.exception == null && ar.result != null) {
- updatePreferredNetworkType(((int []) ar.result)[0]);
- } else {
- //In case of an exception, we will set this to unknown
- updatePreferredNetworkType(PREFERRED_NETWORK_LABELS.length - 1);
- }
- break;
- case EVENT_SET_PREFERRED_TYPE_DONE:
- ar = (AsyncResult) msg.obj;
- if (ar.exception != null) {
- log("Set preferred network type failed.");
- }
- break;
case EVENT_QUERY_SMSC_DONE:
ar = (AsyncResult) msg.obj;
if (ar.exception != null) {
@@ -474,12 +463,12 @@
mQueuedWork = new ThreadPoolExecutor(1, 1, RUNNABLE_TIMEOUT_MS, TimeUnit.MICROSECONDS,
new LinkedBlockingDeque<Runnable>());
- mTelephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
mConnectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
mPhone = PhoneFactory.getDefaultPhone();
+ mTelephonyManager = ((TelephonyManager) getSystemService(TELEPHONY_SERVICE))
+ .createForSubscriptionId(mPhone.getSubId());
- mImsManager = ImsManager.getInstance(getApplicationContext(),
- SubscriptionManager.getDefaultVoicePhoneId());
+ mImsManager = ImsManager.getInstance(getApplicationContext(), mPhone.getPhoneId());
sPhoneIndexLabels = getPhoneIndexLabels(mTelephonyManager);
@@ -498,7 +487,6 @@
mDBm = (TextView) findViewById(R.id.dbm);
mMwi = (TextView) findViewById(R.id.mwi);
mCfi = (TextView) findViewById(R.id.cfi);
- mLocation = (TextView) findViewById(R.id.location);
mCellInfo = (TextView) findViewById(R.id.cellinfo);
mCellInfo.setTypeface(Typeface.MONOSPACE);
@@ -515,6 +503,7 @@
mNrState = (TextView) findViewById(R.id.nr_state);
mNrFrequency = (TextView) findViewById(R.id.nr_frequency);
mPhyChanConfig = (TextView) findViewById(R.id.phy_chan_config);
+ mNetworkSlicingConfig = (TextView) findViewById(R.id.network_slicing_config);
// hide 5G stats on devices that don't support 5G
if ((mTelephonyManager.getSupportedRadioAccessFamily()
@@ -529,6 +518,8 @@
mNrState.setVisibility(View.GONE);
((TextView) findViewById(R.id.nr_frequency_label)).setVisibility(View.GONE);
mNrFrequency.setVisibility(View.GONE);
+ ((TextView) findViewById(R.id.network_slicing_config_label)).setVisibility(View.GONE);
+ mNetworkSlicingConfig.setVisibility(View.GONE);
}
mPreferredNetworkType = (Spinner) findViewById(R.id.preferredNetworkType);
@@ -597,11 +588,20 @@
mDnsCheckToggleButton = (Button) findViewById(R.id.dns_check_toggle);
mDnsCheckToggleButton.setOnClickListener(mDnsCheckButtonHandler);
mCarrierProvisioningButton = (Button) findViewById(R.id.carrier_provisioning);
- mCarrierProvisioningButton.setOnClickListener(mCarrierProvisioningButtonHandler);
+ if (!TextUtils.isEmpty(getCarrierProvisioningAppString())) {
+ mCarrierProvisioningButton.setOnClickListener(mCarrierProvisioningButtonHandler);
+ } else {
+ mCarrierProvisioningButton.setEnabled(false);
+ }
+
mTriggerCarrierProvisioningButton = (Button) findViewById(
R.id.trigger_carrier_provisioning);
- mTriggerCarrierProvisioningButton.setOnClickListener(
- mTriggerCarrierProvisioningButtonHandler);
+ if (!TextUtils.isEmpty(getCarrierProvisioningAppString())) {
+ mTriggerCarrierProvisioningButton.setOnClickListener(
+ mTriggerCarrierProvisioningButtonHandler);
+ } else {
+ mTriggerCarrierProvisioningButton.setEnabled(false);
+ }
mOemInfoButton = (Button) findViewById(R.id.oem_info);
mOemInfoButton.setOnClickListener(mOemInfoButtonHandler);
@@ -616,9 +616,11 @@
mPreferredNetworkTypeResult = PREFERRED_NETWORK_LABELS.length - 1; //Unknown
mSelectedPhoneIndex = 0; //phone 0
- //FIXME: Replace with TelephonyManager call
- mPhone.getPreferredNetworkType(
- mHandler.obtainMessage(EVENT_QUERY_PREFERRED_TYPE_DONE));
+ new Thread(() -> {
+ int networkType = (int) mTelephonyManager.getPreferredNetworkTypeBitmask();
+ updatePreferredNetworkType(
+ RadioAccessFamily.getNetworkTypeFromRaf(networkType));
+ }).start();
restoreFromBundle(icicle);
}
@@ -654,7 +656,6 @@
updateNetworkType();
updateNrStats(null);
- updateLocation(mCellLocationResult);
updateCellInfo(mCellInfoResult);
updateSubscriptionIds();
@@ -665,6 +666,8 @@
mCellInfoRefreshRateSpinner.setOnItemSelectedListener(mCellInfoRefreshRateHandler);
//set selection after registering listener to force update
mCellInfoRefreshRateSpinner.setSelection(mCellInfoRefreshRateIndex);
+ // Request cell information update from RIL.
+ mTelephonyManager.setCellInfoListRate(CELL_INFO_REFRESH_RATES[mCellInfoRefreshRateIndex]);
//set selection before registering to prevent update
mPreferredNetworkType.setSelection(mPreferredNetworkTypeResult, true);
@@ -702,7 +705,7 @@
log("onPause: unregister phone & data intents");
- mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+ mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback);
mTelephonyManager.setCellInfoListRate(sCellInfoListRateDisabled);
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
@@ -803,7 +806,7 @@
}
private void unregisterPhoneStateListener() {
- mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+ mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback);
mPhone.unregisterForPhysicalChannelConfig(mHandler);
// clear all fields so they are blank until the next listener event occurs
@@ -814,7 +817,6 @@
mSent.setText("");
mReceived.setText("");
mCallState.setText("");
- mLocation.setText("");
mMwiValue = false;
mMwi.setText("");
mCfiValue = false;
@@ -826,21 +828,11 @@
mPhyChanConfig.setText("");
}
- // register mPhoneStateListener for relevant fields using the current TelephonyManager
+ // register mTelephonyCallback for relevant fields using the current TelephonyManager
private void registerPhoneStateListener() {
- mPhoneStateListener = new RadioInfoPhoneStateListener();
- mTelephonyManager.listen(mPhoneStateListener,
- PhoneStateListener.LISTEN_CALL_STATE
- //b/27803938 - RadioInfo currently cannot read PRECISE_CALL_STATE
- // | PhoneStateListener.LISTEN_PRECISE_CALL_STATE
- | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE
- | PhoneStateListener.LISTEN_DATA_ACTIVITY
- | PhoneStateListener.LISTEN_CELL_LOCATION
- | PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
- | PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR
- | PhoneStateListener.LISTEN_CELL_INFO
- | PhoneStateListener.LISTEN_SERVICE_STATE
- | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+ mTelephonyCallback = new RadioInfoTelephonyCallback();
+ mTelephonyManager.registerTelephonyCallback(new HandlerExecutor(mHandler),
+ mTelephonyCallback);
}
private void updateDnsCheckState() {
@@ -872,45 +864,6 @@
+ r.getString(R.string.radioInfo_display_asu));
}
- private void updateLocation(CellLocation location) {
- Resources r = getResources();
- if (location instanceof GsmCellLocation) {
- GsmCellLocation loc = (GsmCellLocation) location;
- int lac = loc.getLac();
- int cid = loc.getCid();
- mLocation.setText(r.getString(R.string.radioInfo_lac) + " = "
- + ((lac == -1) ? "unknown" : Integer.toHexString(lac))
- + " "
- + r.getString(R.string.radioInfo_cid) + " = "
- + ((cid == -1) ? "unknown" : Integer.toHexString(cid)));
- } else if (location instanceof CdmaCellLocation) {
- CdmaCellLocation loc = (CdmaCellLocation) location;
- int bid = loc.getBaseStationId();
- int sid = loc.getSystemId();
- int nid = loc.getNetworkId();
- int lat = loc.getBaseStationLatitude();
- int lon = loc.getBaseStationLongitude();
- mLocation.setText("BID = "
- + ((bid == -1) ? "unknown" : Integer.toHexString(bid))
- + " "
- + "SID = "
- + ((sid == -1) ? "unknown" : Integer.toHexString(sid))
- + " "
- + "NID = "
- + ((nid == -1) ? "unknown" : Integer.toHexString(nid))
- + "\n"
- + "LAT = "
- + ((lat == -1) ? "unknown" : Integer.toHexString(lat))
- + " "
- + "LONG = "
- + ((lon == -1) ? "unknown" : Integer.toHexString(lon)));
- } else {
- mLocation.setText("unknown");
- }
-
-
- }
-
private String getCellInfoDisplayString(int i) {
return (i != Integer.MAX_VALUE) ? Integer.toString(i) : "";
}
@@ -1184,6 +1137,19 @@
mNrState.setText(NetworkRegistrationInfo.nrStateToString(ss.getNrState()));
mNrFrequency.setText(ServiceState.frequencyRangeToString(ss.getNrFrequencyRange()));
}
+
+ Executor simpleExecutor = (r) -> r.run();
+ CompletableFuture<NetworkSlicingConfig> resultFuture = new CompletableFuture<>();
+ mTelephonyManager.getNetworkSlicingConfiguration(simpleExecutor, resultFuture::complete);
+ try {
+ NetworkSlicingConfig networkSlicingConfig =
+ resultFuture.get(DEFAULT_TIMEOUT_MS, MILLISECONDS);
+ mNetworkSlicingConfig.setText(networkSlicingConfig.toString());
+ } catch (ExecutionException | InterruptedException | TimeoutException e) {
+ Log.e(TAG, "Unable to get slicing config: " + e.toString());
+ mNetworkSlicingConfig.setText("Unable to get slicing config.");
+ }
+
}
private void updateProperties() {
@@ -1286,11 +1252,9 @@
private void updateAllCellInfo() {
mCellInfo.setText("");
- mLocation.setText("");
final Runnable updateAllCellInfoResults = new Runnable() {
public void run() {
- updateLocation(mCellLocationResult);
updateCellInfo(mCellInfoResult);
}
};
@@ -1299,7 +1263,6 @@
@Override
public void run() {
mCellInfoResult = mTelephonyManager.getAllCellInfo();
- mCellLocationResult = mTelephonyManager.getCellLocation();
mHandler.post(updateAllCellInfoResults);
}
@@ -1393,7 +1356,7 @@
new MenuItem.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
boolean isImsRegistered = mPhone.isImsRegistered();
- boolean availableVolte = mPhone.isVolteEnabled();
+ boolean availableVolte = mPhone.isVoiceOverCellularImsEnabled();
boolean availableWfc = mPhone.isWifiCallingEnabled();
boolean availableVt = mPhone.isVideoEnabled();
boolean availableUt = mPhone.isUtEnabled();
@@ -1520,9 +1483,9 @@
};
private boolean isImsVolteProvisioned() {
- if (mPhone != null && mImsManager != null) {
- return mImsManager.isVolteEnabledByPlatform(mPhone.getContext())
- && mImsManager.isVolteProvisionedOnDevice(mPhone.getContext());
+ if (mImsManager != null) {
+ return mImsManager.isVolteEnabledByPlatform()
+ && mImsManager.isVolteProvisionedOnDevice();
}
return false;
}
@@ -1535,9 +1498,9 @@
};
private boolean isImsVtProvisioned() {
- if (mPhone != null && mImsManager != null) {
- return mImsManager.isVtEnabledByPlatform(mPhone.getContext())
- && mImsManager.isVtProvisionedOnDevice(mPhone.getContext());
+ if (mImsManager != null) {
+ return mImsManager.isVtEnabledByPlatform()
+ && mImsManager.isVtProvisionedOnDevice();
}
return false;
}
@@ -1550,9 +1513,9 @@
};
private boolean isImsWfcProvisioned() {
- if (mPhone != null && mImsManager != null) {
- return mImsManager.isWfcEnabledByPlatform(mPhone.getContext())
- && mImsManager.isWfcProvisionedOnDevice(mPhone.getContext());
+ if (mImsManager != null) {
+ return mImsManager.isWfcEnabledByPlatform()
+ && mImsManager.isWfcProvisionedOnDevice();
}
return false;
}
@@ -1594,13 +1557,16 @@
return provisioned;
}
- private static boolean isEabEnabledByPlatform(Context context) {
- if (context != null) {
+ private boolean isEabEnabledByPlatform() {
+ if (mPhone != null) {
CarrierConfigManager configManager = (CarrierConfigManager)
- context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
- if (configManager != null && configManager.getConfig().getBoolean(
- CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL)) {
- return true;
+ mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
+ if (b != null) {
+ return b.getBoolean(
+ CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, false) || b.getBoolean(
+ CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL,
+ false);
}
}
return false;
@@ -1617,25 +1583,25 @@
mImsVolteProvisionedSwitch.setChecked(isImsVolteProvisioned());
mImsVolteProvisionedSwitch.setOnCheckedChangeListener(mImsVolteCheckedChangeListener);
mImsVolteProvisionedSwitch.setEnabled(!IS_USER_BUILD
- && mImsManager.isVolteEnabledByPlatform(mPhone.getContext()));
+ && mImsManager.isVolteEnabledByPlatform());
mImsVtProvisionedSwitch.setOnCheckedChangeListener(null);
mImsVtProvisionedSwitch.setChecked(isImsVtProvisioned());
mImsVtProvisionedSwitch.setOnCheckedChangeListener(mImsVtCheckedChangeListener);
mImsVtProvisionedSwitch.setEnabled(!IS_USER_BUILD
- && mImsManager.isVtEnabledByPlatform(mPhone.getContext()));
+ && mImsManager.isVtEnabledByPlatform());
mImsWfcProvisionedSwitch.setOnCheckedChangeListener(null);
mImsWfcProvisionedSwitch.setChecked(isImsWfcProvisioned());
mImsWfcProvisionedSwitch.setOnCheckedChangeListener(mImsWfcCheckedChangeListener);
mImsWfcProvisionedSwitch.setEnabled(!IS_USER_BUILD
- && mImsManager.isWfcEnabledByPlatform(mPhone.getContext()));
+ && mImsManager.isWfcEnabledByPlatform());
mEabProvisionedSwitch.setOnCheckedChangeListener(null);
mEabProvisionedSwitch.setChecked(isEabProvisioned());
mEabProvisionedSwitch.setOnCheckedChangeListener(mEabCheckedChangeListener);
mEabProvisionedSwitch.setEnabled(!IS_USER_BUILD
- && isEabEnabledByPlatform(mPhone.getContext()));
+ && isEabEnabledByPlatform());
}
OnClickListener mDnsCheckButtonHandler = new OnClickListener() {
@@ -1683,21 +1649,23 @@
}
};
- OnClickListener mCarrierProvisioningButtonHandler = new OnClickListener() {
- public void onClick(View v) {
- final Intent intent = new Intent("com.android.settings.CARRIER_PROVISIONING");
- final ComponentName serviceComponent = ComponentName.unflattenFromString(
- "com.android.omadm.service/.DMIntentReceiver");
+ OnClickListener mCarrierProvisioningButtonHandler = v -> {
+ String carrierProvisioningApp = getCarrierProvisioningAppString();
+ if (!TextUtils.isEmpty(carrierProvisioningApp)) {
+ final Intent intent = new Intent(CARRIER_PROVISIONING_ACTION);
+ final ComponentName serviceComponent =
+ ComponentName.unflattenFromString(carrierProvisioningApp);
intent.setComponent(serviceComponent);
sendBroadcast(intent);
}
};
- OnClickListener mTriggerCarrierProvisioningButtonHandler = new OnClickListener() {
- public void onClick(View v) {
- final Intent intent = new Intent("com.android.settings.TRIGGER_CARRIER_PROVISIONING");
- final ComponentName serviceComponent = ComponentName.unflattenFromString(
- "com.android.omadm.service/.DMIntentReceiver");
+ OnClickListener mTriggerCarrierProvisioningButtonHandler = v -> {
+ String carrierProvisioningApp = getCarrierProvisioningAppString();
+ if (!TextUtils.isEmpty(carrierProvisioningApp)) {
+ final Intent intent = new Intent(TRIGGER_CARRIER_PROVISIONING_ACTION);
+ final ComponentName serviceComponent =
+ ComponentName.unflattenFromString(carrierProvisioningApp);
intent.setComponent(serviceComponent);
sendBroadcast(intent);
}
@@ -1710,21 +1678,11 @@
if (mPreferredNetworkTypeResult != pos && pos >= 0
&& pos <= PREFERRED_NETWORK_LABELS.length - 2) {
mPreferredNetworkTypeResult = pos;
-
- // TODO: Possibly migrate this to TelephonyManager.setPreferredNetworkType()
- // which today still has some issues (mostly that the "set" is conditional
- // on a successful modem call, which is not what we want). Instead we always
- // want this setting to be set, so that if the radio hiccups and this setting
- // is for some reason unsuccessful, future calls to the radio will reflect
- // the users's preference which is set here.
- final int subId = mPhone.getSubId();
- if (SubscriptionManager.isUsableSubIdValue(subId)) {
- Settings.Global.putInt(mPhone.getContext().getContentResolver(),
- PREFERRED_NETWORK_MODE + subId, mPreferredNetworkTypeResult);
- }
- log("Calling setPreferredNetworkType(" + mPreferredNetworkTypeResult + ")");
- Message msg = mHandler.obtainMessage(EVENT_SET_PREFERRED_TYPE_DONE);
- mPhone.setPreferredNetworkType(mPreferredNetworkTypeResult, msg);
+ new Thread(() -> {
+ mTelephonyManager.setAllowedNetworkTypesForReason(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER,
+ RadioAccessFamily.getRafFromNetworkType(mPreferredNetworkTypeResult));
+ }).start();
}
}
@@ -1772,6 +1730,19 @@
}
};
+ private String getCarrierProvisioningAppString() {
+ if (mPhone != null) {
+ CarrierConfigManager configManager =
+ mPhone.getContext().getSystemService(CarrierConfigManager.class);
+ PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
+ if (b != null) {
+ return b.getString(
+ CarrierConfigManager.KEY_CARRIER_PROVISIONING_APP_STRING, "");
+ }
+ }
+ return "";
+ }
+
boolean isCbrsSupported() {
return getResources().getBoolean(
com.android.internal.R.bool.config_cbrs_supported);
diff --git a/src/com/android/phone/settings/SuppServicesUiUtil.java b/src/com/android/phone/settings/SuppServicesUiUtil.java
index 4e9841f..4f1a79f 100644
--- a/src/com/android/phone/settings/SuppServicesUiUtil.java
+++ b/src/com/android/phone/settings/SuppServicesUiUtil.java
@@ -84,7 +84,7 @@
.create();
}
- private static String makeMessage(Context context, String preferenceKey, Phone phone) {
+ public static String makeMessage(Context context, String preferenceKey, Phone phone) {
String message = "";
int simSlot = (phone.getPhoneId() == 0) ? 1 : 2;
String suppServiceName = getSuppServiceName(context, preferenceKey);
diff --git a/src/com/android/phone/settings/VoicemailSettingsActivity.java b/src/com/android/phone/settings/VoicemailSettingsActivity.java
index 886ce3c..c940748 100644
--- a/src/com/android/phone/settings/VoicemailSettingsActivity.java
+++ b/src/com/android/phone/settings/VoicemailSettingsActivity.java
@@ -245,6 +245,10 @@
mVoicemailNotificationPreference =
findPreference(getString(R.string.voicemail_notifications_key));
+ if (mSubMenuVoicemailSettings == null) {
+ mSubMenuVoicemailSettings =
+ (EditPhoneNumberPreference) findPreference(BUTTON_VOICEMAIL_KEY);
+ }
final Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_CHANNEL_ID,
NotificationChannelController.CHANNEL_ID_VOICE_MAIL);
@@ -257,12 +261,6 @@
super.onResume();
mForeground = true;
- PreferenceScreen prefSet = getPreferenceScreen();
-
- if (mSubMenuVoicemailSettings == null) {
- mSubMenuVoicemailSettings =
- (EditPhoneNumberPreference) findPreference(BUTTON_VOICEMAIL_KEY);
- }
if (mSubMenuVoicemailSettings != null) {
mSubMenuVoicemailSettings.setParentActivity(this, VOICEMAIL_PREF_ID, this);
mSubMenuVoicemailSettings.setDialogOnClosedListener(this);
diff --git a/src/com/android/phone/settings/fdn/BaseFdnContactScreen.java b/src/com/android/phone/settings/fdn/BaseFdnContactScreen.java
new file mode 100644
index 0000000..5beff34
--- /dev/null
+++ b/src/com/android/phone/settings/fdn/BaseFdnContactScreen.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2020 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.phone.settings.fdn;
+
+import static android.view.Window.PROGRESS_VISIBILITY_OFF;
+import static android.view.Window.PROGRESS_VISIBILITY_ON;
+
+import android.app.Activity;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.AsyncQueryHandler;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.Window;
+import android.widget.Toast;
+
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.Phone;
+import com.android.phone.PhoneGlobals;
+import com.android.phone.R;
+import com.android.phone.SubscriptionInfoHelper;
+
+/**
+ * Base activity for FDN contact screen.
+ */
+public abstract class BaseFdnContactScreen extends Activity
+ implements Pin2LockedDialogFragment.Listener {
+ protected static final String LOG_TAG = PhoneGlobals.LOG_TAG;
+ protected static final boolean DBG = false;
+
+ protected static final int EVENT_PIN2_ENTRY_COMPLETE = 10;
+ protected static final int PIN2_REQUEST_CODE = 100;
+
+ protected static final String INTENT_EXTRA_NAME = "name";
+ protected static final String INTENT_EXTRA_NUMBER = "number";
+
+ protected String mName;
+ protected String mNumber;
+ protected String mPin2;
+
+ protected SubscriptionInfoHelper mSubscriptionInfoHelper;
+ protected BaseFdnContactScreen.QueryHandler mQueryHandler;
+
+ protected Handler mHandler = new Handler();
+ protected Phone mPhone;
+
+ protected abstract void pin2AuthenticationSucceed();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ resolveIntent();
+ getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+ }
+
+ protected void authenticatePin2() {
+ Intent intent = new Intent();
+ intent.setClass(this, GetPin2Screen.class);
+ intent.setData(FdnList.getContentUri(mSubscriptionInfoHelper));
+ startActivityForResult(intent, PIN2_REQUEST_CODE);
+ }
+
+ protected void displayProgress(boolean flag) {
+ getWindow().setFeatureInt(
+ Window.FEATURE_INDETERMINATE_PROGRESS,
+ flag ? PROGRESS_VISIBILITY_ON : PROGRESS_VISIBILITY_OFF);
+ }
+
+ protected void handleResult(boolean success) {
+ }
+
+ protected void handleResult(boolean success, boolean invalidNumber) {
+ }
+
+ protected void log(String msg) {
+ Log.d(LOG_TAG, getClass().getSimpleName() + " : " + msg);
+ }
+
+ // Add method to check if Pin2 supplied is correct.
+ protected void processPin2(String pin) {
+ Message onComplete = mFDNHandler
+ .obtainMessage(EVENT_PIN2_ENTRY_COMPLETE);
+ mPhone.getIccCard().supplyPin2(pin, onComplete);
+ }
+
+ protected void resolveIntent() {
+ Intent intent = getIntent();
+
+ mSubscriptionInfoHelper = new SubscriptionInfoHelper(this, intent);
+ mPhone = mSubscriptionInfoHelper.getPhone();
+
+ mName = intent.getStringExtra(INTENT_EXTRA_NAME);
+ mNumber = intent.getStringExtra(INTENT_EXTRA_NUMBER);
+ }
+
+ /**
+ * Removed the status field, with preference to displaying a toast
+ * to match the rest of settings UI.
+ */
+ protected void showStatus(CharSequence statusMsg) {
+ if (statusMsg != null) {
+ Toast.makeText(this, statusMsg, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ private Handler mFDNHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_PIN2_ENTRY_COMPLETE:
+ AsyncResult ar = (AsyncResult) msg.obj;
+ if (ar.exception != null) {
+ // see if PUK2 is requested and alert the user accordingly.
+ CommandException ce = (CommandException) ar.exception;
+ if (ce.getCommandError() == CommandException.Error.SIM_PUK2) {
+ // make sure we set the PUK2 state so that we can skip some
+ // redundant behaviour.
+ showPin2LockedDialog();
+ } else {
+ final int attemptsRemaining = msg.arg1;
+ if (attemptsRemaining > 0) {
+ Toast.makeText(
+ BaseFdnContactScreen.this,
+ getString(R.string.pin2_invalid)
+ + getString(R.string.pin2_attempts,
+ attemptsRemaining), Toast.LENGTH_LONG)
+ .show();
+ finish();
+ }
+ }
+ } else {
+ pin2AuthenticationSucceed();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ protected class QueryHandler extends AsyncQueryHandler {
+ protected QueryHandler(ContentResolver cr) {
+ super(cr);
+ }
+
+ @Override
+ protected void onInsertComplete(int token, Object cookie, Uri uri) {
+ if (DBG) log("onInsertComplete");
+ displayProgress(false);
+ handleResult(uri != null, false);
+ }
+
+ @Override
+ protected void onUpdateComplete(int token, Object cookie, int result) {
+ if (DBG) log("onUpdateComplete");
+ displayProgress(false);
+ handleResult(result > 0, false);
+ }
+
+ @Override
+ protected void onDeleteComplete(int token, Object cookie, int result) {
+ if (DBG) log("onDeleteComplete");
+ displayProgress(false);
+ handleResult(result > 0);
+ }
+ }
+
+ private void showPin2LockedDialog() {
+ final FragmentManager fragmentManager = getFragmentManager();
+ Pin2LockedDialogFragment dialogFragment = (Pin2LockedDialogFragment) fragmentManager
+ .findFragmentByTag(Pin2LockedDialogFragment.TAG_PIN2_LOCKED_DIALOG);
+ if (dialogFragment == null) {
+ dialogFragment = new Pin2LockedDialogFragment();
+ Bundle args = new Bundle();
+ args.putInt(Pin2LockedDialogFragment.KEY_DIALOG_ID,
+ Pin2LockedDialogFragment.DIALOG_ID_PUK2_REQUESTED_ON_PIN_ENTRY);
+ dialogFragment.setArguments(args);
+ dialogFragment.show(fragmentManager, Pin2LockedDialogFragment.TAG_PIN2_LOCKED_DIALOG);
+ } else {
+ FragmentTransaction transaction = fragmentManager.beginTransaction();
+ transaction.show(dialogFragment);
+ transaction.commitNow();
+ }
+ }
+
+ @Override
+ public void onRequestPuk2(int id) {
+ finish();
+ }
+}
diff --git a/src/com/android/phone/settings/fdn/DeleteFdnContactScreen.java b/src/com/android/phone/settings/fdn/DeleteFdnContactScreen.java
index 92baa97..7cd4c93 100644
--- a/src/com/android/phone/settings/fdn/DeleteFdnContactScreen.java
+++ b/src/com/android/phone/settings/fdn/DeleteFdnContactScreen.java
@@ -16,57 +16,24 @@
package com.android.phone.settings.fdn;
-import static android.view.Window.PROGRESS_VISIBILITY_OFF;
-import static android.view.Window.PROGRESS_VISIBILITY_ON;
-
-import android.app.Activity;
-import android.content.AsyncQueryHandler;
-import android.content.ContentResolver;
import android.content.Intent;
-import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
import android.text.TextUtils;
-import android.util.Log;
-import android.view.Window;
-import android.widget.Toast;
-import com.android.phone.PhoneGlobals;
import com.android.phone.R;
-import com.android.phone.SubscriptionInfoHelper;
/**
* Activity to let the user delete an FDN contact.
*/
-public class DeleteFdnContactScreen extends Activity {
- private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
- private static final boolean DBG = false;
-
- private static final String INTENT_EXTRA_NAME = "name";
- private static final String INTENT_EXTRA_NUMBER = "number";
-
- private static final int PIN2_REQUEST_CODE = 100;
-
- private SubscriptionInfoHelper mSubscriptionInfoHelper;
-
- private String mName;
- private String mNumber;
- private String mPin2;
-
- protected QueryHandler mQueryHandler;
-
- private Handler mHandler = new Handler();
+public class DeleteFdnContactScreen extends BaseFdnContactScreen {
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
- resolveIntent();
-
- authenticatePin2();
-
- getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+ // Starts PIN2 authentication only for the first time.
+ if (icicle == null) authenticatePin2();
setContentView(R.layout.delete_fdn_contact_screen);
}
@@ -79,9 +46,7 @@
Bundle extras = (intent != null) ? intent.getExtras() : null;
if (extras != null) {
mPin2 = extras.getString("pin2");
- showStatus(getResources().getText(
- R.string.deleting_fdn_contact));
- deleteContact();
+ processPin2(mPin2);
} else {
// if they cancelled, then we just cancel too.
if (DBG) log("onActivityResult: CANCELLED");
@@ -92,13 +57,9 @@
}
}
- private void resolveIntent() {
- Intent intent = getIntent();
-
- mSubscriptionInfoHelper = new SubscriptionInfoHelper(this, intent);
-
- mName = intent.getStringExtra(INTENT_EXTRA_NAME);
- mNumber = intent.getStringExtra(INTENT_EXTRA_NUMBER);
+ @Override
+ protected void resolveIntent() {
+ super.resolveIntent();
if (TextUtils.isEmpty(mNumber)) {
finish();
@@ -126,29 +87,8 @@
displayProgress(true);
}
- private void authenticatePin2() {
- Intent intent = new Intent();
- intent.setClass(this, GetPin2Screen.class);
- intent.setData(FdnList.getContentUri(mSubscriptionInfoHelper));
- startActivityForResult(intent, PIN2_REQUEST_CODE);
- }
-
- private void displayProgress(boolean flag) {
- getWindow().setFeatureInt(
- Window.FEATURE_INDETERMINATE_PROGRESS,
- flag ? PROGRESS_VISIBILITY_ON : PROGRESS_VISIBILITY_OFF);
- }
-
- // Replace the status field with a toast to make things appear similar
- // to the rest of the settings. Removed the useless status field.
- private void showStatus(CharSequence statusMsg) {
- if (statusMsg != null) {
- Toast.makeText(this, statusMsg, Toast.LENGTH_SHORT)
- .show();
- }
- }
-
- private void handleResult(boolean success) {
+ @Override
+ protected void handleResult(boolean success) {
if (success) {
if (DBG) log("handleResult: success!");
showStatus(getResources().getText(R.string.fdn_contact_deleted));
@@ -156,43 +96,12 @@
if (DBG) log("handleResult: failed!");
showStatus(getResources().getText(R.string.pin2_invalid));
}
-
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- finish();
- }
- }, 2000);
-
+ mHandler.postDelayed(() -> finish(), 2000);
}
- private class QueryHandler extends AsyncQueryHandler {
- public QueryHandler(ContentResolver cr) {
- super(cr);
- }
-
- @Override
- protected void onQueryComplete(int token, Object cookie, Cursor c) {
- }
-
- @Override
- protected void onInsertComplete(int token, Object cookie, Uri uri) {
- }
-
- @Override
- protected void onUpdateComplete(int token, Object cookie, int result) {
- }
-
- @Override
- protected void onDeleteComplete(int token, Object cookie, int result) {
- if (DBG) log("onDeleteComplete");
- displayProgress(false);
- handleResult(result > 0);
- }
-
- }
-
- private void log(String msg) {
- Log.d(LOG_TAG, "[DeleteFdnContact] " + msg);
+ @Override
+ protected void pin2AuthenticationSucceed() {
+ showStatus(getResources().getText(R.string.deleting_fdn_contact));
+ deleteContact();
}
}
diff --git a/src/com/android/phone/settings/fdn/EditFdnContactScreen.java b/src/com/android/phone/settings/fdn/EditFdnContactScreen.java
index edb9f8e..0884e12 100644
--- a/src/com/android/phone/settings/fdn/EditFdnContactScreen.java
+++ b/src/com/android/phone/settings/fdn/EditFdnContactScreen.java
@@ -16,26 +16,21 @@
package com.android.phone.settings.fdn;
-import static android.view.Window.PROGRESS_VISIBILITY_OFF;
-import static android.view.Window.PROGRESS_VISIBILITY_ON;
+
import static android.app.Activity.RESULT_OK;
-
-import android.app.Activity;
-import android.content.AsyncQueryHandler;
import android.content.ContentProvider;
-import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.UserHandle;
import android.provider.ContactsContract.CommonDataKinds;
+import android.telephony.CarrierConfigManager;
import android.telephony.PhoneNumberUtils;
import android.text.Editable;
import android.text.Selection;
@@ -47,50 +42,31 @@
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
-import android.view.Window;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
-import android.widget.Toast;
import com.android.internal.telephony.PhoneFactory;
import com.android.phone.PhoneGlobals;
import com.android.phone.R;
-import com.android.phone.SubscriptionInfoHelper;
-import android.telephony.CarrierConfigManager;
/**
* Activity to let the user add or edit an FDN contact.
*/
-public class EditFdnContactScreen extends Activity {
- private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
- private static final boolean DBG = false;
+public class EditFdnContactScreen extends BaseFdnContactScreen {
// Menu item codes
private static final int MENU_IMPORT = 1;
private static final int MENU_DELETE = 2;
- private static final String INTENT_EXTRA_NAME = "name";
- private static final String INTENT_EXTRA_NUMBER = "number";
-
- private static final int PIN2_REQUEST_CODE = 100;
-
- private SubscriptionInfoHelper mSubscriptionInfoHelper;
-
- private String mName;
- private String mNumber;
- private String mPin2;
private boolean mAddContact;
- private QueryHandler mQueryHandler;
private EditText mNameField;
private EditText mNumberField;
private LinearLayout mPinFieldContainer;
private Button mButton;
- private Handler mHandler = new Handler();
-
/**
* Constants used in importing from contacts
*/
@@ -113,13 +89,10 @@
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
- resolveIntent();
-
- getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.edit_fdn_contact_screen);
setupView();
setTitle(mAddContact ? R.string.add_fdn_contact : R.string.edit_fdn_contact);
- PersistableBundle b = null;
+ PersistableBundle b;
if (mSubscriptionInfoHelper.hasSubId()) {
b = PhoneGlobals.getInstance().getCarrierConfigForSubId(
mSubscriptionInfoHelper.getSubId());
@@ -150,11 +123,7 @@
Bundle extras = (intent != null) ? intent.getExtras() : null;
if (extras != null) {
mPin2 = extras.getString("pin2");
- if (mAddContact) {
- addContact();
- } else {
- updateContact();
- }
+ processPin2(mPin2);
} else if (resultCode != RESULT_OK) {
// if they cancelled, then we just cancel too.
if (DBG) log("onActivityResult: cancelled.");
@@ -244,20 +213,15 @@
return super.onOptionsItemSelected(item);
}
- private void resolveIntent() {
- Intent intent = getIntent();
-
- mSubscriptionInfoHelper = new SubscriptionInfoHelper(this, intent);
-
- mName = intent.getStringExtra(INTENT_EXTRA_NAME);
- mNumber = intent.getStringExtra(INTENT_EXTRA_NUMBER);
-
+ @Override
+ protected void resolveIntent() {
+ super.resolveIntent();
mAddContact = TextUtils.isEmpty(mNumber);
}
/**
* We have multiple layouts, one to indicate that the user needs to
- * open the keyboard to enter information (if the keybord is hidden).
+ * open the keyboard to enter information (if the keyboard is hidden).
* So, we need to make sure that the layout here matches that in the
* layout file.
*/
@@ -387,36 +351,18 @@
finish();
}
- private void authenticatePin2() {
- Intent intent = new Intent();
- intent.setClass(this, GetPin2Screen.class);
- intent.setData(FdnList.getContentUri(mSubscriptionInfoHelper));
- startActivityForResult(intent, PIN2_REQUEST_CODE);
- }
-
- private void displayProgress(boolean flag) {
+ @Override
+ protected void displayProgress(boolean flag) {
+ super.displayProgress(flag);
// indicate we are busy.
mDataBusy = flag;
- getWindow().setFeatureInt(
- Window.FEATURE_INDETERMINATE_PROGRESS,
- mDataBusy ? PROGRESS_VISIBILITY_ON : PROGRESS_VISIBILITY_OFF);
// make sure we don't allow calls to save when we're
// not ready for them.
mButton.setClickable(!mDataBusy);
}
- /**
- * Removed the status field, with preference to displaying a toast
- * to match the rest of settings UI.
- */
- private void showStatus(CharSequence statusMsg) {
- if (statusMsg != null) {
- Toast.makeText(this, statusMsg, Toast.LENGTH_LONG)
- .show();
- }
- }
-
- private void handleResult(boolean success, boolean invalidNumber) {
+ @Override
+ protected void handleResult(boolean success, boolean invalidNumber) {
if (success) {
if (DBG) log("handleResult: success!");
showStatus(getResources().getText(mAddContact ?
@@ -439,13 +385,7 @@
}
}
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- finish();
- }
- }, 2000);
-
+ mHandler.postDelayed(() -> finish(), 2000);
}
private final View.OnClickListener mClicked = new View.OnClickListener() {
@@ -499,35 +439,12 @@
}
};
- private class QueryHandler extends AsyncQueryHandler {
- public QueryHandler(ContentResolver cr) {
- super(cr);
+ @Override
+ protected void pin2AuthenticationSucceed() {
+ if (mAddContact) {
+ addContact();
+ } else {
+ updateContact();
}
-
- @Override
- protected void onQueryComplete(int token, Object cookie, Cursor c) {
- }
-
- @Override
- protected void onInsertComplete(int token, Object cookie, Uri uri) {
- if (DBG) log("onInsertComplete");
- displayProgress(false);
- handleResult(uri != null, false);
- }
-
- @Override
- protected void onUpdateComplete(int token, Object cookie, int result) {
- if (DBG) log("onUpdateComplete");
- displayProgress(false);
- handleResult(result > 0, false);
- }
-
- @Override
- protected void onDeleteComplete(int token, Object cookie, int result) {
- }
- }
-
- private void log(String msg) {
- Log.d(LOG_TAG, "[EditFdnContact] " + msg);
}
}
diff --git a/src/com/android/phone/settings/fdn/FdnSetting.java b/src/com/android/phone/settings/fdn/FdnSetting.java
index 8b5afa4..8f46c85 100644
--- a/src/com/android/phone/settings/fdn/FdnSetting.java
+++ b/src/com/android/phone/settings/fdn/FdnSetting.java
@@ -17,8 +17,8 @@
package com.android.phone.settings.fdn;
import android.app.ActionBar;
-import android.app.AlertDialog;
-import android.content.DialogInterface;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
import android.os.AsyncResult;
import android.os.Bundle;
import android.os.Handler;
@@ -27,7 +27,6 @@
import android.preference.PreferenceScreen;
import android.util.Log;
import android.view.MenuItem;
-import android.view.WindowManager;
import android.widget.Toast;
import com.android.internal.telephony.CommandException;
@@ -42,7 +41,7 @@
* Rewritten to look and behave closer to the other preferences.
*/
public class FdnSetting extends PreferenceActivity
- implements EditPinPreference.OnPinEnteredListener, DialogInterface.OnCancelListener {
+ implements EditPinPreference.OnPinEnteredListener, Pin2LockedDialogFragment.Listener {
private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
private static final boolean DBG = false;
@@ -56,6 +55,7 @@
*/
private static final int EVENT_PIN2_ENTRY_COMPLETE = 100;
private static final int EVENT_PIN2_CHANGE_COMPLETE = 200;
+ private static final int EVENT_PIN2_CHANGE_COMPLETE_TOGGLE_FDN = 300;
// String keys for preference lookup
private static final String BUTTON_FDN_ENABLE_KEY = "button_fdn_enable_key";
@@ -82,8 +82,11 @@
private static final String PIN_CHANGE_STATE_KEY = "pin_change_state_key";
private static final String OLD_PIN_KEY = "old_pin_key";
private static final String NEW_PIN_KEY = "new_pin_key";
+ private static final String PUK_KEY = "puk_key";
private static final String DIALOG_MESSAGE_KEY = "dialog_message_key";
private static final String DIALOG_PIN_ENTRY_KEY = "dialog_pin_entry_key";
+ private static final String FDN_DIALOG_MESSAGE_KEY = "fdn_dialog_message_key";
+ private static final String FDN_DIALOG_PIN_ENTRY_KEY = "fdn_dialog_pin_entry_key";
// size limits for the pin.
private static final int MIN_PIN_LENGTH = 4;
@@ -94,10 +97,10 @@
*/
@Override
public void onPinEntered(EditPinPreference preference, boolean positiveResult) {
- if (preference == mButtonEnableFDN) {
+ if (preference == mButtonEnableFDN && (!mIsPuk2Locked || !positiveResult)) {
toggleFDNEnable(positiveResult);
- } else if (preference == mButtonChangePin2){
- updatePINChangeState(positiveResult);
+ } else {
+ updatePINChangeState(preference, positiveResult);
}
}
@@ -106,6 +109,12 @@
*/
private void toggleFDNEnable(boolean positiveResult) {
if (!positiveResult) {
+ // reset the state on cancel, either to expect PUK2 or PIN2
+ if (!mIsPuk2Locked) {
+ resetPinChangeState();
+ } else {
+ resetPinChangeStateForPUK2();
+ }
return;
}
@@ -129,10 +138,10 @@
/**
* Attempt to change the pin.
*/
- private void updatePINChangeState(boolean positiveResult) {
+ private void updatePINChangeState(EditPinPreference button, boolean positiveResult) {
if (DBG) log("updatePINChangeState positive=" + positiveResult
+ " mPinChangeState=" + mPinChangeState
- + " mSkipOldPin=" + mIsPuk2Locked);
+ + " mIsPuk2Locked=" + mIsPuk2Locked);
if (!positiveResult) {
// reset the state on cancel, either to expect PUK2 or PIN2
@@ -155,80 +164,95 @@
// appears with text to indicate what the issue is.
switch (mPinChangeState) {
case PIN_CHANGE_OLD:
- mOldPin = mButtonChangePin2.getText();
- mButtonChangePin2.setText("");
+ mOldPin = button.getText();
+ button.setText("");
// if the pin is not valid, display a message and reset the state.
if (validatePin (mOldPin, false)) {
mPinChangeState = PIN_CHANGE_NEW;
- displayPinChangeDialog();
+ displayPinChangeDialog(button);
} else {
- displayPinChangeDialog(R.string.invalidPin2, true);
+ displayPinChangeDialog(button, R.string.invalidPin2, true);
}
break;
case PIN_CHANGE_NEW:
- mNewPin = mButtonChangePin2.getText();
- mButtonChangePin2.setText("");
+ mNewPin = button.getText();
+ button.setText("");
// if the new pin is not valid, display a message and reset the state.
if (validatePin (mNewPin, false)) {
mPinChangeState = PIN_CHANGE_REENTER;
- displayPinChangeDialog();
+ displayPinChangeDialog(button);
} else {
- displayPinChangeDialog(R.string.invalidPin2, true);
+ displayPinChangeDialog(button, R.string.invalidPin2, true);
}
break;
case PIN_CHANGE_REENTER:
- // if the re-entered pin is not valid, display a message and reset the state.
- if (!mNewPin.equals(mButtonChangePin2.getText())) {
- mPinChangeState = PIN_CHANGE_NEW;
- mButtonChangePin2.setText("");
- displayPinChangeDialog(R.string.mismatchPin2, true);
+ if (validatePin(button.getText(), false)) {
+ // if the re-entered pin is not valid, display a message and reset the state.
+ if (!mNewPin.equals(button.getText())) {
+ mPinChangeState = PIN_CHANGE_NEW;
+ button.setText("");
+ displayPinChangeDialog(button, R.string.mismatchPin2, true);
+ } else {
+ // If the PIN is valid, then we submit the change PIN request or
+ // display the PUK2 dialog if we KNOW that we're PUK2 locked.
+ button.setText("");
+ Message onComplete = mFDNHandler.obtainMessage(
+ EVENT_PIN2_CHANGE_COMPLETE);
+ if (!mIsPuk2Locked) {
+ mPhone.getIccCard().changeIccFdnPassword(mOldPin,
+ mNewPin, onComplete);
+ } else {
+ mPhone.getIccCard().supplyPuk2(mPuk2, mNewPin,
+ onComplete);
+ }
+ }
} else {
- // If the PIN is valid, then we submit the change PIN request.
- mButtonChangePin2.setText("");
- Message onComplete = mFDNHandler.obtainMessage(
- EVENT_PIN2_CHANGE_COMPLETE);
- mPhone.getIccCard().changeIccFdnPassword(
- mOldPin, mNewPin, onComplete);
+ button.setText("");
+ displayPinChangeDialog(button, R.string.invalidPin2, true);
}
break;
- case PIN_CHANGE_PUK: {
- // Doh! too many incorrect requests, PUK requested.
- mPuk2 = mButtonChangePin2.getText();
- mButtonChangePin2.setText("");
- // if the puk is not valid, display
- // a message and reset the state.
- if (validatePin (mPuk2, true)) {
- mPinChangeState = PIN_CHANGE_NEW_PIN_FOR_PUK;
- displayPinChangeDialog();
- } else {
- displayPinChangeDialog(R.string.invalidPuk2, true);
- }
+ case PIN_CHANGE_PUK:
+ // Doh! too many incorrect requests, PUK requested.
+ mPuk2 = button.getText();
+ button.setText("");
+ // if the puk is not valid, display
+ // a message and reset the state.
+ if (validatePin(mPuk2, true)) {
+ mPinChangeState = PIN_CHANGE_NEW_PIN_FOR_PUK;
+ displayPinChangeDialog(button);
+ } else {
+ displayPinChangeDialog(button, R.string.invalidPuk2, true);
}
break;
case PIN_CHANGE_NEW_PIN_FOR_PUK:
- mNewPin = mButtonChangePin2.getText();
- mButtonChangePin2.setText("");
+ mNewPin = button.getText();
+ button.setText("");
// if the new pin is not valid, display
// a message and reset the state.
if (validatePin (mNewPin, false)) {
mPinChangeState = PIN_CHANGE_REENTER_PIN_FOR_PUK;
- displayPinChangeDialog();
+ displayPinChangeDialog(button);
} else {
- displayPinChangeDialog(R.string.invalidPin2, true);
+ displayPinChangeDialog(button, R.string.invalidPin2, true);
}
break;
case PIN_CHANGE_REENTER_PIN_FOR_PUK:
// if the re-entered pin is not valid, display
// a message and reset the state.
- if (!mNewPin.equals(mButtonChangePin2.getText())) {
+ if (!mNewPin.equals(button.getText())) {
mPinChangeState = PIN_CHANGE_NEW_PIN_FOR_PUK;
- mButtonChangePin2.setText("");
- displayPinChangeDialog(R.string.mismatchPin2, true);
+ button.setText("");
+ displayPinChangeDialog(button, R.string.mismatchPin2, true);
} else {
// Both puk2 and new pin2 are ready to submit
- mButtonChangePin2.setText("");
- Message onComplete = mFDNHandler.obtainMessage(
- EVENT_PIN2_CHANGE_COMPLETE);
+ Message onComplete = null;
+ if (button == mButtonChangePin2) {
+ button.setText("");
+ onComplete = mFDNHandler.obtainMessage(EVENT_PIN2_CHANGE_COMPLETE);
+ } else {
+ onComplete = mFDNHandler.obtainMessage(
+ EVENT_PIN2_CHANGE_COMPLETE_TOGGLE_FDN);
+ }
mPhone.getIccCard().supplyPuk2(mPuk2, mNewPin, onComplete);
}
break;
@@ -246,6 +270,7 @@
// when we are enabling FDN, either we are unsuccessful and display
// a toast, or just update the UI.
case EVENT_PIN2_ENTRY_COMPLETE: {
+ if (DBG) log("Handle EVENT_PIN2_ENTRY_COMPLETE");
AsyncResult ar = (AsyncResult) msg.obj;
if (ar.exception != null) {
if (ar.exception instanceof CommandException) {
@@ -255,11 +280,8 @@
((CommandException) ar.exception).getCommandError();
switch (e) {
case SIM_PUK2:
- // make sure we set the PUK2 state so that we can skip
- // some redundant behaviour.
- displayMessage(R.string.fdn_enable_puk2_requested,
- attemptsRemaining);
- resetPinChangeStateForPUK2();
+ showPin2OrPuk2LockedDialog(Pin2LockedDialogFragment
+ .DIALOG_ID_PUK2_REQUESTED_ON_PIN_ENTRY);
break;
case PASSWORD_INCORRECT:
displayMessage(R.string.pin2_invalid, attemptsRemaining);
@@ -279,7 +301,8 @@
// when changing the pin we need to pay attention to whether or not
// the error requests a PUK (usually after too many incorrect tries)
// Set the state accordingly.
- case EVENT_PIN2_CHANGE_COMPLETE: {
+ case EVENT_PIN2_CHANGE_COMPLETE:
+ case EVENT_PIN2_CHANGE_COMPLETE_TOGGLE_FDN: {
if (DBG)
log("Handle EVENT_PIN2_CHANGE_COMPLETE");
AsyncResult ar = (AsyncResult) msg.obj;
@@ -291,34 +314,24 @@
CommandException ce = (CommandException) ar.exception;
if (ce.getCommandError() == CommandException.Error.SIM_PUK2) {
// throw an alert dialog on the screen, displaying the
- // request for a PUK2. set the cancel listener to
- // FdnSetting.onCancel().
- AlertDialog a = new AlertDialog.Builder(FdnSetting.this)
- .setMessage(R.string.puk2_requested)
- .setCancelable(true)
- .setOnCancelListener(FdnSetting.this)
- .setNeutralButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog,
- int which) {
- resetPinChangeStateForPUK2();
- displayPinChangeDialog(0,true);
- }
- })
- .create();
- a.getWindow().addFlags(
- WindowManager.LayoutParams.FLAG_DIM_BEHIND);
- a.show();
+ // request for a PUK2.
+ showPin2OrPuk2LockedDialog(Pin2LockedDialogFragment
+ .DIALOG_ID_PUK2_REQUESTED_ON_PIN_CHANGED);
} else {
- // set the correct error message depending upon the state.
- // Reset the state depending upon or knowledge of the PUK state.
- if (!mIsPuk2Locked) {
- displayMessage(R.string.badPin2, attemptsRemaining);
- resetPinChangeState();
+ if (mIsPuk2Locked && attemptsRemaining == 0) {
+ showPin2OrPuk2LockedDialog(Pin2LockedDialogFragment
+ .DIALOG_ID_PUK2_LOCKED_OUT);
} else {
- displayMessage(R.string.badPuk2, attemptsRemaining);
- resetPinChangeStateForPUK2();
+ // set the correct error message depending upon the state.
+ // Reset the state depending upon or knowledge of the PUK
+ // state.
+ if (!mIsPuk2Locked) {
+ displayMessage(R.string.badPin2, attemptsRemaining);
+ resetPinChangeState();
+ } else {
+ displayMessage(R.string.badPuk2, attemptsRemaining);
+ resetPinChangeStateForPUK2();
+ }
}
}
} else {
@@ -332,28 +345,25 @@
}
// reset to normal behaviour on successful change.
+ if (msg.what == EVENT_PIN2_CHANGE_COMPLETE_TOGGLE_FDN) {
+ log("Handle EVENT_PIN2_CHANGE_COMPLETE_TOGGLE_FDN");
+ // activate/deactivate FDN
+ toggleFDNEnable(true);
+ }
resetPinChangeState();
}
}
+ mButtonChangePin2.setText("");
+ mButtonEnableFDN.setText("");
break;
}
}
};
/**
- * Cancel listener for the PUK2 request alert dialog.
- */
- @Override
- public void onCancel(DialogInterface dialog) {
- // set the state of the preference and then display the dialog.
- resetPinChangeStateForPUK2();
- displayPinChangeDialog(0, true);
- }
-
- /**
* Display a toast for message, like the rest of the settings.
*/
- private final void displayMessage(int strId, int attemptsRemaining) {
+ private void displayMessage(int strId, int attemptsRemaining) {
String s = getString(strId);
if ((strId == R.string.badPin2) || (strId == R.string.badPuk2) ||
(strId == R.string.pin2_invalid)) {
@@ -367,22 +377,27 @@
Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
}
- private final void displayMessage(int strId) {
+ private void displayMessage(int strId) {
displayMessage(strId, -1);
}
/**
* The next two functions are for updating the message field on the dialog.
*/
- private final void displayPinChangeDialog() {
- displayPinChangeDialog(0, true);
+ private void displayPinChangeDialog(EditPinPreference button) {
+ displayPinChangeDialog(button, 0, true);
}
- private final void displayPinChangeDialog(int strId, boolean shouldDisplay) {
+ private void displayPinChangeDialog(EditPinPreference button,
+ int strId, boolean shouldDisplay) {
int msgId;
switch (mPinChangeState) {
case PIN_CHANGE_OLD:
- msgId = R.string.oldPin2Label;
+ if (button == mButtonEnableFDN) {
+ msgId = R.string.enter_pin2_text;
+ } else {
+ msgId = R.string.oldPin2Label;
+ }
break;
case PIN_CHANGE_NEW:
case PIN_CHANGE_NEW_PIN_FOR_PUK:
@@ -400,14 +415,14 @@
// append the note / additional message, if needed.
if (strId != 0) {
- mButtonChangePin2.setDialogMessage(getText(msgId) + "\n" + getText(strId));
+ button.setDialogMessage(getText(msgId) + "\n" + getText(strId));
} else {
- mButtonChangePin2.setDialogMessage(msgId);
+ button.setDialogMessage(msgId);
}
// only display if requested.
if (shouldDisplay) {
- mButtonChangePin2.showPinDialog();
+ button.showPinDialog();
}
}
@@ -417,7 +432,8 @@
private final void resetPinChangeState() {
if (DBG) log("resetPinChangeState");
mPinChangeState = PIN_CHANGE_OLD;
- displayPinChangeDialog(0, false);
+ displayPinChangeDialog(mButtonEnableFDN, 0, false);
+ displayPinChangeDialog(mButtonChangePin2, 0, false);
mOldPin = mNewPin = "";
mIsPuk2Locked = false;
}
@@ -428,7 +444,8 @@
private final void resetPinChangeStateForPUK2() {
if (DBG) log("resetPinChangeStateForPUK2");
mPinChangeState = PIN_CHANGE_PUK;
- displayPinChangeDialog(0, false);
+ displayPinChangeDialog(mButtonEnableFDN, 0, false);
+ displayPinChangeDialog(mButtonChangePin2, 0, false);
mOldPin = mNewPin = mPuk2 = "";
mIsPuk2Locked = true;
}
@@ -472,7 +489,10 @@
* Reflect the updated change PIN2 state in the UI.
*/
private void updateChangePIN2() {
- if (mPhone.getIccCard().getIccPin2Blocked()) {
+ if (mPhone.getIccCard().getIccPuk2Blocked()) {
+ showPin2OrPuk2LockedDialog(Pin2LockedDialogFragment.DIALOG_ID_PUK2_LOCKED_OUT);
+ resetPinChangeStateForPUK2();
+ } else if (mPhone.getIccCard().getIccPin2Blocked()) {
// If the pin2 is blocked, the state of the change pin2 dialog
// should be set for puk2 use (that is, the user should be prompted
// to enter puk2 code instead of old pin2).
@@ -514,8 +534,14 @@
mPinChangeState = icicle.getInt(PIN_CHANGE_STATE_KEY);
mOldPin = icicle.getString(OLD_PIN_KEY);
mNewPin = icicle.getString(NEW_PIN_KEY);
- mButtonChangePin2.setDialogMessage(icicle.getString(DIALOG_MESSAGE_KEY));
- mButtonChangePin2.setText(icicle.getString(DIALOG_PIN_ENTRY_KEY));
+ mPuk2 = icicle.getString(PUK_KEY);
+ mButtonChangePin2.setDialogMessage(
+ icicle.getString(DIALOG_MESSAGE_KEY));
+ mButtonChangePin2.setText(
+ icicle.getString(DIALOG_PIN_ENTRY_KEY));
+ mButtonEnableFDN.setDialogMessage(
+ icicle.getString(FDN_DIALOG_MESSAGE_KEY));
+ mButtonEnableFDN.setText(icicle.getString(FDN_DIALOG_PIN_ENTRY_KEY));
}
ActionBar actionBar = getActionBar();
@@ -545,8 +571,19 @@
out.putInt(PIN_CHANGE_STATE_KEY, mPinChangeState);
out.putString(OLD_PIN_KEY, mOldPin);
out.putString(NEW_PIN_KEY, mNewPin);
- out.putString(DIALOG_MESSAGE_KEY, mButtonChangePin2.getDialogMessage().toString());
- out.putString(DIALOG_PIN_ENTRY_KEY, mButtonChangePin2.getText());
+ out.putString(PUK_KEY, mPuk2);
+ if (mButtonChangePin2.isEnabled()) {
+ out.putString(DIALOG_MESSAGE_KEY, mButtonChangePin2.getDialogMessage().toString());
+ out.putString(DIALOG_PIN_ENTRY_KEY, mButtonChangePin2.getText());
+ }
+ if (mButtonEnableFDN.isEnabled()) {
+ CharSequence dialogMsg = mButtonEnableFDN.getDialogMessage();
+ if (dialogMsg != null) {
+ out.putString(FDN_DIALOG_MESSAGE_KEY,
+ mButtonEnableFDN.getDialogMessage().toString());
+ }
+ out.putString(FDN_DIALOG_PIN_ENTRY_KEY, mButtonEnableFDN.getText());
+ }
}
@Override
@@ -562,5 +599,31 @@
private void log(String msg) {
Log.d(LOG_TAG, "FdnSetting: " + msg);
}
+
+ @Override
+ public void onRequestPuk2(int id) {
+ resetPinChangeStateForPUK2();
+ final EditPinPreference button =
+ (id == Pin2LockedDialogFragment.DIALOG_ID_PUK2_REQUESTED_ON_PIN_CHANGED)
+ ? mButtonChangePin2 : mButtonEnableFDN;
+ displayPinChangeDialog(button, 0, true);
+ }
+
+ private void showPin2OrPuk2LockedDialog(int id) {
+ final FragmentManager fragmentManager = getFragmentManager();
+ Pin2LockedDialogFragment dialogFragment = (Pin2LockedDialogFragment) fragmentManager
+ .findFragmentByTag(Pin2LockedDialogFragment.TAG_PIN2_LOCKED_DIALOG);
+ if (dialogFragment == null) {
+ dialogFragment = new Pin2LockedDialogFragment();
+ Bundle args = new Bundle();
+ args.putInt(Pin2LockedDialogFragment.KEY_DIALOG_ID, id);
+ dialogFragment.setArguments(args);
+ dialogFragment.show(fragmentManager, Pin2LockedDialogFragment.TAG_PIN2_LOCKED_DIALOG);
+ } else {
+ FragmentTransaction transaction = fragmentManager.beginTransaction();
+ transaction.show(dialogFragment);
+ transaction.commitNow();
+ }
+ }
}
diff --git a/src/com/android/phone/settings/fdn/Pin2LockedDialogFragment.java b/src/com/android/phone/settings/fdn/Pin2LockedDialogFragment.java
new file mode 100644
index 0000000..ff16a7f
--- /dev/null
+++ b/src/com/android/phone/settings/fdn/Pin2LockedDialogFragment.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 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.phone.settings.fdn;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import com.android.phone.R;
+
+/**
+ * Dialog Fragment that displays dialogs indicating that PIN2/PUK2 has been locked out.
+ *
+ * 1. When user fails PIN2 authentication and PIN2 is locked, show the dialog indicating that PIN2
+ * is locked and PUK2 must be entered.
+ * 2. When user fails PUK2 authentication and PUK2 is locked, show the dialog indicating that PUK2
+ * is locked and user must contact service provider to unlock PUK2.
+ */
+public class Pin2LockedDialogFragment extends DialogFragment {
+
+ static final String TAG_PIN2_LOCKED_DIALOG = "tag_pin2_locked_dialog";
+ static final String KEY_DIALOG_ID = "key_dialog_id";
+
+ // AlertDialog IDs
+ static final int DIALOG_ID_PUK2_LOCKED_OUT = 10;
+ static final int DIALOG_ID_PUK2_REQUESTED_ON_PIN_ENTRY = 11;
+ static final int DIALOG_ID_PUK2_REQUESTED_ON_PIN_CHANGED = 12;
+
+ private Listener mListener;
+ private int mId;
+
+ interface Listener {
+ void onRequestPuk2(int id);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ super.onCreateDialog(savedInstanceState);
+ Activity activity = getActivity();
+ if (!(activity instanceof Listener)) {
+ return null;
+ }
+ mListener = (Listener) activity;
+ mId = getArguments().getInt(KEY_DIALOG_ID);
+
+ if (mId == DIALOG_ID_PUK2_LOCKED_OUT) {
+ AlertDialog alert = new AlertDialog.Builder(activity)
+ .setMessage(R.string.puk2_locked)
+ .setCancelable(true)
+ .create();
+ alert.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+ alert.setButton(DialogInterface.BUTTON_NEUTRAL, getText(R.string.ok),
+ (dialog, which) -> {
+ });
+ return alert;
+ }
+
+ if (mId == DIALOG_ID_PUK2_REQUESTED_ON_PIN_CHANGED
+ || mId == DIALOG_ID_PUK2_REQUESTED_ON_PIN_ENTRY) {
+ AlertDialog alert = new AlertDialog.Builder(activity)
+ .setMessage(R.string.puk2_requested)
+ .setCancelable(true)
+ .create();
+ alert.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+ alert.setButton(DialogInterface.BUTTON_NEUTRAL, getText(R.string.ok),
+ (dialog, which) -> {
+ mListener.onRequestPuk2(mId);
+ dialog.dismiss();
+ });
+ return alert;
+ }
+ return null;
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ if (mId == DIALOG_ID_PUK2_REQUESTED_ON_PIN_CHANGED
+ || mId == DIALOG_ID_PUK2_REQUESTED_ON_PIN_ENTRY) {
+ mListener.onRequestPuk2(mId);
+ }
+ dialog.dismiss();
+ }
+}
+
diff --git a/src/com/android/phone/vvm/VvmSimStateTracker.java b/src/com/android/phone/vvm/VvmSimStateTracker.java
index c648d9c..a77bd7b 100644
--- a/src/com/android/phone/vvm/VvmSimStateTracker.java
+++ b/src/com/android/phone/vvm/VvmSimStateTracker.java
@@ -20,13 +20,16 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
import android.os.SystemProperties;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.CarrierConfigManager;
-import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -68,7 +71,8 @@
* Waits for the account to become {@link ServiceState#STATE_IN_SERVICE} and notify the
* connected event. Will unregister itself once the event has been triggered.
*/
- private class ServiceStateListener extends PhoneStateListener {
+ private class ServiceStateListener extends TelephonyCallback implements
+ TelephonyCallback.ServiceStateListener {
private final PhoneAccountHandle mPhoneAccountHandle;
private final Context mContext;
@@ -84,7 +88,8 @@
VvmLog.e(TAG, "Cannot create TelephonyManager from " + mPhoneAccountHandle);
return;
}
- telephonyManager.listen(this, PhoneStateListener.LISTEN_SERVICE_STATE);
+ telephonyManager.registerTelephonyCallback(
+ new HandlerExecutor(new Handler(Looper.getMainLooper())), this);
}
public void unlisten() {
@@ -92,7 +97,7 @@
// PhoneStateListener, and mPhoneAccountHandle might be invalid at this point
// (e.g. SIM removal)
mContext.getSystemService(TelephonyManager.class)
- .listen(this, PhoneStateListener.LISTEN_NONE);
+ .unregisterTelephonyCallback(this);
sListeners.put(mPhoneAccountHandle, null);
}
diff --git a/src/com/android/services/telephony/CdmaConferenceController.java b/src/com/android/services/telephony/CdmaConferenceController.java
index 8523a5f..a076ec8 100644
--- a/src/com/android/services/telephony/CdmaConferenceController.java
+++ b/src/com/android/services/telephony/CdmaConferenceController.java
@@ -20,11 +20,14 @@
import android.telecom.Connection;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccountHandle;
+import android.util.ArraySet;
import com.android.phone.PhoneUtils;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Set;
/**
* Manages CDMA conference calls. CDMA conference calls are much more limited than GSM conference
@@ -83,6 +86,9 @@
private final Handler mHandler = new Handler();
+ private final Set<CdmaConnection> mPendingAddConnections = Collections.synchronizedSet(
+ new ArraySet<>());
+
public CdmaConferenceController(TelephonyConnectionService connectionService) {
mConnectionService = connectionService;
}
@@ -91,7 +97,7 @@
private CdmaConference mConference;
void add(final CdmaConnection connection) {
- if (mCdmaConnections.contains(connection)) {
+ if (mCdmaConnections.contains(connection) || !mPendingAddConnections.add(connection)) {
// Adding a duplicate realistically shouldn't happen.
Log.w(this, "add - connection already tracked; connection=%s", connection);
return;
@@ -140,6 +146,7 @@
private void addInternal(CdmaConnection connection) {
mCdmaConnections.add(connection);
+ mPendingAddConnections.remove(connection);
connection.addTelephonyConnectionListener(mTelephonyConnectionListener);
recalculateConference();
}
diff --git a/src/com/android/services/telephony/CdmaConnection.java b/src/com/android/services/telephony/CdmaConnection.java
index 90e7663..c7b324d 100644
--- a/src/com/android/services/telephony/CdmaConnection.java
+++ b/src/com/android/services/telephony/CdmaConnection.java
@@ -16,11 +16,12 @@
package com.android.services.telephony;
+import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.provider.Settings;
import android.telephony.DisconnectCause;
-import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
@@ -284,8 +285,14 @@
private boolean isEmergency() {
Phone phone = getPhone();
- return phone != null && getAddress() != null && PhoneNumberUtils.isLocalEmergencyNumber(
- phone.getContext(), getAddress().getSchemeSpecificPart());
+ if (phone != null && getAddress() != null) {
+ TelephonyManager tm = (TelephonyManager) phone.getContext()
+ .getSystemService(Context.TELEPHONY_SERVICE);
+ if (tm != null) {
+ return tm.isEmergencyNumber(getAddress().getSchemeSpecificPart());
+ }
+ }
+ return false;
}
/**
diff --git a/src/com/android/services/telephony/DisconnectCauseUtil.java b/src/com/android/services/telephony/DisconnectCauseUtil.java
index f243462..9c89e9e 100644
--- a/src/com/android/services/telephony/DisconnectCauseUtil.java
+++ b/src/com/android/services/telephony/DisconnectCauseUtil.java
@@ -23,6 +23,7 @@
import android.telecom.DisconnectCause;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
+import android.telephony.ims.ImsReasonInfo;
import com.android.internal.telephony.CallFailCause;
import com.android.internal.telephony.Phone;
@@ -64,13 +65,13 @@
* message and tone.
*
* @param telephonyDisconnectCause The code for the reason for the disconnect.
- * @param telephonyPerciseDisconnectCause The code for the percise reason for the disconnect.
+ * @param telephonyPreciseDisconnectCause The code for the precise reason for the disconnect.
* @param reason Description of the reason for the disconnect, not intended for the user to see..
*/
public static DisconnectCause toTelecomDisconnectCause(
- int telephonyDisconnectCause, int telephonyPerciseDisconnectCause, String reason) {
- return toTelecomDisconnectCause(telephonyDisconnectCause, telephonyPerciseDisconnectCause,
- reason, SubscriptionManager.getDefaultVoicePhoneId());
+ int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason) {
+ return toTelecomDisconnectCause(telephonyDisconnectCause, telephonyPreciseDisconnectCause,
+ reason, SubscriptionManager.getDefaultVoicePhoneId(), null);
}
/**
@@ -84,30 +85,33 @@
public static DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause,
String reason, int phoneId) {
return toTelecomDisconnectCause(telephonyDisconnectCause, CallFailCause.NOT_VALID,
- reason, phoneId);
+ reason, phoneId, null);
}
/**
* Converts from a disconnect code in {@link android.telephony.DisconnectCause} into a more
* generic {@link android.telecom.DisconnectCause}.object, possibly populated with a localized
* message and tone for Slot.
- *
* @param telephonyDisconnectCause The code for the reason for the disconnect.
- * @param telephonyPerciseDisconnectCause The code for the percise reason for the disconnect.
- * @param reason Description of the reason for the disconnect, not intended for the user to see..
+ * @param telephonyPreciseDisconnectCause The code for the precise reason for the disconnect.
+ * @param reason Description of the reason for the disconnect, not intended for the user to see.
* @param phoneId To support localized message based on phoneId
+ * @param imsReasonInfo
*/
public static DisconnectCause toTelecomDisconnectCause(
- int telephonyDisconnectCause, int telephonyPerciseDisconnectCause, String reason,
- int phoneId) {
+ int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason,
+ int phoneId, ImsReasonInfo imsReasonInfo) {
Context context = PhoneGlobals.getInstance();
return new DisconnectCause(
toTelecomDisconnectCauseCode(telephonyDisconnectCause),
toTelecomDisconnectCauseLabel(context, telephonyDisconnectCause,
- telephonyPerciseDisconnectCause),
+ telephonyPreciseDisconnectCause),
toTelecomDisconnectCauseDescription(context, telephonyDisconnectCause, phoneId),
toTelecomDisconnectReason(context,telephonyDisconnectCause, reason, phoneId),
- toTelecomDisconnectCauseTone(telephonyDisconnectCause, phoneId));
+ toTelecomDisconnectCauseTone(telephonyDisconnectCause, phoneId),
+ telephonyDisconnectCause,
+ telephonyPreciseDisconnectCause,
+ imsReasonInfo);
}
/**
@@ -233,10 +237,10 @@
* Returns a label for to the disconnect cause to be shown to the user.
*/
private static CharSequence toTelecomDisconnectCauseLabel(
- Context context, int telephonyDisconnectCause, int telephonyPerciseDisconnectCause) {
+ Context context, int telephonyDisconnectCause, int telephonyPreciseDisconnectCause) {
CharSequence label;
- if (telephonyPerciseDisconnectCause != CallFailCause.NOT_VALID) {
- label = getLabelFromPreciseDisconnectCause(context, telephonyPerciseDisconnectCause,
+ if (telephonyPreciseDisconnectCause != CallFailCause.NOT_VALID) {
+ label = getLabelFromPreciseDisconnectCause(context, telephonyPreciseDisconnectCause,
telephonyDisconnectCause);
} else {
label = getLabelFromDisconnectCause(context, telephonyDisconnectCause);
@@ -654,12 +658,14 @@
break;
case android.telephony.DisconnectCause.POWER_OFF:
- // Radio is explictly powered off because the device is in airplane mode.
+ // Radio is explicitly powered off because the device's radio is off.
// TODO: Offer the option to turn the radio on, and automatically retry the call
// once network registration is complete.
- if (ImsUtil.shouldPromoteWfc(context, phoneId)) {
+ if (isRadioOffForThermalMitigation(phoneId)) {
+ resourceId = R.string.incall_error_power_off_thermal;
+ } else if (ImsUtil.shouldPromoteWfc(context, phoneId)) {
resourceId = R.string.incall_error_promote_wfc;
} else if (ImsUtil.isWfcModeWifiOnly(context, phoneId)) {
resourceId = R.string.incall_error_wfc_only_no_wireless_network;
@@ -781,7 +787,12 @@
default:
break;
}
- return resourceId == null ? "" : context.getResources().getString(resourceId);
+ return resourceId == null ? "" : context.getResources().getText(resourceId);
+ }
+
+ private static boolean isRadioOffForThermalMitigation(int phoneId) {
+ Phone phone = PhoneFactory.getPhone(phoneId);
+ return phone.isRadioOffForThermalMitigation();
}
/**
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 82a393d..0be927a 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -867,6 +867,16 @@
mConferenceHostAddress = new Uri[hostAddresses.size()];
mConferenceHostAddress = hostAddresses.toArray(mConferenceHostAddress);
+ Log.i(this, "setConferenceHost: temp log hosts are "
+ + Arrays.stream(mConferenceHostAddress)
+ .map(Uri::toString)
+ .collect(Collectors.joining(", ")));
+
+ Log.i(this, "setConferenceHost: hosts are "
+ + Arrays.stream(mConferenceHostAddress)
+ .map(Uri::getSchemeSpecificPart)
+ .map(ssp -> Rlog.pii(LOG_TAG, ssp))
+ .collect(Collectors.joining(", ")));
Log.i(this, "setConferenceHost: hosts are "
+ Arrays.stream(mConferenceHostAddress)
@@ -1064,7 +1074,12 @@
// If the conference is empty and we're supposed to do a local disconnect, do so now.
if (mCarrierConfig.shouldLocalDisconnectEmptyConference()
- && oldParticipantCount > 0 && newParticipantCount == 0) {
+ // If we dropped from > 0 participants to zero
+ // OR if the conference had a single participant and is emulating a standalone
+ // call.
+ && (oldParticipantCount > 0 || !isMultiparty())
+ // AND the CEP says there is nobody left any more.
+ && newParticipantCount == 0) {
Log.i(this, "handleConferenceParticipantsUpdate: empty conference; "
+ "local disconnect.");
onDisconnect();
@@ -1383,9 +1398,12 @@
}
if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
- Log.i(this,"handleOriginalConnectionChange : SRVCC to GSM");
GsmConnection c = new GsmConnection(originalConnection, getTelecomCallId(),
mConferenceHost.getCallDirection());
+ Log.i(this, "handleOriginalConnectionChange : SRVCC to GSM."
+ + " Created new GsmConnection with objId=" + System.identityHashCode(c)
+ + " and originalConnection objId="
+ + System.identityHashCode(originalConnection));
// This is a newly created conference connection as a result of SRVCC
c.setConferenceSupported(true);
c.setTelephonyConnectionProperties(
diff --git a/src/com/android/services/telephony/MmiCodeUtil.java b/src/com/android/services/telephony/MmiCodeUtil.java
new file mode 100644
index 0000000..d208ec3
--- /dev/null
+++ b/src/com/android/services/telephony/MmiCodeUtil.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2020 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.services.telephony;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class MmiCodeUtil {
+ //***** Constants
+
+ // Supp Service codes from TS 22.030 Annex B
+
+ //Called line presentation
+ static final String SC_CLIP = "30";
+ static final String SC_CLIR = "31";
+
+ // Call Forwarding
+ static final String SC_CFU = "21";
+ static final String SC_CFB = "67";
+ static final String SC_CFNRy = "61";
+ static final String SC_CFNR = "62";
+
+ static final String SC_CF_All = "002";
+ static final String SC_CF_All_Conditional = "004";
+
+ // Call Waiting
+ static final String SC_WAIT = "43";
+
+ // Call Barring
+ static final String SC_BAOC = "33";
+ static final String SC_BAOIC = "331";
+ static final String SC_BAOICxH = "332";
+ static final String SC_BAIC = "35";
+ static final String SC_BAICr = "351";
+
+ static final String SC_BA_ALL = "330";
+ static final String SC_BA_MO = "333";
+ static final String SC_BA_MT = "353";
+
+ // Supp Service Password registration
+ static final String SC_PWD = "03";
+
+ // PIN/PIN2/PUK/PUK2
+ static final String SC_PIN = "04";
+ static final String SC_PIN2 = "042";
+ static final String SC_PUK = "05";
+ static final String SC_PUK2 = "052";
+
+ // See TS 22.030 6.5.2 "Structure of the MMI"
+
+ static Pattern sPatternSuppService = Pattern.compile(
+ "((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)(.*)");
+/* 1 2 3 4 5 6 7 8 9 10 11 12
+
+ 1 = Full string up to and including #
+ 2 = action (activation/interrogation/registration/erasure)
+ 3 = service code
+ 5 = SIA
+ 7 = SIB
+ 9 = SIC
+ 10 = dialing number
+*/
+
+ static final int MATCH_GROUP_SERVICE_CODE = 3;
+
+ public static final String BUTTON_CLIR_KEY = "button_clir_key";
+ public static final String BUTTON_CW_KEY = "button_cw_key";
+ public static final String CALL_FORWARDING_KEY = "call_forwarding_key";
+ public static final String CALL_BARRING_KEY = "call_barring_key";
+
+ //***** Public Class methods
+ public static String getMmiServiceCode(String dialString) {
+ Matcher m;
+ String ret = null;
+
+ m = sPatternSuppService.matcher(dialString);
+
+ if (m.matches()) {
+ ret = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE));
+ }
+
+ return ret;
+ }
+
+ private static String makeEmptyNull(String s) {
+ if (s != null && s.length() == 0) return null;
+
+ return s;
+ }
+
+ static boolean isServiceCodeCallForwarding(String sc) {
+ return sc != null &&
+ (sc.equals(SC_CFU)
+ || sc.equals(SC_CFB) || sc.equals(SC_CFNRy)
+ || sc.equals(SC_CFNR) || sc.equals(SC_CF_All)
+ || sc.equals(SC_CF_All_Conditional));
+ }
+
+ static boolean isServiceCodeCallBarring(String sc) {
+ return sc != null &&
+ (sc.equals(SC_BAOC)
+ || sc.equals(SC_BAOIC) || sc.equals(SC_BAOICxH)
+ || sc.equals(SC_BAIC) || sc.equals(SC_BAICr)
+ || sc.equals(SC_BA_ALL) || sc.equals(SC_BA_MO)
+ || sc.equals(SC_BA_MT));
+ }
+
+ static boolean isPinPukCommand(String sc) {
+ return sc != null && (sc.equals(SC_PIN) || sc.equals(SC_PIN2)
+ || sc.equals(SC_PUK) || sc.equals(SC_PUK2));
+ }
+
+ public static String getSuppServiceKey(String dialString) {
+ String sc = getMmiServiceCode(dialString);
+ if (sc != null && sc.equals(SC_CLIP)) {
+ return "";
+ } else if (sc != null && sc.equals(SC_CLIR)) {
+ return BUTTON_CLIR_KEY;
+ } else if (isServiceCodeCallForwarding(sc)) {
+ return CALL_FORWARDING_KEY;
+ } else if (isServiceCodeCallBarring(sc)) {
+ return CALL_BARRING_KEY;
+ } else if (sc != null && sc.equals(SC_PWD)) {
+ return "";
+ } else if (sc != null && sc.equals(SC_WAIT)) {
+ return BUTTON_CW_KEY;
+ } else if (isPinPukCommand(sc)) {
+ return "";
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/src/com/android/services/telephony/PstnIncomingCallNotifier.java b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
index 76898bd..4191367 100644
--- a/src/com/android/services/telephony/PstnIncomingCallNotifier.java
+++ b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
@@ -25,8 +25,10 @@
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
+import android.telephony.ims.ImsCallProfile;
import android.text.TextUtils;
+import com.android.ims.ImsCall;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Connection;
@@ -39,6 +41,7 @@
import com.android.internal.telephony.imsphone.ImsPhoneConnection;
import com.android.phone.NumberVerificationManager;
import com.android.phone.PhoneUtils;
+import com.android.phone.callcomposer.CallComposerPictureManager;
import com.android.telephony.Rlog;
import java.util.List;
@@ -65,6 +68,12 @@
*/
private static final int MAX_NUMBER_VERIFICATION_HANGUP_DELAY_MILLIS = 10000;
+ /**
+ * Hardcoded extra for a call that's used to provide metrics information to the dialer app.
+ */
+ private static final String EXTRA_CALL_CREATED_TIME_MILLIS =
+ "android.telecom.extra.CALL_CREATED_TIME_MILLIS";
+
/** The phone object to listen to. */
private final Phone mPhone;
@@ -216,6 +225,9 @@
Call call = connection.getCall();
if (call != null && call.getState().isAlive()) {
addNewUnknownCall(connection);
+ } else {
+ Log.i(this, "Skipping new unknown connection because its call is null or dead."
+ + " connection=" + connection);
}
}
}
@@ -242,8 +254,7 @@
}
// Specifies the time the call was added. This is used by the dialer for analytics.
- extras.putLong(TelecomManager.EXTRA_CALL_CREATED_TIME_MILLIS,
- SystemClock.elapsedRealtime());
+ extras.putLong(EXTRA_CALL_CREATED_TIME_MILLIS, SystemClock.elapsedRealtime());
PhoneAccountHandle handle = findCorrectPhoneAccountHandle();
if (handle == null) {
@@ -273,8 +284,7 @@
}
// Specifies the time the call was added. This is used by the dialer for analytics.
- extras.putLong(TelecomManager.EXTRA_CALL_CREATED_TIME_MILLIS,
- SystemClock.elapsedRealtime());
+ extras.putLong(EXTRA_CALL_CREATED_TIME_MILLIS, SystemClock.elapsedRealtime());
if (connection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
if (((ImsPhoneConnection) connection).isRttEnabledForCall()) {
@@ -284,6 +294,33 @@
extras.putString(TelecomManager.EXTRA_CALL_DISCONNECT_MESSAGE,
TelecomManager.CALL_AUTO_DISCONNECT_MESSAGE_STRING);
}
+ ImsCall imsCall = ((ImsPhoneConnection) connection).getImsCall();
+ if (imsCall != null) {
+ ImsCallProfile imsCallProfile = imsCall.getCallProfile();
+ if (imsCallProfile != null) {
+ if (CallComposerPictureManager.sTestMode) {
+ imsCallProfile.setCallExtra(ImsCallProfile.EXTRA_PICTURE_URL,
+ CallComposerPictureManager.FAKE_SERVER_URL);
+ imsCallProfile.setCallExtraInt(ImsCallProfile.EXTRA_PRIORITY,
+ TelecomManager.PRIORITY_URGENT);
+ imsCallProfile.setCallExtra(ImsCallProfile.EXTRA_CALL_SUBJECT,
+ CallComposerPictureManager.FAKE_SUBJECT);
+ imsCallProfile.setCallExtraParcelable(ImsCallProfile.EXTRA_LOCATION,
+ CallComposerPictureManager.FAKE_LOCATION);
+ }
+
+ extras.putInt(TelecomManager.EXTRA_PRIORITY,
+ imsCallProfile.getCallExtraInt(ImsCallProfile.EXTRA_PRIORITY));
+ extras.putString(TelecomManager.EXTRA_CALL_SUBJECT,
+ imsCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_SUBJECT));
+ extras.putParcelable(TelecomManager.EXTRA_LOCATION,
+ imsCallProfile.getCallExtraParcelable(ImsCallProfile.EXTRA_LOCATION));
+ if (!TextUtils.isEmpty(
+ imsCallProfile.getCallExtra(ImsCallProfile.EXTRA_PICTURE_URL))) {
+ extras.putBoolean(TelecomManager.EXTRA_HAS_PICTURE, true);
+ }
+ }
+ }
}
PhoneAccountHandle handle = findCorrectPhoneAccountHandle();
diff --git a/src/com/android/services/telephony/RadioOnHelper.java b/src/com/android/services/telephony/RadioOnHelper.java
index 25ac220..be2ddb2 100644
--- a/src/com/android/services/telephony/RadioOnHelper.java
+++ b/src/com/android/services/telephony/RadioOnHelper.java
@@ -78,7 +78,7 @@
* serialized, and runs on the main looper.)
*/
public void triggerRadioOnAndListen(RadioOnStateListener.Callback callback,
- boolean forEmergencyCall, Phone phoneForEmergencyCall) {
+ boolean forEmergencyCall, Phone phoneForEmergencyCall, boolean isTestEmergencyNumber) {
setupListeners();
mCallback = callback;
mInProgressListeners.clear();
@@ -90,17 +90,17 @@
}
mInProgressListeners.add(mListeners.get(i));
- mListeners.get(i).waitForRadioOn(phone, this, forEmergencyCall,
- forEmergencyCall && phone == phoneForEmergencyCall);
+ mListeners.get(i).waitForRadioOn(phone, this, forEmergencyCall, forEmergencyCall
+ && phone == phoneForEmergencyCall);
}
-
- powerOnRadio(forEmergencyCall, phoneForEmergencyCall);
+ powerOnRadio(forEmergencyCall, phoneForEmergencyCall, isTestEmergencyNumber);
}
/**
* Attempt to power on the radio (i.e. take the device out of airplane mode). We'll eventually
* get an onServiceStateChanged() callback when the radio successfully comes up.
*/
- private void powerOnRadio(boolean forEmergencyCall, Phone phoneForEmergencyCall) {
+ private void powerOnRadio(boolean forEmergencyCall, Phone phoneForEmergencyCall,
+ boolean isTestEmergencyNumber) {
// If airplane mode is on, we turn it off the same way that the Settings activity turns it
// off.
@@ -114,7 +114,12 @@
for (Phone phone : PhoneFactory.getPhones()) {
Log.d(this, "powerOnRadio, enabling Radio");
- phone.setRadioPower(true, forEmergencyCall, phone == phoneForEmergencyCall, false);
+ if (isTestEmergencyNumber) {
+ phone.setRadioPowerOnForTestEmergencyCall(phone == phoneForEmergencyCall);
+ } else {
+ phone.setRadioPower(true, forEmergencyCall, phone == phoneForEmergencyCall,
+ false);
+ }
}
// Post the broadcast intend for change in airplane mode
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 7d6e65a..1d749f4 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -32,6 +32,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.PersistableBundle;
@@ -42,11 +43,11 @@
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.CarrierConfigManager;
-import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.telephony.ims.ImsException;
import android.telephony.ims.ImsMmTelManager;
@@ -106,6 +107,15 @@
*/
private static final int LISTENER_STATE_REGISTERED = 3;
+ /**
+ * Copy-pasted from android.telecom.PhoneAccount -- hidden constant which is unfortunately being
+ * used by some 1P apps, so we're keeping it here until we can remove it.
+ */
+ private static final String EXTRA_SUPPORTS_VIDEO_CALLING_FALLBACK =
+ "android.telecom.extra.SUPPORTS_VIDEO_CALLING_FALLBACK";
+
+ private Handler mHandler;
+
final class AccountEntry implements PstnPhoneCapabilitiesNotifier.Listener {
private final Phone mPhone;
private PhoneAccount mAccount;
@@ -113,13 +123,14 @@
private final PstnPhoneCapabilitiesNotifier mPhoneCapabilitiesNotifier;
private boolean mIsEmergency;
private boolean mIsRttCapable;
+ private boolean mIsCallComposerCapable;
private boolean mIsAdhocConfCapable;
private boolean mIsEmergencyPreferred;
private MmTelFeature.MmTelCapabilities mMmTelCapabilities;
private ImsMmTelManager.CapabilityCallback mMmtelCapabilityCallback;
private RegistrationManager.RegistrationCallback mImsRegistrationCallback;
private ImsMmTelManager mMmTelManager;
- private final boolean mIsDummy;
+ private final boolean mIsTestAccount;
private boolean mIsVideoCapable;
private boolean mIsVideoPresenceSupported;
private boolean mIsVideoPauseSupported;
@@ -131,20 +142,20 @@
private boolean mIsUsingSimCallManager;
private boolean mIsShowPreciseFailedCause;
- AccountEntry(Phone phone, boolean isEmergency, boolean isDummy) {
+ AccountEntry(Phone phone, boolean isEmergency, boolean isTest) {
mPhone = phone;
mIsEmergency = isEmergency;
- mIsDummy = isDummy;
+ mIsTestAccount = isTest;
mIsAdhocConfCapable = mPhone.isImsRegistered();
- mAccount = registerPstnPhoneAccount(isEmergency, isDummy);
+ mAccount = registerPstnPhoneAccount(isEmergency, isTest);
Log.i(this, "Registered phoneAccount: %s with handle: %s",
mAccount, mAccount.getAccountHandle());
mIncomingCallNotifier = new PstnIncomingCallNotifier((Phone) mPhone);
mPhoneCapabilitiesNotifier = new PstnPhoneCapabilitiesNotifier((Phone) mPhone,
this);
- if (mIsDummy || isEmergency) {
- // For dummy and emergency entries, there is no sub ID that can be assigned, so do
+ if (mIsTestAccount || isEmergency) {
+ // For test and emergency entries, there is no sub ID that can be assigned, so do
// not register for capabilities callbacks.
return;
}
@@ -166,6 +177,7 @@
MmTelFeature.MmTelCapabilities capabilities) {
mMmTelCapabilities = capabilities;
updateRttCapability();
+ updateCallComposerCapability(capabilities);
}
};
registerMmTelCapabilityCallback();
@@ -247,7 +259,7 @@
* Trigger re-registration of this account.
*/
public void reRegisterPstnPhoneAccount() {
- PhoneAccount newAccount = buildPstnPhoneAccount(mIsEmergency, mIsDummy);
+ PhoneAccount newAccount = buildPstnPhoneAccount(mIsEmergency, mIsTestAccount);
if (!newAccount.equals(mAccount)) {
Log.i(this, "reRegisterPstnPhoneAccount: subId: " + getSubId()
+ " - re-register due to account change.");
@@ -258,8 +270,8 @@
}
}
- private PhoneAccount registerPstnPhoneAccount(boolean isEmergency, boolean isDummyAccount) {
- PhoneAccount account = buildPstnPhoneAccount(mIsEmergency, mIsDummy);
+ private PhoneAccount registerPstnPhoneAccount(boolean isEmergency, boolean isTestAccount) {
+ PhoneAccount account = buildPstnPhoneAccount(mIsEmergency, mIsTestAccount);
// Register with Telecom and put into the account entry.
mTelecomManager.registerPhoneAccount(account);
return account;
@@ -268,13 +280,13 @@
/**
* Registers the specified account with Telecom as a PhoneAccountHandle.
*/
- private PhoneAccount buildPstnPhoneAccount(boolean isEmergency, boolean isDummyAccount) {
- String dummyPrefix = isDummyAccount ? "Dummy " : "";
+ private PhoneAccount buildPstnPhoneAccount(boolean isEmergency, boolean isTestAccount) {
+ String testPrefix = isTestAccount ? "Test " : "";
// Build the Phone account handle.
PhoneAccountHandle phoneAccountHandle =
PhoneUtils.makePstnPhoneAccountHandleWithPrefix(
- mPhone, dummyPrefix, isEmergency);
+ mPhone, testPrefix, isEmergency);
// Populate the phone account data.
int subId = mPhone.getSubId();
@@ -334,8 +346,8 @@
// The label is user-visible so let's use the display name that the user may
// have set in Settings->Sim cards.
- label = dummyPrefix + subDisplayName;
- description = dummyPrefix + mContext.getResources().getString(
+ label = testPrefix + subDisplayName;
+ description = testPrefix + mContext.getResources().getString(
R.string.sim_description_default, slotIdString);
}
@@ -360,6 +372,10 @@
mIsRttCapable = false;
}
+ if (mIsCallComposerCapable) {
+ capabilities |= PhoneAccount.CAPABILITY_CALL_COMPOSER;
+ }
+
mIsVideoCapable = mPhone.isVideoEnabled();
boolean isVideoEnabledByPlatform = ImsManager.getInstance(mPhone.getContext(),
mPhone.getPhoneId()).isVtEnabledByPlatform();
@@ -416,13 +432,12 @@
extras.putBoolean(PhoneAccount.EXTRA_PLAY_CALL_RECORDING_TONE, true);
}
- extras.putBoolean(PhoneAccount.EXTRA_SUPPORTS_VIDEO_CALLING_FALLBACK,
+ extras.putBoolean(EXTRA_SUPPORTS_VIDEO_CALLING_FALLBACK,
mContext.getResources()
.getBoolean(R.bool.config_support_video_calling_fallback));
if (slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
- extras.putString(PhoneAccount.EXTRA_SORT_ORDER,
- String.valueOf(slotId));
+ extras.putInt(PhoneAccount.EXTRA_SORT_ORDER, slotId);
}
mIsMergeCallSupported = isCarrierMergeCallSupported();
@@ -573,9 +588,15 @@
private boolean isCarrierVideoPresenceSupported() {
PersistableBundle b =
PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
- boolean carrierConfigEnabled = b != null
- && b.getBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL);
- return carrierConfigEnabled && isUserContactDiscoverySettingEnabled();
+ if (b == null) return false;
+
+ // If using the new RcsUceAdapter API, this should be true if
+ // KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL is set. If using the old
+ // KEY_USE_RCS_PRESENCE_BOOL key, we have to also check the user setting.
+ return b.getBoolean(
+ CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL)
+ || (b.getBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL)
+ && isUserContactDiscoverySettingEnabled());
}
/**
@@ -776,7 +797,7 @@
// time we get here, the original phone account could have been torn down.
return;
}
- mAccount = registerPstnPhoneAccount(mIsEmergency, mIsDummy);
+ mAccount = registerPstnPhoneAccount(mIsEmergency, mIsTestAccount);
}
}
@@ -795,7 +816,7 @@
Log.i(this, "updateAdhocConfCapability - changed, new value: "
+ isAdhocConfCapable);
mIsAdhocConfCapable = isAdhocConfCapable;
- mAccount = registerPstnPhoneAccount(mIsEmergency, mIsDummy);
+ mAccount = registerPstnPhoneAccount(mIsEmergency, mIsTestAccount);
}
}
}
@@ -814,7 +835,7 @@
if (mIsVideoPresenceSupported != isVideoPresenceSupported) {
Log.i(this, "updateVideoPresenceCapability for subId=" + mPhone.getSubId()
+ ", new value= " + isVideoPresenceSupported);
- mAccount = registerPstnPhoneAccount(mIsEmergency, mIsDummy);
+ mAccount = registerPstnPhoneAccount(mIsEmergency, mIsTestAccount);
}
}
}
@@ -823,7 +844,18 @@
boolean isRttEnabled = isRttCurrentlySupported();
if (isRttEnabled != mIsRttCapable) {
Log.i(this, "updateRttCapability - changed, new value: " + isRttEnabled);
- mAccount = registerPstnPhoneAccount(mIsEmergency, mIsDummy);
+ mAccount = registerPstnPhoneAccount(mIsEmergency, mIsTestAccount);
+ }
+ }
+
+ public void updateCallComposerCapability(MmTelFeature.MmTelCapabilities capabilities) {
+ boolean isCallComposerCapable = capabilities.isCapable(
+ MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER);
+ if (isCallComposerCapable != mIsCallComposerCapable) {
+ mIsCallComposerCapable = isCallComposerCapable;
+ Log.i(this, "updateCallComposerCapability - changed, new value: "
+ + isCallComposerCapable);
+ mAccount = registerPstnPhoneAccount(mIsEmergency, mIsTestAccount);
}
}
@@ -832,7 +864,7 @@
activeDataSubId);
if (isEmergencyPreferred != mIsEmergencyPreferred) {
Log.i(this, "updateDefaultDataSubId - changed, new value: " + isEmergencyPreferred);
- mAccount = registerPstnPhoneAccount(mIsEmergency, mIsDummy);
+ mAccount = registerPstnPhoneAccount(mIsEmergency, mIsTestAccount);
}
}
@@ -979,6 +1011,9 @@
return mMmTelManager.isAvailable(ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE)
|| mMmTelManager.isAvailable(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
+ MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE)
+ || mMmTelManager.isAvailable(
+ ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM,
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
}
}
@@ -1053,7 +1088,11 @@
}
};
- private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ private final TelephonyCallback mTelephonyCallback = new TelecomAccountTelephonyCallback();
+
+ private class TelecomAccountTelephonyCallback extends TelephonyCallback implements
+ TelephonyCallback.ActiveDataSubscriptionIdListener,
+ TelephonyCallback.ServiceStateListener {
@Override
public void onServiceStateChanged(ServiceState serviceState) {
int newState = serviceState.getState();
@@ -1079,7 +1118,7 @@
}
}
}
- };
+ }
private static TelecomAccountRegistry sInstance;
private final Context mContext;
@@ -1119,6 +1158,7 @@
mTelephonyManager = TelephonyManager.from(context);
mSubscriptionManager = SubscriptionManager.from(context);
mHandlerThread.start();
+ mHandler = new Handler(Looper.getMainLooper());
mRegisterSubscriptionListenerBackoff = new ExponentialBackoff(
REGISTER_START_DELAY_MS,
REGISTER_MAXIMUM_DELAY_MS,
@@ -1141,7 +1181,7 @@
this.mTelephonyConnectionService = telephonyConnectionService;
}
- TelephonyConnectionService getTelephonyConnectionService() {
+ public TelephonyConnectionService getTelephonyConnectionService() {
return mTelephonyConnectionService;
}
@@ -1170,7 +1210,7 @@
* @param handle The {@link PhoneAccountHandle}.
* @return {@code True} if merging calls is supported.
*/
- boolean isMergeCallSupported(PhoneAccountHandle handle) {
+ public boolean isMergeCallSupported(PhoneAccountHandle handle) {
synchronized (mAccountsLock) {
for (AccountEntry entry : mAccounts) {
if (entry.getPhoneAccountHandle().equals(handle)) {
@@ -1188,7 +1228,7 @@
* @param handle The {@link PhoneAccountHandle}.
* @return {@code True} if video conferencing is supported.
*/
- boolean isVideoConferencingSupported(PhoneAccountHandle handle) {
+ public boolean isVideoConferencingSupported(PhoneAccountHandle handle) {
synchronized (mAccountsLock) {
for (AccountEntry entry : mAccounts) {
if (entry.getPhoneAccountHandle().equals(handle)) {
@@ -1206,7 +1246,7 @@
* @param handle The {@link PhoneAccountHandle}.
* @return {@code True} if merging of wifi calls is allowed when VoWIFI is disabled.
*/
- boolean isMergeOfWifiCallsAllowedWhenVoWifiOff(final PhoneAccountHandle handle) {
+ public boolean isMergeOfWifiCallsAllowedWhenVoWifiOff(final PhoneAccountHandle handle) {
synchronized (mAccountsLock) {
Optional<AccountEntry> result = mAccounts.stream().filter(
entry -> entry.getPhoneAccountHandle().equals(handle)).findFirst();
@@ -1226,7 +1266,7 @@
* @param handle The {@link PhoneAccountHandle}.
* @return {@code True} if merging IMS calls is supported.
*/
- boolean isMergeImsCallSupported(PhoneAccountHandle handle) {
+ public boolean isMergeImsCallSupported(PhoneAccountHandle handle) {
synchronized (mAccountsLock) {
for (AccountEntry entry : mAccounts) {
if (entry.getPhoneAccountHandle().equals(handle)) {
@@ -1346,8 +1386,8 @@
// We also need to listen for changes to the service state (e.g. emergency -> in service)
// because this could signal a removal or addition of a SIM in a single SIM phone.
- mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE
- | PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+ mTelephonyManager.registerTelephonyCallback(new HandlerExecutor(mHandler),
+ mTelephonyCallback);
// Listen for user switches. When the user switches, we need to ensure that if the current
// use is not the primary user we disable video calling.
@@ -1367,8 +1407,7 @@
private void registerContentObservers() {
// Listen to the RTT system setting so that we update it when the user flips it.
- ContentObserver rttUiSettingObserver = new ContentObserver(
- new Handler(Looper.getMainLooper())) {
+ ContentObserver rttUiSettingObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
synchronized (mAccountsLock) {
@@ -1384,8 +1423,7 @@
rttSettingUri, false, rttUiSettingObserver);
// Listen to the changes to the user's Contacts Discovery Setting.
- ContentObserver contactDiscoveryObserver = new ContentObserver(
- new Handler(Looper.getMainLooper())) {
+ ContentObserver contactDiscoveryObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
synchronized (mAccountsLock) {
@@ -1419,6 +1457,17 @@
return false;
}
+ PhoneAccountHandle getPhoneAccountHandleForSubId(int subId) {
+ synchronized (mAccountsLock) {
+ for (AccountEntry entry : mAccounts) {
+ if (entry.getSubId() == subId) {
+ return entry.getPhoneAccountHandle();
+ }
+ }
+ }
+ return null;
+ }
+
/**
* Un-registers any {@link PhoneAccount}s which are no longer present in the list
* {@code AccountEntry}(s).
@@ -1476,7 +1525,7 @@
}
mAccounts.add(new AccountEntry(phone, false /* emergency */,
- false /* isDummy */));
+ false /* isTest */));
}
}
} finally {
@@ -1487,14 +1536,14 @@
Log.i(this, "setupAccounts: adding default");
mAccounts.add(
new AccountEntry(PhoneFactory.getDefaultPhone(), true /* emergency */,
- false /* isDummy */));
+ false /* isTest */));
}
}
// Add a fake account entry.
- if (DBG && phones.length > 0 && "TRUE".equals(System.getProperty("dummy_sim"))) {
+ if (DBG && phones.length > 0 && "TRUE".equals(System.getProperty("test_sim"))) {
mAccounts.add(new AccountEntry(phones[0], false /* emergency */,
- true /* isDummy */));
+ true /* isTest */));
}
}
diff --git a/src/com/android/services/telephony/TelephonyConferenceController.java b/src/com/android/services/telephony/TelephonyConferenceController.java
index 95281b3..228541a 100644
--- a/src/com/android/services/telephony/TelephonyConferenceController.java
+++ b/src/com/android/services/telephony/TelephonyConferenceController.java
@@ -177,7 +177,7 @@
}
private void recalculateConference() {
- Set<Connection> conferencedConnections = new HashSet<>();
+ Set<TelephonyConnection> conferencedConnections = new HashSet<>();
int numGsmConnections = 0;
for (TelephonyConnection connection : mTelephonyConnections) {
@@ -254,17 +254,39 @@
PhoneAccountHandle phoneAccountHandle = null;
if (!conferencedConnections.isEmpty()) {
TelephonyConnection telephonyConnection =
- (TelephonyConnection) conferencedConnections.iterator().next();
+ conferencedConnections.iterator().next();
phoneAccountHandle = PhoneUtils.makePstnPhoneAccountHandle(
telephonyConnection.getPhone());
}
mTelephonyConference = new TelephonyConference(phoneAccountHandle);
- for (Connection connection : conferencedConnections) {
+ Log.i(this, "Creating new TelephonyConference to hold conferenced connections."
+ + " conference=" + mTelephonyConference);
+ boolean isDowngradedConference = false;
+ for (TelephonyConnection connection : conferencedConnections) {
Log.d(this, "Adding a connection to a conference call: %s %s",
mTelephonyConference, connection);
+ if ((connection.getConnectionProperties()
+ & Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE) != 0) {
+ // Remove all instances of PROPERTY_IS_DOWNGRADED_CONFERENCE. This
+ // property should only be set on the parent call (i.e. the newly
+ // created TelephonyConference.
+ Log.d(this, "Removing PROPERTY_IS_DOWNGRADED_CONFERENCE from connection"
+ + " %s", connection);
+ int newProperties = connection.getConnectionProperties()
+ & ~Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE;
+ connection.setTelephonyConnectionProperties(newProperties);
+ isDowngradedConference = true;
+ }
mTelephonyConference.addTelephonyConnection(connection);
}
+ // Reapply the downgraded-conference flag to the parent conference if it was on
+ // one of the children.
+ if (isDowngradedConference) {
+ mTelephonyConference.setConnectionProperties(
+ mTelephonyConference.getConnectionProperties()
+ | Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE);
+ }
mTelephonyConference.updateCallRadioTechAfterCreation();
mConnectionService.addConference(mTelephonyConference);
} else {
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index b4dd050..53e923e 100755
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Icon;
@@ -27,8 +28,11 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.Messenger;
import android.os.PersistableBundle;
import android.telecom.CallAudioState;
+import android.telecom.CallDiagnostics;
+import android.telecom.CallScreeningService;
import android.telecom.Conference;
import android.telecom.Connection;
import android.telecom.ConnectionService;
@@ -45,8 +49,12 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ImsStreamMediaProfile;
+import android.telephony.ims.RtpHeaderExtension;
+import android.telephony.ims.RtpHeaderExtensionType;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Pair;
import com.android.ims.ImsCall;
@@ -61,6 +69,14 @@
import com.android.internal.telephony.Connection.PostDialListener;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.d2d.Communicator;
+import com.android.internal.telephony.d2d.DtmfAdapter;
+import com.android.internal.telephony.d2d.DtmfTransport;
+import com.android.internal.telephony.d2d.MessageTypeAndValueHelper;
+import com.android.internal.telephony.d2d.RtpAdapter;
+import com.android.internal.telephony.d2d.RtpTransport;
+import com.android.internal.telephony.d2d.Timeouts;
+import com.android.internal.telephony.d2d.TransportProtocol;
import com.android.internal.telephony.gsm.SuppServiceNotification;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneCall;
@@ -70,6 +86,8 @@
import com.android.phone.PhoneGlobals;
import com.android.phone.PhoneUtils;
import com.android.phone.R;
+import com.android.phone.callcomposer.CallComposerPictureManager;
+import com.android.phone.callcomposer.CallComposerPictureTransfer;
import com.android.telephony.Rlog;
import java.util.ArrayList;
@@ -81,11 +99,13 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
/**
* Base class for CDMA and GSM connections.
*/
-abstract class TelephonyConnection extends Connection implements Holdable {
+abstract class TelephonyConnection extends Connection implements Holdable, Communicator.Callback {
private static final String LOG_TAG = "TelephonyConnection";
private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1;
@@ -96,6 +116,9 @@
private static final int MSG_CONFERENCE_MERGE_FAILED = 6;
private static final int MSG_SUPP_SERVICE_NOTIFY = 7;
+ // the threshold used to compare mAudioCodecBitrateKbps and mAudioCodecBandwidth.
+ private static final float THRESHOLD = 0.01f;
+
/**
* Mappings from {@link com.android.internal.telephony.Connection} extras keys to their
* equivalents defined in {@link android.telecom.Connection}.
@@ -116,6 +139,11 @@
private static final int MSG_ON_CONNECTION_EVENT = 19;
private static final int MSG_REDIAL_CONNECTION_CHANGED = 20;
private static final int MSG_REJECT = 21;
+ private static final int MSG_DTMF_DONE = 22;
+ private static final int MSG_MEDIA_ATTRIBUTES_CHANGED = 23;
+
+ private static final String JAPAN_COUNTRY_CODE_WITH_PLUS_SIGN = "+81";
+ private static final String JAPAN_ISO_COUNTRY_CODE = "JP";
private List<Uri> mParticipants;
private boolean mIsAdhocConferenceCall;
@@ -129,36 +157,15 @@
updateState();
break;
case MSG_HANDOVER_STATE_CHANGED:
+ // fall through
case MSG_REDIAL_CONNECTION_CHANGED:
String what = (msg.what == MSG_HANDOVER_STATE_CHANGED)
? "MSG_HANDOVER_STATE_CHANGED" : "MSG_REDIAL_CONNECTION_CHANGED";
- Log.v(TelephonyConnection.this, what);
+ Log.i(TelephonyConnection.this, "Connection changed due to: %s", what);
AsyncResult ar = (AsyncResult) msg.obj;
com.android.internal.telephony.Connection connection =
(com.android.internal.telephony.Connection) ar.result;
- if (connection == null) {
- setDisconnected(DisconnectCauseUtil
- .toTelecomDisconnectCause(DisconnectCause.OUT_OF_NETWORK,
- "handover failure, no connection"));
- close();
- break;
- }
- if (mOriginalConnection != null) {
- if (connection != null &&
- ((connection.getAddress() != null &&
- mOriginalConnection.getAddress() != null &&
- mOriginalConnection.getAddress().equals(connection.getAddress())) ||
- connection.getState() == mOriginalConnection.getStateBeforeHandover())) {
- Log.d(TelephonyConnection.this,
- "SettingOriginalConnection " + mOriginalConnection.toString()
- + " with " + connection.toString());
- setOriginalConnection(connection);
- mWasImsConnection = false;
- }
- } else {
- Log.w(TelephonyConnection.this,
- what + ": mOriginalConnection==null - invalid state (not cleaned up)");
- }
+ onOriginalConnectionRedialed(connection);
break;
case MSG_RINGBACK_TONE:
Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE");
@@ -225,6 +232,10 @@
setAudioQuality(audioQuality);
break;
+ case MSG_MEDIA_ATTRIBUTES_CHANGED:
+ refreshCodec();
+ break;
+
case MSG_SET_CONFERENCE_PARTICIPANTS:
List<ConferenceParticipant> participants = (List<ConferenceParticipant>) msg.obj;
updateConferenceParticipants(participants);
@@ -280,6 +291,9 @@
int rejectReason = (int) msg.obj;
reject(rejectReason);
break;
+ case MSG_DTMF_DONE:
+ Log.i(this, "MSG_DTMF_DONE");
+ break;
case MSG_SET_CALL_RADIO_TECH:
int vrat = (int) msg.obj;
@@ -318,6 +332,56 @@
}
};
+ private final Messenger mHandlerMessenger = new Messenger(mHandler);
+
+ /**
+ * The underlying telephony Connection has been redialed on a different domain (CS or IMS).
+ * Track the new telephony Connection and set back up appropriate callbacks.
+ * @param connection The new telephony Connection associated with this TelephonyConnection.
+ */
+ @VisibleForTesting
+ public void onOriginalConnectionRedialed(
+ com.android.internal.telephony.Connection connection) {
+ if (connection == null) {
+ setDisconnected(DisconnectCauseUtil
+ .toTelecomDisconnectCause(DisconnectCause.OUT_OF_NETWORK,
+ "handover failure, no connection"));
+ close();
+ return;
+ }
+ if (mOriginalConnection != null) {
+ if ((connection.getAddress() != null
+ && mOriginalConnection.getAddress() != null
+ && mOriginalConnection.getAddress().equals(connection.getAddress()))
+ || connection.getState() == mOriginalConnection.getStateBeforeHandover()) {
+ Log.i(TelephonyConnection.this, "Setting original connection after"
+ + " handover or redial, current original connection="
+ + mOriginalConnection.toString()
+ + ", new original connection="
+ + connection.toString());
+ setOriginalConnection(connection);
+ mWasImsConnection = false;
+ if (mHangupDisconnectCause != DisconnectCause.NOT_VALID) {
+ // A hangup request was initiated during the handover process, so
+ // go ahead and initiate the hangup on the new connection.
+ try {
+ Log.i(TelephonyConnection.this, "user has tried to hangup "
+ + "during handover, retrying hangup.");
+ connection.hangup();
+ } catch (CallStateException e) {
+ // Call state exception may be thrown if the connection was
+ // already disconnected, so just log this case.
+ Log.w(TelephonyConnection.this, "hangup during "
+ + "handover or redial resulted in an exception:" + e);
+ }
+ }
+ }
+ } else {
+ Log.w(TelephonyConnection.this, " mOriginalConnection==null --"
+ + " invalid state (not cleaned up)");
+ }
+ }
+
/**
* Handles {@link SuppServiceNotification}s pertinent to Telephony.
* @param ssn the notification.
@@ -478,6 +542,19 @@
public void onRingbackRequested(Connection c, boolean ringback) {}
}
+ public static class D2DCallStateAdapter extends TelephonyConnectionListener {
+ private Communicator mCommunicator;
+
+ D2DCallStateAdapter(Communicator communicator) {
+ mCommunicator = communicator;
+ }
+
+ @Override
+ public void onStateChanged(android.telecom.Connection c, int state) {
+ mCommunicator.onStateChanged(c.getTelecomCallId(), state);
+ }
+ }
+
private final PostDialListener mPostDialListener = new PostDialListener() {
@Override
public void onPostDialWait() {
@@ -549,6 +626,12 @@
public void onAudioQualityChanged(int audioQuality) {
mHandler.obtainMessage(MSG_SET_AUDIO_QUALITY, audioQuality).sendToTarget();
}
+
+ @Override
+ public void onMediaAttributesChanged() {
+ mHandler.obtainMessage(MSG_MEDIA_ATTRIBUTES_CHANGED).sendToTarget();
+ }
+
/**
* Handles a change in the state of conference participant(s), as reported by the
* {@link com.android.internal.telephony.Connection}.
@@ -685,6 +768,8 @@
@Override
public void onOriginalConnectionReplaced(
com.android.internal.telephony.Connection newConnection) {
+ Log.i(TelephonyConnection.this, "onOriginalConnectionReplaced; newConn=%s",
+ newConnection);
setOriginalConnection(newConnection);
}
@@ -692,10 +777,34 @@
public void onIsNetworkEmergencyCallChanged(boolean isEmergencyCall) {
setIsNetworkIdentifiedEmergencyCall(isEmergencyCall);
}
+
+ /**
+ * Indicates data from an RTP header extension has been received from the network.
+ * @param extensionData The extension data.
+ */
+ @Override
+ public void onReceivedRtpHeaderExtensions(@NonNull Set<RtpHeaderExtension> extensionData) {
+ if (mRtpTransport == null) {
+ return;
+ }
+ Log.i(this, "onReceivedRtpHeaderExtensions: received %d extensions",
+ extensionData.size());
+ mRtpTransport.onRtpHeaderExtensionsReceived(extensionData);
+ }
+
+ @Override
+ public void onReceivedDtmfDigit(char digit) {
+ if (mDtmfTransport == null) {
+ return;
+ }
+ Log.i(this, "onReceivedDtmfDigit: digit=%c", digit);
+ mDtmfTransport.onDtmfReceived(digit);
+ }
};
private TelephonyConnectionService mTelephonyConnectionService;
protected com.android.internal.telephony.Connection mOriginalConnection;
+ private Phone mPhoneForEvents;
private Call.State mConnectionState = Call.State.IDLE;
private Bundle mOriginalConnectionExtras = new Bundle();
private boolean mIsStateOverridden = false;
@@ -796,6 +905,20 @@
private int mHangupDisconnectCause = DisconnectCause.NOT_VALID;
/**
+ * Provides a means for a {@link Communicator} to be informed of call state changes.
+ */
+ private D2DCallStateAdapter mD2DCallStateAdapter;
+
+ private RtpTransport mRtpTransport;
+
+ private DtmfTransport mDtmfTransport;
+
+ /**
+ * Facilitates device to device communication.
+ */
+ private Communicator mCommunicator;
+
+ /**
* Listeners to our TelephonyConnection specific callbacks
*/
private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap(
@@ -810,6 +933,23 @@
}
}
+ @VisibleForTesting
+ protected TelephonyConnection() {
+ // Do nothing
+ }
+
+ @Override
+ public void onCallEvent(String event, Bundle extras) {
+ switch (event) {
+ case Connection.EVENT_DEVICE_TO_DEVICE_MESSAGE:
+ // A Device to device message is being sent by a CallDiagnosticService.
+ handleOutgoingDeviceToDeviceMessage(extras);
+ break;
+ default:
+ break;
+ }
+
+ }
/**
* Creates a clone of the current {@link TelephonyConnection}.
*
@@ -1059,6 +1199,64 @@
}
@Override
+ public void onCallFilteringCompleted(CallFilteringCompletionInfo callFilteringCompletionInfo) {
+ // Check what the call screening service has to say, if it's a system dialer.
+ boolean isAllowedToDisplayPicture;
+ String callScreeningPackage =
+ callFilteringCompletionInfo.getCallScreeningComponent() == null
+ ? null
+ : callFilteringCompletionInfo.getCallScreeningComponent().getPackageName();
+ boolean isResponseFromSystemDialer =
+ Objects.equals(getPhone().getContext()
+ .getSystemService(TelecomManager.class).getSystemDialerPackage(),
+ callScreeningPackage);
+ CallScreeningService.CallResponse callScreeningResponse =
+ callFilteringCompletionInfo.getCallResponse();
+
+ if (isResponseFromSystemDialer && callScreeningResponse != null
+ && callScreeningResponse.getCallComposerAttachmentsToShow() >= 0) {
+ isAllowedToDisplayPicture = (callScreeningResponse.getCallComposerAttachmentsToShow()
+ & CallScreeningService.CallResponse.CALL_COMPOSER_ATTACHMENT_PICTURE) != 0;
+ } else {
+ isAllowedToDisplayPicture = callFilteringCompletionInfo.isInContacts();
+ }
+
+ if (isImsConnection()) {
+ ImsPhone imsPhone = (getPhone() instanceof ImsPhone) ? (ImsPhone) getPhone() : null;
+ if (imsPhone != null
+ && imsPhone.getCallComposerStatus() == TelephonyManager.CALL_COMPOSER_STATUS_ON
+ && !callFilteringCompletionInfo.isBlocked() && isAllowedToDisplayPicture) {
+ ImsPhoneConnection originalConnection = (ImsPhoneConnection) mOriginalConnection;
+ ImsCallProfile profile = originalConnection.getImsCall().getCallProfile();
+ String serverUrl = CallComposerPictureManager.sTestMode
+ ? CallComposerPictureManager.FAKE_SERVER_URL
+ : profile.getCallExtra(ImsCallProfile.EXTRA_PICTURE_URL);
+ if (profile != null
+ && !TextUtils.isEmpty(serverUrl)) {
+ CallComposerPictureManager manager = CallComposerPictureManager
+ .getInstance(getPhone().getContext(), getPhone().getSubId());
+ manager.handleDownloadFromServer(new CallComposerPictureTransfer.Factory() {},
+ serverUrl,
+ (result) -> {
+ if (result.first != null) {
+ Bundle newExtras = new Bundle();
+ newExtras.putParcelable(TelecomManager.EXTRA_PICTURE_URI,
+ result.first);
+ putTelephonyExtras(newExtras);
+ } else {
+ Log.i(this, "Call composer picture download:"
+ + " error=" + result.second);
+ Bundle newExtras = new Bundle();
+ newExtras.putBoolean(TelecomManager.EXTRA_HAS_PICTURE, false);
+ putTelephonyExtras(newExtras);
+ }
+ });
+ }
+ }
+ }
+ }
+
+ @Override
public void handleRttUpgradeResponse(RttTextStream textStream) {
if (!isImsConnection()) {
Log.w(this, "handleRttUpgradeResponse - not in IMS, so RTT cannot be enabled.");
@@ -1072,6 +1270,8 @@
Log.v(this, "performAnswer");
if (isValidRingingCall() && getPhone() != null) {
try {
+ mTelephonyConnectionService.maybeDisconnectCallsOnOtherSubs(
+ getPhoneAccountHandle());
getPhone().acceptCall(videoState);
} catch (CallStateException e) {
Log.e(this, e, "Failed to accept call.");
@@ -1264,7 +1464,7 @@
newProperties = changeBitmask(newProperties, PROPERTY_HIGH_DEF_AUDIO,
hasHighDefAudioProperty());
- newProperties = changeBitmask(newProperties, PROPERTY_WIFI, isWifi());
+ newProperties = changeBitmask(newProperties, PROPERTY_WIFI, isWifi() && !isCrossSimCall());
newProperties = changeBitmask(newProperties, PROPERTY_IS_EXTERNAL_CALL,
isExternalConnection());
newProperties = changeBitmask(newProperties, PROPERTY_HAS_CDMA_VOICE_PRIVACY,
@@ -1276,6 +1476,8 @@
isNetworkIdentifiedEmergencyCall());
newProperties = changeBitmask(newProperties, PROPERTY_IS_ADHOC_CONFERENCE,
isAdhocConferenceCall());
+ newProperties = changeBitmask(newProperties, PROPERTY_CROSS_SIM,
+ isCrossSimCall());
if (getConnectionProperties() != newProperties) {
setTelephonyConnectionProperties(newProperties);
@@ -1295,6 +1497,9 @@
if (isShowingOriginalDialString()
&& mOriginalConnection.getOrigDialString() != null) {
address = getAddressFromNumber(mOriginalConnection.getOrigDialString());
+ } else if (isNeededToFormatIncomingNumberForJp()) {
+ address = getAddressFromNumber(
+ formatIncomingNumberForJp(mOriginalConnection.getAddress()));
} else {
address = getAddressFromNumber(mOriginalConnection.getAddress());
}
@@ -1316,7 +1521,9 @@
setCallerDisplayName(name, namePresentation);
}
- if (PhoneNumberUtils.isEmergencyNumber(mOriginalConnection.getAddress())) {
+ TelephonyManager tm = (TelephonyManager) getPhone().getContext()
+ .getSystemService(Context.TELEPHONY_SERVICE);
+ if (tm.isEmergencyNumber(mOriginalConnection.getAddress())) {
mTreatAsEmergencyCall = true;
}
@@ -1330,8 +1537,29 @@
// Subclass can override this to do cleanup.
}
+ public void registerForCallEvents(Phone phone) {
+ if (mPhoneForEvents == phone) {
+ Log.i(this, "registerForCallEvents - same phone requested for"
+ + "registration, ignoring.");
+ return;
+ }
+ Log.i(this, "registerForCallEvents; phone=%s", phone);
+ // Only one Phone should be registered for events at a time.
+ unregisterForCallEvents();
+ phone.registerForPreciseCallStateChanged(mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null);
+ phone.registerForHandoverStateChanged(mHandler, MSG_HANDOVER_STATE_CHANGED, null);
+ phone.registerForRedialConnectionChanged(mHandler, MSG_REDIAL_CONNECTION_CHANGED, null);
+ phone.registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null);
+ phone.registerForSuppServiceNotification(mHandler, MSG_SUPP_SERVICE_NOTIFY, null);
+ phone.registerForOnHoldTone(mHandler, MSG_ON_HOLD_TONE, null);
+ phone.registerForInCallVoicePrivacyOn(mHandler, MSG_CDMA_VOICE_PRIVACY_ON, null);
+ phone.registerForInCallVoicePrivacyOff(mHandler, MSG_CDMA_VOICE_PRIVACY_OFF, null);
+ mPhoneForEvents = phone;
+ }
+
void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) {
- Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection);
+ Log.i(this, "setOriginalConnection: TelephonyConnection, originalConnection: "
+ + originalConnection);
if (mOriginalConnection != null && originalConnection != null
&& !originalConnection.isIncoming()
&& originalConnection.getOrigDialString() == null
@@ -1346,17 +1574,8 @@
mOriginalConnectionExtras.clear();
mOriginalConnection = originalConnection;
mOriginalConnection.setTelecomCallId(getTelecomCallId());
- getPhone().registerForPreciseCallStateChanged(
- mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null);
- getPhone().registerForHandoverStateChanged(
- mHandler, MSG_HANDOVER_STATE_CHANGED, null);
- getPhone().registerForRedialConnectionChanged(
- mHandler, MSG_REDIAL_CONNECTION_CHANGED, null);
- getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null);
- getPhone().registerForSuppServiceNotification(mHandler, MSG_SUPP_SERVICE_NOTIFY, null);
- getPhone().registerForOnHoldTone(mHandler, MSG_ON_HOLD_TONE, null);
- getPhone().registerForInCallVoicePrivacyOn(mHandler, MSG_CDMA_VOICE_PRIVACY_ON, null);
- getPhone().registerForInCallVoicePrivacyOff(mHandler, MSG_CDMA_VOICE_PRIVACY_OFF, null);
+ registerForCallEvents(getPhone());
+
mOriginalConnection.addPostDialListener(mPostDialListener);
mOriginalConnection.addListener(mOriginalConnectionListener);
@@ -1379,7 +1598,9 @@
mHandler.obtainMessage(MSG_CONNECTION_EXTRAS_CHANGED, connExtras == null ? null :
new Bundle(connExtras)).sendToTarget();
- if (PhoneNumberUtils.isEmergencyNumber(mOriginalConnection.getAddress())) {
+ TelephonyManager tm = (TelephonyManager) getPhone().getContext()
+ .getSystemService(Context.TELEPHONY_SERVICE);
+ if (tm.isEmergencyNumber(mOriginalConnection.getAddress())) {
mTreatAsEmergencyCall = true;
}
// Propagate VERSTAT for IMS calls.
@@ -1388,9 +1609,18 @@
if (isImsConnection()) {
mWasImsConnection = true;
}
+ if (originalConnection instanceof ImsPhoneConnection) {
+ maybeConfigureDeviceToDeviceCommunication();
+ }
mIsMultiParty = mOriginalConnection.isMultiparty();
Bundle extrasToPut = new Bundle();
+ // Also stash the number verification status in a hidden extra key in the connection.
+ // We do this because a RemoteConnection DOES NOT include a getNumberVerificationStatus
+ // method and we need to be able to pass the number verification status up to Telecom
+ // despite the missing pathway in the RemoteConnectionService API surface.
+ extrasToPut.putInt(Connection.EXTRA_CALLER_NUMBER_VERIFICATION_STATUS,
+ mOriginalConnection.getNumberVerificationStatus());
List<String> extrasToRemove = new ArrayList<>();
if (mOriginalConnection.isActiveCallDisconnectedOnAnswer()) {
extrasToPut.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
@@ -1403,6 +1633,15 @@
} else {
extrasToRemove.add(Connection.EXTRA_DISABLE_ADD_CALL);
}
+
+ if (mOriginalConnection != null) {
+ ArrayList<String> forwardedNumber = mOriginalConnection.getForwardedNumber();
+ if (forwardedNumber != null) {
+ extrasToPut.putStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER,
+ forwardedNumber);
+ }
+ }
+
putTelephonyExtras(extrasToPut);
removeTelephonyExtras(extrasToRemove);
@@ -1483,7 +1722,8 @@
}
}
- private void refreshCodecType() {
+ private void refreshCodec() {
+ boolean changed = false;
Bundle newExtras = getExtras();
if (newExtras == null) {
newExtras = new Bundle();
@@ -1499,6 +1739,42 @@
Connection.AUDIO_CODEC_NONE);
if (newCodecType != oldCodecType) {
newExtras.putInt(Connection.EXTRA_AUDIO_CODEC, newCodecType);
+ Log.i(this, "refreshCodec: codec changed; old=%d, new=%d", oldCodecType, newCodecType);
+ changed = true;
+ }
+ if (isImsConnection()) {
+ float newBitrate = getOriginalConnection().getAudioCodecBitrateKbps();
+ float oldBitrate = newExtras.getFloat(Connection.EXTRA_AUDIO_CODEC_BITRATE_KBPS, 0.0f);
+ if (Math.abs(newBitrate - oldBitrate) > THRESHOLD) {
+ newExtras.putFloat(Connection.EXTRA_AUDIO_CODEC_BITRATE_KBPS, newBitrate);
+ Log.i(this, "refreshCodec: bitrate changed; old=%f, new=%f", oldBitrate,
+ newBitrate);
+ changed = true;
+ }
+
+ float newBandwidth = getOriginalConnection().getAudioCodecBandwidthKhz();
+ float oldBandwidth = newExtras.getFloat(Connection.EXTRA_AUDIO_CODEC_BANDWIDTH_KHZ,
+ 0.0f);
+ if (Math.abs(newBandwidth - oldBandwidth) > THRESHOLD) {
+ newExtras.putFloat(Connection.EXTRA_AUDIO_CODEC_BANDWIDTH_KHZ, newBandwidth);
+ Log.i(this, "refreshCodec: bandwidth changed; old=%f, new=%f", oldBandwidth,
+ newBandwidth);
+ changed = true;
+ }
+ } else {
+ ArrayList<String> toRemove = new ArrayList<>();
+ toRemove.add(Connection.EXTRA_AUDIO_CODEC_BITRATE_KBPS);
+ toRemove.add(Connection.EXTRA_AUDIO_CODEC_BANDWIDTH_KHZ);
+ removeTelephonyExtras(toRemove);
+ }
+
+ if (changed) {
+ Log.i(this, "refreshCodec: Codec:"
+ + newExtras.getInt(Connection.EXTRA_AUDIO_CODEC, Connection.AUDIO_CODEC_NONE)
+ + ", Bitrate:"
+ + newExtras.getFloat(Connection.EXTRA_AUDIO_CODEC_BITRATE_KBPS, 0.0f)
+ + ", Bandwidth:"
+ + newExtras.getFloat(Connection.EXTRA_AUDIO_CODEC_BANDWIDTH_KHZ, 0.0f));
putTelephonyExtras(newExtras);
}
}
@@ -1577,7 +1853,7 @@
}
}
- isVowifiEnabled = ImsUtil.isWfcEnabled(phone.getContext(), phone.getPhoneId());
+ isVowifiEnabled = isWfcEnabled(phone);
}
if (isCurrentVideoCall) {
@@ -1711,8 +1987,12 @@
* - not indicated, then the add participant capability is same as before.
*/
if (isCapable && (mOriginalConnection != null) && !mIsMultiParty) {
- isCapable = mOriginalConnectionExtras.getBoolean(
+ // In case OEMs are still using deprecated value, read it and use it as default value.
+ boolean isCapableFromDeprecatedExtra = mOriginalConnectionExtras.getBoolean(
ImsCallProfile.EXTRA_CONFERENCE_AVAIL, isCapable);
+ isCapable = mOriginalConnectionExtras.getBoolean(
+ ImsCallProfile.EXTRA_EXTENDING_TO_CONFERENCE_SUPPORTED,
+ isCapableFromDeprecatedExtra);
}
return isCapable;
}
@@ -1826,28 +2106,43 @@
}
/**
+ * Sets whether to treat this call as an emergency call or not.
+ * @param shouldTreatAsEmergencyCall
+ */
+ @VisibleForTesting
+ public void setShouldTreatAsEmergencyCall(boolean shouldTreatAsEmergencyCall) {
+ mTreatAsEmergencyCall = shouldTreatAsEmergencyCall;
+ }
+
+ /**
* Un-sets the underlying radio connection.
*/
void clearOriginalConnection() {
if (mOriginalConnection != null) {
- if (getPhone() != null) {
- getPhone().unregisterForPreciseCallStateChanged(mHandler);
- getPhone().unregisterForRingbackTone(mHandler);
- getPhone().unregisterForHandoverStateChanged(mHandler);
- getPhone().unregisterForRedialConnectionChanged(mHandler);
- getPhone().unregisterForDisconnect(mHandler);
- getPhone().unregisterForSuppServiceNotification(mHandler);
- getPhone().unregisterForOnHoldTone(mHandler);
- getPhone().unregisterForInCallVoicePrivacyOn(mHandler);
- getPhone().unregisterForInCallVoicePrivacyOff(mHandler);
- }
+ Log.i(this, "clearOriginalConnection; clearing=%s", mOriginalConnection);
+ unregisterForCallEvents();
mOriginalConnection.removePostDialListener(mPostDialListener);
mOriginalConnection.removeListener(mOriginalConnectionListener);
mOriginalConnection = null;
}
}
- protected void hangup(int telephonyDisconnectCode) {
+ public void unregisterForCallEvents() {
+ if (mPhoneForEvents == null) return;
+ mPhoneForEvents.unregisterForPreciseCallStateChanged(mHandler);
+ mPhoneForEvents.unregisterForRingbackTone(mHandler);
+ mPhoneForEvents.unregisterForHandoverStateChanged(mHandler);
+ mPhoneForEvents.unregisterForRedialConnectionChanged(mHandler);
+ mPhoneForEvents.unregisterForDisconnect(mHandler);
+ mPhoneForEvents.unregisterForSuppServiceNotification(mHandler);
+ mPhoneForEvents.unregisterForOnHoldTone(mHandler);
+ mPhoneForEvents.unregisterForInCallVoicePrivacyOn(mHandler);
+ mPhoneForEvents.unregisterForInCallVoicePrivacyOff(mHandler);
+ mPhoneForEvents = null;
+ }
+
+ @VisibleForTesting
+ public void hangup(int telephonyDisconnectCode) {
if (mOriginalConnection != null) {
mHangupDisconnectCause = telephonyDisconnectCode;
try {
@@ -2044,7 +2339,9 @@
// If extras contain Conference support information,
// then ensure capabilities are updated.
if (mOriginalConnectionExtras.containsKey(
- ImsCallProfile.EXTRA_CONFERENCE_AVAIL)) {
+ ImsCallProfile.EXTRA_EXTENDING_TO_CONFERENCE_SUPPORTED)
+ || mOriginalConnectionExtras.containsKey(
+ ImsCallProfile.EXTRA_CONFERENCE_AVAIL)) {
updateConnectionCapabilities();
}
} else {
@@ -2155,18 +2452,28 @@
+ " -> " + mHangupDisconnectCause);
disconnectCause = mHangupDisconnectCause;
}
+ ImsReasonInfo imsReasonInfo = null;
+ if (isImsConnection()) {
+ ImsPhoneConnection imsPhoneConnection =
+ (ImsPhoneConnection) mOriginalConnection;
+ imsReasonInfo = imsPhoneConnection.getImsReasonInfo();
+ }
setTelephonyConnectionDisconnected(
DisconnectCauseUtil.toTelecomDisconnectCause(
disconnectCause,
preciseDisconnectCause,
mOriginalConnection.getVendorDisconnectCause(),
- getPhone().getPhoneId()));
+ getPhone().getPhoneId(), imsReasonInfo));
close();
}
break;
case DISCONNECTING:
break;
}
+
+ if (mCommunicator != null) {
+ mCommunicator.onStateChanged(getTelecomCallId(), getState());
+ }
}
}
@@ -2182,7 +2489,7 @@
updateAddress();
updateMultiparty();
refreshDisableAddCall();
- refreshCodecType();
+ refreshCodec();
}
/**
@@ -2304,6 +2611,16 @@
}
/**
+ * Determines if the current connection is cross sim calling
+ */
+ private boolean isCrossSimCall() {
+ return mOriginalConnection != null
+ && mOriginalConnection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS
+ && mOriginalConnection instanceof ImsPhoneConnection
+ && ((ImsPhoneConnection) mOriginalConnection).isCrossSimCall();
+ }
+
+ /**
* Determines if the current connection is pullable.
*
* A connection is deemed to be pullable if the original connection capabilities state that it
@@ -2541,8 +2858,10 @@
*/
protected boolean isImsConnection() {
com.android.internal.telephony.Connection originalConnection = getOriginalConnection();
- return originalConnection != null &&
- originalConnection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS;
+
+ return originalConnection != null
+ && originalConnection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS
+ && originalConnection instanceof ImsPhoneConnection;
}
/**
@@ -2607,7 +2926,7 @@
}
private void updateStatusHints() {
- if (isWifi() && getPhone() != null) {
+ if (isWifi() && !isCrossSimCall() && getPhone() != null) {
int labelId = isValidRingingCall()
? R.string.status_hint_label_incoming_wifi_call
: R.string.status_hint_label_wifi_call;
@@ -2713,15 +3032,15 @@
boolean isIms = phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS;
boolean isVoWifiEnabled = false;
if (isIms) {
- isVoWifiEnabled = ImsUtil.isWfcEnabled(phone.getContext(), phone.getPhoneId());
+ isVoWifiEnabled = isWfcEnabled(phone);
}
boolean isRttMergeSupported = getCarrierConfig()
.getBoolean(CarrierConfigManager.KEY_ALLOW_MERGING_RTT_CALLS_BOOL);
PhoneAccountHandle phoneAccountHandle = isIms ? PhoneUtils
.makePstnPhoneAccountHandle(phone.getDefaultPhone())
: PhoneUtils.makePstnPhoneAccountHandle(phone);
- TelecomAccountRegistry telecomAccountRegistry = TelecomAccountRegistry
- .getInstance(getPhone().getContext());
+ TelecomAccountRegistry telecomAccountRegistry = getTelecomAccountRegistry(
+ getPhone().getContext());
boolean isConferencingSupported = telecomAccountRegistry
.isMergeCallSupported(phoneAccountHandle);
boolean isImsConferencingSupported = telecomAccountRegistry
@@ -2730,6 +3049,19 @@
.isVideoConferencingSupported(phoneAccountHandle);
boolean isMergeOfWifiCallsAllowedWhenVoWifiOff = telecomAccountRegistry
.isMergeOfWifiCallsAllowedWhenVoWifiOff(phoneAccountHandle);
+ ImsCall imsCall = isImsConnection()
+ ? ((ImsPhoneConnection) getOriginalConnection()).getImsCall()
+ : null;
+ CarrierConfigManager configManager = (CarrierConfigManager) phone.getContext()
+ .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ boolean downGradedVideoCall = false;
+ if (configManager != null) {
+ PersistableBundle config = configManager.getConfigForSubId(phone.getSubId());
+ if (config != null) {
+ downGradedVideoCall = config.getBoolean(
+ CarrierConfigManager.KEY_TREAT_DOWNGRADED_VIDEO_CALLS_AS_VIDEO_CALLS_BOOL);
+ }
+ }
Log.v(this, "refreshConferenceSupported : isConfSupp=%b, isImsConfSupp=%b, " +
"isVidConfSupp=%b, isMergeOfWifiAllowed=%b, " +
@@ -2750,6 +3082,12 @@
} else if (isVideoCall && !mIsCarrierVideoConferencingSupported) {
isConferenceSupported = false;
Log.d(this, "refreshConferenceSupported = false; video conf not supported.");
+ } else if ((imsCall != null) && (imsCall.wasVideoCall() && downGradedVideoCall)
+ && !mIsCarrierVideoConferencingSupported) {
+ isConferenceSupported = false;
+ Log.d(this,
+ "refreshConferenceSupported = false;"
+ + " video conf not supported for downgraded audio call.");
} else if (!isMergeOfWifiCallsAllowedWhenVoWifiOff && isWifi() && !isVoWifiEnabled) {
isConferenceSupported = false;
Log.d(this,
@@ -2763,6 +3101,12 @@
notifyConferenceSupportedChanged(isConferenceSupported);
}
}
+
+ @VisibleForTesting
+ boolean isWfcEnabled(Phone phone) {
+ return ImsUtil.isWfcEnabled(phone.getContext(), phone.getPhoneId());
+ }
+
/**
* Provides a mapping from extras keys which may be found in the
* {@link com.android.internal.telephony.Connection} to their equivalents defined in
@@ -2911,7 +3255,7 @@
}
/**
- * Set this {@link TelephonyConnection} to a held state.
+ * Set this {@link TelephonyConnection} to a disconnected state.
* <p>
* Note: This should be used instead of
* {@link #setDisconnected(android.telecom.DisconnectCause)} to ensure listeners are notified.
@@ -3055,6 +3399,164 @@
}
/**
+ * Where device to device communication is available and this is an IMS call, configures the
+ * D2D communication infrastructure for operation.
+ */
+ private void maybeConfigureDeviceToDeviceCommunication() {
+ if (!getPhone().getContext().getResources().getBoolean(
+ R.bool.config_use_device_to_device_communication)) {
+ Log.i(this, "maybeConfigureDeviceToDeviceCommunication: not using D2D.");
+ notifyD2DAvailabilityChanged(false);
+ return;
+ }
+ if (!isImsConnection()) {
+ Log.i(this, "maybeConfigureDeviceToDeviceCommunication: not an IMS connection.");
+ if (mCommunicator != null) {
+ mCommunicator = null;
+ }
+ notifyD2DAvailabilityChanged(false);
+ return;
+ }
+ if (mTreatAsEmergencyCall || mIsNetworkIdentifiedEmergencyCall) {
+ Log.i(this, "maybeConfigureDeviceToDeviceCommunication: emergency call; no D2D");
+ notifyD2DAvailabilityChanged(false);
+ return;
+ }
+
+ ArrayList<TransportProtocol> supportedTransports = new ArrayList<>(2);
+
+ if (supportsD2DUsingRtp()) {
+ Log.i(this, "maybeConfigureDeviceToDeviceCommunication: carrier supports RTP.");
+ // Implement abstracted out RTP functionality the RTP transport depends on.
+ RtpAdapter rtpAdapter = new RtpAdapter() {
+ @Override
+ public Set<RtpHeaderExtensionType> getAcceptedRtpHeaderExtensions() {
+ ImsPhoneConnection originalConnection =
+ (ImsPhoneConnection) mOriginalConnection;
+ return originalConnection.getAcceptedRtpHeaderExtensions();
+ }
+
+ @Override
+ public void sendRtpHeaderExtensions(
+ @NonNull Set<RtpHeaderExtension> rtpHeaderExtensions) {
+ Log.i(TelephonyConnection.this, "sendRtpHeaderExtensions: sending: %s",
+ rtpHeaderExtensions.stream()
+ .map(r -> r.toString())
+ .collect(Collectors.joining(",")));
+ ImsPhoneConnection originalConnection =
+ (ImsPhoneConnection) mOriginalConnection;
+ originalConnection.sendRtpHeaderExtensions(rtpHeaderExtensions);
+ }
+ };
+ mRtpTransport = new RtpTransport(rtpAdapter, null /* TODO: not needed yet */, mHandler,
+ supportsSdpNegotiationOfRtpHeaderExtensions());
+ supportedTransports.add(mRtpTransport);
+ }
+ if (supportsD2DUsingDtmf()) {
+ Log.i(this, "maybeConfigureDeviceToDeviceCommunication: carrier supports DTMF.");
+ DtmfAdapter dtmfAdapter = digit -> {
+ Log.i(TelephonyConnection.this, "sendDtmf: send digit %c", digit);
+ ImsPhoneConnection originalConnection =
+ (ImsPhoneConnection) mOriginalConnection;
+ Message dtmfComplete = mHandler.obtainMessage(MSG_DTMF_DONE);
+ dtmfComplete.replyTo = mHandlerMessenger;
+ originalConnection.getImsCall().sendDtmf(digit, dtmfComplete);
+ };
+ ContentResolver cr = getPhone().getContext().getContentResolver();
+ mDtmfTransport = new DtmfTransport(dtmfAdapter, new Timeouts.Adapter(cr),
+ Executors.newSingleThreadScheduledExecutor());
+ supportedTransports.add(mDtmfTransport);
+ }
+ if (supportedTransports.size() > 0) {
+ mCommunicator = new Communicator(supportedTransports, this);
+ mD2DCallStateAdapter = new D2DCallStateAdapter(mCommunicator);
+ addTelephonyConnectionListener(mD2DCallStateAdapter);
+ } else {
+ Log.i(this, "maybeConfigureDeviceToDeviceCommunication: no transports; disabled.");
+ notifyD2DAvailabilityChanged(false);
+ }
+ }
+
+ /**
+ * Notifies upper layers of the availability of D2D communication.
+ * @param isAvailable {@code true} if D2D is available, {@code false} otherwise.
+ */
+ private void notifyD2DAvailabilityChanged(boolean isAvailable) {
+ Bundle extras = new Bundle();
+ extras.putBoolean(Connection.EXTRA_IS_DEVICE_TO_DEVICE_COMMUNICATION_AVAILABLE,
+ isAvailable);
+ putTelephonyExtras(extras);
+ }
+
+ /**
+ * @return The D2D communication class, or {@code null} if not set up.
+ */
+ public @Nullable Communicator getCommunicator() {
+ return mCommunicator;
+ }
+
+ /**
+ * Called by {@link Communicator} associated with this {@link TelephonyConnection} when there
+ * are incoming device-to-device messages received.
+ * @param messages the incoming messages.
+ */
+ @Override
+ public void onMessagesReceived(@NonNull Set<Communicator.Message> messages) {
+ Log.i(this, "onMessagesReceived: got d2d messages: %s", messages);
+ // Send connection events up to Telecom so that we can relay the messages to a valid
+ // CallDiagnosticService which is bound.
+ for (Communicator.Message msg : messages) {
+ Integer dcMsgType = MessageTypeAndValueHelper.MSG_TYPE_TO_DC_MSG_TYPE.getValue(
+ msg.getType());
+ if (dcMsgType == null) {
+ // Invalid msg type, skip.
+ continue;
+ }
+
+ Integer dcMsgValue;
+ switch (msg.getType()) {
+ case CallDiagnostics.MESSAGE_CALL_AUDIO_CODEC:
+ dcMsgValue = MessageTypeAndValueHelper.CODEC_TO_DC_CODEC.getValue(
+ msg.getValue());
+ break;
+ case CallDiagnostics.MESSAGE_CALL_NETWORK_TYPE:
+ dcMsgValue = MessageTypeAndValueHelper.RAT_TYPE_TO_DC_NETWORK_TYPE.getValue(
+ msg.getValue());
+ break;
+ case CallDiagnostics.MESSAGE_DEVICE_BATTERY_STATE:
+ dcMsgValue = MessageTypeAndValueHelper.BATTERY_STATE_TO_DC_BATTERY_STATE
+ .getValue(msg.getValue());
+ break;
+ case CallDiagnostics.MESSAGE_DEVICE_NETWORK_COVERAGE:
+ dcMsgValue = MessageTypeAndValueHelper.COVERAGE_TO_DC_COVERAGE
+ .getValue(msg.getValue());
+ break;
+ default:
+ Log.w(this, "onMessagesReceived: msg=%d - invalid msg", msg.getValue());
+ continue;
+ }
+ if (dcMsgValue == null) {
+ Log.w(this, "onMessagesReceived: msg=%d/%d - invalid msg value", msg.getType(),
+ msg.getValue());
+ continue;
+ }
+ Bundle extras = new Bundle();
+ extras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE, dcMsgType);
+ extras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE, dcMsgValue);
+ sendConnectionEvent(Connection.EVENT_DEVICE_TO_DEVICE_MESSAGE, extras);
+ }
+ }
+
+ /**
+ * Handles report from {@link Communicator} when the availability of D2D changes.
+ * @param isAvailable {@code true} if D2D is available, {@code false} if unavailable.
+ */
+ @Override
+ public void onD2DAvailabilitychanged(boolean isAvailable) {
+ notifyD2DAvailabilityChanged(isAvailable);
+ }
+
+ /**
* Called by a {@link ConnectionService} to notify Telecom that a {@link Conference#onMerge()}
* operation has started.
*/
@@ -3201,4 +3703,111 @@
listener.onStatusHintsChanged(this, statusHints);
}
}
+
+ /**
+ * Whether the incoming call number should be formatted to national number for Japan.
+ * @return {@code true} should be convert to the national format, {@code false} otherwise.
+ */
+ private boolean isNeededToFormatIncomingNumberForJp() {
+ if (mOriginalConnection.isIncoming()
+ && !TextUtils.isEmpty(mOriginalConnection.getAddress())
+ && mOriginalConnection.getAddress().startsWith(JAPAN_COUNTRY_CODE_WITH_PLUS_SIGN)) {
+ PersistableBundle b = getCarrierConfig();
+ return b != null && b.getBoolean(
+ CarrierConfigManager.KEY_FORMAT_INCOMING_NUMBER_TO_NATIONAL_FOR_JP_BOOL);
+ }
+ return false;
+ }
+
+ /**
+ * Format the incoming call number to national number for Japan.
+ * @param number
+ * @return the formatted phone number (e.g, "+819012345678" -> "09012345678")
+ */
+ private String formatIncomingNumberForJp(String number) {
+ return PhoneNumberUtils.stripSeparators(
+ PhoneNumberUtils.formatNumber(number, JAPAN_ISO_COUNTRY_CODE));
+ }
+
+ public TelecomAccountRegistry getTelecomAccountRegistry(Context context) {
+ return TelecomAccountRegistry.getInstance(context);
+ }
+
+ /**
+ * @return {@code true} if the carrier supports D2D using RTP header extensions, {@code false}
+ * otherwise.
+ */
+ private boolean supportsD2DUsingRtp() {
+ PersistableBundle b = getCarrierConfig();
+ return b != null && b.getBoolean(
+ CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL);
+ }
+
+ /**
+ * @return {@code true} if the carrier supports D2D using DTMF digits, {@code false} otherwise.
+ */
+ private boolean supportsD2DUsingDtmf() {
+ PersistableBundle b = getCarrierConfig();
+ return b != null && b.getBoolean(
+ CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL);
+ }
+
+ /**
+ * @return {@code true} if the carrier supports using SDP negotiation for the RTP header
+ * extensions used in D2D comms, {@code false} otherwise.
+ */
+ private boolean supportsSdpNegotiationOfRtpHeaderExtensions() {
+ PersistableBundle b = getCarrierConfig();
+ return b != null && b.getBoolean(
+ CarrierConfigManager
+ .KEY_SUPPORTS_SDP_NEGOTIATION_OF_D2D_RTP_HEADER_EXTENSIONS_BOOL);
+ }
+
+ /**
+ * Handles a device to device message which a {@link CallDiagnostics} wishes to send.
+ * @param extras the call event extras bundle.
+ */
+ private void handleOutgoingDeviceToDeviceMessage(Bundle extras) {
+ int messageType = extras.getInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE);
+ int messageValue = extras.getInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE);
+
+ Integer internalMessageValue;
+ switch (messageType) {
+ case CallDiagnostics.MESSAGE_CALL_AUDIO_CODEC:
+ internalMessageValue = MessageTypeAndValueHelper.CODEC_TO_DC_CODEC.getKey(
+ messageValue);
+ break;
+ case CallDiagnostics.MESSAGE_CALL_NETWORK_TYPE:
+ internalMessageValue = MessageTypeAndValueHelper.RAT_TYPE_TO_DC_NETWORK_TYPE.getKey(
+ messageValue);
+ break;
+ case CallDiagnostics.MESSAGE_DEVICE_BATTERY_STATE:
+ internalMessageValue = MessageTypeAndValueHelper.BATTERY_STATE_TO_DC_BATTERY_STATE
+ .getKey(messageValue);
+ break;
+ case CallDiagnostics.MESSAGE_DEVICE_NETWORK_COVERAGE:
+ internalMessageValue = MessageTypeAndValueHelper.COVERAGE_TO_DC_COVERAGE
+ .getKey(messageValue);
+ break;
+ default:
+ Log.w(this, "handleOutgoingDeviceToDeviceMessage: msg=%d - invalid msg",
+ messageType);
+ return;
+ }
+ Integer internalMessageType = MessageTypeAndValueHelper.MSG_TYPE_TO_DC_MSG_TYPE.getKey(
+ messageType);
+ if (internalMessageValue == null) {
+ Log.w(this, "handleOutgoingDeviceToDeviceMessage: msg=%d/%d - invalid value",
+ messageType, messageValue);
+ return;
+ }
+
+ if (mCommunicator != null) {
+ Log.w(this, "handleOutgoingDeviceToDeviceMessage: msg=%d/%d - sending",
+ internalMessageType, internalMessageValue);
+ Set<Communicator.Message> set = new ArraySet<>();
+ set.add(new Communicator.Message(internalMessageType, internalMessageValue));
+ mCommunicator.sendMessages(set);
+ }
+ }
}
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 290b4e1..7a0d8c2 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -17,17 +17,18 @@
package com.android.services.telephony;
import android.annotation.NonNull;
+import android.app.AlertDialog;
+import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
+import android.os.ParcelUuid;
import android.telecom.Conference;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
@@ -46,6 +47,7 @@
import android.telephony.emergency.EmergencyNumber;
import android.text.TextUtils;
import android.util.Pair;
+import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Call;
@@ -59,12 +61,15 @@
import com.android.internal.telephony.PhoneSwitcher;
import com.android.internal.telephony.RIL;
import com.android.internal.telephony.SubscriptionController;
+import com.android.internal.telephony.d2d.Communicator;
import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneConnection;
import com.android.phone.MMIDialogActivity;
import com.android.phone.PhoneUtils;
import com.android.phone.R;
+import com.android.phone.callcomposer.CallComposerPictureManager;
+import com.android.phone.settings.SuppServicesUiUtil;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -72,12 +77,14 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
@@ -86,7 +93,7 @@
* Service for making GSM and CDMA connections.
*/
public class TelephonyConnectionService extends ConnectionService {
-
+ private static final String LOG_TAG = TelephonyConnectionService.class.getSimpleName();
// Timeout before we continue with the emergency call without waiting for DDS switch response
// from the modem.
private static final int DEFAULT_DATA_SWITCH_TIMEOUT_MS = 1000;
@@ -155,6 +162,9 @@
private EmergencyTonePlayer mEmergencyTonePlayer;
private HoldTracker mHoldTracker;
private boolean mIsTtyEnabled;
+ /** Set to true when there is an emergency call pending which will potential trigger a dial.
+ * This must be set to false when the call is dialed. */
+ private volatile boolean mIsEmergencyCallPending;
// Contains one TelephonyConnection that has placed a call and a memory of which Phones it has
// already tried to connect with. There should be only one TelephonyConnection trying to place a
@@ -163,8 +173,6 @@
// destroyed.
@VisibleForTesting
public Pair<WeakReference<TelephonyConnection>, Queue<Phone>> mEmergencyRetryCache;
- private Handler mDdsSwitchHandler;
- private HandlerThread mHandlerThread;
private DeviceState mDeviceState = new DeviceState();
/**
@@ -351,27 +359,6 @@
};
/**
- * Factory for Handler creation in order to remove flakiness during t esting.
- */
- @VisibleForTesting
- public interface HandlerFactory {
- HandlerThread createHandlerThread(String name);
- Handler createHandler(Looper looper);
- }
-
- private HandlerFactory mHandlerFactory = new HandlerFactory() {
- @Override
- public HandlerThread createHandlerThread(String name) {
- return new HandlerThread(name);
- }
-
- @Override
- public Handler createHandler(Looper looper) {
- return new Handler(looper);
- }
- };
-
- /**
* DisconnectCause depends on PhoneGlobals in order to get a system context. Mock out
* dependency for testing.
*/
@@ -462,14 +449,6 @@
}
/**
- * Override Handler creation factory for testing.
- */
- @VisibleForTesting
- public void setHandlerFactory(HandlerFactory handlerFactory) {
- mHandlerFactory = handlerFactory;
- }
-
- /**
* Override DisconnectCause creation for testing.
*/
@VisibleForTesting
@@ -520,16 +499,11 @@
IntentFilter intentFilter = new IntentFilter(
TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED);
registerReceiver(mTtyBroadcastReceiver, intentFilter);
- mHandlerThread = mHandlerFactory.createHandlerThread("DdsSwitchHandlerThread");
- mHandlerThread.start();
- Looper looper = mHandlerThread.getLooper();
- mDdsSwitchHandler = mHandlerFactory.createHandler(looper);
}
@Override
public boolean onUnbind(Intent intent) {
unregisterReceiver(mTtyBroadcastReceiver);
- mHandlerThread.quitSafely();
return super.onUnbind(intent);
}
@@ -795,6 +769,10 @@
if (mRadioOnHelper == null) {
mRadioOnHelper = new RadioOnHelper(this);
}
+
+ if (isEmergencyNumber) {
+ mIsEmergencyCallPending = true;
+ }
mRadioOnHelper.triggerRadioOnAndListen(new RadioOnStateListener.Callback() {
@Override
public void onComplete(RadioOnStateListener listener, boolean isRadioReady) {
@@ -828,7 +806,7 @@
|| serviceState == ServiceState.STATE_IN_SERVICE;
}
}
- }, isEmergencyNumber && !isTestEmergencyNumber, phone);
+ }, isEmergencyNumber && !isTestEmergencyNumber, phone, isTestEmergencyNumber);
// Return the still unconnected GsmConnection and wait for the Radios to boot before
// connecting it to the underlying Phone.
return resultConnection;
@@ -859,14 +837,9 @@
} else {
final Connection resultConnection = getTelephonyConnection(request, numberToDial,
true, handle, phone);
- mDdsSwitchHandler.post(new Runnable() {
- @Override
- public void run() {
- boolean result = delayDialForDdsSwitch(phone);
- Log.i(this,
- "onCreateOutgoingConn - delayDialForDdsSwitch result = " + result);
+ delayDialForDdsSwitch(phone, (result) -> {
+ Log.i(this, "onCreateOutgoingConn - delayDialForDdsSwitch result = " + result);
placeOutgoingConnection(request, resultConnection, phone);
- }
});
return resultConnection;
}
@@ -907,6 +880,14 @@
}
/**
+ * @return whether radio has recently been turned on for emergency call but hasn't actually
+ * dialed the call yet.
+ */
+ public boolean isEmergencyCallPending() {
+ return mIsEmergencyCallPending;
+ }
+
+ /**
* Whether the cellular radio is power off because the device is on Bluetooth.
*/
private boolean isRadioPowerDownOnBluetooth() {
@@ -931,6 +912,7 @@
for (Phone curPhone : mPhoneFactoryProxy.getPhones()) {
curPhone.setRadioPower(true, false, false, true);
}
+ mIsEmergencyCallPending = false;
}
return;
}
@@ -939,23 +921,21 @@
adjustAndPlaceOutgoingConnection(phone, originalConnection, request, numberToDial,
handle, originalPhoneType, false);
} else {
- mDdsSwitchHandler.post(new Runnable() {
- @Override
- public void run() {
- boolean result = delayDialForDdsSwitch(phone);
- Log.i(this, "handleOnComplete - delayDialForDdsSwitch result = " + result);
- adjustAndPlaceOutgoingConnection(phone, originalConnection, request,
- numberToDial, handle, originalPhoneType, true);
- }
+ delayDialForDdsSwitch(phone, result -> {
+ Log.i(this, "handleOnComplete - delayDialForDdsSwitch "
+ + "result = " + result);
+ adjustAndPlaceOutgoingConnection(phone, originalConnection, request,
+ numberToDial, handle, originalPhoneType, true);
+ mIsEmergencyCallPending = false;
});
}
-
} else {
Log.w(this, "onCreateOutgoingConnection, failed to turn on radio");
closeOrDestroyConnection(originalConnection,
mDisconnectCauseFactory.toTelecomDisconnectCause(
android.telephony.DisconnectCause.POWER_OFF,
"Failed to turn on radio."));
+ mIsEmergencyCallPending = false;
}
}
@@ -1055,7 +1035,8 @@
if (state == ServiceState.STATE_OUT_OF_SERVICE) {
int dataNetType = phone.getServiceState().getDataNetworkType();
if (dataNetType == TelephonyManager.NETWORK_TYPE_LTE ||
- dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA) {
+ dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA ||
+ dataNetType == TelephonyManager.NETWORK_TYPE_NR) {
state = phone.getServiceState().getDataRegistrationState();
}
}
@@ -1139,9 +1120,9 @@
phone.getPhoneId()));
}
-
+ PhoneAccountHandle accountHandle = adjustAccountHandle(phone, request.getAccountHandle());
final TelephonyConnection connection =
- createConnectionFor(phone, null, true /* isOutgoing */, request.getAccountHandle(),
+ createConnectionFor(phone, null, true /* isOutgoing */, accountHandle,
request.getTelecomCallId(), request.isAdhocConferenceCall());
if (connection == null) {
return Connection.createFailedConnection(
@@ -1230,10 +1211,15 @@
createConnectionFor(phone, originalConnection, false /* isOutgoing */,
request.getAccountHandle(), request.getTelecomCallId(),
request.isAdhocConferenceCall());
+
handleIncomingRtt(request, originalConnection);
if (connection == null) {
return Connection.createCanceledConnection();
} else {
+ // Add extra to call if answering this incoming call would cause an in progress call on
+ // another subscription to be disconnected.
+ maybeIndicateAnsweringWillDisconnect(connection, request.getAccountHandle());
+
connection.setTtyEnabled(mDeviceState.isTtyModeEnabled(getApplicationContext()));
return connection;
}
@@ -1572,7 +1558,7 @@
Phone imsPhone = phone.getImsPhone();
return imsPhone != null
- && (imsPhone.isVolteEnabled() || imsPhone.isWifiCallingEnabled())
+ && (imsPhone.isVoiceOverCellularImsEnabled() || imsPhone.isWifiCallingEnabled())
&& (imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE);
}
@@ -1678,13 +1664,34 @@
? connection.getAddress().getSchemeSpecificPart()
: "";
- com.android.internal.telephony.Connection originalConnection = null;
+ if (showDataDialog(phone, number)) {
+ connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
+ android.telephony.DisconnectCause.DIALED_MMI, "UT is not available"));
+ return;
+ }
+
+ if (extras != null && extras.containsKey(TelecomManager.EXTRA_OUTGOING_PICTURE)) {
+ ParcelUuid uuid = extras.getParcelable(TelecomManager.EXTRA_OUTGOING_PICTURE);
+ CallComposerPictureManager.getInstance(phone.getContext(), phone.getSubId())
+ .storeUploadedPictureToCallLog(uuid.getUuid(), (uri) -> {
+ if (uri != null) {
+ try {
+ Bundle b = new Bundle();
+ b.putParcelable(TelecomManager.EXTRA_PICTURE_URI, uri);
+ connection.putTelephonyExtras(b);
+ } catch (Exception e) {
+ Log.e(this, e, "Couldn't set picture extra on outgoing call");
+ }
+ }
+ });
+ }
+
+ final com.android.internal.telephony.Connection originalConnection;
try {
if (phone != null) {
EmergencyNumber emergencyNumber =
phone.getEmergencyNumberTracker().getEmergencyNumber(number);
if (emergencyNumber != null) {
- phone.notifyOutgoingEmergencyCall(emergencyNumber);
if (!getAllConnections().isEmpty()) {
if (!shouldHoldForEmergencyCall(phone)) {
// If we do not support holding ongoing calls for an outgoing
@@ -1725,10 +1732,16 @@
.setVideoState(videoState)
.setIntentExtras(extras)
.setRttTextStream(connection.getRttTextStream())
- .build());
+ .build(),
+ // We need to wait until the phone has been chosen in GsmCdmaPhone to
+ // register for the associated TelephonyConnection call event listeners.
+ connection::registerForCallEvents);
+ } else {
+ originalConnection = null;
}
} catch (CallStateException e) {
Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e);
+ connection.unregisterForCallEvents();
handleCallStateException(e, connection, phone);
return;
}
@@ -1736,7 +1749,8 @@
if (originalConnection == null) {
int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
// On GSM phones, null connection means that we dialed an MMI code
- if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
+ if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM ||
+ phone.isUtEnabled()) {
Log.d(this, "dialed MMI code");
int subId = phone.getSubId();
Log.d(this, "subId: "+subId);
@@ -1755,7 +1769,17 @@
"Connection is null", phone.getPhoneId()));
connection.close();
} else {
- connection.setOriginalConnection(originalConnection);
+ if (!getMainThreadHandler().getLooper().isCurrentThread()) {
+ Log.w(this, "placeOriginalConnection - Unexpected, this call "
+ + "should always be on the main thread.");
+ getMainThreadHandler().post(() -> {
+ if (connection.getOriginalConnection() == null) {
+ connection.setOriginalConnection(originalConnection);
+ }
+ });
+ } else {
+ connection.setOriginalConnection(originalConnection);
+ }
}
}
@@ -1913,18 +1937,32 @@
/**
* If needed, block until the the default data is is switched for outgoing emergency call, or
* timeout expires.
+ * @param phone The Phone to switch the DDS on.
+ * @param completeConsumer The consumer to call once the default data subscription has been
+ * switched, provides {@code true} result if the switch happened
+ * successfully or {@code false} if the operation timed out/failed.
*/
- private boolean delayDialForDdsSwitch(Phone phone) {
+ private void delayDialForDdsSwitch(Phone phone, Consumer<Boolean> completeConsumer) {
if (phone == null) {
- return true;
+ // Do not block indefinitely.
+ completeConsumer.accept(false);
}
try {
- return possiblyOverrideDefaultDataForEmergencyCall(phone).get(
- DEFAULT_DATA_SWITCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ // Waiting for PhoneSwitcher to complete the operation.
+ CompletableFuture<Boolean> future = possiblyOverrideDefaultDataForEmergencyCall(phone);
+ // In the case that there is an issue or bug in PhoneSwitcher logic, do not wait
+ // indefinitely for the future to complete. Instead, set a timeout that will complete
+ // the future as to not block the outgoing call indefinitely.
+ CompletableFuture<Boolean> timeout = new CompletableFuture<>();
+ phone.getContext().getMainThreadHandler().postDelayed(
+ () -> timeout.complete(false), DEFAULT_DATA_SWITCH_TIMEOUT_MS);
+ // Also ensure that the Consumer is completed on the main thread.
+ future.acceptEitherAsync(timeout, completeConsumer,
+ phone.getContext().getMainExecutor());
} catch (Exception e) {
Log.w(this, "delayDialForDdsSwitch - exception= "
+ e.getMessage());
- return false;
+
}
}
@@ -2381,6 +2419,78 @@
}
}
+ private boolean showDataDialog(Phone phone, String number) {
+ boolean ret = false;
+ final Context context = getApplicationContext();
+ String suppKey = MmiCodeUtil.getSuppServiceKey(number);
+ if (suppKey != null) {
+ boolean clirOverUtPrecautions = false;
+ boolean cfOverUtPrecautions = false;
+ boolean cbOverUtPrecautions = false;
+ boolean cwOverUtPrecautions = false;
+
+ CarrierConfigManager cfgManager = (CarrierConfigManager)
+ phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (cfgManager != null) {
+ clirOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
+ .getBoolean(CarrierConfigManager.KEY_CALLER_ID_OVER_UT_WARNING_BOOL);
+ cfOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
+ .getBoolean(CarrierConfigManager.KEY_CALL_FORWARDING_OVER_UT_WARNING_BOOL);
+ cbOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
+ .getBoolean(CarrierConfigManager.KEY_CALL_BARRING_OVER_UT_WARNING_BOOL);
+ cwOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
+ .getBoolean(CarrierConfigManager.KEY_CALL_WAITING_OVER_UT_WARNING_BOOL);
+ }
+
+ boolean isSsOverUtPrecautions = SuppServicesUiUtil
+ .isSsOverUtPrecautions(context, phone);
+ if (isSsOverUtPrecautions) {
+ boolean showDialog = false;
+ if (suppKey == MmiCodeUtil.BUTTON_CLIR_KEY && clirOverUtPrecautions) {
+ showDialog = true;
+ } else if (suppKey == MmiCodeUtil.CALL_FORWARDING_KEY && cfOverUtPrecautions) {
+ showDialog = true;
+ } else if (suppKey == MmiCodeUtil.CALL_BARRING_KEY && cbOverUtPrecautions) {
+ showDialog = true;
+ } else if (suppKey == MmiCodeUtil.BUTTON_CW_KEY && cwOverUtPrecautions) {
+ showDialog = true;
+ }
+
+ if (showDialog) {
+ Log.d(this, "Creating UT Data enable dialog");
+ String message = SuppServicesUiUtil.makeMessage(context, suppKey, phone);
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ DialogInterface.OnClickListener networkSettingsClickListener =
+ new Dialog.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ ComponentName mobileNetworkSettingsComponent
+ = new ComponentName(
+ context.getString(
+ R.string.mobile_network_settings_package),
+ context.getString(
+ R.string.mobile_network_settings_class));
+ intent.setComponent(mobileNetworkSettingsComponent);
+ context.startActivity(intent);
+ }
+ };
+ Dialog dialog = builder.setMessage(message)
+ .setNeutralButton(context.getResources().getString(
+ R.string.settings_label),
+ networkSettingsClickListener)
+ .setPositiveButton(context.getResources().getString(
+ R.string.supp_service_over_ut_precautions_dialog_dismiss), null)
+ .create();
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ dialog.show();
+ ret = true;
+ }
+ }
+ }
+ return ret;
+ }
+
/**
* Adds a {@link Conference} to the telephony ConnectionService and registers a listener for
* changes to the conference. Should be used instead of {@link #addConference(Conference)}.
@@ -2390,4 +2500,147 @@
addConference(conference);
conference.addTelephonyConferenceListener(mTelephonyConferenceListener);
}
+
+ /**
+ * Sends a test device to device message on the active call which supports it.
+ * Used exclusively from the telephony shell command to send a test message.
+ *
+ * @param message the message
+ * @param value the value
+ */
+ public void sendTestDeviceToDeviceMessage(int message, int value) {
+ getAllConnections().stream()
+ .filter(f -> f instanceof TelephonyConnection)
+ .forEach(t -> {
+ TelephonyConnection tc = (TelephonyConnection) t;
+ if (!tc.isImsConnection()) {
+ Log.w(this, "sendTestDeviceToDeviceMessage: not an IMS connection");
+ return;
+ }
+ Communicator c = tc.getCommunicator();
+ if (c == null) {
+ Log.w(this, "sendTestDeviceToDeviceMessage: D2D not enabled");
+ return;
+ }
+
+ c.sendMessages(new HashSet<Communicator.Message>() {{
+ add(new Communicator.Message(message, value));
+ }});
+
+ });
+ }
+
+ /**
+ * Overrides the current D2D transport, forcing the specified one to be active. Used for test.
+ * @param transport The class simple name of the transport to make active.
+ */
+ public void setActiveDeviceToDeviceTransport(@NonNull String transport) {
+ getAllConnections().stream()
+ .filter(f -> f instanceof TelephonyConnection)
+ .forEach(t -> {
+ TelephonyConnection tc = (TelephonyConnection) t;
+ Communicator c = tc.getCommunicator();
+ if (c == null) {
+ Log.w(this, "setActiveDeviceToDeviceTransport: D2D not enabled");
+ return;
+ }
+ Log.i(this, "setActiveDeviceToDeviceTransport: callId=%s, set to: %s",
+ tc.getTelecomCallId(), transport);
+ c.setTransportActive(transport);
+ });
+ }
+
+ private PhoneAccountHandle adjustAccountHandle(Phone phone,
+ PhoneAccountHandle origAccountHandle) {
+ int origSubId = PhoneUtils.getSubIdForPhoneAccountHandle(origAccountHandle);
+ int subId = phone.getSubId();
+ if (origSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ && subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ && origSubId != subId) {
+ PhoneAccountHandle handle = TelecomAccountRegistry.getInstance(this)
+ .getPhoneAccountHandleForSubId(subId);
+ if (handle != null) {
+ return handle;
+ }
+ }
+ return origAccountHandle;
+ }
+
+ /**
+ * For the passed in incoming {@link TelephonyConnection}, add
+ * {@link Connection#EXTRA_ANSWERING_DROPS_FG_CALL} if there are ongoing calls on another
+ * subscription (ie phone account handle) than the one passed in.
+ * @param connection The connection.
+ * @param phoneAccountHandle The {@link PhoneAccountHandle} the incoming call originated on;
+ * this is passed in because
+ * {@link Connection#getPhoneAccountHandle()} is not set until after
+ * {@link ConnectionService#onCreateIncomingConnection(
+ * PhoneAccountHandle, ConnectionRequest)} returns.
+ */
+ public void maybeIndicateAnsweringWillDisconnect(@NonNull TelephonyConnection connection,
+ @NonNull PhoneAccountHandle phoneAccountHandle) {
+ if (isCallPresentOnOtherSub(phoneAccountHandle)) {
+ Log.i(this, "maybeIndicateAnsweringWillDisconnect; answering call %s will cause a call "
+ + "on another subscription to drop.", connection.getTelecomCallId());
+ Bundle extras = new Bundle();
+ extras.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
+ connection.putExtras(extras);
+ }
+ }
+
+ /**
+ * Checks to see if there are calls present on a sub other than the one passed in.
+ * @param incomingHandle The new incoming connection {@link PhoneAccountHandle}
+ */
+ private boolean isCallPresentOnOtherSub(@NonNull PhoneAccountHandle incomingHandle) {
+ return getAllConnections().stream()
+ .filter(c ->
+ // Exclude multiendpoint calls as they're not on this device.
+ (c.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) == 0
+ // Include any calls not on same sub as current connection.
+ && !Objects.equals(c.getPhoneAccountHandle(), incomingHandle))
+ .count() > 0;
+ }
+
+ /**
+ * Where there are ongoing calls on another subscription other than the one specified,
+ * disconnect these calls. This is used where there is an incoming call on one sub, but there
+ * are ongoing calls on another sub which need to be disconnected.
+ * @param incomingHandle The incoming {@link PhoneAccountHandle}.
+ */
+ public void maybeDisconnectCallsOnOtherSubs(@NonNull PhoneAccountHandle incomingHandle) {
+ Log.i(this, "maybeDisconnectCallsOnOtherSubs: check for calls not on %s", incomingHandle);
+ maybeDisconnectCallsOnOtherSubs(getAllConnections(), incomingHandle);
+ }
+
+ /**
+ * Used by {@link #maybeDisconnectCallsOnOtherSubs(PhoneAccountHandle)} to perform call
+ * disconnection. This method exists as a convenience so that it is possible to unit test
+ * the core functionality.
+ * @param connections the calls to check.
+ * @param incomingHandle the incoming handle.
+ */
+ @VisibleForTesting
+ public static void maybeDisconnectCallsOnOtherSubs(@NonNull Collection<Connection> connections,
+ @NonNull PhoneAccountHandle incomingHandle) {
+ connections.stream()
+ .filter(c ->
+ // Exclude multiendpoint calls as they're not on this device.
+ (c.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) == 0
+ // Include any calls not on same sub as current connection.
+ && !Objects.equals(c.getPhoneAccountHandle(), incomingHandle))
+ .forEach(c -> {
+ if (c instanceof TelephonyConnection) {
+ TelephonyConnection tc = (TelephonyConnection) c;
+ if (!tc.shouldTreatAsEmergencyCall()) {
+ Log.i(LOG_TAG, "maybeDisconnectCallsOnOtherSubs: disconnect %s due to "
+ + "incoming call on other sub.", tc.getTelecomCallId());
+ // Note: intentionally calling hangup instead of onDisconnect.
+ // onDisconnect posts the disconnection to a handle which means that the
+ // disconnection will take place AFTER we answer the incoming call.
+ tc.hangup(android.telephony.DisconnectCause.LOCAL);
+ }
+ }
+ });
+ }
}
diff --git a/src/com/android/services/telephony/rcs/DelegateBinderStateManager.java b/src/com/android/services/telephony/rcs/DelegateBinderStateManager.java
new file mode 100644
index 0000000..1a016ee
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/DelegateBinderStateManager.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2020 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.services.telephony.rcs;
+
+import android.telephony.ims.DelegateRegistrationState;
+import android.telephony.ims.DelegateRequest;
+import android.telephony.ims.FeatureTagState;
+import android.telephony.ims.SipDelegateConfiguration;
+import android.telephony.ims.SipDelegateConnection;
+import android.telephony.ims.SipDelegateImsConfiguration;
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.aidl.ISipDelegate;
+import android.telephony.ims.aidl.ISipDelegateMessageCallback;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/**
+ * Defines the interface to be used to manage the state of a SipDelegate on the ImsService side.
+ */
+public interface DelegateBinderStateManager {
+
+ /**
+ * Callback interface that allows listeners to listen to changes in registration or
+ * configuration state.
+ */
+ interface StateCallback {
+ /**
+ * The SipDelegate has notified telephony that the registration state has changed.
+ */
+ void onRegistrationStateChanged(DelegateRegistrationState registrationState);
+
+ /**
+ * The SipDelegate has notified telephony that the IMS configuration has changed.
+ */
+ void onImsConfigurationChanged(SipDelegateImsConfiguration config);
+
+ /**
+ * The SipDelegate has notified telephony that the IMS configuration has changed.
+ */
+ void onConfigurationChanged(SipDelegateConfiguration config);
+ }
+
+ /** Allow for mocks to be created for testing. */
+ @VisibleForTesting
+ interface Factory {
+ /**
+ * Create a new instance of this interface, which may change depending on the tags being
+ * denied. See {@link SipDelegateBinderConnectionStub} and
+ * {@link SipDelegateBinderConnection}
+ */
+ DelegateBinderStateManager create(int subId, DelegateRequest requestedConfig,
+ Set<FeatureTagState> transportDeniedTags,
+ Executor executor, List<StateCallback> stateCallbacks);
+ }
+
+ /**
+ * Start the process to create a SipDelegate on the ImsService.
+ * @param cb The Binder interface that the SipDelegate should use to notify new incoming SIP
+ * messages as well as acknowledge whether or not an outgoing SIP message was
+ * successfully sent.
+ * @param createdConsumer The consumer that will be notified when the creation process has
+ * completed. Contains the ISipDelegate interface to communicate with the SipDelegate
+ * and the feature tags the SipDelegate itself denied.
+ * @return true if the creation process started, false if the remote process died. If false, the
+ * consumers will not be notified.
+ */
+ boolean create(ISipDelegateMessageCallback cb,
+ BiConsumer<ISipDelegate, Set<FeatureTagState>> createdConsumer);
+
+ /**
+ * Destroy the existing SipDelegate managed by this object.
+ * <p>
+ * This instance should be cleaned up after this call.
+ * @param reason The reason for why this delegate is being destroyed.
+ * @param destroyedConsumer The consumer that will be notified when this operation completes.
+ * Contains the reason the SipDelegate reported it was destroyed.
+ */
+ void destroy(int reason, Consumer<Integer> destroyedConsumer);
+
+ /**
+ * Called by IMS application, see
+ * {@link SipDelegateManager#triggerFullNetworkRegistration(SipDelegateConnection, int, String)}
+ * for more information about when this is called.
+ */
+ void triggerFullNetworkRegistration(int sipCode, String sipReason);
+}
diff --git a/src/com/android/services/telephony/rcs/DelegateStateTracker.java b/src/com/android/services/telephony/rcs/DelegateStateTracker.java
new file mode 100644
index 0000000..321c7ba
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/DelegateStateTracker.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2020 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.services.telephony.rcs;
+
+import android.os.RemoteException;
+import android.telephony.ims.DelegateRegistrationState;
+import android.telephony.ims.FeatureTagState;
+import android.telephony.ims.SipDelegateConfiguration;
+import android.telephony.ims.SipDelegateImsConfiguration;
+import android.telephony.ims.aidl.ISipDelegate;
+import android.telephony.ims.aidl.ISipDelegateConnectionStateCallback;
+import android.telephony.ims.stub.DelegateConnectionStateCallback;
+import android.util.LocalLog;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Manages the events sent back to the remote IMS application using the AIDL backing for the
+ * {@link DelegateConnectionStateCallback} interface.
+ */
+public class DelegateStateTracker implements DelegateBinderStateManager.StateCallback {
+ private static final String LOG_TAG = "DelegateST";
+
+ private final int mSubId;
+ private final ISipDelegateConnectionStateCallback mAppStateCallback;
+ private final ISipDelegate mLocalDelegateImpl;
+
+ private final LocalLog mLocalLog = new LocalLog(SipTransportController.LOG_SIZE);
+
+ private List<FeatureTagState> mDelegateDeniedTags;
+ private DelegateRegistrationState mLastRegState;
+ private boolean mCreatedCalled = false;
+ private int mRegistrationStateOverride = -1;
+
+ public DelegateStateTracker(int subId, ISipDelegateConnectionStateCallback appStateCallback,
+ ISipDelegate localDelegateImpl) {
+ mSubId = subId;
+ mAppStateCallback = appStateCallback;
+ mLocalDelegateImpl = localDelegateImpl;
+ }
+
+ /**
+ * Notify this state tracker that a new internal SipDelegate has been connected.
+ *
+ * Registration and state updates will be send via the
+ * {@link SipDelegateBinderConnection.StateCallback} callback implemented by this class as they
+ * arrive.
+ * @param deniedTags The tags denied by the SipTransportController and ImsService creating the
+ * SipDelegate. These tags will need to be notified back to the IMS application.
+ */
+ public void sipDelegateConnected(Set<FeatureTagState> deniedTags) {
+ logi("SipDelegate connected with denied tags:" + deniedTags);
+ // From the IMS application perspective, we only call onCreated/onDestroyed once and
+ // provide the local implementation of ISipDelegate, which doesn't change, even though
+ // SipDelegates may be changing underneath.
+ if (!mCreatedCalled) {
+ mCreatedCalled = true;
+ notifySipDelegateCreated();
+ }
+ mRegistrationStateOverride = -1;
+ mDelegateDeniedTags = new ArrayList<>(deniedTags);
+ }
+
+ /**
+ * The underlying SipDelegate is changing due to a state change in the SipDelegateController.
+ *
+ * This will trigger an override of the IMS application's registration state. All feature tags
+ * in the REGISTERED state will be overridden to move to the deregistering state specified until
+ * a new SipDelegate was successfully created and {@link #sipDelegateConnected(Set)} was called
+ * or it was destroyed and {@link #sipDelegateDestroyed(int)} was called.
+ * @param deregisteringReason The new deregistering reason that all feature tags in the
+ * registered state should now report.
+ */
+ public void sipDelegateChanging(int deregisteringReason) {
+ logi("SipDelegate Changing");
+ mRegistrationStateOverride = deregisteringReason;
+ if (mLastRegState == null) {
+ logw("sipDelegateChanging: invalid state, onRegistrationStateChanged never called.");
+ mLastRegState = new DelegateRegistrationState.Builder().build();
+ }
+ onRegistrationStateChanged(mLastRegState);
+ }
+
+ /**
+ * The underlying SipDelegate has been destroyed.
+ *
+ * This should only be called when the entire {@link SipDelegateController} is going down
+ * because the application has requested that the SipDelegate be destroyed.
+ *
+ * This can also be called in error conditions where the IMS application or ImsService has
+ * crashed.
+ * @param reason The reason that will be sent to the IMS application for why the SipDelegate
+ * is being destroyed.
+ */
+ public void sipDelegateDestroyed(int reason) {
+ logi("SipDelegate destroyed:" + reason);
+ mRegistrationStateOverride = -1;
+ try {
+ mAppStateCallback.onDestroyed(reason);
+ } catch (RemoteException e) {
+ logw("sipDelegateDestroyed: IMS application is dead: " + e);
+ }
+ }
+
+ /**
+ * The underlying SipDelegate has reported that its registration state has changed.
+ * @param registrationState The RegistrationState reported by the SipDelegate to be sent to the
+ * IMS application.
+ */
+ @Override
+ public void onRegistrationStateChanged(DelegateRegistrationState registrationState) {
+ if (mRegistrationStateOverride > DelegateRegistrationState.DEREGISTERED_REASON_UNKNOWN) {
+ logi("onRegistrationStateChanged: overriding registered state to "
+ + mRegistrationStateOverride);
+ registrationState = overrideRegistrationForDelegateChange(mRegistrationStateOverride,
+ registrationState);
+ }
+ if (registrationState.equals(mLastRegState)) {
+ logi("onRegistrationStateChanged: skipping notification, state is the same.");
+ return;
+ }
+ mLastRegState = registrationState;
+ logi("onRegistrationStateChanged: sending reg state " + registrationState);
+ try {
+ mAppStateCallback.onFeatureTagStatusChanged(registrationState, mDelegateDeniedTags);
+ } catch (RemoteException e) {
+ logw("onRegistrationStateChanged: IMS application is dead: " + e);
+ }
+ }
+
+ /**
+ * THe underlying SipDelegate has reported that the IMS configuration has changed.
+ * @param config The config to be sent to the IMS application.
+ */
+ @Override
+ public void onImsConfigurationChanged(SipDelegateImsConfiguration config) {
+ logi("onImsConfigurationChanged: Sending new IMS configuration.");
+ try {
+ mAppStateCallback.onImsConfigurationChanged(config);
+ } catch (RemoteException e) {
+ logw("onImsConfigurationChanged: IMS application is dead: " + e);
+ }
+ }
+
+ /**
+ * THe underlying SipDelegate has reported that the IMS configuration has changed.
+ * @param config The config to be sent to the IMS application.
+ */
+ @Override
+ public void onConfigurationChanged(SipDelegateConfiguration config) {
+ logi("onImsConfigurationChanged: Sending new IMS configuration.");
+ try {
+ mAppStateCallback.onConfigurationChanged(config);
+ } catch (RemoteException e) {
+ logw("onImsConfigurationChanged: IMS application is dead: " + e);
+ }
+ }
+
+ /** Write state about this tracker into the PrintWriter to be included in the dumpsys */
+ public void dump(PrintWriter printWriter) {
+ printWriter.println("Last reg state: " + mLastRegState);
+ printWriter.println("Denied tags: " + mDelegateDeniedTags);
+ printWriter.println();
+ printWriter.println("Most recent logs: ");
+ mLocalLog.dump(printWriter);
+ }
+
+ private DelegateRegistrationState overrideRegistrationForDelegateChange(
+ int registerOverrideReason, DelegateRegistrationState state) {
+ Set<String> registeredFeatures = state.getRegisteredFeatureTags();
+ DelegateRegistrationState.Builder overriddenState = new DelegateRegistrationState.Builder();
+ // keep other deregistering/deregistered tags the same.
+ for (FeatureTagState dereging : state.getDeregisteringFeatureTags()) {
+ overriddenState.addDeregisteringFeatureTag(dereging.getFeatureTag(),
+ dereging.getState());
+ }
+ for (FeatureTagState dereged : state.getDeregisteredFeatureTags()) {
+ overriddenState.addDeregisteredFeatureTag(dereged.getFeatureTag(),
+ dereged.getState());
+ }
+ // Override REGISTERED only
+ for (String ft : registeredFeatures) {
+ overriddenState.addDeregisteringFeatureTag(ft, registerOverrideReason);
+ }
+ return overriddenState.build();
+ }
+
+ private void notifySipDelegateCreated() {
+ try {
+ mAppStateCallback.onCreated(mLocalDelegateImpl);
+ } catch (RemoteException e) {
+ logw("notifySipDelegateCreated: IMS application is dead: " + e);
+ }
+ }
+
+ private void logi(String log) {
+ Log.i(SipTransportController.LOG_TAG, LOG_TAG + "[" + mSubId + "] " + log);
+ mLocalLog.log("[I] " + log);
+ }
+ private void logw(String log) {
+ Log.w(SipTransportController.LOG_TAG, LOG_TAG + "[" + mSubId + "] " + log);
+ mLocalLog.log("[W] " + log);
+ }
+}
diff --git a/src/com/android/services/telephony/rcs/MessageTransportWrapper.java b/src/com/android/services/telephony/rcs/MessageTransportWrapper.java
new file mode 100644
index 0000000..45f7d95
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/MessageTransportWrapper.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) 2020 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.services.telephony.rcs;
+
+import android.os.Binder;
+import android.os.RemoteException;
+import android.telephony.ims.DelegateMessageCallback;
+import android.telephony.ims.DelegateRegistrationState;
+import android.telephony.ims.FeatureTagState;
+import android.telephony.ims.SipDelegateConfiguration;
+import android.telephony.ims.SipDelegateImsConfiguration;
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.SipMessage;
+import android.telephony.ims.aidl.ISipDelegate;
+import android.telephony.ims.aidl.ISipDelegateMessageCallback;
+import android.telephony.ims.stub.SipDelegate;
+import android.util.LocalLog;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.SipMessageParsingUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.phone.RcsProvisioningMonitor;
+import com.android.services.telephony.rcs.validator.ValidationResult;
+
+import java.io.PrintWriter;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.function.Consumer;
+
+/**
+ * Wraps the SIP message path both from the IMS application to the SipDelegate and from the
+ * SipDelegate back to the IMS Application.
+ * <p>
+ * Queues incoming and outgoing SIP messages on an Executor and deliver to IMS application and
+ * SipDelegate in order. If there is an error delivering the message, the caller is notified.
+ * Uses {@link TransportSipMessageValidator} to track ongoing SIP dialogs and verify outgoing
+ * messages.
+ * <p>
+ * Note: This handles incoming binder calls, so all calls from other processes should be handled on
+ * the provided Executor.
+ */
+public class MessageTransportWrapper implements DelegateBinderStateManager.StateCallback {
+ private static final String TAG = "MessageTW";
+
+ // SipDelegateConnection(IMS Application) -> SipDelegate(ImsService)
+ private final ISipDelegate.Stub mSipDelegateConnection = new ISipDelegate.Stub() {
+ /**
+ * The IMS application is acknowledging that it has successfully received and processed an
+ * incoming SIP message sent by the SipDelegate in
+ * {@link ISipDelegateMessageCallback#onMessageReceived(SipMessage)}.
+ */
+ @Override
+ public void notifyMessageReceived(String viaTransactionId) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ if (mSipDelegate == null) {
+ logw("notifyMessageReceived called when SipDelegate is not associated for "
+ + "transaction id: " + viaTransactionId);
+ return;
+ }
+ try {
+ mSipSessionTracker.acknowledgePendingMessage(viaTransactionId);
+ mSipDelegate.notifyMessageReceived(viaTransactionId);
+ } catch (RemoteException e) {
+ logw("SipDelegate not available when notifyMessageReceived was called "
+ + "for transaction id: " + viaTransactionId);
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * The IMS application is acknowledging that it received an incoming SIP message sent by the
+ * SipDelegate in {@link ISipDelegateMessageCallback#onMessageReceived(SipMessage)} but it
+ * was unable to process it.
+ */
+ @Override
+ public void notifyMessageReceiveError(String viaTransactionId, int reason) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ if (mSipDelegate == null) {
+ logw("notifyMessageReceiveError called when SipDelegate is not associated "
+ + "for transaction id: " + viaTransactionId);
+ return;
+ }
+ try {
+ mSipSessionTracker.notifyPendingMessageFailed(viaTransactionId);
+ mSipDelegate.notifyMessageReceiveError(viaTransactionId, reason);
+ } catch (RemoteException e) {
+ logw("SipDelegate not available when notifyMessageReceiveError was called "
+ + "for transaction id: " + viaTransactionId);
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * The IMS application is sending an outgoing SIP message to the SipDelegate to be processed
+ * and sent over the network.
+ */
+ @Override
+ public void sendMessage(SipMessage sipMessage, long configVersion) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ ValidationResult result =
+ mSipSessionTracker.verifyOutgoingMessage(sipMessage, configVersion);
+ result = maybeOverrideValidationForTesting(result);
+ if (!result.isValidated) {
+ notifyDelegateSendError("Outgoing - " + result.logReason,
+ sipMessage, result.restrictedReason);
+ return;
+ }
+ try {
+ if (mSipDelegate == null) {
+ logw("sendMessage called when SipDelegate is not associated."
+ + sipMessage);
+ notifyDelegateSendError("No SipDelegate", sipMessage,
+ SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD);
+
+ return;
+ }
+ mSipDelegate.sendMessage(sipMessage, configVersion);
+ } catch (RemoteException e) {
+ notifyDelegateSendError("RemoteException: " + e, sipMessage,
+ SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD);
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * The SipDelegateConnection is requesting that the resources associated with an ongoing SIP
+ * dialog be released as the SIP dialog is now closed.
+ */
+ @Override
+ public void cleanupSession(String callId) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> cleanupSessionInternal(callId));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
+
+ // SipDelegate(ImsService) -> SipDelegateConnection(IMS Application)
+ private final ISipDelegateMessageCallback.Stub mDelegateConnectionMessageCallback =
+ new ISipDelegateMessageCallback.Stub() {
+ /**
+ * An Incoming SIP Message has been received by the SipDelegate and is being routed
+ * to the IMS application for processing.
+ * <p>
+ * IMS application will call {@link ISipDelegate#notifyMessageReceived(String)} to
+ * acknowledge receipt of this incoming message.
+ */
+ @Override
+ public void onMessageReceived(SipMessage message) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ ValidationResult result = mSipSessionTracker.verifyIncomingMessage(message);
+ if (!result.isValidated) {
+ notifyAppReceiveError("Incoming - " + result.logReason, message,
+ result.restrictedReason);
+ return;
+ }
+ try {
+ mAppCallback.onMessageReceived(message);
+ } catch (RemoteException e) {
+ notifyAppReceiveError("RemoteException: " + e, message,
+ SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD);
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * An outgoing SIP message sent previously by the SipDelegateConnection to the SipDelegate
+ * using {@link ISipDelegate#sendMessage(SipMessage, long)} as been successfully sent.
+ */
+ @Override
+ public void onMessageSent(String viaTransactionId) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ if (mSipDelegate == null) {
+ logw("Unexpected state, onMessageSent called when SipDelegate is not "
+ + "associated");
+ }
+ try {
+ mSipSessionTracker.acknowledgePendingMessage(viaTransactionId);
+ mAppCallback.onMessageSent(viaTransactionId);
+ } catch (RemoteException e) {
+ logw("Error sending onMessageSent to SipDelegateConnection, remote not"
+ + "available for transaction ID: " + viaTransactionId);
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * An outgoing SIP message sent previously by the SipDelegateConnection to the SipDelegate
+ * using {@link ISipDelegate#sendMessage(SipMessage, long)} failed to be sent.
+ */
+ @Override
+ public void onMessageSendFailure(String viaTransactionId, int reason) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ if (mSipDelegate == null) {
+ logw("Unexpected state, onMessageSendFailure called when SipDelegate is not"
+ + "associated");
+ }
+ try {
+ mSipSessionTracker.notifyPendingMessageFailed(viaTransactionId);
+ mAppCallback.onMessageSendFailure(viaTransactionId, reason);
+ } catch (RemoteException e) {
+ logw("Error sending onMessageSendFailure to SipDelegateConnection, remote"
+ + " not available for transaction ID: " + viaTransactionId);
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
+
+ /**
+ * Interface for injecting validator override dependencies for testing.
+ */
+ @VisibleForTesting
+ public interface ValidatorOverride {
+ /**
+ * @return {@code null} if the validation result should not be overridden, {@code true} if
+ * the validation result should always pass, {@code false} if the validation result should
+ * always fail.
+ */
+ Boolean getValidatorOverrideState();
+ }
+
+ private final ValidatorOverride mValidatorOverride;
+ private final ISipDelegateMessageCallback mAppCallback;
+ private final Executor mExecutor;
+ private final int mSubId;
+ private final TransportSipMessageValidator mSipSessionTracker;
+ private final LocalLog mLocalLog = new LocalLog(SipTransportController.LOG_SIZE);
+
+ private ISipDelegate mSipDelegate;
+
+ public MessageTransportWrapper(int subId, ScheduledExecutorService executor,
+ ISipDelegateMessageCallback appMessageCallback) {
+ mSubId = subId;
+ mAppCallback = appMessageCallback;
+ mExecutor = executor;
+ mSipSessionTracker = new TransportSipMessageValidator(subId, executor);
+ mValidatorOverride = () -> RcsProvisioningMonitor.getInstance()
+ .getImsFeatureValidationOverride(mSubId);
+ }
+
+ /**
+ * Mock out dependencies for unit testing.
+ */
+ @VisibleForTesting
+ public MessageTransportWrapper(int subId, ScheduledExecutorService executor,
+ ISipDelegateMessageCallback appMessageCallback,
+ TransportSipMessageValidator sipSessionTracker) {
+ mSubId = subId;
+ mAppCallback = appMessageCallback;
+ mExecutor = executor;
+ mSipSessionTracker = sipSessionTracker;
+ // Remove links to static methods calls querying overrides for testing.
+ mValidatorOverride = () -> null;
+ }
+
+ @Override
+ public void onRegistrationStateChanged(DelegateRegistrationState registrationState) {
+ mSipSessionTracker.onRegistrationStateChanged((callIds) -> {
+ for (String id : callIds) {
+ cleanupSessionInternal(id);
+ }
+ }, registrationState);
+ }
+
+ @Override
+ public void onImsConfigurationChanged(SipDelegateImsConfiguration config) {
+ mSipSessionTracker.onImsConfigurationChanged(config);
+ }
+
+ @Override
+ public void onConfigurationChanged(SipDelegateConfiguration config) {
+ mSipSessionTracker.onConfigurationChanged(config);
+ }
+
+ /**
+ * Open the transport and allow SIP messages to be sent/received on the delegate specified.
+ * @param delegate The delegate connection to send SIP messages to on the ImsService.
+ * @param supportedFeatureTags Feature tags that are supported. Outgoing SIP messages relating
+ * to these tags will be allowed.
+ * @param deniedFeatureTags Feature tags that have been denied. Outgoing SIP messages relating
+ * to these tags will be denied.
+ */
+ public void openTransport(ISipDelegate delegate, Set<String> supportedFeatureTags,
+ Set<FeatureTagState> deniedFeatureTags) {
+ logi("openTransport: delegate=" + delegate + ", supportedTags=" + supportedFeatureTags
+ + ", deniedTags=" + deniedFeatureTags);
+ mSipSessionTracker.onTransportOpened(supportedFeatureTags, deniedFeatureTags);
+ mSipDelegate = delegate;
+ }
+
+ /** Dump state about this tracker that should be included in the dumpsys */
+ public void dump(PrintWriter printWriter) {
+ IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
+ pw.println("Most recent logs:");
+ mLocalLog.dump(printWriter);
+ pw.println();
+ pw.println("Dialog Tracker:");
+ pw.increaseIndent();
+ mSipSessionTracker.dump(pw);
+ pw.decreaseIndent();
+ }
+
+ /**
+ * @return SipDelegate implementation to be sent to IMS application.
+ */
+ public ISipDelegate getDelegateConnection() {
+ return mSipDelegateConnection;
+ }
+
+ /**
+ * @return The remote application's message callback.
+ */
+ public ISipDelegateMessageCallback getAppMessageCallback() {
+ return mAppCallback;
+ }
+
+ /**
+ * @return MessageCallback implementation to be sent to the ImsService.
+ */
+ public ISipDelegateMessageCallback getMessageCallback() {
+ return mDelegateConnectionMessageCallback;
+ }
+
+ /**
+ * Gradually close all SIP Sessions by:
+ * 1) denying all new outgoing SIP Dialog requests with the reason specified and
+ * 2) only allowing existing SIP Sessions to continue.
+ * <p>
+ * This will allow traffic to continue on existing SIP Sessions until a BYE is sent and the
+ * corresponding SIP Dialogs are closed or a timeout is hit and
+ * {@link SipDelegate#cleanupSession(String)} (String)} is forcefully called on all open SIP
+ * sessions.
+ * <p>
+ * Any outgoing out-of-dialog traffic on this transport will be denied with the provided reason.
+ * <p>
+ * Incoming out-of-dialog traffic will continue to be set up until the SipDelegate is fully
+ * closed.
+ * @param delegateClosingReason The reason code to return to
+ * {@link DelegateMessageCallback#onMessageSendFailure(String, int)} if a new out-of-dialog SIP
+ * message is received while waiting for existing Dialogs.
+ * @param closedReason reason to return to new outgoing SIP messages via
+ * {@link SipDelegate#notifyMessageReceiveError(String, int)} once the transport
+ * transitions to the fully closed state.
+ * @param resultConsumer The consumer called when the message transport has been closed. It will
+ * return {@code true} if the procedure completed successfully or {@link false} if the
+ * transport needed to be closed forcefully due to the application not responding before
+ * a timeout occurred.
+ */
+ public void closeGracefully(int delegateClosingReason, int closedReason,
+ Consumer<Boolean> resultConsumer) {
+ logi("closeGracefully: closingReason=" + delegateClosingReason + ", closedReason="
+ + closedReason + ", resultConsumer(" + resultConsumer.hashCode() + ")");
+ mSipSessionTracker.closeSessionsGracefully((openCallIds) -> {
+ logi("closeGracefully resultConsumer(" + resultConsumer.hashCode()
+ + "): open call IDs:{" + openCallIds + "}");
+ closeTransport(openCallIds);
+ // propagate event to the consumer
+ resultConsumer.accept(openCallIds.isEmpty() /*successfullyClosed*/);
+ }, delegateClosingReason, closedReason);
+ }
+
+ /**
+ * Close all ongoing SIP sessions immediately and respond to any incoming/outgoing messages with
+ * the provided reason.
+ * @param closedReason The failure reason to provide to incoming/outgoing SIP messages
+ * if an attempt is made to send/receive a message after this method is called.
+ */
+ public void close(int closedReason) {
+ Set<String> openSessions = mSipSessionTracker.closeSessions(closedReason);
+ logi("close: closedReason=" + closedReason + "open call IDs:{" + openSessions + "}");
+ closeTransport(openSessions);
+ }
+
+ // Clean up all state related to the existing SipDelegate immediately.
+ private void closeTransport(Set<String> openCallIds) {
+ for (String id : openCallIds) {
+ cleanupSessionInternal(id);
+ }
+ mSipDelegate = null;
+ }
+
+ private void cleanupSessionInternal(String callId) {
+ logi("cleanupSessionInternal: clean up session with callId: " + callId);
+ try {
+ if (mSipDelegate == null) {
+ logw("cleanupSessionInternal: SipDelegate is not associated, callId: " + callId);
+ } else {
+ // This will close the transport, so call cleanup on ImsService first.
+ mSipDelegate.cleanupSession(callId);
+ }
+ } catch (RemoteException e) {
+ logw("cleanupSessionInternal: remote not available when cleanupSession was called "
+ + "for call id: " + callId);
+ }
+ mSipSessionTracker.onSipSessionCleanup(callId);
+ }
+
+ private ValidationResult maybeOverrideValidationForTesting(ValidationResult result) {
+ Boolean isValidatedOverride = mValidatorOverride.getValidatorOverrideState();
+ if (isValidatedOverride == null) {
+ return result;
+ }
+ if (isValidatedOverride) {
+ return ValidationResult.SUCCESS;
+ } else if (result.isValidated) {
+ // if override is set to false and the original result was validated, return a new
+ // restricted result with UNKNOWN reason.
+ return new ValidationResult(SipDelegateManager.MESSAGE_FAILURE_REASON_UNKNOWN,
+ "validation failed due to a testing override being set");
+ }
+ return result;
+ }
+
+ private void notifyDelegateSendError(String logReason, SipMessage message, int reasonCode) {
+ String transactionId = SipMessageParsingUtils.getTransactionId(message.getHeaderSection());
+ logi("Error sending SipMessage[id: " + transactionId + ", code: " + reasonCode
+ + "] -> SipDelegate for reason: " + logReason);
+ try {
+ mAppCallback.onMessageSendFailure(transactionId, reasonCode);
+ } catch (RemoteException e) {
+ logw("notifyDelegateSendError, SipDelegate is not available: " + e);
+ }
+ }
+
+ private void notifyAppReceiveError(String logReason, SipMessage message, int reasonCode) {
+ String transactionId = SipMessageParsingUtils.getTransactionId(message.getHeaderSection());
+ logi("Error sending SipMessage[id: " + transactionId + ", code: " + reasonCode + "] -> "
+ + "SipDelegateConnection for reason: " + logReason);
+ try {
+ mSipDelegate.notifyMessageReceiveError(transactionId, reasonCode);
+ } catch (RemoteException e) {
+ logw("notifyAppReceiveError, SipDelegate is not available: " + e);
+ }
+ }
+
+ private void logi(String log) {
+ Log.i(SipTransportController.LOG_TAG, TAG + "[" + mSubId + "] " + log);
+ mLocalLog.log("[I] " + log);
+ }
+
+ private void logw(String log) {
+ Log.w(SipTransportController.LOG_TAG, TAG + "[" + mSubId + "] " + log);
+ mLocalLog.log("[W] " + log);
+ }
+}
diff --git a/src/com/android/services/telephony/rcs/RcsFeatureController.java b/src/com/android/services/telephony/rcs/RcsFeatureController.java
index 5094c57..7834903 100644
--- a/src/com/android/services/telephony/rcs/RcsFeatureController.java
+++ b/src/com/android/services/telephony/rcs/RcsFeatureController.java
@@ -20,9 +20,7 @@
import android.content.Context;
import android.net.Uri;
import android.telephony.ims.ImsException;
-import android.telephony.ims.ImsRcsManager;
import android.telephony.ims.ImsReasonInfo;
-import android.telephony.ims.RegistrationManager;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.IImsRegistrationCallback;
import android.telephony.ims.stub.ImsRegistrationImplBase;
@@ -30,7 +28,7 @@
import android.util.Log;
import com.android.ims.FeatureConnector;
-import com.android.ims.IFeatureConnector;
+import com.android.ims.FeatureUpdates;
import com.android.ims.RcsFeatureManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.imsphone.ImsRegistrationCallbackHelper;
@@ -65,28 +63,38 @@
void onRcsDisconnected();
/**
- * The subscription associated with the slot this controller is bound to has changed or its
- * carrier configuration has changed.
+ * The subscription associated with the slot this controller is bound to has changed.
*/
void onAssociatedSubscriptionUpdated(int subId);
/**
+ * The carrier configuration associated with the active subscription id has changed.
+ */
+ void onCarrierConfigChanged();
+
+ /**
* Called when the feature should be destroyed.
*/
void onDestroy();
+
+ /**
+ * Called when a dumpsys is being generated for this RcsFeatureController for all Features
+ * to report their status.
+ */
+ void dump(PrintWriter pw);
}
/**
* Used to inject FeatureConnector instances for testing.
*/
@VisibleForTesting
- public interface FeatureConnectorFactory<T extends IFeatureConnector> {
+ public interface FeatureConnectorFactory<U extends FeatureUpdates> {
/**
- * @return a {@link FeatureConnector} associated for the given {@link IFeatureConnector}
- * and slot id.
+ * @return a {@link FeatureConnector} associated for the given {@link FeatureUpdates}
+ * and slot index.
*/
- FeatureConnector<T> create(Context context, int slotId,
- FeatureConnector.Listener<T> listener, Executor executor, String tag);
+ FeatureConnector<U> create(Context context, int slotIndex,
+ FeatureConnector.Listener<U> listener, Executor executor, String logPrefix);
}
/**
@@ -102,7 +110,8 @@
ImsRegistrationCallbackHelper.ImsRegistrationUpdate cb, Executor executor);
}
- private FeatureConnectorFactory<RcsFeatureManager> mFeatureFactory = FeatureConnector::new;
+ private FeatureConnectorFactory<RcsFeatureManager> mFeatureFactory =
+ RcsFeatureManager::getConnector;
private RegistrationHelperFactory mRegistrationHelperFactory =
ImsRegistrationCallbackHelper::new;
@@ -113,35 +122,37 @@
private final Object mLock = new Object();
private FeatureConnector<RcsFeatureManager> mFeatureConnector;
private RcsFeatureManager mFeatureManager;
+ private int mAssociatedSubId;
private FeatureConnector.Listener<RcsFeatureManager> mFeatureConnectorListener =
new FeatureConnector.Listener<RcsFeatureManager>() {
@Override
- public RcsFeatureManager getFeatureManager() {
- return new RcsFeatureManager(mContext, mSlotId);
- }
-
- @Override
public void connectionReady(RcsFeatureManager manager)
throws com.android.ims.ImsException {
if (manager == null) {
logw("connectionReady returned null RcsFeatureManager");
return;
}
+ logd("connectionReady");
try {
// May throw ImsException if for some reason the connection to the
// ImsService is gone.
+ updateConnectionStatus(manager);
setupConnectionToService(manager);
} catch (ImsException e) {
+ updateConnectionStatus(null /*manager*/);
// Use deprecated Exception for compatibility.
throw new com.android.ims.ImsException(e.getMessage(),
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
- updateConnectionStatus(manager);
}
@Override
- public void connectionUnavailable() {
+ public void connectionUnavailable(int reason) {
+ if (reason == FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE) {
+ loge("unexpected - connectionUnavailable due to server unavailable");
+ }
+ logd("connectionUnavailable");
// Call before disabling connection to manager.
removeConnectionToService();
updateConnectionStatus(null /*manager*/);
@@ -167,9 +178,10 @@
}
};
- public RcsFeatureController(Context context, int slotId) {
+ public RcsFeatureController(Context context, int slotId, int associatedSubId) {
mContext = context;
mSlotId = slotId;
+ mAssociatedSubId = associatedSubId;
mImsRcsRegistrationHelper = mRegistrationHelperFactory.create(mRcsRegistrationUpdate,
mContext.getMainExecutor());
}
@@ -178,9 +190,11 @@
* Should only be used to inject registration helpers for testing.
*/
@VisibleForTesting
- public RcsFeatureController(Context context, int slotId, RegistrationHelperFactory f) {
+ public RcsFeatureController(Context context, int slotId, int associatedSubId,
+ RegistrationHelperFactory f) {
mContext = context;
mSlotId = slotId;
+ mAssociatedSubId = associatedSubId;
mRegistrationHelperFactory = f;
mImsRcsRegistrationHelper = mRegistrationHelperFactory.create(mRcsRegistrationUpdate,
mContext.getMainExecutor());
@@ -244,17 +258,12 @@
}
/**
- * Update the subscription associated with this controller.
+ * Update the Features associated with this controller due to the associated subscription
+ * changing.
*/
public void updateAssociatedSubscription(int newSubId) {
- RcsFeatureManager manager = getFeatureManager();
- if (manager != null) {
- try {
- manager.updateCapabilities();
- } catch (ImsException e) {
- Log.w(LOG_TAG, "associatedSubscriptionChanged failed:" + e);
- }
- }
+ mAssociatedSubId = newSubId;
+ updateCapabilities();
synchronized (mLock) {
for (Feature c : mFeatures.values()) {
c.onAssociatedSubscriptionUpdated(newSubId);
@@ -263,6 +272,19 @@
}
/**
+ * Update the features associated with this controller due to the carrier configuration
+ * changing.
+ */
+ public void onCarrierConfigChangedForSubscription() {
+ updateCapabilities();
+ synchronized (mLock) {
+ for (Feature c : mFeatures.values()) {
+ c.onCarrierConfigChanged();
+ }
+ }
+ }
+
+ /**
* Call before this controller is destroyed to tear down associated features.
*/
public void destroy() {
@@ -280,7 +302,7 @@
}
@VisibleForTesting
- public void setFeatureConnectorFactory(FeatureConnectorFactory factory) {
+ public void setFeatureConnectorFactory(FeatureConnectorFactory<RcsFeatureManager> factory) {
mFeatureFactory = factory;
}
@@ -310,8 +332,8 @@
}
/**
- * Register an {@link ImsRcsManager.AvailabilityCallback} with the associated RcsFeature,
- * which will provide availability updates.
+ * Register an {@link ImsRcsManager.OnAvailabilityChangedListener} with the associated
+ * RcsFeature, which will provide availability updates.
*/
public void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback)
throws ImsException {
@@ -324,7 +346,7 @@
}
/**
- * Remove a registered {@link ImsRcsManager.AvailabilityCallback} from the RcsFeature.
+ * Remove a registered {@link ImsRcsManager.OnAvailabilityChangedListener} from the RcsFeature.
*/
public void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback) {
RcsFeatureManager manager = getFeatureManager();
@@ -349,13 +371,14 @@
/**
* Query the availability of an IMS RCS capability.
*/
- public boolean isAvailable(int capability) throws android.telephony.ims.ImsException {
+ public boolean isAvailable(int capability, int radioTech)
+ throws android.telephony.ims.ImsException {
RcsFeatureManager manager = getFeatureManager();
if (manager == null) {
throw new ImsException("Service is not available",
ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
- return manager.isAvailable(capability);
+ return manager.isAvailable(capability, radioTech);
}
/**
@@ -376,14 +399,27 @@
callback.accept(mImsRcsRegistrationHelper.getImsRegistrationState());
}
+ private void updateCapabilities() {
+ RcsFeatureManager manager = getFeatureManager();
+ if (manager != null) {
+ try {
+ manager.updateCapabilities(mAssociatedSubId);
+ } catch (ImsException e) {
+ Log.w(LOG_TAG, "updateCapabilities failed:" + e);
+ }
+ }
+ }
+
private void setupConnectionToService(RcsFeatureManager manager) throws ImsException {
+ logd("setupConnectionToService");
// Open persistent listener connection, sends RcsFeature#onFeatureReady.
manager.openConnection();
- manager.updateCapabilities();
+ manager.updateCapabilities(mAssociatedSubId);
manager.registerImsRegistrationCallback(mImsRcsRegistrationHelper.getCallbackBinder());
}
private void removeConnectionToService() {
+ logd("removeConnectionToService");
RcsFeatureManager manager = getFeatureManager();
if (manager != null) {
manager.unregisterImsRegistrationCallback(
@@ -427,13 +463,29 @@
pw.print("connected=");
synchronized (mLock) {
pw.println(mFeatureManager != null);
+ pw.println();
+ pw.println("RcsFeatureControllers:");
+ pw.increaseIndent();
+ for (Feature f : mFeatures.values()) {
+ f.dump(pw);
+ pw.println();
+ }
+ pw.decreaseIndent();
}
}
+ private void logd(String log) {
+ Log.d(LOG_TAG, getLogPrefix().append(log).toString());
+ }
+
private void logw(String log) {
Log.w(LOG_TAG, getLogPrefix().append(log).toString());
}
+ private void loge(String log) {
+ Log.e(LOG_TAG, getLogPrefix().append(log).toString());
+ }
+
private StringBuilder getLogPrefix() {
StringBuilder sb = new StringBuilder("[");
sb.append(mSlotId);
diff --git a/src/com/android/services/telephony/rcs/SipDelegateBinderConnection.java b/src/com/android/services/telephony/rcs/SipDelegateBinderConnection.java
new file mode 100644
index 0000000..168a432
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/SipDelegateBinderConnection.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2020 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.services.telephony.rcs;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.telephony.ims.DelegateRegistrationState;
+import android.telephony.ims.DelegateRequest;
+import android.telephony.ims.FeatureTagState;
+import android.telephony.ims.SipDelegateConfiguration;
+import android.telephony.ims.SipDelegateImsConfiguration;
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.aidl.IImsRegistration;
+import android.telephony.ims.aidl.ISipDelegate;
+import android.telephony.ims.aidl.ISipDelegateMessageCallback;
+import android.telephony.ims.aidl.ISipDelegateStateCallback;
+import android.telephony.ims.aidl.ISipTransport;
+import android.telephony.ims.stub.SipDelegate;
+import android.util.LocalLog;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/**
+ * Container for the active connection to the {@link SipDelegate} active on the ImsService.
+ * <p>
+ * New instances of this class will be created and destroyed new {@link SipDelegate}s are created
+ * and destroyed by the {@link SipDelegateController}.
+ */
+public class SipDelegateBinderConnection implements DelegateBinderStateManager,
+ IBinder.DeathRecipient {
+ private static final String LOG_TAG = "BinderConn";
+
+ protected final int mSubId;
+ protected final Set<FeatureTagState> mDeniedTags;
+ protected final Executor mExecutor;
+ protected final List<StateCallback> mStateCallbacks;
+
+ private final LocalLog mLocalLog = new LocalLog(SipTransportController.LOG_SIZE);
+
+ // Callback interface from ImsService to this Connection. State Events will be forwarded to IMS
+ // application through DelegateStateTracker.
+ private final ISipDelegateStateCallback mSipDelegateStateCallback =
+ new ISipDelegateStateCallback.Stub() {
+ @Override
+ public void onCreated(ISipDelegate delegate,
+ List<FeatureTagState> deniedFeatureTags) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() ->
+ notifySipDelegateCreated(delegate, deniedFeatureTags));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void onFeatureTagRegistrationChanged(
+ DelegateRegistrationState registrationState) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ logi("onFeatureTagRegistrationChanged:" + registrationState);
+ for (StateCallback c : mStateCallbacks) {
+ c.onRegistrationStateChanged(registrationState);
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void onImsConfigurationChanged(
+ SipDelegateImsConfiguration registeredSipConfig) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ logi("onImsConfigurationChanged: version="
+ + registeredSipConfig.getVersion());
+ for (StateCallback c : mStateCallbacks) {
+ c.onImsConfigurationChanged(registeredSipConfig);
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(
+ SipDelegateConfiguration registeredSipConfig) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ logi("onConfigurationChanged");
+ for (StateCallback c : mStateCallbacks) {
+ c.onConfigurationChanged(registeredSipConfig);
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void onDestroyed(int reason) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> notifySipDelegateDestroyed(reason));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
+
+ private final ISipTransport mSipTransport;
+ private final IImsRegistration mImsRegistration;
+ private final DelegateRequest mRequestedConfig;
+
+ private ISipDelegate mDelegateBinder;
+ private BiConsumer<ISipDelegate, Set<FeatureTagState>> mPendingCreatedConsumer;
+ private Consumer<Integer> mPendingDestroyedConsumer;
+
+ /**
+ * Create a new Connection object to manage the creation and destruction of a
+ * {@link SipDelegate}.
+ * @param subId The subid that this SipDelegate is being created for.
+ * @param sipTransport The SipTransport implementation that will be used to manage SipDelegates.
+ * @param registrationImpl The ImsRegistration implementation that will be used to manage
+ * registration changes in relation to the SipDelegates.
+ * @param requestedConfig The DelegateRequest to be sent to the ImsService.
+ * @param transportDeniedTags The feature tags that have already been denied by the
+ * SipTransportController and should not be requested.
+ * @param executor The Executor that all binder calls from the remote process will be executed
+ * on.
+ * @param stateCallbacks A list of callbacks that will each be called when the state of the
+ * SipDelegate changes. This will be called on the supplied executor.
+ */
+ public SipDelegateBinderConnection(int subId, ISipTransport sipTransport,
+ IImsRegistration registrationImpl, DelegateRequest requestedConfig,
+ Set<FeatureTagState> transportDeniedTags, Executor executor,
+ List<StateCallback> stateCallbacks) {
+ mSubId = subId;
+ mSipTransport = sipTransport;
+ mImsRegistration = registrationImpl;
+ mRequestedConfig = requestedConfig;
+ mDeniedTags = transportDeniedTags;
+ mExecutor = executor;
+ mStateCallbacks = stateCallbacks;
+ }
+
+ @Override
+ public boolean create(ISipDelegateMessageCallback cb,
+ BiConsumer<ISipDelegate, Set<FeatureTagState>> createdConsumer) {
+ try {
+ mSipTransport.createSipDelegate(mSubId, mRequestedConfig, mSipDelegateStateCallback,
+ cb);
+ mSipTransport.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ logw("create called on unreachable SipTransport:" + e);
+ return false;
+ }
+ mPendingCreatedConsumer = createdConsumer;
+ return true;
+ }
+
+ @Override
+ public void destroy(int reason, Consumer<Integer> destroyedConsumer) {
+ mPendingDestroyedConsumer = destroyedConsumer;
+ try {
+ if (mDelegateBinder != null) {
+ mSipTransport.destroySipDelegate(mDelegateBinder, reason);
+ } else {
+ mExecutor.execute(() -> notifySipDelegateDestroyed(reason));
+ }
+ mStateCallbacks.clear();
+ } catch (RemoteException e) {
+ logw("destroy called on unreachable SipTransport:" + e);
+ mExecutor.execute(() -> notifySipDelegateDestroyed(reason));
+ }
+ try {
+ mSipTransport.asBinder().unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ logw("unlinkToDeath called on already unlinked binder" + e);
+ }
+ }
+
+ @Override
+ public void triggerFullNetworkRegistration(int sipCode, String sipReason) {
+ try {
+ mImsRegistration.triggerFullNetworkRegistration(sipCode, sipReason);
+ } catch (RemoteException e) {
+ logw("triggerFullNetworkRegistration called on unreachable ImsRegistration:" + e);
+ }
+ }
+
+ private void notifySipDelegateCreated(ISipDelegate delegate,
+ List<FeatureTagState> deniedFeatureTags) {
+ logi("Delegate Created: " + delegate + ", deniedTags:" + deniedFeatureTags);
+ if (delegate == null) {
+ logw("Invalid null delegate returned!");
+ }
+ mDelegateBinder = delegate;
+ // Add denied feature tags from SipDelegate to the ones denied by the transport
+ if (deniedFeatureTags != null) {
+ mDeniedTags.addAll(deniedFeatureTags);
+ }
+ if (mPendingCreatedConsumer == null) return;
+ mPendingCreatedConsumer.accept(delegate, mDeniedTags);
+ mPendingCreatedConsumer = null;
+ }
+
+ private void notifySipDelegateDestroyed(int reason) {
+ logi("Delegate Destroyed, reason: " + reason);
+ mDelegateBinder = null;
+ if (mPendingDestroyedConsumer == null) return;
+ mPendingDestroyedConsumer.accept(reason);
+ mPendingDestroyedConsumer = null;
+ }
+
+ /** Dump state about this binder connection that should be included in the dumpsys. */
+ public void dump(PrintWriter printWriter) {
+ mLocalLog.dump(printWriter);
+ }
+
+ protected final void logi(String log) {
+ Log.i(SipTransportController.LOG_TAG, LOG_TAG + "[" + mSubId + "] " + log);
+ mLocalLog.log("[I] " + log);
+ }
+
+ protected final void logw(String log) {
+ Log.w(SipTransportController.LOG_TAG, LOG_TAG + "[" + mSubId + "] " + log);
+ mLocalLog.log("[W] " + log);
+ }
+
+ @Override
+ public void binderDied() {
+ mExecutor.execute(() -> {
+ logw("binderDied!");
+ // Unblock any pending create/destroy operations.
+ // SipTransportController will handle the overall destruction/teardown.
+ notifySipDelegateCreated(null, Collections.emptyList());
+ notifySipDelegateDestroyed(SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD);
+ });
+ }
+}
diff --git a/src/com/android/services/telephony/rcs/SipDelegateBinderConnectionStub.java b/src/com/android/services/telephony/rcs/SipDelegateBinderConnectionStub.java
new file mode 100644
index 0000000..ef12eb8
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/SipDelegateBinderConnectionStub.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 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.services.telephony.rcs;
+
+import android.telephony.ims.DelegateRegistrationState;
+import android.telephony.ims.FeatureTagState;
+import android.telephony.ims.aidl.ISipDelegate;
+import android.telephony.ims.aidl.ISipDelegateMessageCallback;
+import android.telephony.ims.stub.SipDelegate;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/**
+ * Stub implementation used when a SipDelegate needs to be set up in specific cases, but there
+ * is no underlying implementation in the ImsService.
+ *
+ * This is used in cases where all of the requested feature tags were denied for various reasons
+ * from the SipTransportController. In this case, we will "connect", send a update to include the
+ * denied feature tags, and then do nothing until this stub is torn down.
+ */
+public class SipDelegateBinderConnectionStub implements DelegateBinderStateManager {
+ protected final Set<FeatureTagState> mDeniedTags;
+ protected final Executor mExecutor;
+ protected final List<StateCallback> mStateCallbacks;
+
+ /**
+ * Create a new Connection object to manage the creation and destruction of a
+ * {@link SipDelegate}.
+ * @param transportDeniedTags The feature tags that have already been denied by the
+ * SipTransportController and should not be requested.
+ * @param executor The Executor that all binder calls from the remote process will be executed
+ * on.
+ * @param stateCallbacks A list of callbacks that will each be called when the state of the
+ * SipDelegate changes. This will be called on the supplied executor.
+ */
+ public SipDelegateBinderConnectionStub(Set<FeatureTagState> transportDeniedTags,
+ Executor executor, List<StateCallback> stateCallbacks) {
+ mDeniedTags = transportDeniedTags;
+ mExecutor = executor;
+ mStateCallbacks = stateCallbacks;
+ }
+
+ @Override
+ public boolean create(ISipDelegateMessageCallback cb,
+ BiConsumer<ISipDelegate, Set<FeatureTagState>> createdConsumer) {
+ mExecutor.execute(() -> {
+ createdConsumer.accept(null, (mDeniedTags));
+ for (SipDelegateBinderConnection.StateCallback c: mStateCallbacks) {
+ c.onRegistrationStateChanged(new DelegateRegistrationState.Builder().build());
+ }
+ });
+ return true;
+ }
+
+ @Override
+ public void destroy(int reason, Consumer<Integer> destroyedConsumer) {
+ mExecutor.execute(() -> {
+ mStateCallbacks.clear();
+ destroyedConsumer.accept(reason);
+ });
+ }
+
+ @Override
+ public void triggerFullNetworkRegistration(int sipCode, String sipReason) {
+ // This stub is not connected to an ImsService, so this method is intentionally not
+ // implemented.
+ }
+}
diff --git a/src/com/android/services/telephony/rcs/SipDelegateController.java b/src/com/android/services/telephony/rcs/SipDelegateController.java
new file mode 100644
index 0000000..8cc70a4
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/SipDelegateController.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) 2020 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.services.telephony.rcs;
+
+import android.telephony.ims.DelegateRegistrationState;
+import android.telephony.ims.DelegateRequest;
+import android.telephony.ims.FeatureTagState;
+import android.telephony.ims.SipDelegateConnection;
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.aidl.IImsRegistration;
+import android.telephony.ims.aidl.ISipDelegate;
+import android.telephony.ims.aidl.ISipDelegateConnectionStateCallback;
+import android.telephony.ims.aidl.ISipDelegateMessageCallback;
+import android.telephony.ims.aidl.ISipTransport;
+import android.telephony.ims.stub.DelegateConnectionStateCallback;
+import android.telephony.ims.stub.SipDelegate;
+import android.util.ArraySet;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.stream.Collectors;
+
+/**
+ * Created when an IMS application wishes to open up a {@link SipDelegateConnection} and manages the
+ * resulting {@link SipDelegate} that may be created on the ImsService side.
+ */
+public class SipDelegateController {
+ static final String LOG_TAG = "SipDelegateC";
+
+ private class BinderConnectionFactory implements DelegateBinderStateManager.Factory {
+
+ private final ISipTransport mSipTransportImpl;
+ private final IImsRegistration mImsRegistrationImpl;
+
+ BinderConnectionFactory(ISipTransport transport, IImsRegistration registration) {
+ mSipTransportImpl = transport;
+ mImsRegistrationImpl = registration;
+ }
+
+ @Override
+ public DelegateBinderStateManager create(int subId,
+ DelegateRequest requestedConfig, Set<FeatureTagState> transportDeniedTags,
+ Executor executor, List<DelegateBinderStateManager.StateCallback> stateCallbacks) {
+ // We should not actually create a SipDelegate in this case.
+ if (requestedConfig.getFeatureTags().isEmpty()) {
+ return new SipDelegateBinderConnectionStub(transportDeniedTags, executor,
+ stateCallbacks);
+ }
+ return new SipDelegateBinderConnection(mSubId, mSipTransportImpl, mImsRegistrationImpl,
+ requestedConfig, transportDeniedTags, mExecutorService, stateCallbacks);
+ }
+ }
+
+ private final int mSubId;
+ private final String mPackageName;
+ private final DelegateRequest mInitialRequest;
+ private final ScheduledExecutorService mExecutorService;
+ private final MessageTransportWrapper mMessageTransportWrapper;
+ private final DelegateStateTracker mDelegateStateTracker;
+ private final DelegateBinderStateManager.Factory mBinderConnectionFactory;
+ private final LocalLog mLocalLog = new LocalLog(SipTransportController.LOG_SIZE);
+
+ private DelegateBinderStateManager mBinderConnection;
+ private Set<String> mTrackedFeatureTags;
+
+ public SipDelegateController(int subId, DelegateRequest initialRequest, String packageName,
+ ISipTransport transportImpl, IImsRegistration registrationImpl,
+ ScheduledExecutorService executorService,
+ ISipDelegateConnectionStateCallback stateCallback,
+ ISipDelegateMessageCallback messageCallback) {
+ mSubId = subId;
+ mPackageName = packageName;
+ mInitialRequest = initialRequest;
+ mExecutorService = executorService;
+ mBinderConnectionFactory = new BinderConnectionFactory(transportImpl, registrationImpl);
+
+ mMessageTransportWrapper = new MessageTransportWrapper(mSubId, executorService,
+ messageCallback);
+ mDelegateStateTracker = new DelegateStateTracker(mSubId, stateCallback,
+ mMessageTransportWrapper.getDelegateConnection());
+ }
+
+ /**
+ * Inject dependencies for testing only.
+ */
+ @VisibleForTesting
+ public SipDelegateController(int subId, DelegateRequest initialRequest, String packageName,
+ ScheduledExecutorService executorService,
+ MessageTransportWrapper messageTransportWrapper,
+ DelegateStateTracker delegateStateTracker,
+ DelegateBinderStateManager.Factory connectionFactory) {
+ mSubId = subId;
+ mInitialRequest = initialRequest;
+ mPackageName = packageName;
+ mExecutorService = executorService;
+ mMessageTransportWrapper = messageTransportWrapper;
+ mDelegateStateTracker = delegateStateTracker;
+ mBinderConnectionFactory = connectionFactory;
+ }
+
+ /**
+ * @return The InitialRequest from the IMS application. The feature tags that are actually set
+ * up may differ from this request based on the state of this controller.
+ */
+ public DelegateRequest getInitialRequest() {
+ return mInitialRequest;
+ }
+
+ /**
+ * @return The package name of the IMS application associated with this SipDelegateController.
+ */
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * @return The ImsService's SIP delegate binder impl associated with this controller.
+ */
+ public ISipDelegate getSipDelegateInterface() {
+ return mMessageTransportWrapper.getDelegateConnection();
+ }
+
+ /**
+ * @return The IMS app's message callback binder.
+ */
+ public ISipDelegateMessageCallback getAppMessageCallback() {
+ return mMessageTransportWrapper.getAppMessageCallback();
+ }
+
+ /**
+ * Create the underlying SipDelegate.
+ * <p>
+ * This may not happen instantly, The CompletableFuture returned will not complete until
+ * {@link DelegateConnectionStateCallback#onCreated(SipDelegateConnection)} is called by the
+ * SipDelegate or DelegateStateTracker state is updated in the case that all requested features
+ * were denied.
+ * @return A CompletableFuture that will complete once the SipDelegate has been created. If true
+ * is returned, the SipDelegate has been created successfully. If false, the ImsService is not
+ * reachable and the process should be aborted.
+ */
+ public CompletableFuture<Boolean> create(Set<String> supportedSet,
+ Set<FeatureTagState> deniedSet) {
+ logi("create, supported: " + supportedSet + ", denied: " + deniedSet);
+ mTrackedFeatureTags = supportedSet;
+ DelegateBinderStateManager connection =
+ createBinderConnection(supportedSet, deniedSet);
+ CompletableFuture<Pair<ISipDelegate, Set<FeatureTagState>>> pendingCreate =
+ createSipDelegate(connection);
+ // May need to implement special case handling where SipDelegate denies all in supportedSet,
+ // however that should be a very rare case. For now, if that happens, just keep the
+ // SipDelegate bound.
+ // use thenApply here because we need this to happen on the same thread that it was called
+ // on in order to ensure ordering of onCreated being called, followed by registration
+ // state changed. If not, this is subject to race conditions where registered is queued
+ // before the async processing of this future.
+ return pendingCreate.thenApply((resultPair) -> {
+ if (resultPair == null) {
+ logw("create: resultPair returned null");
+ return false;
+ }
+ mBinderConnection = connection;
+ logi("create: created, delegate denied: " + resultPair.second);
+ Set<String> allowedTags = new ArraySet<>(supportedSet);
+ // Start with the supported set and remove all tags that were denied.
+ allowedTags.removeAll(resultPair.second.stream().map(FeatureTagState::getFeatureTag)
+ .collect(Collectors.toSet()));
+ mMessageTransportWrapper.openTransport(resultPair.first, allowedTags,
+ resultPair.second);
+ mDelegateStateTracker.sipDelegateConnected(resultPair.second);
+ return true;
+ });
+ }
+
+ /**
+ * Modify the SipTransport to reflect the new Feature Tag set that the IMS application has
+ * access to.
+ * <p>
+ * This involves the following operations if the new supported tag set does not match the
+ * the existing set:
+ * 1) destroy the existing underlying SipDelegate. If there are SIP Dialogs that are active
+ * on the SipDelegate that is pending to be destroyed, we must move the feature tags into a
+ * deregistering state via
+ * {@link DelegateRegistrationState#DEREGISTERING_REASON_FEATURE_TAGS_CHANGING} to signal to the
+ * IMS application to close all dialogs before the operation can proceed. If any outgoing
+ * out-of-dialog messages are sent at this time, they will also fail with reason
+ * {@link SipDelegateManager#MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION}.
+ * 2) create a new underlying SipDelegate and notify trackers, allowing the transport to
+ * re-open.
+ * @param newSupportedSet The new supported set of feature tags that the SipDelegate should
+ * be opened for.
+ * @param deniedSet The new set of tags that have been denied as well as the reason for the
+ * denial to be reported back to the IMS Application.
+ * @return A CompletableFuture containing the pending operation that will change the supported
+ * feature tags. Any operations to change the supported feature tags of the associated
+ * SipDelegate after this should not happen until this pending operation completes. Will
+ * complete with {@code true} if the operation was successful or {@code false} if the
+ * IMS service was unreachable.
+ */
+ public CompletableFuture<Boolean> changeSupportedFeatureTags(Set<String> newSupportedSet,
+ Set<FeatureTagState> deniedSet) {
+ logi("Received feature tag set change, old: [" + mTrackedFeatureTags + "], new: "
+ + newSupportedSet + ",denied: [" + deniedSet + "]");
+ if (mTrackedFeatureTags != null && mTrackedFeatureTags.equals(newSupportedSet)) {
+ logi("changeSupportedFeatureTags: no change, returning");
+ return CompletableFuture.completedFuture(true);
+ }
+
+ mTrackedFeatureTags = newSupportedSet;
+ // Next perform the destroy operation.
+ CompletableFuture<Integer> pendingDestroy = destroySipDelegate(false/*force*/,
+ SipDelegateManager.MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION,
+ SipDelegateManager.MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION,
+ DelegateRegistrationState.DEREGISTERING_REASON_FEATURE_TAGS_CHANGING,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+
+ // Next perform the create operation with the new set of supported feature tags.
+ return pendingDestroy.thenComposeAsync((reasonFromService) -> {
+ logi("changeSupportedFeatureTags: destroy stage complete, reason reported: "
+ + reasonFromService);
+ return create(newSupportedSet, deniedSet);
+ }, mExecutorService);
+ }
+
+ /**
+ * Destroy this SipDelegate. This controller should be disposed of after this method is
+ * called.
+ * <p>
+ * This may not happen instantly if there are SIP Dialogs that are active on this SipDelegate.
+ * In this case, the CompletableFuture will not complete until
+ * {@link DelegateConnectionStateCallback#onDestroyed(int)} is called by the SipDelegate.
+ * @param force If set true, we will close the transport immediately and call
+ * {@link SipDelegate#closeDialog(String)} on any open dialogs. If false, we will wait for the
+ * SIP Dialogs to close or the close timer to timeout before destroying the underlying
+ * SipDelegate.
+ * @param destroyReason The reason for why this SipDelegate is being destroyed.
+ * @return A CompletableFuture that will complete once the SipDelegate has been destroyed.
+ */
+ public CompletableFuture<Integer> destroy(boolean force, int destroyReason) {
+ logi("destroy, forced " + force + ", destroyReason: " + destroyReason);
+
+ CompletableFuture<Integer> pendingOperationComplete =
+ destroySipDelegate(force, SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED,
+ getMessageFailReasonFromDestroyReason(destroyReason),
+ DelegateRegistrationState.DEREGISTERING_REASON_DESTROY_PENDING,
+ destroyReason);
+ return pendingOperationComplete.thenApplyAsync((reasonFromDelegate) -> {
+ logi("destroy, operation complete, notifying trackers, reason " + reasonFromDelegate);
+ mDelegateStateTracker.sipDelegateDestroyed(reasonFromDelegate);
+ return reasonFromDelegate;
+ }, mExecutorService);
+ };
+
+ /**
+ * The IMS application is notifying the ImsService that it has received a response to a request
+ * that will require that the IMS registration be torn down and brought back up.
+ *<p>
+ * See {@link SipDelegateManager#triggerFullNetworkRegistration} for more information.
+ */
+ public void triggerFullNetworkRegistration(int sipCode, String sipReason) {
+ logi("triggerFullNetworkRegistration, code=" + sipCode + ", reason=" + sipReason);
+ if (mBinderConnection != null) {
+ mBinderConnection.triggerFullNetworkRegistration(sipCode, sipReason);
+ } else {
+ logw("triggerFullNetworkRegistration called when binder connection is null");
+ }
+ }
+
+ private static int getMessageFailReasonFromDestroyReason(int destroyReason) {
+ switch (destroyReason) {
+ case SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD:
+ return SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD;
+ case SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP:
+ case SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_USER_DISABLED_RCS:
+ return SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED;
+ default:
+ return SipDelegateManager.MESSAGE_FAILURE_REASON_UNKNOWN;
+ }
+ }
+
+ /**
+ * @param force If set true, we will close the transport immediately and call
+ * {@link SipDelegate#closeDialog(String)} on any open dialogs. If false, we will wait for the
+ * SIP Dialogs to close or the close timer to timeout before destroying the underlying
+ * SipDelegate.
+ * @param messageDestroyingReason The reason to send back to the IMS application in the case
+ * that a new outgoing SIP message is sent that is out-of-dialog while the message
+ * transport is closing.
+ * @param messageDestroyedReason The reason to send back to the IMS application in the case
+ * that a new outgoing SIP message is sent once the underlying transport is closed.
+ * @param deregisteringReason The deregistering state reported to the IMS application for all
+ * registered feature tags.
+ * @param delegateDestroyedReason The reason to send to the underlying SipDelegate that is being
+ * destroyed.
+ * @return A CompletableFuture containing the reason from the SipDelegate for why it was
+ * destroyed.
+ */
+ private CompletableFuture<Integer> destroySipDelegate(boolean force,
+ int messageDestroyingReason, int messageDestroyedReason, int deregisteringReason,
+ int delegateDestroyedReason) {
+ if (mBinderConnection == null) {
+ logi("destroySipDelegate, called when binder connection is already null");
+ return CompletableFuture.completedFuture(delegateDestroyedReason);
+ }
+ // First, bring down the message transport.
+ CompletableFuture<Boolean> pendingTransportClosed = new CompletableFuture<>();
+ if (force) {
+ logi("destroySipDelegate, forced");
+ mMessageTransportWrapper.close(messageDestroyedReason);
+ pendingTransportClosed.complete(true);
+ } else {
+ mMessageTransportWrapper.closeGracefully(messageDestroyingReason,
+ messageDestroyedReason, pendingTransportClosed::complete);
+ }
+
+ // Do not send an intermediate pending state to app if there are no open SIP dialogs to
+ // worry about.
+ if (!pendingTransportClosed.isDone()) {
+ mDelegateStateTracker.sipDelegateChanging(deregisteringReason);
+ } else {
+ logi("destroySipDelegate, skip DEREGISTERING_REASON_DESTROY_PENDING");
+ }
+
+ // Next, destroy the SipDelegate.
+ return pendingTransportClosed.thenComposeAsync((wasGraceful) -> {
+ logi("destroySipDelegate, transport gracefully closed = " + wasGraceful);
+ CompletableFuture<Integer> pendingDestroy = new CompletableFuture<>();
+ mBinderConnection.destroy(delegateDestroyedReason, pendingDestroy::complete);
+ return pendingDestroy;
+ }, mExecutorService);
+ }
+
+ /**
+ * @return a CompletableFuture that returns a Pair containing SipDelegate Binder interface as
+ * well as rejected feature tags or a {@code null} Pair instance if the ImsService is not
+ * available.
+ */
+ private CompletableFuture<Pair<ISipDelegate, Set<FeatureTagState>>> createSipDelegate(
+ DelegateBinderStateManager connection) {
+ CompletableFuture<Pair<ISipDelegate, Set<FeatureTagState>>> createdFuture =
+ new CompletableFuture<>();
+ boolean isStarted = connection.create(mMessageTransportWrapper.getMessageCallback(),
+ (delegate, delegateDeniedTags) ->
+ createdFuture.complete(new Pair<>(delegate, delegateDeniedTags)));
+ if (!isStarted) {
+ logw("Couldn't create binder connection, ImsService is not available.");
+ connection.destroy(SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD, null);
+ return CompletableFuture.completedFuture(null);
+ }
+ return createdFuture;
+ }
+
+ private DelegateBinderStateManager createBinderConnection(Set<String> supportedSet,
+ Set<FeatureTagState> deniedSet) {
+
+ List<DelegateBinderStateManager.StateCallback> stateCallbacks = new ArrayList<>(2);
+ stateCallbacks.add(mDelegateStateTracker);
+ stateCallbacks.add(mMessageTransportWrapper);
+
+ return mBinderConnectionFactory.create(mSubId,
+ new DelegateRequest(supportedSet), deniedSet, mExecutorService, stateCallbacks);
+ }
+
+ /**
+ * Write the current state of this controller in String format using the PrintWriter provided
+ * for dumpsys.
+ */
+ public void dump(PrintWriter printWriter) {
+ IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
+ pw.println("SipDelegateController" + "[" + mSubId + "]:");
+ pw.increaseIndent();
+ pw.println("Most recent logs:");
+ pw.increaseIndent();
+ mLocalLog.dump(pw);
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("DelegateStateTracker:");
+ pw.increaseIndent();
+ mDelegateStateTracker.dump(pw);
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("MessageStateTracker:");
+ pw.increaseIndent();
+ mMessageTransportWrapper.dump(pw);
+ pw.decreaseIndent();
+
+ pw.decreaseIndent();
+ }
+
+ private void logi(String log) {
+ Log.i(SipTransportController.LOG_TAG, LOG_TAG + "[" + mSubId + "] " + log);
+ mLocalLog.log("[I] " + log);
+ }
+
+ private void logw(String log) {
+ Log.w(SipTransportController.LOG_TAG, LOG_TAG + "[" + mSubId + "] " + log);
+ mLocalLog.log("[W] " + log);
+ }
+}
diff --git a/src/com/android/services/telephony/rcs/SipDialog.java b/src/com/android/services/telephony/rcs/SipDialog.java
new file mode 100644
index 0000000..508d515
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/SipDialog.java
@@ -0,0 +1,254 @@
+/*
+ * 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.services.telephony.rcs;
+
+import android.telephony.ims.SipMessage;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.SipMessageParsingUtils;
+
+import java.time.Instant;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Track the state of a SIP Dialog.
+ * <p>
+ * SIP Dialogs follow the following initialization flow:
+ * <pre>
+ * (INVITE) ---> EARLY -(2XX)-> CONFIRMED -(BYE)-----> CLOSED
+ * ^ | \ ^
+ * |--(1XX) -(3XX-7XX) ----------------|
+ * </pre>
+ * <p> A special note on forking INVITE requests:
+ * During the EARLY phase, a 1XX or 2XX response can carry a To header tag param. This To header
+ * tag param will be set once a INVITE reaches the remote and responds. If the proxy has multiple
+ * endpoints available for the same contact, the INVITE may fork and multiple responses may be
+ * received for the same INVITE request, each with a different To header tag parameter (but the
+ * same call-id). This will generate another SIP dialog within the same SIP session.
+ */
+public class SipDialog {
+
+ /**
+ * The device has sent out a dialog starting event and is awaiting a confirmation.
+ */
+ public static final int STATE_EARLY = 0;
+
+ /**
+ * The device has received a 2XX response to the early dialog.
+ */
+ public static final int STATE_CONFIRMED = 1;
+
+ /**
+ * The device has received either a 3XX+ response to a pending dialog request or a BYE
+ * request has been sent on this dialog.
+ */
+ public static final int STATE_CLOSED = 2;
+
+ private final String mBranchId;
+ private final String mCallId;
+ private final String mFromTag;
+ private final Set<String> mAcceptContactFeatureTags;
+ private String mToTag;
+ private int mState = STATE_EARLY;
+ private Instant mLastInteraction;
+
+ /**
+ * @return A SipDialog instance representing the SIP request.
+ */
+ public static SipDialog fromSipMessage(SipMessage m) {
+ if (!SipMessageParsingUtils.isSipRequest(m.getStartLine())) return null;
+ String fromTag = SipMessageParsingUtils.getFromTag(m.getHeaderSection());
+ Set<String> acceptContactTags = SipMessageParsingUtils.getAcceptContactFeatureTags(
+ m.getHeaderSection());
+ return new SipDialog(m.getViaBranchParameter(), m.getCallIdParameter(), fromTag,
+ acceptContactTags);
+ }
+
+ /**
+ * Track a new SIP dialog, which will be starting in {@link #STATE_EARLY}.
+ *
+ * @param branchId The via branch param of the INVITE request, which is used to match
+ * responses.
+ * @param callId The callId of the SIP dialog.
+ * @param fromTag The from header's tag parameter.
+ */
+ private SipDialog(String branchId, String callId, String fromTag, Set<String> featureTags) {
+ mBranchId = branchId;
+ mCallId = callId;
+ mFromTag = fromTag;
+ mAcceptContactFeatureTags = featureTags;
+ mLastInteraction = Instant.now();
+ }
+
+ /**
+ * @return The call id associated with the SIP dialog.
+ */
+ public String getCallId() {
+ return mCallId;
+ }
+
+ /**
+ * @return the state of the SIP dialog, either {@link #STATE_EARLY},
+ * {@link #STATE_CONFIRMED}, {@link #STATE_CLOSED}.
+ */
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * @return The to header's tag parameter if this dialog has gotten a response from the remote
+ * party or {@code null} if it has not.
+ */
+ public String getToTag() {
+ return mToTag;
+ }
+
+ /**
+ * @return The feature tags contained in the "Accept-Contact" header.
+ */
+ public Set<String> getAcceptContactFeatureTags() {
+ return mAcceptContactFeatureTags;
+ }
+
+ /**
+ * @return A new instance with branch param, call-id value, and from tag param populated.
+ */
+ public SipDialog forkDialog() {
+ return new SipDialog(mBranchId, mCallId, mFromTag, mAcceptContactFeatureTags);
+ }
+
+ /**
+ * A early response has been received (101-199) and contains a to tag, which will create a
+ * dialog.
+ * @param toTag The to tag of the SIP response.
+ */
+ public void earlyResponse(String toTag) {
+ if (TextUtils.isEmpty(toTag) || mState != STATE_EARLY) {
+ // invalid state
+ return;
+ }
+ mLastInteraction = Instant.now();
+ // Only accept To tag if one has not been assigned yet.
+ if (mToTag == null) {
+ mToTag = toTag;
+ }
+ }
+
+ /**
+ * The early dialog has been confirmed and
+ * @param toTag The To header's tag parameter.
+ */
+ public void confirm(String toTag) {
+ if (mState != STATE_EARLY) {
+ // Invalid state
+ return;
+ }
+ mLastInteraction = Instant.now();
+ mState = STATE_CONFIRMED;
+ // Only accept a To tag if one has not been assigned yet.
+ if (mToTag == null) {
+ mToTag = toTag;
+ }
+ }
+
+ /**
+ * Close the SIP dialog
+ */
+ public void close() {
+ mLastInteraction = Instant.now();
+ mState = STATE_CLOSED;
+ }
+
+ /**
+ * @return {@code true} if a SIP response's branch, call-id, and from tags match this dialog,
+ * {@code false} if it does not. This may match multiple Dialogs in the case of SIP INVITE
+ * forking.
+ */
+ public boolean isResponseAssociatedWithDialog(SipMessage m) {
+ if (!mBranchId.equals(m.getViaBranchParameter())) return false;
+ if (!mCallId.equals(m.getCallIdParameter())) return false;
+ String fromTag = SipMessageParsingUtils.getFromTag(m.getHeaderSection());
+ return mFromTag.equals(fromTag);
+ }
+
+ /**
+ * @return {@code true} if the SIP request is part of the SIP Dialog, {@code false} if it is
+ * not.
+ */
+ public boolean isRequestAssociatedWithDialog(SipMessage m) {
+ if (!mCallId.equals(m.getCallIdParameter())) return false;
+ String fromTag = SipMessageParsingUtils.getFromTag(m.getHeaderSection());
+ String toTag = SipMessageParsingUtils.getToTag(m.getHeaderSection());
+ // Requests can only be associated if both to and from tag of message are populated. The
+ // dialog's to tag must also be non-null meaning we got a response from the remote.
+ if (fromTag == null || toTag == null || mToTag == null) return false;
+ // For outgoing requests, recorded from tag will match their from tag and for incoming
+ // requests recorded from tag will match their to tag. Same with our recorded to tag.
+ return (mFromTag.equals(fromTag) || mFromTag.equals(toTag))
+ && (mToTag.equals(toTag) || mToTag.equals(fromTag));
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder("SipDialog[");
+ switch (mState) {
+ case STATE_EARLY:
+ b.append("early");
+ break;
+ case STATE_CONFIRMED:
+ b.append("confirmed");
+ break;
+ case STATE_CLOSED:
+ b.append("closed");
+ break;
+ default:
+ b.append(mState);
+ }
+ b.append("] bId=");
+ b.append(mBranchId);
+ b.append(", cId=");
+ b.append(mCallId);
+ b.append(", f=");
+ b.append(mFromTag);
+ b.append(", t=");
+ b.append(mToTag);
+ b.append(", Last Interaction: ");
+ b.append(mLastInteraction);
+ return b.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SipDialog sipDialog = (SipDialog) o;
+ // Does not include mState and last interaction time, as a dialog is only the same if
+ // its branch, callId, and to/from tags are equal.
+ return mBranchId.equals(sipDialog.mBranchId)
+ && Objects.equals(mCallId, sipDialog.mCallId)
+ && Objects.equals(mFromTag, sipDialog.mFromTag)
+ && Objects.equals(mToTag, sipDialog.mToTag);
+ }
+
+ @Override
+ public int hashCode() {
+ // Does not include mState and last interaction time, as a dialog is only the same if
+ // its branch, callId, and to/from tags are equal.
+ return Objects.hash(mBranchId, mCallId, mFromTag, mToTag);
+ }
+}
diff --git a/src/com/android/services/telephony/rcs/SipSessionTracker.java b/src/com/android/services/telephony/rcs/SipSessionTracker.java
new file mode 100644
index 0000000..5ab482f
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/SipSessionTracker.java
@@ -0,0 +1,368 @@
+/*
+ * 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.services.telephony.rcs;
+
+import android.telephony.ims.SipMessage;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.LocalLog;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.SipMessageParsingUtils;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Tracks the state of SIP sessions started by a SIP INVITE (see RFC 3261)
+ * <p>
+ * Each SIP session created will consist of one or more SIP with, each dialog in the session
+ * having the same call-ID. Each SIP dialog will be in one of three states: EARLY, CONFIRMED, and
+ * CLOSED.
+ * <p>
+ * The SIP session will be closed once all of the associated dialogs are closed.
+ */
+public class SipSessionTracker {
+ private static final String TAG = "SessionT";
+
+ /**
+ * SIP request methods that will start a new SIP Dialog and move it into the PENDING state
+ * while we wait for a response. Note: INVITE is not the only SIP dialog that will create a
+ * dialog, however it is the only one that we wish to track for this use case.
+ */
+ public static final String[] SIP_REQUEST_DIALOG_START_METHODS = new String[] { "invite" };
+
+ /**
+ * The SIP request method that will close a SIP Dialog in the ACTIVE state with the same
+ * Call-Id.
+ */
+ private static final String SIP_CLOSE_DIALOG_REQUEST_METHOD = "bye";
+
+ private final LocalLog mLocalLog = new LocalLog(SipTransportController.LOG_SIZE);
+ private final ArrayList<SipDialog> mTrackedDialogs = new ArrayList<>();
+ // Operations that are pending an ack from the remote application processing the message before
+ // they can be applied here. Maps the via header branch parameter of the message to the
+ // associated pending operation.
+ private final ArrayMap<String, Runnable> mPendingAck = new ArrayMap<>();
+
+ /**
+ * Filter a SIP message to determine if it will result in a new SIP dialog. This will need to be
+ * successfully acknowledged by the remote IMS stack using
+ * {@link #acknowledgePendingMessage(String)} before we do any further processing.
+ *
+ * @param message The Incoming SIP message.
+ */
+ public void filterSipMessage(SipMessage message) {
+ final Runnable r;
+ if (startsEarlyDialog(message)) {
+ r = getCreateDialogRunnable(message);
+ } else if (closesDialog(message)) {
+ r = getCloseDialogRunnable(message);
+ } else if (SipMessageParsingUtils.isSipResponse(message.getStartLine())) {
+ r = getDialogStateChangeRunnable(message);
+ } else {
+ r = null;
+ }
+
+ if (r != null) {
+ if (mPendingAck.containsKey(message.getViaBranchParameter())) {
+ Runnable lastEvent = mPendingAck.get(message.getViaBranchParameter());
+ logw("Adding new message when there was already a pending event for branch: "
+ + message.getViaBranchParameter());
+ Runnable concatRunnable = () -> {
+ // No choice but to concatenate the Runnables together.
+ if (lastEvent != null) lastEvent.run();
+ r.run();
+ };
+ mPendingAck.put(message.getViaBranchParameter(), concatRunnable);
+ } else {
+ mPendingAck.put(message.getViaBranchParameter(), r);
+ }
+ }
+ }
+
+ /**
+ * The pending SIP message has been received by the remote IMS stack. We can now track dialogs
+ * associated with this message.
+ * message.
+ * @param viaBranchId The SIP message's Via header's branch parameter, which is used as a
+ * unique token.
+ */
+ public void acknowledgePendingMessage(String viaBranchId) {
+ Runnable r = mPendingAck.get(viaBranchId);
+ if (r != null) {
+ mPendingAck.remove(viaBranchId);
+ r.run();
+ }
+ }
+
+ /**
+ * The pending SIP message has failed to be sent to the remote so remove the pending task.
+ * @param viaBranchId The failed message's Via header's branch parameter.
+ */
+ public void pendingMessageFailed(String viaBranchId) {
+ mPendingAck.remove(viaBranchId);
+ }
+
+ /**
+ * A SIP session tracked by the remote application's IMS stack has been closed, so we can stop
+ * tracking it.
+ * @param callId The callId of the SIP session that has been closed.
+ */
+ public void cleanupSession(String callId) {
+ List<SipDialog> dialogsToCleanup = mTrackedDialogs.stream()
+ .filter(d -> d.getCallId().equals(callId))
+ .collect(Collectors.toList());
+ if (dialogsToCleanup.isEmpty()) return;
+ logi("Cleanup dialogs associated with call id: " + callId);
+ for (SipDialog d : dialogsToCleanup) {
+ d.close();
+ logi("Dialog closed: " + d);
+ }
+ mTrackedDialogs.removeAll(dialogsToCleanup);
+ }
+
+ /**
+ * @return the call IDs of the dialogs associated with the provided feature tags.
+ */
+ public Set<String> getCallIdsAssociatedWithFeatureTag(Set<String> featureTags) {
+ if (featureTags.isEmpty()) return Collections.emptySet();
+ Set<String> associatedIds = new ArraySet<>();
+ for (String featureTag : featureTags) {
+ for (SipDialog dialog : mTrackedDialogs) {
+ boolean isAssociated = dialog.getAcceptContactFeatureTags().stream().anyMatch(
+ d -> d.equalsIgnoreCase(featureTag));
+ if (isAssociated) associatedIds.add(dialog.getCallId());
+ }
+ }
+ return associatedIds;
+ }
+
+ /**
+ * @return All dialogs that have not received a final response yet 2XX or 3XX+.
+ */
+ public Set<SipDialog> getEarlyDialogs() {
+ return mTrackedDialogs.stream().filter(d -> d.getState() == SipDialog.STATE_EARLY)
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * @return All confirmed dialogs that have received a 2XX response and are active.
+ */
+ public Set<SipDialog> getConfirmedDialogs() {
+ return mTrackedDialogs.stream().filter(d -> d.getState() == SipDialog.STATE_CONFIRMED)
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * @return Dialogs that have been closed via a BYE or 3XX+ response and
+ * {@link #cleanupSession(String)} has not been called yet.
+ */
+ @VisibleForTesting
+ public Set<SipDialog> getClosedDialogs() {
+ return mTrackedDialogs.stream().filter(d -> d.getState() == SipDialog.STATE_CLOSED)
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * @return All of the tracked dialogs, even the ones that have been closed but
+ * {@link #cleanupSession(String)} has not been called.
+ */
+ public Set<SipDialog> getTrackedDialogs() {
+ return new ArraySet<>(mTrackedDialogs);
+ }
+
+ /**
+ * Clears all tracked sessions.
+ */
+ public void clearAllSessions() {
+ mTrackedDialogs.clear();
+ mPendingAck.clear();
+ }
+
+ /**
+ * Dump the state of this tracker to the provided PrintWriter.
+ */
+ public void dump(PrintWriter printWriter) {
+ IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
+ pw.println("SipSessionTracker:");
+ pw.increaseIndent();
+ pw.print("Early Call IDs: ");
+ pw.println(getEarlyDialogs().stream().map(SipDialog::getCallId)
+ .collect(Collectors.toSet()));
+ pw.print("Confirmed Call IDs: ");
+ pw.println(getConfirmedDialogs().stream().map(SipDialog::getCallId)
+ .collect(Collectors.toSet()));
+ pw.print("Closed Call IDs: ");
+ pw.println(getClosedDialogs().stream().map(SipDialog::getCallId)
+ .collect(Collectors.toSet()));
+ pw.println("Tracked Dialogs:");
+ pw.increaseIndent();
+ for (SipDialog d : mTrackedDialogs) {
+ pw.println(d);
+ }
+ pw.decreaseIndent();
+ pw.println();
+ pw.println("Local Logs");
+ mLocalLog.dump(pw);
+ pw.decreaseIndent();
+ }
+
+ /**
+ * @return {@code true}, if the SipMessage passed in should start a new SIP dialog,
+ * {@code false} if it should not.
+ */
+ private boolean startsEarlyDialog(SipMessage m) {
+ if (!SipMessageParsingUtils.isSipRequest(m.getStartLine())) {
+ return false;
+ }
+ String[] startLineSegments = SipMessageParsingUtils.splitStartLineAndVerify(
+ m.getStartLine());
+ if (startLineSegments == null) {
+ return false;
+ }
+ return Arrays.stream(SIP_REQUEST_DIALOG_START_METHODS)
+ .anyMatch(r -> r.equalsIgnoreCase(startLineSegments[0]));
+ }
+
+ /**
+ * @return {@code true}, if the SipMessage passed in should close a confirmed dialog,
+ * {@code false} if it should not.
+ */
+ private boolean closesDialog(SipMessage m) {
+ if (!SipMessageParsingUtils.isSipRequest(m.getStartLine())) {
+ return false;
+ }
+ String[] startLineSegments = SipMessageParsingUtils.splitStartLineAndVerify(
+ m.getStartLine());
+ if (startLineSegments == null) {
+ return false;
+ }
+ return SIP_CLOSE_DIALOG_REQUEST_METHOD.equalsIgnoreCase(startLineSegments[0]);
+ }
+
+ private Runnable getCreateDialogRunnable(SipMessage m) {
+ return () -> {
+ List<SipDialog> duplicateDialogs = mTrackedDialogs.stream()
+ .filter(d -> d.getCallId().equals(m.getCallIdParameter()))
+ .collect(Collectors.toList());
+ if (duplicateDialogs.size() > 0) {
+ logi("trying to create a dialog for a call ID that already exists, skip: "
+ + duplicateDialogs);
+ return;
+ }
+ SipDialog dialog = SipDialog.fromSipMessage(m);
+ logi("Starting new SipDialog: " + dialog);
+ mTrackedDialogs.add(dialog);
+ };
+ }
+
+ private Runnable getCloseDialogRunnable(SipMessage m) {
+ return () -> {
+ List<SipDialog> dialogsToClose = mTrackedDialogs.stream()
+ .filter(d -> d.isRequestAssociatedWithDialog(m))
+ .collect(Collectors.toList());
+ if (dialogsToClose.isEmpty()) return;
+ logi("Closing dialogs associated with: " + m);
+ for (SipDialog d : dialogsToClose) {
+ d.close();
+ logi("Dialog closed: " + d);
+ }
+ };
+ }
+
+ private Runnable getDialogStateChangeRunnable(SipMessage m) {
+ return () -> {
+ // This will return a dialog and all of its potential forks
+ List<SipDialog> associatedDialogs = mTrackedDialogs.stream()
+ .filter(d -> d.isResponseAssociatedWithDialog(m))
+ .collect(Collectors.toList());
+ if (associatedDialogs.isEmpty()) return;
+ String messageToTag = SipMessageParsingUtils.getToTag(m.getHeaderSection());
+ // If the to tag matches (or message to tag doesn't exist in dialog yet because this is
+ // the first response), then we are done.
+ SipDialog match = associatedDialogs.stream()
+ .filter(d -> d.getToTag() == null || d.getToTag().equals(messageToTag))
+ .findFirst().orElse(null);
+ if (match == null) {
+ // If it doesn't then we have a situation where we need to fork the existing dialog.
+ // The dialog used to fork doesn't matter, since the required params are the same,
+ // so simply use the first one in the returned list.
+ logi("Dialog forked");
+ match = associatedDialogs.get(0).forkDialog();
+ mTrackedDialogs.add(match);
+ }
+ if (match != null) {
+ logi("Dialog: " + match + " is associated with: " + m);
+ updateSipDialogState(match, m);
+ logi("Dialog state updated to " + match);
+ } else {
+ logi("No Dialogs are associated with: " + m);
+ }
+ };
+ }
+
+ private void updateSipDialogState(SipDialog d, SipMessage m) {
+ String[] startLineSegments = SipMessageParsingUtils.splitStartLineAndVerify(
+ m.getStartLine());
+ if (startLineSegments == null) {
+ logw("Could not parse start line for SIP message: " + m.getStartLine());
+ return;
+ }
+ int statusCode = 0;
+ try {
+ statusCode = Integer.parseInt(startLineSegments[1]);
+ } catch (NumberFormatException e) {
+ logw("Could not parse status code for SIP message: " + m.getStartLine());
+ return;
+ }
+ String toTag = SipMessageParsingUtils.getToTag(m.getHeaderSection());
+ logi("updateSipDialogState: message has statusCode: " + statusCode + ", and to tag: "
+ + toTag);
+ // If specifically 100 Trying, then do not do anything.
+ if (statusCode <= 100) return;
+ // If 300+, then this dialog has received an error response and should move to closed state.
+ if (statusCode >= 300) {
+ d.close();
+ return;
+ }
+ if (toTag == null) logw("updateSipDialogState: No to tag for message: " + m);
+ if (statusCode >= 200) {
+ d.confirm(toTag);
+ return;
+ }
+ // 1XX responses still require updates to dialogs.
+ d.earlyResponse(toTag);
+ }
+
+ private void logi(String log) {
+ Log.i(SipTransportController.LOG_TAG, TAG + ": " + log);
+ mLocalLog.log("[I] " + log);
+ }
+
+ private void logw(String log) {
+ Log.w(SipTransportController.LOG_TAG, TAG + ": " + log);
+ mLocalLog.log("[W] " + log);
+ }
+}
diff --git a/src/com/android/services/telephony/rcs/SipTransportController.java b/src/com/android/services/telephony/rcs/SipTransportController.java
new file mode 100644
index 0000000..709e142
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/SipTransportController.java
@@ -0,0 +1,1088 @@
+/*
+ * Copyright (C) 2020 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.services.telephony.rcs;
+
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.os.PersistableBundle;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.ims.DelegateRequest;
+import android.telephony.ims.FeatureTagState;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsService;
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.aidl.IImsRegistration;
+import android.telephony.ims.aidl.ISipDelegate;
+import android.telephony.ims.aidl.ISipDelegateConnectionStateCallback;
+import android.telephony.ims.aidl.ISipDelegateMessageCallback;
+import android.telephony.ims.aidl.ISipTransport;
+import android.telephony.ims.stub.DelegateConnectionMessageCallback;
+import android.telephony.ims.stub.DelegateConnectionStateCallback;
+import android.telephony.ims.stub.SipDelegate;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.LocalLog;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.ims.RcsFeatureManager;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.phone.RcsProvisioningMonitor;
+
+import com.google.common.base.Objects;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+/**
+ * Manages the creation and destruction of SipDelegates in response to an IMS application requesting
+ * a SipDelegateConnection registered to one or more IMS feature tags.
+ * <p>
+ * This allows an IMS application to forward traffic related to those feature tags over the existing
+ * IMS registration managed by the {@link ImsService} associated with this cellular subscription
+ * instead of requiring that the IMS application manage its own IMS registration over-the-top. This
+ * is required for some cellular carriers, which mandate that all IMS SIP traffic must be sent
+ * through a single IMS registration managed by the system IMS service.
+ *
+ * //TODO: Support other roles besides SMS
+ * //TODO: Bring in carrier provisioning to influence features that can be created.
+ * //TODO: Generate registration change events.
+ */
+public class SipTransportController implements RcsFeatureController.Feature,
+ OnRoleHoldersChangedListener {
+ public static final String LOG_TAG = "SipTransportC";
+ static final int LOG_SIZE = 50;
+
+ /**See {@link TimerAdapter#getReevaluateThrottleTimerMilliseconds()}.*/
+ private static final int REEVALUATE_THROTTLE_DEFAULT_MS = 1000;
+ /**See {@link TimerAdapter#getUpdateRegistrationDelayMilliseconds()}.*/
+ private static final int TRIGGER_UPDATE_REGISTRATION_DELAY_DEFAULT_MS = 1000;
+
+ /**
+ * {@link RoleManager} is final so we have to wrap the implementation for testing.
+ */
+ @VisibleForTesting
+ public interface RoleManagerAdapter {
+ /** See {@link RoleManager#getRoleHolders(String)} */
+ List<String> getRoleHolders(String roleName);
+ /** See {@link RoleManager#addOnRoleHoldersChangedListenerAsUser} */
+ void addOnRoleHoldersChangedListenerAsUser(Executor executor,
+ OnRoleHoldersChangedListener listener, UserHandle user);
+ /** See {@link RoleManager#removeOnRoleHoldersChangedListenerAsUser} */
+ void removeOnRoleHoldersChangedListenerAsUser(OnRoleHoldersChangedListener listener,
+ UserHandle user);
+ }
+
+ /**
+ * Adapter for timers related to this class so they can be modified during testing.
+ */
+ @VisibleForTesting
+ public interface TimerAdapter {
+ /**
+ * Time we will delay after a {@link #createSipDelegate} or {@link #destroySipDelegate}
+ * command to re-evaluate and apply any changes to the list of tracked
+ * SipDelegateControllers.
+ * <p>
+ * Another create/destroy request sent during this time will not postpone re-evaluation
+ * again.
+ */
+ int getReevaluateThrottleTimerMilliseconds();
+
+ /**
+ * Time after re-evaluate we will wait to trigger the update of IMS registration.
+ * <p>
+ * Another re-evaluate while waiting to trigger a registration update will cause this
+ * controller to cancel and reschedule the event again, further delaying the trigger to send
+ * a registration update.
+ */
+ int getUpdateRegistrationDelayMilliseconds();
+ }
+
+ private static class TimerAdapterImpl implements TimerAdapter {
+
+ @Override
+ public int getReevaluateThrottleTimerMilliseconds() {
+ return REEVALUATE_THROTTLE_DEFAULT_MS;
+ }
+
+ @Override
+ public int getUpdateRegistrationDelayMilliseconds() {
+ return TRIGGER_UPDATE_REGISTRATION_DELAY_DEFAULT_MS;
+ }
+ }
+
+ private static class RoleManagerAdapterImpl implements RoleManagerAdapter {
+
+ private final RoleManager mRoleManager;
+
+ private RoleManagerAdapterImpl(Context context) {
+ mRoleManager = context.getSystemService(RoleManager.class);
+ }
+
+ @Override
+ public List<String> getRoleHolders(String roleName) {
+ return mRoleManager.getRoleHolders(roleName);
+ }
+
+ @Override
+ public void addOnRoleHoldersChangedListenerAsUser(Executor executor,
+ OnRoleHoldersChangedListener listener, UserHandle user) {
+ mRoleManager.addOnRoleHoldersChangedListenerAsUser(executor, listener, user);
+ }
+
+ @Override
+ public void removeOnRoleHoldersChangedListenerAsUser(OnRoleHoldersChangedListener listener,
+ UserHandle user) {
+ mRoleManager.removeOnRoleHoldersChangedListenerAsUser(listener, user);
+ }
+ }
+
+ /**
+ * Extends RemoteCallbackList to track callbacks to the IMS applications with
+ * SipDelegateConnections and cleans them up if they die.
+ */
+ private class TrackedAppBinders extends RemoteCallbackList<ISipDelegateMessageCallback> {
+ @Override
+ public void onCallbackDied(ISipDelegateMessageCallback callback, Object cookie) {
+ mExecutorService.execute(() -> {
+ if (cookie instanceof SipDelegateController) {
+ SipDelegateController c = (SipDelegateController) cookie;
+ logi("onCallbackDied - Cleaning up delegates associated with package: "
+ + c.getPackageName());
+ boolean isNewDestroyQueued = addPendingDestroy(c,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD);
+ if (isNewDestroyQueued) {
+ CompletableFuture<Void> f = new CompletableFuture<>();
+ scheduleReevaluateNow(f);
+ f.thenRun(() -> logi("onCallbackDied - clean up complete for package: "
+ + c.getPackageName()));
+ }
+ } else {
+ logw("onCallbackDied: unexpected - cookie is not an instance of "
+ + "SipDelegateController");
+ }
+ });
+ }
+ }
+
+ /**
+ * Used in {@link #destroySipDelegate(int, ISipDelegate, int)} to store pending destroy
+ * requests.
+ */
+ private static final class DestroyRequest {
+ public final SipDelegateController controller;
+ public final int reason;
+
+ DestroyRequest(SipDelegateController c, int r) {
+ controller = c;
+ reason = r;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ DestroyRequest that = (DestroyRequest) o;
+ // Only use controller for comparison, as we want to only have one DestroyRequest active
+ // per controller.
+ return controller.equals(that.controller);
+ }
+
+ @Override
+ public int hashCode() {
+ // Only use controller for hash, as we want to only have one DestroyRequest active per
+ // controller.
+ return java.util.Objects.hash(controller);
+ }
+
+ @Override
+ public String toString() {
+ return "DestroyRequest{" + "controller=" + controller + ", reason=" + reason + '}';
+ }
+ }
+
+ /**
+ * Allow the ability for tests to easily mock out the SipDelegateController for testing.
+ */
+ @VisibleForTesting
+ public interface SipDelegateControllerFactory {
+ /** See {@link SipDelegateController} */
+ SipDelegateController create(int subId, DelegateRequest initialRequest, String packageName,
+ ISipTransport sipTransportImpl, IImsRegistration registrationImpl,
+ ScheduledExecutorService executorService,
+ ISipDelegateConnectionStateCallback stateCallback,
+ ISipDelegateMessageCallback messageCallback);
+ }
+
+ private SipDelegateControllerFactory mDelegateControllerFactory = SipDelegateController::new;
+ private final int mSlotId;
+ private final ScheduledExecutorService mExecutorService;
+ private final RoleManagerAdapter mRoleManagerAdapter;
+ private final TimerAdapter mTimerAdapter;
+ private final LocalLog mLocalLog = new LocalLog(LOG_SIZE);
+
+ // A priority queue of active SipDelegateControllers, where the oldest SipDelegate gets
+ // access to the feature tag if multiple apps are allowed to request the same feature tag.
+ private final List<SipDelegateController> mDelegatePriorityQueue = new ArrayList<>();
+ // SipDelegateControllers who have been created and are pending to be added to the priority
+ // queue. Will be added into the queue in the same order as they were added here.
+ private final List<SipDelegateController> mDelegatePendingCreate = new ArrayList<>();
+ // SipDelegateControllers that are pending to be destroyed.
+ private final List<DestroyRequest> mDelegatePendingDestroy = new ArrayList<>();
+ // Cache of Binders to remote IMS applications for tracking their potential death
+ private final TrackedAppBinders mActiveAppBinders = new TrackedAppBinders();
+
+ // Future scheduled for operations that require the list of SipDelegateControllers to
+ // be evaluated. When the timer expires and triggers the reevaluate method, this controller
+ // will iterate through mDelegatePriorityQueue and assign Feature Tags based on role+priority.
+ private ScheduledFuture<?> mScheduledEvaluateFuture;
+ // mPendingEvaluateFTFuture creates this CompletableFuture, exposed in order to stop other
+ // evaluates from occurring while another is waiting for a result on other threads.
+ private CompletableFuture<Void> mEvaluateCompleteFuture;
+ // Future scheduled that will trigger the ImsService to update the IMS registration for the
+ // SipDelegate configuration. Will be scheduled TRIGGER_UPDATE_REGISTRATION_DELAY_MS
+ // milliseconds after a pending evaluate completes.
+ private ScheduledFuture<?> mPendingUpdateRegistrationFuture;
+ // Subscription id will change as new subscriptions are loaded on the slot.
+ private int mSubId;
+ // Will go up/down as the ImsService associated with this slotId goes up/down.
+ private RcsFeatureManager mRcsManager;
+ // Cached package name of the app that is considered the default SMS app.
+ private String mCachedSmsRolePackageName = "";
+ // Callback to monitor rcs provisioning change
+ private CarrierConfigManager mCarrierConfigManager;
+ // Cached allowed feature tags from carrier config
+ private ArraySet<String> mFeatureTagsAllowed = new ArraySet<>();
+
+ /**
+ * Create an instance of SipTransportController.
+ * @param context The Context associated with this controller.
+ * @param slotId The slot index associated with this controller.
+ * @param subId The subscription ID associated with this controller when it was first created.
+ */
+ public SipTransportController(Context context, int slotId, int subId) {
+ mSlotId = slotId;
+ mSubId = subId;
+
+ mRoleManagerAdapter = new RoleManagerAdapterImpl(context);
+ mTimerAdapter = new TimerAdapterImpl();
+ mExecutorService = Executors.newSingleThreadScheduledExecutor();
+ mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
+ }
+
+ /**
+ * Constructor to inject dependencies for testing.
+ */
+ @VisibleForTesting
+ public SipTransportController(Context context, int slotId, int subId,
+ SipDelegateControllerFactory delegateFactory, RoleManagerAdapter roleManagerAdapter,
+ TimerAdapter timerAdapter, ScheduledExecutorService executor) {
+ mSlotId = slotId;
+ mSubId = subId;
+
+ mRoleManagerAdapter = roleManagerAdapter;
+ mTimerAdapter = timerAdapter;
+ mDelegateControllerFactory = delegateFactory;
+ mExecutorService = executor;
+ mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
+ logi("created");
+ }
+
+ @Override
+ public void onRcsConnected(RcsFeatureManager manager) {
+ mExecutorService.submit(() -> onRcsManagerChanged(manager));
+ }
+
+ @Override
+ public void onRcsDisconnected() {
+ mExecutorService.submit(() -> onRcsManagerChanged(null));
+ }
+
+ @Override
+ public void onAssociatedSubscriptionUpdated(int subId) {
+ mExecutorService.submit(()-> onSubIdChanged(subId));
+ }
+
+ @Override
+ public void onCarrierConfigChanged() {
+ mExecutorService.submit(this::onCarrierConfigChangedInternal);
+ }
+
+ @Override
+ public void onDestroy() {
+ mExecutorService.submit(()-> {
+ // Ensure new create/destroy requests are denied.
+ mSubId = -1;
+ triggerDeregistrationEvent();
+ scheduleDestroyDelegates(
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SUBSCRIPTION_TORN_DOWN)
+ .thenRun(mExecutorService::shutdown);
+ });
+ }
+
+ /**
+ * Optionally create a new {@link SipDelegate} based off of the {@link DelegateRequest} given
+ * based on the state of this controller and associate it with the given callbacks.
+ * <p>
+ * Once the {@link SipDelegate} has been created,
+ * {@link ISipDelegateConnectionStateCallback#onCreated(ISipDelegate)} must be called with
+ * the AIDL instance corresponding to the remote {@link SipDelegate}.
+ * @param subId the subId associated with the request.
+ * @param request The request parameters used to create the {@link SipDelegate}.
+ * @param delegateState The {@link DelegateConnectionStateCallback} Binder connection.
+ * @param delegateMessage The {@link DelegateConnectionMessageCallback} Binder Connection
+ * @throws ImsException if the request to create the {@link SipDelegate} did not complete.
+ */
+ public void createSipDelegate(int subId, DelegateRequest request, String packageName,
+ ISipDelegateConnectionStateCallback delegateState,
+ ISipDelegateMessageCallback delegateMessage) throws ImsException {
+ logi("createSipDelegate: request= " + request + ", packageName= " + packageName);
+ CompletableFuture<ImsException> result = new CompletableFuture<>();
+ mExecutorService.submit(() -> createSipDelegateInternal(subId, request, packageName,
+ delegateState,
+ // Capture any ImsExceptions generated during the process.
+ delegateMessage, result::complete));
+ try {
+ ImsException e = result.get();
+ logi("createSipDelegate: request finished");
+ if (e != null) {
+ throw e;
+ }
+ } catch (InterruptedException | ExecutionException e) {
+ logw("createSipDelegate: exception completing future: " + e);
+ }
+ }
+
+ /**
+ * The remote IMS application has requested the destruction of an existing {@link SipDelegate}.
+ * @param subId The subId associated with the request.
+ * @param connection The internal Binder connection associated with the {@link SipDelegate}.
+ * @param reason The reason for why the {@link SipDelegate} was destroyed.
+ */
+ public void destroySipDelegate(int subId, ISipDelegate connection, int reason) {
+ mExecutorService.execute(() -> destroySipDelegateInternal(subId, connection, reason));
+ }
+
+ /**
+ * The remote IMS application has requested that the ImsService tear down and re-register for
+ * IMS features due to an error it received on the network in response to a SIP request.
+ */
+ public void triggerFullNetworkRegistration(int subId, ISipDelegate connection, int sipCode,
+ String sipReason) {
+ mExecutorService.execute(() -> triggerFullNetworkRegistrationInternal(subId, connection,
+ sipCode, sipReason));
+ }
+
+ /**
+ * @return Whether or not SipTransports are supported on the connected ImsService. This can
+ * change based on the capabilities of the ImsService.
+ * @throws ImsException if the ImsService connected to this controller is currently down.
+ */
+ public boolean isSupported(int subId) throws ImsException {
+ Boolean result = waitForMethodToComplete(() -> isSupportedInternal(subId));
+ if (result == null) {
+ logw("isSupported, unexpected null result, returning false");
+ return false;
+ }
+ return result;
+ }
+
+ private void createSipDelegateInternal(int subId, DelegateRequest request, String packageName,
+ ISipDelegateConnectionStateCallback delegateState,
+ ISipDelegateMessageCallback delegateMessage,
+ Consumer<ImsException> startedErrorConsumer) {
+ ISipTransport transport;
+ IImsRegistration registration;
+ // Send back any errors via Consumer early in creation process if it is clear that the
+ // SipDelegate will never be created.
+ try {
+ checkStateOfController(subId);
+ transport = mRcsManager.getSipTransport();
+ registration = mRcsManager.getImsRegistration();
+ if (transport == null) {
+ logw("createSipDelegateInternal, transport null during request.");
+ startedErrorConsumer.accept(new ImsException("SipTransport not supported",
+ ImsException.CODE_ERROR_UNSUPPORTED_OPERATION));
+ return;
+ } else {
+ // Release the binder thread as there were no issues processing the initial request.
+ startedErrorConsumer.accept(null);
+ }
+ } catch (ImsException e) {
+ logw("createSipDelegateInternal, ImsException during create: " + e);
+ startedErrorConsumer.accept(e);
+ return;
+ }
+
+ SipDelegateController c = mDelegateControllerFactory.create(subId, request, packageName,
+ transport, registration, mExecutorService, delegateState, delegateMessage);
+ logi("createSipDelegateInternal: request= " + request + ", packageName= " + packageName
+ + ", controller created: " + c);
+ addPendingCreateAndEvaluate(c);
+ }
+
+ private void destroySipDelegateInternal(int subId, ISipDelegate connection, int reason) {
+ if (subId != mSubId) {
+ logw("destroySipDelegateInternal: ignoring destroy, as this is about to be destroyed "
+ + "anyway due to subId change, delegate=" + connection);
+ return;
+ }
+ if (connection == null) {
+ logw("destroySipDelegateInternal: ignoring destroy, null connection binder.");
+ return;
+ }
+ SipDelegateController match = null;
+ for (SipDelegateController controller : mDelegatePriorityQueue) {
+ if (Objects.equal(connection.asBinder(),
+ controller.getSipDelegateInterface().asBinder())) {
+ match = controller;
+ break;
+ }
+ }
+ if (match == null) {
+ logw("destroySipDelegateInternal: could not find matching connection=" + connection);
+ return;
+ }
+
+ logi("destroySipDelegateInternal: destroy queued for connection= " + connection);
+ addPendingDestroyAndEvaluate(match, reason);
+ }
+
+ private void triggerFullNetworkRegistrationInternal(int subId, ISipDelegate connection,
+ int sipCode, String sipReason) {
+ if (subId != mSubId) {
+ logw("triggerFullNetworkRegistrationInternal: ignoring network reg request, as this is"
+ + "about to be destroyed anyway due to subId change, delegate=" + connection);
+ return;
+ }
+ if (connection == null) {
+ logw("triggerFullNetworkRegistrationInternal: ignoring, null connection binder.");
+ return;
+ }
+ // Ensure the requester has a valid SipDelegate registered.
+ SipDelegateController match = null;
+ for (SipDelegateController controller : mDelegatePriorityQueue) {
+ if (Objects.equal(connection.asBinder(),
+ controller.getSipDelegateInterface().asBinder())) {
+ match = controller;
+ break;
+ }
+ }
+ if (match == null) {
+ logw("triggerFullNetworkRegistrationInternal: could not find matching connection, "
+ + "ignoring");
+ return;
+ }
+
+ match.triggerFullNetworkRegistration(sipCode, sipReason);
+ }
+
+ /**
+ * Cancel pending update IMS registration events if they exist and instead send a deregister
+ * event.
+ */
+ private void triggerDeregistrationEvent() {
+ logi("triggerDeregistrationEvent: Sending deregister event to ImsService");
+ cancelPendingUpdateRegistration();
+
+ IImsRegistration registrationImpl = mRcsManager.getImsRegistration();
+ if (registrationImpl != null) {
+ try {
+ registrationImpl.triggerSipDelegateDeregistration();
+ } catch (RemoteException e) {
+ logi("triggerDeregistrationEvent: received RemoteException: " + e);
+ }
+ }
+ }
+
+ /**
+ * Schedule an update to the IMS registration. If there is an existing update scheduled, cancel
+ * it and reschedule.
+ * <p>
+ * We want to wait because this can directly result in changes to the IMS registration on the
+ * network, so we need to wait for a steady state where all changes have been made before
+ * triggering an update to the network registration.
+ */
+ private void scheduleUpdateRegistration() {
+ cancelPendingUpdateRegistration();
+
+ ScheduledFuture<?> f = mExecutorService.schedule(this::triggerUpdateRegistrationEvent,
+ mTimerAdapter.getUpdateRegistrationDelayMilliseconds(), TimeUnit.MILLISECONDS);
+ logi("scheduleUpdateRegistration: scheduling new event: " + f);
+ mPendingUpdateRegistrationFuture = f;
+ }
+
+ /**
+ * Cancel an existing pending task to update the IMS registration associated with SIP delegates.
+ */
+ private void cancelPendingUpdateRegistration() {
+ if (mPendingUpdateRegistrationFuture == null
+ || mPendingUpdateRegistrationFuture.isDone()) {
+ return;
+ }
+ // Cancel the old pending operation and reschedule again.
+ mPendingUpdateRegistrationFuture.cancel(false);
+ logi("scheduleUpdateRegistration: cancelling existing reg update event: "
+ + mPendingUpdateRegistrationFuture);
+ }
+
+ /**
+ * Triggers an event to update the IMS registration of the ImsService. Should only be called
+ * from {@link #scheduleUpdateRegistration()}.
+ */
+ private void triggerUpdateRegistrationEvent() {
+ logi("triggerUpdateRegistrationEvent: Sending update registration event to ImsService");
+ IImsRegistration registrationImpl = mRcsManager.getImsRegistration();
+ if (registrationImpl != null) {
+ try {
+ registrationImpl.triggerUpdateSipDelegateRegistration();
+ } catch (RemoteException e) {
+ logi("triggerUpdateRegistrationEvent: received RemoteException: " + e);
+ }
+ }
+ }
+
+ /**
+ * Returns whether or not the ImsService implementation associated with the supplied subId
+ * supports the SipTransport APIs.
+ * <p>
+ * This should only be called on the ExecutorService.
+ * @return true if SipTransport is supported on this subscription, false otherwise.
+ * @throws ImsException thrown if there was an error determining the state of the ImsService.
+ */
+ private boolean isSupportedInternal(int subId) throws ImsException {
+ checkStateOfController(subId);
+ return (mRcsManager.getSipTransport() != null);
+ }
+
+ private boolean addPendingDestroy(SipDelegateController c, int reason) {
+ DestroyRequest request = new DestroyRequest(c, reason);
+ if (!mDelegatePendingDestroy.contains(request)) {
+ return mDelegatePendingDestroy.add(request);
+ }
+ return false;
+ }
+
+ /**
+ * The supplied SipDelegateController has been destroyed and associated feature tags have been
+ * released. Trigger the re-evaluation of the priority queue with the new configuration.
+ */
+ private void addPendingDestroyAndEvaluate(SipDelegateController c, int reason) {
+ if (addPendingDestroy(c, reason)) {
+ scheduleThrottledReevaluate();
+ }
+ }
+
+ /**
+ * A new SipDelegateController has been created, add to the back of the priority queue and
+ * trigger the re-evaluation of the priority queue with the new configuration.
+ */
+ private void addPendingCreateAndEvaluate(SipDelegateController c) {
+ mDelegatePendingCreate.add(c);
+ scheduleThrottledReevaluate();
+ }
+
+ /**
+ * The priority queue has changed, which will cause a re-evaluation of the feature tags granted
+ * to each SipDelegate.
+ * <p>
+ * Note: re-evaluations are throttled to happen at a minimum to occur every
+ * REEVALUATE_THROTTLE_MS seconds. We also do not reevaluate while another reevaluate operation
+ * is in progress, so in this case, defer schedule itself.
+ */
+ private void scheduleThrottledReevaluate() {
+ if (isEvaluatePendingAndNotInProgress()) {
+ logi("scheduleThrottledReevaluate: throttling reevaluate, eval already pending: "
+ + mScheduledEvaluateFuture);
+ } else {
+ mScheduledEvaluateFuture = mExecutorService.schedule(this::reevaluateDelegates,
+ mTimerAdapter.getReevaluateThrottleTimerMilliseconds(), TimeUnit.MILLISECONDS);
+ logi("scheduleThrottledReevaluate: new reevaluate scheduled: "
+ + mScheduledEvaluateFuture);
+ }
+ }
+
+ /**
+ * @return true if there is a evaluate pending, false if there is not. If evaluate has already
+ * begun, but we are waiting for it to complete, this will also return false.
+ */
+ private boolean isEvaluatePendingAndNotInProgress() {
+ boolean isEvalScheduled = mScheduledEvaluateFuture != null
+ && !mScheduledEvaluateFuture.isDone();
+ boolean isEvalInProgress = mEvaluateCompleteFuture != null
+ && !mEvaluateCompleteFuture.isDone();
+ return isEvalScheduled && !isEvalInProgress;
+ }
+
+ /**
+ * Cancel any pending re-evaluates and perform it as soon as possible. This is done in the case
+ * where we need to do something like tear down this controller or change subId.
+ */
+ private void scheduleReevaluateNow(CompletableFuture<Void> onDoneFuture) {
+ if (isEvaluatePendingAndNotInProgress()) {
+ mScheduledEvaluateFuture.cancel(false /*interrupt*/);
+ logi("scheduleReevaluateNow: cancelling pending reevaluate: "
+ + mScheduledEvaluateFuture);
+ }
+ // we have tasks that depend on this potentially, so once the last reevaluate is done,
+ // schedule a new one right away.
+ if (mEvaluateCompleteFuture != null && !mEvaluateCompleteFuture.isDone()) {
+ mEvaluateCompleteFuture.thenRunAsync(
+ () -> scheduleReevaluateNow(onDoneFuture), mExecutorService);
+ return;
+ }
+
+ reevaluateDelegates();
+ mEvaluateCompleteFuture.thenAccept(onDoneFuture::complete);
+ }
+
+ /**
+ * Apply all operations that have been pending by collecting pending create/destroy operations
+ * and batch applying them to the mDelegatePriorityQueue.
+ *
+ * First perform the operation of destroying all SipDelegateConnections that have been pending
+ * destroy. Next, add all pending new SipDelegateControllers to the end of
+ * mDelegatePriorityQueue and loop through all in the queue, applying feature tags to the
+ * appropriate SipDelegateController if they pass role checks and have not already been claimed
+ * by another delegate higher in the priority queue.
+ */
+ private void reevaluateDelegates() {
+ // We need to cancel the pending update now and reschedule IMS registration update for
+ // after the reevaluate is complete.
+ cancelPendingUpdateRegistration();
+ if (mEvaluateCompleteFuture != null && !mEvaluateCompleteFuture.isDone()) {
+ logw("reevaluateDelegates: last evaluate not done, deferring new request");
+ // Defer re-evaluate until after the pending re-evaluate is complete.
+ mEvaluateCompleteFuture.thenRunAsync(this::scheduleThrottledReevaluate,
+ mExecutorService);
+ return;
+ }
+
+ // Remove tracking for all SipDelegates being destroyed first
+ for (DestroyRequest d : mDelegatePendingDestroy) {
+ logi("reevaluateDelegates: starting destroy for: " + d.controller.getPackageName());
+ mActiveAppBinders.unregister(d.controller.getAppMessageCallback());
+ }
+ // Destroy all pending destroy delegates first. Order doesn't matter.
+ List<CompletableFuture<Void>> pendingDestroyList = mDelegatePendingDestroy.stream()
+ .map(d -> triggerDestroy(d.controller, d.reason)).collect(
+ Collectors.toList());
+ CompletableFuture<Void> pendingDestroy = CompletableFuture.allOf(
+ pendingDestroyList.toArray(new CompletableFuture[mDelegatePendingDestroy.size()]));
+ mDelegatePriorityQueue.removeAll(mDelegatePendingDestroy.stream().map(d -> d.controller)
+ .collect(Collectors.toList()));
+ mDelegatePendingDestroy.clear();
+
+ // Add newly created SipDelegates to end of queue before evaluating associated features.
+ mDelegatePriorityQueue.addAll(mDelegatePendingCreate);
+ for (SipDelegateController c : mDelegatePendingCreate) {
+ logi("reevaluateDelegates: pending create: " + c.getPackageName());
+ mActiveAppBinders.register(c.getAppMessageCallback(), c);
+ }
+ mDelegatePendingCreate.clear();
+
+ // Wait for destroy stages to complete, then loop from oldest to most recent and associate
+ // feature tags that the app has requested to the SipDelegate.
+ // Each feature tag can only be associated with one SipDelegate, so as feature tags are
+ // taken, do not allow other SipDelegates to be associated with those tags as well. Each
+ // stage of the CompletableFuture chain passes the previously claimed feature tags into the
+ // next stage so that those feature tags can be denied if already claimed.
+ // Executor doesn't matter here, just composing here to transform to the next stage.
+ CompletableFuture<Set<String>> pendingChange = pendingDestroy.thenCompose((ignore) -> {
+ logi("reevaluateDelegates: destroy phase complete");
+ return CompletableFuture.completedFuture(new ArraySet<>());
+ });
+ final String cachedSmsRolePackage = mCachedSmsRolePackageName;
+ for (SipDelegateController c : mDelegatePriorityQueue) {
+ logi("reevaluateDelegates: pending reeval: " + c);
+ pendingChange = pendingChange.thenComposeAsync((takenTags) -> {
+ logi("reevaluateDelegates: last stage completed with result:" + takenTags);
+ if (takenTags == null) {
+ // return early, the ImsService is no longer available. This will eventually be
+ // destroyed.
+ return CompletableFuture.completedFuture(null /*failed*/);
+ }
+ return changeSupportedFeatureTags(c, cachedSmsRolePackage, takenTags);
+ }, mExecutorService);
+ }
+
+ // Executor doesn't matter here, schedule an event to update the IMS registration.
+ mEvaluateCompleteFuture = pendingChange
+ .whenComplete((f, ex) -> {
+ if (ex != null) {
+ logw("reevaluateDelegates: Exception caught: " + ex);
+ }
+ }).thenAccept((associatedFeatures) -> {
+ logi("reevaluateDelegates: reevaluate complete, feature tags associated: "
+ + associatedFeatures);
+ scheduleUpdateRegistration();
+ });
+ logi("reevaluateDelegates: future created.");
+ }
+
+ private CompletableFuture<Void> triggerDestroy(SipDelegateController c, int reason) {
+ return c.destroy(isForcedFromReason(reason), reason)
+ // Executor doesn't matter here, just for logging.
+ .thenAccept((delegateReason) -> logi("destroy triggered with " + reason
+ + " and finished with reason= " + delegateReason));
+ }
+
+ private boolean isForcedFromReason(int reason) {
+ switch (reason) {
+ case SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_UNKNOWN:
+ logw("isForcedFromReason, unknown reason");
+ /*intentional fallthrough*/
+ case SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP:
+ /*intentional fallthrough*/
+ case SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_USER_DISABLED_RCS:
+ return false;
+ case SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD:
+ /*intentional fallthrough*/
+ case SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SUBSCRIPTION_TORN_DOWN:
+ return true;
+ }
+ logw("isForcedFromReason, unexpected reason: " + reason);
+ return false;
+ }
+
+ /**
+ * Called by RoleManager when a role has changed so that we can query the new role holder.
+ * @param roleName the name of the role whose holders are changed
+ * @param user the user for this role holder change
+ */
+ // Called on mExecutorThread
+ @Override
+ public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
+ logi("onRoleHoldersChanged, roleName= " + roleName + ", user= " + user);
+ // Only monitor changes on the system
+ if (!UserHandle.SYSTEM.equals(user)) {
+ return;
+ }
+
+ if (!RoleManager.ROLE_SMS.equals(roleName)) {
+ logi("onRoleHoldersChanged, ignoring non SMS role change");
+ // TODO: only target default sms app for now and add new roles later using
+ // CarrierConfigManager
+ return;
+ }
+ boolean roleChanged = updateRoleCache();
+ if (roleChanged) {
+ if (!TextUtils.isEmpty(mCachedSmsRolePackageName)) {
+ // the role change event will go A -> "" and then "" -> B. In order to not send two
+ // events to the modem, only trigger the deregistration event when we move to a
+ // non-empty package name.
+ triggerDeregistrationEvent();
+ }
+ // new denied tags will be picked up when reevaluate completes.
+ scheduleThrottledReevaluate();
+ }
+ }
+
+
+ /**
+ * @return true, if the role cache has changed, false otherwise.
+ */
+ private boolean updateRoleCache() {
+ String newSmsRolePackageName = "";
+ try {
+ // Only one app can fulfill the SMS role.
+ newSmsRolePackageName = mRoleManagerAdapter.getRoleHolders(RoleManager.ROLE_SMS)
+ .stream().findFirst().orElse("");
+ } catch (Exception e) {
+ logi("updateRoleCache: exception=" + e);
+ }
+
+ logi("updateRoleCache: new packageName=" + newSmsRolePackageName);
+ if (TextUtils.equals(mCachedSmsRolePackageName, newSmsRolePackageName)) {
+ logi("updateRoleCache, skipping, role did not change");
+ return false;
+ }
+ mCachedSmsRolePackageName = newSmsRolePackageName;
+ return true;
+ }
+
+ /**
+ * Check the requested roles for the specified package name and return the tags that were
+ * applied to that SipDelegateController.
+ * @param controller Controller to attribute feature tags to.
+ * @param alreadyRequestedTags The feature tags that were already granted to other SipDelegates.
+ * @return Once complete, contains the set of feature tags that the SipDelegate now has
+ * associated with it along with the feature tags that previous SipDelegates had.
+ *
+ * // TODO: we currently only track SMS role, extend to support other roles as well.
+ */
+ private CompletableFuture<Set<String>> changeSupportedFeatureTags(
+ SipDelegateController controller, String smsRolePackageName,
+ Set<String> alreadyRequestedTags) {
+ Set<String> requestedFeatureTags = controller.getInitialRequest().getFeatureTags();
+ String packageName = controller.getPackageName();
+ if (!smsRolePackageName.equals(packageName)) {
+ // Deny all tags.
+ Set<FeatureTagState> deniedTags = new ArraySet<>();
+ for (String s : requestedFeatureTags) {
+ deniedTags.add(new FeatureTagState(s,
+ SipDelegateManager.DENIED_REASON_NOT_ALLOWED));
+ }
+ CompletableFuture<Boolean> pendingDeny = controller.changeSupportedFeatureTags(
+ Collections.emptySet(), deniedTags);
+ logi("changeSupportedFeatureTags pendingDeny=" + pendingDeny);
+ // do not worry about executor used here, this stage used to interpret result + add log.
+ return pendingDeny.thenApply((completedSuccessfully) -> {
+ logi("changeSupportedFeatureTags: deny completed: " + completedSuccessfully);
+ if (!completedSuccessfully) return null;
+ // Return back the previous list of requested tags, as we did not add any more.
+ return alreadyRequestedTags;
+ });
+ }
+
+ ArraySet<String> previouslyGrantedTags = new ArraySet<>(alreadyRequestedTags);
+ ArraySet<String> candidateFeatureTags = new ArraySet<>(requestedFeatureTags);
+ Set<FeatureTagState> deniedTags =
+ updateSupportedTags(candidateFeatureTags, previouslyGrantedTags);
+
+ // Add newly granted tags to the already requested tags list.
+ previouslyGrantedTags.addAll(candidateFeatureTags);
+ CompletableFuture<Boolean> pendingChange = controller.changeSupportedFeatureTags(
+ candidateFeatureTags, deniedTags);
+ logi("changeSupportedFeatureTags pendingChange=" + pendingChange);
+ // do not worry about executor used here, this stage used to interpret result + add log.
+ return pendingChange.thenApply((completedSuccessfully) -> {
+ logi("changeSupportedFeatureTags: change completed: " + completedSuccessfully);
+ if (!completedSuccessfully) return null;
+ return previouslyGrantedTags;
+ });
+ }
+
+ /**
+ * Update candidate feature tags according to feature tags allowed by carrier config,
+ * and previously granted by other SipDelegates.
+ *
+ * @param candidateFeatureTags The candidate feature tags to be updated. It will be
+ * updated as needed per the carrier config and previously granted feature tags.
+ * @param previouslyGrantedTags The feature tags already granted by other SipDelegates.
+ * @return The set of denied feature tags.
+ */
+ private Set<FeatureTagState> updateSupportedTags(Set<String> candidateFeatureTags,
+ Set<String> previouslyGrantedTags) {
+ Boolean overrideRes = RcsProvisioningMonitor.getInstance()
+ .getImsFeatureValidationOverride(mSubId);
+ // deny tags already used by other delegates
+ Set<FeatureTagState> deniedTags = new ArraySet<>();
+
+ // match config if feature validation is not overridden
+ if (overrideRes == null) {
+ Iterator<String> it = candidateFeatureTags.iterator();
+ while (it.hasNext()) {
+ String tag = it.next();
+ if (previouslyGrantedTags.contains(tag)) {
+ logi(tag + " has already been granted previously.");
+ it.remove();
+ deniedTags.add(new FeatureTagState(tag,
+ SipDelegateManager.DENIED_REASON_IN_USE_BY_ANOTHER_DELEGATE));
+ } else if (!mFeatureTagsAllowed.contains(tag.trim().toLowerCase())) {
+ logi(tag + " is not allowed per config.");
+ it.remove();
+ deniedTags.add(new FeatureTagState(tag,
+ SipDelegateManager.DENIED_REASON_NOT_ALLOWED));
+ }
+ }
+ } else if (Boolean.FALSE.equals(overrideRes)) {
+ logi("all features are denied for test purpose.");
+ for (String s : candidateFeatureTags) {
+ deniedTags.add(new FeatureTagState(s,
+ SipDelegateManager.DENIED_REASON_NOT_ALLOWED));
+ }
+ candidateFeatureTags.clear();
+ }
+
+ return deniedTags;
+ }
+
+ /**
+ * Run a Callable on the ExecutorService Thread and wait for the result.
+ * If an ImsException is thrown, catch it and rethrow it to caller.
+ */
+ private <T> T waitForMethodToComplete(Callable<T> callable) throws ImsException {
+ Future<T> r = mExecutorService.submit(callable);
+ T result;
+ try {
+ result = r.get();
+ } catch (InterruptedException e) {
+ result = null;
+ } catch (ExecutionException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof ImsException) {
+ // Rethrow the exception
+ throw (ImsException) cause;
+ }
+ logw("Unexpected Exception, returning null: " + cause);
+ result = null;
+ }
+ return result;
+ }
+
+ /**
+ * Throw an ImsException for common scenarios where the state of the controller is not ready
+ * for communication.
+ * <p>
+ * This should only be called while running on the on the ExecutorService.
+ */
+ private void checkStateOfController(int subId) throws ImsException {
+ if (mSubId != subId) {
+ // sub ID has changed while this was in the queue.
+ throw new ImsException("subId is no longer valid for this request.",
+ ImsException.CODE_ERROR_INVALID_SUBSCRIPTION);
+ }
+ if (mRcsManager == null) {
+ throw new ImsException("Connection to ImsService is not available",
+ ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+ }
+ }
+
+ private void onRcsManagerChanged(RcsFeatureManager m) {
+ logi("manager changed, " + mRcsManager + "->" + m);
+ if (mRcsManager == m) return;
+ mRcsManager = m;
+ if (mRcsManager == null) {
+ logi("onRcsManagerChanged: lost connection to ImsService, tearing down...");
+ unregisterListeners();
+ scheduleDestroyDelegates(SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD);
+ } else {
+ logi("onRcsManagerChanged: registering listeners/updating role cache...");
+ registerListeners();
+ updateRoleCache();
+ }
+ }
+
+ private void registerListeners() {
+ try {
+ mRoleManagerAdapter.addOnRoleHoldersChangedListenerAsUser(mExecutorService, this,
+ UserHandle.SYSTEM);
+ } catch (Exception e) {
+ logi("registerListeners: exception=" + e);
+ }
+ }
+
+ private void unregisterListeners() {
+ mCachedSmsRolePackageName = "";
+ mRoleManagerAdapter.removeOnRoleHoldersChangedListenerAsUser(this, UserHandle.SYSTEM);
+ }
+
+ /**
+ * Called when the sub ID associated with the slot has changed.
+ */
+ private void onSubIdChanged(int newSubId) {
+ logi("subId changed, " + mSubId + "->" + newSubId);
+ if (mSubId != newSubId) {
+ // Swap subId, any pending create/destroy on old subId will be denied.
+ mSubId = newSubId;
+ scheduleDestroyDelegates(
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SUBSCRIPTION_TORN_DOWN);
+ }
+
+ onCarrierConfigChangedInternal();
+ }
+
+ /**
+ * Called when the carrier configuration associated with the same subId has changed.
+ */
+ private void onCarrierConfigChangedInternal() {
+ logi("Carrier Config changed for subId: " + mSubId);
+ mFeatureTagsAllowed.clear();
+ PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(mSubId);
+ String[] tagConfigs = carrierConfig.getStringArray(
+ CarrierConfigManager.Ims.KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY);
+ if (tagConfigs != null && tagConfigs.length > 0) {
+ for (String tag : tagConfigs) {
+ mFeatureTagsAllowed.add(tag.trim().toLowerCase());
+ }
+ }
+ }
+
+ /**
+ * Destroy all tracked SipDelegateConnections due to the subscription being torn down.
+ * @return A CompletableFuture that will complete when all SipDelegates have been torn down.
+ */
+ private CompletableFuture<Void> scheduleDestroyDelegates(int reason) {
+ boolean addedDestroy = false;
+ for (SipDelegateController c : mDelegatePriorityQueue) {
+ logi("scheduleDestroyDelegates: Controller pending destroy: " + c);
+ addedDestroy |= addPendingDestroy(c, reason);
+ }
+ if (addedDestroy) {
+ CompletableFuture<Void> pendingDestroy = new CompletableFuture<>();
+ scheduleReevaluateNow(pendingDestroy);
+ return pendingDestroy;
+ } else {
+ return CompletableFuture.completedFuture(null);
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter printWriter) {
+ IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
+ pw.println("SipTransportController" + "[" + mSlotId + "->" + mSubId + "]:");
+ pw.increaseIndent();
+ pw.println("LocalLog:");
+ pw.increaseIndent();
+ mLocalLog.dump(pw);
+ pw.decreaseIndent();
+ pw.println("SipDelegateControllers (in priority order):");
+ pw.increaseIndent();
+ if (mDelegatePriorityQueue.isEmpty()) {
+ pw.println("[NONE]");
+ } else {
+ for (SipDelegateController c : mDelegatePriorityQueue) {
+ c.dump(pw);
+ }
+ }
+ pw.decreaseIndent();
+ pw.decreaseIndent();
+ }
+
+ private void logi(String log) {
+ Log.i(LOG_TAG, "[" + mSlotId + "->" + mSubId + "] " + log);
+ mLocalLog.log("[I] " + log);
+ }
+
+ private void logw(String log) {
+ Log.w(LOG_TAG, "[" + mSlotId + "->" + mSubId + "] " + log);
+ mLocalLog.log("[W] " + log);
+ }
+}
diff --git a/src/com/android/services/telephony/rcs/TelephonyRcsService.java b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
index c85e9a9..034382c 100644
--- a/src/com/android/services/telephony/rcs/TelephonyRcsService.java
+++ b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
@@ -33,6 +33,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.PhoneConfigurationManager;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.phone.R;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -47,34 +48,60 @@
private static final String LOG_TAG = "TelephonyRcsService";
/**
- * Used to inject RcsFeatureController and UserCapabilityExchangeImpl instances for testing.
+ * Used to inject RcsFeatureController and UceController instances for testing.
*/
@VisibleForTesting
public interface FeatureFactory {
/**
- * @return an {@link RcsFeatureController} assoicated with the slot specified.
+ * @return an {@link RcsFeatureController} associated with the slot specified.
*/
- RcsFeatureController createController(Context context, int slotId);
+ RcsFeatureController createController(Context context, int slotId, int subId);
/**
- * @return an instance of {@link UserCapabilityExchangeImpl} associated with the slot
+ * @return an instance of {@link UceControllerManager} associated with the slot specified.
+ */
+ UceControllerManager createUceControllerManager(Context context, int slotId, int subId);
+
+ /**
+ * @return an instance of {@link SipTransportController} for the slot and subscription
* specified.
*/
- UserCapabilityExchangeImpl createUserCapabilityExchange(Context context, int slotId,
- int subId);
+ SipTransportController createSipTransportController(Context context, int slotId, int subId);
}
private FeatureFactory mFeatureFactory = new FeatureFactory() {
@Override
- public RcsFeatureController createController(Context context, int slotId) {
- return new RcsFeatureController(context, slotId);
+ public RcsFeatureController createController(Context context, int slotId, int subId) {
+ return new RcsFeatureController(context, slotId, subId);
}
@Override
- public UserCapabilityExchangeImpl createUserCapabilityExchange(Context context, int slotId,
+ public UceControllerManager createUceControllerManager(Context context, int slotId,
int subId) {
- return new UserCapabilityExchangeImpl(context, slotId, subId);
+ return new UceControllerManager(context, slotId, subId);
}
+
+ @Override
+ public SipTransportController createSipTransportController(Context context, int slotId,
+ int subId) {
+ return new SipTransportController(context, slotId, subId);
+ }
+ };
+
+ /**
+ * Used to inject device resource for testing.
+ */
+ @VisibleForTesting
+ public interface ResourceProxy {
+ /**
+ * @return an whether the device supports User Capability Exchange.
+ */
+ boolean getDeviceUceEnabled(Context context);
+ }
+
+ private static ResourceProxy sResourceProxy = context -> {
+ return context.getResources().getBoolean(
+ R.bool.config_rcs_user_capability_exchange_enabled);
};
// Notifies this service that there has been a change in available slots.
@@ -86,6 +113,11 @@
// Maps slot ID -> RcsFeatureController.
private SparseArray<RcsFeatureController> mFeatureControllers;
+ // Maps slotId -> associatedSubIds
+ private SparseArray<Integer> mSlotToAssociatedSubIds;
+
+ // Whether the device supports User Capability Exchange
+ private boolean mRcsUceEnabled;
private BroadcastReceiver mCarrierConfigChangedReceiver = new BroadcastReceiver() {
@Override
@@ -102,7 +134,7 @@
SubscriptionManager.INVALID_PHONE_INDEX);
int subId = bundle.getInt(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
- updateFeatureControllerSubscription(slotId, subId);
+ onCarrierConfigChangedForSlot(slotId, subId);
}
}
};
@@ -129,6 +161,18 @@
mContext = context;
mNumSlots = numSlots;
mFeatureControllers = new SparseArray<>(numSlots);
+ mSlotToAssociatedSubIds = new SparseArray<>(numSlots);
+ mRcsUceEnabled = sResourceProxy.getDeviceUceEnabled(mContext);
+ }
+
+ @VisibleForTesting
+ public TelephonyRcsService(Context context, int numSlots, ResourceProxy resourceProxy) {
+ mContext = context;
+ mNumSlots = numSlots;
+ mFeatureControllers = new SparseArray<>(numSlots);
+ mSlotToAssociatedSubIds = new SparseArray<>(numSlots);
+ sResourceProxy = resourceProxy;
+ mRcsUceEnabled = sResourceProxy.getDeviceUceEnabled(mContext);
}
/**
@@ -178,6 +222,8 @@
// Do not add feature controllers for inactive subscriptions
if (c.hasActiveFeatures()) {
mFeatureControllers.put(i, c);
+ // Do not change mSlotToAssociatedSubIds, it will be updated upon carrier
+ // config change.
}
}
} else {
@@ -185,6 +231,7 @@
RcsFeatureController c = mFeatureControllers.get(i);
if (c != null) {
mFeatureControllers.remove(i);
+ mSlotToAssociatedSubIds.remove(i);
c.destroy();
}
}
@@ -192,19 +239,29 @@
}
}
- private void updateFeatureControllerSubscription(int slotId, int newSubId) {
+ /**
+ * ACTION_CARRIER_CONFIG_CHANGED was received by this service for a specific slot.
+ * @param slotId The slotId associated with the event.
+ * @param subId The subId associated with the event. May cause the subId associated with the
+ * RcsFeatureController to change if the subscription itself has changed.
+ */
+ private void onCarrierConfigChangedForSlot(int slotId, int subId) {
synchronized (mLock) {
RcsFeatureController f = mFeatureControllers.get(slotId);
- Log.i(LOG_TAG, "updateFeatureControllerSubscription: slotId=" + slotId + " newSubId="
- + newSubId + ", existing feature=" + (f != null));
- if (SubscriptionManager.isValidSubscriptionId(newSubId)) {
+ final int oldSubId = mSlotToAssociatedSubIds.get(slotId,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ mSlotToAssociatedSubIds.put(slotId, subId);
+ Log.i(LOG_TAG, "updateFeatureControllerSubscription: slotId=" + slotId
+ + ", oldSubId= " + oldSubId + ", subId=" + subId + ", existing feature="
+ + (f != null));
+ if (SubscriptionManager.isValidSubscriptionId(subId)) {
if (f == null) {
// A controller doesn't exist for this slot yet.
- f = mFeatureFactory.createController(mContext, slotId);
- updateSupportedFeatures(f, slotId, newSubId);
+ f = mFeatureFactory.createController(mContext, slotId, subId);
+ updateSupportedFeatures(f, slotId, subId);
if (f.hasActiveFeatures()) mFeatureControllers.put(slotId, f);
} else {
- updateSupportedFeatures(f, slotId, newSubId);
+ updateSupportedFeatures(f, slotId, subId);
// Do not keep an empty container around.
if (!f.hasActiveFeatures()) {
f.destroy();
@@ -212,44 +269,83 @@
}
}
}
- if (f != null) f.updateAssociatedSubscription(newSubId);
+ if (f != null) {
+ if (oldSubId == subId) {
+ f.onCarrierConfigChangedForSubscription();
+ } else {
+ f.updateAssociatedSubscription(subId);
+ }
+ }
}
}
private RcsFeatureController constructFeatureController(int slotId) {
- RcsFeatureController c = mFeatureFactory.createController(mContext, slotId);
int subId = getSubscriptionFromSlot(slotId);
+ RcsFeatureController c = mFeatureFactory.createController(mContext, slotId, subId);
updateSupportedFeatures(c, slotId, subId);
return c;
}
private void updateSupportedFeatures(RcsFeatureController c, int slotId, int subId) {
- if (doesSubscriptionSupportPresence(subId)) {
- if (c.getFeature(UserCapabilityExchangeImpl.class) == null) {
- c.addFeature(mFeatureFactory.createUserCapabilityExchange(mContext, slotId, subId),
- UserCapabilityExchangeImpl.class);
+ if (isDeviceUceEnabled() && doesSubscriptionSupportPresence(subId)) {
+ if (c.getFeature(UceControllerManager.class) == null) {
+ c.addFeature(mFeatureFactory.createUceControllerManager(mContext, slotId, subId),
+ UceControllerManager.class);
}
} else {
- if (c.getFeature(UserCapabilityExchangeImpl.class) != null) {
- c.removeFeature(UserCapabilityExchangeImpl.class);
+ if (c.getFeature(UceControllerManager.class) != null) {
+ c.removeFeature(UceControllerManager.class);
+ }
+ }
+
+ if (doesSubscriptionSupportSingleRegistration(subId)) {
+ if (c.getFeature(SipTransportController.class) == null) {
+ c.addFeature(mFeatureFactory.createSipTransportController(mContext, slotId, subId),
+ SipTransportController.class);
+ }
+ } else {
+ if (c.getFeature(SipTransportController.class) != null) {
+ c.removeFeature(SipTransportController.class);
}
}
// Only start the connection procedure if we have active features.
if (c.hasActiveFeatures()) c.connect();
}
+ /**
+ * Get whether the device supports RCS User Capability Exchange or not.
+ */
+ public boolean isDeviceUceEnabled() {
+ return mRcsUceEnabled;
+ }
+
+ /**
+ * Set the device supports RCS User Capability Exchange.
+ */
+ public void setDeviceUceEnabled(boolean isEnabled) {
+ mRcsUceEnabled = isEnabled;
+ }
+
private boolean doesSubscriptionSupportPresence(int subId) {
if (!SubscriptionManager.isValidSubscriptionId(subId)) return false;
CarrierConfigManager carrierConfigManager =
mContext.getSystemService(CarrierConfigManager.class);
if (carrierConfigManager == null) return false;
boolean supportsUce = carrierConfigManager.getConfigForSubId(subId).getBoolean(
- CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL);
+ CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL);
supportsUce |= carrierConfigManager.getConfigForSubId(subId).getBoolean(
CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL);
return supportsUce;
}
+ private boolean doesSubscriptionSupportSingleRegistration(int subId) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) return false;
+ CarrierConfigManager carrierConfigManager =
+ mContext.getSystemService(CarrierConfigManager.class);
+ if (carrierConfigManager == null) return false;
+ return carrierConfigManager.getConfigForSubId(subId).getBoolean(
+ CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL);
+ }
private int getSubscriptionFromSlot(int slotId) {
SubscriptionManager manager = mContext.getSystemService(SubscriptionManager.class);
@@ -274,6 +370,7 @@
synchronized (mLock) {
for (int i = 0; i < mNumSlots; i++) {
RcsFeatureController f = mFeatureControllers.get(i);
+ if (f == null) continue;
pw.increaseIndent();
f.dump(fd, printWriter, args);
pw.decreaseIndent();
diff --git a/src/com/android/services/telephony/rcs/TransportSipMessageValidator.java b/src/com/android/services/telephony/rcs/TransportSipMessageValidator.java
new file mode 100644
index 0000000..777026c
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/TransportSipMessageValidator.java
@@ -0,0 +1,551 @@
+/*
+ * 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.services.telephony.rcs;
+
+import android.telephony.ims.DelegateRegistrationState;
+import android.telephony.ims.FeatureTagState;
+import android.telephony.ims.SipDelegateConfiguration;
+import android.telephony.ims.SipDelegateImsConfiguration;
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.SipMessage;
+import android.util.LocalLog;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.services.telephony.rcs.validator.IncomingTransportStateValidator;
+import com.android.services.telephony.rcs.validator.MalformedSipMessageValidator;
+import com.android.services.telephony.rcs.validator.OutgoingTransportStateValidator;
+import com.android.services.telephony.rcs.validator.RestrictedOutgoingSipRequestValidator;
+import com.android.services.telephony.rcs.validator.RestrictedOutgoingSubscribeValidator;
+import com.android.services.telephony.rcs.validator.SipMessageValidator;
+import com.android.services.telephony.rcs.validator.ValidationResult;
+
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Track incoming and outgoing SIP messages passing through this delegate and verify these messages
+ * by doing the following:
+ * <ul>
+ * <li>Track the SipDelegate's registration state to ensure that a registration event has
+ * occurred before allowing outgoing messages. Once it has occurred, filter outgoing SIP messages
+ * based on the open/restricted feature tag registration state.</li>
+ * <li>Track the SipDelegate's IMS configuration version and deny any outgoing SipMessages
+ * associated with a stale IMS configuration version.</li>
+ * <li>Track the SipDelegate open/close state to allow/deny outgoing messages based on the
+ * session's state.</li>
+ * <li>Validate outgoing SIP messages for both restricted request methods as well as restricted/
+ * malformed headers.</li>
+ * </ul>
+ */
+public class TransportSipMessageValidator {
+
+ private static final String LOG_TAG = "SipMessageV";
+
+ /**
+ * the time in milliseconds that we will wait for SIP sessions to close before we will timeout
+ * and force the sessions to be cleaned up.
+ */
+ private static final int PENDING_CLOSE_TIMEOUT_MS = 1000;
+ /**
+ * time in milliseconds that we will wait for SIP sessions to be closed before we timeout and
+ * force the sessions associated with the deregistering feature tags to be cleaned up.
+ */
+ private static final int PENDING_REGISTRATION_CHANGE_TIMEOUT_MS = 1000;
+
+ /**
+ * Timeouts used in this class that are visible for testing.
+ */
+ @VisibleForTesting
+ public interface Timeouts {
+ /**
+ * @return the time in milliseconds that we will wait for SIP sessions to close before we
+ * will timeout and force the sessions to be cleaned up.
+ */
+ int getPendingCloseTimeoutMs();
+
+ /**
+ * @return the time in milliseconds that we will wait for SIP sessions to be closed before
+ * we timeout and force the sessions associated with the deregistering feature tags to be
+ * cleaned up.
+ */
+ int getPendingRegistrationChangeTimeoutMs();
+ }
+
+ /**
+ * Tracks a pending task that has been scheduled on the associated Executor.
+ */
+ private abstract static class PendingTask implements Runnable {
+
+ private ScheduledFuture<?> mFuture;
+
+ public void scheduleDelayed(ScheduledExecutorService executor, int timeMs) {
+ mFuture = executor.schedule(this, timeMs, TimeUnit.MILLISECONDS);
+ }
+
+ public boolean isDone() {
+ return mFuture != null && mFuture.isDone();
+ }
+
+ public void cancel() {
+ if (mFuture == null) return;
+ mFuture.cancel(false /*interrupt*/);
+ }
+ }
+
+ /**
+ * Tracks a pending reg cleanup task that has been scheduled on the associated Executor.
+ */
+ private abstract static class PendingRegCleanupTask extends PendingTask {
+ public final Set<String> pendingCallIds;
+ public final Set<String> featureTags;
+
+ PendingRegCleanupTask(Set<String> tags, Set<String> callIds) {
+ featureTags = tags;
+ pendingCallIds = callIds;
+ }
+ }
+
+ private final int mSubId;
+ private final ScheduledExecutorService mExecutor;
+ private final LocalLog mLocalLog = new LocalLog(SipTransportController.LOG_SIZE);
+ private final SipSessionTracker mSipSessionTracker;
+ // Validators
+ private final IncomingTransportStateValidator mIncomingTransportStateValidator;
+ private final OutgoingTransportStateValidator mOutgoingTransportStateValidator;
+ private final SipMessageValidator mOutgoingMessageValidator;
+ private final SipMessageValidator mIncomingMessageValidator;
+
+ private Set<String> mSupportedFeatureTags;
+ private Set<FeatureTagState> mDeniedFeatureTags;
+ private long mConfigVersion = -1;
+ private Consumer<Set<String>> mClosingCompleteConsumer;
+ private PendingTask mPendingClose;
+ private PendingRegCleanupTask mPendingRegCleanup;
+ private Consumer<Set<String>> mRegistrationAppliedConsumer;
+
+ public TransportSipMessageValidator(int subId, ScheduledExecutorService executor) {
+ mSubId = subId;
+ mExecutor = executor;
+ mSipSessionTracker = new SipSessionTracker();
+ mOutgoingTransportStateValidator = new OutgoingTransportStateValidator(mSipSessionTracker);
+ mIncomingTransportStateValidator = new IncomingTransportStateValidator();
+ mOutgoingMessageValidator = new MalformedSipMessageValidator().andThen(
+ new RestrictedOutgoingSipRequestValidator()).andThen(
+ new RestrictedOutgoingSubscribeValidator()).andThen(
+ mOutgoingTransportStateValidator);
+ mIncomingMessageValidator = mIncomingTransportStateValidator;
+ }
+
+ @VisibleForTesting
+ public TransportSipMessageValidator(int subId, ScheduledExecutorService executor,
+ SipSessionTracker sipSessionTracker,
+ OutgoingTransportStateValidator outgoingStateValidator,
+ IncomingTransportStateValidator incomingStateValidator) {
+ mSubId = subId;
+ mExecutor = executor;
+ mSipSessionTracker = sipSessionTracker;
+ mOutgoingTransportStateValidator = outgoingStateValidator;
+ mIncomingTransportStateValidator = incomingStateValidator;
+ mOutgoingMessageValidator = mOutgoingTransportStateValidator;
+ mIncomingMessageValidator = mIncomingTransportStateValidator;
+ }
+
+ /**
+ * Notify this tracker that a registration state change has occurred.
+ * <p>
+ * In some scenarios, this will require that existing SIP dialogs are closed (for example, when
+ * moving a feature tag from REGISTERED->DEREGISTERING). This method allows the caller to
+ * provide a Consumer that will be called when either there are no SIP dialogs active on
+ * DEREGISTERING feature tags, or a timeout has occurred. In the case that a timeout has
+ * occurred, this Consumer will accept a list of callIds that will be manually closed by the
+ * framework to unblock the IMS stack.
+ * <p>
+ * @param stateChangeComplete A one time Consumer that when completed, will contain a List of
+ * callIds corresponding to SIP Dialogs that have not been closed yet. It is the callers
+ * responsibility to close the dialogs associated with the provided callIds. If another
+ * state update occurs before the previous was completed, the previous consumer will be
+ * completed with an empty list and the new Consumer will be executed when the new state
+ * changes.
+ * @param regState The new registration state.
+ */
+ public void onRegistrationStateChanged(Consumer<Set<String>> stateChangeComplete,
+ DelegateRegistrationState regState) {
+ if (mRegistrationAppliedConsumer != null) {
+ logw("onRegistrationStateChanged: pending registration change, completing now.");
+ // complete the pending consumer with no dialogs pending, this will be re-evaluated
+ // and new configuration will be applied.
+ cleanupAndNotifyRegistrationAppliedConsumer(Collections.emptySet());
+ }
+ Set<String> restrictedTags = Stream.concat(
+ regState.getDeregisteringFeatureTags().stream(),
+ regState.getDeregisteredFeatureTags().stream()).map(FeatureTagState::getFeatureTag)
+ .collect(Collectors.toSet());
+ mOutgoingTransportStateValidator.restrictFeatureTags(restrictedTags);
+ mRegistrationAppliedConsumer = stateChangeComplete;
+ if (mPendingClose == null || mPendingClose.isDone()) {
+ // Only update the pending registration cleanup task if we do not already have a pending
+ // close in progress.
+ updatePendingRegCleanupTask(restrictedTags);
+ } else {
+ logi("skipping update reg cleanup due to pending close task.");
+ }
+ }
+
+ /**
+ * Notify this tracker that the IMS configuration has changed.
+ *
+ * Parameters contained in the IMS configuration will be used to validate outgoing messages,
+ * such as the configuration version.
+ * @param c The newest IMS configuration.
+ */
+ public void onImsConfigurationChanged(SipDelegateImsConfiguration c) {
+ if (c.getVersion() == mConfigVersion) {
+ return;
+ }
+ logi("onImsConfigurationChanged: " + mConfigVersion + "->" + c.getVersion());
+ mConfigVersion = c.getVersion();
+ }
+
+ /**
+ * Notify this tracker that the IMS configuration has changed.
+ *
+ * Parameters contained in the IMS configuration will be used to validate outgoing messages,
+ * such as the configuration version.
+ * @param c The newest IMS configuration.
+ */
+ public void onConfigurationChanged(SipDelegateConfiguration c) {
+ if (c.getVersion() == mConfigVersion) {
+ return;
+ }
+ logi("onConfigurationChanged: " + mConfigVersion + "->" + c.getVersion());
+ mConfigVersion = c.getVersion();
+ }
+
+ /**
+ * A new message transport has been opened to a SipDelegate.
+ * <p>
+ * Initializes this tracker and resets any state required to process messages.
+ * @param supportedFeatureTags feature tags that are supported and should pass message
+ * verification.
+ * @param deniedFeatureTags feature tags that were denied and should fail message verification.
+ */
+ public void onTransportOpened(Set<String> supportedFeatureTags,
+ Set<FeatureTagState> deniedFeatureTags) {
+ logi("onTransportOpened: moving to open state");
+ mSupportedFeatureTags = supportedFeatureTags;
+ mDeniedFeatureTags = deniedFeatureTags;
+ mOutgoingTransportStateValidator.open(supportedFeatureTags, deniedFeatureTags.stream().map(
+ FeatureTagState::getFeatureTag).collect(Collectors.toSet()));
+ mIncomingTransportStateValidator.open();
+ }
+
+ /**
+ * A SIP session has been cleaned up and should no longer be tracked.
+ * @param callId The call ID associated with the SIP session.
+ */
+ public void onSipSessionCleanup(String callId) {
+ mSipSessionTracker.cleanupSession(callId);
+ onCallIdsChanged();
+ }
+
+ /**
+ * Move this tracker into a restricted state, where only outgoing SIP messages associated with
+ * an ongoing SIP Session may be sent. Any out-of-dialog outgoing SIP messages will be denied.
+ * This does not affect incoming SIP messages (for example, an incoming SIP INVITE).
+ * <p>
+ * This tracker will stay in this state until either all open SIP Sessions are closed by the
+ * remote application, or a timeout occurs. Once this happens, the provided Consumer will accept
+ * a List of call IDs associated with the open SIP Sessions that did not close before the
+ * timeout. The caller must then close all open SIP Sessions before closing the transport.
+ * @param closingCompleteConsumer A Consumer that will be called when the transport can be
+ * closed and may contain a list of callIds associated with SIP sessions that have not
+ * been closed.
+ * @param closingReason The reason that will be provided if an outgoing out-of-dialog SIP
+ * message is sent while the transport is closing.
+ * @param closedReason The reason that will be provided if any outgoing SIP message is sent
+ * once the transport is closed.
+ */
+ public void closeSessionsGracefully(Consumer<Set<String>> closingCompleteConsumer,
+ int closingReason, int closedReason) {
+ if (closingCompleteConsumer == null) {
+ logw("closeSessionsGracefully: unexpected - called with null consumer... closing now");
+ closeSessions(closedReason);
+ return;
+ }
+ if (mClosingCompleteConsumer != null) {
+ // In this case, all we can do is combine the consumers and wait for the other pending
+ // close to complete, finishing both.
+ logw("closeSessionsGracefully: unexpected - existing close pending, combining"
+ + " consumers.");
+ mClosingCompleteConsumer = callIds -> {
+ mClosingCompleteConsumer.accept(callIds);
+ closingCompleteConsumer.accept(callIds);
+ };
+ return;
+ } else {
+ mClosingCompleteConsumer = closingCompleteConsumer;
+ }
+ if (getTrackedSipSessionCallIds().isEmpty()) {
+ logi("closeSessionsGracefully: moving to closed state now, reason=" + closedReason);
+ closeSessionsInternal(closedReason);
+ cancelClosingTimeoutAndSendComplete(Collections.emptySet());
+ return;
+ }
+ cancelPendingRegCleanupTask();
+ logi("closeSessionsGracefully: moving to restricted state, reason=" + closingReason);
+ mOutgoingTransportStateValidator.restrict(closingReason);
+ mPendingClose = new PendingTask() {
+ @Override
+ public void run() {
+ closeSessions(closingReason);
+ }
+ };
+ mPendingClose.scheduleDelayed(mExecutor, PENDING_CLOSE_TIMEOUT_MS);
+ }
+
+ /**
+ * Close the transport now. If there are any open SIP sessions and this is closed due to a
+ * configuration change (SIM subscription change, user disabled RCS, the service is dead,
+ * etc...) then we will return the call IDs of all open sessions and ask them to be closed.
+ * @param closedReason The error reason for why the message transport was closed that will be
+ * sent back to the caller if a new SIP message is sent.
+ * @return A List of call IDs associated with sessions that were still open at the time that the
+ * tracker closed the transport.
+ */
+ public Set<String> closeSessions(int closedReason) {
+ Set<String> openCallIds = getTrackedSipSessionCallIds();
+ logi("closeSessions: moving to closed state, reason=" + closedReason + ", open call ids: "
+ + openCallIds);
+ closeSessionsInternal(closedReason);
+ boolean consumerHandledPendingSessions = cancelClosingTimeoutAndSendComplete(openCallIds);
+ if (consumerHandledPendingSessions) {
+ logw("closeSessions: call ID closure handled through consumer");
+ // sent the open call IDs through the pending complete mechanism to unblock any previous
+ // graceful close command and close them early.
+ return Collections.emptySet();
+ }
+ return openCallIds;
+ }
+
+ /**
+ * Verify a new outgoing SIP message before sending to the SipDelegate (ImsService).
+ * @param message The SIP message being verified
+ * @return The result of verifying the outgoing message.
+ */
+
+ public ValidationResult verifyOutgoingMessage(SipMessage message, long configVersion) {
+ if (mConfigVersion != configVersion) {
+ return new ValidationResult(
+ SipDelegateManager.MESSAGE_FAILURE_REASON_STALE_IMS_CONFIGURATION,
+ "stale IMS configuration: " + configVersion + ", expected: "
+ + mConfigVersion);
+ }
+ ValidationResult result = mOutgoingMessageValidator.validate(message);
+ logi("verifyOutgoingMessage: " + result + ", message=" + message);
+ if (result.isValidated) mSipSessionTracker.filterSipMessage(message);
+ return result;
+ }
+
+ /**
+ * Verify a new incoming SIP message before sending it to the
+ * DelegateConnectionMessageCallback (remote application).
+ * @param message The SipMessage to verify.
+ * @return The result of verifying the incoming message.
+ */
+ public ValidationResult verifyIncomingMessage(SipMessage message) {
+ ValidationResult result = mIncomingMessageValidator.validate(message);
+ logi("verifyIncomingMessage: " + result + ", message=" + message);
+ if (result.isValidated) mSipSessionTracker.filterSipMessage(message);
+ return result;
+ }
+
+ /**
+ * Acknowledge that a pending incoming or outgoing SIP message has been delivered successfully
+ * to the remote.
+ * @param transactionId The transaction ID associated with the message.
+ */
+ public void acknowledgePendingMessage(String transactionId) {
+ logi("acknowledgePendingMessage: id=" + transactionId);
+ mSipSessionTracker.acknowledgePendingMessage(transactionId);
+ onCallIdsChanged();
+ }
+
+ /**
+ * A pending incoming or outgoing SIP message has failed and should not be tracked.
+ * @param transactionId The transaction ID associated with the message.
+ */
+ public void notifyPendingMessageFailed(String transactionId) {
+ logi("notifyPendingMessageFailed: id=" + transactionId);
+ mSipSessionTracker.pendingMessageFailed(transactionId);
+ }
+
+ /** Dump state about this tracker that should be included in the dumpsys */
+ public void dump(PrintWriter printWriter) {
+ IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
+ pw.println("Supported Tags:" + mSupportedFeatureTags);
+ pw.println("Denied Tags:" + mDeniedFeatureTags);
+ pw.println(mOutgoingTransportStateValidator);
+ pw.println(mIncomingTransportStateValidator);
+ pw.println("Reg consumer pending: " + (mRegistrationAppliedConsumer != null));
+ pw.println("Close consumer pending: " + (mClosingCompleteConsumer != null));
+ pw.println();
+ mSipSessionTracker.dump(pw);
+ pw.println();
+ pw.println("Most recent logs:");
+ mLocalLog.dump(printWriter);
+ }
+
+ /**
+ * A event has occurred that can change the list of active call IDs.
+ */
+ private void onCallIdsChanged() {
+ if (getTrackedSipSessionCallIds().isEmpty() && mPendingClose != null
+ && !mPendingClose.isDone()) {
+ logi("onCallIdsChanged: no open sessions, completing any pending close events.");
+ // do not wait for timeout if pending sessions closed.
+ mPendingClose.cancel();
+ mPendingClose.run();
+ }
+ if (mPendingRegCleanup != null && !mPendingRegCleanup.isDone()) {
+ logi("onCallIdsChanged: updating pending reg cleanup task.");
+ // Recalculate the open call IDs based on the same feature tag set in the case that the
+ // call ID change has caused a change in pending reg cleanup task.
+ updatePendingRegCleanupTask(mPendingRegCleanup.featureTags);
+ }
+ }
+
+ /**
+ * If there are any pending registration clean up tasks, cancel them and clean up consumers.
+ */
+ private void cancelPendingRegCleanupTask() {
+ if (mPendingRegCleanup != null && !mPendingRegCleanup.isDone()) {
+ logi("cancelPendingRegCleanupTask: cancelling...");
+ mPendingRegCleanup.cancel();
+ }
+ cleanupAndNotifyRegistrationAppliedConsumer(Collections.emptySet());
+ }
+
+ /**
+ * Update the pending registration change clean up task based on the new set of restricted
+ * feature tags generated from the deregistering/deregistered feature tags.
+ *
+ * <p>
+ * This set of restricted tags will generate a set of call IDs associated to dialogs that
+ * are active and associated with the restricted tags. If there is no pending cleanup task, it
+ * will create a new one. If there was already a pending reg cleanup task, it will compare them
+ * and create a new one and cancel the old one if the new set of call ids is different from the
+ * old one.
+ */
+ private void updatePendingRegCleanupTask(Set<String> restrictedTags) {
+ Set<String> pendingCallIds = mSipSessionTracker.getCallIdsAssociatedWithFeatureTag(
+ restrictedTags);
+ if (pendingCallIds.isEmpty()) {
+ if (mPendingRegCleanup != null && !mPendingRegCleanup.isDone()) {
+ logi("updatePendingRegCleanupTask: no remaining call ids, finish cleanup task "
+ + "now.");
+ mPendingRegCleanup.cancel();
+ mPendingRegCleanup.run();
+ } else {
+ if (mRegistrationAppliedConsumer != null) {
+ logi("updatePendingRegCleanupTask: notify no pending call ids.");
+ cleanupAndNotifyRegistrationAppliedConsumer(Collections.emptySet());
+ }
+ }
+ return;
+ }
+ if (mPendingRegCleanup != null && !mPendingRegCleanup.isDone()) {
+ if (mPendingRegCleanup.pendingCallIds.equals(pendingCallIds)) {
+ logi("updatePendingRegCleanupTask: pending reg change has same set of pending call"
+ + " IDs, so keeping pending task");
+ return;
+ }
+ logi("updatePendingRegCleanupTask: cancelling, call ids have changed.");
+ mPendingRegCleanup.cancel();
+ }
+ mPendingRegCleanup = new PendingRegCleanupTask(restrictedTags, pendingCallIds) {
+ @Override
+ public void run() {
+ cleanupAndNotifyRegistrationAppliedConsumer(pendingCallIds);
+ }
+ };
+ logi("updatePendingRegCleanupTask: scheduling for call ids: " + pendingCallIds);
+ mPendingRegCleanup.scheduleDelayed(mExecutor, PENDING_REGISTRATION_CHANGE_TIMEOUT_MS);
+ }
+
+ /**
+ * Notify the pending registration applied consumer of the call ids that need to be cleaned up.
+ */
+ private void cleanupAndNotifyRegistrationAppliedConsumer(Set<String> pendingCallIds) {
+ if (mRegistrationAppliedConsumer != null) {
+ mRegistrationAppliedConsumer.accept(pendingCallIds);
+ mRegistrationAppliedConsumer = null;
+ }
+ }
+
+ /**
+ * Cancel any pending timeout to close pending sessions and send the provided call IDs to any
+ * pending closing complete consumer.
+ * @return {@code true} if a consumer was notified, {@code false} if there were no consumers.
+ */
+ private boolean cancelClosingTimeoutAndSendComplete(Set<String> openCallIds) {
+ if (mPendingClose != null && !mPendingClose.isDone()) {
+ logi("completing pending close consumer");
+ mPendingClose.cancel();
+ }
+ // Complete the pending consumer with no open sessions.
+ if (mClosingCompleteConsumer != null) {
+ mClosingCompleteConsumer.accept(openCallIds);
+ mClosingCompleteConsumer = null;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Close and clear all stateful trackers and validators.
+ */
+ private void closeSessionsInternal(int closedReason) {
+ cancelPendingRegCleanupTask();
+ mOutgoingTransportStateValidator.close(closedReason);
+ mIncomingTransportStateValidator.close(closedReason);
+ mSipSessionTracker.clearAllSessions();
+ }
+
+ private Set<String> getTrackedSipSessionCallIds() {
+ return mSipSessionTracker.getTrackedDialogs().stream().map(SipDialog::getCallId)
+ .collect(Collectors.toSet());
+ }
+
+ private void logi(String log) {
+ Log.i(SipTransportController.LOG_TAG, LOG_TAG + "[" + mSubId + "] " + log);
+ mLocalLog.log("[I] " + log);
+ }
+
+ private void logw(String log) {
+ Log.w(SipTransportController.LOG_TAG, LOG_TAG + "[" + mSubId + "] " + log);
+ mLocalLog.log("[W] " + log);
+ }
+}
diff --git a/src/com/android/services/telephony/rcs/UceControllerManager.java b/src/com/android/services/telephony/rcs/UceControllerManager.java
new file mode 100644
index 0000000..995d685
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/UceControllerManager.java
@@ -0,0 +1,484 @@
+/*
+ * Copyright (C) 2020 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.services.telephony.rcs;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.Uri;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.RcsContactUceCapability;
+import android.telephony.ims.RcsUceAdapter;
+import android.telephony.ims.RcsUceAdapter.PublishState;
+import android.telephony.ims.aidl.IRcsUceControllerCallback;
+import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import com.android.ims.RcsFeatureManager;
+import com.android.ims.rcs.uce.UceController;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * Responsible for managing the creation and destruction of UceController. It also received the
+ * requests from {@link com.android.phone.ImsRcsController} and pass these requests to
+ * {@link UceController}
+ */
+public class UceControllerManager implements RcsFeatureController.Feature {
+
+ private static final String LOG_TAG = "UceControllerManager";
+
+ private final int mSlotId;
+ private final Context mContext;
+ private final ExecutorService mExecutorService;
+
+ private volatile @Nullable UceController mUceController;
+ private volatile @Nullable RcsFeatureManager mRcsFeatureManager;
+
+ public UceControllerManager(Context context, int slotId, int subId) {
+ Log.d(LOG_TAG, "create: slotId=" + slotId + ", subId=" + subId);
+ mSlotId = slotId;
+ mContext = context;
+ mExecutorService = Executors.newSingleThreadExecutor();
+ initUceController(subId);
+ }
+
+ /**
+ * Constructor to inject dependencies for testing.
+ */
+ @VisibleForTesting
+ public UceControllerManager(Context context, int slotId, ExecutorService executor,
+ UceController uceController) {
+ mSlotId = slotId;
+ mContext = context;
+ mExecutorService = executor;
+ mUceController = uceController;
+ }
+
+ @Override
+ public void onRcsConnected(RcsFeatureManager manager) {
+ mExecutorService.submit(() -> {
+ mRcsFeatureManager = manager;
+ if (mUceController != null) {
+ mUceController.onRcsConnected(manager);
+ } else {
+ Log.d(LOG_TAG, "onRcsConnected: UceController is null");
+ }
+ });
+ }
+
+ @Override
+ public void onRcsDisconnected() {
+ mExecutorService.submit(() -> {
+ mRcsFeatureManager = null;
+ if (mUceController != null) {
+ mUceController.onRcsDisconnected();
+ } else {
+ Log.d(LOG_TAG, "onRcsDisconnected: UceController is null");
+ }
+ });
+ }
+
+ @Override
+ public void onDestroy() {
+ mExecutorService.submit(() -> {
+ Log.d(LOG_TAG, "onDestroy");
+ if (mUceController != null) {
+ mUceController.onDestroy();
+ }
+ });
+ // When the shutdown is called, it will refuse any new tasks and let existing tasks finish.
+ mExecutorService.shutdown();
+ }
+
+ /**
+ * This method will be called when the subscription ID associated with the slot has
+ * changed.
+ */
+ @Override
+ public void onAssociatedSubscriptionUpdated(int newSubId) {
+ mExecutorService.submit(() -> {
+ Log.i(LOG_TAG, "onAssociatedSubscriptionUpdated: slotId=" + mSlotId
+ + ", newSubId=" + newSubId);
+
+ // Check and create the UceController with the new updated subscription ID.
+ initUceController(newSubId);
+
+ // The RCS should be connected when the mRcsFeatureManager is not null. Set it to the
+ // new UceController instance.
+ if (mUceController != null && mRcsFeatureManager != null) {
+ mUceController.onRcsConnected(mRcsFeatureManager);
+ }
+ });
+ }
+
+ /**
+ * This method will be called when the carrier config of the subscription associated with this
+ * manager has changed.
+ */
+ @Override
+ public void onCarrierConfigChanged() {
+ mExecutorService.submit(() -> {
+ Log.i(LOG_TAG, "onCarrierConfigChanged");
+ if (mUceController != null) {
+ mUceController.onCarrierConfigChanged();
+ } else {
+ Log.d(LOG_TAG, "onCarrierConfigChanged: UceController is null");
+ }
+ });
+ }
+
+ /**
+ * Request the capabilities for contacts.
+ *
+ * @param contactNumbers A list of numbers that the capabilities are being requested for.
+ * @param c A callback for when the request for capabilities completes.
+ * @throws ImsException if the ImsService connected to this controller is currently down.
+ */
+ public void requestCapabilities(List<Uri> contactNumbers, IRcsUceControllerCallback c)
+ throws ImsException {
+ Future future = mExecutorService.submit(() -> {
+ checkUceControllerState();
+ mUceController.requestCapabilities(contactNumbers, c);
+ return true;
+ });
+
+ try {
+ future.get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.w(LOG_TAG, "requestCapabilities: " + e);
+ Throwable cause = e.getCause();
+ if (cause instanceof ImsException) {
+ throw (ImsException) cause;
+ }
+ }
+ }
+
+ /**
+ * Request the capabilities for the given contact.
+ * @param contactNumber The contact of the capabilities are being requested for.
+ * @param c A callback for when the request for capabilities completes.
+ * @throws ImsException if the ImsService connected to this controller is currently down.
+ */
+ public void requestNetworkAvailability(Uri contactNumber, IRcsUceControllerCallback c)
+ throws ImsException {
+ Future future = mExecutorService.submit(() -> {
+ checkUceControllerState();
+ mUceController.requestAvailability(contactNumber, c);
+ return true;
+ });
+
+ try {
+ future.get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.w(LOG_TAG, "requestNetworkAvailability exception: " + e);
+ Throwable cause = e.getCause();
+ if (cause instanceof ImsException) {
+ throw (ImsException) cause;
+ }
+ }
+ }
+
+ /**
+ * Get the UCE publish state.
+ *
+ * @throws ImsException if the ImsService connected to this controller is currently down.
+ */
+ public @PublishState int getUcePublishState() throws ImsException {
+ Future<Integer> future = mExecutorService.submit(() -> {
+ checkUceControllerState();
+ return mUceController.getUcePublishState();
+ });
+
+ try {
+ return future.get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.w(LOG_TAG, "getUcePublishState exception: " + e);
+ Throwable cause = e.getCause();
+ if (cause instanceof ImsException) {
+ throw (ImsException) cause;
+ }
+ return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR;
+ }
+ }
+
+ /**
+ * Add new feature tags to the Set used to calculate the capabilities in PUBLISH.
+ */
+ public RcsContactUceCapability addUceRegistrationOverride(
+ Set<String> featureTags) throws ImsException {
+ Future<RcsContactUceCapability> future = mExecutorService.submit(() -> {
+ checkUceControllerState();
+ return mUceController.addRegistrationOverrideCapabilities(featureTags);
+ });
+
+ try {
+ return future.get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.w(LOG_TAG, "addUceRegistrationOverride exception: " + e);
+ Throwable cause = e.getCause();
+ if (cause instanceof ImsException) {
+ throw (ImsException) cause;
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Remove existing feature tags to the Set used to calculate the capabilities in PUBLISH.
+ */
+ public RcsContactUceCapability removeUceRegistrationOverride(
+ Set<String> featureTags) throws ImsException {
+ Future<RcsContactUceCapability> future = mExecutorService.submit(() -> {
+ checkUceControllerState();
+ return mUceController.removeRegistrationOverrideCapabilities(featureTags);
+ });
+
+ try {
+ return future.get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.w(LOG_TAG, "removeUceRegistrationOverride exception: " + e);
+ Throwable cause = e.getCause();
+ if (cause instanceof ImsException) {
+ throw (ImsException) cause;
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Clear all overrides in the Set used to calculate the capabilities in PUBLISH.
+ */
+ public RcsContactUceCapability clearUceRegistrationOverride() throws ImsException {
+ Future<RcsContactUceCapability> future = mExecutorService.submit(() -> {
+ checkUceControllerState();
+ return mUceController.clearRegistrationOverrideCapabilities();
+ });
+
+ try {
+ return future.get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.w(LOG_TAG, "clearUceRegistrationOverride exception: " + e);
+ Throwable cause = e.getCause();
+ if (cause instanceof ImsException) {
+ throw (ImsException) cause;
+ }
+ return null;
+ }
+ }
+
+ /**
+ * @return current RcsContactUceCapability instance that will be used for PUBLISH.
+ */
+ public RcsContactUceCapability getLatestRcsContactUceCapability() throws ImsException {
+ Future<RcsContactUceCapability> future = mExecutorService.submit(() -> {
+ checkUceControllerState();
+ return mUceController.getLatestRcsContactUceCapability();
+ });
+
+ try {
+ return future.get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.w(LOG_TAG, "getLatestRcsContactUceCapability exception: " + e);
+ Throwable cause = e.getCause();
+ if (cause instanceof ImsException) {
+ throw (ImsException) cause;
+ }
+ return null;
+ }
+ }
+
+ /**
+ *
+ * @return The last PIDF XML sent to the IMS stack to be published.
+ */
+ public String getLastPidfXml() throws ImsException {
+ Future<String> future = mExecutorService.submit(() -> {
+ checkUceControllerState();
+ return mUceController.getLastPidfXml();
+ });
+
+ try {
+ return future.get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.w(LOG_TAG, "getLastPidfXml exception: " + e);
+ Throwable cause = e.getCause();
+ if (cause instanceof ImsException) {
+ throw (ImsException) cause;
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Remove UCE requests cannot be sent to the network status.
+ * @return true if this command is successful.
+ */
+ public boolean removeUceRequestDisallowedStatus() throws ImsException {
+ Future<Boolean> future = mExecutorService.submit(() -> {
+ if (mUceController == null) {
+ throw new ImsException("UCE controller is null",
+ ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+ }
+ mUceController.removeRequestDisallowedStatus();
+ return true;
+ });
+
+ try {
+ return future.get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.w(LOG_TAG, "removeUceRequestDisallowedStatus exception: " + e);
+ Throwable cause = e.getCause();
+ if (cause instanceof ImsException) {
+ throw (ImsException) cause;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Set the timeout for contact capabilities request.
+ * @param timeoutAfterMs How long when the capabilities request will time up.
+ * @return true if this command is successful.
+ */
+ public boolean setCapabilitiesRequestTimeout(long timeoutAfterMs) throws ImsException {
+ Future<Boolean> future = mExecutorService.submit(() -> {
+ if (mUceController == null) {
+ throw new ImsException("UCE controller is null",
+ ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+ }
+ mUceController.setCapabilitiesRequestTimeout(timeoutAfterMs);
+ return true;
+ });
+
+ try {
+ return future.get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.w(LOG_TAG, "setCapabilitiesRequestTimeout exception: " + e);
+ Throwable cause = e.getCause();
+ if (cause instanceof ImsException) {
+ throw (ImsException) cause;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Register the Publish state changed callback.
+ *
+ * @throws ImsException if the ImsService connected to this controller is currently down.
+ */
+ public void registerPublishStateCallback(IRcsUcePublishStateCallback c) throws ImsException {
+ Future future = mExecutorService.submit(() -> {
+ checkUceControllerState();
+ mUceController.registerPublishStateCallback(c);
+ return true;
+ });
+
+ try {
+ future.get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.w(LOG_TAG, "registerPublishStateCallback exception: " + e);
+ Throwable cause = e.getCause();
+ if (cause instanceof ImsException) {
+ throw (ImsException) cause;
+ }
+ }
+ }
+
+ /**
+ * Unregister the existing publish state changed callback.
+ */
+ public void unregisterPublishStateCallback(IRcsUcePublishStateCallback c) {
+ Future future = mExecutorService.submit(() -> {
+ if (checkUceControllerState()) {
+ mUceController.unregisterPublishStateCallback(c);
+ }
+ return true;
+ });
+
+ try {
+ future.get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.w(LOG_TAG, "unregisterPublishStateCallback exception: " + e);
+ }
+ }
+
+ /**
+ * Initialize the UceController instance associated with the given subscription ID.
+ * The existing UceController will be destroyed if the original subscription ID is different
+ * from the new subscription ID.
+ * If the new subscription ID is invalid, the UceController instance will be null.
+ */
+ private void initUceController(int newSubId) {
+ Log.d(LOG_TAG, "initUceController: newSubId=" + newSubId + ", current UceController subId="
+ + ((mUceController == null) ? "null" : mUceController.getSubId()));
+ if (mUceController == null) {
+ // Create new UceController only when the subscription ID is valid.
+ if (SubscriptionManager.isValidSubscriptionId(newSubId)) {
+ mUceController = new UceController(mContext, newSubId);
+ }
+ } else if (mUceController.getSubId() != newSubId) {
+ // The subscription ID is updated. Remove the old UceController instance.
+ mUceController.onDestroy();
+ mUceController = null;
+ // Create new UceController only when the subscription ID is valid.
+ if (SubscriptionManager.isValidSubscriptionId(newSubId)) {
+ mUceController = new UceController(mContext, newSubId);
+ }
+ }
+ }
+
+ private boolean checkUceControllerState() throws ImsException {
+ if (mUceController == null || mUceController.isUnavailable()) {
+ throw new ImsException("UCE controller is unavailable",
+ ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+ }
+ return true;
+ }
+
+ /**
+ * Get the UceController instance.
+ * <p>
+ * Used for testing ONLY.
+ */
+ @VisibleForTesting
+ public UceController getUceController() {
+ return mUceController;
+ }
+
+ @Override
+ public void dump(PrintWriter printWriter) {
+ IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
+ pw.println("UceControllerManager" + "[" + mSlotId + "]:");
+ pw.increaseIndent();
+ if (mUceController != null) {
+ mUceController.dump(pw);
+ } else {
+ pw.println("UceController is null.");
+ }
+ pw.decreaseIndent();
+ }
+}
diff --git a/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java b/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
deleted file mode 100644
index ac8f9bf..0000000
--- a/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2020 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.services.telephony.rcs;
-
-import android.content.Context;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.telephony.ims.RcsContactUceCapability;
-import android.telephony.ims.RcsUceAdapter;
-import android.telephony.ims.aidl.IRcsUceControllerCallback;
-import android.util.Log;
-
-import com.android.ims.RcsFeatureManager;
-import com.android.ims.ResultCode;
-import com.android.phone.R;
-import com.android.service.ims.presence.ContactCapabilityResponse;
-import com.android.service.ims.presence.PresenceBase;
-import com.android.service.ims.presence.PresencePublication;
-import com.android.service.ims.presence.PresencePublisher;
-import com.android.service.ims.presence.PresenceSubscriber;
-import com.android.service.ims.presence.SubscribePublisher;
-
-import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
-
-/**
- * Implements User Capability Exchange using Presence.
- */
-public class UserCapabilityExchangeImpl implements RcsFeatureController.Feature, SubscribePublisher,
- PresencePublisher {
-
- private static final String LOG_TAG = "RcsUceImpl";
-
- private int mSlotId;
- private int mSubId;
-
- private final PresencePublication mPresencePublication;
- private final PresenceSubscriber mPresenceSubscriber;
-
- private final ConcurrentHashMap<Integer, IRcsUceControllerCallback> mPendingCapabilityRequests =
- new ConcurrentHashMap<>();
-
- UserCapabilityExchangeImpl(Context context, int slotId, int subId) {
- mSlotId = slotId;
- mSubId = subId;
- logi("created");
-
- String[] volteError = context.getResources().getStringArray(
- R.array.config_volte_provision_error_on_publish_response);
- String[] rcsError = context.getResources().getStringArray(
- R.array.config_rcs_provision_error_on_publish_response);
-
- // Initialize PresencePublication
- mPresencePublication = new PresencePublication(null /*PresencePublisher*/, context,
- volteError, rcsError);
- // Initialize PresenceSubscriber
- mPresenceSubscriber = new PresenceSubscriber(null /*SubscribePublisher*/, context,
- volteError, rcsError);
-
- onAssociatedSubscriptionUpdated(mSubId);
- }
-
-
- // Runs on main thread.
- @Override
- public void onRcsConnected(RcsFeatureManager rcsFeatureManager) {
- logi("onRcsConnected");
- mPresencePublication.updatePresencePublisher(this);
- mPresenceSubscriber.updatePresenceSubscriber(this);
- }
-
- // Runs on main thread.
- @Override
- public void onRcsDisconnected() {
- logi("onRcsDisconnected");
- mPresencePublication.removePresencePublisher();
- mPresenceSubscriber.removePresenceSubscriber();
- }
-
- // Runs on main thread.
- @Override
- public void onAssociatedSubscriptionUpdated(int subId) {
- mPresencePublication.handleAssociatedSubscriptionChanged(subId);
- mPresenceSubscriber.handleAssociatedSubscriptionChanged(subId);
- }
-
- /**
- * Should be called before destroying this instance.
- * This instance is not usable after this method is called.
- */
- // Called on main thread.
- public void onDestroy() {
- onRcsDisconnected();
- }
-
- /**
- * @return the UCE Publish state.
- */
- // May happen on a Binder thread, PresencePublication locks to get result.
- public int getUcePublishState() {
- int publishState = mPresencePublication.getPublishState();
- return toUcePublishState(publishState);
- }
-
- /**
- * Perform a capabilities request and call {@link IRcsUceControllerCallback} with the result.
- */
- // May happen on a Binder thread, PresenceSubscriber locks when requesting Capabilities.
- public void requestCapabilities(List<Uri> contactNumbers, IRcsUceControllerCallback c) {
- List<String> numbers = contactNumbers.stream()
- .map(UserCapabilityExchangeImpl::getNumberFromUri).collect(Collectors.toList());
- int taskId = mPresenceSubscriber.requestCapability(numbers,
- new ContactCapabilityResponse() {
- @Override
- public void onSuccess(int reqId) {
- logi("onSuccess called for reqId:" + reqId);
- }
-
- @Override
- public void onError(int reqId, int resultCode) {
- IRcsUceControllerCallback c = mPendingCapabilityRequests.remove(reqId);
- try {
- if (c != null) {
- c.onError(toUceError(resultCode));
- } else {
- logw("onError called for unknown reqId:" + reqId);
- }
- } catch (RemoteException e) {
- logi("Calling back to dead service");
- }
- }
-
- @Override
- public void onFinish(int reqId) {
- logi("onFinish called for reqId:" + reqId);
- }
-
- @Override
- public void onTimeout(int reqId) {
- IRcsUceControllerCallback c = mPendingCapabilityRequests.remove(reqId);
- try {
- if (c != null) {
- c.onError(RcsUceAdapter.ERROR_REQUEST_TIMEOUT);
- } else {
- logw("onTimeout called for unknown reqId:" + reqId);
- }
- } catch (RemoteException e) {
- logi("Calling back to dead service");
- }
- }
-
- @Override
- public void onCapabilitiesUpdated(int reqId,
- List<RcsContactUceCapability> contactCapabilities,
- boolean updateLastTimestamp) {
- IRcsUceControllerCallback c = mPendingCapabilityRequests.remove(reqId);
- try {
- if (c != null) {
- c.onCapabilitiesReceived(contactCapabilities);
- } else {
- logw("onCapabilitiesUpdated, unknown reqId:" + reqId);
- }
- } catch (RemoteException e) {
- logw("onCapabilitiesUpdated on dead service");
- }
- }
- });
- if (taskId < 0) {
- try {
- c.onError(toUceError(taskId));
- return;
- } catch (RemoteException e) {
- logi("Calling back to dead service");
- }
- }
- mPendingCapabilityRequests.put(taskId, c);
- }
-
- @Override
- public int getPublisherState() {
- return 0;
- }
-
- @Override
- public int requestPublication(RcsContactUceCapability capabilities, String contactUri,
- int taskId) {
- return 0;
- }
-
- @Override
- public int requestCapability(String[] formatedContacts, int taskId) {
- return 0;
- }
-
- @Override
- public int requestAvailability(String formattedContact, int taskId) {
- return 0;
- }
-
- @Override
- public int getStackStatusForCapabilityRequest() {
- return 0;
- }
-
- @Override
- public void updatePublisherState(int publishState) {
-
- }
-
- private static String getNumberFromUri(Uri uri) {
- String number = uri.getSchemeSpecificPart();
- String[] numberParts = number.split("[@;:]");
-
- if (numberParts.length == 0) {
- return null;
- }
- return numberParts[0];
- }
-
- private static int toUcePublishState(int publishState) {
- switch (publishState) {
- case PresenceBase.PUBLISH_STATE_200_OK:
- return RcsUceAdapter.PUBLISH_STATE_OK;
- case PresenceBase.PUBLISH_STATE_NOT_PUBLISHED:
- return RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED;
- case PresenceBase.PUBLISH_STATE_VOLTE_PROVISION_ERROR:
- return RcsUceAdapter.PUBLISH_STATE_VOLTE_PROVISION_ERROR;
- case PresenceBase.PUBLISH_STATE_RCS_PROVISION_ERROR:
- return RcsUceAdapter.PUBLISH_STATE_RCS_PROVISION_ERROR;
- case PresenceBase.PUBLISH_STATE_REQUEST_TIMEOUT:
- return RcsUceAdapter.PUBLISH_STATE_REQUEST_TIMEOUT;
- case PresenceBase.PUBLISH_STATE_OTHER_ERROR:
- return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR;
- default:
- return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR;
- }
- }
-
- private static int toUceError(int resultCode) {
- switch (resultCode) {
- case ResultCode.SUBSCRIBE_NOT_REGISTERED:
- return RcsUceAdapter.ERROR_NOT_REGISTERED;
- case ResultCode.SUBSCRIBE_REQUEST_TIMEOUT:
- return RcsUceAdapter.ERROR_REQUEST_TIMEOUT;
- case ResultCode.SUBSCRIBE_FORBIDDEN:
- return RcsUceAdapter.ERROR_FORBIDDEN;
- case ResultCode.SUBSCRIBE_NOT_FOUND:
- return RcsUceAdapter.ERROR_NOT_FOUND;
- case ResultCode.SUBSCRIBE_TOO_LARGE:
- return RcsUceAdapter.ERROR_REQUEST_TOO_LARGE;
- case ResultCode.SUBSCRIBE_INSUFFICIENT_MEMORY:
- return RcsUceAdapter.ERROR_INSUFFICIENT_MEMORY;
- case ResultCode.SUBSCRIBE_LOST_NETWORK:
- return RcsUceAdapter.ERROR_LOST_NETWORK;
- case ResultCode.SUBSCRIBE_ALREADY_IN_QUEUE:
- return RcsUceAdapter.ERROR_ALREADY_IN_QUEUE;
- default:
- return RcsUceAdapter.ERROR_GENERIC_FAILURE;
- }
- }
-
- private void logi(String log) {
- Log.i(LOG_TAG, getLogPrefix().append(log).toString());
- }
-
- private void logw(String log) {
- Log.w(LOG_TAG, getLogPrefix().append(log).toString());
- }
-
- private StringBuilder getLogPrefix() {
- StringBuilder builder = new StringBuilder("[");
- builder.append(mSlotId);
- builder.append("->");
- builder.append(mSubId);
- builder.append("] ");
- return builder;
- }
-}
diff --git a/src/com/android/services/telephony/rcs/validator/IncomingTransportStateValidator.java b/src/com/android/services/telephony/rcs/validator/IncomingTransportStateValidator.java
new file mode 100644
index 0000000..24ab45e
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/validator/IncomingTransportStateValidator.java
@@ -0,0 +1,81 @@
+/*
+ * 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.services.telephony.rcs.validator;
+
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.SipMessage;
+import android.util.ArrayMap;
+
+/**
+ * Tracks the incoming SIP message transport state from the ImsService to the remote IMS
+ * application. Validates incoming SIP messages based on this state.
+ */
+public class IncomingTransportStateValidator implements SipMessageValidator {
+
+ /**
+ * The message transport is closed, meaning there can be no more incoming messages
+ */
+ private static final int STATE_CLOSED = 0;
+
+ /**
+ * The message transport is open and incoming traffic is not restricted.
+ */
+ private static final int STATE_OPEN = 1;
+
+ private static final ArrayMap<Integer, String> ENUM_TO_STRING_MAP = new ArrayMap<>(2);
+ static {
+ ENUM_TO_STRING_MAP.append(STATE_CLOSED, "CLOSED");
+ ENUM_TO_STRING_MAP.append(STATE_OPEN, "OPEN");
+ }
+
+ private int mState = STATE_CLOSED;
+ private int mReason = SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED;
+
+ /**
+ * The SIP message transport is open and will successfully validate SIP messages.
+ */
+ public void open() {
+ mState = STATE_OPEN;
+ mReason = -1;
+ }
+
+ /**
+ * The SIP message transport is closed for incoming SIP messages.
+ * @param reason The error reason sent in response to any incoming SIP messages requests.
+ */
+ public void close(int reason) {
+ mState = STATE_CLOSED;
+ mReason = reason;
+ }
+
+ @Override
+ public ValidationResult validate(SipMessage message) {
+ if (mState != STATE_OPEN) {
+ return new ValidationResult(mReason,
+ "incoming transport closed");
+ }
+ return ValidationResult.SUCCESS;
+ }
+
+ @Override
+ public String toString() {
+ return "Incoming Transport State: " + ENUM_TO_STRING_MAP.getOrDefault(mState,
+ String.valueOf(mState)) + ", reason: "
+ + SipDelegateManager.MESSAGE_FAILURE_REASON_STRING_MAP.getOrDefault(mReason,
+ String.valueOf(mReason));
+ }
+}
diff --git a/src/com/android/services/telephony/rcs/validator/MalformedSipMessageValidator.java b/src/com/android/services/telephony/rcs/validator/MalformedSipMessageValidator.java
new file mode 100644
index 0000000..a76ac3e
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/validator/MalformedSipMessageValidator.java
@@ -0,0 +1,42 @@
+/*
+ * 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.services.telephony.rcs.validator;
+
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.SipMessage;
+
+import com.android.internal.telephony.SipMessageParsingUtils;
+
+/**
+ * Validates that the SipMessage is not malformed before sending the message to the vendor
+ * ImsService by ensuring:
+ * <ul>The SipMessage is a valid SIP request or SIP response.</ul>
+ */
+public class MalformedSipMessageValidator implements SipMessageValidator {
+
+ @Override
+ public ValidationResult validate(SipMessage message) {
+ // Verify the request and response start lines are valid.
+ if (!SipMessageParsingUtils.isSipRequest(message.getStartLine())
+ && !SipMessageParsingUtils.isSipResponse(message.getStartLine())) {
+ return new ValidationResult(
+ SipDelegateManager.MESSAGE_FAILURE_REASON_INVALID_START_LINE,
+ "malformed start line: " + message.getStartLine());
+ }
+ return ValidationResult.SUCCESS;
+ }
+}
diff --git a/src/com/android/services/telephony/rcs/validator/OutgoingTransportStateValidator.java b/src/com/android/services/telephony/rcs/validator/OutgoingTransportStateValidator.java
new file mode 100644
index 0000000..72d22f8
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/validator/OutgoingTransportStateValidator.java
@@ -0,0 +1,235 @@
+/*
+ * 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.services.telephony.rcs.validator;
+
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.SipMessage;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.telephony.SipMessageParsingUtils;
+import com.android.services.telephony.rcs.SipDialog;
+import com.android.services.telephony.rcs.SipSessionTracker;
+import com.android.services.telephony.rcs.SipTransportController;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Tracks the state of the outgoing SIP message transport from the remote IMS application to the
+ * ImsService. Used to validate outgoing SIP messages based off of this state.
+ */
+public class OutgoingTransportStateValidator implements SipMessageValidator {
+
+ /**
+ * The message transport is closed, meaning there can be no more outgoing messages
+ */
+ private static final int STATE_CLOSED = 0;
+
+ /**
+ * The message transport is restricted to only in-dialog outgoing traffic
+ */
+ private static final int STATE_RESTRICTED = 1;
+
+ /**
+ * The message transport is open and outgoing traffic is not restricted.
+ */
+ private static final int STATE_OPEN = 2;
+
+ private static final ArrayMap<Integer, String> ENUM_TO_STRING_MAP = new ArrayMap<>(3);
+ static {
+ ENUM_TO_STRING_MAP.append(STATE_CLOSED, "CLOSED");
+ ENUM_TO_STRING_MAP.append(STATE_RESTRICTED, "RESTRICTED");
+ ENUM_TO_STRING_MAP.append(STATE_OPEN, "OPEN");
+ }
+
+ private final SipSessionTracker mSipSessionTracker;
+ private int mState = STATE_CLOSED;
+ private int mReason = SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED;
+ private Set<String> mAllowedTags = Collections.emptySet();
+ private Set<String> mDeniedTags = Collections.emptySet();
+ private Set<String> mRestrictedFeatureTags;
+
+ public OutgoingTransportStateValidator(SipSessionTracker sessionTracker) {
+ mSipSessionTracker = sessionTracker;
+ }
+
+ /**
+ * The SIP message transport is open and will successfully validate both in and out of dialog
+ * SIP messages.
+ */
+ public void open(Set<String> allowedFeatureTags, Set<String> deniedFeatureTags) {
+ mState = STATE_OPEN;
+ mReason = -1;
+ // This is for validation, so try to reduce matching errors due to upper/lower case.
+ mAllowedTags = allowedFeatureTags.stream().map(String::trim).map(String::toLowerCase)
+ .collect(Collectors.toSet());
+ mDeniedTags = deniedFeatureTags.stream().map(String::trim).map(String::toLowerCase)
+ .collect(Collectors.toSet());
+ mRestrictedFeatureTags = null;
+ }
+
+ /**
+ * Restrict the starting of dialogs for specific feature tags, excluding requests associated
+ * with ongoing sessions.
+ * @param restrictedFeatureTags The feature tags that are deregistering or deregistered and can
+ * not have new dialogs associated with them.
+ */
+ public void restrictFeatureTags(Set<String> restrictedFeatureTags) {
+ // This is for validation, so try to reduce matching errors due to upper/lower case.
+ mRestrictedFeatureTags = restrictedFeatureTags.stream().map(String::trim)
+ .map(String::toLowerCase).collect(Collectors.toSet());
+ }
+
+ /**
+ * The SIP message transport is restricted and only allows in-dialog outgoing messages.
+ * @param reason The reason that will be returned to outgoing out-of-dialog SIP messages that
+ * are denied.
+ */
+ public void restrict(int reason) {
+ mState = STATE_RESTRICTED;
+ mReason = reason;
+ }
+
+ /**
+ * The SIP message transport is closed for outgoing SIP messages.
+ * @param reason The error reason sent in response to any outgoing SIP messages requests.
+ */
+ public void close(int reason) {
+ mState = STATE_CLOSED;
+ mReason = reason;
+ mAllowedTags = Collections.emptySet();
+ }
+
+ @Override
+ public ValidationResult validate(SipMessage message) {
+ switch (mState) {
+ case STATE_CLOSED:
+ return new ValidationResult(mReason, "outgoing transport closed.");
+ case STATE_RESTRICTED:
+ return verifyRestrictedMessage(message);
+ case STATE_OPEN:
+ return verifyOpenMessage(message);
+ default:
+ Log.w(SipTransportController.LOG_TAG, "OutgoingTSV - warning, unexpected state");
+ return ValidationResult.SUCCESS;
+ }
+ }
+
+ public Set<String> getAllowedCallIds() {
+ return Stream.concat(mSipSessionTracker.getEarlyDialogs().stream(),
+ mSipSessionTracker.getConfirmedDialogs().stream()).map(SipDialog::getCallId)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public String toString() {
+ return "Outgoing Transport State: " + ENUM_TO_STRING_MAP.getOrDefault(mState,
+ String.valueOf(mState)) + ", reason: "
+ + SipDelegateManager.MESSAGE_FAILURE_REASON_STRING_MAP.getOrDefault(mReason,
+ String.valueOf(mReason)) + ", allowed tags: " + mAllowedTags + ", restricted tags: "
+ + mRestrictedFeatureTags + ", denied tags: " + mDeniedTags;
+ }
+
+ private ValidationResult verifyOpenMessage(SipMessage m) {
+ // No need to validate responses to requests.
+ if (!SipMessageParsingUtils.isSipRequest(m.getStartLine())) {
+ return ValidationResult.SUCCESS;
+ }
+ if (mRestrictedFeatureTags == null) {
+ return new ValidationResult(SipDelegateManager.MESSAGE_FAILURE_REASON_NOT_REGISTERED,
+ "no reg state from vendor");
+ }
+ String[] segments = SipMessageParsingUtils.splitStartLineAndVerify(m.getStartLine());
+ if (segments == null) {
+ return new ValidationResult(
+ SipDelegateManager.MESSAGE_FAILURE_REASON_INVALID_START_LINE,
+ "couldn't parse start line: " + m.getStartLine());
+ }
+ // Only need to validate requests that start dialogs.
+ boolean startsDialog = Arrays.stream(SipSessionTracker.SIP_REQUEST_DIALOG_START_METHODS)
+ .anyMatch(req -> req.equals(segments[0].trim().toLowerCase()));
+ // If part of an existing dialog, then no need to validate.
+ boolean needsFeatureValidation = startsDialog && !getAllowedCallIds()
+ .contains(m.getCallIdParameter());
+ if (needsFeatureValidation) {
+ return validateMessageFeatureTag(m);
+ }
+ return ValidationResult.SUCCESS;
+ }
+
+ /**
+ * Compares the "Accept-Contact" header against the supported/denied feature tags and ensures
+ * that there are no restricted or denied tags included.
+ */
+ private ValidationResult validateMessageFeatureTag(SipMessage m) {
+ Set<String> featureTags = SipMessageParsingUtils.getAcceptContactFeatureTags(
+ m.getHeaderSection());
+ // Get rid of potential formatting issues first.
+ featureTags = featureTags.stream().map(String::toLowerCase).map(String::trim)
+ .collect(Collectors.toSet());
+ long acceptedFeatureTagCount = featureTags.stream()
+ .filter(f -> mAllowedTags.contains(f)).count();
+ long deniedFeatureTagCount = featureTags.stream()
+ .filter(f -> mDeniedTags.contains(f)).count();
+ long restrictedFeatureTagCount = featureTags.stream()
+ .filter(f -> mRestrictedFeatureTags.contains(f)).count();
+ // we should not have any feature tags that are denied/restricted and there should be at
+ // least one accepted tag
+ if (deniedFeatureTagCount > 0) {
+ return new ValidationResult(
+ SipDelegateManager.MESSAGE_FAILURE_REASON_INVALID_FEATURE_TAG,
+ "contains denied tags in Accept-Contact: " + featureTags);
+ }
+ if (restrictedFeatureTagCount > 0) {
+ return new ValidationResult(
+ SipDelegateManager.MESSAGE_FAILURE_REASON_INVALID_FEATURE_TAG,
+ "contains restricted tags in Accept-Contact: " + featureTags);
+ }
+
+ if (acceptedFeatureTagCount == 0) {
+ return new ValidationResult(
+ SipDelegateManager.MESSAGE_FAILURE_REASON_INVALID_FEATURE_TAG,
+ "No Accept-Contact feature tags are in accepted feature tag list: "
+ + featureTags);
+ }
+
+ return ValidationResult.SUCCESS;
+ }
+
+ private ValidationResult verifyRestrictedMessage(SipMessage m) {
+ // The validator is in the restricted state, so only in dialog requests and all responses
+ // are allowed.
+ if (!SipMessageParsingUtils.isSipRequest(m.getStartLine())) {
+ return ValidationResult.SUCCESS;
+ }
+ String callId = m.getCallIdParameter();
+ if (TextUtils.isEmpty(callId)) {
+ return new ValidationResult(mReason, "empty call id");
+ }
+ Set<String> mAllowedCallIds = getAllowedCallIds();
+ if (!mAllowedCallIds.contains(callId)) {
+ return new ValidationResult(mReason, "call id " + callId + " is not associated with"
+ + " any active sessions");
+ }
+ return ValidationResult.SUCCESS;
+ }
+}
diff --git a/src/com/android/services/telephony/rcs/validator/RestrictedOutgoingSipRequestValidator.java b/src/com/android/services/telephony/rcs/validator/RestrictedOutgoingSipRequestValidator.java
new file mode 100644
index 0000000..2c2632f
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/validator/RestrictedOutgoingSipRequestValidator.java
@@ -0,0 +1,58 @@
+/*
+ * 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.services.telephony.rcs.validator;
+
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.SipMessage;
+
+import com.android.internal.telephony.SipMessageParsingUtils;
+
+import java.util.Arrays;
+
+/**
+ * Validate that any outgoing SIP request message does not contain methods that are only generated
+ * internally by the ImsService implementation.
+ */
+public class RestrictedOutgoingSipRequestValidator implements SipMessageValidator {
+
+ /**
+ * These SIP requests are always handled by the ImsService and are restricted to being
+ * generated internally. Messages with these request methods should fail validation.
+ */
+ private static final String[] IMS_SERVICE_HANDLED_REQUEST_METHODS = new String[]{
+ "register", "options", "publish"};
+
+ @Override
+ public ValidationResult validate(SipMessage message) {
+ String startLine = message.getStartLine();
+ if (SipMessageParsingUtils.isSipRequest(startLine)) {
+ String[] segments = SipMessageParsingUtils.splitStartLineAndVerify(startLine);
+ if (segments == null) {
+ return new ValidationResult(
+ SipDelegateManager.MESSAGE_FAILURE_REASON_INVALID_START_LINE,
+ "malformed start line: " + message.getStartLine());
+ }
+ if (Arrays.stream(IMS_SERVICE_HANDLED_REQUEST_METHODS).anyMatch(
+ s -> segments[0].toLowerCase().contains(s))) {
+ return new ValidationResult(
+ SipDelegateManager.MESSAGE_FAILURE_REASON_INVALID_START_LINE,
+ "restricted method: " + segments[0]);
+ }
+ }
+ return ValidationResult.SUCCESS;
+ }
+}
diff --git a/src/com/android/services/telephony/rcs/validator/RestrictedOutgoingSubscribeValidator.java b/src/com/android/services/telephony/rcs/validator/RestrictedOutgoingSubscribeValidator.java
new file mode 100644
index 0000000..41074ed
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/validator/RestrictedOutgoingSubscribeValidator.java
@@ -0,0 +1,71 @@
+/*
+ * 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.services.telephony.rcs.validator;
+
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.SipMessage;
+import android.util.Pair;
+
+import com.android.internal.telephony.SipMessageParsingUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Ensure that if there is an outgoing SUBSCRIBE request, that it does not contain the "Event"
+ * header "presence"
+ */
+public class RestrictedOutgoingSubscribeValidator implements SipMessageValidator {
+
+ private static final String SUBSCRIBE_REQUEST = "subscribe";
+ private static final String SUBSCRIBE_EVENT_HEADER = "event";
+ private static final String[] RESTRICTED_EVENTS = new String[]{ "presence" };
+
+
+ @Override
+ public ValidationResult validate(SipMessage message) {
+ if (!SipMessageParsingUtils.isSipRequest(message.getStartLine())) {
+ return ValidationResult.SUCCESS;
+ }
+ String[] requestSegments = SipMessageParsingUtils.splitStartLineAndVerify(
+ message.getStartLine());
+ if (requestSegments == null) {
+ return new ValidationResult(
+ SipDelegateManager.MESSAGE_FAILURE_REASON_INVALID_START_LINE,
+ "malformed start line: " + message.getStartLine());
+ }
+ // Request-Line = Method SP Request-URI SP SIP-Version CRLF, verify Method
+ if (!requestSegments[0].equalsIgnoreCase(SUBSCRIBE_REQUEST)) {
+ return ValidationResult.SUCCESS;
+ }
+
+ List<Pair<String, String>> eventHeaders = SipMessageParsingUtils.parseHeaders(
+ message.getHeaderSection(), true /*stopAtFirstMatch*/, SUBSCRIBE_EVENT_HEADER);
+ if (eventHeaders.size() == 0) {
+ return ValidationResult.SUCCESS;
+ }
+ boolean isRestricted = eventHeaders.stream().map(e -> e.second)
+ .anyMatch(e -> Arrays.asList(RESTRICTED_EVENTS).contains(e.trim().toLowerCase()));
+
+ return isRestricted ? new ValidationResult(
+ SipDelegateManager.MESSAGE_FAILURE_REASON_INVALID_HEADER_FIELDS,
+ "matched a restricted header field: " + eventHeaders.stream().map(e -> e.second)
+ .collect(Collectors.toSet())) :
+ ValidationResult.SUCCESS;
+ }
+}
diff --git a/src/com/android/services/telephony/rcs/validator/SipMessageValidator.java b/src/com/android/services/telephony/rcs/validator/SipMessageValidator.java
new file mode 100644
index 0000000..6bbfbf4
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/validator/SipMessageValidator.java
@@ -0,0 +1,47 @@
+/*
+ * 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.services.telephony.rcs.validator;
+
+import android.telephony.ims.SipMessage;
+
+/**
+ * Validates a SipMessage and returns the result via an instance of {@link ValidationResult}.
+ */
+public interface SipMessageValidator {
+ /**
+ * Validate that the SipMessage is allowed to be sent to the remote.
+ * @param message The SipMessage being validated.
+ * @return A {@link ValidationResult} that represents whether or not the message was validated.
+ * If not validated, it also returns a reason why the SIP message was not validated.
+ */
+ ValidationResult validate(SipMessage message);
+
+ /**
+ * Compose a SipMessageValidator out of two validators, this validator running before the next
+ * validator.
+ * @param next The next validator that will be run if this validator validates the message
+ * successfully.
+ * @return A new SipMessageValidator composed of this validator and the next one.
+ */
+ default SipMessageValidator andThen(SipMessageValidator next) {
+ return (SipMessage m) -> {
+ ValidationResult result = validate(m);
+ if (!result.isValidated) return result;
+ return next.validate(m);
+ };
+ }
+}
diff --git a/src/com/android/services/telephony/rcs/validator/ValidationResult.java b/src/com/android/services/telephony/rcs/validator/ValidationResult.java
new file mode 100644
index 0000000..e434163
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/validator/ValidationResult.java
@@ -0,0 +1,84 @@
+/*
+ * 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.services.telephony.rcs.validator;
+
+import android.telephony.ims.SipDelegateManager;
+
+/**
+ * Communicates the result of validating whether a SIP message should be sent to a remote based on
+ * the contents of the SIP message as well as if the transport is in an appropriate state for the
+ * intended recipient of the message.
+ */
+public class ValidationResult {
+ public static final ValidationResult SUCCESS = new ValidationResult();
+
+ /**
+ * If {@code true}, the requested SIP message has been validated and may be sent to the remote.
+ * If {@code false}, the SIP message has failed validation and should not be sent to the
+ * remote. The {@link #restrictedReason} field will contain the reason for the validation
+ * failure.
+ */
+ public final boolean isValidated;
+
+ /**
+ * The reason associated with why the SIP message was not verified and generated a
+ * {@code false} result for {@link #isValidated}.
+ */
+ public final int restrictedReason;
+
+ /**
+ * The human readable reason for why the validation failed for logging.
+ */
+ public final String logReason;
+
+ /**
+ * Communicates a validated result of success. Use {@link #SUCCESS} instead.
+ */
+ private ValidationResult() {
+ isValidated = true;
+ restrictedReason = SipDelegateManager.MESSAGE_FAILURE_REASON_UNKNOWN;
+ logReason = "";
+ }
+
+ /**
+ * The result of validating that the SIP Message should be sent.
+ *
+ * @param reason The reason associated with why the SIP message was not validated and
+ * generated a {@code false} result for {@link #isValidated}.
+ */
+ public ValidationResult(@SipDelegateManager.MessageFailureReason int reason, String log) {
+ isValidated = false;
+ restrictedReason = reason;
+ logReason = log;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ b.append("ValidationResult{");
+ b.append("validated=");
+ b.append(isValidated);
+ if (!isValidated) {
+ b.append(", restrictedReason=");
+ b.append(restrictedReason);
+ b.append(", logReason=");
+ b.append(logReason);
+ }
+ b.append('}');
+ return b.toString();
+ }
+}
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/EmbmsServiceTestApp/AndroidManifest.xml b/testapps/EmbmsServiceTestApp/AndroidManifest.xml
index 91d8508..943fc78 100644
--- a/testapps/EmbmsServiceTestApp/AndroidManifest.xml
+++ b/testapps/EmbmsServiceTestApp/AndroidManifest.xml
@@ -15,30 +15,31 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- package="com.android.phone.testapps.embmsmw"
- coreApp="true">
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="com.android.phone.testapps.embmsmw"
+ coreApp="true">
<uses-permission android:name="android.permission.SEND_EMBMS_INTENTS"/>
<application android:label="EmbmsTestMiddleware">
<service android:name="com.android.phone.testapps.embmsmw.EmbmsTestStreamingService"
- android:launchMode="singleInstance"
- androidprv:systemUserOnly="true">
+ android:launchMode="singleInstance"
+ androidprv:systemUserOnly="true"
+ android:exported="true">
<intent-filter>
- <action android:name="android.telephony.action.EmbmsStreaming" />
+ <action android:name="android.telephony.action.EmbmsStreaming"/>
</intent-filter>
</service>
<service android:name="com.android.phone.testapps.embmsmw.EmbmsSampleDownloadService"
- android:launchMode="singleInstance"
- androidprv:systemUserOnly="true">
+ android:launchMode="singleInstance"
+ androidprv:systemUserOnly="true"
+ android:exported="true">
<intent-filter>
- <action android:name="android.telephony.action.EmbmsDownload" />
+ <action android:name="android.telephony.action.EmbmsDownload"/>
</intent-filter>
</service>
<receiver android:name="com.android.phone.testapps.embmsmw.SideChannelReceiver"
- android:enabled="true"
- android:exported="true"/>
+ android:enabled="true"
+ android:exported="true"/>
</application>
</manifest>
-
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/EmbmsTestDownloadApp/AndroidManifest.xml b/testapps/EmbmsTestDownloadApp/AndroidManifest.xml
index e93cd19..640fcd1 100644
--- a/testapps/EmbmsTestDownloadApp/AndroidManifest.xml
+++ b/testapps/EmbmsTestDownloadApp/AndroidManifest.xml
@@ -15,57 +15,54 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.phone.testapps.embmsdownload">
+ package="com.android.phone.testapps.embmsdownload">
<application android:label="EmbmsTestDownloadApp">
- <activity
- android:name=".EmbmsTestDownloadApp"
- android:label="EmbmsDownloadFrontend">
+ <activity android:name=".EmbmsTestDownloadApp"
+ android:label="EmbmsDownloadFrontend"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- This is the receiver defined by the MBMS api. -->
- <receiver
- android:name="android.telephony.mbms.MbmsDownloadReceiver"
- android:permission="android.permission.SEND_EMBMS_INTENTS"
- android:enabled="true"
- android:exported="true">
+ <receiver android:name="android.telephony.mbms.MbmsDownloadReceiver"
+ android:permission="android.permission.SEND_EMBMS_INTENTS"
+ android:enabled="true"
+ android:exported="true">
</receiver>
<!-- This is the receiver defined by app to receive the download-done intent that was
passed into DownloadRequest. -->
- <receiver
- android:name="com.android.phone.testapps.embmsdownload.DownloadCompletionReceiver"
- android:enabled="true">
+ <receiver android:name="com.android.phone.testapps.embmsdownload.DownloadCompletionReceiver"
+ android:enabled="true">
</receiver>
<!-- This is the provider that apps must declare in their manifest. It allows the
middleware to obtain file descriptors to temp files in the app's file space -->
<!-- grantUriPermissions must be set to true -->
- <provider
- android:name="android.telephony.mbms.MbmsTempFileProvider"
- android:authorities="com.android.phone.testapps.embmsdownload"
- android:exported="false"
- android:grantUriPermissions="true">
+ <provider android:name="android.telephony.mbms.MbmsTempFileProvider"
+ android:authorities="com.android.phone.testapps.embmsdownload"
+ android:exported="false"
+ android:grantUriPermissions="true">
<!-- This is a mandatory piece of metadata that contains the directory where temp
files should be put. It should be a relative path from Context.getFilesDir() or from
Context.getExternalStorageDir(null), depending on the value of the
use-external-storage metadata. -->
- <meta-data android:name="temp-file-path" android:value="/mbms-temp/"/>
+ <meta-data android:name="temp-file-path"
+ android:value="/mbms-temp/"/>
<!-- This tells the provider whether to use the sdcard partition for the temp files or
not. -->
- <meta-data android:name="use-external-storage" android:value="false"/>
+ <meta-data android:name="use-external-storage"
+ android:value="false"/>
</provider>
<!-- This is a mandatory piece of metadata that contains the authority string for the
provider declared above -->
- <meta-data
- android:name="mbms-file-provider-authority"
- android:value="com.android.phone.testapps.embmsdownload"/>
+ <meta-data android:name="mbms-file-provider-authority"
+ android:value="com.android.phone.testapps.embmsdownload"/>
</application>
</manifest>
-
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/EmbmsTestStreamingApp/AndroidManifest.xml b/testapps/EmbmsTestStreamingApp/AndroidManifest.xml
index d13425d..9cb83f2 100644
--- a/testapps/EmbmsTestStreamingApp/AndroidManifest.xml
+++ b/testapps/EmbmsTestStreamingApp/AndroidManifest.xml
@@ -15,17 +15,16 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.phone.testapps.embmsfrontend">
+ package="com.android.phone.testapps.embmsfrontend">
<application android:label="EmbmsTestStreamingApp">
- <activity
- android:name=".EmbmsTestStreamingApp"
- android:label="EmbmsStreamingFrontend">
+ <activity android:name=".EmbmsTestStreamingApp"
+ android:label="EmbmsStreamingFrontend"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
-
diff --git a/testapps/GbaTestApp/Android.bp b/testapps/GbaTestApp/Android.bp
new file mode 100644
index 0000000..b3c45dd
--- /dev/null
+++ b/testapps/GbaTestApp/Android.bp
@@ -0,0 +1,35 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// 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 {
+ // 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: [
+ "androidx.appcompat_appcompat",
+ "androidx-constraintlayout_constraintlayout",
+ "ub-uiautomator",
+ ],
+ srcs: ["src/**/*.java"],
+ javacflags: ["-parameters"],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/testapps/GbaTestApp/AndroidManifest.xml b/testapps/GbaTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..8554461
--- /dev/null
+++ b/testapps/GbaTestApp/AndroidManifest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.phone.testapps.gbatestapp">
+
+ <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+ <uses-permission android:name="android.permission.MODIFY_PHONE_STATE"/>
+ <uses-permission android:name="android.permission.BIND_GBA_SERVICE" />
+
+ <application
+ android:allowBackup="true"
+ android:label="@string/app_name"
+ android:theme="@style/Theme.AppCompat"
+ android:supportsRtl="true">
+ <service
+ android:name=".TestGbaService"
+ android:directBootAware="true"
+ android:permission="android.permission.BIND_GBA_SERVICE"
+ android:enabled="true"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.telephony.gba.GbaService"/>
+ </intent-filter>
+ </service>
+
+ <activity android:name=".MainActivity"
+ android:enabled="true"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/testapps/GbaTestApp/res/layout/fragment_carrier_config.xml b/testapps/GbaTestApp/res/layout/fragment_carrier_config.xml
new file mode 100644
index 0000000..f15fa2a
--- /dev/null
+++ b/testapps/GbaTestApp/res/layout/fragment_carrier_config.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/black"
+ tools:context=".ui.main.CarrierConfigFragment">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/service_package_name" />
+
+ <EditText
+ android:id="@+id/editServicePackageName"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:ems="10"
+ android:gravity="start|top"
+ android:inputType="textMultiLine" />
+
+ <TextView
+ android:id="@+id/textTestLabel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/service_release_time" />
+
+ <EditText
+ android:id="@+id/editServiceReleaseTime"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ems="10"
+ android:inputType="numberSigned" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/layout_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:orientation="vertical">
+
+ <Button
+ android:id="@+id/carrier_config_clear"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/button_name_clear" />
+
+ <Button
+ android:id="@+id/carrier_config_done"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/button_name_done" />
+ </LinearLayout>
+
+</FrameLayout>
diff --git a/testapps/GbaTestApp/res/layout/fragment_service_config.xml b/testapps/GbaTestApp/res/layout/fragment_service_config.xml
new file mode 100644
index 0000000..50090c2
--- /dev/null
+++ b/testapps/GbaTestApp/res/layout/fragment_service_config.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/black"
+ tools:context=".ui.main.ServiceConfigFragment">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_above="@id/layout_buttons"
+ android:orientation="vertical">
+
+ <CheckBox
+ android:id="@+id/checkBoxResult"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/response_success" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/response_key" />
+
+ <EditText
+ android:id="@+id/editKey"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ems="10"
+ android:inputType="textPersonName" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:editable="false"
+ android:text="@string/response_btid" />
+
+ <EditText
+ android:id="@+id/editBTid"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ems="10"
+ android:inputType="textPersonName" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/response_fail_reason" />
+
+ <EditText
+ android:id="@+id/editFailReason"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ems="10"
+ android:inputType="numberSigned" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/layout_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_gravity="bottom"
+ android:orientation="vertical">
+
+ <Button
+ android:id="@+id/service_config_clear"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/button_name_clear" />
+
+ <Button
+ android:id="@+id/service_config_done"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/button_name_done" />
+ </LinearLayout>
+
+</FrameLayout>
diff --git a/testapps/GbaTestApp/res/layout/fragment_test_config.xml b/testapps/GbaTestApp/res/layout/fragment_test_config.xml
new file mode 100644
index 0000000..d8016f0
--- /dev/null
+++ b/testapps/GbaTestApp/res/layout/fragment_test_config.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/black"
+ tools:context=".ui.main.TestConfigFragment">
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_marginBottom="8dp"
+ android:scrollbarStyle="outsideInset"
+ app:layout_constraintBottom_toTopOf="@id/layout_buttons"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/request_app_type" />
+
+ <EditText
+ android:id="@+id/editAppType"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ems="10"
+ android:inputType="numberSigned" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/request_naf_url" />
+
+ <EditText
+ android:id="@+id/editUrl"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ems="10"
+ android:inputType="textUri" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/request_org" />
+
+ <EditText
+ android:id="@+id/editOrg"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ems="10"
+ android:inputType="numberSigned" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/request_security_protocol" />
+
+ <EditText
+ android:id="@+id/editSpId"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ems="10"
+ android:inputType="numberSigned" />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/request_tls_cipher_suite" />
+
+ <EditText
+ android:id="@+id/editTlsCs"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ems="10"
+ android:inputType="numberSigned" />
+
+ <CheckBox
+ android:id="@+id/checkBoxForce"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/request_force_bootstrapping" />
+
+ </LinearLayout>
+ </ScrollView>
+
+ <LinearLayout
+ android:id="@+id/layout_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="false"
+ android:layout_gravity="bottom"
+ android:orientation="vertical"
+ app:layout_constraintBottom_toBottomOf="parent">
+
+ <Button
+ android:id="@+id/client_config_clear"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/button_name_clear" />
+
+ <Button
+ android:id="@+id/client_config_done"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/button_name_done" />
+ </LinearLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/testapps/GbaTestApp/res/layout/main_activity.xml b/testapps/GbaTestApp/res/layout/main_activity.xml
new file mode 100644
index 0000000..1dfb73b
--- /dev/null
+++ b/testapps/GbaTestApp/res/layout/main_activity.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".MainActivity" />
\ No newline at end of file
diff --git a/testapps/GbaTestApp/res/layout/main_fragment.xml b/testapps/GbaTestApp/res/layout/main_fragment.xml
new file mode 100644
index 0000000..33bb6e1
--- /dev/null
+++ b/testapps/GbaTestApp/res/layout/main_fragment.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/main"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".ui.main.MainFragment">
+
+ <LinearLayout
+ android:id="@+id/layout_config"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:orientation="vertical"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/label_settings" />
+
+ <Button
+ android:id="@+id/carrier_config_change_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/label_carrier" />
+
+ <Button
+ android:id="@+id/service_config"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/label_service" />
+
+ <Button
+ android:id="@+id/client_config"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/label_test" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/layout_test"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:orientation="vertical"
+ app:layout_constraintBottom_toTopOf="@id/layout_exit"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/layout_config">
+
+ <Button
+ android:id="@+id/send_request"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/button_name_running" />
+
+ <TextView
+ android:id="@+id/textTestLabel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/label_test_result" />
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/viewTestOutput"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+ </ScrollView>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/layout_exit"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_gravity="bottom"
+ android:orientation="vertical"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent">
+
+ <Button
+ android:id="@+id/test_exit"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/button_name_exit" />
+ </LinearLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/testapps/GbaTestApp/res/values-af/strings.xml b/testapps/GbaTestApp/res/values-af/strings.xml
new file mode 100644
index 0000000..7f690de
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-af/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Instellings"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Diensverskafferopstelling"</string>
+ <string name="label_service" msgid="2668963955237345578">"Diensopstelling"</string>
+ <string name="label_test" msgid="8425079572898571918">"Toetsopstelling"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Loop tans"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Verlaat"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Toetsresultaat"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Stel terug"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Klaar"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Pakketnaam van GBA-diens"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Hoe lank voor diens vrygestel word nadat gebel is"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC-programtipe"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Netwerkprogramfunksie (NAF) -URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Verplig selflaai?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Organisasiekode"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID vir UA-sekuriteitsprotokol"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID vir TLS Cipher Suite"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA-stawingsukses?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Foutrede-ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA-sleutel (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Selflaaitransaksie-identifiseerder (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-am/strings.xml b/testapps/GbaTestApp/res/values-am/strings.xml
new file mode 100644
index 0000000..b78cd6c
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-am/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"ቅንብሮች"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"የአገልግሎት አቅራቢ ውቅረት"</string>
+ <string name="label_service" msgid="2668963955237345578">"የአገልግሎት ውቅረት"</string>
+ <string name="label_test" msgid="8425079572898571918">"የሙከራ ውቅረት"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"በመስራት ላይ"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"ውጣ"</string>
+ <string name="label_test_result" msgid="892984695972956196">"የሙከራ ውጤት"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"ዳግም አስጀምር"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"ተከናውኗል"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"የGBA አገልግሎት የጥቅል ስም"</string>
+ <string name="service_release_time" msgid="532937496122890892">"ጥሪ ከተደረገ በኋላ አገልግሎት የሚለቀቅበት ጊዜ"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC የመተግበሪያ አይነት"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"የአውታረ መረብ የመተግበሪያ ተግባር (NAF) ዩአርኤል"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"በማስነሻ ተገዶ ይነሳ?"</string>
+ <string name="request_org" msgid="8416693445448308975">"የድርጅት ኮድ"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA የደህንነት ፕሮቶኮል መታወቂያ"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"የTLS ማመስጠሪያ ጥቅል መታወቂያ"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA ማረጋገጥ ተሳክቷል?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ያለመሳካት ምክንያት መታወቂያ"</string>
+ <string name="response_key" msgid="8839847772051686309">"የGBA ቁልፍ (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"የማስነሻ የግብይት መለያ (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-ar/strings.xml b/testapps/GbaTestApp/res/values-ar/strings.xml
new file mode 100644
index 0000000..2c0094b
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-ar/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"الإعدادات"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"إعداد مشغّل شبكة الجوّال"</string>
+ <string name="label_service" msgid="2668963955237345578">"إعداد الخدمة"</string>
+ <string name="label_test" msgid="8425079572898571918">"اختبار الإعداد"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"العملية جارية"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"خروج"</string>
+ <string name="label_test_result" msgid="892984695972956196">"نتيجة الاختبار"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"إعادة الضبط"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"تم"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"اسم الحزمة لخدمة GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"المدة الزمنية المتبقية قبل تفعيل الخدمة بعد الاتصال"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"نوع تطبيق UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"عنوان URL لوظيفة التطبيقات على الشبكة (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"هل تريد فرض التشغيل التمهيدي؟"</string>
+ <string name="request_org" msgid="8416693445448308975">"رمز المؤسسة"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"رقم تعريف بروتوكول أمان UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"رقم تعريف حزمة رموز طبقة النقل الآمنة"</string>
+ <string name="response_success" msgid="2469204471244527663">"هل تمت مصادقة GBA بنجاح؟"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"رقم تعريف سبب الإخفاق"</string>
+ <string name="response_key" msgid="8839847772051686309">"مفتاح GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"معرّف عملية التشغيل التمهيدي (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-as/strings.xml b/testapps/GbaTestApp/res/values-as/strings.xml
new file mode 100644
index 0000000..3fbba0f
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-as/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"ছেটিং"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"বাহকৰ কনফিগাৰেশ্বন"</string>
+ <string name="label_service" msgid="2668963955237345578">"সেৱাৰ কনফিগাৰেশ্বন"</string>
+ <string name="label_test" msgid="8425079572898571918">"পৰীক্ষাৰ কনফিগাৰেশ্বন"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"চলি আছে"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"বাহিৰ হওক"</string>
+ <string name="label_test_result" msgid="892984695972956196">"পৰীক্ষাৰ ফলাফল"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"ৰিছেট কৰক"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"হ’ল"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA সেৱাৰ পেকেজৰ নাম"</string>
+ <string name="service_release_time" msgid="532937496122890892">"কল কৰাৰ কিমান সময়ৰ পাছত সেৱা প্ৰদান কৰা হ’ব"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC এপৰ প্ৰকাৰ"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"নেটৱৰ্ক এপ্লিকেশ্বন ফাংশ্বন (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"বলেৰে বুটষ্ট্ৰেপিং কৰিবনে?"</string>
+ <string name="request_org" msgid="8416693445448308975">"প্ৰতিষ্ঠানৰ ক’ড"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA সুৰক্ষা প্ৰ’ট’কল আইডি"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS Cipher Suite আইডি"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA প্ৰমাণীকৰণ সফল হৈছে নেকি?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ব্যৰ্থতাৰ কাৰণৰ আইডি"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA কী (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"বুটষ্ট্ৰেপিং লেনদেন চিনাক্তকাৰী (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-az/strings.xml b/testapps/GbaTestApp/res/values-az/strings.xml
new file mode 100644
index 0000000..5d78cde
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-az/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Ayarlar"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Operator konfiqurasiyası"</string>
+ <string name="label_service" msgid="2668963955237345578">"Xidmət konfiqurasiyası"</string>
+ <string name="label_test" msgid="8425079572898571918">"Test konfiqurasiyası"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Proses gedir"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Çıxın"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Test nəticəsi"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Sıfırlayın"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Oldu"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA xidmətinin paket adı"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Zəngdən sonra xidməti hansı müddətə buraxmalı"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC tətbiq növü"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Şəbəkə tətbiqi funksiyası (NAF) URL-i"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Öz-özünə yükləməyə müdaxilə edilsin?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Təşkilat kodu"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA təhlükəsizlik protokolu ID-si"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS şifr dəsti ID-si"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA doğrulama alındı?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Uğursuzluq səbəbi ID-si"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA açarı (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Öz-özünə yükləmə tranzaksiyası üzrə identifikator (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-b+sr+Latn/strings.xml b/testapps/GbaTestApp/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..4ae2c86
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Podešavanja"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Konfiguracija mobilnog operatera"</string>
+ <string name="label_service" msgid="2668963955237345578">"Konfiguracija usluge"</string>
+ <string name="label_test" msgid="8425079572898571918">"Probna konfiguracija"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Aktivno"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Zatvori"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Rezultat testa"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Resetuj"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Gotovo"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Naziv paketa usluge GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Vreme dok se usluga ne aktivira posle poziva"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Tip UICC aplikacije"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL funkcije mrežne aplikacije (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Želite li da nametnete samostalno uzorkovanje?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Kôd organizacije"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID bezbednosnog protokola univerzalne analitike"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID TLS paketa za šifrovanje"</string>
+ <string name="response_success" msgid="2469204471244527663">"Da li je potvrda identiteta za GBA uspela?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID razloga za neuspeh"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA taster (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Identifikator transakcije za samostalno uzorkovanje (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-be/strings.xml b/testapps/GbaTestApp/res/values-be/strings.xml
new file mode 100644
index 0000000..d9d8f35
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-be/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Налады"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Канфігурацыя аператара"</string>
+ <string name="label_service" msgid="2668963955237345578">"Канфігурацыя сэрвісу"</string>
+ <string name="label_test" msgid="8425079572898571918">"Тэставая канфігурацыя"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Выконваецца"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Выйсці"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Вынік тэсціравання"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Скінуць"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Гатова"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Назва пакета сэрвісу GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Час вызвалення сэрвісу пасля выкліку"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Тып праграмы UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL-адрас функцыі сеткавай праграмы (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Выканаць пачатковую загрузку прымусова?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Код арганізацыі"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"Ідэнтыфікатар пратакола бяспекі UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"Ідэнтыфікатар пакета шыфравання TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"Аўтэнтыфікацыя GBA выканана?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Ідэнтыфікатар прычыны памылкі"</string>
+ <string name="response_key" msgid="8839847772051686309">"Ключ GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Ідэнтыфікатар трансакцыі пачатковай загрузкі (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-bg/strings.xml b/testapps/GbaTestApp/res/values-bg/strings.xml
new file mode 100644
index 0000000..664b304
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-bg/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Настройки"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Конфигурация от оператора"</string>
+ <string name="label_service" msgid="2668963955237345578">"Конфигурация на услугата"</string>
+ <string name="label_test" msgid="8425079572898571918">"Конфигурация на теста"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Изпълнява се"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Изход"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Резултат от теста"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Повторно задаване"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Готово"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Име на пакета за услугата GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Колко време след обаждането да бъде освободена услугата"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Тип на приложението в UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL адрес на функцията за мрежово приложение (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Да се извърши ли принудително начално зареждане?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Код на организацията"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"Идентификатор на протокола за сигурност в UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"Идентификатор на пакета за шифроване с TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"Упълномощаването в GBA успя ли?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Идентификатор на причината за неуспех"</string>
+ <string name="response_key" msgid="8839847772051686309">"Ключ за GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Идентификатор на транзакцията при начално зареждане (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-bn/strings.xml b/testapps/GbaTestApp/res/values-bn/strings.xml
new file mode 100644
index 0000000..c661312
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-bn/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"সেটিংস"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"পরিষেবা প্রদানকারী কনফিগার করুন"</string>
+ <string name="label_service" msgid="2668963955237345578">"পরিষেবা কনফিগার করুন"</string>
+ <string name="label_test" msgid="8425079572898571918">"টেস্ট কনফিগার করুন"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"চলছে"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"বেরিয়ে আসুন"</string>
+ <string name="label_test_result" msgid="892984695972956196">"টেস্টের ফলাফল"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"রিসেট করুন"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"হয়ে গেছে"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA পরিষেবা সংক্রান্ত প্যাকেজের নাম"</string>
+ <string name="service_release_time" msgid="532937496122890892">"কল করার কতক্ষণ পরে পরিষেবা পাওয়া যাবে"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC অ্যাপের ধরন"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"নেটওয়ার্ক অ্যাপ্লিকেশনের কাজ (NAF) সংক্রান্ত URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"জোর করেই বুটস্ট্র্যাপিং করবেন?"</string>
+ <string name="request_org" msgid="8416693445448308975">"অর্গানাইজেশনের কোড"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA নিরাপত্তা সংক্রান্ত আইডি"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS গোপন কোডের স্যুট ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA যাচাই করা গেছে?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ব্যর্থতার কারণ সংক্রান্ত ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA কী (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"বুটস্ট্র্যাপিং ট্রানজ্যাকশন শনাক্তকারী (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-bs/strings.xml b/testapps/GbaTestApp/res/values-bs/strings.xml
new file mode 100644
index 0000000..1cfc0d1
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-bs/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Postavke"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Konfiguracija mobilnog operatera"</string>
+ <string name="label_service" msgid="2668963955237345578">"Konfiguracija usluge"</string>
+ <string name="label_test" msgid="8425079572898571918">"Testna konfiguracija"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Pokrenuto"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Izađi"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Rezultati testa"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Vrati na zadano"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Gotovo"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Ime paketa usluge GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Vrijeme prije nego se usluga aktivira nakon pozivanja"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Vrsta aplikacije UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL funkcije mrežne aplikacije (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Prisilno omogućiti metodu ponovnog uzorkovanja?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Kôd organizacije"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID sigurnosnog protokola UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID paketa TLS Cipher"</string>
+ <string name="response_success" msgid="2469204471244527663">"Uspjeli ste izvršiti autorizaciju GBA?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID razloga za neuspjeh"</string>
+ <string name="response_key" msgid="8839847772051686309">"Tipka GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Identifikator transakcije za metodu ponovnog uzorkovanja (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-ca/strings.xml b/testapps/GbaTestApp/res/values-ca/strings.xml
new file mode 100644
index 0000000..83991ac
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-ca/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Configuració"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Configuració de l\'operador"</string>
+ <string name="label_service" msgid="2668963955237345578">"Configuració del servei"</string>
+ <string name="label_test" msgid="8425079572898571918">"Configuració de la prova"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"S\'està executant"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Surt"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Resultat de la prova"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Restableix"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Fet"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Nom de paquet del servei de GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Temps per desvincular el servei després de la trucada"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Tipus d\'aplicació UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL de la funció d\'aplicació de xarxa (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Vols forçar l\'arrencada?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Codi d\'organització"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"Identificador de protocol de seguretat d\'UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"Identificador de sistema de xifratge TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"Autenticació de GBA correcta?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Identificador de motiu d\'error"</string>
+ <string name="response_key" msgid="8839847772051686309">"Clau GBA (CK i IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Identificador de transacció d\'arrencada (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-cs/strings.xml b/testapps/GbaTestApp/res/values-cs/strings.xml
new file mode 100644
index 0000000..59b50b5
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-cs/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Nastavení"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Konfigurace operátora"</string>
+ <string name="label_service" msgid="2668963955237345578">"Konfigurace služby"</string>
+ <string name="label_test" msgid="8425079572898571918">"Konfigurace testu"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Běží"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Ukončit"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Výsledek testu"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Resetovat"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Hotovo"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Název balíčku služby GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Za jak dlouho po volání uvolnit službu"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Typ aplikace UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Adresa URL funkce síťové aplikace (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Vynutit bootstrapping?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Kód organizace"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID protokolu zabezpečení UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID sady šifer TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"Úspěšné ověření GBA?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID důvodu selhání"</string>
+ <string name="response_key" msgid="8839847772051686309">"Klíč GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Identifikátor transakce bootstrappingu (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-da/strings.xml b/testapps/GbaTestApp/res/values-da/strings.xml
new file mode 100644
index 0000000..1c3860d
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-da/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Indstillinger"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Konfiguration af mobilselskab"</string>
+ <string name="label_service" msgid="2668963955237345578">"Konfiguration af tjeneste"</string>
+ <string name="label_test" msgid="8425079572898571918">"Test konfiguration"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Kører"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Afslut"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Testresultat"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Nulstil"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Udfør"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Pakkenavn på GBA-tjeneste"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Hvor lang tid der går, før tjenesten frigives efter opkaldet"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC-apptype"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Webadresse til NAF (Network application function)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Vil du gennemtvinge bootstrapping?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Organisationskode"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"Id for UA-sikkerhedsprotokol"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"Id for TLS-krypteringsalgoritmepakke"</string>
+ <string name="response_success" msgid="2469204471244527663">"Blev GBA godkendt?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Id for fejlårsag"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA-tat (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Transaktions-id for bootstrapping (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-de/strings.xml b/testapps/GbaTestApp/res/values-de/strings.xml
new file mode 100644
index 0000000..2b575aa
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-de/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Einstellungen"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Konfiguration des Mobilfunkanbieters"</string>
+ <string name="label_service" msgid="2668963955237345578">"Dienstkonfiguration"</string>
+ <string name="label_test" msgid="8425079572898571918">"Testkonfiguration"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Wird ausgeführt"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Beenden"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Testergebnis"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Zurücksetzen"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Fertig"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Paketname des GBA-Diensts"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Dauer bis zur Freigabe des Diensts nach Aufruf"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC-App-Typ"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"NAF-URL (Netzwerkanwendungsfunktion)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Bootstrapping erzwingen?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Organisationscode"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID für UA-Sicherheitsprotokoll"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS Cipher Suite-ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA-Authentifizierung erfolgreich?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID der Fehlerursache"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA-Schlüssel (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Bootstrapping-Transaktions-ID (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-el/strings.xml b/testapps/GbaTestApp/res/values-el/strings.xml
new file mode 100644
index 0000000..574842d
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-el/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Ρυθμίσεις"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Διαμόρφωση παρόχου"</string>
+ <string name="label_service" msgid="2668963955237345578">"Διαμόρφωση υπηρεσίας"</string>
+ <string name="label_test" msgid="8425079572898571918">"Διαμόρφωση δοκιμής"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Σε εξέλιξη"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Έξοδος"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Αποτέλεσμα δοκιμής"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Επαναφορά"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Τέλος"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Όνομα πακέτου υπηρεσίας GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Διάρκεια αποδέσμευσης υπηρεσίας μετά την κλήση"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Τύπος εφαρμογής UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL λειτουργίας εφαρμογής δικτύου (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Επιβολή εκκίνησης;"</string>
+ <string name="request_org" msgid="8416693445448308975">"Κωδικός Οργανισμού"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"Αναγνωριστικό πρωτοκόλλου ασφαλείας UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"Αναγνωριστικό TLS Cipher Suite"</string>
+ <string name="response_success" msgid="2469204471244527663">"Επιτυχία GBA Auth;"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Αναγνωριστικό αιτίου αποτυχίας"</string>
+ <string name="response_key" msgid="8839847772051686309">"Κλειδί GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Αναγνωριστικό συναλλαγής εκκίνησης (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-en-rAU/strings.xml b/testapps/GbaTestApp/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..6c4a199
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-en-rAU/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Settings"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Operator config"</string>
+ <string name="label_service" msgid="2668963955237345578">"Service config"</string>
+ <string name="label_test" msgid="8425079572898571918">"Test config"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Running"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Exit"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Test result"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Reset"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Done"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Package name of GBA service"</string>
+ <string name="service_release_time" msgid="532937496122890892">"How long to release service after calling"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC app type"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Network application function (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Force bootstrapping?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Organisation code"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA security protocol ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS cipher suite ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA auth success?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Fail reason ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA key (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Bootstrapping transaction identifier (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-en-rCA/strings.xml b/testapps/GbaTestApp/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..6c4a199
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-en-rCA/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Settings"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Operator config"</string>
+ <string name="label_service" msgid="2668963955237345578">"Service config"</string>
+ <string name="label_test" msgid="8425079572898571918">"Test config"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Running"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Exit"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Test result"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Reset"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Done"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Package name of GBA service"</string>
+ <string name="service_release_time" msgid="532937496122890892">"How long to release service after calling"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC app type"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Network application function (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Force bootstrapping?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Organisation code"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA security protocol ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS cipher suite ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA auth success?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Fail reason ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA key (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Bootstrapping transaction identifier (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-en-rGB/strings.xml b/testapps/GbaTestApp/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..6c4a199
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-en-rGB/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Settings"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Operator config"</string>
+ <string name="label_service" msgid="2668963955237345578">"Service config"</string>
+ <string name="label_test" msgid="8425079572898571918">"Test config"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Running"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Exit"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Test result"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Reset"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Done"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Package name of GBA service"</string>
+ <string name="service_release_time" msgid="532937496122890892">"How long to release service after calling"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC app type"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Network application function (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Force bootstrapping?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Organisation code"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA security protocol ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS cipher suite ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA auth success?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Fail reason ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA key (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Bootstrapping transaction identifier (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-en-rIN/strings.xml b/testapps/GbaTestApp/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..6c4a199
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-en-rIN/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Settings"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Operator config"</string>
+ <string name="label_service" msgid="2668963955237345578">"Service config"</string>
+ <string name="label_test" msgid="8425079572898571918">"Test config"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Running"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Exit"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Test result"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Reset"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Done"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Package name of GBA service"</string>
+ <string name="service_release_time" msgid="532937496122890892">"How long to release service after calling"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC app type"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Network application function (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Force bootstrapping?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Organisation code"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA security protocol ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS cipher suite ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA auth success?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Fail reason ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA key (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Bootstrapping transaction identifier (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-en-rXC/strings.xml b/testapps/GbaTestApp/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..e3fe56b
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-en-rXC/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Settings"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Carrier Config"</string>
+ <string name="label_service" msgid="2668963955237345578">"Service Config"</string>
+ <string name="label_test" msgid="8425079572898571918">"Test Config"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Running"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Exit"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Test Result"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Reset"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Done"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Package name of GBA service"</string>
+ <string name="service_release_time" msgid="532937496122890892">"How long to release service after calling"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC App Type"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Network application function (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Force Bootstrapping?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Organization Code"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA Security Protocol ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS Cipher Suite ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA Auth Success?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Fail Reason ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA Key (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Bootstrapping Transaction Identifier (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-es-rUS/strings.xml b/testapps/GbaTestApp/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..cbe3542
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-es-rUS/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Configuración"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Configuración del proveedor"</string>
+ <string name="label_service" msgid="2668963955237345578">"Configuración del servicio"</string>
+ <string name="label_test" msgid="8425079572898571918">"Configuración de prueba"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"En ejecución"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Salir"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Resultado de la prueba"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Restablecer"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Listo"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Nombre del paquete del servicio de GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Cuánto tarda en liberarse el servicio después de llamar"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Tipo de app UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL de función de aplicaciones de red (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"¿Quieres forzar el arranque?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Código de organización"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID del protocolo de seguridad de UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID de paquete de algoritmo de cifrado TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"¿Se completó la autenticación de GBA?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID de motivo de error"</string>
+ <string name="response_key" msgid="8839847772051686309">"Llave de GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Identificador de transacción de arranque (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-es/strings.xml b/testapps/GbaTestApp/res/values-es/strings.xml
new file mode 100644
index 0000000..269ea86
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-es/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Ajustes"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Configuración del operador"</string>
+ <string name="label_service" msgid="2668963955237345578">"Configuración del servicio"</string>
+ <string name="label_test" msgid="8425079572898571918">"Configuración de la prueba"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"En ejecución"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Salir"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Resultado de la prueba"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Restablecer"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Hecho"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Nombre de paquete del servicio GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Durante cuánto tiempo se debe desenlazar el servicio tras la llamada"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Tipo de aplicación UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL de la función de aplicación de red (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"¿Forzar arranque?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Código de la organización"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID del protocolo de seguridad de UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID del conjunto de algoritmos de cifrado TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"¿Autorización GBA correcta?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID del motivo del error"</string>
+ <string name="response_key" msgid="8839847772051686309">"Llave GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Identificador de transacción de arranque (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-et/strings.xml b/testapps/GbaTestApp/res/values-et/strings.xml
new file mode 100644
index 0000000..d1bb73e
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-et/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Seaded"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Operaatori seadistamine"</string>
+ <string name="label_service" msgid="2668963955237345578">"Teenuse seadistamine"</string>
+ <string name="label_test" msgid="8425079572898571918">"Testi seadistamine"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Töötab"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Välju"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Testi tulemus"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Lähtesta"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Valmis"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA teenuse paketi nimi"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Teenuse vabastusaeg pärast helistamist"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC rakenduse tüüp"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Võrgurakenduse funktsiooni (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Kas sundida alglaadimine?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Organisatsiooni kood"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA turvaprotokolli ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS-i šifrikomplekti ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"Kas GBA autentimine õnnestus?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Nurjumise põhjuse ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA võti (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Alglaadimise tehingu identifikaator (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-eu/strings.xml b/testapps/GbaTestApp/res/values-eu/strings.xml
new file mode 100644
index 0000000..6774d99
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-eu/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Ezarpenak"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Operadorearen konfigurazioa"</string>
+ <string name="label_service" msgid="2668963955237345578">"Zerbitzuaren konfigurazioa"</string>
+ <string name="label_test" msgid="8425079572898571918">"Probako konfigurazioa"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Abian"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Irten"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Probaren emaitza"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Berrezarri"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Eginda"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA zerbitzuaren paketearen izena"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Deitu ondoren zerbitzua libre uzteko igarotzen den denbora"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC aplikazio mota"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Sareko aplikazioaren funtzioaren (NAF) URLa"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Bootstrapping-a erabiltzera behartu nahi duzu?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Erakundearen kodea"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA segurtasun-protokoloaren IDa"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS Cipher Suite ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"Burutu da GBA autentifikazioa?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Hutsegitearen arrazoiaren IDa"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA-ko gakoa (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Bootstrapping Transaction Identifier (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-fa/strings.xml b/testapps/GbaTestApp/res/values-fa/strings.xml
new file mode 100644
index 0000000..92801e7
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-fa/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"تنظیمات"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"پیکربندی شرکت مخابراتی"</string>
+ <string name="label_service" msgid="2668963955237345578">"پیکربندی سرویس"</string>
+ <string name="label_test" msgid="8425079572898571918">"پیکربندی آزمایش"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"درحال اجرا"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"خروج"</string>
+ <string name="label_test_result" msgid="892984695972956196">"نتیجه آزمایش"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"بازنشانی"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"تمام"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"نام بسته سرویس «معماری خود راهاندازی فراگیر» (GBA)"</string>
+ <string name="service_release_time" msgid="532937496122890892">"مدت زمان عرضه سرویس بعداز تماس"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"نوع برنامه «کارت مدار مجتمع جهانی» (UICC)"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"نشانی وب «عملکرد برنامه شبکه» (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"«خود راهاندازی» بهصورت اجباری اجرا شود؟"</string>
+ <string name="request_org" msgid="8416693445448308975">"کد سازمان"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"شناسه پروتکل امنیتی UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"شناسه مجموعه رمزنگاری امنیت لایه انتقال"</string>
+ <string name="response_success" msgid="2469204471244527663">"احراز هویت «معماری خود راهاندازی فراگیر» (GBA) موفق بود؟"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"شناسه دلیل انجام نشدن"</string>
+ <string name="response_key" msgid="8839847772051686309">"کلید «معماری خود راهاندازی فراگیر» (GBA) (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"شناسه تبادل خود راهاندازی (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-fi/strings.xml b/testapps/GbaTestApp/res/values-fi/strings.xml
new file mode 100644
index 0000000..ad5862d
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-fi/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Asetukset"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Operaattorin määritys"</string>
+ <string name="label_service" msgid="2668963955237345578">"Palvelun määritys"</string>
+ <string name="label_test" msgid="8425079572898571918">"Testimääritys"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Käynnissä"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Sulje"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Testitulokset"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Nollaa"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Valmis"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA-palvelun paketin nimi"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Kuinka pian soittamisen jälkeen palvelu vapautetaan"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC-sovelluksen tyyppi"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"NAF-URL-osoite (Network Application Function)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Pakotetaanko käynnistäminen?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Organisaation koodi"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA-suojausprotokollan tunnus"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS-salauspaketin tunnus"</string>
+ <string name="response_success" msgid="2469204471244527663">"Onnistuiko GBA-todennus?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Epäonnistumisen syyn tunnus"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA-avain (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"B-TID (Bootstrapping Transaction Identifier)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-fr-rCA/strings.xml b/testapps/GbaTestApp/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..2c7abc6
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-fr-rCA/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Paramètres"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Configuration d\'opérateur"</string>
+ <string name="label_service" msgid="2668963955237345578">"Configuration de service"</string>
+ <string name="label_test" msgid="8425079572898571918">"Configuration de test"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"En cours d\'exécution…"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Quitter"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Résultat du test"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Réinitialiser"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"OK"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Nom de l\'ensemble de service GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Délai de libération du service après un appel"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Type d\'application UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL de fonction d\'application réseau (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Forcer l\'amorçage?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Code d\'organisation"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"Identifiant de protocole de sécurité UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"Identifiant de suite de chiffrement TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"Réussite d\'autorisation GBA?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Identifiant de motif d\'échec"</string>
+ <string name="response_key" msgid="8839847772051686309">"Clé GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Identifiant de transaction d\'amorçage (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-fr/strings.xml b/testapps/GbaTestApp/res/values-fr/strings.xml
new file mode 100644
index 0000000..a396bae
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-fr/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Paramètres"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Configuration de l\'opérateur"</string>
+ <string name="label_service" msgid="2668963955237345578">"Configuration du service"</string>
+ <string name="label_test" msgid="8425079572898571918">"Configuration de test"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"En cours d\'exécution"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Quitter"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Résultat du test"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Réinitialiser"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"OK"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Nom de package du service GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Intervalle entre l\'appel de l\'API et l\'activation du service"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Type d\'application UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL de la fonction d\'application réseau (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Forcer l\'amorçage ?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Code d\'organisation"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID de protocole de sécurité UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID de suite de chiffrement TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"Réussite de l\'authentification GBA ?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID du motif de l\'échec"</string>
+ <string name="response_key" msgid="8839847772051686309">"Clé GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Identifiant de transaction d\'amorçage (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-gl/strings.xml b/testapps/GbaTestApp/res/values-gl/strings.xml
new file mode 100644
index 0000000..d5547c7
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-gl/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Configuración"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Configuración do operador"</string>
+ <string name="label_service" msgid="2668963955237345578">"Configuración do servizo"</string>
+ <string name="label_test" msgid="8425079572898571918">"Configuración da proba"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"En execución"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Saír"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Resultado da proba"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Restablecer"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Feito"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Nome do paquete do servizo de GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Tempo que se tarda en desvincular o servizo despois da chamada"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Tipo de aplicación UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL da función de aplicacións de rede (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Queres forzar o arranque?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Código da organización"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"Código do protocolo de seguranza UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"Código do paquete de cifraxe TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"Completouse a autenticación de GBA?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Código do motivo de erro"</string>
+ <string name="response_key" msgid="8839847772051686309">"Chave de GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Identificador da transacción de arranque (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-gu/strings.xml b/testapps/GbaTestApp/res/values-gu/strings.xml
new file mode 100644
index 0000000..b43ecad
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-gu/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"સેટિંગ"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"મોબાઇલ ઑપરેટરની ગોઠવણી"</string>
+ <string name="label_service" msgid="2668963955237345578">"સેવાની ગોઠવણી"</string>
+ <string name="label_test" msgid="8425079572898571918">"પરીક્ષણની ગોઠવણી"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"ચાલે છે"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"બહાર નીકળો"</string>
+ <string name="label_test_result" msgid="892984695972956196">"પરીક્ષણનું પરિણામ"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"રીસેટ કરો"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"થઈ ગયું"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA સેવાનું પૅકેજિંગ નામ"</string>
+ <string name="service_release_time" msgid="532937496122890892">"કૉલ કર્યા પછી કેટલા સમયે સેવા રિલીઝ કરવી, તે સમયગાળો"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC ઍપ પ્રકાર"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"નેટવર્ક ઍપ્લિકેશન કાર્ય (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"શું બળજબરીથી બૂટસ્ટ્રેપિંગ કરીએ?"</string>
+ <string name="request_org" msgid="8416693445448308975">"સંસ્થાનો કોડ"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA સિક્યુરિટી પ્રોટોકૉલ ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS સાઇફર સ્યૂટ ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA Aપ્રમાણીકરણ સફળ થયું?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"નિષ્ફળતા કારણ ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA કી (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"બૂટસ્ટ્રેપિંગ વ્યવહાર ઓળખકર્તા (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-hi/strings.xml b/testapps/GbaTestApp/res/values-hi/strings.xml
new file mode 100644
index 0000000..442d65f
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-hi/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"सेटिंग"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"मोबाइल और इंटरनेट सेवा देने वाली कंपनी का कॉन्फ़िगरेशन"</string>
+ <string name="label_service" msgid="2668963955237345578">"सेवा का कॉन्फ़िगरेशन"</string>
+ <string name="label_test" msgid="8425079572898571918">"टेस्ट के लिए कॉन्फ़िगरेशन"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"चल रहा है"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"बाहर निकलें"</string>
+ <string name="label_test_result" msgid="892984695972956196">"टेस्ट का नतीजा"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"रीसेट करें"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"हो गया"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA सेवा के पैकेज का नाम"</string>
+ <string name="service_release_time" msgid="532937496122890892">"प्रोग्राम चालू करने के बाद, सेवा शुरू होने में कितना समय लगेगा"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC ऐप्लिकेशन टाइप"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"नेटवर्क ऐप्लिकेशन फ़ंक्शन (NAF) यूआरएल"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"क्या आप ज़बरदस्ती बूटस्ट्रैपिंग करना चाहते हैं?"</string>
+ <string name="request_org" msgid="8416693445448308975">"संगठन कोड"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA सिक्योरिटी प्रोटोकॉल आईडी"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS साइफ़र सुइट आईडी"</string>
+ <string name="response_success" msgid="2469204471244527663">"क्या GBA की पुष्टि हुई?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ऐप्लिकेशन लोड नहीं हो पाने की वजह का आईडी"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA कुंजी (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"बूटस्ट्रैपिंग ट्रांज़ैक्शन आइडेंटिफ़ायर (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-hr/strings.xml b/testapps/GbaTestApp/res/values-hr/strings.xml
new file mode 100644
index 0000000..4215e31
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-hr/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Postavke"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Konfiguracija mobilnog operatera"</string>
+ <string name="label_service" msgid="2668963955237345578">"Konfiguracija usluge"</string>
+ <string name="label_test" msgid="8425079572898571918">"Konfiguracija testa"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"U tijeku"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Izlaz"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Rezultat testa"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Poništi"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Gotovo"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Naziv paketa usluge GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Vrijeme do aktivacije usluge nakon poziva"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Vrsta aplikacije UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL funkcije mrežne aplikacije (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Želite li prisilno pokrenuti program?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Organizacijski kod"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID sigurnosnog protokola UA-a"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID paketa šifriranja za TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"Uspješna autentifikacija GBA-a?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID razloga neuspjeha"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA ključ (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Identifikator transakcije pokretanja programa (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-hu/strings.xml b/testapps/GbaTestApp/res/values-hu/strings.xml
new file mode 100644
index 0000000..a574220
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-hu/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Beállítások"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Szolgáltatói konfiguráció"</string>
+ <string name="label_service" msgid="2668963955237345578">"Szolgáltatáskonfiguráció"</string>
+ <string name="label_test" msgid="8425079572898571918">"Tesztkonfiguráció"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Fut"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Kilépés"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Teszteredmények"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Alaphelyzet"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Kész"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"A GBA-szolgáltatás csomagneve"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Ennyi idő múlva szabaduljon fel a szolgáltatás a hívás után"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC-alkalmazástípus"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Hálózati alkalmazásfunkció (NAF) URL-címe"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Kényszeríti a rendszerbetöltést?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Szervezet kódja"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA biztonsági protokoll azonosítója"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS titkosítási csomag azonosítója"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA-hitelesítés sikeres?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Hiba okának azonosítója"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA-kulcs (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Rendszertöltési tranzakció azonosítója (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-hy/strings.xml b/testapps/GbaTestApp/res/values-hy/strings.xml
new file mode 100644
index 0000000..7c306c1
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-hy/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Կարգավորումներ"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Օպերատորի կարգավորում"</string>
+ <string name="label_service" msgid="2668963955237345578">"Ծառայության կարգավորում"</string>
+ <string name="label_test" msgid="8425079572898571918">"Փորձարկման կարգավորում"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Աշխատանք"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Դուրս գալ"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Փորձարկման արդյունքը"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Վերակայել"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Պատրաստ է"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA ծառայության փաթեթի անունը"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Որքան ժամանակով դադարեցնել ծառայությունը զանգից հետո"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC հավելվածի տեսակը"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Ցանցային հավելվածի գործառույթի (Network application function, NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Հարկադրե՞լ բութսթրափ"</string>
+ <string name="request_org" msgid="8416693445448308975">"Կազմակերպության կոդը"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA անվտանգության հաղորդակարգի ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS ծածկագրերի հավաքածուի ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA նույնականացումը հաջողվե՞ց"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Սխալի պատճառի ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA բանալի (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Բութսթրափի գործարքի նույնացուցիչ (Bootstrapping Transaction Identifier, B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-in/strings.xml b/testapps/GbaTestApp/res/values-in/strings.xml
new file mode 100644
index 0000000..cd5b0a8
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-in/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Setelan"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Konfigurasi Operator"</string>
+ <string name="label_service" msgid="2668963955237345578">"Konfigurasi Layanan"</string>
+ <string name="label_test" msgid="8425079572898571918">"Konfigurasi Pengujian"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Berjalan"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Keluar"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Hasil Uji"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Reset"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Selesai"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Nama paket layanan GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Berapa lama merilis layanan setelah panggilan"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Jenis Aplikasi UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL fungsi aplikasi jaringan (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Paksa Bootstrap?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Kode Organisasi"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID Protokol Keamanan UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID TLS Cipher Suite"</string>
+ <string name="response_success" msgid="2469204471244527663">"Autentikasi GBA Berhasil?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID Alasan Gagal"</string>
+ <string name="response_key" msgid="8839847772051686309">"Tombol GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"ID Transaksi Bootstrap (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-is/strings.xml b/testapps/GbaTestApp/res/values-is/strings.xml
new file mode 100644
index 0000000..926d220
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-is/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Stillingar"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Grunnstilling símafyrirtækis"</string>
+ <string name="label_service" msgid="2668963955237345578">"Grunnstilling þjónustu"</string>
+ <string name="label_test" msgid="8425079572898571918">"Grunnstilling prófs"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Í gangi"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Hætta"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Prófniðurstöður"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Endurstilla"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Lokið"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Pakkaheiti GBA-þjónustu"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Hversu lengi skal losa þjónustu eftir kall"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC-forritsgerð"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Vefslóð NAF-þjónustu (Network application function)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Þvinga ræsingu?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Fyrirtækiskóði"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"Auðkenni UA-öryggisreglu"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"Auðkenni TLS-dulritunarsafns"</string>
+ <string name="response_success" msgid="2469204471244527663">"Tókst GBA-auðkenning?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Auðkenni ástæðu mistaka"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA-lykill (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"B-TID-auðkenni (Bootstrapping Transaction Identifier)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-it/strings.xml b/testapps/GbaTestApp/res/values-it/strings.xml
new file mode 100644
index 0000000..f290a5b
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-it/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Impostazioni"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Configurazione operatore"</string>
+ <string name="label_service" msgid="2668963955237345578">"Configurazione servizio"</string>
+ <string name="label_test" msgid="8425079572898571918">"Configurazione test"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"In esecuzione"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Esci"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Risultato test"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Reimposta"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Fine"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Nome pacchetto del servizio GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Intervallo di tempo tra la chiamata dell\'API e l\'attivazione del servizio"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Tipo di app UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL NAF (Network Application Function)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Forzare il bootstrapping?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Codice organizzazione"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID protocollo sicurezza UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID suite di crittografia TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"Autenticazione GBA riuscita?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID motivo errore"</string>
+ <string name="response_key" msgid="8839847772051686309">"Chiave GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Identificatore transazione bootstrapping (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-iw/strings.xml b/testapps/GbaTestApp/res/values-iw/strings.xml
new file mode 100644
index 0000000..76412fa
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-iw/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"הגדרות"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"הגדרת הספק"</string>
+ <string name="label_service" msgid="2668963955237345578">"הגדרת השירות"</string>
+ <string name="label_test" msgid="8425079572898571918">"הגדרת הבדיקה"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"בפעולה"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"יציאה"</string>
+ <string name="label_test_result" msgid="892984695972956196">"תוצאת הבדיקה"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"איפוס"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"סיום"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"שם החבילה של שירות GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"תוך כמה זמן לשחרר את השירות אחרי השיחה?"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"סוג אפליקציית UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"כתובת ה-URL של פונקציית היישום של הרשת (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"לאלץ אתחול מסוג Bootstrapping?"</string>
+ <string name="request_org" msgid="8416693445448308975">"קוד הארגון"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"מזהה פרוטוקול אבטחה של UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"מזהה חבילת הצפנה של TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"האם אימות GBA בוצע בהצלחה?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"מזהה סיבת כישלון"</string>
+ <string name="response_key" msgid="8839847772051686309">"מפתח GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"מזהה טרנזקציית אתחול מסוג Bootstrapping (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-ja/strings.xml b/testapps/GbaTestApp/res/values-ja/strings.xml
new file mode 100644
index 0000000..7cb53e8
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-ja/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"設定"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"携帯通信会社の設定"</string>
+ <string name="label_service" msgid="2668963955237345578">"サービスの設定"</string>
+ <string name="label_test" msgid="8425079572898571918">"テストの設定"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"実行中"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"終了"</string>
+ <string name="label_test_result" msgid="892984695972956196">"テスト結果"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"リセット"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"完了"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA サービスのパッケージ名"</string>
+ <string name="service_release_time" msgid="532937496122890892">"呼び出してからサービスがリリースされるまでの時間"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC アプリの種類"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"ネットワーク アプリケーション ファンクション(NAF)の URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"ブートストラップを強制実行しますか?"</string>
+ <string name="request_org" msgid="8416693445448308975">"組織コード"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA セキュリティ プロトコル ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS 暗号スイート ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA 認証は成功しましたか?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"失敗理由 ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA キー(CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"ブートストラップ トランザクション識別子(B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-ka/strings.xml b/testapps/GbaTestApp/res/values-ka/strings.xml
new file mode 100644
index 0000000..2baf3f6
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-ka/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"პარამეტრები"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"კურიერის კონფიგურაცია"</string>
+ <string name="label_service" msgid="2668963955237345578">"სერვისის კონფიგურაცია"</string>
+ <string name="label_test" msgid="8425079572898571918">"ტესტის კონფიგურაცია"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"სირბილი"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"გასვლა"</string>
+ <string name="label_test_result" msgid="892984695972956196">"ტესტის შედეგი"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"გადაყენება"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"მზადაა"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA სერვისის პაკეტის ნომერი"</string>
+ <string name="service_release_time" msgid="532937496122890892">"რამდენ ხანში მოხდეს სერვისის გამოშვება დარეკვის შემდეგ"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC აპის ტიპი"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"ქსელური აპლიკაციის ფუნქციის (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"გსურთ Bootstrap-ის იძულებით გაშვება?"</string>
+ <string name="request_org" msgid="8416693445448308975">"ორგანიზაციის კოდი"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA უსაფრთხოების პროტოკოლის ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS შიფრების ნაკრების ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA ავტორიზაცია წარმატებულია?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"მარცხის მიზეზის ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA კლავიში (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Bootstrap ტრანსაქციის იდენტიფიკატორი (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-kk/strings.xml b/testapps/GbaTestApp/res/values-kk/strings.xml
new file mode 100644
index 0000000..a34d08d
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-kk/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Параметрлер"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Оператор конфигурациясы"</string>
+ <string name="label_service" msgid="2668963955237345578">"Қызмет конфигурациясы"</string>
+ <string name="label_test" msgid="8425079572898571918">"Сынақ конфигурациясы"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Жұмыс істеп тұр"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Шығу"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Сынақ нәтижесі"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Қайтару"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Дайын"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA қызметі пакетінің атауы"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Қоңырау шалғаннан кейін қызметті босату ұзақтығы"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC қолданба түрі"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Желілік қолданба функциясының (NAF) URL мекенжайы"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Бастапқы жүктеуді мәжбүрлеп орындау қажет пе?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Ұйым коды"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA қауіпсіздік протоколының идентификаторы"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS шифрлар жинағының идентификаторы"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA авторизациясы орындалды ма?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Ақаулық себебінің идентификаторы"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA кілті (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Бастапқы жүктеу транзакциясының идентификаторы (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-km/strings.xml b/testapps/GbaTestApp/res/values-km/strings.xml
new file mode 100644
index 0000000..390cef0
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-km/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"ការកំណត់"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"ការកំណត់រចនាសម្ព័ន្ធក្រុមហ៊ុនសេវាទូរសព្ទ"</string>
+ <string name="label_service" msgid="2668963955237345578">"ការកំណត់រចនាសម្ព័ន្ធសេវាកម្ម"</string>
+ <string name="label_test" msgid="8425079572898571918">"ការកំណត់រចនាសម្ព័ន្ធការធ្វើតេស្ត"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"កំពុងដំណើរការ"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"ចាកចេញ"</string>
+ <string name="label_test_result" msgid="892984695972956196">"លទ្ធផលធ្វើតេស្ត"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"កំណត់ឡើងវិញ"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"រួចរាល់"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"ឈ្មោះកញ្ចប់នៃសេវាកម្ម GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"រយៈពេលដែលត្រូវបញ្ចេញសេវាកម្ម បន្ទាប់ពីហៅទូរសព្ទ"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"ប្រភេទកម្មវិធី UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"មុខងារកម្មវិធីបណ្ដាញ (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"បង្ខំ Bootstrapping ឬ?"</string>
+ <string name="request_org" msgid="8416693445448308975">"កូដរៀបចំ"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"លេខសម្គាល់ប្រូតូកូលសុវត្ថិភាព UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"លេខសម្គាល់កម្មវិធីកញ្ចប់ TLS Cipher"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA Auth ជោគជ័យដែរទេ?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"លេខសម្គាល់មូលហេតុដែលមិនដំណើរការ"</string>
+ <string name="response_key" msgid="8839847772051686309">"គ្រាប់ចុច GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"ព័ត៌មានសម្គាល់ប្រតិបត្តិការ Bootstrapping (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-kn/strings.xml b/testapps/GbaTestApp/res/values-kn/strings.xml
new file mode 100644
index 0000000..d37f0f5
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-kn/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"ವಾಹಕ ಕಾನ್ಫಿಗ್"</string>
+ <string name="label_service" msgid="2668963955237345578">"ಸೇವೆ ಕಾನ್ಫಿಗ್"</string>
+ <string name="label_test" msgid="8425079572898571918">"ಪರೀಕ್ಷಾ ಕಾನ್ಫಿಗ್"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"ರನ್ನಿಂಗ್"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"ನಿರ್ಗಮಿಸಿ"</string>
+ <string name="label_test_result" msgid="892984695972956196">"ಪರೀಕ್ಷಾ ಫಲಿತಾಂಶ"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"ಮರುಹೊಂದಿಸಿ"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"ಮುಗಿದಿದೆ"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA ಸೇವೆಯ ಪ್ಯಾಕೇಜ್ ಹೆಸರು"</string>
+ <string name="service_release_time" msgid="532937496122890892">"ಕರೆ ಮಾಡಿದ ನಂತರ ಎಷ್ಟು ಸಮಯದವರೆಗೆ ಸೇವೆಯನ್ನು ಬಿಡುಗಡೆ ಮಾಡುವುದು"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC ಆ್ಯಪ್ ಪ್ರಕಾರ"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"ನೆಟ್ವರ್ಕ್ ಅಪ್ಲಿಕೇಶನ್ ಫಂಕ್ಷನ್ (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"ಬೂಟ್ ಸ್ಟ್ರಾಪಿಂಗ್ ಸ್ಥಗಿತಗೊಳಿಸುವುದೇ?"</string>
+ <string name="request_org" msgid="8416693445448308975">"ಸಂಸ್ಥೆ ಕೋಡ್"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA ಭದ್ರತಾ ಪ್ರೊಟೊಕಾಲ್ ಐಡಿ"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS ಸೈಫರ್ ಸೂಟ್ ಐಡಿ"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA ಪ್ರಮಾಣೀಕರಣ ಯಶಸ್ವಿಯಾಗಿದೆಯೇ?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ವಿಫಲವಾದ ಕಾರಣ ಐಡಿ"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA ಕೀ (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"ಬೂಟ್ ಸ್ಟ್ರಾಪಿಂಗ್ ವಹಿವಾಟು ಗುರುತಿಸುವಿಕೆ (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-ko/strings.xml b/testapps/GbaTestApp/res/values-ko/strings.xml
new file mode 100644
index 0000000..96ac378
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-ko/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"설정"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"이동통신사 구성"</string>
+ <string name="label_service" msgid="2668963955237345578">"서비스 구성"</string>
+ <string name="label_test" msgid="8425079572898571918">"테스트 구성"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"실행 중"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"종료"</string>
+ <string name="label_test_result" msgid="892984695972956196">"테스트 결과"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"초기화"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"완료"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA 서비스의 패키지 이름"</string>
+ <string name="service_release_time" msgid="532937496122890892">"통화 후 서비스 릴리스까지 시간"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC 앱 유형"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"네트워크 애플리케이션 기능(NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"부트스트랩을 강제 시작하시겠습니까?"</string>
+ <string name="request_org" msgid="8416693445448308975">"조직 코드"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA 보안 프로토콜 ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS Cipher Suite ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA 인증 성공 여부"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"실패 사유 ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA 키(CK 및 IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"B-TID(Bootstrapping Transaction Identifier)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-ky/strings.xml b/testapps/GbaTestApp/res/values-ky/strings.xml
new file mode 100644
index 0000000..bf5cd30
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-ky/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Жөндөөлөр"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Байланыш операторунун конфигурациясы"</string>
+ <string name="label_service" msgid="2668963955237345578">"Кызматтын конфигурациясы"</string>
+ <string name="label_test" msgid="8425079572898571918">"Сыноо конфигурациясы"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Аткарылууда"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Чыгуу"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Сыноо натыйжасы"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Кайра коюу"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Бүттү"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA кызматынын топтом аталышы"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Чалуудан кийин кызматты бошотуу убакыты"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC колдонмосунун түрү"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Тармактагы колдонмонун (NAF) URL функциясы"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Баштапкы жүктөө мажбурлап күйгүзүлсүнбү?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Уюмдун коду"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA коопсуздук протоколунун идентификатору"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS шифр топтомунун идентификатору"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA авторизациясы ишке аштыбы?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Ката себебинин идентификатору"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA ачкычы (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Баштапкы жүктөөнүн транзакция идентификатору (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-lo/strings.xml b/testapps/GbaTestApp/res/values-lo/strings.xml
new file mode 100644
index 0000000..d411d52
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-lo/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"ການຕັ້ງຄ່າ"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"ການຕັ້ງຄ່າຜູ້ໃຫ້ບໍລິການ"</string>
+ <string name="label_service" msgid="2668963955237345578">"ການຕັ້ງຄ່າການບໍລິການ"</string>
+ <string name="label_test" msgid="8425079572898571918">"ການຕັ້ງຄ່າການທົດສອບ"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"ການແລ່ນ"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"ອອກ"</string>
+ <string name="label_test_result" msgid="892984695972956196">"ຜົນການທົດສອບ"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"ຣີເຊັດ"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"ແລ້ວໆ"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"ຊື່ແພັກເກດຂອງການບໍລິການ GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"ໄລຍະເວລາທີ່ຈະໃຫ້ບໍລິການຫຼັງຈາກການໂທ"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"ປະເພດແອັບ UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL ຂອງຟັງຊັນແອັບພລິເຄຊັນເຄືອຂ່າຍ (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"ບັງຄັບໃຫ້ເລີ່ມຕົ້ນບໍ?"</string>
+ <string name="request_org" msgid="8416693445448308975">"ລະຫັດຂອງອົງການ"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID ໂປຣໂຕຄໍຄວາມປອດໄພ UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID ຂອງ TLS Cipher Suite"</string>
+ <string name="response_success" msgid="2469204471244527663">"ການກວດສອບສິດ GBA ສຳເລັດບໍ?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID ເຫດຜົນທີ່ບໍ່ສຳເລັດ"</string>
+ <string name="response_key" msgid="8839847772051686309">"ປຸ່ມ GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"ຕົວລະບຸທຸລະກຳການບັງຄັບເລີ່ມຕົ້ນ (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-lt/strings.xml b/testapps/GbaTestApp/res/values-lt/strings.xml
new file mode 100644
index 0000000..780166e
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-lt/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Nustatymai"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Operatoriaus konfigūracija"</string>
+ <string name="label_service" msgid="2668963955237345578">"Paslaugos konfigūracija"</string>
+ <string name="label_test" msgid="8425079572898571918">"Bandomoji konfigūracija"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Vykdoma"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Išeiti"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Bandymo rezultatas"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Nustatyti iš naujo"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Atlikta"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA paslaugos paketo pavadinimas"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Po kiek laiko atsieti paslaugą po skambučio"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC programos tipas"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Tinklo programos funkcijos (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Priverstinai paleisti pakopinį paleidimą?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Organizacijos kodas"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA saugos protokolo ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS šifrų komplekto ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA autentifikavimas sėkmingas?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Nesėkmės priežasties ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA raktas (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Pakopinio paleidimo operacijos identifikatorius (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-lv/strings.xml b/testapps/GbaTestApp/res/values-lv/strings.xml
new file mode 100644
index 0000000..2a2500a
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-lv/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Iestatījumi"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Mobilo sakaru operatora konfigurācija"</string>
+ <string name="label_service" msgid="2668963955237345578">"Pakalpojuma konfigurācija"</string>
+ <string name="label_test" msgid="8425079572898571918">"Testa konfigurācija"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Darbojas"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Iziet"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Testa rezultāts"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Atiestatīt"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Gatavs"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA pakalpojuma pakotnes nosaukums"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Cik ilgi atsaistīt pakalpojumu pēc izsaukšanas"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC lietotnes veids"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Tīkla lietojumprogrammas funkcijas (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Vai veikt piespiedu sākumpalaišanu?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Organizācijas kods"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA drošības protokola ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS šifru komplekta ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"Vai GBA autorizācija ir sekmīga?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Kļūdas iemesla ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA atslēga (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Sākumpalaišanas transakcijas identifikators (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-mk/strings.xml b/testapps/GbaTestApp/res/values-mk/strings.xml
new file mode 100644
index 0000000..2acc50a
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-mk/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Поставки"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Конфигурација на оператор"</string>
+ <string name="label_service" msgid="2668963955237345578">"Конфигурација на услуга"</string>
+ <string name="label_test" msgid="8425079572898571918">"Конфигурација за тестирање"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Running"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Излези"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Резултат од тестирање"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Ресетирај"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Готово"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Име на пакет на услуга на GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"За колку време се ослободува услугата по повикување"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Тип апликација UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL до функција на мрежна апликација (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Да се подигне присилно?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Код на организација"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID на безбедносен протокол на UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID на збир алгоритми на TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"Дали успеа автентицирањето на GBA?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID на причина за неуспешен обид"</string>
+ <string name="response_key" msgid="8839847772051686309">"Копче за GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Идентификатор на трансакција за подигање (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-ml/strings.xml b/testapps/GbaTestApp/res/values-ml/strings.xml
new file mode 100644
index 0000000..bc93aa3
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-ml/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"ക്രമീകരണം"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"കാരിയർ കോൺഫിഗറേഷൻ"</string>
+ <string name="label_service" msgid="2668963955237345578">"സർവീസ് കോൺഫിഗറേഷൻ"</string>
+ <string name="label_test" msgid="8425079572898571918">"ടെസ്റ്റ് കോൺഫിഗറേഷൻ"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"റൺ ചെയ്യുന്നു"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"പുറത്ത് കടക്കുക"</string>
+ <string name="label_test_result" msgid="892984695972956196">"ടെസ്റ്റ് ഫലം"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"റീസെറ്റ് ചെയ്യുക"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"പൂർത്തിയായി"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA സേവനത്തിന്റെ പാക്കേജ് പേര്"</string>
+ <string name="service_release_time" msgid="532937496122890892">"കോൾ ചെയ്തതിന് ശേഷം സേവനം റിലീസ് ചെയ്യാൻ എത്ര സമയമെടുക്കും"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC ആപ്പ് തരം"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"നെറ്റ്വർക്ക് അപ്ലിക്കേഷൻ ഫംഗ്ഷൻ (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"ബലം പ്രയോഗിച്ച് ബൂട്ട്സ്ട്രാപ്പിംഗ് ചെയ്യണോ?"</string>
+ <string name="request_org" msgid="8416693445448308975">"സ്ഥാപനത്തിന്റെ കോഡ്"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA സുരക്ഷാ പ്രോട്ടോക്കോൾ ഐഡി"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS സെെഫർ സ്വീറ്റ് ഐഡി"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA പരിശോധിച്ചുറപ്പിക്കൽ വിജയകരമായിരുന്നോ?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"പരാജയ കാരണ ഐഡി"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA കീ (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"ബൂട്ട്സ്ട്രാപ്പിംഗ് ട്രാൻസാക്ഷൻ ഐഡന്റിഫയർ (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-mn/strings.xml b/testapps/GbaTestApp/res/values-mn/strings.xml
new file mode 100644
index 0000000..0aa62b0
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-mn/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Тохиргоо"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Оператор компанийн тохируулга"</string>
+ <string name="label_service" msgid="2668963955237345578">"Үйлчилгээний тохируулга"</string>
+ <string name="label_test" msgid="8425079572898571918">"Туршилтын тохируулга"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Ажиллаж байна"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Гарах"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Туршилтын үр дүн"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Шинэчлэх"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Дууссан"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA-н үйлчилгээний багцын нэр"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Дуудлага хийсний дараа үйлчилгээг хэдий хугацааны дараа салгах вэ?"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC Аппын төрөл"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Сүлжээний аппликэйшний функц (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Хүчээр эхлүүлэх үү?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Байгууллагын код"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA-н Аюулгүй байдлын протоколын ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS-н Шифрийн цуглуулгын ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA-г амжилттай баталгаажуулсан уу?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Амжилтгүй болсон шалтгааны ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA-н түлхүүр (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Эхлүүлэх гүйлгээний таниулбар (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-mr/strings.xml b/testapps/GbaTestApp/res/values-mr/strings.xml
new file mode 100644
index 0000000..75aaf5f
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-mr/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"सेटिंग्ज"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"वाहक कॉंफिगरेशन"</string>
+ <string name="label_service" msgid="2668963955237345578">"सेवा कॉंफिगरेशन"</string>
+ <string name="label_test" msgid="8425079572898571918">"चाचणी कॉंफिगरेशन"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"रन करत आहे"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"बाहेर पडा"</string>
+ <string name="label_test_result" msgid="892984695972956196">"चाचणी परिणाम"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"रीसेट करा"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"पूर्ण झाले"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA सेवेच्या पॅकेजचे नाव"</string>
+ <string name="service_release_time" msgid="532937496122890892">"कॉलनंतर किती वेळाने रिलीझ होईल"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC ॲप प्रकार"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"नेटवर्क अॅप्लिकेशन फंक्शन (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"सक्तीने बूटस्ट्रॅपिंग करायचे का?"</string>
+ <string name="request_org" msgid="8416693445448308975">"संस्थेचा कोड"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA सुरक्षा प्रोटोकॉल आयडी"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS सायफर स्वीट आयडी"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA ऑथेंटिकेशन यशस्वी झाले का?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"अयशस्वी होण्याच्या कारणाचा आयडी"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA की (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"बूटस्ट्रॅपिंग ट्रांजॅक्शन आयडेंटिफायर (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-ms/strings.xml b/testapps/GbaTestApp/res/values-ms/strings.xml
new file mode 100644
index 0000000..35838b7
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-ms/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Tetapan"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Konfigurasi Pembawa"</string>
+ <string name="label_service" msgid="2668963955237345578">"Konfigurasi Perkhidmatan"</string>
+ <string name="label_test" msgid="8425079572898571918">"Konfigurasi Ujian"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Berjalan"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Keluar"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Keputusan Ujian"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Tetapkan semula"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Selesai"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Nama pakej perkhidmatan GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Tempoh untuk mengaktifkan perkhidmatan selepas panggilan"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Jenis Apl UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL fungsi aplikasi rangkaian (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Paksa Butstrap?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Kod Organisasi"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID Protokol Keselamatan UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID Suite Sifer TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"Pengesahan GBA Berjaya?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID Sebab Gagal"</string>
+ <string name="response_key" msgid="8839847772051686309">"Kunci GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Pengecam Transaksi Butstrap (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-my/strings.xml b/testapps/GbaTestApp/res/values-my/strings.xml
new file mode 100644
index 0000000..f39f77a
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-my/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"ဆက်တင်များ"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"ဖုန်းလိုင်း စီစဉ်သတ်မှတ်ချက်"</string>
+ <string name="label_service" msgid="2668963955237345578">"ဝန်ဆောင်မှု စီစဉ်သတ်မှတ်ချက်"</string>
+ <string name="label_test" msgid="8425079572898571918">"စမ်းသပ်မှု စီစဉ်သတ်မှတ်ချက်"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"လုပ်ဆောင်နေသည်"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"ထွက်ရန်"</string>
+ <string name="label_test_result" msgid="892984695972956196">"စမ်းသပ်မှုရလဒ်"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"ပြင်ဆင်သတ်မှတ်ရန်"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"ပြီးပြီ"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA ဝန်ဆောင်မှု၏ ပက်ကေ့ဂျ်အမည်"</string>
+ <string name="service_release_time" msgid="532937496122890892">"ခေါ်ဆိုပြီးနောက် ဝန်ဆောင်မှုကို ဖြန့်ချိရန် ကြာချိန်"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC အက်ပ်အမျိုးအစား"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"ကွန်ရက်အပလီကေးရှင်း လုပ်ဆောင်ချက် (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"စနစ်စတင်ခြင်းကို မဖြစ်မနေလုပ်မလား။"</string>
+ <string name="request_org" msgid="8416693445448308975">"အဖွဲ့အစည်းကုဒ်"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA လုံခြုံရေးပရိုတိုကော ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS Cipher Suite ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA Auth အောင်မြင်ပါသလား။"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"မအောင်မြင်မှု အကြောင်းရင်း ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA ကီး (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"စနစ်စတင်ခြင်းလုပ်ငန်း သတ်မှတ်မှုစနစ် (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-nb/strings.xml b/testapps/GbaTestApp/res/values-nb/strings.xml
new file mode 100644
index 0000000..cbcfcb8
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-nb/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Innstillinger"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Operatørkonfigurering"</string>
+ <string name="label_service" msgid="2668963955237345578">"Tjenestekonfigurering"</string>
+ <string name="label_test" msgid="8425079572898571918">"Testkonfigurering"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Kjører"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Lukk"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Testresultat"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Tilbakestill"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Ferdig"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Pakkenavn for GBA-tjeneste"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Hvor lenge tjenesten skal frigis etter kalling"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC-apptype"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Nettadresse til nettverksprogramfunksjon (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Vil du tvinge frem bruk av bootstrapping?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Organisasjonskode"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"Sikkerhetsprotokoll-ID for UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID for TLS-chifferpakke"</string>
+ <string name="response_success" msgid="2469204471244527663">"Lyktes GBA-autentisering?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Feilårsaks-ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA-nøkkel (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Identifikator for bootstrapping-transaksjon (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-ne/strings.xml b/testapps/GbaTestApp/res/values-ne/strings.xml
new file mode 100644
index 0000000..741c716
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-ne/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"सेटिङ"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"टेलिफोन कम्पनीसम्बन्धी कन्फिगुरेसन"</string>
+ <string name="label_service" msgid="2668963955237345578">"सेवासम्बन्धी कन्फिगुरेसन"</string>
+ <string name="label_test" msgid="8425079572898571918">"परीक्षणसम्बन्धी कन्फिगुरेसन"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"चलिरहेको छ"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"बाहिरिनुहोस्"</string>
+ <string name="label_test_result" msgid="892984695972956196">"परीक्षणको नतिजा"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"रिसेट गर्नुहोस्"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"सम्पन्न भयो"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA सेवाको प्याकेजको नाम"</string>
+ <string name="service_release_time" msgid="532937496122890892">"कल गरेपछि सेवा सुरु हुन कति समय लाग्ने छ"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC एपको प्रकार"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"नेटवर्क एप्लिकेसन फङ्सन (NAF) को URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"जबरजस्ती बुटस्ट्रयाप गर्ने हो?"</string>
+ <string name="request_org" msgid="8416693445448308975">"सङ्गठनको कोड"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA को सुरक्षा प्रोटोकोल ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS Cipher Suite ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA को पुष्टि गर्ने कार्य सफल भयो?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"कार्य असफल हुनुको कारणको ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA सुरक्षा साँचो (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"बुटस्ट्रयापिङ ट्रान्जेक्सन आइडेन्टिफायर (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-nl/strings.xml b/testapps/GbaTestApp/res/values-nl/strings.xml
new file mode 100644
index 0000000..3c8640f
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-nl/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Instellingen"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Providerconfiguratie"</string>
+ <string name="label_service" msgid="2668963955237345578">"Serviceconfiguratie"</string>
+ <string name="label_test" msgid="8425079572898571918">"Testconfiguratie"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Actief"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Sluiten"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Testresultaat"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Resetten"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Klaar"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Pakketnaam van GBA-service"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Hoelang de service moet worden vrijgegeven na de aanroep"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Type UICC-app"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"NAF-URL (Network application function)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Bootstrapping forceren?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Organisatiecode"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID van UA-beveiligingsprotocol"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID van Cipher Suite voor TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA-autorisatie geslaagd?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID voor reden van mislukking"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA-sleutel (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Bootstrapping Transaction Identifier (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-or/strings.xml b/testapps/GbaTestApp/res/values-or/strings.xml
new file mode 100644
index 0000000..456751d
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-or/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"ସେଟିଂସ୍"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"କ୍ୟାରିଅର୍ କନଫିଗରେସନ୍"</string>
+ <string name="label_service" msgid="2668963955237345578">"ସେବା କନଫିଗରେସନ୍"</string>
+ <string name="label_test" msgid="8425079572898571918">"ଟେଷ୍ଟ କନଫିଗରେସନ୍"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"ଚାଲୁଛି"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"ବାହାରି ଯାଆନ୍ତୁ"</string>
+ <string name="label_test_result" msgid="892984695972956196">"ଟେଷ୍ଟ ଫଳାଫଳ"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"ରିସେଟ୍ କରନ୍ତୁ"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"ହୋଇଗଲା"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA ସେବାର ପ୍ୟାକେଜ୍ ନାମ"</string>
+ <string name="service_release_time" msgid="532937496122890892">"କଲ୍ କରିବାର କେତେ ସମୟ ପରେ ସେବା ରିଲିଜ୍ ହେବ"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC ଆପ୍ ପ୍ରକାର"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"ନେଟୱାର୍କ ଆପ୍ଲିକେସନ୍ ଫଙ୍କସନ୍ (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"ବଳପୂର୍ବକ ବୁଟଷ୍ଟ୍ରାପିଂ କରିବେ?"</string>
+ <string name="request_org" msgid="8416693445448308975">"ସଂସ୍ଥାର କୋଡ୍"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA ସୁରକ୍ଷା ପ୍ରୋଟୋକଲ୍ ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS ସାଇଫର୍ ସୁଟ୍ ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA ଅଥୋରାଇଜେସନ୍ ସଫଳ?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ବିଫଳ ହେବାର କାରଣ ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA କୀ (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"ବୁଟଷ୍ଟ୍ରାପିଂ ଟ୍ରାଞ୍ଜେକସନ୍ ଚିହ୍ନଟକାରୀ (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-pa/strings.xml b/testapps/GbaTestApp/res/values-pa/strings.xml
new file mode 100644
index 0000000..068d8ab
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-pa/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"ਸੈਟਿੰਗਾਂ"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"ਕੈਰੀਅਰ ਦਾ ਸੰਰੂਪਣ"</string>
+ <string name="label_service" msgid="2668963955237345578">"ਸੇਵਾ ਦਾ ਸੰਰੂਪਣ"</string>
+ <string name="label_test" msgid="8425079572898571918">"ਜਾਂਚ ਦਾ ਸੰਰੂਪਣ"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"ਚੱਲ ਰਹੀ ਹੈ"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"ਬਾਹਰ ਜਾਓ"</string>
+ <string name="label_test_result" msgid="892984695972956196">"ਜਾਂਚ ਦਾ ਨਤੀਜਾ"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"ਰੀਸੈੱਟ ਕਰੋ"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"ਹੋ ਗਿਆ"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA ਸੇਵਾ ਦਾ ਪੈਕੇਜ ਨਾਮ"</string>
+ <string name="service_release_time" msgid="532937496122890892">"ਕਾਲ ਕਰਨ ਤੋਂ ਬਾਅਦ ਸੇਵਾ ਨੂੰ ਰਿਲੀਜ਼ ਕਰਨ ਵਿੱਚ ਕਿੰਨਾ ਸਮਾਂ ਲੱਗੇਗਾ"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC ਐਪ ਦੀ ਕਿਸਮ"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"ਨੈੱਟਵਰਕ ਐਪਲੀਕੇਸ਼ਨ ਫੰਕਸ਼ਨ (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"ਕੀ ਬੂਟਸਟ੍ਰੈਪਿੰਗ ਨੂੰ ਜ਼ਬਰਦਸਤੀ ਲਾਗੂ ਕਰਨਾ ਹੈ?"</string>
+ <string name="request_org" msgid="8416693445448308975">"ਸੰਸਥਾ ਕੋਡ"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA ਸੁਰੱਖਿਆ ਪ੍ਰੋਟੋਕੋਲ ਆਈਡੀ"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS ਸਾਈਫਰ ਸੂਟ ਆਈਡੀ"</string>
+ <string name="response_success" msgid="2469204471244527663">"ਕੀ GBA ਪ੍ਰਮਾਣੀਕਰਨ ਵਿੱਚ ਸਫਲਤਾ ਪ੍ਰਾਪਤ ਹੋਈ?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ਅਸਫਲਤਾ ਕਾਰਨ ਆਈਡੀ"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA ਕੁੰਜੀ (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"ਬੂਟਸਟ੍ਰੈਪਿੰਗ ਲੈਣ-ਦੇਣ ਪਛਾਣਕਰਤਾ (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-pl/strings.xml b/testapps/GbaTestApp/res/values-pl/strings.xml
new file mode 100644
index 0000000..9fca28f
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-pl/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Ustawienia"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Konfiguracja operatora"</string>
+ <string name="label_service" msgid="2668963955237345578">"Konfiguracja usługi"</string>
+ <string name="label_test" msgid="8425079572898571918">"Konfiguracja testu"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Działa"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Wyjdź"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Wyniki testu"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Resetuj"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Gotowe"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Nazwa pakietu usługi GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Po jakim czasie od połączenia dopuścić usługę"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Typ aplikacji UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL funkcji aplikacji sieciowych (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Wymusić Bootstrapping?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Kod organizacji"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"Identyfikator protokołu bezpieczeństwa UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"Identyfikator TLS Cipher Suite"</string>
+ <string name="response_success" msgid="2469204471244527663">"Uwierzytelnianie GBA zakończone powodzeniem?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Identyfikator powodu niepowodzenia"</string>
+ <string name="response_key" msgid="8839847772051686309">"Klucz GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Identyfikator transakcji Bootstrapping (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-pt-rPT/strings.xml b/testapps/GbaTestApp/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..715aabb
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-pt-rPT/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Definições"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Configuração do operador"</string>
+ <string name="label_service" msgid="2668963955237345578">"Configuração do serviço"</string>
+ <string name="label_test" msgid="8425079572898571918">"Configuração do teste"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Em execução"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Sair"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Resultado do teste"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Repor"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Concluído"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Nome do pacote do serviço GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Tempo de libertação do serviço após a chamada"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Tipo de app UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL da função de aplicação de rede (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Forçar bootstrapping?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Código de organização"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID de protocolo de segurança UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID de conjunto de cifra TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"Sucesso na autenticação GBA?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID do motivo da falha"</string>
+ <string name="response_key" msgid="8839847772051686309">"Chave GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Identificador de transação de bootstrapping (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-pt/strings.xml b/testapps/GbaTestApp/res/values-pt/strings.xml
new file mode 100644
index 0000000..cf4ea72
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-pt/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Configurações"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Configurações da operadora"</string>
+ <string name="label_service" msgid="2668963955237345578">"Configurações do serviço"</string>
+ <string name="label_test" msgid="8425079572898571918">"Configurações de teste"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Executando"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Sair"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Resultado do teste"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Redefinir"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Concluído"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Nome do pacote de serviços da GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Tempo para liberar o serviço após a chamada"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Tipo de app UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL da função de aplicativo de rede (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Forçar inicialização?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Código da organização"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID do protocolo de segurança UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID do pacote de criptografia TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"Autenticação da GBA concluída?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID do motivo da falha"</string>
+ <string name="response_key" msgid="8839847772051686309">"Chave de segurança da GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Identificador de transações de inicialização (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-ro/strings.xml b/testapps/GbaTestApp/res/values-ro/strings.xml
new file mode 100644
index 0000000..d7f9e7b
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-ro/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Setări"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Configurația operatorului"</string>
+ <string name="label_service" msgid="2668963955237345578">"Configurația serviciului"</string>
+ <string name="label_test" msgid="8425079572898571918">"Configurație de testare"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Rulează"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Ieșiți"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Rezultatul testului"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Resetați"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Terminat"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Numele de pachet al serviciului GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Durata până la lansarea serviciului după apel"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Tipul aplicației UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Adresa URL pentru funcția aplicației pentru rețea (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Forțați inițializarea?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Cod organizațional"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID-ul protocolului de securitate pentru Google Analytics universal"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID-ul suitei TLS Cipher"</string>
+ <string name="response_success" msgid="2469204471244527663">"Succes la autentificarea GBA?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID-ul motivului eșecului"</string>
+ <string name="response_key" msgid="8839847772051686309">"Cheie GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Se inițializează identificatorul tranzacției (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-ru/strings.xml b/testapps/GbaTestApp/res/values-ru/strings.xml
new file mode 100644
index 0000000..a5d0bff
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-ru/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Настройки"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Конфигурация оператора связи"</string>
+ <string name="label_service" msgid="2668963955237345578">"Конфигурация сервиса"</string>
+ <string name="label_test" msgid="8425079572898571918">"Тестовая конфигурация"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Выполняется"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Выйти"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Результат тестирования"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Сбросить"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Готово"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Название пакета сервиса GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Через какое время освобождать сервис после вызова"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Тип приложения UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL функции сетевого приложения (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Включить начальную загрузку принудительно?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Код организации"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"Идентификатор протокола безопасности UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"Идентификатор шифронабора TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"Авторизация GBA выполнена?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Идентификатор причины сбоя"</string>
+ <string name="response_key" msgid="8839847772051686309">"Ключ GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Идентификатор транзакции начальной загрузки (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-si/strings.xml b/testapps/GbaTestApp/res/values-si/strings.xml
new file mode 100644
index 0000000..7a66d1f
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-si/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"සැකසීම්"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"වාහකය වින්යාසය"</string>
+ <string name="label_service" msgid="2668963955237345578">"සේවා වින්යාසය"</string>
+ <string name="label_test" msgid="8425079572898571918">"පරීක්ෂණ වින්යාසය"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"ධාවනය වේ"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"පිටවන්න"</string>
+ <string name="label_test_result" msgid="892984695972956196">"පරීක්ෂණ ප්රතිඵලය"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"යළි පිහිටුවන්න"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"නිමයි"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA සේවාවෙහි පැකේජයේ නම"</string>
+ <string name="service_release_time" msgid="532937496122890892">"ඇමතුමෙන් පසු සේවාව මුදා හැරීමට කාලය"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC යෙදුම් වර්ගය"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"ජාල යෙදුම් කාර්යය (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"ඇරඹුම් පටිය බලාත්මක කරන්න ද?"</string>
+ <string name="request_org" msgid="8416693445448308975">"සංවිධාන කේතය"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA ආරක්ෂක ප්රොටොකෝල ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS කේතාංක කට්ටල ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA සත්යාපනය සාර්ථක ද?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"අසමත් හේතුව ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA යතුර (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"ඇරඹුම් පටි ගනුදෙනු හැඳුනුම්කාරකය (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-sk/strings.xml b/testapps/GbaTestApp/res/values-sk/strings.xml
new file mode 100644
index 0000000..17b788f
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-sk/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Nastavenia"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Konfigurácia operátora"</string>
+ <string name="label_service" msgid="2668963955237345578">"Konfigurácia služby"</string>
+ <string name="label_test" msgid="8425079572898571918">"Konfigurácia testu"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Spustené"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Ukončiť"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Výsledok testu"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Resetovať"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Hotovo"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Názov balíka služby GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Ako dlho trvá uvoľnenie služby po volaní"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Typ aplikácie UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Webová adresa funkcie NAF"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Chcete vynútiť bootstrapping?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Kód organizácie"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"Identifikátor bezpečnostného protokolu UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"Identifikátor šifrovacieho balíka TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"Bola autorizácia GBA úspešná?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Identifikátor dôvodu zlyhania"</string>
+ <string name="response_key" msgid="8839847772051686309">"Kľúč GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Bootstrappingový identifikátor transakcie (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-sl/strings.xml b/testapps/GbaTestApp/res/values-sl/strings.xml
new file mode 100644
index 0000000..6e3196b
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-sl/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Nastavitve"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Konfiguracija operaterja"</string>
+ <string name="label_service" msgid="2668963955237345578">"Konfiguracija storitve"</string>
+ <string name="label_test" msgid="8425079572898571918">"Preizkusi konfiguracijo"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Se izvaja"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Zapri"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Rezultat preizkusa"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Ponastavi"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Končano"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Ime paketa storitve GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Čas, preden se storitev aktivira po klicanju"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Vrsta aplikacije UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL funkcije uporabe v omrežju (NAF – Network application function)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Želite vsiliti zagon?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Koda organizacije"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID varnostnega protokola za UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID zbirke šifriranja TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"Je preverjanje pristnosti GBA uspelo?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID vzroka za napako"</string>
+ <string name="response_key" msgid="8839847772051686309">"Ključ GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Identifikator transakcije zagona (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-sq/strings.xml b/testapps/GbaTestApp/res/values-sq/strings.xml
new file mode 100644
index 0000000..402345e
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-sq/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Cilësimet"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Konfigurimi i operatorit celular"</string>
+ <string name="label_service" msgid="2668963955237345578">"Konfigurimi i shërbimit"</string>
+ <string name="label_test" msgid="8425079572898571918">"Konfigurimi i testit"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Në ekzekutim"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Dil"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Rezultati i testit"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Rivendos"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"U krye"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Emri i paketës së shërbimit GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Koha për lirimin e shërbimit pas thirrjes"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Lloji i aplikacionit UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL-ja e funksionit të aplikacionit të rrjetit (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Të detyrohet nisja automatike?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Kodi i organizatës"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ID-ja e protokollit të sigurisë UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ID-ja e paketës së shifrimit TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"Vërtetimi i shërbimit GBA me sukses?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID-ja e arsyes së dështimit"</string>
+ <string name="response_key" msgid="8839847772051686309">"Çelësi GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Identifikuesi i transaksionit të nisjes automatike (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-sr/strings.xml b/testapps/GbaTestApp/res/values-sr/strings.xml
new file mode 100644
index 0000000..9d8cbd6
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-sr/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Подешавања"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Конфигурација мобилног оператера"</string>
+ <string name="label_service" msgid="2668963955237345578">"Конфигурација услуге"</string>
+ <string name="label_test" msgid="8425079572898571918">"Пробна конфигурација"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Активно"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Затвори"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Резултат теста"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Ресетуј"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Готово"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Назив пакета услуге GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Време док се услуга не активира после позива"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Тип UICC апликације"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL функције мрежне апликације (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Желите ли да наметнете самостално узорковање?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Кôд организације"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"ИД безбедносног протокола универзалне аналитике"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"ИД TLS пакета за шифровање"</string>
+ <string name="response_success" msgid="2469204471244527663">"Да ли је потврда идентитета за GBA успела?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ИД разлога за неуспех"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA тастер (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Идентификатор трансакције за самостално узорковање (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-sv/strings.xml b/testapps/GbaTestApp/res/values-sv/strings.xml
new file mode 100644
index 0000000..d228f8e
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-sv/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Inställningar"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Operatörskonfiguration"</string>
+ <string name="label_service" msgid="2668963955237345578">"Tjänstkonfiguration"</string>
+ <string name="label_test" msgid="8425079572898571918">"Testkonfiguration"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Körs"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Stäng"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Testresultat"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Återställ"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Klar"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA-tjänstens paketnamn"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Hur länge tjänsten frigörs efter uppringning"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Apptyp UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"NAF-URL (Network application function)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Tvinga start?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Organisationsnummer"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA säkerhetsprotokoll-ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS chiffersvit-ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA-aut. slutförd?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Felorsaks-ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA-nyckel (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Bootstrapping Transaction Identifier (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-sw/strings.xml b/testapps/GbaTestApp/res/values-sw/strings.xml
new file mode 100644
index 0000000..9dcd5cb
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-sw/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Mipangilio"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Mipangilio ya Mtoa huduma"</string>
+ <string name="label_service" msgid="2668963955237345578">"Mipangilio ya Huduma"</string>
+ <string name="label_test" msgid="8425079572898571918">"Mipangilio ya Jaribio"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Inatekeleza"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Funga"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Matokeo ya Jaribio"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Weka upya"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Imemaliza"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Jina la kifurushi cha huduma ya GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Muda wa kutoa huduma baada ya kupiga simu"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Aina ya Programu ya UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL ya kipengele cha programu ya mtandao (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Ungependa Kulazimisha Bootstrapping?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Msimbo wa Shirika"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"Kitambulisho cha Itifaki ya Usalama cha UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"Kitambulisho cha TLS cha Cipher Suite"</string>
+ <string name="response_success" msgid="2469204471244527663">"Je, GBA Auth Imefaulu?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Kitambulisho cha Sababu ya Kufeli"</string>
+ <string name="response_key" msgid="8839847772051686309">"Ufunguo wa GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Kitambulishi cha Shughuli za Utatuzi kwa Rasilimali Zilizopo (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-ta/strings.xml b/testapps/GbaTestApp/res/values-ta/strings.xml
new file mode 100644
index 0000000..147078e
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-ta/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"அமைப்புகள்"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"மொபைல் நிறுவன உள்ளமைவு"</string>
+ <string name="label_service" msgid="2668963955237345578">"சேவை உள்ளமைவு"</string>
+ <string name="label_test" msgid="8425079572898571918">"சோதனை உள்ளமைவு"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"இயங்குகிறது"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"வெளியேறு"</string>
+ <string name="label_test_result" msgid="892984695972956196">"சோதனை முடிவு"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"மீட்டமை"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"முடிந்தது"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA சேவையின் தொகுப்புப் பெயர்"</string>
+ <string name="service_release_time" msgid="532937496122890892">"அழைத்தபிறகு எவ்வளவு நேரத்தில் சேவை வெளியிடப்படும்?"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC ஆப்ஸ் வகை"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"நெட்வொர்க் ஆப்ஸ் செயல்பாட்டின் (NAF - Network application function) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"உடனே பூட்ஸ்ட்ராப்பிங் செய்யவா?"</string>
+ <string name="request_org" msgid="8416693445448308975">"நிறுவனக் குறியீடு"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA பாதுகாப்பு நெறிமுறை ஐடி"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS சைஃபர் தொகுப்பு ஐடி"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA அங்கீகரிக்கப்பட்டதா?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"தோல்வியின் காரணத்திற்கான ஐடி"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA விசை (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"பூட்ஸ்ட்ராப்பிங் பரிமாற்ற அடையாளங்காட்டி (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-te/strings.xml b/testapps/GbaTestApp/res/values-te/strings.xml
new file mode 100644
index 0000000..7434bca
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-te/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"సెట్టింగ్లు"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"క్యారియర్ కాన్ఫిగరేషన్"</string>
+ <string name="label_service" msgid="2668963955237345578">"సర్వీస్ కాన్ఫిగరేషన్"</string>
+ <string name="label_test" msgid="8425079572898571918">"పరీక్ష కాన్ఫిగరేషన్"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"రన్ అవుతోంది"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"నిష్క్రమించు"</string>
+ <string name="label_test_result" msgid="892984695972956196">"పరీక్ష ఫలితం"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"రీసెట్ చేయి"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"పూర్తయింది"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA సర్వీస్ ప్యాకేజీ పేరు"</string>
+ <string name="service_release_time" msgid="532937496122890892">"కాల్ చేసిన తర్వాత సర్వీస్ను రిలీజ్ చేయడానికి ఎంత సమయం పడుతుంది"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC యాప్ రకం"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"నెట్వర్క్ యాప్ ఫంక్షన్ (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"బూట్స్ట్రాపింగ్ ఫోర్స్ చేయాలా?"</string>
+ <string name="request_org" msgid="8416693445448308975">"సంస్థ కోడ్"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA సెక్యూరిటీ ప్రోటోకాల్ ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS సైఫర్ Suite ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA Auth విజయవంతమైందా?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"వైఫల్యానికి కారణమైన ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA కీ (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"బూట్స్ట్రాపింగ్ ట్రాన్జాక్షన్ ఐడెంటిఫయర్ (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-th/strings.xml b/testapps/GbaTestApp/res/values-th/strings.xml
new file mode 100644
index 0000000..4e58913
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-th/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"การตั้งค่า"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"กำหนดผู้ให้บริการ"</string>
+ <string name="label_service" msgid="2668963955237345578">"กำหนดบริการ"</string>
+ <string name="label_test" msgid="8425079572898571918">"กำหนดการทดสอบ"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"วิ่ง"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"ออก"</string>
+ <string name="label_test_result" msgid="892984695972956196">"ผลการทดสอบ"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"รีเซ็ต"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"เสร็จ"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"ชื่อแพ็กเกจบริการ GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"ระยะเวลาที่จะให้บริการหลังจากโทร"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"ประเภทแอป UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL ของ Network Application Function (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"บังคับให้เริ่มต้นไหม"</string>
+ <string name="request_org" msgid="8416693445448308975">"รหัสองค์กร"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"รหัสโปรโตคอลความปลอดภัยของ UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"รหัสของ TLS Cipher Suite"</string>
+ <string name="response_success" msgid="2469204471244527663">"การตรวจสอบสิทธิ์ GBA สำเร็จไหม"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"รหัสเหตุผลที่ล้มเหลว"</string>
+ <string name="response_key" msgid="8839847772051686309">"คีย์ GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Bootstrapping Transaction Identifier (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-tl/strings.xml b/testapps/GbaTestApp/res/values-tl/strings.xml
new file mode 100644
index 0000000..d96464b
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-tl/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Mga Setting"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Config ng Carrier"</string>
+ <string name="label_service" msgid="2668963955237345578">"Config ng Serbisyo"</string>
+ <string name="label_test" msgid="8425079572898571918">"Config ng Pagsubok"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Tumatakbo"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Lumabas"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Resulta ng Pagsubok"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"I-reset"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Tapos na"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Pangalan ng package ng serbisyo ng GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Gaano katagal ire-release ang serbisyo pagkatapos ng pagtawag"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Uri ng UICC App"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL ng Network application function (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Puwersahin ang Pag-bootstrap?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Organization Code"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"Security Protocol ID ng UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"Suite ID ng TLS Cipher"</string>
+ <string name="response_success" msgid="2469204471244527663">"Matagumpay ba ang GBA Auth?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ID ng Dahilan ng Pagkabigo"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA Key (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Bootstrapping Transaction Identifier (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-tr/strings.xml b/testapps/GbaTestApp/res/values-tr/strings.xml
new file mode 100644
index 0000000..8078563
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-tr/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Ayarlar"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Operatör Yapılandırması"</string>
+ <string name="label_service" msgid="2668963955237345578">"Hizmet Yapılandırması"</string>
+ <string name="label_test" msgid="8425079572898571918">"Test Yapılandırması"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Çalışıyor"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Çık"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Test Sonucu"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Sıfırla"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Bitti"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA hizmetinin paket adı"</string>
+ <string name="service_release_time" msgid="532937496122890892">"API çağrısından sonra hizmetin ne süreyle serbest bırakılacağı"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC Uygulaması Türü"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Ağ uygulaması işlevi (NAF) URL\'si"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Önyüklemeye Zorlansın mı?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Kuruluş Kodu"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA Güvenlik Protokolü Kimliği"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS Şifre Paketi Kimliği"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA Kimlik Doğrulaması Başarılı mı?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Hata Nedeni Kimliği"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA Anahtarı (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Önyükleme İşlem Tanımlayıcısı (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-uk/strings.xml b/testapps/GbaTestApp/res/values-uk/strings.xml
new file mode 100644
index 0000000..fa2e23c
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-uk/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Налаштування"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Конфігурація оператора"</string>
+ <string name="label_service" msgid="2668963955237345578">"Конфігурація сервісу"</string>
+ <string name="label_test" msgid="8425079572898571918">"Конфігурація перевірки"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Виконується"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Вийти"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Результат перевірки"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Скинути"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Готово"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Назва пакета сервісу GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Скільки має працювати сервіс після виклику"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Тип додатка UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL-адреса функції додатка мережі (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Ініціювати початкове завантаження примусово?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Код організації"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"Ідентифікатор протоколу безпеки UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"Ідентифікатор пакета шифрування TLS"</string>
+ <string name="response_success" msgid="2469204471244527663">"Чи успішно пройшла автентифікація GBA?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Ідентифікатор причини помилки"</string>
+ <string name="response_key" msgid="8839847772051686309">"Ключ GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Ідентифікатор транзакції початкового завантаження (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-ur/strings.xml b/testapps/GbaTestApp/res/values-ur/strings.xml
new file mode 100644
index 0000000..26e5c04
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-ur/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"ترتیبات"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"کیریئر کنفیگریشن"</string>
+ <string name="label_service" msgid="2668963955237345578">"سروس کنفیگریشن"</string>
+ <string name="label_test" msgid="8425079572898571918">"ٹیسٹ کنفیگریشن"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"دوڑ"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"باہر نکلیں"</string>
+ <string name="label_test_result" msgid="892984695972956196">"ٹیسٹ کا نتیجہ"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"ری سیٹ کریں"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"ہو گیا"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA سروس کا پیکیج نام"</string>
+ <string name="service_release_time" msgid="532937496122890892">"فون کرنے کے بعد سروس کب تک ریلیز کی جائے"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC ایپ کی قسم"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"نیٹ ورک ایپلیکیشن فنکشن (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"بوٹ اسٹراپنگ فورس کریں؟"</string>
+ <string name="request_org" msgid="8416693445448308975">"آرگنائزیشن کوڈ"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA سکیورٹی پروٹوکول ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS Cipher سوئیٹ ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA کی توثیق کامیاب کریں؟"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"ناکامی کی وجہ ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA کلید (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"بوٹ اسٹراپنگ ٹرانزیکشن شناخت کار (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-uz/strings.xml b/testapps/GbaTestApp/res/values-uz/strings.xml
new file mode 100644
index 0000000..369cfaa
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-uz/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Sozlamalar"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Aloqa operatorini sozlash"</string>
+ <string name="label_service" msgid="2668963955237345578">"Xizmatni sozlash"</string>
+ <string name="label_test" msgid="8425079572898571918">"Sinovli sozlash"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Bajarilmoqda"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Chiqish"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Sinov natijalari"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Asliga qaytarish"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Tayyor"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA xizmati paketi nomi"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Chaqiruvdan keyin xizmatni bo‘shatish uchun muddat"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC ilovasi turi"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Tarmoq ilovasi funksiyasining (NAF) URL manzili"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Avtomatik yuklash majburiy yoqilsinmi?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Tashkilot kodi"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA xavfsizlik protokoli identifikatori"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS shifrli terish identifikatori"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA tekshiruvi bajarildimi?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Nosozlik sababi identifikatori"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA kaliti (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Avtomatik yuklash tranzaksiyasining identifikatori (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-vi/strings.xml b/testapps/GbaTestApp/res/values-vi/strings.xml
new file mode 100644
index 0000000..e826f7b
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-vi/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Cài đặt"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Cấu hình nhà mạng"</string>
+ <string name="label_service" msgid="2668963955237345578">"Cấu hình dịch vụ"</string>
+ <string name="label_test" msgid="8425079572898571918">"Cấu hình thử nghiệm"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Đang chạy"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Thoát"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Kết quả thử nghiệm"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Đặt lại"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Xong"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Tên gói của dịch vụ GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Mất bao lâu để hủy liên kết dịch vụ sau khi thực hiện lệnh gọi"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Loại ứng dụng UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"URL chức năng ứng dụng mạng (NAF)"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Buộc dùng phương pháp tự thân?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Mã tổ chức"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"Mã giao thức bảo mật UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"Mã bộ thuật toán TLS (Bảo mật tầng truyền tải)"</string>
+ <string name="response_success" msgid="2469204471244527663">"Xác thực thành công GBA?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"Mã lý do bị lỗi"</string>
+ <string name="response_key" msgid="8839847772051686309">"Mã kiến trúc tự thân chung (GBA) (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"Giá trị nhận dạng giao dịch theo phương thức tự thân (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-zh-rCN/strings.xml b/testapps/GbaTestApp/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..ae99979
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-zh-rCN/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"设置"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"运营商配置"</string>
+ <string name="label_service" msgid="2668963955237345578">"服务配置"</string>
+ <string name="label_test" msgid="8425079572898571918">"测试配置"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"正在运行"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"退出"</string>
+ <string name="label_test_result" msgid="892984695972956196">"测试结果"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"重置"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"完成"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA 服务的软件包名称"</string>
+ <string name="service_release_time" msgid="532937496122890892">"调用 API 后释放服务所需的时间"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC 应用类型"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"网络应用功能 (NAF) 网址"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"要强制使用引导吗?"</string>
+ <string name="request_org" msgid="8416693445448308975">"单位代码"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA 安全协议 ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"传输层安全协议 (TLS) 加密套件 ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA 身份验证成功了吗?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"失败原因 ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA 密钥 (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"引导事务标识符 (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-zh-rHK/strings.xml b/testapps/GbaTestApp/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..eb93a5d
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-zh-rHK/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"設定"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"流動網絡供應商設定"</string>
+ <string name="label_service" msgid="2668963955237345578">"服務設定"</string>
+ <string name="label_test" msgid="8425079572898571918">"測試設定"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"正在執行"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"結束"</string>
+ <string name="label_test_result" msgid="892984695972956196">"測試結果"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"重設"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"完成"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA 服務套件名稱"</string>
+ <string name="service_release_time" msgid="532937496122890892">"調用後多久才釋出服務"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC 應用程式類型"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"網絡應用功能 (NAF) 網址"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"要強制啟動嗎?"</string>
+ <string name="request_org" msgid="8416693445448308975">"機構代碼"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA 安全性通訊協定 ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"TLS 密碼套件 ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"GBA 驗證成功?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"失敗原因 ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA 鍵 (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"啟動交易識別碼 (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-zh-rTW/strings.xml b/testapps/GbaTestApp/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..d0a6a76
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-zh-rTW/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"設定"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"電信業者設定"</string>
+ <string name="label_service" msgid="2668963955237345578">"服務設定"</string>
+ <string name="label_test" msgid="8425079572898571918">"測試設定"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Running"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"結束"</string>
+ <string name="label_test_result" msgid="892984695972956196">"測試結果"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"重設"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"完成"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"GBA 服務的套件名稱"</string>
+ <string name="service_release_time" msgid="532937496122890892">"呼叫後多久釋出服務"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"UICC 應用程式類型"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"網路應用程式功能 (NAF) 網址"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"要強制引導嗎?"</string>
+ <string name="request_org" msgid="8416693445448308975">"機構代碼"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"UA 安全通訊協定 ID"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"傳輸層安全標準 (TLS) 加密套裝組合 ID"</string>
+ <string name="response_success" msgid="2469204471244527663">"是否通過 GBA 驗證?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"失敗原因 ID"</string>
+ <string name="response_key" msgid="8839847772051686309">"GBA 金鑰 (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"引導交易 ID (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values-zu/strings.xml b/testapps/GbaTestApp/res/values-zu/strings.xml
new file mode 100644
index 0000000..8a221a2
--- /dev/null
+++ b/testapps/GbaTestApp/res/values-zu/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2922839697457005451">"I-GbaTestApp"</string>
+ <string name="label_settings" msgid="8030871890526865502">"Amasethingi"</string>
+ <string name="label_carrier" msgid="1470455313066422804">"Ukulungiswa Kwenkampani Yenethiwekhi"</string>
+ <string name="label_service" msgid="2668963955237345578">"Ukulungiswa Kwesevisi"</string>
+ <string name="label_test" msgid="8425079572898571918">"Ukulungiswa Kohlolo"</string>
+ <string name="button_name_running" msgid="4557363091224858010">"Uyagijima"</string>
+ <string name="button_name_exit" msgid="8025683733431538975">"Phuma"</string>
+ <string name="label_test_result" msgid="892984695972956196">"Umphumela wokuhlolwa"</string>
+ <string name="button_name_clear" msgid="436313515327318537">"Setha kabusha"</string>
+ <string name="button_name_done" msgid="6030406534322497491">"Kwenziwe"</string>
+ <string name="title_activity_carrier_config" msgid="7807803900475957717">"I-CarrierConfigActivity"</string>
+ <string name="title_activity_service_config" msgid="5394662739555577279">"I-ServiceConfigActivity"</string>
+ <string name="title_activity_test_config" msgid="7585204485536359713">"I-TestConfigActivity"</string>
+ <string name="service_package_name" msgid="7204809439090483315">"Igama lephakheji lesevisi ye-GBA"</string>
+ <string name="service_release_time" msgid="532937496122890892">"Uyikhulula isikhathi eside kangakanani isevisi ngemuva kokushaya"</string>
+ <string name="request_app_type" msgid="3975978141673475612">"Uhlobo Lohlelo Lokusebenza lwe-UICC"</string>
+ <string name="request_naf_url" msgid="4487793541217737042">"Umsebenzi wohlelo lokusebenza lenethiwekhi (NAF) URL"</string>
+ <string name="request_force_bootstrapping" msgid="206043602616214325">"Phoqelela i-Bootstrapping?"</string>
+ <string name="request_org" msgid="8416693445448308975">"Ikhodi yenhlangano"</string>
+ <string name="request_security_protocol" msgid="1444164827561010482">"I-ID Yephrothokholi Yokuphepha ye-UA"</string>
+ <string name="request_tls_cipher_suite" msgid="6659854717595308404">"I-ID ye-TLS Cipher Suite"</string>
+ <string name="response_success" msgid="2469204471244527663">"Ukuphumelela kwe-GBA Auth?"</string>
+ <string name="response_fail_reason" msgid="3401426967253202496">"I-ID Yesizathu Sokwahluleka"</string>
+ <string name="response_key" msgid="8839847772051686309">"Ukhiye we-GBA (CK + IK)"</string>
+ <string name="response_btid" msgid="2550216722679350756">"I-Bootstrapping Transaction Identifier (B-TID)"</string>
+ <string name="sample_naf" msgid="255371174145881001">"3GPP-bootstrapping@naf1.operator.com"</string>
+ <string name="sample_btid" msgid="6967620309576384966">"(B-TID)"</string>
+ <string name="sample_key" msgid="3337589659405527482">"6629fae49393a05397450978507c4ef1"</string>
+</resources>
diff --git a/testapps/GbaTestApp/res/values/colors.xml b/testapps/GbaTestApp/res/values/colors.xml
new file mode 100644
index 0000000..f8c6127
--- /dev/null
+++ b/testapps/GbaTestApp/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="purple_200">#FFBB86FC</color>
+ <color name="purple_500">#FF6200EE</color>
+ <color name="purple_700">#FF3700B3</color>
+ <color name="teal_200">#FF03DAC5</color>
+ <color name="teal_700">#FF018786</color>
+ <color name="black">#FF000000</color>
+ <color name="white">#FFFFFFFF</color>
+</resources>
\ No newline at end of file
diff --git a/testapps/GbaTestApp/res/values/strings.xml b/testapps/GbaTestApp/res/values/strings.xml
new file mode 100644
index 0000000..e74c181
--- /dev/null
+++ b/testapps/GbaTestApp/res/values/strings.xml
@@ -0,0 +1,30 @@
+<resources>
+ <string name="app_name">GbaTestApp</string>
+ <string name="label_settings">Settings</string>
+ <string name="label_carrier">Carrier Config</string>
+ <string name="label_service">Service Config</string>
+ <string name="label_test">Test Config</string>
+ <string name="button_name_running">Running</string>
+ <string name="button_name_exit">Exit</string>
+ <string name="label_test_result">Test Result</string>
+ <string name="button_name_clear">Reset</string>
+ <string name="button_name_done">Done</string>
+ <string name="title_activity_carrier_config">CarrierConfigActivity</string>
+ <string name="title_activity_service_config">ServiceConfigActivity</string>
+ <string name="title_activity_test_config">TestConfigActivity</string>
+ <string name="service_package_name">Package name of GBA service</string>
+ <string name="service_release_time">How long to release service after calling</string>
+ <string name="request_app_type">UICC App Type</string>
+ <string name="request_naf_url">Network application function (NAF) URL</string>
+ <string name="request_force_bootstrapping">Force Bootstrapping?</string>
+ <string name="request_org">Organization Code</string>
+ <string name="request_security_protocol">UA Security Protocol ID</string>
+ <string name="request_tls_cipher_suite">TLS Cipher Suite ID</string>
+ <string name="response_success">GBA Auth Success?</string>
+ <string name="response_fail_reason">Fail Reason ID</string>
+ <string name="response_key">GBA Key (CK + IK)</string>
+ <string name="response_btid">Bootstrapping Transaction Identifier (B-TID)</string>
+ <string name="sample_naf">3GPP-bootstrapping@naf1.operator.com</string>
+ <string name="sample_btid">(B-TID)</string>
+ <string name="sample_key">6629fae49393a05397450978507c4ef1</string>
+</resources>
diff --git a/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/MainActivity.java b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/MainActivity.java
new file mode 100644
index 0000000..72cbf5c
--- /dev/null
+++ b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/MainActivity.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2020 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.phone.testapps.gbatestapp;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.android.phone.testapps.gbatestapp.ui.main.MainFragment;
+
+/** main activity of the gba test app */
+public class MainActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main_activity);
+ if (savedInstanceState == null) {
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.container, MainFragment.newInstance())
+ .commitNow();
+ }
+ }
+}
diff --git a/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/Settings.java b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/Settings.java
new file mode 100644
index 0000000..eaa424a
--- /dev/null
+++ b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/Settings.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright 2020 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.phone.testapps.gbatestapp;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.RemoteException;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.ITelephony;
+
+import java.util.Locale;
+
+/** class to load and save the settings */
+public class Settings {
+
+ private static final String TAG = "SETTINGS";
+
+ private static final String PREF_CARRIER_CONFIG = "pref_carrier_config";
+ private static final String KEY_SERVICE_PACKAGE = "key_service_package";
+ private static final String KEY_RELEASE_TIME = "key_release_time";
+
+ private static final String PREF_TEST_CONFIG = "pref_test_config";
+ private static final String KEY_APP_TYPE = "key_app_type";
+ private static final String KEY_NAF_URL = "key_naf_url";
+ private static final String KEY_FORCE_BT = "key_force_bt";
+ private static final String KEY_ORG = "org";
+ private static final String KEY_SP_ID = "key_sp_id";
+ private static final String KEY_TLS_CS = "key_tls_cs";
+
+ private static final String PREF_SERVICE_CONFIG = "pref_carrier_config";
+ private static final String KEY_AUTH_RESULT = "key_auth_result";
+ private static final String KEY_GBA_KEY = "key_gba_key";
+ private static final String KEY_B_TID = "key_b_tid";
+ private static final String KEY_FAIL_REASON = "key_fail_reason";
+
+ private ITelephony mTelephony;
+ private int mSubId;
+ private String mServicePackageName;
+ private int mReleaseTime;
+ private int mAppType;
+ private String mNafUrl;
+ private boolean mForceBootstrap;
+ private int mOrg;
+ private int mSpId;
+ private int mTlsCs;
+ private boolean mIsAuthSuccess;
+ private String mGbaKey;
+ private String mBTid;
+ private int mFailReason;
+
+ private static Settings sInstance;
+
+ private Settings(Context cxt) {
+ mTelephony = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
+ .getTelephonyServiceManager().getTelephonyServiceRegisterer().get());
+ mSubId = SubscriptionManager.getDefaultSubscriptionId();
+ SharedPreferences sharedPref = cxt.getSharedPreferences(
+ PREF_CARRIER_CONFIG, Context.MODE_PRIVATE);
+ mServicePackageName = loadServicePackageName(mSubId, sharedPref);
+ mReleaseTime = loadReleaseTime(mSubId, sharedPref);
+
+ sharedPref = cxt.getSharedPreferences(PREF_TEST_CONFIG, Context.MODE_PRIVATE);
+ mAppType = sharedPref.getInt(KEY_APP_TYPE, TelephonyManager.APPTYPE_SIM);
+ mNafUrl = sharedPref.getString(KEY_NAF_URL, null);
+ mForceBootstrap = sharedPref.getBoolean(KEY_FORCE_BT, false);
+ mOrg = sharedPref.getInt(KEY_ORG, 0);
+ mSpId = sharedPref.getInt(KEY_SP_ID, 0);
+ mTlsCs = sharedPref.getInt(KEY_TLS_CS, 0);
+
+ sharedPref = cxt.getSharedPreferences(PREF_SERVICE_CONFIG, Context.MODE_PRIVATE);
+ mIsAuthSuccess = sharedPref.getBoolean(KEY_AUTH_RESULT, false);
+ mFailReason = sharedPref.getInt(KEY_FAIL_REASON, 0);
+ mGbaKey = sharedPref.getString(KEY_GBA_KEY, null);
+ mBTid = sharedPref.getString(KEY_B_TID, null);
+ }
+
+ /** Get the instance of Settings*/
+ public static Settings getSettings(Context cxt) {
+ if (sInstance == null) {
+ sInstance = new Settings(cxt);
+ }
+
+ return sInstance;
+ }
+
+ /** update carrier config settings */
+ public void updateCarrierConfig(Context cxt, String packageName, int releaseTime) {
+ new Thread(() -> {
+ synchronized (PREF_CARRIER_CONFIG) {
+
+ if (TextUtils.equals(mServicePackageName, packageName)
+ && (mReleaseTime == releaseTime)) {
+ return;
+ }
+
+ if (!TextUtils.equals(mServicePackageName, packageName)) {
+ mServicePackageName = packageName;
+
+ try {
+ mTelephony.setBoundGbaServiceOverride(mSubId, packageName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "fail to set package name due to " + e);
+ }
+
+ }
+
+ if (mReleaseTime != releaseTime) {
+ mReleaseTime = releaseTime;
+
+ try {
+ mTelephony.setGbaReleaseTimeOverride(mSubId, releaseTime);
+ } catch (RemoteException e) {
+ Log.e(TAG, "fail to set release time due to " + e);
+ }
+ }
+
+ SharedPreferences sharedPref = cxt.getSharedPreferences(
+ PREF_CARRIER_CONFIG, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putString(KEY_SERVICE_PACKAGE, packageName);
+ editor.putInt(KEY_RELEASE_TIME, releaseTime);
+ editor.commit();
+ }
+ }).start();
+ }
+
+ /** get the config of gba service package name */
+ public String getServicePackageName() {
+ synchronized (PREF_CARRIER_CONFIG) {
+ return mServicePackageName;
+ }
+ }
+
+ /** get the config of gba release time */
+ public int getReleaseTime() {
+ synchronized (PREF_CARRIER_CONFIG) {
+ return mReleaseTime;
+ }
+ }
+
+ /** get the config of gba service package name used for now*/
+ public String loadServicePackageName(int subId, SharedPreferences sharedPref) {
+ try {
+ return mTelephony.getBoundGbaService(subId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "fail to get package name due to " + e);
+ }
+ return sharedPref != null ? sharedPref.getString(KEY_SERVICE_PACKAGE, null) : null;
+ }
+
+ /** get the config of gba release time used for now */
+ public int loadReleaseTime(int subId, SharedPreferences sharedPref) {
+ try {
+ return mTelephony.getGbaReleaseTime(subId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "fail to get package name due to " + e);
+ }
+ return sharedPref != null ? sharedPref.getInt(KEY_RELEASE_TIME, 0) : 0;
+ }
+
+ /** update the config of test gba service */
+ public void updateServiceConfig(Context cxt, boolean success, int reason,
+ String key, String btId) {
+ new Thread(() -> {
+ synchronized (PREF_SERVICE_CONFIG) {
+ mIsAuthSuccess = success;
+ mFailReason = reason;
+ mGbaKey = key;
+ mBTid = btId;
+ SharedPreferences sharedPref = cxt.getSharedPreferences(
+ PREF_SERVICE_CONFIG, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putBoolean(KEY_AUTH_RESULT, success);
+ editor.putInt(KEY_FAIL_REASON, reason);
+ editor.putString(KEY_GBA_KEY, key);
+ editor.putString(KEY_B_TID, btId);
+ editor.commit();
+ }
+ }).start();
+ }
+
+ /** get the config of the authentication result */
+ public boolean getAuthResult() {
+ synchronized (PREF_SERVICE_CONFIG) {
+ return mIsAuthSuccess;
+ }
+ }
+
+ /** get the config of authentication fail cause */
+ public int getFailReason() {
+ synchronized (PREF_SERVICE_CONFIG) {
+ return mFailReason;
+ }
+ }
+
+ /** get the config of GBA key */
+ public String getGbaKey() {
+ synchronized (PREF_SERVICE_CONFIG) {
+ return mGbaKey;
+ }
+ }
+
+ /** get the config of B-Tid */
+ public String getBTid() {
+ synchronized (PREF_SERVICE_CONFIG) {
+ return mBTid;
+ }
+ }
+
+ /** update the config of the test */
+ public void updateTestConfig(Context cxt, int appType, String url,
+ boolean force, int org, int spId, int tlsCs) {
+ new Thread(() -> {
+ synchronized (PREF_TEST_CONFIG) {
+ mAppType = appType;
+ mNafUrl = url;
+ mForceBootstrap = force;
+ mOrg = org;
+ mSpId = spId;
+ mTlsCs = tlsCs;
+
+ SharedPreferences sharedPref = cxt.getSharedPreferences(
+ PREF_TEST_CONFIG, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putInt(KEY_APP_TYPE, appType);
+ editor.putString(KEY_NAF_URL, url);
+ editor.putBoolean(KEY_FORCE_BT, force);
+ editor.putInt(KEY_ORG, org);
+ editor.putInt(KEY_SP_ID, spId);
+ editor.putInt(KEY_TLS_CS, tlsCs);
+ editor.commit();
+ }
+ }).start();
+ }
+
+ /** get the config of the uicc application type*/
+ public int getAppType() {
+ synchronized (PREF_TEST_CONFIG) {
+ return mAppType;
+ }
+ }
+
+ /** get the config of NAF url */
+ public String getNafUrl() {
+ synchronized (PREF_TEST_CONFIG) {
+ return mNafUrl;
+ }
+ }
+
+ /** get the config if bootstrap is forced */
+ public boolean isForceBootstrap() {
+ synchronized (PREF_TEST_CONFIG) {
+ return mForceBootstrap;
+ }
+ }
+
+ /** get the config of the organization code */
+ public int getOrg() {
+ synchronized (PREF_TEST_CONFIG) {
+ return mOrg;
+ }
+ }
+
+ /** get the config of the security protocol id */
+ public int getSpId() {
+ synchronized (PREF_TEST_CONFIG) {
+ return mSpId;
+ }
+ }
+
+ /** get the config of the tls ciper suite id */
+ public int getTlsCs() {
+ synchronized (PREF_TEST_CONFIG) {
+ return mTlsCs;
+ }
+ }
+
+ /** convert byte arry to hex string */
+ public static String byteArrayToHexString(byte[] data) {
+ if (data == null || data.length == 0) {
+ return "";
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (byte b : data) {
+ sb.append(String.format(Locale.US, "%02X", b));
+ }
+ return sb.toString();
+ }
+}
diff --git a/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/TestGbaService.java b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/TestGbaService.java
new file mode 100644
index 0000000..4b0636c
--- /dev/null
+++ b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/TestGbaService.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2020 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.phone.testapps.gbatestapp;
+
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.IBinder;
+import android.telephony.gba.GbaService;
+import android.util.Log;
+
+/** test GbaService to be used for Gba api test */
+public class TestGbaService extends GbaService {
+
+ private static final String TAG = "TestGbaService";
+
+ private Settings mSettings;
+
+ @Override
+ public void onCreate() {
+ Log.i(TAG, "TestGbaService: onCreate");
+ mSettings = Settings.getSettings(getApplicationContext());
+ }
+
+ @Override
+ public void onAuthenticationRequest(int subId, int token, int appType,
+ @NonNull Uri nafUrl, @NonNull byte[] securityProtocol, boolean forceBootStrapping) {
+ boolean isSuccess = mSettings.getAuthResult();
+ int reason = mSettings.getFailReason();
+ String key = mSettings.getGbaKey();
+ String btid = mSettings.getBTid();
+
+ if (isSuccess) {
+ reportKeysAvailable(token, key.getBytes(), btid);
+ } else {
+ reportAuthenticationFailure(token, reason);
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.d(TAG, "onBind intent:" + intent);
+ return super.onBind(intent);
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "onDestroy!");
+ super.onDestroy();
+ }
+}
diff --git a/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/CarrierConfigFragment.java b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/CarrierConfigFragment.java
new file mode 100644
index 0000000..b0bfc32
--- /dev/null
+++ b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/CarrierConfigFragment.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2020 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.phone.testapps.gbatestapp.ui.main;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.phone.testapps.gbatestapp.R;
+import com.android.phone.testapps.gbatestapp.Settings;
+import com.android.phone.testapps.gbatestapp.TestGbaService;
+
+/**
+ * A simple {@link Fragment} subclass.
+ * Use the {@link CarrierConfigFragment#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class CarrierConfigFragment extends Fragment {
+ private static final String TAG = "CARRIER";
+
+ private static CarrierConfigFragment sInstance;
+
+ private Settings mSettings;
+ private EditText mEditPackageName;
+ private EditText mEditReleaseTime;
+
+ /** get the instance of CarrierConfigFragment */
+ public static CarrierConfigFragment newInstance() {
+ if (sInstance == null) {
+ Log.d(TAG, "new instance:");
+ sInstance = new CarrierConfigFragment();
+ }
+ return sInstance;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSettings = Settings.getSettings(getActivity());
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ View viewHierarchy = inflater.inflate(R.layout.fragment_carrier_config, container, false);
+ mEditPackageName = viewHierarchy.findViewById(R.id.editServicePackageName);
+ mEditReleaseTime = viewHierarchy.findViewById(R.id.editServiceReleaseTime);
+ getConfig(false);
+
+ Button buttonDone = viewHierarchy.findViewById(R.id.carrier_config_done);
+ buttonDone.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSettings.updateCarrierConfig(getActivity(),
+ mEditPackageName.getText().toString(),
+ Integer.parseInt(mEditReleaseTime.getText().toString()));
+ getFragmentManager().beginTransaction().remove(
+ CarrierConfigFragment.this).commitNow();
+ }
+ }
+ );
+
+ Button buttonClear = viewHierarchy.findViewById(R.id.carrier_config_clear);
+ buttonClear.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ getConfig(true);
+ }
+ }
+ );
+ return viewHierarchy;
+ }
+
+ private void getConfig(boolean isDefault) {
+ String packagename = mSettings.getServicePackageName();
+ if (isDefault || packagename == null) {
+ packagename = TestGbaService.class.getPackage().getName();
+ }
+ mEditPackageName.setText(packagename);
+ mEditReleaseTime.setText(isDefault ? "0" : Integer.toString(mSettings.getReleaseTime()));
+ }
+}
diff --git a/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/MainFragment.java b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/MainFragment.java
new file mode 100644
index 0000000..ff50f5c
--- /dev/null
+++ b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/MainFragment.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2020 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.phone.testapps.gbatestapp.ui.main;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.telephony.TelephonyManager;
+import android.telephony.gba.UaSecurityProtocolIdentifier;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.android.phone.testapps.gbatestapp.R;
+import com.android.phone.testapps.gbatestapp.Settings;
+
+/** main fragent to update settings and run the test */
+public class MainFragment extends Fragment {
+
+ private static final String TAG = "GBATestApp";
+ private static final String TAG_CARRIER = "carrier";
+ private static final String TAG_SERVICE = "service";
+ private static final String TAG_CLIENT = "client";
+
+ private static MainFragment sInstance;
+
+ private Settings mSettings;
+ private TelephonyManager mTelephonyManager;
+
+ /** Get the instance of MainFragment*/
+ public static MainFragment newInstance() {
+ if (sInstance == null) {
+ sInstance = new MainFragment();
+ }
+ return sInstance;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View viewHierarchy = inflater.inflate(R.layout.main_fragment, container, false);
+
+ Button buttonCarrier = viewHierarchy.findViewById(R.id.carrier_config_change_button);
+ buttonCarrier.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Fragment carrierFrag = getChildFragmentManager()
+ .findFragmentByTag(TAG_CARRIER);
+ if (carrierFrag == null) {
+ carrierFrag = CarrierConfigFragment.newInstance();
+ }
+ getChildFragmentManager()
+ .beginTransaction()
+ .replace(R.id.main, carrierFrag, TAG_CARRIER)
+ .commitNow();
+ }
+ }
+ );
+
+ Button buttonService = viewHierarchy.findViewById(R.id.service_config);
+ buttonService.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Fragment serviceFrag = getChildFragmentManager()
+ .findFragmentByTag(TAG_SERVICE);
+ if (serviceFrag == null) {
+ serviceFrag = ServiceConfigFragment.newInstance();
+ }
+ getChildFragmentManager()
+ .beginTransaction()
+ .replace(R.id.main, serviceFrag, TAG_SERVICE)
+ .commitNow();
+ }
+ }
+ );
+
+ Button buttonClient = viewHierarchy.findViewById(R.id.client_config);
+ buttonClient.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Fragment testFrag = getChildFragmentManager()
+ .findFragmentByTag(TAG_CLIENT);
+ if (testFrag == null) {
+ testFrag = TestConfigFragment.newInstance();
+ }
+
+ getChildFragmentManager()
+ .beginTransaction()
+ .replace(R.id.main, testFrag, TAG_CLIENT)
+ .commitNow();
+ }
+ }
+ );
+
+ Button buttonExit = viewHierarchy.findViewById(R.id.test_exit);
+ buttonExit.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ getActivity().finish();
+ }
+ }
+ );
+
+ Button buttonRunning = viewHierarchy.findViewById(R.id.send_request);
+ buttonRunning.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Log.d(TAG, "starting test...");
+ TelephonyManager.BootstrapAuthenticationCallback cb = new
+ TelephonyManager.BootstrapAuthenticationCallback() {
+ @Override
+ public void onKeysAvailable(byte[] gbaKey, String btId) {
+ String result = "onKeysAvailable, key:"
+ + Settings.byteArrayToHexString(gbaKey)
+ + ", btid:" + btId;
+ Log.d(TAG, result);
+ getActivity().runOnUiThread(()-> {
+ mTestLog.append(result + "\n");
+ });
+ }
+
+ @Override
+ public void onAuthenticationFailure(int reason) {
+ String result = "onAuthFailure, cause:" + reason;
+ Log.d(TAG, result);
+ getActivity().runOnUiThread(
+ () -> mTestLog.append(result + "\n"));
+ }
+ };
+ UaSecurityProtocolIdentifier.Builder builder =
+ new UaSecurityProtocolIdentifier.Builder();
+ try {
+ if (mSettings.getOrg() != 0 || mSettings.getSpId() != 0
+ || mSettings.getTlsCs() != 0) {
+ builder.setOrg(mSettings.getOrg())
+ .setProtocol(mSettings.getSpId())
+ .setTlsCipherSuite(mSettings.getTlsCs());
+ }
+ } catch (IllegalArgumentException e) {
+ getActivity().runOnUiThread(() -> mTestLog.append(
+ "Fail to create UaSecurityProtocolIdentifier " + e + "\n"));
+ return;
+ }
+
+ UaSecurityProtocolIdentifier spId = builder.build();
+ Log.d(TAG, "bootstrapAuthenticationRequest with parameters [appType:"
+ + mSettings.getAppType() + ", NAF:" + mSettings.getNafUrl()
+ + ", spId:" + spId + ", isForceBootstrap:"
+ + mSettings.isForceBootstrap() + "]");
+ try {
+ mTelephonyManager.bootstrapAuthenticationRequest(
+ mSettings.getAppType(), Uri.parse(mSettings.getNafUrl()),
+ spId, mSettings.isForceBootstrap(),
+ AsyncTask.SERIAL_EXECUTOR, cb);
+ } catch (NullPointerException e) {
+ getActivity().runOnUiThread(() -> mTestLog.append(
+ "Invalid parameters, please check!" + "\n"));
+ }
+ }
+ }
+ );
+
+ return viewHierarchy;
+ }
+
+ TextView mTestLog;
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ mTelephonyManager = (TelephonyManager)
+ getContext().getSystemService(Context.TELEPHONY_SERVICE);
+ mSettings = Settings.getSettings(getContext());
+ mTestLog = getActivity().findViewById(R.id.viewTestOutput);
+ }
+
+}
diff --git a/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/ServiceConfigFragment.java b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/ServiceConfigFragment.java
new file mode 100644
index 0000000..5e7f2fa
--- /dev/null
+++ b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/ServiceConfigFragment.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2020 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.phone.testapps.gbatestapp.ui.main;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.phone.testapps.gbatestapp.R;
+import com.android.phone.testapps.gbatestapp.Settings;
+
+/**
+ * A simple {@link Fragment} subclass.
+ * Use the {@link ServiceConfigFragment#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class ServiceConfigFragment extends Fragment {
+
+ private static final String TAG = "SERVICE";
+
+ private static ServiceConfigFragment sInstance;
+
+ private Settings mSettings;
+
+ private CheckBox mAuthResult;
+ private EditText mGbaKey;
+ private EditText mBTid;
+ private EditText mFailReason;
+
+ /** get the instance of ServiceConfigFragment */
+ public static ServiceConfigFragment newInstance() {
+ if (sInstance == null) {
+ sInstance = new ServiceConfigFragment();
+ }
+ return sInstance;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSettings = Settings.getSettings(getActivity());
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View viewHierarchy = inflater.inflate(R.layout.fragment_service_config, container, false);
+
+ mAuthResult = viewHierarchy.findViewById(R.id.checkBoxResult);
+ mFailReason = viewHierarchy.findViewById(R.id.editFailReason);
+ mGbaKey = viewHierarchy.findViewById(R.id.editKey);
+ mBTid = viewHierarchy.findViewById(R.id.editBTid);
+
+ setDefault();
+
+ Button buttonDone = viewHierarchy.findViewById(R.id.service_config_done);
+ buttonDone.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSettings.updateServiceConfig(getActivity(), mAuthResult.isChecked(),
+ Integer.parseInt(mFailReason.getText().toString()),
+ mGbaKey.getText().toString(), mBTid.getText().toString());
+ getFragmentManager().beginTransaction()
+ .remove(ServiceConfigFragment.this).commitNow();
+ }
+ }
+ );
+
+ Button buttonClear = viewHierarchy.findViewById(R.id.service_config_clear);
+ buttonClear.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setDefault();
+ }
+ }
+ );
+
+ return viewHierarchy;
+ }
+
+ private void setDefault() {
+ mAuthResult.setChecked(mSettings.getAuthResult());
+ String key = mSettings.getGbaKey();
+ if (key == null || key.isEmpty()) {
+ key = getString(R.string.sample_key);
+ }
+ mGbaKey.setText(key);
+ String id = mSettings.getBTid();
+ if (id == null || id.isEmpty()) {
+ id = getString(R.string.sample_btid);
+ }
+ mBTid.setText(id);
+ mFailReason.setText(Integer.toString(mSettings.getFailReason()));
+ }
+}
diff --git a/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/TestConfigFragment.java b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/TestConfigFragment.java
new file mode 100644
index 0000000..4049082
--- /dev/null
+++ b/testapps/GbaTestApp/src/com/android/phone/testapps/gbatestapp/ui/main/TestConfigFragment.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2020 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.phone.testapps.gbatestapp.ui.main;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.phone.testapps.gbatestapp.R;
+import com.android.phone.testapps.gbatestapp.Settings;
+
+/**
+ * A simple {@link Fragment} subclass.
+ * Use the {@link TestConfigFragment#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class TestConfigFragment extends Fragment {
+
+ private static final String TAG = "TEST_CONFIG";
+
+ private static TestConfigFragment sInstance;
+
+ private Settings mSettings;
+
+ private EditText mAppType;
+ private EditText mUrl;
+ private EditText mOrg;
+ private EditText mSpId;
+ private EditText mTlsCs;
+ private CheckBox mForce;
+
+ /** get the instance of TestConfigFragment */
+ public static TestConfigFragment newInstance() {
+ if (sInstance == null) {
+ sInstance = new TestConfigFragment();
+ }
+ return sInstance;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSettings = Settings.getSettings(getActivity());
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View viewHierarchy = inflater.inflate(R.layout.fragment_test_config, container, false);
+ mAppType = viewHierarchy.findViewById(R.id.editAppType);
+ mUrl = viewHierarchy.findViewById(R.id.editUrl);
+ mOrg = viewHierarchy.findViewById(R.id.editOrg);
+ mSpId = viewHierarchy.findViewById(R.id.editSpId);
+ mTlsCs = viewHierarchy.findViewById(R.id.editTlsCs);
+ mForce = viewHierarchy.findViewById(R.id.checkBoxForce);
+
+ setDefault();
+
+ Button buttonDone = viewHierarchy.findViewById(R.id.client_config_done);
+ buttonDone.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Log.d(TAG, "updateTestConfig");
+ mSettings.updateTestConfig(getActivity(),
+ Integer.parseInt(mAppType.getText().toString()),
+ mUrl.getText().toString(),
+ mForce.isChecked(),
+ Integer.parseInt(mOrg.getText().toString()),
+ Integer.parseInt(mSpId.getText().toString()),
+ Integer.parseInt(mTlsCs.getText().toString()));
+ getFragmentManager().beginTransaction().remove(
+ TestConfigFragment.this).commitNow();
+ }
+ }
+ );
+
+ Button buttonClear = viewHierarchy.findViewById(R.id.client_config_clear);
+ buttonClear.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setDefault();
+ }
+ }
+ );
+
+ return viewHierarchy;
+ }
+
+ void setDefault() {
+ Log.d(TAG, "setDefault");
+ mAppType.setText(Integer.toString(mSettings.getAppType()));
+ String naf = mSettings.getNafUrl();
+ if (naf == null || naf.isEmpty()) {
+ naf = getString(R.string.sample_naf);
+ }
+ mUrl.setText(naf);
+ mOrg.setText(Integer.toString(mSettings.getOrg()));
+ mSpId.setText(Integer.toString(mSettings.getSpId()));
+ mTlsCs.setText(Integer.toString(mSettings.getTlsCs()));
+ mForce.setChecked(mSettings.isForceBootstrap());
+ }
+}
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/ImsTestService/AndroidManifest.xml b/testapps/ImsTestService/AndroidManifest.xml
index eea54b8..6177e73 100644
--- a/testapps/ImsTestService/AndroidManifest.xml
+++ b/testapps/ImsTestService/AndroidManifest.xml
@@ -16,40 +16,42 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- coreApp="true"
- package="com.android.phone.testapps.imstestapp">
+ coreApp="true"
+ package="com.android.phone.testapps.imstestapp">
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<!--Beware, declaring the below permission will cause the device to not boot unless you add
this app and permission to frameworks/base/data/etc/privapp-permissions-platform.xml-->
- <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
- <application
- android:label="ImsTestService"
- android:directBootAware="true">
- <activity
- android:name=".ImsTestServiceApp"
- android:label="ImsTestService">
+ <!--uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/-->
+ <application android:label="ImsTestService"
+ android:directBootAware="true">
+ <activity android:name=".ImsTestServiceApp"
+ android:label="ImsTestService"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
- <activity android:name=".ImsRegistrationActivity" android:label="IMS Registration" />
- <activity android:name=".ImsCallingActivity" android:label="IMS Calling" />
- <activity android:name=".ImsConfigActivity" android:label="IMS Config" />
+ <activity android:name=".ImsRegistrationActivity"
+ android:label="IMS Registration"/>
+ <activity android:name=".ImsCallingActivity"
+ android:label="IMS Calling"/>
+ <activity android:name=".ImsConfigActivity"
+ android:label="IMS Config"/>
<service android:name=".TestImsService"
- android:exported="true"
- android:enabled="true"
- android:persistent="true"
- android:permission="android.permission.BIND_IMS_SERVICE">
- <!--meta-data android:name="android.telephony.ims.MMTEL_FEATURE" android:value="true"/-->
+ android:exported="true"
+ android:enabled="true"
+ android:persistent="true"
+ android:permission="android.permission.BIND_IMS_SERVICE">
+ <!--meta-data android:name="android.telephony.ims.MMTEL_FEATURE"
+ android:value="true"/-->
<!-- No features means we will get queried for dynamic config. -->
<intent-filter>
- <action android:name="android.telephony.ims.ImsService" />
+ <action android:name="android.telephony.ims.ImsService"/>
</intent-filter>
</service>
</application>
</manifest>
-
diff --git a/testapps/ImsTestService/src/com/android/phone/testapps/imstestapp/ImsCallingActivity.java b/testapps/ImsTestService/src/com/android/phone/testapps/imstestapp/ImsCallingActivity.java
index 0ff6cc1..477bbc0 100644
--- a/testapps/ImsTestService/src/com/android/phone/testapps/imstestapp/ImsCallingActivity.java
+++ b/testapps/ImsTestService/src/com/android/phone/testapps/imstestapp/ImsCallingActivity.java
@@ -188,6 +188,9 @@
sb.append("}, \nIWLAN: ");
sb.append("{");
sb.append(caps.get(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN));
+ sb.append("}, \nCROSS-SIM: ");
+ sb.append("{");
+ sb.append(caps.get(ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM));
sb.append("}");
mCapEnabledText.setText(sb.toString());
}
diff --git a/testapps/ImsTestService/src/com/android/phone/testapps/imstestapp/SipTransportImpl.java b/testapps/ImsTestService/src/com/android/phone/testapps/imstestapp/SipTransportImpl.java
new file mode 100644
index 0000000..1ae2594
--- /dev/null
+++ b/testapps/ImsTestService/src/com/android/phone/testapps/imstestapp/SipTransportImpl.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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.phone.testapps.imstestapp;
+
+import android.telephony.ims.stub.SipTransportImplBase;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Test stub implementation of SipTransport.
+ */
+public class SipTransportImpl extends SipTransportImplBase {
+
+ private static SipTransportImpl sSipTransportInstance;
+
+ public static SipTransportImpl getInstance(Executor e) {
+ if (sSipTransportInstance == null) {
+ sSipTransportInstance = new SipTransportImpl(e);
+ }
+ return sSipTransportInstance;
+ };
+
+ public SipTransportImpl(Executor e) {
+ super(e);
+ }
+}
diff --git a/testapps/ImsTestService/src/com/android/phone/testapps/imstestapp/TestImsService.java b/testapps/ImsTestService/src/com/android/phone/testapps/imstestapp/TestImsService.java
index 71323d8..477c638 100644
--- a/testapps/ImsTestService/src/com/android/phone/testapps/imstestapp/TestImsService.java
+++ b/testapps/ImsTestService/src/com/android/phone/testapps/imstestapp/TestImsService.java
@@ -39,6 +39,7 @@
public TestMmTelFeatureImpl mTestMmTelFeature;
public TestRcsFeatureImpl mTestRcsFeature;
public TestImsConfigImpl mTestImsConfig;
+ public SipTransportImpl mSipTransportImpl;
public static TestImsService getInstance() {
return mInstance;
@@ -51,7 +52,8 @@
mTestMmTelFeature = TestMmTelFeatureImpl.getInstance();
mTestRcsFeature = new TestRcsFeatureImpl();
mTestImsConfig = TestImsConfigImpl.getInstance();
-
+ mSipTransportImpl = SipTransportImpl.getInstance(
+ getApplicationContext().getMainExecutor());
mInstance = this;
}
@@ -60,10 +62,16 @@
return new ImsFeatureConfiguration.Builder()
.addFeature(0, ImsFeature.FEATURE_EMERGENCY_MMTEL)
.addFeature(0, ImsFeature.FEATURE_MMTEL)
+ .addFeature(0, ImsFeature.FEATURE_RCS)
.build();
}
@Override
+ public long getImsServiceCapabilities() {
+ return CAPABILITY_SIP_DELEGATE_CREATION;
+ }
+
+ @Override
public MmTelFeature createMmTelFeature(int slotId) {
Log.i(LOG_TAG, "TestImsService: onCreateMmTelImsFeature");
return mTestMmTelFeature;
@@ -84,4 +92,9 @@
public ImsConfigImplBase getConfig(int slotId) {
return mTestImsConfig;
}
+
+ @Override
+ public SipTransportImpl getSipTransport(int slotId) {
+ return mSipTransportImpl;
+ }
}
diff --git a/testapps/SmsManagerTestApp/Android.bp b/testapps/SmsManagerTestApp/Android.bp
index 5333eab..4d4afcb 100644
--- a/testapps/SmsManagerTestApp/Android.bp
+++ b/testapps/SmsManagerTestApp/Android.bp
@@ -1,5 +1,16 @@
+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"],
- sdk_version: "current",
+ platform_apis: true,
+ certificate: "platform",
+ privileged: true,
}
diff --git a/testapps/SmsManagerTestApp/AndroidManifest.xml b/testapps/SmsManagerTestApp/AndroidManifest.xml
index c5f4621..1be2b2d 100644
--- a/testapps/SmsManagerTestApp/AndroidManifest.xml
+++ b/testapps/SmsManagerTestApp/AndroidManifest.xml
@@ -16,29 +16,89 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.phone.testapps.smsmanagertestapp">
- <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="29" />
+ package="com.android.phone.testapps.smsmanagertestapp">
+ <uses-sdk android:minSdkVersion="24"
+ android:targetSdkVersion="29"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+ <uses-permission android:name="android.permission.PERFORM_IMS_SINGLE_REGISTRATION"/>
<application android:label="SmsManagerTestApp">
- <activity
- android:name=".SmsManagerTestApp"
- android:label="SmsManagerTestApp">
+ <activity android:name=".SmsManagerTestApp"
+ android:label="SmsManagerTestApp"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
- <service android:name=".SmsManagerTestService" android:exported="false" />
+ <service android:name=".SmsManagerTestService"
+ android:exported="false"/>
<receiver android:name=".SendStatusReceiver"
- android:exported="false">
+ android:exported="false">
<intent-filter>
- <action android:name="com.android.phone.testapps.smsmanagertestapp.message_sent_action" />
- <data android:scheme="content" />
+ <action android:name="com.android.phone.testapps.smsmanagertestapp.message_sent_action"/>
+ <data android:scheme="content"/>
+ </intent-filter>
+ </receiver>
+ <service android:name=".PersistentService"
+ android:exported="false"
+ android:process=":persistent"
+ android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE">
+ <intent-filter>
+ <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE" />
+ </intent-filter>
+ </service>
+
+ <!-- Stuff required to become the default messaging app defined below, doesn't actually do
+ anything useful for now. -->
+
+ <!-- Fake BroadcastReceiver that listens for incoming SMS messages -->
+ <receiver android:name=".SmsReceiver"
+ android:exported="true"
+ android:permission="android.permission.BROADCAST_SMS">
+ <intent-filter>
+ <action android:name="android.provider.Telephony.SMS_DELIVER" />
</intent-filter>
</receiver>
+ <!-- Fake BroadcastReceiver that listens for incoming MMS messages -->
+ <receiver android:name=".MmsReceiver"
+ android:permission="android.permission.BROADCAST_WAP_PUSH"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
+ <data android:mimeType="application/vnd.wap.mms-message" />
+ </intent-filter>
+ </receiver>
+
+ <!-- Fake Activity that allows the user to send new SMS/MMS messages -->
+ <activity android:name=".ComposeSmsActivity" android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.SEND" />
+ <action android:name="android.intent.action.SENDTO" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="sms" />
+ <data android:scheme="smsto" />
+ <data android:scheme="mms" />
+ <data android:scheme="mmsto" />
+ </intent-filter>
+ </activity>
+
+ <!-- Fake Service that delivers messages from the phone "quick response" -->
+ <service android:name=".HeadlessSmsSendService"
+ android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+ android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="sms" />
+ <data android:scheme="smsto" />
+ <data android:scheme="mms" />
+ <data android:scheme="mmsto" />
+ </intent-filter>
+ </service>
+
</application>
</manifest>
-
diff --git a/testapps/SmsManagerTestApp/res/layout/activity_main.xml b/testapps/SmsManagerTestApp/res/layout/activity_main.xml
index 39fb6c6..ddaf3a6 100644
--- a/testapps/SmsManagerTestApp/res/layout/activity_main.xml
+++ b/testapps/SmsManagerTestApp/res/layout/activity_main.xml
@@ -63,5 +63,23 @@
android:layout_height="wrap_content"
android:paddingRight="4dp"
android:text="@string/get_sub_for_result_button"/>
+ <Button
+ android:id="@+id/enable_persistent_service"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/enable_persistent_service"/>
+ <Button
+ android:id="@+id/disable_persistent_service"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/disable_persistent_service"/>
+ <Button
+ android:id="@+id/check_single_reg_permission"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/check_single_reg_permission"/>
</LinearLayout>
</LinearLayout>
\ No newline at end of file
diff --git a/testapps/SmsManagerTestApp/res/values/donottranslate_strings.xml b/testapps/SmsManagerTestApp/res/values/donottranslate_strings.xml
index d6497a3..ca07c54 100644
--- a/testapps/SmsManagerTestApp/res/values/donottranslate_strings.xml
+++ b/testapps/SmsManagerTestApp/res/values/donottranslate_strings.xml
@@ -19,4 +19,7 @@
<string name="send_text_button">Send Outgoing Text Now.</string>
<string name="send_text_service_button">Send Outgoing Text after 5 sec.</string>
<string name="get_sub_for_result_button">Ask user for sub id.</string>
+ <string name="enable_persistent_service">Enable Persistent Service</string>
+ <string name="disable_persistent_service">Disable Persistent Service</string>
+ <string name="check_single_reg_permission">Check RCS Single Reg Perm</string>
</resources>
\ No newline at end of file
diff --git a/testapps/SmsManagerTestApp/src/com/android/phone/testapps/smsmanagertestapp/PersistentService.java b/testapps/SmsManagerTestApp/src/com/android/phone/testapps/smsmanagertestapp/PersistentService.java
new file mode 100644
index 0000000..16b7ecd
--- /dev/null
+++ b/testapps/SmsManagerTestApp/src/com/android/phone/testapps/smsmanagertestapp/PersistentService.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 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.phone.testapps.smsmanagertestapp;
+
+import android.content.Intent;
+import android.service.carrier.CarrierMessagingClientService;
+import android.util.Log;
+
+/**
+ * A test persistent service that should be started by the framework when this app becomes the
+ * default SMS app and destroyed when it is removed. See {@link CarrierMessagingClientService} for
+ * more information.
+ */
+public class PersistentService extends CarrierMessagingClientService {
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.i("SmsTestApp", "onCreate");
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.i("SmsTestApp", "onDestroy");
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ Log.i("SmsTestApp", "onUnbind");
+ return false;
+ }
+}
diff --git a/testapps/SmsManagerTestApp/src/com/android/phone/testapps/smsmanagertestapp/SmsManagerTestApp.java b/testapps/SmsManagerTestApp/src/com/android/phone/testapps/smsmanagertestapp/SmsManagerTestApp.java
index 75536f3..cc3769e 100644
--- a/testapps/SmsManagerTestApp/src/com/android/phone/testapps/smsmanagertestapp/SmsManagerTestApp.java
+++ b/testapps/SmsManagerTestApp/src/com/android/phone/testapps/smsmanagertestapp/SmsManagerTestApp.java
@@ -44,6 +44,10 @@
private static final ComponentName SETTINGS_SUB_PICK_ACTIVITY = new ComponentName(
"com.android.settings", "com.android.settings.sim.SimDialogActivity");
+ // Can't import PERFORM_IMS_SINGLE_REGISTRATION const directly beause it's a @SystemApi
+ private static final String PERFORM_IMS_SINGLE_REGISTRATION =
+ "android.permission.PERFORM_IMS_SINGLE_REGISTRATION";
+
/*
* Forwarded constants from SimDialogActivity.
*/
@@ -66,6 +70,12 @@
findViewById(R.id.send_text_button_service)
.setOnClickListener(this::sendOutgoingSmsService);
findViewById(R.id.get_sub_for_result_button).setOnClickListener(this::getSubIdForResult);
+ findViewById(R.id.enable_persistent_service)
+ .setOnClickListener(this::setPersistentServiceComponentEnabled);
+ findViewById(R.id.disable_persistent_service)
+ .setOnClickListener(this::setPersistentServiceComponentDisabled);
+ findViewById(R.id.check_single_reg_permission)
+ .setOnClickListener(this::checkSingleRegPermission);
mPhoneNumber = (EditText) findViewById(R.id.phone_number_text);
}
@@ -183,6 +193,32 @@
}
}
+ private void setPersistentServiceComponentEnabled(View view) {
+ getPackageManager().setComponentEnabledSetting(
+ new ComponentName(this, PersistentService.class),
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+
+ private void setPersistentServiceComponentDisabled(View view) {
+ getPackageManager().setComponentEnabledSetting(
+ new ComponentName(this, PersistentService.class),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ }
+
+ private void checkSingleRegPermission(View view) {
+ if (checkSelfPermission(PERFORM_IMS_SINGLE_REGISTRATION)
+ == PackageManager.PERMISSION_GRANTED) {
+ Toast.makeText(this, "Single Reg permission granted",
+ Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(this, "Single Reg permission NOT granted",
+ Toast.LENGTH_SHORT).show();
+ }
+
+ }
+
private Intent getSendStatusIntent() {
// Encode requestId in intent data
return new Intent(SendStatusReceiver.MESSAGE_SENT_ACTION, null, this,
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/TelephonyManagerTestApp/AndroidManifest.xml b/testapps/TelephonyManagerTestApp/AndroidManifest.xml
index 044d0b2..40fc549 100644
--- a/testapps/TelephonyManagerTestApp/AndroidManifest.xml
+++ b/testapps/TelephonyManagerTestApp/AndroidManifest.xml
@@ -15,7 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.phone.testapps.telephonymanagertestapp">
+ package="com.android.phone.testapps.telephonymanagertestapp">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE"/>
@@ -26,31 +26,29 @@
<uses-permission android:name="android.permission.CALL_PRIVILEGED"/>
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
- android.Manifest.permission.ACCESS_FINE_LOCATION
+ android.Manifest.permission.ACCESS_FINE_LOCATION
<application android:label="TelephonyManagerTestApp">
- <activity
- android:name=".TelephonyManagerTestApp"
- android:label="TelephonyManagerTestApp">
+ <activity android:name=".TelephonyManagerTestApp"
+ android:label="TelephonyManagerTestApp"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <action android:name="android.intent.action.SEARCH" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN"/>
+ <action android:name="android.intent.action.SEARCH"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
- <meta-data
- android:name="android.app.searchable"
- android:resource="@xml/searchable">
+ <meta-data android:name="android.app.searchable"
+ android:resource="@xml/searchable">
</meta-data>
</activity>
- <activity
- android:name=".CallingMethodActivity"
- android:label="CallingMethodActivity">
+ <activity android:name=".CallingMethodActivity"
+ android:label="CallingMethodActivity"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
</manifest>
-
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/TelephonyRegistryTestApp/AndroidManifest.xml b/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
index 550c9f0..7432156 100644
--- a/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
+++ b/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
@@ -15,24 +15,23 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.phone.testapps.telephonyregistry">
+ package="com.android.phone.testapps.telephonyregistry">
<uses-sdk android:minSdkVersion="25"
- android:targetSdkVersion="25"/>
+ android:targetSdkVersion="25"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<uses-permission android:name="android.permission.READ_PRECISE_PHONE_STATE"/>
<application android:label="TelephonyRegistryTestApp">
- <activity
- android:name=".TelephonyRegistryTestApp"
- android:label="TelephonyRegistryTestApp">
+ <activity android:name=".TelephonyRegistryTestApp"
+ android:label="TelephonyRegistryTestApp"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
-
diff --git a/testapps/TestRcsApp/OWNERS b/testapps/TestRcsApp/OWNERS
new file mode 100644
index 0000000..1d0d52b
--- /dev/null
+++ b/testapps/TestRcsApp/OWNERS
@@ -0,0 +1,3 @@
+allenwtsu@google.com
+calvinpan@google.com
+jamescflin@google.com
diff --git a/testapps/TestRcsApp/TestApp/Android.bp b/testapps/TestRcsApp/TestApp/Android.bp
new file mode 100644
index 0000000..cda7d17
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/Android.bp
@@ -0,0 +1,40 @@
+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",
+
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ static_libs: [
+ "androidx-constraintlayout_constraintlayout",
+ "aosp_test_rcs_client_base",
+ "androidx.appcompat_appcompat",
+ "libphonenumber-platform"
+ ],
+
+ libs: ["org.apache.http.legacy"],
+
+ certificate: "platform",
+ privileged: true,
+ product_specific: true,
+
+ sdk_version: "system_current",
+ min_sdk_version: "30",
+ required: ["privapp-permissions-com.google.android.sample.rcsclient.xml"]
+}
+
+prebuilt_etc {
+ name: "privapp-permissions-com.google.android.sample.rcsclient.xml",
+ src: "etc/permissions/privapp-permissions-com.google.android.sample.rcsclient.xml",
+ sub_dir:"permissions",
+ product_specific: true,
+}
diff --git a/testapps/TestRcsApp/TestApp/AndroidManifest.xml b/testapps/TestRcsApp/TestApp/AndroidManifest.xml
new file mode 100644
index 0000000..35a0822
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/AndroidManifest.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //packages/services/Telephony/testapps/TestRcsApp/TestApp/AndroidManifest.xml
+**
+** Copyright 2020, 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.
+*/
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.sample.rcsclient"
+ android:versionCode="14"
+ android:versionName="1.0.13">
+
+ <uses-sdk
+ android:minSdkVersion="30"
+ android:targetSdkVersion="30" />
+
+ <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+ <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
+ <!-- Granted by SMS role-->
+ <uses-permission android:name="android.permission.PERFORM_IMS_SINGLE_REGISTRATION" />
+ <!-- Required for UCE and granted by SMS role -->
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+ <uses-permission android:name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".DelegateActivity" />
+ <activity android:name=".UceActivity" />
+ <activity android:name=".GbaActivity" />
+ <activity android:name=".PhoneNumberActivity" />
+ <activity android:name=".ChatActivity" />
+ <activity android:name=".ContactListActivity" />
+ <activity android:name=".ProvisioningActivity" />
+ <activity android:name=".FileUploadActivity" />
+
+ <provider
+ android:name=".util.ChatProvider"
+ android:authorities="rcsprovider" />
+
+
+ <!-- In order to make this App eligible to be selected as the default Message App, the
+ following components are required to be declared even if they are not implemented.
+ -->
+
+ <!-- BroadcastReceiver that listens for incoming SMS messages -->
+ <receiver
+ android:name=".SmsReceiver"
+ android:permission="android.permission.BROADCAST_SMS">
+ <intent-filter>
+ <action android:name="android.provider.Telephony.SMS_DELIVER" />
+ </intent-filter>
+ </receiver>
+
+ <!-- BroadcastReceiver that listens for incoming MMS messages -->
+ <receiver
+ android:name=".MmsReceiver"
+ android:permission="android.permission.BROADCAST_WAP_PUSH">
+ <intent-filter>
+ <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
+ <data android:mimeType="application/vnd.wap.mms-message" />
+ </intent-filter>
+ </receiver>
+
+ <!-- Activity that allows the user to send new SMS/MMS messages -->
+ <activity android:name=".ComposeSmsActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.SEND" />
+ <action android:name="android.intent.action.SENDTO" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+
+ <data android:scheme="sms" />
+ <data android:scheme="smsto" />
+ <data android:scheme="mms" />
+ <data android:scheme="mmsto" />
+ </intent-filter>
+ </activity>
+
+ <!-- Service that delivers messages from the phone "quick response" -->
+ <service
+ android:name=".HeadlessSmsSendService"
+ android:exported="true"
+ android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE">
+ <intent-filter>
+ <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
+ <category android:name="android.intent.category.DEFAULT" />
+
+ <data android:scheme="sms" />
+ <data android:scheme="smsto" />
+ <data android:scheme="mms" />
+ <data android:scheme="mmsto" />
+ </intent-filter>
+ </service>
+
+ </application>
+
+</manifest>
diff --git a/testapps/TestRcsApp/TestApp/etc/permissions/privapp-permissions-com.google.android.sample.rcsclient.xml b/testapps/TestRcsApp/TestApp/etc/permissions/privapp-permissions-com.google.android.sample.rcsclient.xml
new file mode 100644
index 0000000..4064db4
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/etc/permissions/privapp-permissions-com.google.android.sample.rcsclient.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<permissions>
+ <privapp-permissions package="com.google.android.sample.rcsclient">
+ <permission name="android.permission.MODIFY_PHONE_STATE"/>
+ <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+ <permission name="android.permission.ACCESS_NETWORK_STATE"/>
+ <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
+ </privapp-permissions>
+</permissions>
\ No newline at end of file
diff --git a/testapps/TestRcsApp/TestApp/res/drawable-v24/ic_launcher_foreground.xml b/testapps/TestRcsApp/TestApp/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..fc0c6ab
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,42 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:fillType="evenOdd"
+ android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,
+ 49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
+ android:strokeWidth="1"
+ android:strokeColor="#00000000">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:endX="78.5885"
+ android:endY="90.9159"
+ android:startX="48.7653"
+ android:startY="61.0927"
+ android:type="linear">
+ <item
+ android:color="#44000000"
+ android:offset="0.0" />
+ <item
+ android:color="#00000000"
+ android:offset="1.0" />
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:fillColor="#FFFFFF"
+ android:fillType="nonZero"
+ android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,
+ 50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,
+ 37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,
+ 42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,
+ 40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,
+ 52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,
+ 56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,
+ 52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
+ android:strokeWidth="1"
+ android:strokeColor="#00000000" />
+</vector>
diff --git a/testapps/TestRcsApp/TestApp/res/drawable/ic_launcher_background.xml b/testapps/TestRcsApp/TestApp/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:fillColor="#3DDC84"
+ android:pathData="M0,0h108v108h-108z" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M9,0L9,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,0L19,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,0L29,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,0L39,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,0L49,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,0L59,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,0L69,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,0L79,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M89,0L89,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M99,0L99,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,9L108,9"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,19L108,19"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,29L108,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,39L108,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,49L108,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,59L108,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,69L108,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,79L108,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,89L108,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,99L108,99"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,29L89,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,39L89,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,49L89,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,59L89,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,69L89,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,79L89,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,19L29,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,19L39,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,19L49,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,19L59,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,19L69,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,19L79,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/testapps/TestRcsApp/TestApp/res/layout/activity_main.xml b/testapps/TestRcsApp/TestApp/res/layout/activity_main.xml
new file mode 100644
index 0000000..939feb0
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/layout/activity_main.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".MainActivity">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <Button
+ android:id="@+id/provision"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/provisioning_test"
+ android:textAlignment="center"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/delegate"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/delegate_test"
+ android:textAlignment="center"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/uce"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/uce_test"
+ android:textAlignment="center"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/gba"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/gba_test"
+ android:textAlignment="center"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/msgClient"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/test_msg_client"
+ android:textAlignment="center"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/uploadFile"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/upload_file_gba"
+ android:textAlignment="center"
+ android:textAllCaps="false" />
+
+ <TextView
+ android:id="@+id/version_info"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/version_info"
+ android:textAlignment="center"
+ android:paddingTop="7dp"/>
+ </LinearLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/testapps/TestRcsApp/TestApp/res/layout/chat_layout.xml b/testapps/TestRcsApp/TestApp/res/layout/chat_layout.xml
new file mode 100644
index 0000000..e184b04
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/layout/chat_layout.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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
+ -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/session_tips"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="15dp"
+ android:textStyle="bold" />
+
+ <LinearLayout
+ android:id="@id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_below="@+id/session_tips">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/to"
+ android:textSize="15dp"
+ android:textStyle="bold" />
+
+ <EditText
+ android:id="@+id/destNum"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inputType="phone"
+ android:digits="0123456789+"
+ android:hint="+15555551212" />
+ </LinearLayout>
+
+
+ <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/title">
+
+ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/relative_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"></RelativeLayout>
+ </ScrollView>
+
+ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true">
+
+ <EditText
+ android:id="@+id/new_msg"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_toLeftOf="@+id/chat_btn"
+ android:text="@string/chat_message" />
+
+ <Button
+ android:id="@+id/chat_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:text="@string/send" />
+ </RelativeLayout>
+
+</RelativeLayout>
diff --git a/testapps/TestRcsApp/TestApp/res/layout/contact_list.xml b/testapps/TestRcsApp/TestApp/res/layout/contact_list.xml
new file mode 100644
index 0000000..0117549
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/layout/contact_list.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/tips"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="15dp"
+ android:textStyle="bold" />
+
+ <Button
+ android:id="@+id/start_chat_btn"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:text="@string/start_chat"
+ android:textAllCaps="false" />
+
+ <ListView
+ android:id="@+id/listview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_alignParentBottom="true" />
+
+</LinearLayout>
diff --git a/testapps/TestRcsApp/TestApp/res/layout/delegate_layout.xml b/testapps/TestRcsApp/TestApp/res/layout/delegate_layout.xml
new file mode 100644
index 0000000..94d6efa
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/layout/delegate_layout.xml
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".DelegateActivity">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <CheckBox
+ android:id="@+id/standalone-pager"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/standalone_pager" />
+
+ <CheckBox
+ android:id="@+id/standalone-large"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/standalone_large" />
+
+ <CheckBox
+ android:id="@+id/standalone-deferred"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/standalone_deferred" />
+
+ <CheckBox
+ android:id="@+id/standalone-pager-large"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/standalone_pager_large" />
+
+ <CheckBox
+ android:id="@+id/chat"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/chat" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <CheckBox
+ android:id="@+id/file_transfer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/file_transfer" />
+
+ <CheckBox
+ android:id="@+id/geolocation_sms"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/geolocation_sms" />
+
+ <CheckBox
+ android:id="@+id/chatbot_session"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/chatbot_session" />
+
+ <CheckBox
+ android:id="@+id/chatbot_standalone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/chatbot_standalone" />
+
+ <CheckBox
+ android:id="@+id/chatbot_version"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/chatbot_version" />
+ </LinearLayout>
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/init_btn"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:text="@string/initialize_delegate"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/destroy_btn"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:text="@string/destroy_delegate"
+ android:textAllCaps="false" />
+
+ <TextView
+ android:id="@+id/delegate_callback_result"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="30dp"
+ android:scrollbars="vertical"
+ android:text="@string/callback_result"
+ android:textSize="15dp"
+ android:textStyle="bold" />
+
+ </LinearLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/testapps/TestRcsApp/TestApp/res/layout/file_upload_layout.xml b/testapps/TestRcsApp/TestApp/res/layout/file_upload_layout.xml
new file mode 100644
index 0000000..a41376b
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/layout/file_upload_layout.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".FileUploadActivity">
+
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/server"
+ android:textSize="15dp"
+ android:textStyle="bold" />
+
+ <EditText
+ android:id="@+id/ft_uri"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="15dp" />
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/browse_btn"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:text="@string/browse"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/upload_btn"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:text="@string/upload"
+ android:textAllCaps="false" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView
+ android:text="@string/file_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="15dp"
+ android:textStyle="bold"/>
+ <TextView
+ android:id="@+id/file_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="15dp"
+ android:textStyle="bold"/>
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/upload_file_result"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/result"
+ android:scrollbars="vertical"
+ android:layout_marginTop="20dp"
+ android:textSize="15dp"
+ android:textStyle="bold" />
+
+ </LinearLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/testapps/TestRcsApp/TestApp/res/layout/gba_layout.xml b/testapps/TestRcsApp/TestApp/res/layout/gba_layout.xml
new file mode 100644
index 0000000..f9866e8
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/layout/gba_layout.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".GbaActivity">
+
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/organization"
+ android:textSize="15dp"
+ android:textStyle="bold" />
+
+ <Spinner
+ android:id="@+id/organization_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/uicc_type"
+ android:textSize="15dp"
+ android:textStyle="bold" />
+
+ <Spinner
+ android:id="@+id/uicc_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/protocol"
+ android:textSize="15dp"
+ android:textStyle="bold" />
+
+ <Spinner
+ android:id="@+id/protocol_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/tls_cs"
+ android:textSize="15dp"
+ android:textStyle="bold" />
+
+ <EditText
+ android:id="@+id/tls_id"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inputType="number"
+ android:text="47"
+ android:textSize="15dp" />
+ </LinearLayout>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/naf"
+ android:textSize="15dp"
+ android:textStyle="bold" />
+
+ <EditText
+ android:id="@+id/naf_url"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="15dp" />
+
+ <Button
+ android:id="@+id/gba_btn"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:text="@string/gba_bootstrap"
+ android:textAllCaps="false" />
+
+ <TextView
+ android:id="@+id/gba_result"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scrollbars="vertical"
+ android:text="@string/result"
+ android:textSize="15dp"
+ android:textStyle="bold" />
+ </LinearLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/testapps/TestRcsApp/TestApp/res/layout/number_to_chat.xml b/testapps/TestRcsApp/TestApp/res/layout/number_to_chat.xml
new file mode 100644
index 0000000..7e31581
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/layout/number_to_chat.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/to"
+ android:textSize="15dp"
+ android:textStyle="bold" />
+
+ <EditText
+ android:id="@+id/destNum"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inputType="phone"
+ android:digits="0123456789+"
+ android:hint="+15555551212" />
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/launch_chat_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/ok" />
+
+</LinearLayout>
diff --git a/testapps/TestRcsApp/TestApp/res/layout/provision_layout.xml b/testapps/TestRcsApp/TestApp/res/layout/provision_layout.xml
new file mode 100644
index 0000000..47f534a
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/layout/provision_layout.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".ProvisionActivity">
+
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/rcs_profile"
+ android:textSize="15dp"
+ android:textStyle="bold" />
+
+ <Spinner
+ android:id="@+id/rcs_profile_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/provisioning_register_btn"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:text="@string/register_provisioning_callback"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/provisioning_unregister_btn"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:text="@string/unregister_provisioning_callback"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/provisioning_singlereg_btn"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:text="@string/isRcsVolteSingleRegCapable"
+ android:textAllCaps="false" />
+
+ <TextView
+ android:id="@+id/provisioning_singlereg_result"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/result"
+ android:textSize="15dp"
+ android:textStyle="bold" />
+
+ <TextView
+ android:id="@+id/provisioning_callback_result"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:scrollbars="vertical"
+ android:text="@string/callback_result"
+ android:textSize="15dp"
+ android:textStyle="bold" />
+
+ </LinearLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/testapps/TestRcsApp/TestApp/res/layout/uce_layout.xml b/testapps/TestRcsApp/TestApp/res/layout/uce_layout.xml
new file mode 100644
index 0000000..a4e6ff2
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/layout/uce_layout.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".UceActivity">
+
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/uce_description"
+ android:textSize="15dp"
+ android:textStyle="bold" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/number"
+ android:textSize="15dp"
+ android:textStyle="bold" />
+
+ <EditText
+ android:id="@+id/number_list"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inputType="phone"
+ android:digits="0123456789+,"
+ android:hint="+16505551212,+16505551213" />
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/capability_btn"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:text="@string/request_capability"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/availability_btn"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:text="@string/request_availability"
+ android:textAllCaps="false" />
+
+ <TextView
+ android:id="@+id/capability_result"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/result"
+ android:scrollbars="vertical"
+ android:textSize="15dp"
+ android:textStyle="bold" />
+ </LinearLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/testapps/TestRcsApp/TestApp/res/mipmap-anydpi-v26/ic_launcher.xml b/testapps/TestRcsApp/TestApp/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/testapps/TestRcsApp/TestApp/res/mipmap-anydpi-v26/ic_launcher_round.xml b/testapps/TestRcsApp/TestApp/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/testapps/TestRcsApp/TestApp/res/values-as/strings.xml b/testapps/TestRcsApp/TestApp/res/values-as/strings.xml
new file mode 100644
index 0000000..2fac78c
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-as/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"RcsClient"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"প্ৰ’ভিজনিং পৰীক্ষা"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"অধিকাৰ প্ৰদান কৰাৰ পৰীক্ষা"</string>
+ <string name="uce_test" msgid="6466919844953883038">"UCE পৰীক্ষা"</string>
+ <string name="gba_test" msgid="8786745991279224003">"GBA পৰীক্ষা"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DBClient"</string>
+ <string name="result" msgid="435222369457564619">"ফলাফল:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"কলবেকৰ ফলাফল:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initializeSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"সন্ধানৰ সামৰ্থ্যত নম্বৰটো দিয়ক আৰু বহুকেইটা থাকিলে \',\'ৰ দ্বাৰা পৃথক কৰক।"</string>
+ <string name="number" msgid="9096578126043995890">"সংখ্যা:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"চাট আৰম্ভ কৰক"</string>
+ <string name="to" msgid="1156781884609653284">"প্ৰতি:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"চাট বার্তা"</string>
+ <string name="send" msgid="8797467615003240434">"পঠিয়াওক"</string>
+ <string name="ok" msgid="2206671077510386729">"ঠিক আছে"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"ছেশ্বন আৰম্ভ কৰাত সফল হৈছে"</string>
+ <string name="session_failed" msgid="4524986240102190509">"ছেশ্বন আৰম্ভ কৰাত বিফল হৈছে"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"ছেশ্বন সাজু নহয়"</string>
+ <string name="organization" msgid="913518283470119113">"সংস্থা:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"UICCৰ ধৰণ:"</string>
+ <string name="protocol" msgid="8626139294022821930">"প্ৰট’কল:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"TLS চাইফাৰ ছুইট:"</string>
+ <string name="naf" msgid="7620247915001854662">"NAF URI:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"ষ্টেণ্ডএল’ন পেজাৰ"</string>
+ <string name="standalone_large" msgid="336170805696694543">"ষ্টেণ্ডএল’ন ডাঙৰ"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"ষ্টেণ্ডএল’ন স্থগিত ৰখা হৈছে"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"ষ্টেণ্ডএল’ন ডাঙৰ পেজাৰ"</string>
+ <string name="chat" msgid="5500583808773151982">"চাট"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"ফাইল স্থানান্তৰণ"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"জিঅ’লোকেশ্বনৰ এছএমএছ"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"চাটব’ট ছেশ্বন"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"ষ্টেণ্ডএল’ন চাটব’ট"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"চাটব’টৰ সংস্কৰণ"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"প্ৰ’ভিজনিং সম্পন্ন হৈছে"</string>
+ <string name="registration_done" msgid="492878616836655421">"পঞ্জীয়ন সম্পন্ন হৈছে"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"একো নাই"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"স্থানীয়"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"অজ্ঞাত"</item>
+ <item msgid="1715684709703792003">"ছিম"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-be/strings.xml b/testapps/TestRcsApp/TestApp/res/values-be/strings.xml
new file mode 100644
index 0000000..5196cf4
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-be/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"RcsClient"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"Тэст ініцыялізацыі"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"Дэлегаванне тэста"</string>
+ <string name="uce_test" msgid="6466919844953883038">"Тэст UCE"</string>
+ <string name="gba_test" msgid="8786745991279224003">"Тэст GBA"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DBClient"</string>
+ <string name="result" msgid="435222369457564619">"Вынік:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"Вынік зваротнага выкліку"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initializeSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"Увядзіце нумар для запыту магчымасці. Калі нумароў некалькі, раздзяліце іх коскамі."</string>
+ <string name="number" msgid="9096578126043995890">"Нумар:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"Пачаць чат"</string>
+ <string name="to" msgid="1156781884609653284">"Каму:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"Паведамленне чата"</string>
+ <string name="send" msgid="8797467615003240434">"Адправіць"</string>
+ <string name="ok" msgid="2206671077510386729">"ОК"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"Ініцыялізацыя сеанса выканана"</string>
+ <string name="session_failed" msgid="4524986240102190509">"Збой ініцыялізацыі сеанса"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"Сеанс не гатовы"</string>
+ <string name="organization" msgid="913518283470119113">"Арганізацыя:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"Тып UICC:"</string>
+ <string name="protocol" msgid="8626139294022821930">"Пратакол:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"Набор шыфраў TLS:"</string>
+ <string name="naf" msgid="7620247915001854662">"NAF URI:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"Аўтаномны пэйджар"</string>
+ <string name="standalone_large" msgid="336170805696694543">"Аўтаномны вялікі"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"Аўтаномны адкладзены"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"Аўтаномны вялікі пэйджар"</string>
+ <string name="chat" msgid="5500583808773151982">"Чат"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"Перадача файлаў"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"SMS геалакацыі"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"Сеанс чат-бота"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"Аўтаномны чат-бот"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"Версія чат-бота"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"Ініцыялізацыя выканана"</string>
+ <string name="registration_done" msgid="492878616836655421">"Рэгістрацыя выканана"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"НЯМА"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"МЯСЦОВАЯ СЕТКА"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"НЕВЯДОМА"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-bs/strings.xml b/testapps/TestRcsApp/TestApp/res/values-bs/strings.xml
new file mode 100644
index 0000000..8787d26
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-bs/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"RcsClient"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"Test za dodjeljivanje"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"Test ovlaštenika"</string>
+ <string name="uce_test" msgid="6466919844953883038">"Test UCE"</string>
+ <string name="gba_test" msgid="8786745991279224003">"Test GBA"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DBClient"</string>
+ <string name="result" msgid="435222369457564619">"Rezultat:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"Rezultat povratnog poziva:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initializeSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"Unesite broj da pošaljte upit o kapacitetu te upite razdvojite \",\" ako ih ima više."</string>
+ <string name="number" msgid="9096578126043995890">"Broj:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"Započni chat"</string>
+ <string name="to" msgid="1156781884609653284">"Za:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"Poruka chata"</string>
+ <string name="send" msgid="8797467615003240434">"Pošalji"</string>
+ <string name="ok" msgid="2206671077510386729">"Uredu"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"Pokretanje sesije je uspjelo"</string>
+ <string name="session_failed" msgid="4524986240102190509">"Pokretanje sesije nije uspjelo"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"Sesija nije spremna"</string>
+ <string name="organization" msgid="913518283470119113">"Organizacija:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"Vrsta UICC:"</string>
+ <string name="protocol" msgid="8626139294022821930">"Protokol:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"Paket TLS Cipher:"</string>
+ <string name="naf" msgid="7620247915001854662">"NAF URI:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"Samostalni pejdžer"</string>
+ <string name="standalone_large" msgid="336170805696694543">"Samostalno veliko"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"Samostalno odgođeno"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"Samostalni veliki pejdžer"</string>
+ <string name="chat" msgid="5500583808773151982">"Chat"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"Prijenos fajlova"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"SMS za geolokaciju"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"Sesija bota za chat"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"Samostalni bot za chat"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"Vezija bota za chat"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"Dodjeljivanje je završeno"</string>
+ <string name="registration_done" msgid="492878616836655421">"Registracija je završena"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"NEMA"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"LOKALNO"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"CERTIFIKAT_PRETPLATNIKA"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"NEPOZNATO"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-en-rAU/strings.xml b/testapps/TestRcsApp/TestApp/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..bec17d3
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-en-rAU/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"Rcs client"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"Provisioning test"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"Delegate test"</string>
+ <string name="uce_test" msgid="6466919844953883038">"UCE test"</string>
+ <string name="gba_test" msgid="8786745991279224003">"GBA test"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DB client"</string>
+ <string name="result" msgid="435222369457564619">"Result:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"Callback result:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initialiseSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"Enter the number to query capability and separate by \',\' if multiple ones."</string>
+ <string name="number" msgid="9096578126043995890">"Number:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"Start chat"</string>
+ <string name="to" msgid="1156781884609653284">"To:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"Chat message"</string>
+ <string name="send" msgid="8797467615003240434">"Send"</string>
+ <string name="ok" msgid="2206671077510386729">"OK"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"Session init succeeded"</string>
+ <string name="session_failed" msgid="4524986240102190509">"Session init failed"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"Session not ready"</string>
+ <string name="organization" msgid="913518283470119113">"Organisation:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"UICC type:"</string>
+ <string name="protocol" msgid="8626139294022821930">"Protocol:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"TLS cipher suite:"</string>
+ <string name="naf" msgid="7620247915001854662">"NAF URI:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"Standalone pager"</string>
+ <string name="standalone_large" msgid="336170805696694543">"Standalone large"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"Standalone deferred"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"Standalone large pager"</string>
+ <string name="chat" msgid="5500583808773151982">"Chat"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"File transfer"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"Geolocation SMS"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"Chatbot session"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"Chatbot standalone"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"Chatbot version"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"Provisioning done"</string>
+ <string name="registration_done" msgid="492878616836655421">"Registration done"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"NONE"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"LOCAL"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"UNKNOWN"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-en-rCA/strings.xml b/testapps/TestRcsApp/TestApp/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..bec17d3
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-en-rCA/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"Rcs client"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"Provisioning test"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"Delegate test"</string>
+ <string name="uce_test" msgid="6466919844953883038">"UCE test"</string>
+ <string name="gba_test" msgid="8786745991279224003">"GBA test"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DB client"</string>
+ <string name="result" msgid="435222369457564619">"Result:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"Callback result:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initialiseSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"Enter the number to query capability and separate by \',\' if multiple ones."</string>
+ <string name="number" msgid="9096578126043995890">"Number:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"Start chat"</string>
+ <string name="to" msgid="1156781884609653284">"To:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"Chat message"</string>
+ <string name="send" msgid="8797467615003240434">"Send"</string>
+ <string name="ok" msgid="2206671077510386729">"OK"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"Session init succeeded"</string>
+ <string name="session_failed" msgid="4524986240102190509">"Session init failed"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"Session not ready"</string>
+ <string name="organization" msgid="913518283470119113">"Organisation:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"UICC type:"</string>
+ <string name="protocol" msgid="8626139294022821930">"Protocol:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"TLS cipher suite:"</string>
+ <string name="naf" msgid="7620247915001854662">"NAF URI:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"Standalone pager"</string>
+ <string name="standalone_large" msgid="336170805696694543">"Standalone large"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"Standalone deferred"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"Standalone large pager"</string>
+ <string name="chat" msgid="5500583808773151982">"Chat"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"File transfer"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"Geolocation SMS"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"Chatbot session"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"Chatbot standalone"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"Chatbot version"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"Provisioning done"</string>
+ <string name="registration_done" msgid="492878616836655421">"Registration done"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"NONE"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"LOCAL"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"UNKNOWN"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-en-rGB/strings.xml b/testapps/TestRcsApp/TestApp/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..bec17d3
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-en-rGB/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"Rcs client"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"Provisioning test"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"Delegate test"</string>
+ <string name="uce_test" msgid="6466919844953883038">"UCE test"</string>
+ <string name="gba_test" msgid="8786745991279224003">"GBA test"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DB client"</string>
+ <string name="result" msgid="435222369457564619">"Result:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"Callback result:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initialiseSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"Enter the number to query capability and separate by \',\' if multiple ones."</string>
+ <string name="number" msgid="9096578126043995890">"Number:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"Start chat"</string>
+ <string name="to" msgid="1156781884609653284">"To:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"Chat message"</string>
+ <string name="send" msgid="8797467615003240434">"Send"</string>
+ <string name="ok" msgid="2206671077510386729">"OK"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"Session init succeeded"</string>
+ <string name="session_failed" msgid="4524986240102190509">"Session init failed"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"Session not ready"</string>
+ <string name="organization" msgid="913518283470119113">"Organisation:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"UICC type:"</string>
+ <string name="protocol" msgid="8626139294022821930">"Protocol:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"TLS cipher suite:"</string>
+ <string name="naf" msgid="7620247915001854662">"NAF URI:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"Standalone pager"</string>
+ <string name="standalone_large" msgid="336170805696694543">"Standalone large"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"Standalone deferred"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"Standalone large pager"</string>
+ <string name="chat" msgid="5500583808773151982">"Chat"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"File transfer"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"Geolocation SMS"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"Chatbot session"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"Chatbot standalone"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"Chatbot version"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"Provisioning done"</string>
+ <string name="registration_done" msgid="492878616836655421">"Registration done"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"NONE"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"LOCAL"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"UNKNOWN"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-en-rIN/strings.xml b/testapps/TestRcsApp/TestApp/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..bec17d3
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-en-rIN/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"Rcs client"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"Provisioning test"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"Delegate test"</string>
+ <string name="uce_test" msgid="6466919844953883038">"UCE test"</string>
+ <string name="gba_test" msgid="8786745991279224003">"GBA test"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DB client"</string>
+ <string name="result" msgid="435222369457564619">"Result:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"Callback result:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initialiseSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"Enter the number to query capability and separate by \',\' if multiple ones."</string>
+ <string name="number" msgid="9096578126043995890">"Number:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"Start chat"</string>
+ <string name="to" msgid="1156781884609653284">"To:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"Chat message"</string>
+ <string name="send" msgid="8797467615003240434">"Send"</string>
+ <string name="ok" msgid="2206671077510386729">"OK"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"Session init succeeded"</string>
+ <string name="session_failed" msgid="4524986240102190509">"Session init failed"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"Session not ready"</string>
+ <string name="organization" msgid="913518283470119113">"Organisation:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"UICC type:"</string>
+ <string name="protocol" msgid="8626139294022821930">"Protocol:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"TLS cipher suite:"</string>
+ <string name="naf" msgid="7620247915001854662">"NAF URI:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"Standalone pager"</string>
+ <string name="standalone_large" msgid="336170805696694543">"Standalone large"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"Standalone deferred"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"Standalone large pager"</string>
+ <string name="chat" msgid="5500583808773151982">"Chat"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"File transfer"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"Geolocation SMS"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"Chatbot session"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"Chatbot standalone"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"Chatbot version"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"Provisioning done"</string>
+ <string name="registration_done" msgid="492878616836655421">"Registration done"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"NONE"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"LOCAL"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"UNKNOWN"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-en-rXC/strings.xml b/testapps/TestRcsApp/TestApp/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..ef9b6cb
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-en-rXC/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"RcsClient"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"Provisioning Test"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"Delegate Test"</string>
+ <string name="uce_test" msgid="6466919844953883038">"UCE Test"</string>
+ <string name="gba_test" msgid="8786745991279224003">"GBA Test"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DBClient"</string>
+ <string name="result" msgid="435222369457564619">"Result:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"Callback Result:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initializeSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"Enter the number to query capability and separate by \',\' if multiple ones."</string>
+ <string name="number" msgid="9096578126043995890">"Number:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"Start Chat"</string>
+ <string name="to" msgid="1156781884609653284">"To:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"Chat Message"</string>
+ <string name="send" msgid="8797467615003240434">"Send"</string>
+ <string name="ok" msgid="2206671077510386729">"OK"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"Session init succeeded"</string>
+ <string name="session_failed" msgid="4524986240102190509">"Session init failed"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"Session not ready"</string>
+ <string name="organization" msgid="913518283470119113">"Organization:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"UICC Type:"</string>
+ <string name="protocol" msgid="8626139294022821930">"Protocol:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"TLS Cipher Suite:"</string>
+ <string name="naf" msgid="7620247915001854662">"NAF URI:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"Standalone Pager"</string>
+ <string name="standalone_large" msgid="336170805696694543">"Standalone Large"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"Standalone Deferred"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"Standalone Large Pager"</string>
+ <string name="chat" msgid="5500583808773151982">"Chat"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"File Transfer"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"Geolocation SMS"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"Chatbot Session"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"Chatbot Standalone"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"Chatbot Version"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"Provisioning Done"</string>
+ <string name="registration_done" msgid="492878616836655421">"Registration Done"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"NONE"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"LOCAL"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"UNKNOWN"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-gu/strings.xml b/testapps/TestRcsApp/TestApp/res/values-gu/strings.xml
new file mode 100644
index 0000000..4842877
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-gu/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"RcsClient"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"જોગવાઈનું પરીક્ષણ"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"અધિકાર સોંપવાનું પરીક્ષણ"</string>
+ <string name="uce_test" msgid="6466919844953883038">"UCE પરીક્ષણ"</string>
+ <string name="gba_test" msgid="8786745991279224003">"GBA પરીક્ષણ"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DBClient"</string>
+ <string name="result" msgid="435222369457564619">"પરિણામ:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"કૉલબૅકનું પરિણામ:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initializeSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"ક્વેરીની ક્ષમતામાં નંબર દાખલ કરો અને એક કરતાં વધુ હોય તો વડે \',\' જુદાં પાડો."</string>
+ <string name="number" msgid="9096578126043995890">"નંબર:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"ચૅટ શરૂ કરો"</string>
+ <string name="to" msgid="1156781884609653284">"પ્રતિ:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"ચૅટ સંદેશ"</string>
+ <string name="send" msgid="8797467615003240434">"મોકલો"</string>
+ <string name="ok" msgid="2206671077510386729">"ઓકે"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"સત્ર શરૂ કરવાનું સફળ થયું"</string>
+ <string name="session_failed" msgid="4524986240102190509">"સત્ર શરૂ કરવાનું નિષ્ફળ રહ્યું"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"સત્ર તૈયાર નથી"</string>
+ <string name="organization" msgid="913518283470119113">"સંસ્થા:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"UICCનો પ્રકાર:"</string>
+ <string name="protocol" msgid="8626139294022821930">"પ્રોટોકૉલ:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"TLS સાઇફર સ્યૂટ:"</string>
+ <string name="naf" msgid="7620247915001854662">"NAF URI:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"સ્ટેન્ડઅલોન પેજર"</string>
+ <string name="standalone_large" msgid="336170805696694543">"સ્ટેન્ડઅલોન મોટું"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"સ્ટેન્ડઅલોન સ્થગિત થયું"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"સ્ટેન્ડઅલોન મોટું પેજર"</string>
+ <string name="chat" msgid="5500583808773151982">"ચૅટ"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"ફાઇલ ટ્રાન્સફર"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"ભૌગોલિક સ્થાન SMS"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"ચૅટબૉટ સત્ર"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"ચૅટબૉટ સ્ટેન્ડઅલોન"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"ચૅટબૉટ વર્ઝન"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"જોગવાઈ કરી"</string>
+ <string name="registration_done" msgid="492878616836655421">"રજિસ્ટ્રેશન થઈ ગયું"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"કોઈ નહીં"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"સ્થાનિક"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"અજાણ"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-ko/strings.xml b/testapps/TestRcsApp/TestApp/res/values-ko/strings.xml
new file mode 100644
index 0000000..38f1bd5
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-ko/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"RcsClient"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"프로비저닝 테스트"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"테스트 위임"</string>
+ <string name="uce_test" msgid="6466919844953883038">"UCE 테스트"</string>
+ <string name="gba_test" msgid="8786745991279224003">"GBA 테스트"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DBClient"</string>
+ <string name="result" msgid="435222369457564619">"결과:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"콜백 결과:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initializeSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"기능을 쿼리할 숫자를 입력하고, 여러 개일 경우 \',\' 기호로 구분하세요."</string>
+ <string name="number" msgid="9096578126043995890">"번호:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"채팅 시작"</string>
+ <string name="to" msgid="1156781884609653284">"대상:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"채팅 메시지"</string>
+ <string name="send" msgid="8797467615003240434">"보내기"</string>
+ <string name="ok" msgid="2206671077510386729">"확인"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"세션 초기화 성공"</string>
+ <string name="session_failed" msgid="4524986240102190509">"세션 초기화 실패"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"세션이 준비되지 않음"</string>
+ <string name="organization" msgid="913518283470119113">"조직:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"UICC 유형:"</string>
+ <string name="protocol" msgid="8626139294022821930">"프로토콜:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"TLS 암호화 스위트:"</string>
+ <string name="naf" msgid="7620247915001854662">"NAF URI:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"독립형 페이저"</string>
+ <string name="standalone_large" msgid="336170805696694543">"독립형 대형"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"독립형 지연됨"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"독립형 대형 페이저"</string>
+ <string name="chat" msgid="5500583808773151982">"채팅"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"파일 전송"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"위치정보 SMS"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"챗봇 세션"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"독립형 챗봇"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"챗봇 버전"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"프로비저닝 완료"</string>
+ <string name="registration_done" msgid="492878616836655421">"등록 완료"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"없음"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"로컬"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"알 수 없음"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-lo/strings.xml b/testapps/TestRcsApp/TestApp/res/values-lo/strings.xml
new file mode 100644
index 0000000..6050bad
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-lo/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"RcsClient"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"Provisioning Test"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"ທົດສອບຕົວແທນ"</string>
+ <string name="uce_test" msgid="6466919844953883038">"ທົດສອບ UCE"</string>
+ <string name="gba_test" msgid="8786745991279224003">"GBA Test"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DBClient"</string>
+ <string name="result" msgid="435222369457564619">"ຜົນໄດ້ຮັບ:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"ຜົນການໂທກັບ:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initializeSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"ປ້ອນໝາຍເລກໃສ່ຄຳຊອກຫາ ແລະ ຂັ້ນດ້ວຍ \',\' ຖ້າມີຫຼາຍກວ່າໜຶ່ງ."</string>
+ <string name="number" msgid="9096578126043995890">"ໝາຍເລກ:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"ເລີ່ມການສົນທະນາ"</string>
+ <string name="to" msgid="1156781884609653284">"ຫາ:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"ຂໍ້ຄວາມສົນທະນາ"</string>
+ <string name="send" msgid="8797467615003240434">"ສົ່ງ"</string>
+ <string name="ok" msgid="2206671077510386729">"ຕົກລົງ"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"ເລີ່ມເຊດຊັນສຳເລັດ"</string>
+ <string name="session_failed" msgid="4524986240102190509">"ເລີ່ມເຊດຊັນຜິດພາດ"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"ເຊດຊັນບໍ່ທັນພ້ອມ"</string>
+ <string name="organization" msgid="913518283470119113">"ອົງການ:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"ປະເພດ UICC:"</string>
+ <string name="protocol" msgid="8626139294022821930">"ໂປຣໂຕຄໍ:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"ຊຸດ TLS Cipher:"</string>
+ <string name="naf" msgid="7620247915001854662">"NAF URI:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"Standalone Pager"</string>
+ <string name="standalone_large" msgid="336170805696694543">"Standalone Large"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"Standalone Deferred"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"Standalone Large Pager"</string>
+ <string name="chat" msgid="5500583808773151982">"ສົນທະນາ"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"ການໂອນໄຟລ໌"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"ທີ່ຕັ້ງພູມສາດ SMS"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"ເຊດຊັນ Chatbot"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"Chatbot ແບບດ່ຽວ"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"ເວີຊັນ Chatbot"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"Provisioning Done"</string>
+ <string name="registration_done" msgid="492878616836655421">"Registration Done"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"ບໍ່ມີ"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"ພາຍໃນ"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"ບໍ່ຮູ້ຈັກ"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-ml/strings.xml b/testapps/TestRcsApp/TestApp/res/values-ml/strings.xml
new file mode 100644
index 0000000..00d9a4e
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-ml/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"RcsClient"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"പ്രൊവിഷനിംഗ് ടെസ്റ്റ്"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"ഡെലിഗേറ്റ് ടെസ്റ്റ്"</string>
+ <string name="uce_test" msgid="6466919844953883038">"UCE ടെസ്റ്റ്"</string>
+ <string name="gba_test" msgid="8786745991279224003">"GBA ടെസ്റ്റ്"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DBClient"</string>
+ <string name="result" msgid="435222369457564619">"ഫലം:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"തിരികെ വിളിക്കൽ ഫലം:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initializeSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"ചോദ്യത്തിന്റെ കാര്യക്ഷമത എന്നതിലേക്ക് നമ്പർ നൽകുക, ഒന്നിലധികം ഉണ്ടെങ്കിൽ അവയെ \',\' ഉപയോഗിച്ച് വേർതിരിക്കുക."</string>
+ <string name="number" msgid="9096578126043995890">"നമ്പർ:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"ചാറ്റ് ആരംഭിക്കുക"</string>
+ <string name="to" msgid="1156781884609653284">"സ്വീകർത്താവ്:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"Chat സന്ദേശം"</string>
+ <string name="send" msgid="8797467615003240434">"അയയ്ക്കുക"</string>
+ <string name="ok" msgid="2206671077510386729">"ശരി"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"സെഷൻ ആരംഭിച്ചു"</string>
+ <string name="session_failed" msgid="4524986240102190509">"സെഷൻ ആരംഭിക്കാനായില്ല"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"സെഷൻ തയ്യാറല്ല"</string>
+ <string name="organization" msgid="913518283470119113">"ഓർഗനൈസേഷൻ:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"UICC തരം:"</string>
+ <string name="protocol" msgid="8626139294022821930">"പ്രോട്ടോക്കോൾ:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"TLS Cipher Suite:"</string>
+ <string name="naf" msgid="7620247915001854662">"NAF URI:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"സ്റ്റാൻഡ്എലോൺ പേജർ"</string>
+ <string name="standalone_large" msgid="336170805696694543">"സ്റ്റാൻഡ്എലോൺ ലാർജ്"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"സ്റ്റാൻഡ്എലോൺ ഡെഫേഡ്"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"സ്റ്റാൻഡ്എലോൺ ലാർജ് പേജർ"</string>
+ <string name="chat" msgid="5500583808773151982">"Chat"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"ഫയൽ കൈമാറൽ"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"ജിയോലൊക്കേഷൻ SMS"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"ചാറ്റ്ബോട്ട് സെഷൻ"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"ചാറ്റ്ബോട്ട് സ്റ്റാൻഡ്എലോൺ"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"ചാറ്റ്ബോട്ട് പതിപ്പ്"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"പ്രൊവിഷനിംഗ് പൂർത്തിയായി"</string>
+ <string name="registration_done" msgid="492878616836655421">"രജിസ്ട്രേഷൻ പൂർത്തിയായി"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"ഒന്നുമില്ല"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"LOCAL"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"UNKNOWN"</item>
+ <item msgid="1715684709703792003">"സിം"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-ms/strings.xml b/testapps/TestRcsApp/TestApp/res/values-ms/strings.xml
new file mode 100644
index 0000000..758b2bd
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-ms/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"RcsClient"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"Ujian Peruntukan"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"Tugaskan Ujian"</string>
+ <string name="uce_test" msgid="6466919844953883038">"Ujian UCE"</string>
+ <string name="gba_test" msgid="8786745991279224003">"Ujian GBA"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DBClient"</string>
+ <string name="result" msgid="435222369457564619">"Hasil:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"Hasil Panggil Balik:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initializeSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"Masukkan nombor untuk bertanya keupayaan dan pisahkan dengan \',\' jika terdapat berbilang nombor."</string>
+ <string name="number" msgid="9096578126043995890">"Nombor:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"Mulakan Sembang"</string>
+ <string name="to" msgid="1156781884609653284">"Kepada:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"Mesej Sembang"</string>
+ <string name="send" msgid="8797467615003240434">"Hantar"</string>
+ <string name="ok" msgid="2206671077510386729">"OK"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"Pemulaan sesi berjaya"</string>
+ <string name="session_failed" msgid="4524986240102190509">"Pemulaan sesi gagal"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"Sesi belum sedia"</string>
+ <string name="organization" msgid="913518283470119113">"Organisasi:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"Jenis UICC:"</string>
+ <string name="protocol" msgid="8626139294022821930">"Protokol:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"Set Sifer TLS:"</string>
+ <string name="naf" msgid="7620247915001854662">"URI NAF:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"Alat Kelui Kendiri"</string>
+ <string name="standalone_large" msgid="336170805696694543">"Apl Kendiri Besar"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"Apl Kendiri Ditunda"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"Alat Kelui Kendiri Besar"</string>
+ <string name="chat" msgid="5500583808773151982">"Sembang"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"Pemindahan Fail"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"SMS Geolokasi"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"Sesi Chatbot"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"Apl Kendiri Chatbot"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"Versi Chatbot"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"Peruntukan Selesai"</string>
+ <string name="registration_done" msgid="492878616836655421">"Pendaftaran Selesai"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"TIADA"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"SETEMPAT"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"TIDAK DIKETAHUI"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-ne/strings.xml b/testapps/TestRcsApp/TestApp/res/values-ne/strings.xml
new file mode 100644
index 0000000..51cf376
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-ne/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"RcsClient"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"प्रबन्ध मिलाउने परीक्षण"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"परीक्षण गर्ने जिम्मा दिनुहोस्"</string>
+ <string name="uce_test" msgid="6466919844953883038">"UCE परीक्षण"</string>
+ <string name="gba_test" msgid="8786745991279224003">"GBA परीक्षण"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DBClient"</string>
+ <string name="result" msgid="435222369457564619">"परिणाम:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"कलब्याकसम्बन्धी परिणाम:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initializeSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"क्वेरी क्यापाबिलिटीको नम्बर हाल्नुहोस्। एकभन्दा बढी नम्बर छन् भने \',\' ले छुट्याउनुहोस्।"</string>
+ <string name="number" msgid="9096578126043995890">"नम्बर:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"च्याट गर्नुहोस्"</string>
+ <string name="to" msgid="1156781884609653284">"प्रापक:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"च्याट म्यासेज"</string>
+ <string name="send" msgid="8797467615003240434">"पठाउनुहोस्"</string>
+ <string name="ok" msgid="2206671077510386729">"ठिक छ"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"सत्र सफलतापूर्वक सुरु गरियो"</string>
+ <string name="session_failed" msgid="4524986240102190509">"सत्र सुरु गर्न सकिएन"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"सत्र तयार छैन"</string>
+ <string name="organization" msgid="913518283470119113">"सङ्गठन:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"UICC को प्रकार:"</string>
+ <string name="protocol" msgid="8626139294022821930">"प्रोटोकोल:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"TLS Cipher Suite:"</string>
+ <string name="naf" msgid="7620247915001854662">"NAF URI:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"स्ट्यान्डअलोन पेजर"</string>
+ <string name="standalone_large" msgid="336170805696694543">"स्ट्यान्डअलोन ठुलो"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"स्ट्यान्डअलोन विलम्बित गरिएको"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"स्ट्यान्डअलोन ठुलो पेजर"</string>
+ <string name="chat" msgid="5500583808773151982">"Chat"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"फाइल स्थानान्तरण"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"भूस्थानसम्बन्धी SMS"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"च्याटबटसँगको सत्र"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"च्याटबट स्ट्यान्डअलोन"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"च्याटबटको संस्करण"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"प्रबन्ध मिलाउने कार्य पूरा भयो"</string>
+ <string name="registration_done" msgid="492878616836655421">"दर्ता गर्ने कार्य पूरा भयो"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"कुनै पनि होइन"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"स्थानीय"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"अज्ञात"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-or/strings.xml b/testapps/TestRcsApp/TestApp/res/values-or/strings.xml
new file mode 100644
index 0000000..5c9491b
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-or/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"RcsClient"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"ପ୍ରୋଭିଜନିଂ ଟେଷ୍ଟ"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"ଟେଷ୍ଟ ଡେଲିଗେଟ୍ କରନ୍ତୁ"</string>
+ <string name="uce_test" msgid="6466919844953883038">"UCE ଟେଷ୍ଟ"</string>
+ <string name="gba_test" msgid="8786745991279224003">"GBA ଟେଷ୍ଟ"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DBClient"</string>
+ <string name="result" msgid="435222369457564619">"ଫଳାଫଳ:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"କଲବ୍ୟାକ୍ ଫଳାଫଳ:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initializeSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"ସାମର୍ଥ୍ୟ ପାଇଁ କ୍ୱେରୀ କରିବାକୁ ନମ୍ବର ଲେଖନ୍ତୁ ଏବଂ ଯଦି ଏକାଧିକ ନମ୍ବର ଅଛି ତେବେ \',\' ଦ୍ୱାରା ଅଲଗା କରନ୍ତୁ।"</string>
+ <string name="number" msgid="9096578126043995890">"ନମ୍ବର:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"ଚାଟ୍ ଆରମ୍ଭ କରନ୍ତୁ"</string>
+ <string name="to" msgid="1156781884609653284">"ପ୍ରାପ୍ତେଷୁ:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"ଚାଟ୍ ମେସେଜ୍"</string>
+ <string name="send" msgid="8797467615003240434">"ପଠାନ୍ତୁ"</string>
+ <string name="ok" msgid="2206671077510386729">"ଠିକ୍ ଅଛି"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"ସେସନ୍ ଆରମ୍ଭ ସଫଳ ହୋଇଛି"</string>
+ <string name="session_failed" msgid="4524986240102190509">"ସେସନ୍ ଆରମ୍ଭ ବିଫଳ ହୋଇଛି"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"ସେସନ୍ ପ୍ରସ୍ତୁତ ନୁହେଁ"</string>
+ <string name="organization" msgid="913518283470119113">"ସଂସ୍ଥା:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"UICC ପ୍ରକାର:"</string>
+ <string name="protocol" msgid="8626139294022821930">"ପ୍ରୋଟୋକଲ୍:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"TLS ସାଇଫର୍ ସୁଟ୍:"</string>
+ <string name="naf" msgid="7620247915001854662">"NAF URI:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"ଷ୍ଟାଣ୍ଡଏଲୋନ୍ ପେଜର୍"</string>
+ <string name="standalone_large" msgid="336170805696694543">"ଷ୍ଟାଣ୍ଡଏଲୋନ୍ ବଡ଼"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"ଷ୍ଟାଣ୍ଡଏଲୋନ୍ ସ୍ଥଗିତ ହୋଇଛି"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"ଷ୍ଟାଣ୍ଡଏଲୋନ୍ ବଡ଼ ପେଜର୍"</string>
+ <string name="chat" msgid="5500583808773151982">"ଚାଟ୍"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"ଫାଇଲ୍ ଟ୍ରାନ୍ସଫର୍"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"ଜିଓଲୋକେସନ୍ SMS"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"ଚାଟବଟ୍ ସେସନ୍"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"ଚାଟବଟ୍ ଷ୍ଟାଣ୍ଡଏଲୋନ୍"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"ଚାଟବଟ୍ ସଂସ୍କରଣ"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"ପ୍ରୋଭିଜନିଂ ହୋଇଗଲା"</string>
+ <string name="registration_done" msgid="492878616836655421">"ପଞ୍ଜିକରଣ ହୋଇଗଲା"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"କିଛି ନାହିଁ"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"ସ୍ଥାନୀୟ"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"ଅଜଣା"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-ro/strings.xml b/testapps/TestRcsApp/TestApp/res/values-ro/strings.xml
new file mode 100644
index 0000000..593cf46
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-ro/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"RcsClient"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"Test de configurare a accesului pentru utilizatori"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"Test delegat"</string>
+ <string name="uce_test" msgid="6466919844953883038">"Test UCE"</string>
+ <string name="gba_test" msgid="8786745991279224003">"Test GBA"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DBClient"</string>
+ <string name="result" msgid="435222369457564619">"Rezultat:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"Rezultatul apelării inverse:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initializeSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"Introduceți numărul pentru capacitatea de interogare și separați prin „,” dacă aveți mai multe."</string>
+ <string name="number" msgid="9096578126043995890">"Număr:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"Începeți conversația"</string>
+ <string name="to" msgid="1156781884609653284">"Către:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"Mesaj prin chat"</string>
+ <string name="send" msgid="8797467615003240434">"Trimiteți"</string>
+ <string name="ok" msgid="2206671077510386729">"OK"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"S-a inițializat sesiunea"</string>
+ <string name="session_failed" msgid="4524986240102190509">"Sesiunea nu a fost inițializată"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"Sesiunea nu este gata"</string>
+ <string name="organization" msgid="913518283470119113">"Organizație:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"Tip UICC:"</string>
+ <string name="protocol" msgid="8626139294022821930">"Protocol:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"Suita cifrului TLS:"</string>
+ <string name="naf" msgid="7620247915001854662">"URI NAF:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"Pager autonom"</string>
+ <string name="standalone_large" msgid="336170805696694543">"Autonom mare"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"Autonom amânat"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"Pager autonom mare"</string>
+ <string name="chat" msgid="5500583808773151982">"Chat"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"Transfer de fișier"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"SMS pentru localizare geografică"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"Sesiune chatbot"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"Chatbot autonom"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"Versiune chatbot"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"S-a configurat accesul pentru utilizatori"</string>
+ <string name="registration_done" msgid="492878616836655421">"S-a înregistrat"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"NICIUNA"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"LOCAL"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"NECUNOSCUTĂ"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-ru/strings.xml b/testapps/TestRcsApp/TestApp/res/values-ru/strings.xml
new file mode 100644
index 0000000..1276015
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-ru/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"RcsClient"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"Тест инициализации"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"Делегирование теста"</string>
+ <string name="uce_test" msgid="6466919844953883038">"Тест UCE"</string>
+ <string name="gba_test" msgid="8786745991279224003">"Тест GBA"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DBClient"</string>
+ <string name="result" msgid="435222369457564619">"Результат:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"Результат обратного вызова:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initializeSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"Введите номер для запроса возможности. Разделяйте номера запятыми, если их несколько."</string>
+ <string name="number" msgid="9096578126043995890">"Номер:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"Начать чат"</string>
+ <string name="to" msgid="1156781884609653284">"Кому:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"Сообщение чата"</string>
+ <string name="send" msgid="8797467615003240434">"Отправить"</string>
+ <string name="ok" msgid="2206671077510386729">"ОК"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"Инициализация сеанса выполнена"</string>
+ <string name="session_failed" msgid="4524986240102190509">"Сбой инициализации сеанса"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"Сеанс не готов\\"</string>
+ <string name="organization" msgid="913518283470119113">"Организация:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"Тип UICC:"</string>
+ <string name="protocol" msgid="8626139294022821930">"Протокол:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"Шифронабор TLS:"</string>
+ <string name="naf" msgid="7620247915001854662">"URI NAF:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"Автономный пейджер"</string>
+ <string name="standalone_large" msgid="336170805696694543">"Автономный большой"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"Автономный отложенный"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"Автономный большой пейджер"</string>
+ <string name="chat" msgid="5500583808773151982">"Чат"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"Передача файла"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"SMS геолокации"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"Сеанс чат-бота"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"Автономный чат-бот"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"Версия чат-бота"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"Инициализация выполнена"</string>
+ <string name="registration_done" msgid="492878616836655421">"Регистрация выполнена"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"НЕТ"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"МЕСТНАЯ СЕТЬ"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"НЕИЗВЕСТНО"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-sl/strings.xml b/testapps/TestRcsApp/TestApp/res/values-sl/strings.xml
new file mode 100644
index 0000000..ca62620
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-sl/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"RcsClient"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"Preizkus priprave za uporabo"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"Preizkus dodelitve"</string>
+ <string name="uce_test" msgid="6466919844953883038">"Preizkus UCE"</string>
+ <string name="gba_test" msgid="8786745991279224003">"Preizkus GBA"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DBClient"</string>
+ <string name="result" msgid="435222369457564619">"Rezultat:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"Rezultat povratnega klica:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initializeSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"Vnesite številko, da pošljete poizvedbo za zmogljivost. Če jih je več, jih ločite z vejicami."</string>
+ <string name="number" msgid="9096578126043995890">"Številka:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"Zaženi Chat"</string>
+ <string name="to" msgid="1156781884609653284">"Za:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"Sporočilo v storitvi Chat"</string>
+ <string name="send" msgid="8797467615003240434">"Pošlji"</string>
+ <string name="ok" msgid="2206671077510386729">"V redu"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"Inicializacija seje je uspela"</string>
+ <string name="session_failed" msgid="4524986240102190509">"Inicializacija seje ni uspela"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"Seja ni pripravljena"</string>
+ <string name="organization" msgid="913518283470119113">"Organizacija:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"Vrsta UICC:"</string>
+ <string name="protocol" msgid="8626139294022821930">"Protokol:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"Zbirka šifriranja TLS:"</string>
+ <string name="naf" msgid="7620247915001854662">"URI NAF:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"Samostojni pozivnik"</string>
+ <string name="standalone_large" msgid="336170805696694543">"Samostojno veliko"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"Samostojno odloženo"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"Samostojni veliki pozivnik"</string>
+ <string name="chat" msgid="5500583808773151982">"Chat"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"Prenos datotek"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"Geolokacijski SMS"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"Seja bota za klepet"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"Samostojni bot za klepet"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"Različica bota za klepet"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"Priprava za uporabo je končana"</string>
+ <string name="registration_done" msgid="492878616836655421">"Registracija je končana"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"NONE"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"LOCAL"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"UNKNOWN"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-te/strings.xml b/testapps/TestRcsApp/TestApp/res/values-te/strings.xml
new file mode 100644
index 0000000..7a0643d
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-te/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"RcsClient"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"నిర్వహణ టెస్ట్"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"డెలిగేట్ టెస్ట్"</string>
+ <string name="uce_test" msgid="6466919844953883038">"UCE టెస్ట్"</string>
+ <string name="gba_test" msgid="8786745991279224003">"GBA టెస్ట్"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DBClient"</string>
+ <string name="result" msgid="435222369457564619">"ఫలితం:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"కాల్బ్యాక్ ఫలితం:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initializeSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"క్వెరీ సామర్థ్యం కోసం నంబర్ను ఎంటర్ చేసి, అవి ఒకటి కంటే ఎక్కువ ఉంటే \',\'తో వేరు చేయండి."</string>
+ <string name="number" msgid="9096578126043995890">"నంబర్:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"చాట్ను ప్రారంభించండి"</string>
+ <string name="to" msgid="1156781884609653284">"వీరికి:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"చాట్ మెసేజ్"</string>
+ <string name="send" msgid="8797467615003240434">"పంపు"</string>
+ <string name="ok" msgid="2206671077510386729">"సరే"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"సెషన్ ప్రారంభం విజయవంతమైంది"</string>
+ <string name="session_failed" msgid="4524986240102190509">"సెషన్ ప్రారంభం విఫలమైంది"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"సెషన్ సిద్ధంగా ఉంది"</string>
+ <string name="organization" msgid="913518283470119113">"సంస్థ:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"UICC రకం:"</string>
+ <string name="protocol" msgid="8626139294022821930">"ప్రోటోకాల్:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"TLS Cipher Suite:"</string>
+ <string name="naf" msgid="7620247915001854662">"NAF URI:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"స్టాండ్ఎలోన్ పేజర్"</string>
+ <string name="standalone_large" msgid="336170805696694543">"స్టాండ్ఎలోన్ లార్జ్"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"స్టాండ్ఎలోన్ వాయిదా వేయబడింది"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"స్టాండ్ఎలోన్ లార్జ్ పేజర్"</string>
+ <string name="chat" msgid="5500583808773151982">"చాట్"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"ఫైల్ బదిలీ"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"భౌగోళిక స్థాన SMS"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"చాట్బాట్ సెషన్"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"చాట్బాట్ స్టాండ్ఎలోన్"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"చాట్బాట్ వెర్షన్"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"నిర్వహణ పూర్తయింది"</string>
+ <string name="registration_done" msgid="492878616836655421">"రిజిస్ట్రేషన్ పూర్తయింది"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"ఏదీ కాదు"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"LOCAL"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"తెలియదు"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-tl/strings.xml b/testapps/TestRcsApp/TestApp/res/values-tl/strings.xml
new file mode 100644
index 0000000..e2dc0cb
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-tl/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"RcsClient"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"Pagsusuri ng Provisioning"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"Pagsusuri ng Pinaglaanan"</string>
+ <string name="uce_test" msgid="6466919844953883038">"Pagsusuri ng UCE"</string>
+ <string name="gba_test" msgid="8786745991279224003">"Pagsusuri ng GBA"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DBClient"</string>
+ <string name="result" msgid="435222369457564619">"Resulta:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"Resulta ng Callback:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initializeSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"Ilagay ang numero para i-query ang kakayahan at paghiwa-hiwalayin gamit ang \',\' kung marami ito."</string>
+ <string name="number" msgid="9096578126043995890">"Numero:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"Simulan ang Chat"</string>
+ <string name="to" msgid="1156781884609653284">"Para sa:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"Mensahe sa Chat"</string>
+ <string name="send" msgid="8797467615003240434">"Ipadala"</string>
+ <string name="ok" msgid="2206671077510386729">"OK"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"Matagumpay na nasimulan ang session"</string>
+ <string name="session_failed" msgid="4524986240102190509">"Hindi nasimulan ang session"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"Hindi pa handa ang session"</string>
+ <string name="organization" msgid="913518283470119113">"Organisasyon:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"Uri ng UICC:"</string>
+ <string name="protocol" msgid="8626139294022821930">"Protocol:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"TLS Cipher Suite:"</string>
+ <string name="naf" msgid="7620247915001854662">"URI ng NAF:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"Standalone Pager"</string>
+ <string name="standalone_large" msgid="336170805696694543">"Standalone Large"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"Standalone Deferred"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"Standalone Large Pager"</string>
+ <string name="chat" msgid="5500583808773151982">"Chat"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"Paglilipat ng File"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"SMS ng Geolocation"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"Chatbot Session"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"Chatbot Standalone"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"Bersyon ng Chatbot"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"Tapos na ang Provisioning"</string>
+ <string name="registration_done" msgid="492878616836655421">"Tapos na ang Pagpaparehistro"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"WALA"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"LOKAL"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"HINDI ALAM"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-zh-rHK/strings.xml b/testapps/TestRcsApp/TestApp/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..ec7f74a
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-zh-rHK/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"RcsClient"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"佈建測試"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"委派測試"</string>
+ <string name="uce_test" msgid="6466919844953883038">"UCE 測試"</string>
+ <string name="gba_test" msgid="8786745991279224003">"GBA 測試"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DBClient"</string>
+ <string name="result" msgid="435222369457564619">"結果:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"回撥電話結果:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initializeSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"輸入要查詢功能的號碼;如果有多組號碼,請以半形逗號 (,) 分隔。"</string>
+ <string name="number" msgid="9096578126043995890">"號碼:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"發起即時通訊"</string>
+ <string name="to" msgid="1156781884609653284">"收件者:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"即時通訊訊息"</string>
+ <string name="send" msgid="8797467615003240434">"傳送"</string>
+ <string name="ok" msgid="2206671077510386729">"確定"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"工作階段初始化失敗成功"</string>
+ <string name="session_failed" msgid="4524986240102190509">"工作階段初始化失敗"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"工作階段尚未就緒"</string>
+ <string name="organization" msgid="913518283470119113">"機構:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"UICC 類型:"</string>
+ <string name="protocol" msgid="8626139294022821930">"通訊協定:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"TLS 加密套裝組合:"</string>
+ <string name="naf" msgid="7620247915001854662">"NAF URI:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"一般版 UI:呼叫器"</string>
+ <string name="standalone_large" msgid="336170805696694543">"一般版 UI:大型"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"一般版 UI:已延後"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"一般版 UI:大型呼叫器"</string>
+ <string name="chat" msgid="5500583808773151982">"即時通訊"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"檔案傳輸"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"地理位置簡訊"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"聊天機器人工作階段"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"聊天機器人一般版 UI"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"聊天機器人版本"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"已完成佈建"</string>
+ <string name="registration_done" msgid="492878616836655421">"已完成註冊"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"無"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"本地"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"不明"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-zh-rTW/strings.xml b/testapps/TestRcsApp/TestApp/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..ec7f74a
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-zh-rTW/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"RcsClient"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"佈建測試"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"委派測試"</string>
+ <string name="uce_test" msgid="6466919844953883038">"UCE 測試"</string>
+ <string name="gba_test" msgid="8786745991279224003">"GBA 測試"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"DBClient"</string>
+ <string name="result" msgid="435222369457564619">"結果:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"回撥電話結果:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initializeSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"輸入要查詢功能的號碼;如果有多組號碼,請以半形逗號 (,) 分隔。"</string>
+ <string name="number" msgid="9096578126043995890">"號碼:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"發起即時通訊"</string>
+ <string name="to" msgid="1156781884609653284">"收件者:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"即時通訊訊息"</string>
+ <string name="send" msgid="8797467615003240434">"傳送"</string>
+ <string name="ok" msgid="2206671077510386729">"確定"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"工作階段初始化失敗成功"</string>
+ <string name="session_failed" msgid="4524986240102190509">"工作階段初始化失敗"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"工作階段尚未就緒"</string>
+ <string name="organization" msgid="913518283470119113">"機構:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"UICC 類型:"</string>
+ <string name="protocol" msgid="8626139294022821930">"通訊協定:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"TLS 加密套裝組合:"</string>
+ <string name="naf" msgid="7620247915001854662">"NAF URI:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"一般版 UI:呼叫器"</string>
+ <string name="standalone_large" msgid="336170805696694543">"一般版 UI:大型"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"一般版 UI:已延後"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"一般版 UI:大型呼叫器"</string>
+ <string name="chat" msgid="5500583808773151982">"即時通訊"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"檔案傳輸"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"地理位置簡訊"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"聊天機器人工作階段"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"聊天機器人一般版 UI"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"聊天機器人版本"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"已完成佈建"</string>
+ <string name="registration_done" msgid="492878616836655421">"已完成註冊"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"無"</item>
+ <item msgid="7841117287115880098">"3GPP"</item>
+ <item msgid="5871357530951196401">"3GPP2"</item>
+ <item msgid="4979706772323445701">"OMA"</item>
+ <item msgid="1202873556413702218">"GSMA"</item>
+ <item msgid="2237392451840970059">"本地"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"不明"</item>
+ <item msgid="1715684709703792003">"SIM"</item>
+ <item msgid="4592543026990143036">"USIM"</item>
+ <item msgid="8763183137100323328">"RSIM"</item>
+ <item msgid="3778400020205644807">"CSIM"</item>
+ <item msgid="3774607711141191499">"ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values-zu/strings.xml b/testapps/TestRcsApp/TestApp/res/values-zu/strings.xml
new file mode 100644
index 0000000..d05fa16
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values-zu/strings.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="1411081210960307023">"I-RcsClient"</string>
+ <string name="provisioning_test" msgid="1035549386706009246">"Isivivinyo Sokunikezwa"</string>
+ <string name="delegate_test" msgid="8212368545489779643">"Susa Isivivinyo"</string>
+ <string name="uce_test" msgid="6466919844953883038">"Isivivinyo se-UCE"</string>
+ <string name="gba_test" msgid="8786745991279224003">"Isivivinyo se-GBA"</string>
+ <string name="test_msg_client" msgid="5345166277460949996">"TestMessageClient"</string>
+ <string name="db_client" msgid="5131401341721936367">"I-DBClient"</string>
+ <string name="result" msgid="435222369457564619">"Umphumela:"</string>
+ <string name="callback_result" msgid="7162990408241244749">"Imiphumela yokuphinda ushaye:"</string>
+ <string name="initialize_delegate" msgid="1772072838514674246">"initializeSipDelegate"</string>
+ <string name="destroy_delegate" msgid="1678202834033613371">"destroySipDelegate"</string>
+ <string name="uce_description" msgid="1117661457446955869">"Faka inombolo ukubuza amandla futhi uhlukanise ngokuthi \',\' uma kukuningi."</string>
+ <string name="number" msgid="9096578126043995890">"Inombolo:"</string>
+ <string name="request_capability" msgid="7644752985651089411">"requestCapability"</string>
+ <string name="request_availability" msgid="6387685341316953239">"requestNetworkAvailability"</string>
+ <string name="gba_bootstrap" msgid="549563024448934689">"bootstrapAuthenticationRequest"</string>
+ <string name="start_chat" msgid="96121236227112396">"Qalisa Ingxoxo"</string>
+ <string name="to" msgid="1156781884609653284">"Ku:"</string>
+ <string name="chat_message" msgid="3945528453939746046">"Umlayezo Wengxoxo"</string>
+ <string name="send" msgid="8797467615003240434">"Thumela"</string>
+ <string name="ok" msgid="2206671077510386729">"KULUNGILE"</string>
+ <string name="session_succeeded" msgid="4692992136758212220">"Ukuqaliswa kweseshini kuphumelele"</string>
+ <string name="session_failed" msgid="4524986240102190509">"Ukuqaliswa kweseshini kwehlulekile"</string>
+ <string name="session_not_ready" msgid="4612635394279901948">"Iseshini ayilungile"</string>
+ <string name="organization" msgid="913518283470119113">"Inhlangano:"</string>
+ <string name="uicc_type" msgid="5584433205228698588">"Uhlobo lwe-UICC:"</string>
+ <string name="protocol" msgid="8626139294022821930">"Iphrothokholi:"</string>
+ <string name="tls_cs" msgid="5410567052979996834">"I-TLS Cipher Suite:"</string>
+ <string name="naf" msgid="7620247915001854662">"I-NAF URI:"</string>
+ <string name="standalone_pager" msgid="1351341861641131423">"Ipheja ye-Standalone"</string>
+ <string name="standalone_large" msgid="336170805696694543">"I-Standalone Enkulu"</string>
+ <string name="standalone_deferred" msgid="4906576900729108844">"I-Standalone Iyekiswe ukubhekiswa"</string>
+ <string name="standalone_pager_large" msgid="5662294171123433547">"Ipheja ye-Standalone Enkulu"</string>
+ <string name="chat" msgid="5500583808773151982">"Ingxoxo"</string>
+ <string name="file_transfer" msgid="5522080545470609737">"Ukudluliselwa kwefayela"</string>
+ <string name="geolocation_sms" msgid="4463328438708195479">"I-SMS ye-Geolocation"</string>
+ <string name="chatbot_session" msgid="5639314220589146717">"Iseshini ye-Chatbot"</string>
+ <string name="chatbot_standalone" msgid="8159185526765238041">"I-Chatbot Standalone"</string>
+ <string name="chatbot_version" msgid="2431939061806512544">"Inguqulo ye-Chatbot"</string>
+ <string name="provisioning_done" msgid="204618840179891984">"Ukunikezwa kwenziwe"</string>
+ <string name="registration_done" msgid="492878616836655421">"Ukubhalisa Kwenziwe"</string>
+ <string-array name="organization">
+ <item msgid="9074061482474975772">"LUTHO"</item>
+ <item msgid="7841117287115880098">"I-3GPP"</item>
+ <item msgid="5871357530951196401">"I-3GPP2"</item>
+ <item msgid="4979706772323445701">"I-OMA"</item>
+ <item msgid="1202873556413702218">"I-GSMA"</item>
+ <item msgid="2237392451840970059">"OKWENDAWO"</item>
+ </string-array>
+ <string-array name="protocol">
+ <item msgid="7955884038486794595">"SUBSCRIBER_CERTIFICATE"</item>
+ <item msgid="6305648228108532104">"I-MBMS"</item>
+ <item msgid="4369286857827087787">"HTTP_DIGEST_AUTH"</item>
+ <item msgid="1047245839171009229">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="1728229445161381738">"GENERIC_PUSH_LAYER"</item>
+ <item msgid="8586727908453122385">"IMS_MEDIA_PLANE"</item>
+ <item msgid="5721752897501838331">"GENERATION_TMPI"</item>
+ <item msgid="1622910302975417378">"3GPP_HTTP_BASED_MBMS"</item>
+ <item msgid="7519270727796373646">"TLS_DEFAULT"</item>
+ <item msgid="7444251498958422990">"TLS_BROWSER"</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item msgid="5878327014492655902">"AKWAZIWA"</item>
+ <item msgid="1715684709703792003">"I-SIM"</item>
+ <item msgid="4592543026990143036">"I-USIM"</item>
+ <item msgid="8763183137100323328">"I-RSIM"</item>
+ <item msgid="3778400020205644807">"I-CSIM"</item>
+ <item msgid="3774607711141191499">"I-ISIM"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values/colors.xml b/testapps/TestRcsApp/TestApp/res/values/colors.xml
new file mode 100644
index 0000000..3d5cded
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values/colors.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="colorPrimary">#008577</color>
+ <color name="colorPrimaryDark">#00574B</color>
+ <color name="colorAccent">#D81B60</color>
+</resources>
+
diff --git a/testapps/TestRcsApp/TestApp/res/values/donottranslate_strings.xml b/testapps/TestRcsApp/TestApp/res/values/donottranslate_strings.xml
new file mode 100644
index 0000000..f52b70d
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values/donottranslate_strings.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<resources>
+ <string name="app_name">RcsClient</string>
+ <string name="provisioning_test">Provisioning Test</string>
+ <string name="delegate_test">Delegate Test</string>
+ <string name="uce_test">UCE Test</string>
+ <string name="gba_test">GBA Test</string>
+ <string name="test_msg_client">TestMessageClient</string>
+ <string name="db_client">DBClient</string>
+ <string name="rcs_profile">RcsProfile:</string>
+ <string name="register_provisioning_callback">registerProvisioningCallback</string>
+ <string name="unregister_provisioning_callback">unRegisterProvisioningCallback</string>
+ <string name="isRcsVolteSingleRegCapable">isRcsVolteSingleRegCapable</string>
+ <string name="result">Result:</string>
+ <string name="callback_result">Callback Result:</string>
+ <string name="initialize_delegate">initializeSipDelegate</string>
+ <string name="destroy_delegate">destroySipDelegate</string>
+ <string name="uce_description">Enter the number to query capability and separate by \',\' if
+ multiple ones.</string>
+ <string name="number">Number: </string>
+ <string name="request_capability">requestCapability</string>
+ <string name="request_availability">requestNetworkAvailability (1st number)</string>
+ <string name="gba_bootstrap">bootstrapAuthenticationRequest</string>
+ <string name="start_chat">Start Chat</string>
+ <string name="to">To:</string>
+ <string name="chat_message">Chat Message</string>
+ <string name="send">Send</string>
+ <string name="ok">OK</string>
+ <string name="session_initiating">Initializing chat session..</string>
+ <string name="session_timeout">Session initialization timeout</string>
+ <string name="session_succeeded">Session initialization succeeded</string>
+ <string name="session_failed">Session initialization failed</string>
+ <string name="session_broken_or_not_ready">Session broken or not ready</string>
+ <string name="organization">Organization:</string>
+ <string name="uicc_type">UICC Type:</string>
+ <string name="protocol">Protocol:</string>
+ <string name="tls_cs">TLS Cipher Suite:</string>
+ <string name="naf">NAF URI:</string>
+ <string name="standalone_pager">Standalone Pager</string>
+ <string name="standalone_large">Standalone Large</string>
+ <string name="standalone_deferred">Standalone Deferred</string>
+ <string name="standalone_pager_large">Standalone Large Pager</string>
+ <string name="chat">Chat</string>
+ <string name="file_transfer">File Transfer</string>
+ <string name="geolocation_sms">Geolocation SMS</string>
+ <string name="chatbot_session">Chatbot Session</string>
+ <string name="chatbot_standalone">Chatbot Standalone</string>
+ <string name="chatbot_version">Chatbot Version</string>
+ <string name="start_provisioning">Start Provisioning....</string>
+ <string name="provisioning_timeout">Provisioning timeout.</string>
+ <string name="provisioning_done">Provisioning done, Start registering...</string>
+ <string name="registration_timeout">Registration timeout</string>
+ <string name="registration_done">Registration done. Enjoy chat!</string>
+ <string name="registration_failed">Registration failed</string>
+ <string name="attach">+</string>
+ <string name="browse">Browse</string>
+ <string name="upload">Upload</string>
+ <string name="upload_file_gba">Upload File with GBA</string>
+ <string name="invalid_parameters">Invalid Parameters</string>
+ <string name="server">Server:</string>
+ <string name="file_name">File Name:</string>
+ <string name="server_empty">Server is empty</string>
+ <string name="file_empty">File is empty</string>
+ <string name="version_info">Version: %s</string>
+
+ <string-array name="rcs_profile">
+ <item>UP_1.0</item>
+ <item>UP_2.3</item>
+ </string-array>
+ <string-array name="organization">
+ <item>NONE</item>
+ <item>3GPP</item>
+ <item>3GPP2</item>
+ <item>OMA</item>
+ <item>GSMA</item>
+ <item>LOCAL</item>
+ </string-array>
+ <string-array name="protocol">
+ <item>SUBSCRIBER_CERTIFICATE</item>
+ <item>MBMS</item>
+ <item>HTTP_DIGEST_AUTH</item>
+ <item>3GPP_HTTP_BASED_MBMS</item>
+ <item>GENERIC_PUSH_LAYER</item>
+ <item>IMS_MEDIA_PLANE</item>
+ <item>GENERATION_TMPI</item>
+ <item>3GPP_HTTP_BASED_MBMS</item>
+ <item>TLS_DEFAULT</item>
+ <item>TLS_BROWSER</item>
+ </string-array>
+ <string-array name="uicc_type">
+ <item>UNKNOWN</item>
+ <item>SIM</item>
+ <item>USIM</item>
+ <item>RSIM</item>
+ <item>CSIM</item>
+ <item>ISIM</item>
+ </string-array>
+ <string-array name="server">
+ <item>STAGING</item>
+ <item>PRODUCTION</item>
+ </string-array>
+
+</resources>
diff --git a/testapps/TestRcsApp/TestApp/res/values/styles.xml b/testapps/TestRcsApp/TestApp/res/values/styles.xml
new file mode 100644
index 0000000..5885930
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/values/styles.xml
@@ -0,0 +1,11 @@
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+ <!-- Customize your theme here. -->
+ <item name="colorPrimary">@color/colorPrimary</item>
+ <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+ <item name="colorAccent">@color/colorAccent</item>
+ </style>
+
+</resources>
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
new file mode 100644
index 0000000..40a108d
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ChatActivity.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2020 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;
+
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.MenuItem;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+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 com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/** An activity to show chat message with specific number. */
+public class ChatActivity extends AppCompatActivity {
+
+ public static final String EXTRA_REMOTE_PHONE_NUMBER = "REMOTE_PHONE_NUMBER";
+ private static final String TAG = "TestRcsApp.ChatActivity";
+ private static final int INIT_LIST = 1;
+ private static final int SHOW_STATUS = 2;
+ private static final int EMPTY_MSG = 3;
+ private static final float TEXT_SIZE = 20.0f;
+ private static final int MARGIN_SIZE = 20;
+ private static final long TIMEOUT_IN_MS = 10000L;
+ private final ExecutorService mFixedThreadPool = Executors.newFixedThreadPool(3);
+ private boolean mSessionInitResult = false;
+ private Button mSend;
+ private String mDestNumber;
+ private TextView mDestNumberView, mTips;
+ private EditText mNewMessage;
+ private ChatObserver mChatObserver;
+ private Handler mHandler;
+ private int mSubId;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.i(TAG, "onCreate");
+ setContentView(R.layout.chat_layout);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+
+ mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ super.handleMessage(msg);
+ Log.d(TAG, "handleMessage:" + msg.what);
+ switch (msg.what) {
+ case INIT_LIST:
+ initChatMessageLayout((Cursor) msg.obj);
+ break;
+ case SHOW_STATUS:
+ mTips.setText(msg.obj.toString());
+ break;
+ case EMPTY_MSG:
+ mNewMessage.setText("");
+ break;
+ default:
+ Log.d(TAG, "unknown msg:" + msg.what);
+ break;
+ }
+
+ }
+ };
+ mDestNumberView = findViewById(R.id.destNum);
+ mTips = findViewById(R.id.session_tips);
+ initDestNumber();
+ mChatObserver = new ChatObserver(mHandler);
+ }
+
+ private void initDestNumber() {
+ Intent intent = getIntent();
+ mDestNumber = intent.getStringExtra(EXTRA_REMOTE_PHONE_NUMBER);
+ mDestNumberView.setText(mDestNumber);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ initChatButton();
+ queryChatData();
+ getContentResolver().registerContentObserver(ChatProvider.CHAT_URI, false,
+ mChatObserver);
+ }
+
+ private void initChatButton() {
+ mNewMessage = findViewById(R.id.new_msg);
+ mSend = findViewById(R.id.chat_btn);
+
+ mSubId = SubscriptionManager.getDefaultSmsSubscriptionId();
+ if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
+ Log.e(TAG, "invalid subId:" + mSubId);
+ 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);
+ mTips.setText(ChatActivity.this.getResources().getString(R.string.session_initiating));
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(SHOW_STATUS,
+ ChatActivity.this.getResources().getString(R.string.session_timeout)),
+ TIMEOUT_IN_MS);
+ ChatManager.getInstance(getApplicationContext(), mSubId).initChatSession(
+ mDestNumber, new SessionStateCallback() {
+ @Override
+ public void onSuccess() {
+ Log.i(TAG, "session init succeeded");
+ String success = ChatActivity.this.getResources().getString(
+ R.string.session_succeeded);
+ if (mHandler.hasMessages(SHOW_STATUS)) {
+ mHandler.removeMessages(SHOW_STATUS);
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(SHOW_STATUS, success));
+ mSessionInitResult = true;
+ }
+
+ @Override
+ public void onFailure() {
+ Log.i(TAG, "session init failed");
+ String failure = ChatActivity.this.getResources().getString(
+ R.string.session_failed);
+ if (mHandler.hasMessages(SHOW_STATUS)) {
+ mHandler.removeMessages(SHOW_STATUS);
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(SHOW_STATUS, failure));
+ mSessionInitResult = false;
+ }
+ });
+
+ mSend.setOnClickListener(view -> {
+ if (!ChatManager.getInstance(getApplicationContext(), mSubId).isRegistered()
+ || !mSessionInitResult) {
+ Toast.makeText(ChatActivity.this,
+ getResources().getString(R.string.session_broken_or_not_ready),
+ Toast.LENGTH_SHORT).show();
+ Log.i(TAG, "session broken or not ready");
+ return;
+ }
+ mFixedThreadPool.execute(() -> {
+ if (TextUtils.isEmpty(mDestNumber)) {
+ Log.i(TAG, "Destination number is empty");
+ } else {
+ Log.i(TAG, "send message");
+ sendChatMessage();
+ }
+ });
+ });
+ } catch (Exception e) {
+ Log.e(TAG, "Exception: " + e);
+ e.printStackTrace();
+ Toast.makeText(this, getResources().getString(R.string.session_failed),
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void sendChatMessage() {
+ Uri result = ChatManager.getInstance(getApplicationContext(), mSubId).addNewMessage(
+ mNewMessage.getText().toString(), ChatManager.SELF, mDestNumber);
+ String chatId = result.getPathSegments().get(1);
+ Futures.addCallback(
+ ChatManager.getInstance(getApplicationContext(),
+ mSubId).sendMessage(
+ mDestNumber,
+ mNewMessage.getText().toString()),
+ new FutureCallback<Void>() {
+ @Override
+ public void onSuccess(Void param) {
+ Log.i(TAG, "send chat msg successfully");
+ ChatManager.getInstance(getApplicationContext(), mSubId).updateMsgResult(
+ chatId, true);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ Log.i(TAG, "fail to send chat message:" + t);
+ ChatManager.getInstance(getApplicationContext(), mSubId).updateMsgResult(
+ chatId, false);
+ }
+ },
+ MoreExecutors.directExecutor());
+ mHandler.sendMessage(mHandler.obtainMessage(EMPTY_MSG));
+ }
+
+ private void initChatMessageLayout(Cursor cursor) {
+ Log.i(TAG, "initChatMessageLayout");
+ RelativeLayout rl = findViewById(R.id.relative_layout);
+ int id = 1;
+ if (cursor != null && cursor.moveToNext()) {
+ TextView chatMessage = initChatMessageItem(cursor, id++, true);
+ rl.addView(chatMessage);
+ }
+ while (cursor != null && cursor.moveToNext()) {
+ TextView chatMessage = initChatMessageItem(cursor, id++, false);
+ rl.addView(chatMessage);
+ }
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ private TextView initChatMessageItem(Cursor cursor, int id, boolean isFirst) {
+ TextView chatMsg = new TextView(this);
+ chatMsg.setId(id);
+ chatMsg.setText(
+ cursor.getString(cursor.getColumnIndex(ChatProvider.RcsColumns.CHAT_MESSAGE)));
+ chatMsg.setTextSize(TEXT_SIZE);
+ chatMsg.setTypeface(null, Typeface.BOLD);
+ RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ lp.setMargins(0, MARGIN_SIZE, 0, 0);
+ if (messageFromSelf(cursor)) {
+ lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
+ int result = cursor.getInt(cursor.getColumnIndex(ChatProvider.RcsColumns.RESULT));
+ chatMsg.setBackgroundColor(result == 1 ? Color.GREEN : Color.RED);
+ } else {
+ lp.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
+ chatMsg.setBackgroundColor(Color.LTGRAY);
+ }
+ if (!isFirst) {
+ lp.addRule(RelativeLayout.BELOW, id - 1);
+ }
+ chatMsg.setLayoutParams(lp);
+ return chatMsg;
+ }
+
+ private boolean messageFromSelf(Cursor cursor) {
+ return ChatManager.SELF.equals(
+ cursor.getString(cursor.getColumnIndex(ChatProvider.RcsColumns.SRC_PHONE_NUMBER)));
+ }
+
+ private void queryChatData() {
+ mFixedThreadPool.execute(() -> {
+ Cursor cursor = getContentResolver().query(ChatProvider.CHAT_URI,
+ new String[]{ChatProvider.RcsColumns.SRC_PHONE_NUMBER,
+ ChatProvider.RcsColumns.DEST_PHONE_NUMBER,
+ ChatProvider.RcsColumns.CHAT_MESSAGE,
+ ChatProvider.RcsColumns.RESULT},
+ ChatProvider.RcsColumns.SRC_PHONE_NUMBER + "=? OR "
+ + ChatProvider.RcsColumns.DEST_PHONE_NUMBER + "=?",
+ new String[]{mDestNumber, mDestNumber},
+ ChatProvider.RcsColumns.MSG_TIMESTAMP + " ASC");
+ mHandler.sendMessage(mHandler.obtainMessage(INIT_LIST, cursor));
+ });
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ Log.i(TAG, "onStop");
+ getContentResolver().unregisterContentObserver(mChatObserver);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ Log.i(TAG, "onDestroy");
+ ChatManager.getInstance(getApplicationContext(), mSubId).terminateSession(mDestNumber);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private class ChatObserver extends ContentObserver {
+ ChatObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ Log.i(TAG, "onChange");
+ queryChatData();
+ }
+ }
+}
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ContactListActivity.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ContactListActivity.java
new file mode 100644
index 0000000..b641606
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ContactListActivity.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2020 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;
+
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.SubscriptionManager;
+import android.util.Log;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.android.libraries.rcs.simpleclient.SimpleRcsClient.State;
+
+import com.google.android.sample.rcsclient.util.ChatManager;
+import com.google.android.sample.rcsclient.util.ChatProvider;
+
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/** An activity to show the contacts which UE ever chatted before. */
+public class ContactListActivity extends AppCompatActivity {
+
+ private static final String TAG = "TestRcsApp.ContactListActivity";
+ private static final int RENDER_LISTVIEW = 1;
+ private static final int SHOW_STATUS = 2;
+ private static final long TIMEOUT_IN_MS = 10000L;
+ private final ExecutorService mSingleThread = Executors.newSingleThreadExecutor();
+ private TextView mTips;
+ private Button mStartChatButton;
+ private Handler mHandler;
+ private SummaryObserver mSummaryObserver;
+ private ArrayAdapter mAdapter;
+ private ListView mListview;
+ private State mState;
+ private ArrayList<ContactAttributes> mContactList;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.i(TAG, "onCreate");
+ setContentView(R.layout.contact_list);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+
+ mContactList = new ArrayList<>();
+ mTips = findViewById(R.id.tips);
+ mListview = findViewById(R.id.listview);
+ mStartChatButton = findViewById(R.id.start_chat_btn);
+ mStartChatButton.setOnClickListener(view -> {
+ Intent intent = new Intent(ContactListActivity.this, PhoneNumberActivity.class);
+ ContactListActivity.this.startActivity(intent);
+ });
+ setButtonClickable(false);
+
+ mHandler = new Handler() {
+ public void handleMessage(Message message) {
+ Log.i(TAG, "handleMessage:" + message.what);
+ switch (message.what) {
+ case RENDER_LISTVIEW:
+ renderListView();
+ break;
+ case SHOW_STATUS:
+ mTips.setText(message.obj.toString());
+ break;
+ default:
+ Log.i(TAG, "unknown msg:" + message.what);
+ }
+ }
+ };
+ initListView();
+ initSipDelegate();
+ mSummaryObserver = new SummaryObserver(mHandler);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ Log.i(TAG, "onStart");
+ querySummaryData();
+ getContentResolver().registerContentObserver(ChatProvider.SUMMARY_URI, false,
+ mSummaryObserver);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ Log.i(TAG, "onStop");
+ getContentResolver().unregisterContentObserver(mSummaryObserver);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ Log.i(TAG, "onDestroy");
+ dispose();
+ }
+
+ private void dispose() {
+ int subId = SubscriptionManager.getDefaultSmsSubscriptionId();
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ Log.e(TAG, "invalid subId:" + subId);
+ return;
+ }
+ ChatManager chatManager = ChatManager.getInstance(this, subId);
+ chatManager.deregister();
+ }
+
+ private void initListView() {
+ Log.i(TAG, "initListView");
+
+ mAdapter = new ArrayAdapter<ContactAttributes>(this,
+ android.R.layout.simple_list_item_2,
+ android.R.id.text1) {
+ @Override
+ public View getView(int pos, View convert, ViewGroup group) {
+ View v = super.getView(pos, convert, group);
+ TextView t1 = (TextView) v.findViewById(android.R.id.text1);
+ TextView t2 = (TextView) v.findViewById(android.R.id.text2);
+ t1.setText(getItem(pos).phoneNumber);
+ t2.setText(getItem(pos).message);
+ if (!getItem(pos).isRead) {
+ t1.setTypeface(null, Typeface.BOLD);
+ t2.setTypeface(null, Typeface.BOLD);
+ }
+ return v;
+ }
+ };
+ mListview.setAdapter(mAdapter);
+ }
+
+ private void querySummaryData() {
+ mSingleThread.execute(() -> {
+ Cursor cursor = getContentResolver().query(ChatProvider.SUMMARY_URI,
+ new String[]{ChatProvider.SummaryColumns.REMOTE_PHONE_NUMBER,
+ ChatProvider.SummaryColumns.LATEST_MESSAGE,
+ ChatProvider.SummaryColumns.IS_READ}, null, null, null);
+
+ mContactList.clear();
+ while (cursor.moveToNext()) {
+ String phoneNumber = getPhoneNumber(cursor);
+ String latestMessage = getLatestMessage(cursor);
+ boolean isRead = getIsRead(cursor);
+ mContactList.add(new ContactAttributes(phoneNumber, latestMessage, isRead));
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(RENDER_LISTVIEW));
+ cursor.close();
+ });
+ }
+
+ private void renderListView() {
+ mAdapter.clear();
+ mAdapter.addAll(mContactList);
+ }
+
+ private void setListViewClickable(boolean clickable) {
+ if (clickable) {
+ mListview.setOnItemClickListener((parent, view, position, id) -> {
+ Intent intent = new Intent(ContactListActivity.this, ChatActivity.class);
+ intent.putExtra(ChatActivity.EXTRA_REMOTE_PHONE_NUMBER,
+ mContactList.get(position).phoneNumber);
+ ContactListActivity.this.startActivity(intent);
+ });
+ } else {
+ mListview.setOnItemClickListener(null);
+ }
+ }
+
+ private void initSipDelegate() {
+ int subId = SubscriptionManager.getDefaultSmsSubscriptionId();
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ Log.e(TAG, "invalid subId:" + subId);
+ return;
+ }
+ Log.i(TAG, "initSipDelegate");
+ ChatManager chatManager = ChatManager.getInstance(this, subId);
+ chatManager.setRcsStateChangedCallback((oldState, newState) -> {
+ //Show toast when provisioning or registration is done.
+ mState = newState;
+ String tips = "";
+ String timeoutTips = "";
+ switch (newState) {
+ case PROVISIONING:
+ tips = ContactListActivity.this.getResources().getString(
+ R.string.start_provisioning);
+ mHandler.sendMessage(mHandler.obtainMessage(SHOW_STATUS, tips));
+ timeoutTips = ContactListActivity.this.getResources().getString(
+ R.string.provisioning_timeout);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(SHOW_STATUS, timeoutTips),
+ TIMEOUT_IN_MS);
+ break;
+ case REGISTERING:
+ tips = ContactListActivity.this.getResources().getString(
+ R.string.provisioning_done);
+ if (mHandler.hasMessages(SHOW_STATUS)) {
+ mHandler.removeMessages(SHOW_STATUS);
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(SHOW_STATUS, tips));
+ timeoutTips = ContactListActivity.this.getResources().getString(
+ R.string.registration_timeout);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(SHOW_STATUS, timeoutTips),
+ TIMEOUT_IN_MS);
+ break;
+ case REGISTERED:
+ tips = ContactListActivity.this.getResources().getString(
+ R.string.registration_done);
+ if (mHandler.hasMessages(SHOW_STATUS)) {
+ mHandler.removeMessages(SHOW_STATUS);
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(SHOW_STATUS, tips));
+ setButtonClickable(true);
+ setListViewClickable(true);
+ break;
+ case NOT_REGISTERED:
+ tips = ContactListActivity.this.getResources().getString(
+ R.string.registration_failed);
+ mHandler.sendMessage(mHandler.obtainMessage(SHOW_STATUS, tips));
+ setButtonClickable(false);
+ setListViewClickable(false);
+ break;
+
+ default:
+ Log.i(TAG, "unknown state:" + newState);
+ }
+ });
+ chatManager.register();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+
+ private String getPhoneNumber(Cursor cursor) {
+ return cursor.getString(
+ cursor.getColumnIndex(ChatProvider.SummaryColumns.REMOTE_PHONE_NUMBER));
+ }
+
+ private String getLatestMessage(Cursor cursor) {
+ return cursor.getString(cursor.getColumnIndex(ChatProvider.SummaryColumns.LATEST_MESSAGE));
+ }
+
+ private boolean getIsRead(Cursor cursor) {
+ return 1 == cursor.getInt(cursor.getColumnIndex(ChatProvider.SummaryColumns.IS_READ));
+ }
+
+ private void setButtonClickable(boolean clickable) {
+ if (clickable) {
+ mStartChatButton.setAlpha(1);
+ mStartChatButton.setClickable(true);
+ } else {
+ mStartChatButton.setAlpha(.5f);
+ mStartChatButton.setClickable(false);
+ }
+ }
+
+ class ContactAttributes {
+ public String phoneNumber;
+ public String message;
+ public boolean isRead;
+
+ ContactAttributes(String phoneNumber, String message, boolean isRead) {
+ this.phoneNumber = phoneNumber;
+ this.message = message;
+ this.isRead = isRead;
+ }
+ }
+
+ private class SummaryObserver extends ContentObserver {
+ SummaryObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ querySummaryData();
+ }
+ }
+}
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java
new file mode 100644
index 0000000..1407671
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2020 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;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.SmsManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.DelegateRegistrationState;
+import android.telephony.ims.DelegateRequest;
+import android.telephony.ims.FeatureTagState;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsManager;
+import android.telephony.ims.SipDelegateConfiguration;
+import android.telephony.ims.SipDelegateConnection;
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.SipMessage;
+import android.telephony.ims.stub.DelegateConnectionMessageCallback;
+import android.telephony.ims.stub.DelegateConnectionStateCallback;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.view.MenuItem;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/** An activity to verify SipDelegate creation and destruction. */
+public class DelegateActivity extends AppCompatActivity {
+
+ private static final String TAG = "TestRcsApp.DelegateActivity";
+ private static final String ICSI = "+g.3gpp.icsi-ref=";
+ private static final String IARI = "+g.3gpp.iari-ref=";
+
+ //https://www.gsma.com/futurenetworks/wp-content/uploads/2019/10/RCC.07-v11.0.pdf
+ private static final String SESSION_TAG =
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"";
+ private static final String STANDALONE_PAGER =
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg\"";
+ private static final String STANDALONE_LARGE =
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.largemsg\"";
+ private static final String STANDALONE_DEFERRED =
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.deferred\"";
+ private static final String STANDALONE_LARGE_PAGER = "+g.gsma.rcs.cpm.pager-large";
+
+
+ private static final String FILE_TRANSFER =
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.fthttp\"";
+ private static final String GEOLOCATION_SMS =
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.geosms\"";
+
+ private static final String CHATBOT_SESSION =
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.chatbot\"";
+ private static final String CHATBOT_STANDALONE =
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.chatbot.sa\"";
+ private static final String CHATBOT_VERSION = "+g.gsma.rcs.botversion=\"#=1,#=2\"";
+
+
+ private static final int MSG_RESULT = 1;
+ private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
+ // Callback for incoming messages on the modem connection
+ private final DelegateConnectionMessageCallback mMessageCallback =
+ new DelegateConnectionMessageCallback() {
+ @Override
+ public void onMessageReceived(@NonNull SipMessage message) {
+ Log.i(TAG, "onMessageReceived:" + message);
+ }
+
+ @Override
+ public void onMessageSendFailure(@NonNull String viaTransactionId, int reason) {
+ Log.i(TAG, "onMessageSendFailure, viaTransactionId:" + viaTransactionId
+ + " reason:" + reason);
+ }
+
+ @Override
+ public void onMessageSent(@NonNull String viaTransactionId) {
+ Log.i(TAG, "onMessageSent, viaTransactionId:" + viaTransactionId);
+ }
+
+ };
+ private String mCallbackResultStr = "";
+ private int mDefaultSmsSubId;
+ private SipDelegateManager mSipDelegateManager;
+ private SipDelegateConnection mSipDelegateConnection;
+ private Button mInitButton;
+ private Button mDestroyButton;
+ private TextView mCallbackResult;
+ private CheckBox mChatCb, mStandalonePagerCb, mStandaloneLargeCb, mStandaloneDeferredCb,
+ mStandaloneLargePagerCb, mFileTransferCb, mGeolocationSmsCb, mChatbotSessionCb,
+ mChatbotStandaloneCb, mChatbotVersionCb;
+ private Handler mHandler;
+ private final DelegateConnectionStateCallback mConnectionCallback =
+ new DelegateConnectionStateCallback() {
+
+ @Override
+ public void onCreated(SipDelegateConnection c) {
+ mSipDelegateConnection = c;
+ mCallbackResultStr += "onCreated\r\n\r\n";
+ Log.i(TAG, mCallbackResultStr);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_RESULT));
+ }
+
+ @Override
+ public void onConfigurationChanged(
+ SipDelegateConfiguration registeredSipConfig) {
+ mCallbackResultStr += "onConfigurationChanged SipDelegateConfiguration:"
+ + "\r\n\r\n";
+ Log.i(TAG, "onConfigurationChanged: " + registeredSipConfig);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_RESULT));
+ dumpConfig(registeredSipConfig);
+ }
+
+
+ @Override
+ public void onFeatureTagStatusChanged(
+ @NonNull DelegateRegistrationState registrationState,
+ @NonNull Set<FeatureTagState> deniedFeatureTags) {
+ StringBuilder stringBuilder = new StringBuilder(
+ "onFeatureTagStatusChanged ").append(
+ " deniedFeatureTags:[");
+ Iterator<FeatureTagState> iterator = deniedFeatureTags.iterator();
+ while (iterator.hasNext()) {
+ FeatureTagState featureTagState = iterator.next();
+ stringBuilder.append(featureTagState.getFeatureTag()).append(" ").append(
+ featureTagState.getState());
+ }
+ Set<String> registeredFt = registrationState.getRegisteredFeatureTags();
+ Iterator<String> iteratorStr = registeredFt.iterator();
+ stringBuilder.append("] registeredFT:[");
+ while (iteratorStr.hasNext()) {
+ String ft = iteratorStr.next();
+ stringBuilder.append(ft).append(" ");
+ }
+ stringBuilder.append("]\r\n\r\n");
+ mCallbackResultStr += stringBuilder.toString();
+ Log.i(TAG, mCallbackResultStr);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_RESULT));
+ }
+
+ @Override
+ public void onDestroyed(int reason) {
+ mCallbackResultStr = "onDestroyed reason:" + reason;
+ Log.i(TAG, mCallbackResultStr);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_RESULT));
+ }
+ };
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.delegate_layout);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+
+ mHandler = new Handler() {
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_RESULT:
+ mCallbackResult.setText(mCallbackResultStr);
+ break;
+ }
+ }
+ };
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ init();
+ }
+
+ private void init() {
+ mInitButton = findViewById(R.id.init_btn);
+ mDestroyButton = findViewById(R.id.destroy_btn);
+ mCallbackResult = findViewById(R.id.delegate_callback_result);
+ mChatCb = findViewById(R.id.chat);
+ mStandalonePagerCb = findViewById(R.id.standalone_pager);
+ mStandaloneLargeCb = findViewById(R.id.standalone_large);
+ mStandaloneDeferredCb = findViewById(R.id.standalone_deferred);
+ mStandaloneLargePagerCb = findViewById(R.id.standalone_pager_large);
+
+ mFileTransferCb = findViewById(R.id.file_transfer);
+ mGeolocationSmsCb = findViewById(R.id.geolocation_sms);
+ mChatbotSessionCb = findViewById(R.id.chatbot_session);
+ mChatbotStandaloneCb = findViewById(R.id.chatbot_standalone);
+ mChatbotVersionCb = findViewById(R.id.chatbot_version);
+
+ mChatCb.setChecked(true);
+
+ mDefaultSmsSubId = SmsManager.getDefaultSmsSubscriptionId();
+ mCallbackResult.setMovementMethod(new ScrollingMovementMethod());
+
+ ImsManager imsManager = this.getSystemService(ImsManager.class);
+ if (SubscriptionManager.isValidSubscriptionId(mDefaultSmsSubId)) {
+ mSipDelegateManager = imsManager.getSipDelegateManager(mDefaultSmsSubId);
+ }
+ setClickable(mDestroyButton, false);
+
+ mInitButton.setOnClickListener(view -> {
+ mCallbackResultStr = "";
+ if (mSipDelegateManager != null) {
+ Set<String> featureTags = getFeatureTags();
+ try {
+ Log.i(TAG, "createSipDelegate");
+ dumpFt(featureTags);
+ mSipDelegateManager.createSipDelegate(new DelegateRequest(featureTags),
+ mExecutorService, mConnectionCallback, mMessageCallback);
+ } catch (ImsException e) {
+ //e.printStackTrace();
+ mCallbackResult.setText(e.toString());
+ Log.e(TAG, e.toString());
+ }
+ setClickable(mInitButton, false);
+ setClickable(mDestroyButton, true);
+ }
+ });
+
+ mDestroyButton.setOnClickListener(view -> {
+ mCallbackResultStr = "";
+ if (mSipDelegateManager != null && mSipDelegateConnection != null) {
+ Log.i(TAG, "destroySipDelegate");
+ mSipDelegateManager.destroySipDelegate(mSipDelegateConnection,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ setClickable(mInitButton, true);
+ setClickable(mDestroyButton, false);
+ }
+ });
+ }
+
+ private Set<String> getFeatureTags() {
+ HashSet<String> fts = new HashSet<>();
+ if (mChatCb.isChecked()) {
+ fts.add(SESSION_TAG);
+ }
+ if (mStandalonePagerCb.isChecked()) {
+ fts.add(STANDALONE_PAGER);
+ }
+ if (mStandaloneLargeCb.isChecked()) {
+ fts.add(STANDALONE_LARGE);
+ }
+ if (mStandaloneDeferredCb.isChecked()) {
+ fts.add(STANDALONE_DEFERRED);
+ }
+ if (mStandaloneLargePagerCb.isChecked()) {
+ fts.add(STANDALONE_LARGE_PAGER);
+ }
+ if (mFileTransferCb.isChecked()) {
+ fts.add(FILE_TRANSFER);
+ }
+ if (mGeolocationSmsCb.isChecked()) {
+ fts.add(GEOLOCATION_SMS);
+ }
+ if (mChatbotSessionCb.isChecked()) {
+ fts.add(CHATBOT_SESSION);
+ }
+ if (mChatbotStandaloneCb.isChecked()) {
+ fts.add(CHATBOT_STANDALONE);
+ }
+ if (mChatbotVersionCb.isChecked()) {
+ fts.add(CHATBOT_VERSION);
+ }
+ return fts;
+ }
+
+ private void dumpFt(Set<String> fts) {
+ Iterator<String> iterator = fts.iterator();
+ StringBuilder res = new StringBuilder();
+ while (iterator.hasNext()) {
+ res.append(iterator.next()).append("\r\n");
+ }
+ Log.i(TAG, "FeatureTag: " + res.toString());
+ }
+
+ private void setClickable(Button button, boolean clickable) {
+ if (clickable) {
+ button.setAlpha(1);
+ button.setClickable(true);
+ } else {
+ button.setAlpha(.5f);
+ button.setClickable(false);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (mSipDelegateManager != null && mSipDelegateConnection != null) {
+ Log.i(TAG, "onStop() destroySipDelegate");
+ mSipDelegateManager.destroySipDelegate(mSipDelegateConnection,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ setClickable(mInitButton, true);
+ setClickable(mDestroyButton, false);
+ }
+
+ }
+
+ private void dumpConfig(SipDelegateConfiguration config) {
+ String result = "SipDelegateConfiguration{"
+ + "mVersion=" + config.getVersion()
+ + ", \n\tmTransportType=" + config.getTransportType()
+ + ", \n\tmLocalIpAddr=" + config.getLocalAddress()
+ + ", \n\tmSipServerAddr=" + config.getSipServerAddress()
+ + ", \n\tmIsSipCompactFormEnabled=" + config.isSipCompactFormEnabled()
+ + ", \n\tmIsSipKeepaliveEnabled=" + config.isSipKeepaliveEnabled()
+ + ", \n\tmMaxUdpPayloadSize=" + config.getMaxUdpPayloadSizeBytes()
+ + ", \n\tmPublicUserIdentifier=" + config.getPublicUserIdentifier()
+ + ", \n\tmPrivateUserIdentifier=" + config.getPrivateUserIdentifier()
+ + ", \n\tmHomeDomain=" + config.getHomeDomain()
+ + ", \n\tmImei=" + config.getImei()
+ + ", \n\tmGruu=" + config.getPublicGruuUri()
+ + ", \n\tmSipAuthHeader=" + config.getSipAuthenticationHeader()
+ + ", \n\tmSipAuthNonce=" + config.getSipAuthenticationNonce()
+ + ", \n\tmServiceRouteHeader=" + config.getSipServiceRouteHeader()
+ + ", \n\tmPathHeader=" + config.getSipPathHeader()
+ + ", \n\tmUserAgentHeader=" + config.getSipUserAgentHeader()
+ + ", \n\tmContactUserParam=" + config.getSipContactUserParameter()
+ + ", \n\tmPaniHeader=" + config.getSipPaniHeader()
+ + ", \n\tmPlaniHeader=" + config.getSipPlaniHeader()
+ + ", \n\tmCniHeader=" + config.getSipCniHeader()
+ + ", \n\tmAssociatedUriHeader=" + config.getSipAssociatedUriHeader()
+ + ", \n\tmIpSecConfiguration=" + config.getIpSecConfiguration()
+ + ", \n\tmNatConfiguration=" + config.getNatSocketAddress() + '}';
+ Log.i(TAG, result);
+ }
+
+}
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java
new file mode 100644
index 0000000..b9078f8
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java
@@ -0,0 +1,358 @@
+/*
+ * 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;
+
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.provider.OpenableColumns;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SmsManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.ProvisioningManager.RcsProvisioningCallback;
+import android.telephony.ims.RcsClientConfiguration;
+import android.text.TextUtils;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.util.Xml;
+import android.view.MenuItem;
+import android.webkit.MimeTypeMap;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.android.libraries.rcs.simpleclient.filetransfer.FileTransferController;
+import com.android.libraries.rcs.simpleclient.filetransfer.FileTransferControllerImpl;
+import com.android.libraries.rcs.simpleclient.filetransfer.requestexecutor.GbaAuthenticationProvider;
+import com.android.libraries.rcs.simpleclient.filetransfer.requestexecutor.GbaRequestExecutor;
+import com.android.libraries.rcs.simpleclient.filetransfer.requestexecutor.HttpRequestExecutor;
+
+import com.google.common.io.ByteStreams;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.UUID;
+
+/** An activity to verify file upload with GBA authentication. */
+public class FileUploadActivity extends AppCompatActivity {
+
+ private static final String TAG = "TestRcsApp.FileUploadActivity";
+ private static final String NAF_PREFIX = "https://3GPP-bootstrapping@";
+ private static final int PICKFILE_RESULT = 1;
+ private static final String HTTP_URI = "ftHTTPCSURI";
+ private static final String PARM = "parm";
+ private static final String NAME = "name";
+ private static final String VALUE = "value";
+
+
+ private ProvisioningManager mProvisioningManager;
+ private int mDefaultSmsSubId;
+ private File mFile;
+ private Button mUpload, mBrowse;
+ private TextView mUploadResult;
+ private TextView mFileName;
+ private EditText mServerUri;
+ private RcsProvisioningCallback mCallback =
+ new RcsProvisioningCallback() {
+ @Override
+ public void onConfigurationChanged(@NonNull byte[] configXml) {
+ String configResult = new String(configXml);
+ String server = getFtServerUri(configXml);
+ Log.i(TAG, "RcsProvisioningCallback.onConfigurationChanged called with xml:");
+ Log.i(TAG, configResult);
+ Log.i(TAG, "serverUri:" + server);
+ mServerUri.setText(server);
+ }
+
+ @Override
+ public void onConfigurationReset() {
+ Log.i(TAG, "RcsProvisioningCallback.onConfigurationReset called.");
+ }
+
+ @Override
+ public void onRemoved() {
+ Log.i(TAG, "RcsProvisioningCallback.onRemoved called.");
+ }
+ };
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.file_upload_layout);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+
+ initLayout();
+ registerProvisioning();
+ }
+
+ private void initLayout() {
+ mServerUri = findViewById(R.id.ft_uri);
+ mUpload = findViewById(R.id.upload_btn);
+ mBrowse = findViewById(R.id.browse_btn);
+ mFileName = findViewById(R.id.file_name);
+ mUploadResult = findViewById(R.id.upload_file_result);
+ mUploadResult.setMovementMethod(new ScrollingMovementMethod());
+
+ mBrowse.setOnClickListener(view -> {
+ Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
+ chooseFile.setType("*/*");
+ chooseFile = Intent.createChooser(chooseFile, "Choose a file");
+ startActivityForResult(chooseFile, PICKFILE_RESULT);
+ });
+
+ mUpload.setOnClickListener(view -> {
+ if (TextUtils.isEmpty(mServerUri.getText())) {
+ Toast.makeText(FileUploadActivity.this,
+ getResources().getString(R.string.server_empty),
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+ if (mFile == null) {
+ Toast.makeText(FileUploadActivity.this,
+ getResources().getString(R.string.file_empty),
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ Log.i(TAG, "upload file");
+ try {
+ FileTransferController fileTransferController = initFileTransferController();
+ if (fileTransferController == null) {
+ Log.i(TAG, "FileTransferController null");
+ return;
+ }
+ mUploadResult.setText("");
+ Futures.addCallback(
+ fileTransferController.uploadFile(UUID.randomUUID().toString(),
+ mFile),
+ new FutureCallback<String>() {
+ @Override
+ public void onSuccess(String xml) {
+ String text;
+ if (TextUtils.isEmpty(xml)) {
+ text = "onFailure: Empty Xml";
+ Log.i(TAG, text);
+ mUploadResult.setText(text);
+ return;
+ }
+ text = "onSuccess\r\n" + xml;
+ Log.i(TAG, text);
+ mUploadResult.setText(text);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ String text = "onFailure:" + t;
+ Log.i(TAG, text);
+ mUploadResult.setText(text);
+ }
+ },
+ getMainExecutor());
+ } catch (IOException e) {
+ Log.e(TAG, e.getMessage());
+ }
+ });
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case PICKFILE_RESULT:
+ if (resultCode == RESULT_OK) {
+ Uri fileUri = data.getData();
+ String fileName = getFileName(fileUri);
+ mFileName.setText(fileName);
+ try {
+ mFile = uriToFile(fileUri);
+ Log.i(TAG, "mFile:" + mFile);
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage());
+ e.printStackTrace();
+ }
+ }
+ break;
+ }
+ }
+
+ private void registerProvisioning() {
+ mDefaultSmsSubId = SmsManager.getDefaultSmsSubscriptionId();
+ Log.i(TAG, "mDefaultSmsSubId:" + mDefaultSmsSubId);
+ if (SubscriptionManager.isValidSubscriptionId(mDefaultSmsSubId)) {
+ try {
+ mProvisioningManager = ProvisioningManager.createForSubscriptionId(
+ mDefaultSmsSubId);
+ mProvisioningManager.setRcsClientConfiguration(getDefaultClientConfiguration());
+ mProvisioningManager.registerRcsProvisioningCallback(getMainExecutor(), mCallback);
+ } catch (ImsException e) {
+ Log.e(TAG, e.getMessage());
+ }
+ }
+ }
+
+ private RcsClientConfiguration getDefaultClientConfiguration() {
+ SharedPreferences pref = getSharedPreferences("CONFIG", MODE_PRIVATE);
+
+ return new RcsClientConfiguration(
+ /*rcsVersion=*/ pref.getString("RCS_VERSION", "6.0"),
+ /*rcsProfile=*/ pref.getString("RCS_PROFILE", "UP_1.0"),
+ /*clientVendor=*/ "Goog",
+ /*clientVersion=*/ "RCSAndrd-1.0");
+ }
+
+ private FileTransferController initFileTransferController() {
+ mDefaultSmsSubId = SmsManager.getDefaultSmsSubscriptionId();
+ if (SubscriptionManager.isValidSubscriptionId(mDefaultSmsSubId)) {
+ TelephonyManager telephonyManager = getSystemService(
+ TelephonyManager.class).createForSubscriptionId(mDefaultSmsSubId);
+ PersistableBundle carrierConfig = telephonyManager.getCarrierConfig();
+ String uploadUrl = carrierConfig.getString(
+ CarrierConfigManager.KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING);
+ String carrierName = telephonyManager.getSimOperatorName();
+
+ HttpRequestExecutor executor = new GbaRequestExecutor(
+ new GbaAuthenticationProvider(getSystemService(TelephonyManager.class),
+ NAF_PREFIX + uploadUrl, getMainExecutor()));
+ return new FileTransferControllerImpl(executor, mServerUri.getText().toString(),
+ carrierName);
+ } else {
+ Log.i(TAG, "Invalid subId:" + mDefaultSmsSubId);
+ return null;
+ }
+ }
+
+ private String getFileName(Uri uri) throws IllegalArgumentException {
+ Cursor cursor = getContentResolver().query(uri, null, null, null, null);
+
+ if (cursor.getCount() <= 0) {
+ cursor.close();
+ throw new IllegalArgumentException("Can't obtain file name, cursor is empty");
+ }
+ cursor.moveToFirst();
+ String fileName = cursor.getString(
+ cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));
+ cursor.close();
+
+ return fileName;
+ }
+
+ private File uriToFile(Uri uri) {
+ File file = null;
+ if (uri == null) return file;
+ if (uri.getScheme().equals(ContentResolver.SCHEME_FILE)) {
+ file = new File(uri.getPath());
+ } else if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
+ ContentResolver contentResolver = getContentResolver();
+ String cachedName = System.currentTimeMillis() + Math.round((Math.random() + 1) * 1000)
+ + "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(
+ contentResolver.getType(uri));
+
+ try {
+ InputStream is = contentResolver.openInputStream(uri);
+ File cache = new File(getExternalCacheDir().getAbsolutePath(), cachedName);
+ FileOutputStream fos = new FileOutputStream(cache);
+ ByteStreams.copy(is, fos);
+ file = cache;
+ fos.close();
+ is.close();
+ } catch (IOException e) {
+ Log.i(TAG, e.getMessage());
+ }
+ }
+ return file;
+ }
+
+ private String getContentType(Uri uri) {
+ MimeTypeMap mime = MimeTypeMap.getSingleton();
+ return mime.getExtensionFromMimeType(getContentResolver().getType(uri));
+ }
+
+
+ /**
+ * According GSMA RCC.72, get FileTransfer URI from the config xml whose content includes the
+ * following parameter.
+ * <parm name="ftHTTPCSURI"
+ * value="https://ftcontentserver.rcs.mnc008.mcc123.pub.3gppnetwork.org/content/"/>
+ */
+ private String getFtServerUri(byte[] xml) {
+ try {
+ InputStream inputStream = new ByteArrayInputStream(xml);
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(inputStream, "utf-8");
+
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ switch (eventType) {
+ case XmlPullParser.START_TAG:
+ if (parser.getName().equals(PARM)) {
+ String name = parser.getAttributeValue(null, NAME);
+ if (HTTP_URI.equalsIgnoreCase(name)) {
+ return parser.getAttributeValue(null, VALUE);
+ }
+ }
+ }
+ eventType = parser.next();
+ }
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage());
+ return "";
+ }
+ return "";
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ //delete cache files
+ File cache = new File(getExternalCacheDir().getAbsolutePath());
+ File[] files = cache.listFiles();
+ for (File file : files) {
+ file.delete();
+ }
+ if (mProvisioningManager != null) {
+ mProvisioningManager.unregisterRcsProvisioningCallback(mCallback);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java
new file mode 100644
index 0000000..9ee2a35
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2020 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;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SmsManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.TelephonyManager.BootstrapAuthenticationCallback;
+import android.telephony.gba.UaSecurityProtocolIdentifier;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import java.util.Locale;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/** An activity to verify GBA authentication. */
+public class GbaActivity extends AppCompatActivity {
+
+ private static final String TAG = "TestRcsApp.GbaActivity";
+ private static final String NAF_PREFIX = "https://3GPP-bootstrapping@";
+
+ private static final int MSG_RESULT = 1;
+ private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
+ private Button mGbaButton;
+ private TextView mCallbackResult;
+ private Spinner mOrganizationSpinner, mProtocolSpinner, mUiccSpinner;
+ private EditText mTlsCs;
+ private EditText mNaf;
+ private Handler mHandler;
+ private int mOrganization;
+ private int mProtocol;
+ private int mUiccType;
+
+ private static String bytesToHex(byte[] bytes) {
+ StringBuilder result = new StringBuilder();
+ for (byte aByte : bytes) {
+ result.append(String.format(Locale.US, "%02X", aByte));
+ }
+ return result.toString();
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.gba_layout);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+ initLayout();
+ mHandler = new Handler() {
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_RESULT:
+ mCallbackResult.setText(message.obj.toString());
+ break;
+ }
+ }
+ };
+ }
+
+ private void initLayout() {
+ mGbaButton = findViewById(R.id.gba_btn);
+ mCallbackResult = findViewById(R.id.gba_result);
+ mCallbackResult.setMovementMethod(new ScrollingMovementMethod());
+ mTlsCs = findViewById(R.id.tls_id);
+ mNaf = findViewById(R.id.naf_url);
+
+ initOrganization();
+ initProtocol();
+ initUicctype();
+
+ int defaultSmsSubId = SmsManager.getDefaultSmsSubscriptionId();
+ if (!SubscriptionManager.isValidSubscriptionId(defaultSmsSubId)) {
+ Log.i(TAG, "invalid subId:" + defaultSmsSubId);
+ return;
+ }
+ TelephonyManager telephonyManager = getSystemService(
+ TelephonyManager.class).createForSubscriptionId(defaultSmsSubId);
+ PersistableBundle carrierConfig = telephonyManager.getCarrierConfig();
+ String uploadUrl = carrierConfig.getString(
+ CarrierConfigManager.KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING);
+ mNaf.setText(NAF_PREFIX + uploadUrl);
+
+ mGbaButton.setOnClickListener(view -> {
+ Log.i(TAG, "trigger bootstrapAuthenticationRequest");
+ UaSecurityProtocolIdentifier.Builder builder =
+ new UaSecurityProtocolIdentifier.Builder();
+ try {
+ builder.setOrg(mOrganization)
+ .setProtocol(mProtocol)
+ .setTlsCipherSuite(Integer.parseInt(mTlsCs.getText().toString()));
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, e.getMessage());
+ return;
+ }
+ UaSecurityProtocolIdentifier spId = builder.build();
+ telephonyManager.bootstrapAuthenticationRequest(mUiccType,
+ Uri.parse(mNaf.getText().toString()),
+ spId,
+ true,
+ mExecutorService,
+ new BootstrapAuthenticationCallback() {
+ @Override
+ public void onKeysAvailable(byte[] gbaKey, String btId) {
+ String result = "OnKeysAvailable key:" + bytesToHex(gbaKey)
+ + "\r\n btId:" + btId;
+ Log.i(TAG, result);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_RESULT, result));
+ }
+
+ @Override
+ public void onAuthenticationFailure(int reason) {
+ String result = "OnAuthenticationFailure reason: " + reason;
+ Log.i(TAG, result);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_RESULT, result));
+ }
+ });
+ });
+ }
+
+ private void initOrganization() {
+ mOrganizationSpinner = findViewById(R.id.organization_list);
+ ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
+ R.array.organization, android.R.layout.simple_spinner_item);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mOrganizationSpinner.setAdapter(adapter);
+ mOrganizationSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ Log.i(TAG, "Organization position:" + position);
+ switch (position) {
+ case 0:
+ mOrganization = UaSecurityProtocolIdentifier.ORG_NONE;
+ break;
+ case 1:
+ mOrganization = UaSecurityProtocolIdentifier.ORG_3GPP;
+ break;
+ case 2:
+ mOrganization = UaSecurityProtocolIdentifier.ORG_3GPP2;
+ break;
+ case 3:
+ mOrganization = UaSecurityProtocolIdentifier.ORG_GSMA;
+ break;
+ case 4:
+ mOrganization = UaSecurityProtocolIdentifier.ORG_LOCAL;
+ break;
+ default:
+ Log.e(TAG, "invalid position:" + position);
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // TODO Auto-generated method stub
+ }
+ });
+ mOrganizationSpinner.setSelection(1);
+ }
+
+ private void initProtocol() {
+ mProtocolSpinner = findViewById(R.id.protocol_list);
+ ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
+ R.array.protocol, android.R.layout.simple_spinner_item);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mProtocolSpinner.setAdapter(adapter);
+ mProtocolSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ Log.i(TAG, "Protocol position:" + position);
+ switch (position) {
+ case 0:
+ mProtocol = UaSecurityProtocolIdentifier
+ .UA_SECURITY_PROTOCOL_3GPP_SUBSCRIBER_CERTIFICATE;
+ break;
+ case 1:
+ mProtocol = UaSecurityProtocolIdentifier
+ .UA_SECURITY_PROTOCOL_3GPP_MBMS;
+ break;
+ case 2:
+ mProtocol = UaSecurityProtocolIdentifier
+ .UA_SECURITY_PROTOCOL_3GPP_HTTP_DIGEST_AUTHENTICATION;
+ break;
+ case 3:
+ mProtocol = UaSecurityProtocolIdentifier
+ .UA_SECURITY_PROTOCOL_3GPP_HTTP_BASED_MBMS;
+ break;
+ case 4:
+ mProtocol = UaSecurityProtocolIdentifier
+ .UA_SECURITY_PROTOCOL_3GPP_SIP_BASED_MBMS;
+ break;
+ case 5:
+ mProtocol = UaSecurityProtocolIdentifier
+ .UA_SECURITY_PROTOCOL_3GPP_GENERIC_PUSH_LAYER;
+ break;
+ case 6:
+ mProtocol = UaSecurityProtocolIdentifier
+ .UA_SECURITY_PROTOCOL_3GPP_IMS_MEDIA_PLANE;
+ break;
+ case 7:
+ mProtocol = UaSecurityProtocolIdentifier
+ .UA_SECURITY_PROTOCOL_3GPP_GENERATION_TMPI;
+ break;
+ case 8:
+ mProtocol = UaSecurityProtocolIdentifier
+ .UA_SECURITY_PROTOCOL_3GPP_TLS_DEFAULT;
+ break;
+ case 9:
+ mProtocol = UaSecurityProtocolIdentifier
+ .UA_SECURITY_PROTOCOL_3GPP_TLS_BROWSER;
+ break;
+ default:
+ Log.e(TAG, "invalid position:" + position);
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // TODO Auto-generated method stub
+ }
+ });
+ mProtocolSpinner.setSelection(8);
+ }
+
+ private void initUicctype() {
+ mUiccSpinner = findViewById(R.id.uicc_list);
+ ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
+ R.array.uicc_type, android.R.layout.simple_spinner_item);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mUiccSpinner.setAdapter(adapter);
+ mUiccSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ Log.i(TAG, "uicc position:" + position);
+ switch (position) {
+ case 0:
+ mUiccType = TelephonyManager.APPTYPE_UNKNOWN;
+ break;
+ case 1:
+ mUiccType = TelephonyManager.APPTYPE_SIM;
+ break;
+ case 2:
+ mUiccType = TelephonyManager.APPTYPE_USIM;
+ break;
+ case 3:
+ mUiccType = TelephonyManager.APPTYPE_RUIM;
+ break;
+ case 4:
+ mUiccType = TelephonyManager.APPTYPE_CSIM;
+ break;
+ case 5:
+ mUiccType = TelephonyManager.APPTYPE_ISIM;
+ break;
+ default:
+ Log.e(TAG, "invalid position:" + position);
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // TODO Auto-generated method stub
+ }
+ });
+ mUiccSpinner.setSelection(5);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/MainActivity.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/MainActivity.java
new file mode 100644
index 0000000..89c5268
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/MainActivity.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2020 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;
+
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.MenuItem;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+/** An activity to show function list. */
+public class MainActivity extends AppCompatActivity {
+ private static final String TAG = "TestRcsApp.MainActivity";
+ private Button mProvisionButton;
+ private Button mDelegateButton;
+ private Button mUceButton;
+ private Button mGbaButton;
+ private Button mMessageClientButton;
+ private Button mFileUploadButton;
+ private TextView mVersionInfo;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+
+ mProvisionButton = (Button) this.findViewById(R.id.provision);
+ mDelegateButton = (Button) this.findViewById(R.id.delegate);
+ mMessageClientButton = (Button) this.findViewById(R.id.msgClient);
+ mUceButton = (Button) this.findViewById(R.id.uce);
+ mGbaButton = (Button) this.findViewById(R.id.gba);
+ mFileUploadButton = findViewById(R.id.uploadFile);
+ mVersionInfo = this.findViewById(R.id.version_info);
+ mProvisionButton.setOnClickListener(view -> {
+ Intent intent = new Intent(this, ProvisioningActivity.class);
+ MainActivity.this.startActivity(intent);
+ });
+
+ mDelegateButton.setOnClickListener(view -> {
+ Intent intent = new Intent(this, DelegateActivity.class);
+ MainActivity.this.startActivity(intent);
+ });
+
+ mUceButton.setOnClickListener(view -> {
+ Intent intent = new Intent(this, UceActivity.class);
+ MainActivity.this.startActivity(intent);
+ });
+
+ mGbaButton.setOnClickListener(view -> {
+ Intent intent = new Intent(this, GbaActivity.class);
+ MainActivity.this.startActivity(intent);
+ });
+ mMessageClientButton.setOnClickListener(view -> {
+ Intent intent = new Intent(this, ContactListActivity.class);
+ MainActivity.this.startActivity(intent);
+ });
+ mFileUploadButton.setOnClickListener(view -> {
+ Intent intent = new Intent(this, FileUploadActivity.class);
+ MainActivity.this.startActivity(intent);
+ });
+
+ String appVersionName = getVersionCode(getPackageName());
+ if (!TextUtils.isEmpty(appVersionName)) {
+ String version = String.format(getResources().getString(R.string.version_info),
+ appVersionName);
+ mVersionInfo.setText(version);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private String getVersionCode(String packageName) {
+ try {
+ // get android:versionName from the android manifest
+ PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), 0 /*flags*/);
+ return info.versionName;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "couldn't get version info for package name:" + packageName);
+ }
+ return null;
+ }
+}
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
new file mode 100644
index 0000000..b432979
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/PhoneNumberActivity.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 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;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+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 {
+
+ private static final String TAG = "TestRcsApp.PhoneNumberActivity";
+ private Button mChatButton;
+ private EditText mPhoneNumber;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.number_to_chat);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+
+ mChatButton = this.findViewById(R.id.launch_chat_btn);
+ mPhoneNumber = findViewById(R.id.destNum);
+ mChatButton.setOnClickListener(view -> {
+ String formattedNumber = NumberUtils.formatNumber(PhoneNumberActivity.this,
+ mPhoneNumber.getText().toString());
+ 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();
+ }
+ });
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ Log.i(TAG, "onStop");
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onStop();
+ Log.i(TAG, "onDestroy");
+ }
+}
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
new file mode 100644
index 0000000..dae2835
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2020 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;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.SmsManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.ProvisioningManager.RcsProvisioningCallback;
+import android.telephony.ims.RcsClientConfiguration;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+
+/** An activity to verify provisioning. */
+public class ProvisioningActivity extends AppCompatActivity {
+
+ private static final String TAG = "TestRcsApp.ProvisioningActivity";
+ private static final String UP_10 = "UP_1.0";
+ private static final String UP_23 = "UP_2.3";
+ private static final String V_6 = "6.0";
+ private static final String V_9 = "9.0";
+ private static final String RCS_CONFIG = "CONFIG";
+ private static final String RCS_PROFILE = "RCS_PROFILE";
+ private static final String RCS_VERSION = "RCS_VERSION";
+ private static final int MSG_RESULT = 1;
+
+ private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
+ private int mDefaultSmsSubId;
+ private ProvisioningManager mProvisioningManager;
+ private Spinner mRcsProfileSpinner;
+ private String mRcsVersion;
+ private String mRcsProfile;
+ private Button mRegisterButton;
+ private Button mUnRegisterButton;
+ private Button mIsCapableButton;
+ private TextView mSingleRegResult;
+ private TextView mCallbackResult;
+ private SharedPreferences mPref;
+ private SingleRegCapabilityReceiver mSingleRegCapabilityReceiver;
+ private boolean mIsRegistered = false;
+ private Handler mHandler;
+ private RcsProvisioningCallback mCallback =
+ new RcsProvisioningCallback() {
+ @Override
+ public void onConfigurationChanged(@NonNull byte[] configXml) {
+ String configResult = new String(configXml);
+ Log.i(TAG, "RcsProvisioningCallback.onConfigurationChanged called with xml:");
+ Log.i(TAG, configResult);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_RESULT,
+ "onConfigurationChanged \r\n" + configResult));
+ }
+
+ @Override
+ public void onConfigurationReset() {
+ Log.i(TAG, "RcsProvisioningCallback.onConfigurationReset called.");
+ mHandler.sendMessage(
+ mHandler.obtainMessage(MSG_RESULT, "onConfigurationReset"));
+ }
+
+ @Override
+ public void onRemoved() {
+ Log.i(TAG, "RcsProvisioningCallback.onRemoved called.");
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_RESULT, "onRemoved"));
+ }
+ };
+
+ // Static configuration.
+ private RcsClientConfiguration getDefaultClientConfiguration() {
+ return new RcsClientConfiguration(
+ /*rcsVersion=*/ mRcsVersion,
+ /*rcsProfile=*/ mRcsProfile,
+ /*clientVendor=*/ "Goog",
+ /*clientVersion=*/ "RCSAndrd-1.0");
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.provision_layout);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+ mSingleRegCapabilityReceiver = new SingleRegCapabilityReceiver();
+ this.registerReceiver(mSingleRegCapabilityReceiver, new IntentFilter(
+ ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE));
+ mHandler = new Handler() {
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_RESULT:
+ mCallbackResult.setText(message.obj.toString());
+ break;
+ }
+ }
+ };
+ mPref = getSharedPreferences(RCS_CONFIG, MODE_PRIVATE);
+ initRcsProfile();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mDefaultSmsSubId = SmsManager.getDefaultSmsSubscriptionId();
+ Log.i(TAG, "defaultSmsSubId:" + mDefaultSmsSubId);
+ if (SubscriptionManager.isValidSubscriptionId(mDefaultSmsSubId)) {
+ mProvisioningManager = ProvisioningManager.createForSubscriptionId(mDefaultSmsSubId);
+ init();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ this.unregisterReceiver(mSingleRegCapabilityReceiver);
+ if (mIsRegistered) {
+ mProvisioningManager.unregisterRcsProvisioningCallback(mCallback);
+ }
+ }
+
+ private void init() {
+ mRegisterButton = findViewById(R.id.provisioning_register_btn);
+ mUnRegisterButton = findViewById(R.id.provisioning_unregister_btn);
+ mIsCapableButton = findViewById(R.id.provisioning_singlereg_btn);
+ mSingleRegResult = findViewById(R.id.provisioning_singlereg_result);
+ mCallbackResult = findViewById(R.id.provisioning_callback_result);
+ mCallbackResult.setMovementMethod(new ScrollingMovementMethod());
+
+ boolean isSingleRegCapable = false;
+ try {
+ mProvisioningManager.isRcsVolteSingleRegistrationCapable();
+ } catch (ImsException e) {
+ Log.i(TAG, e.getMessage());
+ }
+ if (isSingleRegCapable && !mIsRegistered) {
+ setClickable(mRegisterButton, true);
+ }
+
+ mRegisterButton.setOnClickListener(view -> {
+ if (mProvisioningManager != null) {
+ Log.i(TAG, "Using configuration: " + getDefaultClientConfiguration());
+ try {
+ Log.i(TAG, "setRcsClientConfiguration()");
+ Log.i(TAG, "registerRcsProvisioningCallback()");
+ mProvisioningManager.setRcsClientConfiguration(getDefaultClientConfiguration());
+ mProvisioningManager.registerRcsProvisioningCallback(mExecutorService,
+ mCallback);
+ mIsRegistered = true;
+ } catch (ImsException e) {
+ Log.e(TAG, e.getMessage());
+ }
+ setClickable(mRegisterButton, false);
+ setClickable(mUnRegisterButton, true);
+ } else {
+ Log.i(TAG, "provisioningManager null");
+ }
+ });
+ mUnRegisterButton.setOnClickListener(view -> {
+ if (mProvisioningManager != null) {
+ mProvisioningManager.unregisterRcsProvisioningCallback(mCallback);
+ setClickable(mRegisterButton, false);
+ setClickable(mRegisterButton, true);
+ mIsRegistered = false;
+ }
+ });
+ mIsCapableButton.setOnClickListener(view -> {
+ if (mProvisioningManager != null) {
+ try {
+ boolean capable = mProvisioningManager.isRcsVolteSingleRegistrationCapable();
+ mSingleRegResult.setText(String.valueOf(capable));
+ Log.i(TAG, "isRcsVolteSingleRegistrationCapable:" + capable);
+ } catch (ImsException e) {
+ Log.e(TAG, e.getMessage());
+ }
+ }
+ });
+ }
+
+ private void setClickable(Button button, boolean clickable) {
+ if (clickable) {
+ button.setAlpha(1);
+ button.setClickable(true);
+ } else {
+ button.setAlpha(.5f);
+ button.setClickable(false);
+ }
+ }
+
+ private void initRcsProfile() {
+ mRcsProfileSpinner = findViewById(R.id.rcs_profile_list);
+ ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
+ R.array.rcs_profile, android.R.layout.simple_spinner_item);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mRcsProfileSpinner.setAdapter(adapter);
+ mRcsProfileSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ Log.i(TAG, "rcs profile position:" + position);
+ switch (position) {
+ case 0:
+ mRcsProfile = UP_10;
+ mRcsVersion = V_6;
+ break;
+ case 1:
+ mRcsProfile = UP_23;
+ mRcsVersion = V_9;
+ break;
+ default:
+ Log.e(TAG, "invalid position:" + position);
+ return;
+ }
+ mPref.edit().putString(RCS_PROFILE, mRcsProfile)
+ .putString(RCS_VERSION, mRcsVersion)
+ .commit();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // TODO Auto-generated method stub
+ }
+ });
+ mRcsProfile = mPref.getString(RCS_PROFILE, UP_10);
+ mRcsVersion = mPref.getString(RCS_VERSION, V_6);
+ mRcsProfileSpinner.setSelection(mRcsProfile.equals(UP_10) ? 0 : 1);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ class SingleRegCapabilityReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ Log.i(TAG, "onReceive action:" + action);
+ if (ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE.equals(
+ action)) {
+ int status = intent.getIntExtra(ProvisioningManager.EXTRA_STATUS,
+ ProvisioningManager.STATUS_DEVICE_NOT_CAPABLE);
+ Log.i(TAG, "singleRegCap status:" + status);
+ if (mRegisterButton != null && !mIsRegistered) {
+ if (status == ProvisioningManager.STATUS_DEVICE_NOT_CAPABLE) {
+ setClickable(mRegisterButton, true);
+ } else {
+ setClickable(mRegisterButton, false);
+ }
+ }
+
+ }
+ }
+ }
+
+}
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/RcsStateChangedCallback.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/RcsStateChangedCallback.java
new file mode 100644
index 0000000..fd36f01
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/RcsStateChangedCallback.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2020 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;
+
+import com.android.libraries.rcs.simpleclient.SimpleRcsClient.State;
+
+/** A callback used to notify RCS state change. */
+public interface RcsStateChangedCallback {
+ /** callback for RCS state change. */
+ void notifyStateChange(State oldState, State newState);
+}
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/SessionStateCallback.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/SessionStateCallback.java
new file mode 100644
index 0000000..3881775
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/SessionStateCallback.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 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;
+
+/** A callback used to inform chat session creation result. */
+public interface SessionStateCallback {
+ /** callback for successful session creation */
+ void onSuccess();
+
+ /**callback for failed session creation. */
+ void onFailure();
+}
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
new file mode 100644
index 0000000..0fae8f6
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2020 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;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.telephony.SmsManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsManager;
+import android.telephony.ims.ImsRcsManager;
+import android.telephony.ims.RcsContactPresenceTuple;
+import android.telephony.ims.RcsContactUceCapability;
+import android.telephony.ims.RcsUceAdapter;
+import android.text.TextUtils;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.view.MenuItem;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+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;
+
+/** An activity to verify UCE. */
+public class UceActivity extends AppCompatActivity {
+
+ private static final String TAG = "TestRcsApp.UceActivity";
+ private static final String TELURI_PREFIX = "tel:";
+ private Button mCapabilityButton;
+ private Button mAvailabilityButton;
+ private TextView mCapabilityResult;
+ private EditText mNumbers;
+ private int mDefaultSmsSubId;
+ private ImsRcsManager mImsRcsManager;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.uce_layout);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+
+ initLayout();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mDefaultSmsSubId = SmsManager.getDefaultSmsSubscriptionId();
+ Log.i(TAG, "defaultSmsSubId:" + mDefaultSmsSubId);
+ if (SubscriptionManager.isValidSubscriptionId(mDefaultSmsSubId)) {
+ mImsRcsManager = getImsRcsManager(mDefaultSmsSubId);
+ if (mImsRcsManager != null) {
+ initLayout();
+ }
+ }
+ }
+
+ private void initLayout() {
+ mCapabilityButton = findViewById(R.id.capability_btn);
+ mAvailabilityButton = findViewById(R.id.availability_btn);
+ mCapabilityResult = findViewById(R.id.capability_result);
+ mCapabilityResult.setMovementMethod(new ScrollingMovementMethod());
+
+ 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, getMainExecutor(),
+ new RcsUceAdapter.CapabilitiesCallback() {
+ public void onCapabilitiesReceived(
+ List<RcsContactUceCapability> contactCapabilities) {
+ StringBuilder b = new StringBuilder("onCapabilitiesReceived:\n");
+ for (RcsContactUceCapability c : contactCapabilities) {
+ b.append(getReadableCapability(c));
+ b.append("\n");
+ }
+ mCapabilityResult.append(b.toString() + "\n");
+ Log.i(TAG, b.toString());
+ }
+
+ public void onComplete() {
+ Log.i(TAG, "onComplete()");
+ mCapabilityResult.append("onComplete");
+
+ }
+
+ public void onError(int errorCode, long retryAfterMilliseconds) {
+ String result =
+ "onError() errorCode:" + errorCode + " retryAfterMs:"
+ + retryAfterMilliseconds + "\n";
+ Log.i(TAG, result);
+ mCapabilityResult.append(result);
+ }
+ });
+ } catch (ImsException e) {
+ mCapabilityResult.setText("ImsException:" + e);
+ }
+ });
+
+ mAvailabilityButton.setOnClickListener(view -> {
+ List<Uri> contactList = getContectList();
+ if (contactList.size() == 0) {
+ Log.i(TAG, "empty contact list");
+ return;
+ }
+ mCapabilityResult.setText("pending...\n");
+ try {
+ mImsRcsManager.getUceAdapter().requestAvailability(contactList.get(0),
+ getMainExecutor(), new RcsUceAdapter.CapabilitiesCallback() {
+ public void onCapabilitiesReceived(
+ List<RcsContactUceCapability> contactCapabilities) {
+ StringBuilder b = new StringBuilder("onCapabilitiesReceived:\n");
+ for (RcsContactUceCapability c : contactCapabilities) {
+ b.append(getReadableCapability(c));
+ b.append("\n");
+ }
+ mCapabilityResult.append(b.toString() + "\n");
+ Log.i(TAG, b.toString());
+ }
+
+ public void onComplete() {
+ Log.i(TAG, "onComplete()");
+ mCapabilityResult.append("onComplete");
+
+ }
+
+ public void onError(int errorCode, long retryAfterMilliseconds) {
+ String result =
+ "onError() errorCode:" + errorCode + " retryAfterMs:"
+ + retryAfterMilliseconds + "\n";
+ Log.i(TAG, result);
+ mCapabilityResult.append(result);
+ }
+ });
+ } catch (ImsException e) {
+ mCapabilityResult.setText("ImsException:" + e);
+ }
+ });
+ }
+
+ private List<Uri> getContectList() {
+ mNumbers = findViewById(R.id.number_list);
+ 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) {
+ String formattedNumber = NumberUtils.formatNumber(this, number);
+ if (formattedNumber != null) {
+ contactList.add(Uri.parse(TELURI_PREFIX + formattedNumber));
+ } else {
+ Log.w(TAG, "number formatted improperly, skipping: " + number);
+ }
+ }
+ }
+
+ return contactList;
+ }
+
+ private ImsRcsManager getImsRcsManager(int subId) {
+ ImsManager imsManager = getSystemService(ImsManager.class);
+ if (imsManager != null) {
+ try {
+ return imsManager.getImsRcsManager(subId);
+ } catch (Exception e) {
+ Log.e(TAG, "fail to getImsRcsManager " + e.getMessage());
+ return null;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private String getReadableCapability(RcsContactUceCapability c) {
+ StringBuilder b = new StringBuilder("RcsContactUceCapability: uri=");
+ b.append(c.getContactUri());
+ b.append(", requestResult=");
+ b.append(c.getRequestResult());
+ b.append(", sourceType=");
+ b.append(c.getSourceType());
+ if (c.getCapabilityMechanism() == RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE) {
+ b.append(", tuples={");
+ for (RcsContactPresenceTuple t : c.getCapabilityTuples()) {
+ b.append("[uri=");
+ b.append(t.getContactUri());
+ b.append(", serviceId=");
+ b.append(t.getServiceId());
+ b.append(", serviceVersion=");
+ b.append(t.getServiceVersion());
+ 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=");
+ b.append(servCaps.getUnsupportedDuplexModes());
+ b.append("))");
+ }
+ b.append("]");
+ }
+ b.append("}");
+ }
+ return b.toString();
+ }
+}
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/ChatManager.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/ChatManager.java
new file mode 100644
index 0000000..ed22f03
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/ChatManager.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2020 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.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.telephony.ims.ImsManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.libraries.rcs.simpleclient.SimpleRcsClient;
+import com.android.libraries.rcs.simpleclient.SimpleRcsClient.State;
+import com.android.libraries.rcs.simpleclient.provisioning.ProvisioningController;
+import com.android.libraries.rcs.simpleclient.provisioning.StaticConfigProvisioningController;
+import com.android.libraries.rcs.simpleclient.registration.RegistrationController;
+import com.android.libraries.rcs.simpleclient.registration.RegistrationControllerImpl;
+import com.android.libraries.rcs.simpleclient.service.chat.MinimalCpmChatService;
+import com.android.libraries.rcs.simpleclient.service.chat.SimpleChatSession;
+
+import com.google.android.sample.rcsclient.RcsStateChangedCallback;
+import com.google.android.sample.rcsclient.SessionStateCallback;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import gov.nist.javax.sip.address.AddressFactoryImpl;
+
+import java.text.ParseException;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.sip.address.AddressFactory;
+import javax.sip.address.URI;
+
+/**
+ * This class takes advantage of rcs library to manage chat session and send/receive chat message.
+ */
+public class ChatManager {
+ public static final String SELF = "self";
+ private static final String TAG = "TestRcsApp.ChatManager";
+ private static final String TELURI_PREFIX = "tel:";
+ private static AddressFactory sAddressFactory = new AddressFactoryImpl();
+ private static HashMap<Integer, ChatManager> sChatManagerInstances = new HashMap<>();
+ private final ExecutorService mFixedThreadPool = Executors.newFixedThreadPool(5);
+ private Context mContext;
+ private ProvisioningController mProvisioningController;
+ private RegistrationController mRegistrationController;
+ private MinimalCpmChatService mImsService;
+ private SimpleRcsClient mSimpleRcsClient;
+ private State mState;
+ private int mSubId;
+ private HashMap<String, SimpleChatSession> mContactSessionMap = new HashMap<>();
+ private RcsStateChangedCallback mRcsStateChangedCallback;
+
+ private ChatManager(Context context, int subId) {
+ mContext = context;
+ mSubId = subId;
+ mProvisioningController = StaticConfigProvisioningController.createForSubscriptionId(subId,
+ context);
+ ImsManager imsManager = mContext.getSystemService(ImsManager.class);
+ mRegistrationController = new RegistrationControllerImpl(subId, mFixedThreadPool,
+ imsManager);
+ mImsService = new MinimalCpmChatService(context);
+ mSimpleRcsClient = SimpleRcsClient.newBuilder()
+ .registrationController(mRegistrationController)
+ .provisioningController(mProvisioningController)
+ .imsService(mImsService).build();
+
+ mState = State.NEW;
+ // register callback for state change
+ mSimpleRcsClient.onStateChanged((oldState, newState) -> {
+ Log.i(TAG, "notifyStateChange() oldState:" + oldState + " newState:" + newState);
+ mState = newState;
+ mRcsStateChangedCallback.notifyStateChange(oldState, newState);
+ });
+ mImsService.setListener((session) -> {
+ Log.i(TAG, "onIncomingSession():" + session.getRemoteUri());
+ String phoneNumber = getNumberFromUri(session.getRemoteUri().toString());
+ mContactSessionMap.put(phoneNumber, session);
+ session.setListener(
+ // implement onMessageReceived()
+ (message) -> {
+ mFixedThreadPool.execute(() -> {
+ String msg = message.content();
+ if (TextUtils.isEmpty(phoneNumber)) {
+ Log.i(TAG, "dest number is empty, uri:"
+ + session.getRemoteUri());
+ } else {
+ addNewMessage(msg, phoneNumber, SELF);
+ }
+ });
+ });
+ });
+ }
+
+ /**
+ * Create ChatManager with a specific subId.
+ */
+ public static ChatManager getInstance(Context context, int subId) {
+ synchronized (sChatManagerInstances) {
+ if (sChatManagerInstances.containsKey(subId)) {
+ return sChatManagerInstances.get(subId);
+ }
+ ChatManager chatManager = new ChatManager(context, subId);
+ sChatManagerInstances.put(subId, chatManager);
+ return chatManager;
+ }
+ }
+
+ /**
+ * Try to parse the given uri.
+ *
+ * @throws IllegalArgumentException in case of parsing error.
+ */
+ public static URI createUri(String uri) {
+ try {
+ return sAddressFactory.createURI(uri);
+ } catch (ParseException exception) {
+ throw new IllegalArgumentException("URI cannot be created", exception);
+ }
+ }
+
+ private static String getNumberFromUri(String number) {
+ String[] numberParts = number.split("[@;:]");
+ if (numberParts.length < 2) {
+ return null;
+ }
+ return numberParts[1];
+ }
+
+ /**
+ * set callback for RCS state change.
+ */
+ public void setRcsStateChangedCallback(RcsStateChangedCallback callback) {
+ mRcsStateChangedCallback = callback;
+ }
+
+ /**
+ * Start to register by doing provisioning and creating SipDelegate
+ */
+ public void register() {
+ Log.i(TAG, "do start(), State State = " + mState);
+ if (mState == State.NEW) {
+ mSimpleRcsClient.start();
+ }
+ }
+
+ /**
+ * Deregister chat feature.
+ */
+ public void deregister() {
+ Log.i(TAG, "deregister");
+ sChatManagerInstances.remove(mSubId);
+ mSimpleRcsClient.stop();
+ }
+
+ /**
+ * Initiate 1 to 1 chat session.
+ *
+ * @param contact destination phone number.
+ * @param callback callback for session state.
+ */
+ public void initChatSession(String contact, SessionStateCallback callback) {
+ if (mState != State.REGISTERED) {
+ Log.i(TAG, "Could not init session due to State = " + mState);
+ return;
+ }
+ Log.i(TAG, "initChatSession contact: " + contact);
+ if (mContactSessionMap.containsKey(contact)) {
+ callback.onSuccess();
+ Log.i(TAG, "contact exists");
+ return;
+ }
+ Futures.addCallback(
+ mImsService.startOriginatingChatSession(TELURI_PREFIX + contact),
+ new FutureCallback<SimpleChatSession>() {
+ @Override
+ public void onSuccess(SimpleChatSession chatSession) {
+ String phoneNumber = getNumberFromUri(
+ chatSession.getRemoteUri().toString());
+ mContactSessionMap.put(phoneNumber, chatSession);
+ chatSession.setListener(
+ // implement onMessageReceived()
+ (message) -> {
+ mFixedThreadPool.execute(() -> {
+ String msg = message.content();
+ if (TextUtils.isEmpty(phoneNumber)) {
+ Log.i(TAG, "dest number is empty, uri:"
+ + chatSession.getRemoteUri());
+ } else {
+ addNewMessage(msg, phoneNumber, SELF);
+ }
+ });
+
+ });
+ callback.onSuccess();
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ callback.onFailure();
+ }
+ },
+ MoreExecutors.directExecutor());
+ }
+
+ /**
+ * Send a chat message.
+ *
+ * @param contact destination phone number.
+ * @param message chat message.
+ */
+ public ListenableFuture<Void> sendMessage(String contact, String message) {
+ SimpleChatSession chatSession = mContactSessionMap.get(contact);
+ if (chatSession == null) {
+ Log.i(TAG, "session is unavailable for contact = " + contact);
+ return Futures.immediateFailedFuture(
+ new IllegalStateException("Chat session does not exist"));
+ }
+ return chatSession.sendMessage(message);
+ }
+
+ public boolean isRegistered() {
+ return (mState == State.REGISTERED);
+ }
+
+ /**
+ * Terminate the chat session.
+ *
+ * @param contact destination phone number.
+ */
+ public void terminateSession(String contact) {
+ Log.i(TAG, "terminateSession");
+ SimpleChatSession chatSession = mContactSessionMap.get(contact);
+ if (chatSession == null) {
+ Log.i(TAG, "session is unavailable for contact = " + contact);
+ return;
+ }
+ chatSession.terminate();
+ mContactSessionMap.remove(contact);
+ }
+
+ /**
+ * Insert chat information into database.
+ *
+ * @param message chat message.
+ * @param src source phone number.
+ * @param dest destination phone number.
+ */
+ public Uri addNewMessage(String message, String src, String dest) {
+ long currentTime = Instant.now().getEpochSecond();
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(ChatProvider.RcsColumns.SRC_PHONE_NUMBER, src);
+ contentValues.put(ChatProvider.RcsColumns.DEST_PHONE_NUMBER, dest);
+ contentValues.put(ChatProvider.RcsColumns.CHAT_MESSAGE, message);
+ contentValues.put(ChatProvider.RcsColumns.MSG_TIMESTAMP, currentTime);
+ contentValues.put(ChatProvider.RcsColumns.IS_READ, Boolean.TRUE);
+ // insert chat table
+ Uri result = mContext.getContentResolver().insert(ChatProvider.CHAT_URI, contentValues);
+
+ ContentValues summary = new ContentValues();
+ summary.put(ChatProvider.SummaryColumns.LATEST_MESSAGE, message);
+ summary.put(ChatProvider.SummaryColumns.MSG_TIMESTAMP, currentTime);
+ summary.put(ChatProvider.SummaryColumns.IS_READ, Boolean.TRUE);
+
+ String remoteNumber = src.equals(SELF) ? dest : src;
+ if (remoteNumberExists(remoteNumber)) {
+ mContext.getContentResolver().update(ChatProvider.SUMMARY_URI, summary,
+ ChatProvider.SummaryColumns.REMOTE_PHONE_NUMBER + "=?",
+ new String[]{remoteNumber});
+ } else {
+ summary.put(ChatProvider.SummaryColumns.REMOTE_PHONE_NUMBER, remoteNumber);
+ mContext.getContentResolver().insert(ChatProvider.SUMMARY_URI, summary);
+ }
+ return result;
+ }
+
+ /**
+ * Update MSRP chat message sent result.
+ */
+ public void updateMsgResult(String id, boolean success) {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(ChatProvider.RcsColumns.RESULT, success);
+ mContext.getContentResolver().update(ChatProvider.CHAT_URI, contentValues,
+ ChatProvider.RcsColumns._ID + "=?", new String[]{id});
+ }
+
+ /**
+ * Check if the number exists in the database.
+ */
+ public boolean remoteNumberExists(String number) {
+ Cursor cursor = mContext.getContentResolver().query(ChatProvider.SUMMARY_URI, null,
+ ChatProvider.SummaryColumns.REMOTE_PHONE_NUMBER + "=?", new String[]{number},
+ null);
+ if (cursor != null) {
+ int count = cursor.getCount();
+ return count > 0;
+ }
+ return false;
+ }
+
+}
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/ChatProvider.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/ChatProvider.java
new file mode 100644
index 0000000..98f3ceb
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/ChatProvider.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2020 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.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.text.TextUtils;
+import android.util.Log;
+
+/** A database to store chat message. */
+public class ChatProvider extends ContentProvider {
+ public static final Uri CHAT_URI = Uri.parse("content://rcsprovider/chat");
+ public static final Uri SUMMARY_URI = Uri.parse("content://rcsprovider/summary");
+ public static final String AUTHORITY = "rcsprovider";
+ private static final String TAG = "TestRcsApp.ChatProvider";
+ private static final int DATABASE_VERSION = 2;
+ private static final String CHAT_TABLE_NAME = "chat";
+ private static final String SUMMARY_TABLE_NAME = "summary";
+
+ private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+ private static final int URI_CHAT = 1;
+ private static final int URI_SUMMARY = 2;
+ private static final String QUERY_CHAT_TABLE = " SELECT * FROM " + CHAT_TABLE_NAME;
+
+ static {
+ URI_MATCHER.addURI(AUTHORITY, "chat", URI_CHAT);
+ URI_MATCHER.addURI(AUTHORITY, "summary", URI_SUMMARY);
+ }
+
+ private RcsDatabaseHelper mRcsHelper;
+
+ @Override
+ public boolean onCreate() {
+ mRcsHelper = new RcsDatabaseHelper(getContext());
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ SQLiteDatabase db = mRcsHelper.getReadableDatabase();
+ int match = URI_MATCHER.match(uri);
+
+ Log.d(TAG, "Query URI: " + match);
+ switch (match) {
+ case URI_CHAT:
+ qb.setTables(CHAT_TABLE_NAME);
+ return qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
+ case URI_SUMMARY:
+ qb.setTables(SUMMARY_TABLE_NAME);
+ return qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
+ default:
+ Log.e(TAG, "no such uri");
+ return null;
+ }
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues contentValues) {
+ SQLiteDatabase db = mRcsHelper.getWritableDatabase();
+ int match = URI_MATCHER.match(uri);
+ long id;
+ switch (match) {
+ case URI_CHAT:
+ id = db.insert(CHAT_TABLE_NAME, "", contentValues);
+ break;
+ case URI_SUMMARY:
+ id = db.insert(SUMMARY_TABLE_NAME, "", contentValues);
+ break;
+ default:
+ Log.e(TAG, "no such uri");
+ throw new SQLException("no such uri");
+ }
+ if (id > 0) {
+ Uri msgUri = Uri.withAppendedPath(uri, String.valueOf(id));
+ getContext().getContentResolver().notifyChange(uri, null);
+ Log.i(TAG, "msgUri:" + msgUri);
+ return msgUri;
+ } else {
+ throw new SQLException("fail to add chat message");
+ }
+ }
+
+ @Override
+ public int delete(Uri uri, String s, String[] strings) {
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues contentValues, String selection,
+ String[] selectionArgs) {
+ SQLiteDatabase db = mRcsHelper.getWritableDatabase();
+ int match = URI_MATCHER.match(uri);
+ int result = 0;
+ String tableName = "";
+ switch (match) {
+ case URI_CHAT:
+ tableName = CHAT_TABLE_NAME;
+ break;
+ case URI_SUMMARY:
+ tableName = SUMMARY_TABLE_NAME;
+ break;
+ }
+ if (!TextUtils.isEmpty(tableName)) {
+ result = db.updateWithOnConflict(tableName, contentValues,
+ selection, selectionArgs, SQLiteDatabase.CONFLICT_REPLACE);
+ getContext().getContentResolver().notifyChange(uri, null);
+ Log.d(TAG, "Update uri: " + match + " result: " + result);
+ } else {
+ Log.d(TAG, "Update. Not support URI.");
+ }
+ return result;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ /** Define columns for the chat table. */
+ public static class RcsColumns implements BaseColumns {
+ public static final String SRC_PHONE_NUMBER = "source_phone_number";
+ public static final String DEST_PHONE_NUMBER = "destination_phone_number";
+ public static final String CHAT_MESSAGE = "chat_message";
+ public static final String MSG_TIMESTAMP = "msg_timestamp";
+ public static final String IS_READ = "is_read";
+ public static final String RESULT = "result";
+ }
+
+ /** Define columns for the summary table. */
+ public static class SummaryColumns implements BaseColumns {
+ public static final String REMOTE_PHONE_NUMBER = "remote_phone_number";
+ public static final String LATEST_MESSAGE = "latest_message";
+ public static final String MSG_TIMESTAMP = "msg_timestamp";
+ public static final String IS_READ = "is_read";
+ }
+
+ /** Database helper */
+ public static final class RcsDatabaseHelper extends SQLiteOpenHelper {
+ public static final String SQL_CREATE_RCS_TABLE = "CREATE TABLE "
+ + CHAT_TABLE_NAME
+ + " ("
+ + RcsColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + RcsColumns.SRC_PHONE_NUMBER + " Text DEFAULT NULL, "
+ + RcsColumns.DEST_PHONE_NUMBER + " Text DEFAULT NULL, "
+ + RcsColumns.CHAT_MESSAGE + " Text DEFAULT NULL, "
+ + RcsColumns.MSG_TIMESTAMP + " LONG DEFAULT NULL, "
+ + RcsColumns.IS_READ + " BOOLEAN DEFAULT false, "
+ + RcsColumns.RESULT + " BOOLEAN DEFAULT true);";
+
+ public static final String SQL_CREATE_SUMMARY_TABLE = "CREATE TABLE "
+ + SUMMARY_TABLE_NAME
+ + " ("
+ + SummaryColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + SummaryColumns.REMOTE_PHONE_NUMBER + " Text DEFAULT NULL, "
+ + SummaryColumns.LATEST_MESSAGE + " Text DEFAULT NULL, "
+ + SummaryColumns.MSG_TIMESTAMP + " LONG DEFAULT NULL, "
+ + SummaryColumns.IS_READ + " BOOLEAN DEFAULT false);";
+ private static final String DB_NAME = "RcsDatabase";
+
+ public RcsDatabaseHelper(Context context) {
+ super(context, DB_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(SQL_CREATE_RCS_TABLE);
+ db.execSQL(SQL_CREATE_SUMMARY_TABLE);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.d(TAG, "DB upgrade from " + oldVersion + " to " + newVersion);
+ db.beginTransaction();
+ try {
+ switch (oldVersion) {
+ case 1:
+ upgradeDatabaseToVersion2(db);
+ break;
+ default: // fall out
+ }
+ db.setTransactionSuccessful();
+ } catch (Exception ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ private static void upgradeDatabaseToVersion2(SQLiteDatabase db) {
+ try {
+ Log.d(TAG, "upgradeDatabaseToVersion2");
+ String alterTable = "ALTER TABLE " + CHAT_TABLE_NAME + " ADD COLUMN ";
+ db.execSQL(alterTable + RcsColumns.RESULT + " BOOLEAN DEFAULT true");
+ } catch (SQLiteException e) {
+ Log.w(TAG, "[upgradeDatabaseToVersion10] Exception adding column: " + e);
+ }
+ }
+ }
+}
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..14d3b9c
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/NumberUtils.java
@@ -0,0 +1,46 @@
+/*
+ * 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.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 {
+
+ /**
+ * 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();
+ 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
new file mode 100644
index 0000000..215c692
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/Android.bp
@@ -0,0 +1,45 @@
+
+
+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",
+
+ srcs: ["src/com/android/libraries/rcs/**/*.java"],
+
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.concurrent_concurrent-futures",
+ "guava",
+ "nist-sip",
+ ],
+
+ libs: [
+ "auto_value_annotations",
+ "org.apache.http.legacy",
+ ],
+
+ plugins: [
+ "auto_value_plugin",
+ ],
+
+ sdk_version: "system_current",
+ min_sdk_version: "30",
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/AndroidManifest.xml b/testapps/TestRcsApp/aosp_test_rcsclient/AndroidManifest.xml
new file mode 100644
index 0000000..b167aa8
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/AndroidManifest.xml
+**
+** Copyright 2020, 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.
+*/
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.libraries.rcs.simpleclient"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk
+ android:minSdkVersion="21"
+ android:targetSdkVersion="23" />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
+</manifest>
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/LICENSE b/testapps/TestRcsApp/aosp_test_rcsclient/LICENSE
new file mode 100644
index 0000000..b9b9d2a
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/LICENSE
@@ -0,0 +1,176 @@
+Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
\ No newline at end of file
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/protocol/msrp/MsrpChunkTest.java b/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpChunkTest.java
new file mode 100644
index 0000000..90f1714
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpChunkTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 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.msrp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpChunk.Continuation;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class MsrpChunkTest {
+
+ private static MsrpChunk writeAndReadChunk(MsrpChunk chunk) throws IOException {
+ ByteArrayOutputStream bo = new ByteArrayOutputStream();
+ MsrpSerializer.serialize(bo, chunk);
+
+ ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
+ return MsrpParser.parse(bi);
+ }
+
+ @Test
+ public void whenSerializeParseRequest_success() throws IOException {
+ MsrpChunk chunk = MsrpChunk.newBuilder()
+ .method(MsrpChunk.Method.SEND)
+ .addHeader("To-Path", "msrp://123.1.11:9/testreceiver;tcp")
+ .addHeader("From-Path", "msrp://123.1.11:9/testsender;tcp")
+ .addHeader("Byte-Range", "1-*/*")
+ .transactionId("123123")
+ .addHeader("Content-Type", "text/plain")
+ .content("Hallo Welt\r\n".getBytes(UTF_8))
+ .continuation(Continuation.COMPLETE)
+ .build();
+
+ MsrpChunk chunk2 = writeAndReadChunk(chunk);
+
+ assertThat(chunk2).isEqualTo(chunk);
+ }
+
+ @Test
+ public void whenSerializeParseEmptyRequest_success() throws IOException {
+ MsrpChunk chunk = MsrpChunk.newBuilder()
+ .method(MsrpChunk.Method.SEND)
+ .transactionId("testtransaction")
+ .addHeader("To-Path", "msrp://123.1.11:9/testreceiver;tcp")
+ .addHeader("From-Path", "msrp://123.1.11:9/testsender;tcp")
+ .continuation(Continuation.COMPLETE)
+ .build();
+
+ MsrpChunk chunk2 = writeAndReadChunk(chunk);
+
+ assertThat(chunk2).isEqualTo(chunk);
+ }
+
+ @Test
+ public void whenSerializeParseResponse_success() throws IOException {
+ MsrpChunk chunk = MsrpChunk.newBuilder()
+ .responseCode(200)
+ .responseReason("OK")
+ .transactionId("testtransaction")
+ .addHeader("To-Path", "msrp://123.1.11:9/testreceiver;tcp")
+ .addHeader("From-Path", "msrp://123.1.11:9/testsender;tcp")
+ .continuation(Continuation.COMPLETE)
+ .build();
+
+ MsrpChunk chunk2 = writeAndReadChunk(chunk);
+
+ assertThat(chunk2).isEqualTo(chunk);
+ }
+
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSessionTest.java b/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSessionTest.java
new file mode 100644
index 0000000..dc60d37
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSessionTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 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.msrp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpChunk.Continuation;
+
+import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ *
+ */
+@RunWith(AndroidJUnit4.class)
+public class MsrpSessionTest {
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private Socket socket;
+
+ @Before
+ public void setUp() throws IOException {
+ PipedInputStream input = new PipedInputStream();
+ when(socket.getInputStream()).thenReturn(input);
+ when(socket.getOutputStream()).thenReturn(new PipedOutputStream(input));
+ when(socket.isConnected()).thenReturn(true);
+ }
+
+ @Test
+ public void foo() throws IOException, ExecutionException, InterruptedException {
+
+ AtomicReference<MsrpChunk> receivedRequest = new AtomicReference<>();
+
+ final MsrpSession session =
+ new MsrpSession(
+ socket,
+ (m) -> {
+ receivedRequest.set(m);
+ });
+
+ MsrpChunk request = generateRequest();
+ Future<MsrpChunk> future = session.send(request);
+
+ Executors.newSingleThreadExecutor().execute(session::run);
+
+ MsrpChunk response = future.get();
+
+ assertThat(request).isEqualTo(receivedRequest.get());
+ assertThat(response).isEqualTo(generateSuccessResponse());
+ }
+
+ private MsrpChunk generateRequest() {
+ return MsrpChunk.newBuilder()
+ .transactionId("txid")
+ .method(MsrpChunk.Method.SEND)
+ .addHeader(MsrpConstants.HEADER_TO_PATH, "msrp://test:1234/sessionA;tcp")
+ .addHeader(MsrpConstants.HEADER_FROM_PATH, "msrp://test:1234/sessionB;tcp")
+ .addHeader(MsrpConstants.HEADER_BYTE_RANGE, "1-*/*")
+ .addHeader(MsrpConstants.HEADER_MESSAGE_ID, "abcde")
+ .addHeader(MsrpConstants.HEADER_CONTENT_TYPE, "text/plain")
+ .content("Hallo Welt\r\n".getBytes(StandardCharsets.UTF_8))
+ .continuation(Continuation.COMPLETE)
+ .build();
+ }
+
+ private MsrpChunk generateSuccessResponse() {
+ return MsrpChunk.newBuilder()
+ .transactionId("txid")
+ .responseCode(200)
+ .responseReason("OK")
+ .addHeader(MsrpConstants.HEADER_TO_PATH, "msrp://test:1234/sessionB;tcp")
+ .addHeader(MsrpConstants.HEADER_FROM_PATH, "msrp://test:1234/sessionA;tcp")
+ .continuation(Continuation.COMPLETE)
+ .build();
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/protocol/sdp/SimpleSdpMessageTest.java b/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/protocol/sdp/SimpleSdpMessageTest.java
new file mode 100644
index 0000000..4b5f31a
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/protocol/sdp/SimpleSdpMessageTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 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.sdp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import java.io.ByteArrayInputStream;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SimpleSdpMessageTest {
+ private static final String SAMPLE_SDP_REGEX =
+ "v=0\r\n"
+ + "o=TestRcsClient .+ .+ IN IP4 192.168.1.1\r\n"
+ + "s=-\r\n"
+ + "c=IN IP4 192.168.1.1\r\n"
+ + "t=0 0\r\n"
+ + "m=message 9 TCP/MSRP \\*\r\n"
+ + "a=path:msrp://192.168.1.1:9/.+;tcp\r\n"
+ + "a=setup:active\r\n"
+ + "a=accept-types:message/cpim application/im-iscomposing\\+xml\r\n"
+ + "a=accept-wrapped-types:text/plain message/imdn\\+xml"
+ + " application/vnd.gsma.rcs-ft-http\\+xml application/vnd.gsma"
+ + ".rcspushlocation\\+xml\r\n"
+ + "a=sendrecv\r\n";
+
+ @Test
+ public void createForMsrp_returnExpectedSdpString() {
+ SimpleSdpMessage sdp =
+ SdpUtils.createSdpForMsrp(/* address= */ "192.168.1.1", /* isTls= */ false);
+
+ assertThat(sdp.encode()).matches(SAMPLE_SDP_REGEX);
+ }
+
+ @Test
+ public void encodeAndParse_shouldBeEqualToOriginal() throws Exception {
+ SimpleSdpMessage original =
+ SdpUtils.createSdpForMsrp(/* address= */ "192.168.1.1", /* isTls= */ false);
+
+ SimpleSdpMessage parsedSdp =
+ SimpleSdpMessage.parse(new ByteArrayInputStream(original.encode().getBytes(UTF_8)));
+
+ assertThat(parsedSdp).isEqualTo(original);
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/protocol/sip/SipUtilsTest.java b/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/protocol/sip/SipUtilsTest.java
new file mode 100644
index 0000000..be043f5
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/protocol/sip/SipUtilsTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2020 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.sip;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.collect.Lists;
+
+import gov.nist.javax.sip.message.SIPRequest;
+
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SipUtilsTest {
+ private static final String LOCAL_URI = "tel:+1234567890";
+ private static final String REMOTE_URI = "tel:+1234567891";
+ private static final String CONVERSATION_ID = "abcd-1234";
+
+ SipSessionConfiguration configuration =
+ new SipSessionConfiguration() {
+ @Override
+ public long getVersion() {
+ return 0;
+ }
+
+ @Override
+ public String getOutboundProxyAddr() {
+ return "3001:4870:e00b:5e94:21b8:8d20:c425:5e6c";
+ }
+
+ @Override
+ public int getOutboundProxyPort() {
+ return 5060;
+ }
+
+ @Override
+ public String getLocalIpAddress() {
+ return "2001:4870:e00b:5e94:21b8:8d20:c425:5e6c";
+ }
+
+ @Override
+ public int getLocalPort() {
+ return 5060;
+ }
+
+ @Override
+ public String getSipTransport() {
+ return "TCP";
+ }
+
+ @Override
+ public String getPublicUserIdentity() {
+ return "sip:+1234567890@foo.bar";
+ }
+
+ @Override
+ public String getDomain() {
+ return "foo.bar";
+ }
+
+ @Override
+ public List<String> getAssociatedUris() {
+ return Lists.newArrayList(LOCAL_URI, "sip:+1234567890@foo.bar");
+ }
+
+ @Override
+ public String getSecurityVerifyHeader() {
+ return "ipsec-3gpp;q=0.5;alg=hmac-sha-1-96;prot=esp;mod=trans;ealg=null;"
+ + "spi-c=983227540;spi-s=2427966379;port-c=65528;port-s=65529";
+ }
+
+ @Override
+ public List<String> getServiceRouteHeaders() {
+ return Lists.newArrayList();
+ }
+
+ @Override
+ public String getContactUser() {
+ return "abcd-efgh";
+ }
+
+ @Override
+ public String getImei() {
+ return "35293211-111080-0";
+ }
+
+ @Override
+ public String getPaniHeader() {
+ return null;
+ }
+
+ @Override
+ public String getPlaniHeader() {
+ return null;
+ }
+
+ @Override
+ public int getMaxPayloadSizeOnUdp() {
+ return 0;
+ }
+ };
+
+ @Test
+ public void buildInvite_returnExpectedInviteMessage() throws Exception {
+ SIPRequest request = SipUtils.buildInvite(configuration, REMOTE_URI, CONVERSATION_ID);
+
+ assertThat(request.getRequestURI().toString()).isEqualTo(REMOTE_URI);
+ assertThat(request.getFrom().getAddress().getURI().toString()).isEqualTo(LOCAL_URI);
+ assertThat(request.getTo().getAddress().getURI().toString()).isEqualTo(REMOTE_URI);
+ assertThat(request.hasHeader("Conversation-ID")).isTrue();
+ assertThat(request.hasHeader("Contribution-ID")).isTrue();
+ assertThat(request.hasHeader("Accept-Contact")).isTrue();
+ assertThat(request.hasHeader("Security-Verify")).isTrue();
+ }
+
+ @Test
+ public void buildInvite_sizeIsGreaterThanMaxPayloadSize_transportShouldBeTcp()
+ throws Exception {
+ SIPRequest request = SipUtils.buildInvite(configuration, REMOTE_URI, CONVERSATION_ID);
+
+ // The size is always greater than maxPayloadSizeOnUdp = 0
+ assertThat(request.getTopmostVia().getTransport()).isEqualTo("TCP");
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningControllerTest.java b/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningControllerTest.java
new file mode 100644
index 0000000..b9065de
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningControllerTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 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.provisioning;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.support.annotation.RequiresPermission;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import java.util.Optional;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class StaticConfigProvisioningControllerTest {
+
+ private static final byte[] CONFIG_DATA = "<xml></xml>".getBytes();
+
+ private StaticConfigProvisioningController client;
+ private Optional<byte[]> configXmlData = Optional.empty();
+ private ProvisioningStateChangeCallback cb =
+ configXml -> configXmlData = Optional.ofNullable(configXml);
+
+ @Before
+ public void setUp() {
+ client = StaticConfigProvisioningController.createForSubscriptionId(/*subscriptionId=*/ 2);
+ client.onConfigurationChange(cb);
+ }
+
+ @Test
+ @RequiresPermission(value = "Manifest.permission.READ_PRIVILEGED_PHONE_STATE")
+ public void whenGetConfigCalled_returnsCorrectXmlData() throws Exception {
+ client.register();
+ client.getProvisioningManager().getCallbackForTests().onConfigurationChanged(CONFIG_DATA);
+
+ assertThat(client.isRcsVolteSingleRegistrationCapable()).isTrue();
+ assertThat(client.getLatestConfiguration()).isEqualTo(CONFIG_DATA);
+ assertThat(configXmlData.get()).isEqualTo(CONFIG_DATA);
+ client.unRegister();
+ }
+
+ @Test
+ @RequiresPermission(value = "Manifest.permission.READ_PRIVILEGED_PHONE_STATE")
+ public void whenGetConfigCalled_throwsErrorWhenNoConfigPresent() throws Exception {
+ client.register();
+ client.triggerReconfiguration();
+
+ assertThat(client.isRcsVolteSingleRegistrationCapable()).isTrue();
+ assertThrows(IllegalStateException.class, () -> client.getLatestConfiguration());
+ assertThat(configXmlData.isPresent()).isFalse();
+
+ client.unRegister();
+ }
+
+ @Test
+ @RequiresPermission(value = "Manifest.permission.READ_PRIVILEGED_PHONE_STATE")
+ public void unRegister_failsWhenCalledWithoutRegister() {
+ assertThrows(IllegalStateException.class, () -> client.unRegister());
+ }
+}
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
new file mode 100644
index 0000000..2723940
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/service/chat/SimpleChatSessionTest.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2020 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.service.chat;
+
+import static com.google.common.labs.truth.FutureSubject.assertThat;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.libraries.rcs.simpleclient.SimpleRcsClientContext;
+import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpManager;
+import com.android.libraries.rcs.simpleclient.protocol.sdp.SdpUtils;
+import com.android.libraries.rcs.simpleclient.protocol.sip.SipSession;
+import com.android.libraries.rcs.simpleclient.protocol.sip.SipSessionConfiguration;
+import com.android.libraries.rcs.simpleclient.protocol.sip.SipSessionListener;
+
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+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;
+
+@RunWith(AndroidJUnit4.class)
+public class SimpleChatSessionTest {
+ private static final String LOCAL_URI = "tel:+1234567890";
+ private static final String REMOTE_URI = "tel:+1234567891";
+ private final MsrpManager msrpManager =
+ new MsrpManager(ApplicationProvider.getApplicationContext());
+ SipSessionConfiguration configuration =
+ new SipSessionConfiguration() {
+ @Override
+ public long getVersion() {
+ return 0;
+ }
+
+ @Override
+ public String getOutboundProxyAddr() {
+ return "3001:4870:e00b:5e94:21b8:8d20:c425:5e6c";
+ }
+
+ @Override
+ public int getOutboundProxyPort() {
+ return 5060;
+ }
+
+ @Override
+ public String getLocalIpAddress() {
+ return "2001:4870:e00b:5e94:21b8:8d20:c425:5e6c";
+ }
+
+ @Override
+ public int getLocalPort() {
+ return 5060;
+ }
+
+ @Override
+ public String getSipTransport() {
+ return "TCP";
+ }
+
+ @Override
+ public String getPublicUserIdentity() {
+ return "sip:+1234567890@foo.bar";
+ }
+
+ @Override
+ public String getDomain() {
+ return "foo.bar";
+ }
+
+ @Override
+ public List<String> getAssociatedUris() {
+ return Lists.newArrayList(LOCAL_URI, "sip:+1234567890@foo.bar");
+ }
+
+ @Override
+ public String getSecurityVerifyHeader() {
+ return "ipsec-3gpp;q=0.5;alg=hmac-sha-1-96;prot=esp;mod=trans;ealg=null;"
+ + "spi-c=983227540;spi-s=2427966379;port-c=65528;port-s=65529";
+ }
+
+ @Override
+ public List<String> getServiceRouteHeaders() {
+ return Lists.newArrayList();
+ }
+
+ @Override
+ public String getContactUser() {
+ return "abcd-efgh";
+ }
+
+ @Override
+ public String getImei() {
+ return "35293211-111080-0";
+ }
+
+ @Override
+ public String getPaniHeader() {
+ return "IEEE-802.11;i-wlan-node-id=PANIC01EB5B0";
+ }
+
+ @Override
+ 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(
+ /* provisioningController= */ null,
+ /* registrationController= */ null,
+ /* imsService= */ null,
+ new SipSession() {
+ @Override
+ public SipSessionConfiguration getSessionConfiguration() {
+ return configuration;
+ }
+
+ @Override
+ public ListenableFuture<Boolean> send(Message message) {
+ return Futures.immediateFuture(true);
+ }
+
+ @Override
+ public void setSessionListener(SipSessionListener listener) {
+ }
+ });
+
+ @Test
+ public void start_reply200_returnSuccessfulFuture() throws Exception {
+ SimpleChatSession session =
+ new SimpleChatSession(
+ context,
+ new MinimalCpmChatService(ApplicationProvider.getApplicationContext()) {
+ @Override
+ ListenableFuture<Boolean> sendSipRequest(SIPRequest msg,
+ SimpleChatSession session) {
+ if (msg.getMethod().equals(Request.INVITE)) {
+ SIPResponse response = msg.createResponse(/* statusCode= */
+ 200);
+ response.setMessageContent(
+ /* type= */ "application",
+ /* subType= */ "sdp",
+ SdpUtils.createSdpForMsrp(/* address= */
+ "127.0.0.1", /* isTls= */ false)
+ .encode());
+ session.receiveMessage(response);
+ }
+ return Futures.immediateFuture(true);
+ }
+ },
+ msrpManager);
+
+ // session.start should return the successful void future.
+ assertThat(session.start(REMOTE_URI)).whenDone().isSuccessful();
+ }
+
+ @Test
+ public void start_reply404_returnFailedFuture() throws Exception {
+ SimpleChatSession session =
+ new SimpleChatSession(
+ context,
+ new MinimalCpmChatService(ApplicationProvider.getApplicationContext()) {
+ @Override
+ ListenableFuture<Boolean> sendSipRequest(SIPRequest msg,
+ SimpleChatSession session) {
+ if (msg.getMethod().equals(Request.INVITE)) {
+ SIPResponse response = msg.createResponse(/* statusCode= */
+ 404);
+ session.receiveMessage(response);
+ }
+ return Futures.immediateFuture(true);
+ }
+ },
+ msrpManager);
+
+ // session.start should return the failed future with the exception.
+ assertThat(session.start(REMOTE_URI)).whenDone().isFailedWith(ChatServiceException.class);
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/SimpleRcsClient.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/SimpleRcsClient.java
new file mode 100644
index 0000000..0469bc0
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/SimpleRcsClient.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2020 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;
+
+import android.os.Build.VERSION_CODES;
+import android.telephony.ims.ImsException;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.libraries.rcs.simpleclient.protocol.sip.SipSession;
+import com.android.libraries.rcs.simpleclient.provisioning.ProvisioningController;
+import com.android.libraries.rcs.simpleclient.registration.RegistrationController;
+import com.android.libraries.rcs.simpleclient.registration.RegistrationStateChangeCallback;
+import com.android.libraries.rcs.simpleclient.service.ImsService;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Simple RCS client implementation.
+ *
+ * State is covered by a context instance.
+ */
+@RequiresApi(api = VERSION_CODES.R)
+public class SimpleRcsClient {
+ private static final String TAG = SimpleRcsClient.class.getSimpleName();
+ private final AtomicReference<State> state = new AtomicReference<>(State.NEW);
+ private ProvisioningController provisioningController;
+ private RegistrationController registrationController;
+ private ImsService imsService;
+ private SimpleRcsClientContext context;
+ private StateChangedCallback stateChangedCallback;
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public SimpleRcsClientContext getContext() {
+ return context;
+ }
+
+ public void start() {
+ provision();
+ }
+
+ public void stop() {
+ Log.i(TAG, "stop..");
+ registrationController.deregister();
+ provisioningController.unRegister();
+ provisioningController = null;
+ registrationController = null;
+ imsService = null;
+ }
+
+ public void onStateChanged(StateChangedCallback cb) {
+ this.stateChangedCallback = cb;
+ }
+
+ private boolean enterState(State expected, State newState) {
+ boolean result = state.compareAndSet(expected, newState);
+
+ if (result && stateChangedCallback != null) {
+ try {
+ stateChangedCallback.notifyStateChange(expected, newState);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception on calling state change callback", e);
+ }
+ }
+ Log.i(TAG, "expected:" + expected + " new:" + newState + " res:" + result);
+ return result;
+ }
+
+ private void provision() {
+ if (!enterState(State.NEW, State.PROVISIONING)) {
+ return;
+ }
+ provisioningController.onConfigurationChange(configXml -> {
+ register();
+ });
+ try {
+ provisioningController.triggerProvisioning();
+ } catch (ImsException e) {
+ // TODO: ...
+ }
+ }
+
+ private void register() {
+ if (!enterState(State.PROVISIONING, State.REGISTERING)) {
+ return;
+ }
+
+ registrationController.register(imsService,
+ new RegistrationStateChangeCallback() {
+ @Override
+ public void notifyRegStateChanged(ImsService imsService) {
+
+ }
+
+ @Override
+ public void onSuccess(SipSession sipSession) {
+ Log.i(TAG, "onSuccess");
+ registered(sipSession);
+ }
+
+ @Override
+ public void onFailure(String reason) {
+ Log.i(TAG, "onFailure reason:" + reason);
+ notRegistered();
+ }
+ });
+ }
+
+ private void registered(SipSession session) {
+ if (state.get().equals(State.REGISTERING) || state.get().equals(State.NOT_REGISTERED)) {
+ enterState(state.get(), State.REGISTERED);
+ }
+
+ context = new SimpleRcsClientContext(provisioningController, registrationController,
+ imsService,
+ session);
+
+ imsService.start(context);
+ }
+
+ private void notRegistered() {
+ enterState(State.REGISTERED, State.NOT_REGISTERED);
+ }
+
+ /**
+ * Possible client states.
+ */
+ public enum State {
+ NEW,
+ PROVISIONING,
+ REGISTERING,
+ REGISTERED,
+ NOT_REGISTERED,
+ }
+
+ /**
+ * Builder for creating new SimpleRcsClient instances.
+ */
+ public static class Builder {
+
+ private ProvisioningController provisioningController;
+ private RegistrationController registrationController;
+ private ImsService imsService;
+
+ public Builder provisioningController(ProvisioningController controller) {
+ this.provisioningController = controller;
+ return this;
+ }
+
+ public Builder registrationController(RegistrationController controller) {
+ this.registrationController = controller;
+ return this;
+ }
+
+ public Builder imsService(ImsService imsService) {
+ this.imsService = imsService;
+ return this;
+ }
+
+ public SimpleRcsClient build() {
+ SimpleRcsClient client = new SimpleRcsClient();
+ client.registrationController = registrationController;
+ client.provisioningController = provisioningController;
+ client.imsService = imsService;
+
+ return client;
+ }
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/SimpleRcsClientContext.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/SimpleRcsClientContext.java
new file mode 100644
index 0000000..1be6403
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/SimpleRcsClientContext.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 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;
+
+import com.android.libraries.rcs.simpleclient.protocol.sip.SipSession;
+import com.android.libraries.rcs.simpleclient.provisioning.ProvisioningController;
+import com.android.libraries.rcs.simpleclient.registration.RegistrationController;
+import com.android.libraries.rcs.simpleclient.service.ImsService;
+
+/**
+ * State container for a {@link SimpleRcsClient} instance.
+ */
+public class SimpleRcsClientContext {
+
+ private final ProvisioningController provisioningController;
+
+ private final RegistrationController registrationController;
+
+ private final ImsService imsService;
+
+ private final SipSession sipSession;
+
+ public SimpleRcsClientContext(
+ ProvisioningController provisioningController,
+ RegistrationController registrationController,
+ ImsService imsService,
+ SipSession sipSession) {
+ this.provisioningController = provisioningController;
+ this.registrationController = registrationController;
+ this.imsService = imsService;
+ this.sipSession = sipSession;
+ }
+
+ public ProvisioningController getProvisioningController() {
+ return provisioningController;
+ }
+
+ public RegistrationController getRegistrationController() {
+ return registrationController;
+ }
+
+ public ImsService getImsService() {
+ return imsService;
+ }
+
+ public SipSession getSipSession() {
+ return sipSession;
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/StateChangedCallback.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/StateChangedCallback.java
new file mode 100644
index 0000000..87f6566
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/StateChangedCallback.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 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;
+
+import com.android.libraries.rcs.simpleclient.SimpleRcsClient.State;
+
+/**
+ * Callback for processing state changes.
+ */
+public interface StateChangedCallback {
+ void notifyStateChange(State oldState, State newState);
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/FileTransferController.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/FileTransferController.java
new file mode 100644
index 0000000..f6548d8
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/FileTransferController.java
@@ -0,0 +1,47 @@
+/*
+ * 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.filetransfer;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** File transfer functionality. */
+public interface FileTransferController {
+
+ /**
+ * Downloads a file from the content server.
+ *
+ * @param fileUrl http URL to the file content on the server.
+ * @return the response for the file download.
+ */
+ ListenableFuture<InputStream> downloadFile(String fileUrl);
+
+ /**
+ * Uploads a file to the content server.
+ *
+ * @param transactionId the transaction id of the file upload.
+ * @param file the file to be uploaded.
+ * @return the XML response for the file upload, as defined in RCC.07.0-v19.0. This can then be
+ * parsed by the FileInfoParse to get the URL to be used for the download.
+ */
+ ListenableFuture<String> uploadFile(
+ String transactionId, File file)
+ throws IOException;
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/FileTransferControllerImpl.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/FileTransferControllerImpl.java
new file mode 100644
index 0000000..dde340c
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/FileTransferControllerImpl.java
@@ -0,0 +1,49 @@
+/*
+ * 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.filetransfer;
+
+import com.android.libraries.rcs.simpleclient.filetransfer.requestexecutor.HttpRequestExecutor;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** FileTransferController implementation. */
+public class FileTransferControllerImpl implements FileTransferController {
+
+ private final FileUploadController fileUploadController;
+
+ public FileTransferControllerImpl(HttpRequestExecutor requestExecutor,
+ String contentServerUri, String carrierName) {
+ this.fileUploadController = new FileUploadController(requestExecutor, contentServerUri,
+ carrierName);
+ }
+
+ @Override
+ public ListenableFuture<InputStream> downloadFile(String fileUrl) {
+ throw new UnsupportedOperationException("File download not supported");
+ }
+
+ @Override
+ public ListenableFuture<String> uploadFile(
+ String transactionId, File file)
+ throws IOException {
+ return fileUploadController.uploadFile(transactionId, file);
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/FileUploadController.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/FileUploadController.java
new file mode 100644
index 0000000..0d45828
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/FileUploadController.java
@@ -0,0 +1,274 @@
+/*
+ * 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.filetransfer;
+
+import android.net.Uri;
+import android.os.Build;
+import android.util.Log;
+
+import com.android.internal.http.multipart.FilePart;
+import com.android.internal.http.multipart.MultipartEntity;
+import com.android.internal.http.multipart.Part;
+import com.android.internal.http.multipart.StringPart;
+import com.android.libraries.rcs.simpleclient.filetransfer.requestexecutor.HttpRequestExecutor;
+
+import com.google.common.io.ByteStreams;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.auth.AUTH;
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.MalformedChallengeException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.params.AuthPolicy;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.impl.auth.DigestScheme;
+import org.apache.http.impl.auth.RFC2617Scheme;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.concurrent.Executors;
+
+/** File upload functionality. */
+final class FileUploadController {
+
+ private static final String TAG = "FileUploadController";
+ private static final String ATTRIBUTE_PREEMPTIVE_AUTH = "preemptive-auth";
+ private static final String PARAM_NONCE = "nonce";
+ private static final String PARAM_REALM = "realm";
+ private static final String FILE_PART_NAME = "File";
+ private static final String TRANSFER_ID_PART_NAME = "tid";
+ private static final String CONTENT_TYPE = "text/plain";
+ private static final String THREE_GPP_GBA = "3gpp-gba";
+ private static final int HTTPS_PORT = 443;
+
+ private final HttpRequestExecutor requestExecutor;
+ private final String contentServerUri;
+ private final ListeningExecutorService executor =
+ MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(4));
+ private String mCarrierName;
+
+ FileUploadController(HttpRequestExecutor requestExecutor, String contentServerUri,
+ String carrierName) {
+ this.requestExecutor = requestExecutor;
+ this.contentServerUri = contentServerUri;
+ this.mCarrierName = carrierName;
+ }
+
+ public ListenableFuture<String> uploadFile(
+ String transactionId, File file) {
+ DefaultHttpClient httpClient = getSecureHttpClient();
+
+ Log.i(TAG, "sendEmptyPost");
+ // Send an empty post.
+ ListenableFuture<HttpResponse> initialResponseFuture = sendEmptyPost(httpClient);
+
+ BasicHttpContext httpContext = new BasicHttpContext();
+ ListenableFuture<AuthScheme> prepareAuthFuture =
+ Futures.transform(
+ initialResponseFuture,
+ initialResponse -> {
+ Log.i(TAG, "Response for the empty post: "
+ + initialResponse.getStatusLine());
+ if (initialResponse.getStatusLine().getStatusCode()
+ != HttpURLConnection.HTTP_UNAUTHORIZED) {
+ throw new IllegalArgumentException(
+ "Expected HTTP_UNAUTHORIZED, but got "
+ + initialResponse.getStatusLine());
+ }
+ try {
+ initialResponse.getEntity().consumeContent();
+ } catch (IOException e) {
+ throw new IllegalArgumentException(e);
+ }
+
+ // Override nonce and realm in the HTTP context.
+ RFC2617Scheme authScheme = createAuthScheme(initialResponse);
+ httpContext.setAttribute(ATTRIBUTE_PREEMPTIVE_AUTH, authScheme);
+ return authScheme;
+ },
+ executor);
+
+ // Executing the post with credentials.
+ return Futures.transformAsync(
+ prepareAuthFuture,
+ authScheme ->
+ executeAuthenticatedPost(
+ httpClient, httpContext, authScheme, transactionId, file),
+ executor);
+ }
+
+ private RFC2617Scheme createAuthScheme(HttpResponse initialResponse) {
+ if (!initialResponse.containsHeader(AUTH.WWW_AUTH)) {
+ throw new IllegalArgumentException(
+ AUTH.WWW_AUTH + " header not found in the original response.");
+ }
+
+ Header authHeader = initialResponse.getFirstHeader(AUTH.WWW_AUTH);
+ String scheme = authHeader.getValue();
+
+ if (scheme.contains(AuthPolicy.DIGEST)) {
+ DigestScheme digestScheme = new DigestScheme();
+ try {
+ digestScheme.processChallenge(authHeader);
+ } catch (MalformedChallengeException e) {
+ throw new IllegalArgumentException(e);
+ }
+ return digestScheme;
+ } else {
+ throw new IllegalArgumentException("Unable to create authentication scheme " + scheme);
+ }
+ }
+
+ private DefaultHttpClient getSecureHttpClient() {
+ SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
+ Uri uri = Uri.parse(contentServerUri);
+ int port = uri.getPort();
+ if (port <= 0) {
+ port = HTTPS_PORT;
+ }
+
+ Scheme scheme = new Scheme("https", socketFactory, port);
+ DefaultHttpClient httpClient = new DefaultHttpClient();
+ ClientConnectionManager manager = httpClient.getConnectionManager();
+ SchemeRegistry registry = manager.getSchemeRegistry();
+ registry.register(scheme);
+
+ return httpClient;
+ }
+
+ private ListenableFuture<HttpResponse> sendEmptyPost(HttpClient httpClient) {
+ Log.i(TAG, "Sending an empty post: ");
+ HttpPost emptyPost = new HttpPost(contentServerUri);
+ emptyPost.setHeader("User-Agent", getUserAgent());
+ return executor.submit(() -> httpClient.execute(emptyPost));
+ }
+
+ private ListenableFuture<String> executeAuthenticatedPost(
+ DefaultHttpClient httpClient,
+ HttpContext context,
+ AuthScheme authScheme,
+ String transactionId,
+ File file)
+ throws IOException {
+
+ Part[] parts = {
+ new StringPart(TRANSFER_ID_PART_NAME, transactionId),
+ new FilePart(FILE_PART_NAME, file)
+ };
+ MultipartEntity entity = new MultipartEntity(parts);
+
+ HttpPost postRequest = new HttpPost(contentServerUri);
+ postRequest.setHeader("User-Agent", getUserAgent());
+ postRequest.setEntity(entity);
+ Log.i(TAG, "Created file upload POST:" + contentServerUri);
+
+ ListenableFuture<HttpResponse> responseFuture =
+ requestExecutor.executeAuthenticatedRequest(httpClient, context, postRequest,
+ authScheme);
+
+ Futures.addCallback(
+ responseFuture,
+ new FutureCallback<HttpResponse>() {
+ @Override
+ public void onSuccess(HttpResponse response) {
+ Log.i(TAG, "onSuccess:" + response.toString());
+ Log.i(TAG, "statusLine:" + response.getStatusLine());
+ Log.i(TAG, "statusCode:" + response.getStatusLine().getStatusCode());
+ Log.i(TAG, "contentLentgh:" + response.getEntity().getContentLength());
+ Log.i(TAG, "contentType:" + response.getEntity().getContentType());
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ Log.e(TAG, "onFailure", t);
+ throw new IllegalArgumentException(t);
+ }
+ },
+ executor);
+
+ return Futures.transform(
+ responseFuture,
+ response -> {
+ try {
+ return consumeResponse(response);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(e);
+ }
+ },
+ executor);
+ }
+
+ public String consumeResponse(HttpResponse response) throws IOException {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode != HttpURLConnection.HTTP_OK) {
+ throw new IllegalArgumentException(
+ "Server responded with error code " + statusCode + "!");
+ }
+ HttpEntity responseEntity = response.getEntity();
+
+ if (responseEntity == null) {
+ throw new IOException("Did not receive a response body.");
+ }
+
+ return readResponseData(responseEntity.getContent());
+ }
+
+ public String readResponseData(InputStream inputStream) throws IOException {
+ Log.i(TAG, "readResponseData");
+ ByteArrayOutputStream data = new ByteArrayOutputStream();
+ ByteStreams.copy(inputStream, data);
+
+ data.flush();
+ Log.i(TAG, "Parsed HTTP POST response: " + data.toString());
+
+ return data.toString();
+ }
+
+ private String getUserAgent() {
+ String buildId = Build.ID;
+ String buildDate = DateTimeFormatter.ofPattern("yyyy-MM-dd")
+ .withZone(ZoneId.systemDefault())
+ .format(Instant.ofEpochMilli(Build.TIME));
+ String buildVersion = Build.VERSION.RELEASE_OR_CODENAME;
+ String deviceName = Build.DEVICE;
+ String userAgent = String.format("%s %s %s %s %s %s %s",
+ mCarrierName, buildId, buildDate, "Android", buildVersion,
+ deviceName, THREE_GPP_GBA);
+ Log.i(TAG, "UserAgent:" + userAgent);
+ return userAgent;
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java
new file mode 100644
index 0000000..008fb76
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java
@@ -0,0 +1,140 @@
+/*
+ * 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.filetransfer.requestexecutor;
+
+import android.net.Uri;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
+import android.telephony.gba.TlsParams;
+import android.telephony.gba.UaSecurityProtocolIdentifier;
+import android.util.Log;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.io.BaseEncoding;
+import com.google.common.util.concurrent.SettableFuture;
+
+import org.apache.http.auth.Credentials;
+
+import java.security.Principal;
+import java.util.concurrent.Executor;
+
+/** Provides GBA authentication credentials. */
+public class GbaAuthenticationProvider {
+
+ private static final String TAG = "GbaAuthenticationProvider";
+ private final TelephonyManager telephonyManager;
+ private final String contentServerUrl;
+ private final Executor executor;
+
+ public GbaAuthenticationProvider(
+ TelephonyManager telephonyManager, String contentServerUrl, Executor executor) {
+ this.telephonyManager = telephonyManager;
+ this.contentServerUrl = contentServerUrl;
+ this.executor = executor;
+ }
+
+ public SettableFuture<Credentials> provideCredentials(boolean forceBootstrapping) {
+ SettableFuture<Credentials> credentialsFuture = SettableFuture.create();
+
+ UaSecurityProtocolIdentifier.Builder builder =
+ new UaSecurityProtocolIdentifier.Builder();
+ try {
+ PersistableBundle carrierConfig = telephonyManager.getCarrierConfig();
+ int organization = carrierConfig.getInt(
+ CarrierConfigManager.KEY_GBA_UA_SECURITY_ORGANIZATION_INT);
+ int protocol = carrierConfig.getInt(
+ CarrierConfigManager.KEY_GBA_UA_SECURITY_PROTOCOL_INT);
+ int cipherSuite = carrierConfig.getInt(
+ CarrierConfigManager.KEY_GBA_UA_TLS_CIPHER_SUITE_INT);
+ Log.i(TAG, "organization:" + organization + ", protocol:" + protocol + ", cipherSuite:"
+ + cipherSuite + ", contentServerUrl:" + contentServerUrl);
+
+ builder.setOrg(organization)
+ .setProtocol(protocol);
+ if (cipherSuite == TlsParams.TLS_NULL_WITH_NULL_NULL) {
+ builder.setTlsCipherSuite(TlsParams.TLS_RSA_WITH_AES_128_CBC_SHA);
+ } else {
+ builder.setTlsCipherSuite(cipherSuite);
+ }
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, e.getMessage());
+ credentialsFuture.setException(e);
+ return credentialsFuture;
+ }
+ UaSecurityProtocolIdentifier spId = builder.build();
+ TelephonyManager.BootstrapAuthenticationCallback callback =
+ new TelephonyManager.BootstrapAuthenticationCallback() {
+ @Override
+ public void onKeysAvailable(byte[] gbaKey, String btId) {
+ Log.i(TAG, "onKeysAvailable: String key:[" + new String(gbaKey) + "] btid:["
+ + btId + "]" + "Base64 key:[" + BaseEncoding.base64().encode(gbaKey)
+ + "]");
+ credentialsFuture.set(GbaCredentials.create(btId, gbaKey));
+ }
+
+ @Override
+ public void onAuthenticationFailure(int reason) {
+ Log.i(TAG, "onAuthenticationFailure:" + reason);
+ credentialsFuture.setException(
+ new BootstrapAuthenticationException(reason));
+ }
+ };
+ telephonyManager.bootstrapAuthenticationRequest(
+ TelephonyManager.APPTYPE_ISIM,
+ Uri.parse(contentServerUrl),
+ spId,
+ forceBootstrapping,
+ executor,
+ callback);
+
+ return credentialsFuture;
+ }
+
+ @SuppressWarnings("AndroidJdkLibsChecker")
+ @AutoValue
+ abstract static class GbaCredentials implements Credentials {
+
+ public static GbaCredentials create(String btId, byte[] gbaKey) {
+ return new AutoValue_GbaAuthenticationProvider_GbaCredentials(
+ GbaPrincipal.create(btId), BaseEncoding.base64().encode(gbaKey));
+ }
+
+ @Override
+ public abstract Principal getUserPrincipal();
+
+ @Override
+ public abstract String getPassword();
+ }
+
+ @AutoValue
+ abstract static class GbaPrincipal implements Principal {
+
+ public static GbaPrincipal create(String name) {
+ return new AutoValue_GbaAuthenticationProvider_GbaPrincipal(name);
+ }
+
+ @Override
+ public abstract String getName();
+ }
+
+ static class BootstrapAuthenticationException extends Exception {
+ BootstrapAuthenticationException(int reason) {
+ super("Bootstrap authentication request failure: " + reason);
+ }
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaRequestExecutor.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaRequestExecutor.java
new file mode 100644
index 0000000..83d5a8a
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaRequestExecutor.java
@@ -0,0 +1,115 @@
+/*
+ * 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.filetransfer.requestexecutor;
+
+import android.util.Log;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.AuthState;
+import org.apache.http.auth.Credentials;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.client.protocol.ClientContext;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.protocol.HttpContext;
+
+import java.net.HttpURLConnection;
+import java.util.concurrent.Executors;
+
+/** Executes GBA authenticated HTTP requests. */
+public class GbaRequestExecutor implements HttpRequestExecutor {
+
+ private static final String TAG = "GbaRequestExecutor";
+ private final GbaAuthenticationProvider gbaAuthenticationProvider;
+ private final ListeningExecutorService executor =
+ MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(4));
+
+ public GbaRequestExecutor(GbaAuthenticationProvider gbaAuthenticationProvider) {
+ this.gbaAuthenticationProvider = gbaAuthenticationProvider;
+ }
+
+ @Override
+ @SuppressWarnings("CheckReturnValue")
+ public ListenableFuture<HttpResponse> executeAuthenticatedRequest(
+ DefaultHttpClient httpClient, HttpContext context, HttpRequestBase request,
+ AuthScheme authScheme) {
+
+ // Set authentication for the client.
+ ListenableFuture<Credentials> credentialsFuture =
+ gbaAuthenticationProvider.provideCredentials(/*forceBootrapping*/ false);
+
+ ListenableFuture<HttpResponse> responseFuture =
+ Futures.transformAsync(
+ credentialsFuture,
+ credentials -> {
+ Log.i(TAG,
+ "Obtained credentialsFuture, making the POST with credentials"
+ + ".");
+ httpClient.addRequestInterceptor((req, ctx) -> {
+ AuthState authState = (AuthState) context.getAttribute(
+ ClientContext.TARGET_AUTH_STATE);
+ authState.setAuthScope(AuthScope.ANY);
+ authState.setAuthScheme(authScheme);
+ authState.setCredentials(credentials);
+ }, /* index= */ 0);
+
+ // Make the first request.
+ return executor.submit(() -> httpClient.execute(request, context));
+ },
+ executor);
+
+ return Futures.transformAsync(
+ responseFuture,
+ response -> {
+
+ // If the response code is 401, the keys might be invalid so force boostrapping.
+ if (response.getStatusLine().getStatusCode()
+ != HttpURLConnection.HTTP_UNAUTHORIZED) {
+ return Futures.immediateFuture(response);
+ }
+ Log.i(TAG, "Obtained 401 for the authneticated request. Forcing boostrapping.");
+
+ ListenableFuture<Credentials> forceBootstrappedCredentialsFuture =
+ gbaAuthenticationProvider.provideCredentials(/*forceBoostrapping*/
+ true);
+
+ return Futures.transformAsync(
+ forceBootstrappedCredentialsFuture,
+ forceBootstrappedCredentials -> {
+ httpClient
+ .getCredentialsProvider()
+ .setCredentials(AuthScope.ANY,
+ forceBootstrappedCredentials);
+
+ // Make a second request.
+ Log.i(TAG,
+ "Obtained new credentialsFuture, making POST with the new"
+ + " credentials.");
+ return Futures.submit(() -> httpClient.execute(request, context),
+ executor);
+ },
+ executor);
+ },
+ executor);
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/HttpRequestExecutor.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/HttpRequestExecutor.java
new file mode 100644
index 0000000..0026790
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/HttpRequestExecutor.java
@@ -0,0 +1,36 @@
+/*
+ * 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.filetransfer.requestexecutor;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.protocol.HttpContext;
+
+import java.io.IOException;
+
+/** Executes authenticated HTTP requests. */
+public interface HttpRequestExecutor {
+
+ ListenableFuture<HttpResponse> executeAuthenticatedRequest(
+ DefaultHttpClient httpClient, HttpContext context, HttpRequestBase request,
+ AuthScheme authScheme)
+ throws IOException;
+}
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
new file mode 100644
index 0000000..6bb8eec
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/cpim/CpimUtils.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 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 java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Random;
+
+/** Collections of utility functions for CPIM */
+public class CpimUtils {
+ private static final String ANONYMOUS_URI = "<sip:anonymous@anonymous.invalid>";
+ public static final String CPIM_CONTENT_TYPE = "message/cpim";
+
+ private CpimUtils() {
+ }
+
+ @SuppressWarnings("AndroidJdkLibsChecker")
+ public static SimpleCpimMessage createForText(String text) {
+ return SimpleCpimMessage.newBuilder()
+ .addNamespace("imdn", "urn:ietf:params:imdn")
+ .addHeader("imdn.Message-ID", generateImdnMessageId())
+ .addHeader("imdn.Disposition-Notification", "positive-delivery, display")
+ .addHeader("To", ANONYMOUS_URI)
+ .addHeader("From", ANONYMOUS_URI)
+ .addHeader("DateTime", ZonedDateTime.now(ZoneId.systemDefault()).format(
+ DateTimeFormatter.ISO_INSTANT))
+ .setContentType("text/plain")
+ .setContent(text)
+ .build();
+ }
+
+ private static String generateImdnMessageId() {
+ Random random = new Random();
+ 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
new file mode 100644
index 0000000..e2efafe
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/cpim/SimpleCpimMessage.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2020 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 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
+ * represent a simple message for test purpose.
+ */
+@AutoValue
+public abstract class SimpleCpimMessage {
+ private static final String CRLF = "\r\n";
+ private static final String COLSP = ": ";
+ 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();
+
+ public abstract ImmutableMap<String, String> headers();
+
+ public abstract String contentType();
+
+ public abstract String content();
+
+ public String encode() {
+ StringBuilder builder = new StringBuilder();
+ for (Map.Entry<String, String> entry : namespaces().entrySet()) {
+ builder
+ .append("NS: ")
+ .append(entry.getKey())
+ .append(" <")
+ .append(entry.getValue())
+ .append(">")
+ .append(CRLF);
+ }
+
+ for (Map.Entry<String, String> entry : headers().entrySet()) {
+ builder.append(entry.getKey()).append(COLSP).append(entry.getValue()).append(CRLF);
+ }
+
+ builder.append(CRLF);
+ builder.append("Content-Type").append(COLSP).append(contentType());
+ 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();
+
+ public abstract ImmutableMap.Builder<String, String> headersBuilder();
+
+ public abstract Builder setContentType(String value);
+
+ public abstract Builder setContent(String value);
+
+ public abstract SimpleCpimMessage build();
+
+ public Builder addNamespace(String name, String value) {
+ namespacesBuilder().put(name, value);
+ return this;
+ }
+
+ public Builder addHeader(String name, String value) {
+ headersBuilder().put(name, value);
+ 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
new file mode 100644
index 0000000..57bb75a
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/ImsPdnNetworkFetcher.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2020 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.msrp;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+
+import androidx.annotation.RequiresPermission;
+
+import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** A really incomplete implementation for fetching networks from ConnectivityManager. */
+public final class ImsPdnNetworkFetcher {
+
+ private final Context context;
+
+ public ImsPdnNetworkFetcher(Context context) {
+ this.context = context;
+ }
+
+ private static NetworkRequest createNetworkRequest() {
+ NetworkRequest.Builder builder =
+ new NetworkRequest.Builder().addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
+ return builder.build();
+ }
+
+ ListenableFuture<Network> getImsPdnNetwork() {
+ return requestNetwork();
+ }
+
+ ListenableFuture<List<String>> getImsPdnIpAddresses() {
+ return FluentFuture.from(getImsPdnNetwork())
+ .transform(this::getNetworkIpAddresses, MoreExecutors.directExecutor());
+ }
+
+ @RequiresPermission("android.permission.ACCESS_NETWORK_STATE")
+ List<String> getNetworkIpAddresses(Network network) {
+ return getConnectivityManager().getLinkProperties(network).getLinkAddresses().stream()
+ .map(link -> link.getAddress().getHostAddress())
+ .collect(Collectors.toList());
+ }
+
+ private ListenableFuture<Network> requestNetwork() {
+ SettableFuture<Network> result = SettableFuture.create();
+ ConnectivityManager cm = getConnectivityManager();
+
+ ConnectivityManager.NetworkCallback cb =
+ new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onAvailable(Network network) {
+ if (!result.isDone() && !result.isCancelled()) {
+ result.set(network);
+ }
+ cm.unregisterNetworkCallback(this);
+ }
+ };
+
+ cm.requestNetwork(createNetworkRequest(), cb);
+ return result;
+ }
+
+ ConnectivityManager getConnectivityManager() {
+ return context.getSystemService(ConnectivityManager.class);
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpChunk.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpChunk.java
new file mode 100644
index 0000000..0d9e62f
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpChunk.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2020 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.msrp;
+
+import static com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpConstants.FLAG_ABORT_CHUNK;
+import static com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpConstants.FLAG_LAST_CHUNK;
+import static com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpConstants.FLAG_MORE_CHUNK;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Single MSRP chunk containing a request or a response.
+ */
+@AutoValue
+public abstract class MsrpChunk {
+
+ public static Builder newBuilder() {
+ return new AutoValue_MsrpChunk.Builder()
+ .method(Method.UNKNOWN)
+ .responseCode(0)
+ .responseReason("")
+ .content(new byte[0])
+ .continuation(Continuation.UNKNOWN);
+ }
+
+ public abstract Method method();
+
+ public abstract String transactionId();
+
+ public abstract Continuation continuation();
+
+ public abstract int responseCode();
+
+ public abstract String responseReason();
+
+ public abstract ImmutableList<MsrpChunkHeader> headers();
+
+ public abstract byte[] content();
+
+ public MsrpChunkHeader header(String headerName) {
+ for (MsrpChunkHeader header : headers()) {
+ if (header.name().equals(headerName)) {
+ return header;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Methods for requests
+ */
+ public enum Method {
+ UNKNOWN,
+ SEND,
+ REPORT,
+ }
+
+
+ /**
+ * Continuation flag for the chunk
+ */
+ public enum Continuation {
+ UNKNOWN(0),
+ COMPLETE(FLAG_LAST_CHUNK),
+ MORE(FLAG_MORE_CHUNK),
+ ABORTED(FLAG_ABORT_CHUNK);
+
+ private final int value;
+
+ Continuation(int value) {
+ this.value = value;
+ }
+
+ public static Continuation valueOf(int read) {
+ if (read == COMPLETE.value) {
+ return COMPLETE;
+ }
+ if (read == MORE.value) {
+ return MORE;
+ }
+ if (read == ABORTED.value) {
+ return ABORTED;
+ }
+ return UNKNOWN;
+ }
+
+ public byte toByte() {
+ return (byte) value;
+ }
+ }
+
+ /**
+ * Builder for new MSRP chunk.
+ */
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ public abstract Builder method(Method method);
+
+ public abstract Builder transactionId(String id);
+
+ public abstract String transactionId();
+
+ public abstract Continuation continuation();
+
+ public abstract Builder continuation(Continuation continuation);
+
+ public abstract Builder responseCode(int continuation);
+
+ public abstract Builder responseReason(String reason);
+
+ public abstract Builder content(byte[] content);
+
+ public Builder addHeader(MsrpChunkHeader header) {
+ headersBuilder().add(header);
+ return this;
+ }
+
+ public Builder addHeader(String name, String value) {
+ headersBuilder().add(MsrpChunkHeader.newBuilder().name(name).value(value).build());
+ return this;
+ }
+
+ abstract ImmutableList.Builder<MsrpChunkHeader> headersBuilder();
+
+ MsrpChunkHeader header(String name) {
+ for (MsrpChunkHeader header : headersBuilder().build()) {
+ if (header.name().equals(name)) {
+ return header;
+ }
+ }
+ return null;
+ }
+
+ public abstract MsrpChunk build();
+
+
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpChunkHeader.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpChunkHeader.java
new file mode 100644
index 0000000..fad17e0
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpChunkHeader.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 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.msrp;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * Single header in MSRP chunk (From-Path, To-Path, ...)
+ */
+@AutoValue
+public abstract class MsrpChunkHeader {
+
+ public static Builder newBuilder() {
+ return new AutoValue_MsrpChunkHeader.Builder();
+ }
+
+ public abstract String name();
+
+ public abstract String value();
+
+ /**
+ * Builder for new MSRP header.
+ */
+ @AutoValue.Builder
+ public static abstract class Builder {
+
+ public abstract Builder name(String name);
+
+ public abstract Builder value(String value);
+
+ public abstract MsrpChunkHeader build();
+ }
+}
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
new file mode 100644
index 0000000..ba424c6
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpConstants.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 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.msrp;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * Several constants used for MSRP parsing and serializing.
+ */
+public class MsrpConstants {
+ public static final byte[] HEADER_DELIMITER_BYTES = ": ".getBytes();
+ public static final String MSRP_PROTOCOL = "MSRP";
+ public static final byte[] MSRP_PROTOCOL_BYTES = MSRP_PROTOCOL.getBytes(UTF_8);
+ public static final String NEW_LINE = "\r\n";
+ public static final byte[] NEW_LINE_BYTES = NEW_LINE.getBytes(UTF_8);
+ public static final String END_MSRP_MSG = "-------";
+ public static final byte[] END_MSRP_MSG_BYTES = END_MSRP_MSG.getBytes(UTF_8);
+ public static final String NEW_LINE_END_MSRP_MSG = NEW_LINE + END_MSRP_MSG;
+ public static final int END_MSRP_MSG_LENGTH = END_MSRP_MSG.length();
+ public static final int FLAG_LAST_CHUNK = '$';
+ public static final int FLAG_MORE_CHUNK = '+';
+ public static final int FLAG_ABORT_CHUNK = '#';
+ public static final byte CHAR_SP = ' ';
+ public static final byte CHAR_LF = '\r';
+ public static final byte CHAR_MIN = '-';
+ public static final byte CHAR_DOUBLE_POINT = ':';
+ public static final String HEADER_BYTE_RANGE = "Byte-Range";
+ public static final String HEADER_CONTENT_TYPE = "Content-Type";
+ public static final String HEADER_MESSAGE_ID = "Message-ID";
+ 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() {
+ }
+}
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
new file mode 100644
index 0000000..58a6eef
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpManager.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 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.msrp;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+
+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.net.InetAddress;
+import java.net.Socket;
+
+/** Provides creating and managing {@link MsrpSession} */
+public class MsrpManager {
+ private final ImsPdnNetworkFetcher imsPdnNetworkFetcher;
+ private Context context;
+
+ public MsrpManager(Context context) {
+ this.context = context;
+ imsPdnNetworkFetcher = new ImsPdnNetworkFetcher(context);
+ }
+
+ private 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, context,
+ network, socket, listener);
+ Thread thread = new Thread(msrpSession::run);
+ thread.start();
+ return msrpSession;
+ }
+
+ public ListenableFuture<MsrpSession> createMsrpSession(
+ String host, int port, String localIp, int localPort, MsrpSessionListener listener) {
+ return Futures.transformAsync(
+ imsPdnNetworkFetcher.getImsPdnNetwork(),
+ network -> {
+ if (network != null) {
+ return Futures.immediateFuture(
+ createMsrpSession(imsPdnNetworkFetcher.getConnectivityManager(),
+ network, host, port, localIp, localPort, listener));
+ } else {
+ return Futures.immediateFailedFuture(
+ new IllegalStateException("Network is null"));
+ }
+ },
+ MoreExecutors.directExecutor());
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpParser.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpParser.java
new file mode 100644
index 0000000..3376544
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpParser.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2020 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.msrp;
+
+import static com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpConstants.CHAR_DOUBLE_POINT;
+import static com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpConstants.CHAR_LF;
+import static com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpConstants.CHAR_MIN;
+import static com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpConstants.CHAR_SP;
+import static com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpConstants.END_MSRP_MSG_LENGTH;
+import static com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpConstants.HEADER_BYTE_RANGE;
+import static com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpConstants.NEW_LINE_END_MSRP_MSG;
+
+import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpChunk.Continuation;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * Simple parser for reading MSRP messages from a stream.
+ */
+public final class MsrpParser {
+
+ private MsrpParser() {
+ }
+
+ public static MsrpChunk parse(final InputStream stream) throws IOException {
+ MsrpChunk.Builder transaction = MsrpChunk.newBuilder();
+
+ // Read a chunk (blocking method)
+ int i = stream.read();
+
+ final StringBuilder value = new StringBuilder();
+ // Read MSRP tag
+ skipWithDelimiter(stream, CHAR_SP);
+
+ if (i == -1) {
+ // End of stream
+ return null;
+ }
+
+ // Read the transaction ID
+ do {
+ i = stream.read();
+ if (i != CHAR_SP) {
+ value.append((char) i);
+ }
+ } while ((i != CHAR_SP) && (i != -1));
+
+ if (i == -1) {
+ return null;
+ }
+
+ final String txId = value.toString();
+ value.setLength(0);
+
+ // Read response code or method name
+ MsrpChunk.Method method = MsrpChunk.Method.UNKNOWN;
+ int responseCode = -1;
+ for (i = stream.read(); (i != CHAR_LF) && (i != -1); i = stream.read()) {
+ if (i == CHAR_SP && responseCode == -1) {
+ // There is a space -> it's a response
+ try {
+ responseCode = Integer.parseInt(value.toString());
+ } catch (NumberFormatException nfe) {
+ // This is an invalid response.
+ return null;
+ }
+ value.setLength(0);
+ continue;
+ }
+ value.append((char) i);
+ }
+
+ if (responseCode == -1) {
+ try {
+ responseCode = Integer.parseInt(value.toString());
+ value.setLength(0);
+ } catch (final NumberFormatException e) {
+ method = MsrpChunk.Method.valueOf(value.toString());
+ }
+ }
+
+ i = stream.read();
+
+ if (i == -1) {
+ // End of stream
+ return null;
+ }
+
+ final boolean isResponse = responseCode > -1;
+ if (isResponse) {
+ transaction.transactionId(txId).responseCode(responseCode).responseReason(
+ value.toString());
+ } else {
+ transaction.transactionId(txId).method(method);
+ }
+
+ value.setLength(0);
+
+ // Read MSRP headers
+ readHeaders(stream, transaction, value);
+
+ // We already received end of message
+ if (transaction.continuation() != Continuation.UNKNOWN) {
+ return transaction.build();
+ }
+
+ i = stream.read();
+ if (i == -1) {
+ // End of stream
+ return null;
+ }
+
+ // Process MSRP request
+ if (method == MsrpChunk.Method.SEND) {
+ readChunk(stream, transaction);
+ }
+
+ return transaction.build();
+ }
+
+ private static void readHeaders(
+ final InputStream stream, final MsrpChunk.Builder transaction,
+ final StringBuilder value)
+ throws IOException {
+ for (int i = stream.read(); (i != CHAR_LF) && (i != -1); ) {
+
+ for (; (i != CHAR_DOUBLE_POINT) && (i != -1); i = stream.read()) {
+ value.append((char) i);
+ }
+
+ final String headerName = value.toString();
+ value.setLength(0);
+
+ stream.read(); // skip space
+
+ for (i = stream.read(); (i != CHAR_LF) && (i != -1); i = stream.read()) {
+ value.append((char) i);
+ }
+
+ final String headerValue = value.toString();
+ value.setLength(0);
+
+ transaction.addHeader(headerName, headerValue);
+
+ stream.read();
+
+ // It's the end of the header part
+ i = stream.read();
+ if (i == CHAR_MIN) {
+ final int length = END_MSRP_MSG_LENGTH - 1 + transaction.transactionId().length();
+ stream.skip(length);
+ transaction.continuation(Continuation.valueOf(stream.read()));
+
+ // For response
+ for (; (i != CHAR_LF) && (i != -1); i = stream.read()) {
+ }
+ break;
+ }
+ }
+ }
+
+ private static void readChunk(final InputStream stream, final MsrpChunk.Builder chunk)
+ throws IOException {
+ final String byteRange = chunk.header(HEADER_BYTE_RANGE).value();
+
+ if (byteRange == null) {
+ throw new IllegalStateException("expected non-null byteRange");
+ }
+ final int chunkSize = getChunkSize(byteRange);
+ final long totalSize = getTotalSize(byteRange);
+
+ if (totalSize == Integer.MIN_VALUE || chunkSize < -1) {
+ throw new IOException("Invalid byte range: " + byteRange);
+ }
+
+ if (chunkSize == -1) {
+ readUnknownChunk(stream, chunk);
+ } else {
+ readKnownChunk(stream, chunk, chunkSize);
+ skipEndLine(stream, chunk);
+ }
+
+ readContinuationFlag(stream, chunk);
+ }
+
+ private static void readKnownChunk(
+ final InputStream stream, final MsrpChunk.Builder chunk, final int chunkSize)
+ throws IOException {
+ // Read the data
+ final byte[] data = new byte[chunkSize];
+ int nbRead = 0;
+ int nbData = -1;
+ while ((nbRead < chunkSize)
+ && ((nbData = stream.read(data, nbRead, chunkSize - nbRead)) != -1)) {
+ nbRead += nbData;
+ }
+
+ chunk.content(data);
+
+ stream.read();
+ stream.read();
+ }
+
+ private static void readUnknownChunk(final InputStream stream, final MsrpChunk.Builder chunk)
+ throws IOException {
+
+ final byte[] bufferArray = new byte[4096];
+ final byte[] endOfChunkPattern =
+ (NEW_LINE_END_MSRP_MSG + chunk.transactionId()).getBytes();
+ int pp = 0;
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+ final ByteBuffer buffer = ByteBuffer.wrap(bufferArray);
+ while (true) {
+ final int i = stream.read();
+
+ if (i < 0) {
+ throw new IOException("EOS reached");
+ }
+
+ if (i == endOfChunkPattern[pp]) {
+ pp++;
+ } else if (i == endOfChunkPattern[0]) {
+ pp = 1;
+ } else {
+ pp = 0;
+ }
+
+ buffer.put((byte) i);
+
+ if (pp == endOfChunkPattern.length) {
+ outputStream.write(bufferArray, 0, buffer.position() - endOfChunkPattern.length);
+ break;
+ }
+
+ if (buffer.remaining() == 0) {
+ if (pp > 0) {
+ outputStream.write(bufferArray, 0, bufferArray.length - pp);
+ System.arraycopy(endOfChunkPattern, 0, bufferArray, 0, pp);
+ buffer.position(pp);
+ } else {
+ outputStream.write(bufferArray, 0, bufferArray.length);
+ buffer.rewind();
+ }
+ }
+ }
+
+ chunk.content(outputStream.toByteArray());
+ }
+
+ private static void skipEndLine(final InputStream stream, final MsrpChunk.Builder chunk)
+ throws IOException {
+ // skip the "-------" + txid
+ final int length = END_MSRP_MSG_LENGTH + chunk.transactionId().length();
+ final byte[] endline = new byte[256];
+ readFromStream(stream, endline, 0, length);
+ }
+
+ private static void readContinuationFlag(
+ final InputStream stream, final MsrpChunk.Builder transaction) throws IOException {
+ transaction.continuation(Continuation.valueOf(stream.read()));
+ stream.read();
+ stream.read();
+ }
+
+ /**
+ * Get the chunk size
+ *
+ * @param header MSRP header
+ * @return Size in bytes
+ */
+ private static int getChunkSize(final String header) {
+ final int index1 = header.indexOf("-");
+ final int index2 = header.indexOf("/");
+ if ((index1 != -1) && (index2 != -1)) {
+ final String lowByteValue = header.substring(0, index1);
+ final String highByteValue = header.substring(index1 + 1, index2);
+
+ if ("*".equals(highByteValue)) {
+ return -1;
+ } else {
+ try {
+ final int lowByte = Integer.parseInt(lowByteValue);
+ final int highByte = Integer.parseInt(highByteValue);
+ if (lowByte > highByte) {
+ return Integer.MIN_VALUE;
+ }
+ return (highByte - lowByte) + 1;
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException("Could not read chunksize!");
+ }
+ }
+ }
+ return Integer.MIN_VALUE;
+ }
+
+ /**
+ * Get the total size
+ *
+ * @param header MSRP header
+ * @return Size in bytes
+ */
+ private static long getTotalSize(final String header) {
+ final int index = header.indexOf("/");
+ if (index != -1) {
+ if ("*".equals(header.substring(index + 1))) {
+ return -1;
+ }
+ try {
+ return Long.parseLong(header.substring(index + 1));
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException("Could not read total size!");
+ }
+ }
+ return Integer.MIN_VALUE;
+ }
+
+ private static void readFromStream(
+ InputStream stream, final byte[] buffer, final int offset, final int length)
+ throws IOException {
+ int read = 0;
+ while (read < length) {
+ try {
+ read += stream.read(buffer, offset + read, length - read);
+ } catch (IndexOutOfBoundsException e) {
+ throw new IOException("Invalid ID length", e);
+ }
+ }
+ }
+
+ private static int skipWithDelimiter(InputStream stream, byte delimiter) throws IOException {
+ int i = stream.read();
+ for (; (i != delimiter) && (i != -1); i = stream.read()) {
+ }
+ return i;
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSerializer.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSerializer.java
new file mode 100644
index 0000000..bd4daa5
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSerializer.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 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.msrp;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Serializer for writing messages
+ */
+public final class MsrpSerializer {
+
+ private MsrpSerializer() {
+ }
+
+ public static void serialize(OutputStream outputStream, MsrpChunk message) throws IOException {
+
+ writeRequestLine(outputStream, message);
+ for (MsrpChunkHeader header : message.headers()) {
+ writeHeader(outputStream, header);
+ }
+
+ if (message.content().length > 0) {
+ outputStream.write(MsrpConstants.NEW_LINE_BYTES);
+ outputStream.write(message.content());
+ outputStream.write(MsrpConstants.NEW_LINE_BYTES);
+ }
+
+ writeEndLine(outputStream, message);
+ }
+
+ private static void writeRequestLine(OutputStream outputStream, MsrpChunk chunk)
+ throws IOException {
+
+ outputStream.write(MsrpConstants.MSRP_PROTOCOL_BYTES);
+ outputStream.write(MsrpConstants.CHAR_SP);
+ outputStream.write(chunk.transactionId().getBytes());
+ outputStream.write(MsrpConstants.CHAR_SP);
+
+ if (chunk.method() != MsrpChunk.Method.UNKNOWN) {
+ outputStream.write(chunk.method().name().getBytes(UTF_8));
+ } else {
+ outputStream.write(
+ (chunk.responseCode() + " " + chunk.responseReason()).getBytes(UTF_8));
+ }
+
+ outputStream.write(MsrpConstants.NEW_LINE_BYTES);
+ }
+
+ private static void writeHeader(OutputStream outputStream, MsrpChunkHeader header)
+ throws IOException {
+ outputStream.write(header.name().getBytes(UTF_8));
+ outputStream.write(MsrpConstants.HEADER_DELIMITER_BYTES);
+ outputStream.write(header.value().getBytes(UTF_8));
+ outputStream.write(MsrpConstants.NEW_LINE_BYTES);
+ }
+
+ private static void writeEndLine(OutputStream outputStream, MsrpChunk chunk)
+ throws IOException {
+ outputStream.write(MsrpConstants.END_MSRP_MSG_BYTES);
+ outputStream.write(chunk.transactionId().getBytes(UTF_8));
+ outputStream.write(chunk.continuation().toByte());
+ outputStream.write(MsrpConstants.NEW_LINE_BYTES);
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..7436da4
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSession.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2020 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.msrp;
+
+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.content.Context;
+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.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.concurrent.futures.CallbackToFutureAdapter.Completer;
+
+import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpChunk.Continuation;
+
+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;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Provides MSRP sending and receiving messages ability.
+ */
+public class MsrpSession {
+ private static final String DEDICATED_BEARER_SUCCESS = "Dedicated bearer succeeded";
+ private static final String DEDICATED_BEARER_ERROR = "Dedicated bearer error";
+ private static final int TOAST_MSG = 1;
+ private final Network network;
+ private final Socket socket;
+ private final InputStream input;
+ private final OutputStream output;
+ private final AtomicBoolean isOpen = new AtomicBoolean(true);
+ private final ConcurrentHashMap<String, MsrpTransaction> transactions =
+ new ConcurrentHashMap<>();
+ private final MsrpSessionListener listener;
+ private final ConnectivityManager connectivityManager;
+ private final String LOG_TAG = MsrpSession.class.getSimpleName();
+ private final Context context;
+ private Handler handler;
+
+ /** Creates a new MSRP session on the given listener and the provided streams. */
+ MsrpSession(ConnectivityManager connectivityManager, Context context, Network network,
+ Socket socket, MsrpSessionListener listener) throws IOException {
+ this.connectivityManager = connectivityManager;
+ this.context = context;
+ this.network = network;
+ this.socket = socket;
+ this.input = socket.getInputStream();
+ this.output = socket.getOutputStream();
+ this.listener = listener;
+
+
+ handler = new Handler(context.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ Toast.makeText(context, msg.obj.toString(), Toast.LENGTH_SHORT).show();
+ }
+ };
+
+ listenForBearer();
+ }
+
+ private final QosCallback qosCallback = new QosCallback() {
+ @Override
+ public void onError(@NonNull QosCallbackException exception) {
+ Log.e(LOG_TAG, "onError: " + exception.toString());
+ handler.sendMessage(handler.obtainMessage(TOAST_MSG, DEDICATED_BEARER_ERROR));
+ }
+
+ @Override
+ public void onQosSessionAvailable(@NonNull QosSession session,
+ @NonNull QosSessionAttributes sessionAttributes) {
+ Log.d(LOG_TAG, "onQosSessionAvailable: " + session.toString() + ", "
+ + sessionAttributes.toString());
+ handler.sendMessage(handler.obtainMessage(TOAST_MSG, DEDICATED_BEARER_SUCCESS));
+ }
+
+ @Override
+ public void onQosSessionLost(@NonNull QosSession session) {
+ Log.e(LOG_TAG, "onQosSessionLost: " + session.toString());
+ handler.sendMessage(handler.obtainMessage(TOAST_MSG, DEDICATED_BEARER_ERROR));
+ }
+ };
+
+ private void listenForBearer() {
+ try {
+ connectivityManager.registerQosCallback(new QosSocketInfo(network, socket),
+ MoreExecutors.directExecutor(), qosCallback);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Sends the given MSRP chunk.
+ */
+ public ListenableFuture<MsrpChunk> send(MsrpChunk request) {
+ if (request.method() == UNKNOWN) {
+ throw new IllegalArgumentException("Given chunk must be a request");
+ }
+
+ if (!isOpen.get()) {
+ throw new IllegalStateException("Session terminated");
+ }
+
+ if (!socket.isConnected()) {
+ throw new IllegalStateException("Socket is not connected");
+ }
+
+ if (request.method() == SEND) {
+ return CallbackToFutureAdapter.getFuture(
+ completer -> {
+ final MsrpTransaction transaction = new MsrpTransaction(completer);
+ transactions.put(request.transactionId(), transaction);
+ try {
+ synchronized (output) {
+ MsrpSerializer.serialize(output, request);
+ }
+ output.flush();
+ } catch (IOException e) {
+ completer.setException(e);
+ }
+ return "MsrpSession.send(" + request.transactionId() + ")";
+ }
+ );
+ } else {
+ try {
+ synchronized (output) {
+ MsrpSerializer.serialize(output, request);
+ }
+ return Futures.immediateFuture(request);
+ } catch (IOException e) {
+ return Futures.immediateFailedFuture(e);
+ }
+ }
+ }
+
+ /**
+ * Blocking method which reads from the provided InputStream until the session
+ * is terminated or the stream read throws an exception.
+ */
+ public void run() {
+ new StreamReader(this).run();
+ }
+
+ public void terminate() throws IOException {
+ if (isOpen.getAndSet(false)) {
+ output.flush();
+ }
+ connectivityManager.unregisterQosCallback(qosCallback);
+ socket.close();
+ }
+
+ /**
+ * Reads and parses MSRP messages from the session input stream.
+ */
+ private static class StreamReader {
+
+ private final MsrpSession session;
+ private final InputStream stream;
+ private final AtomicBoolean active;
+
+ StreamReader(MsrpSession session) {
+ this.session = session;
+ this.stream = session.input;
+ this.active = session.isOpen;
+ }
+
+ void run() {
+ while (active.get()) {
+ MsrpChunk chunk = null;
+ try {
+ chunk = MsrpParser.parse(stream);
+
+ if (chunk.method() == UNKNOWN) {
+ completeTransaction(chunk);
+ } else {
+ receiveRequest(chunk);
+ }
+ } catch (IOException e) {
+ active.compareAndSet(true, false);
+ }
+ }
+ }
+
+ private void receiveRequest(MsrpChunk chunk) throws IOException {
+ sendResponse(chunk);
+ session.listener.onChunkReceived(chunk);
+ }
+
+ private void completeTransaction(MsrpChunk chunk) {
+ MsrpTransaction transaction = session.transactions.remove(chunk.transactionId());
+ if (transaction != null) {
+ transaction.complete(chunk);
+ }
+ }
+
+ private void sendResponse(MsrpChunk chunk) throws IOException {
+ // check if response is required
+ MsrpChunkHeader failureReport = chunk.header(MsrpConstants.HEADER_FAILURE_REPORT);
+ if (failureReport == null || failureReport.value().equals("yes")) {
+ MsrpChunkHeader toPath = chunk.header(MsrpConstants.HEADER_TO_PATH);
+ MsrpChunkHeader fromPath = chunk.header(MsrpConstants.HEADER_FROM_PATH);
+
+ MsrpChunk response = MsrpChunk.newBuilder()
+ .transactionId(chunk.transactionId())
+ .responseCode(200)
+ .responseReason("OK")
+ .addHeader(MsrpConstants.HEADER_TO_PATH, fromPath.value())
+ .addHeader(MsrpConstants.HEADER_FROM_PATH, toPath.value())
+ .continuation(Continuation.COMPLETE)
+ .build();
+
+ synchronized (session.output) {
+ MsrpSerializer.serialize(session.output, response);
+ session.output.flush();
+ }
+ }
+ }
+ }
+
+ /**
+ * Transaction holder.
+ */
+ private static class MsrpTransaction {
+ private final Completer<MsrpChunk> completed;
+
+ public MsrpTransaction(Completer<MsrpChunk> chunkCompleter) {
+ this.completed = chunkCompleter;
+ }
+
+ public void complete(MsrpChunk response) {
+ completed.set(response);
+ }
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSessionListener.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSessionListener.java
new file mode 100644
index 0000000..4235c25
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSessionListener.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2020 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.msrp;
+
+/**
+ * Listener for MSRP session events.
+ */
+public interface MsrpSessionListener {
+ void onChunkReceived(MsrpChunk chunk);
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpUtils.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpUtils.java
new file mode 100644
index 0000000..c135bc0
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpUtils.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 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.msrp;
+
+import com.android.libraries.rcs.simpleclient.protocol.sip.SipUtils;
+import java.security.SecureRandom;
+
+/** Collections of utility functions for MSRP */
+public final class MsrpUtils {
+
+ private static final SecureRandom random = new SecureRandom();
+
+ private MsrpUtils() {
+ }
+
+ /** Generate a path attribute defined in RFC 4975 for the given address, port. */
+ public static String generatePath(String address, int port, boolean isSecure) {
+ StringBuilder builder = new StringBuilder();
+
+ if (SipUtils.isIPv6Address(address)) {
+ address = "[" + address + "]";
+ }
+
+ builder
+ .append(isSecure ? "msrps" : "msrp")
+ .append("://")
+ .append(address)
+ .append(":")
+ .append(port)
+ .append("/")
+ .append(System.currentTimeMillis())
+ .append(";tcp");
+
+ return builder.toString();
+ }
+
+ public static String generateRandomId() {
+ byte[] randomBytes = new byte[8];
+ random.nextBytes(randomBytes);
+ String hex = "";
+ for (byte b : randomBytes) {
+ hex = hex + String.format("%02x", b);
+ }
+ return hex;
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sdp/SdpMedia.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sdp/SdpMedia.java
new file mode 100644
index 0000000..bdb34ba
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sdp/SdpMedia.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 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.sdp;
+
+import android.text.TextUtils;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableMap;
+
+import java.text.ParseException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The media part of SDP implementation as per RFC 4566. This class supports minimal fields that is
+ * required to represent MSRP session.
+ */
+@AutoValue
+public abstract class SdpMedia {
+ private static final String CRLF = "\r\n";
+
+ public static Builder parseMediaLine(String line) throws ParseException {
+ List<String> elements = Splitter.on(" ").limit(4).splitToList(line);
+
+ // The valid media line should have 4 elements:
+ // m=<name> <port> <protocol> <format>
+ if (elements.size() != 4) {
+ throw new ParseException("Invalid media line", 0);
+ }
+
+ // Parse each field from the media line.
+ Builder builder = SdpMedia.newBuilder();
+ builder
+ .setName(elements.get(0))
+ .setPort(Integer.parseInt(elements.get(1)))
+ .setProtocol(elements.get(2))
+ .setFormat(elements.get(3));
+
+ return builder;
+ }
+
+ public static Builder newBuilder() {
+ return new AutoValue_SdpMedia.Builder();
+ }
+
+ public abstract String name();
+
+ public abstract int port();
+
+ public abstract String protocol();
+
+ public abstract String format();
+
+ public abstract ImmutableMap<String, String> attributes();
+
+ /** Encode the media section as a string. */
+ public String encode() {
+ StringBuilder builder = new StringBuilder();
+ builder
+ .append("m=")
+ .append(name())
+ .append(" ")
+ .append(port())
+ .append(" ")
+ .append(protocol())
+ .append(" ")
+ .append(format())
+ .append(CRLF);
+
+ for (Map.Entry<String, String> attribute : attributes().entrySet()) {
+ builder.append("a=").append(attribute.getKey());
+ if (!TextUtils.isEmpty(attribute.getValue())) {
+ builder.append(":").append(attribute.getValue());
+ }
+ builder.append(CRLF);
+ }
+
+ return builder.toString();
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder setName(String name);
+
+ public abstract Builder setPort(int port);
+
+ public abstract Builder setProtocol(String protocol);
+
+ public abstract Builder setFormat(String payload);
+
+ public abstract ImmutableMap.Builder<String, String> attributesBuilder();
+
+ public Builder addAttribute(String name, String value) {
+ attributesBuilder().put(name, value);
+ return this;
+ }
+
+ public Builder addAttribute(String name) {
+ return addAttribute(name, "");
+ }
+
+ public abstract SdpMedia build();
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sdp/SdpUtils.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sdp/SdpUtils.java
new file mode 100644
index 0000000..e290b29
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sdp/SdpUtils.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2020 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.sdp;
+
+import static com.android.libraries.rcs.simpleclient.protocol.sip.SipUtils.isIPv6Address;
+
+import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpUtils;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
+
+/** Collections of utility functions for SDP */
+public final class SdpUtils {
+ public static final String SDP_CONTENT_TYPE = "application";
+ public static final String SDP_CONTENT_SUB_TYPE = "sdp";
+
+ private static final ImmutableSet<String> DEFAULT_ACCEPT_TYPES =
+ ImmutableSet.of("message/cpim", "application/im-iscomposing+xml");
+ private static final ImmutableSet<String> DEFAULT_ACCEPT_WRAPPED_TYPES =
+ ImmutableSet.of(
+ "text/plain",
+ "message/imdn+xml",
+ "application/vnd.gsma.rcs-ft-http+xml",
+ "application/vnd.gsma.rcspushlocation+xml");
+
+ private static final String DEFAULT_NAME = "message";
+ private static final String DEFAULT_SETUP = "active";
+ private static final String DEFAULT_DIRECTION = "sendrecv";
+ private static final int DEFAULT_MSRP_PORT = 9;
+ private static final String PROTOCOL_TCP_MSRP = "TCP/MSRP";
+ private static final String PROTOCOL_TLS_MSRP = "TCP/TLS/MSRP";
+ private static final String DEFAULT_FORMAT = "*";
+
+ private static final String ATTRIBUTE_PATH = "path";
+ private static final String ATTRIBUTE_SETUP = "setup";
+ private static final String ATTRIBUTE_ACCEPT_TYPES = "accept-types";
+ private static final String ATTRIBUTE_ACCEPT_WRAPPED_TYPES = "accept-wrapped-types";
+
+ private SdpUtils() {
+ }
+
+ /**
+ * Create a simple SDP message for MSRP. Most attributes except address and transport type
+ * will be
+ * generated automatically.
+ *
+ * @param address The local IP address of the MSRP connection.
+ * @param isTls True if the MSRP connection uses TLS.
+ */
+ public static SimpleSdpMessage createSdpForMsrp(String address, boolean isTls) {
+ return SimpleSdpMessage.newBuilder()
+ .setVersion("0")
+ .setOrigin(generateOrigin(address))
+ .setSession("-")
+ .setConnection(generateConnection(address))
+ .setTime("0 0")
+ .addMedia(createSdpMediaForMsrp(address, isTls))
+ .build();
+ }
+
+ private static String generateOrigin(String address) {
+ StringBuilder builder = new StringBuilder();
+ builder
+ .append("TestRcsClient ")
+ .append(System.currentTimeMillis())
+ .append(" ")
+ .append(System.currentTimeMillis())
+ .append(" IN ")
+ .append(isIPv6Address(address) ? "IP6 " : "IP4 ")
+ .append(address);
+
+ return builder.toString();
+ }
+
+ private static String generateConnection(String address) {
+ return "IN " + (isIPv6Address(address) ? "IP6 " : "IP4 ") + address;
+ }
+
+ /**
+ * Create a media part of the SDP message for MSRP. Most attributes except address and transport
+ * type will be generated automatically.
+ *
+ * @param address The local IP address of the MSRP connection.
+ * @param isTls True if the MSRP connection uses TLS.
+ */
+ public static SdpMedia createSdpMediaForMsrp(String address, boolean isTls) {
+ return SdpMedia.newBuilder()
+ .setName(DEFAULT_NAME)
+ .setPort(DEFAULT_MSRP_PORT)
+ .setProtocol(isTls ? PROTOCOL_TLS_MSRP : PROTOCOL_TCP_MSRP)
+ .setFormat(DEFAULT_FORMAT)
+ .addAttribute(ATTRIBUTE_PATH,
+ MsrpUtils.generatePath(address, DEFAULT_MSRP_PORT, isTls))
+ .addAttribute(ATTRIBUTE_SETUP, DEFAULT_SETUP)
+ .addAttribute(ATTRIBUTE_ACCEPT_TYPES, Joiner.on(" ").join(DEFAULT_ACCEPT_TYPES))
+ .addAttribute(
+ ATTRIBUTE_ACCEPT_WRAPPED_TYPES,
+ Joiner.on(" ").join(DEFAULT_ACCEPT_WRAPPED_TYPES))
+ .addAttribute(DEFAULT_DIRECTION)
+ .build();
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sdp/SimpleSdpMessage.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sdp/SimpleSdpMessage.java
new file mode 100644
index 0000000..4abbf87
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sdp/SimpleSdpMessage.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2020 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.sdp;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.text.ParseException;
+import java.util.List;
+import java.util.Optional;
+import java.util.OptionalInt;
+
+/**
+ * The SDP implementation as per RFC 4566. This class supports minimal fields that is required to
+ * represent MSRP session.
+ */
+@AutoValue
+public abstract class SimpleSdpMessage {
+ private static final String CRLF = "\r\n";
+
+ private static final String PREFIX_VERSION = "v";
+ private static final String PREFIX_ORIGIN = "o";
+ private static final String PREFIX_SESSION = "s";
+ private static final String PREFIX_CONNECTION = "c";
+ private static final String PREFIX_TIME = "t";
+ private static final String PREFIX_MEDIA = "m";
+ private static final String PREFIX_ATTRIBUTE = "a";
+ private static final String EQUAL = "=";
+
+ public static SimpleSdpMessage parse(InputStream stream) throws ParseException, IOException {
+ Builder builder = new AutoValue_SimpleSdpMessage.Builder();
+ SdpMedia.Builder currentMediaBuilder = null;
+ BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+
+ String line = reader.readLine();
+ while (line != null) {
+ List<String> parts = Splitter.on("=").trimResults().limit(2).splitToList(line);
+ if (parts.size() != 2) {
+ throw new ParseException("Invalid SDP format", 0);
+ }
+ String prefix = parts.get(0);
+ String value = parts.get(1);
+
+ switch (prefix) {
+ case PREFIX_VERSION:
+ builder.setVersion(value);
+ break;
+ case PREFIX_ORIGIN:
+ builder.setOrigin(value);
+ break;
+ case PREFIX_SESSION:
+ builder.setSession(value);
+ break;
+ case PREFIX_CONNECTION:
+ builder.setConnection(value);
+ break;
+ case PREFIX_TIME:
+ builder.setTime(value);
+ break;
+ case PREFIX_MEDIA:
+ if (currentMediaBuilder != null) {
+ builder.addMedia(currentMediaBuilder.build());
+ }
+ currentMediaBuilder = SdpMedia.parseMediaLine(value);
+ break;
+ case PREFIX_ATTRIBUTE:
+ if (currentMediaBuilder != null) {
+ List<String> kv = Splitter.on(":").trimResults().limit(2).splitToList(
+ value);
+ currentMediaBuilder.addAttribute(kv.get(0), kv.size() < 2 ? "" : kv.get(1));
+ }
+ break;
+ default:
+ // Rest of the fields are ignored as they're not used for describing MSRP
+ // session.
+ break;
+ }
+ line = reader.readLine();
+ }
+
+ if (currentMediaBuilder != null) {
+ builder.addMedia(currentMediaBuilder.build());
+ }
+
+ return builder.build();
+ }
+
+ private static String encodeLine(String prefix, String value) {
+ return prefix + EQUAL + value + CRLF;
+ }
+
+ public static Builder newBuilder() {
+ return new AutoValue_SimpleSdpMessage.Builder();
+ }
+
+ public abstract String version();
+
+ public abstract String origin();
+
+ public abstract String session();
+
+ public abstract String connection();
+
+ public abstract String time();
+
+ public abstract ImmutableList<SdpMedia> media();
+
+ /** Return the IP address in the connection line. */
+ public Optional<String> getAddress() {
+ if (connection() == null) {
+ return Optional.empty();
+ }
+
+ List<String> parts = Splitter.on(" ").limit(3).trimResults().splitToList(connection());
+ if (parts.size() != 3) {
+ return Optional.empty();
+ }
+
+ return Optional.of(parts.get(2));
+ }
+
+ /** Return the port in the first media line. */
+ public OptionalInt getPort() {
+ if (media().isEmpty()) {
+ return OptionalInt.empty();
+ }
+
+ return OptionalInt.of(media().get(0).port());
+ }
+
+ public Optional<String> getPath() {
+ if (media().isEmpty()) {
+ return Optional.empty();
+ }
+
+ return Optional.ofNullable(media().get(0).attributes().get("path"));
+ }
+
+ /** Encode the entire SDP fields as a string. */
+ public String encode() {
+ StringBuilder builder = new StringBuilder();
+ builder
+ .append(encodeLine(PREFIX_VERSION, version()))
+ .append(encodeLine(PREFIX_ORIGIN, origin()))
+ .append(encodeLine(PREFIX_SESSION, session()))
+ .append(encodeLine(PREFIX_CONNECTION, connection()))
+ .append(encodeLine(PREFIX_TIME, time()));
+
+ for (SdpMedia media : media()) {
+ builder.append(media.encode());
+ }
+
+ return builder.toString();
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder setVersion(String version);
+
+ public abstract Builder setOrigin(String origin);
+
+ public abstract Builder setSession(String session);
+
+ public abstract Builder setConnection(String connection);
+
+ public abstract Builder setTime(String connection);
+
+ public abstract ImmutableList.Builder<SdpMedia> mediaBuilder();
+
+ public Builder addMedia(SdpMedia media) {
+ mediaBuilder().add(media);
+ return this;
+ }
+
+ public abstract SimpleSdpMessage build();
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sip/SipSession.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sip/SipSession.java
new file mode 100644
index 0000000..9629961
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sip/SipSession.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2020 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.sip;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import javax.sip.message.Message;
+
+/**
+ * Abstraction of the underlying SIP channel for sending and receiving SIP messages.
+ */
+public interface SipSession {
+
+ SipSessionConfiguration getSessionConfiguration();
+
+ ListenableFuture<Boolean> send(Message message);
+
+ void setSessionListener(SipSessionListener listener);
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sip/SipSessionConfiguration.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sip/SipSessionConfiguration.java
new file mode 100644
index 0000000..59a0541
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sip/SipSessionConfiguration.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 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.sip;
+
+import java.util.List;
+
+public interface SipSessionConfiguration {
+ public long getVersion();
+
+ String getOutboundProxyAddr();
+
+ int getOutboundProxyPort();
+
+ String getLocalIpAddress();
+
+ int getLocalPort();
+
+ String getSipTransport();
+
+ String getPublicUserIdentity();
+
+ String getDomain();
+
+ List<String> getAssociatedUris();
+
+ String getSecurityVerifyHeader();
+
+ List<String> getServiceRouteHeaders();
+
+ String getContactUser();
+
+ String getImei();
+
+ String getPaniHeader();
+
+ String getPlaniHeader();
+
+ /**
+ * @return the user agent header from the ims config.
+ */
+ String getUserAgentHeader();
+
+ default int getMaxPayloadSizeOnUdp() {
+ return 0;
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sip/SipSessionListener.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sip/SipSessionListener.java
new file mode 100644
index 0000000..5fe61e6
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sip/SipSessionListener.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 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.sip;
+
+import javax.sip.message.Message;
+
+/**
+ * Listener for incoming messages on a {@link SipSession}.
+ */
+public interface SipSessionListener {
+
+ void onMessageReceived(Message sipMessage);
+}
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
new file mode 100644
index 0000000..13fa53a
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sip/SipUtils.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2020 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.sip;
+
+import static com.android.libraries.rcs.simpleclient.protocol.sdp.SdpUtils.SDP_CONTENT_SUB_TYPE;
+import static com.android.libraries.rcs.simpleclient.protocol.sdp.SdpUtils.SDP_CONTENT_TYPE;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.libraries.rcs.simpleclient.protocol.sdp.SimpleSdpMessage;
+
+import com.google.common.base.Ascii;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.net.InetAddresses;
+
+import gov.nist.javax.sip.Utils;
+import gov.nist.javax.sip.address.AddressFactoryImpl;
+import gov.nist.javax.sip.header.ContentType;
+import gov.nist.javax.sip.header.HeaderFactoryImpl;
+import gov.nist.javax.sip.header.Via;
+import gov.nist.javax.sip.header.extensions.SessionExpires;
+import gov.nist.javax.sip.header.ims.PPreferredIdentityHeader;
+import gov.nist.javax.sip.header.ims.PPreferredServiceHeader;
+import gov.nist.javax.sip.header.ims.SecurityVerifyHeader;
+import gov.nist.javax.sip.message.SIPMessage;
+import gov.nist.javax.sip.message.SIPRequest;
+import gov.nist.javax.sip.message.SIPResponse;
+
+import java.net.Inet6Address;
+import java.text.ParseException;
+import java.util.List;
+import java.util.UUID;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.address.AddressFactory;
+import javax.sip.address.SipURI;
+import javax.sip.address.URI;
+import javax.sip.header.ContactHeader;
+import javax.sip.header.Header;
+import javax.sip.header.HeaderFactory;
+import javax.sip.header.ViaHeader;
+import javax.sip.message.Request;
+import javax.sip.message.Response;
+
+/** Collections of utility functions for SIP */
+public final class SipUtils {
+ private static final String TAG = "SipUtils";
+ private static final String SUPPORTED_TIMER_TAG = "timer";
+ private static final String ICSI_REF_PARAM_NAME = "+g.3gpp.icsi-ref";
+ private static final String SIP_INSTANCE_PARAM_NAME = "+sip.instance";
+ private static final String CPM_SESSION_FEATURE_TAG_PARAM_VALUE =
+ "\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"";
+ private static final String CPM_SESSION_FEATURE_TAG_FULL_STRING =
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"";
+ private static final String CPM_SESSION_SERVICE_NAME =
+ "urn:urn-7:3gpp-service.ims.icsi.oma.cpm.session";
+ private static final String CONTRIBUTION_ID_HEADER_NAME = "Contribution-ID";
+ private static final String CONVERSATION_ID_HEADER_NAME = "Conversation-ID";
+ private static final String ACCEPT_CONTACT_HEADER_NAME = "Accept-Contact";
+ private static final String PANI_HEADER_NAME = "P-Access-Network-Info";
+ private static final String PLANI_HEADER_NAME = "P-Last-Access-Network-Info";
+ private static final String USER_AGENT_HEADER = "RcsTestClient";
+
+ private static AddressFactory sAddressFactory = new AddressFactoryImpl();
+ private static HeaderFactory sHeaderFactory = new HeaderFactoryImpl();
+
+ private SipUtils() {
+ }
+
+ /**
+ * Try to parse the given uri.
+ *
+ * @throws IllegalArgumentException in case of parsing error.
+ */
+ public static URI createUri(String uri) {
+ try {
+ return sAddressFactory.createURI(uri);
+ } catch (ParseException exception) {
+ throw new IllegalArgumentException("URI cannot be created", exception);
+ }
+ }
+
+ /**
+ * Create SIP INVITE request for a CPM 1:1 chat.
+ *
+ * @param configuration The SipSessionConfiguration instance used for populating SIP headers.
+ * @param targetUri The uri to be targeted.
+ * @param conversationId The id to be contained in Conversation-ID header.
+ */
+ public static SIPRequest buildInvite(
+ SipSessionConfiguration configuration,
+ String targetUri,
+ String conversationId,
+ byte[] content)
+ throws ParseException {
+ String address = configuration.getLocalIpAddress();
+ int port = configuration.getLocalPort();
+ String transport = configuration.getSipTransport();
+ List<String> associatedUris = configuration.getAssociatedUris();
+ String preferredUri = Iterables.getFirst(associatedUris,
+ configuration.getPublicUserIdentity());
+
+ SIPRequest request = new SIPRequest();
+ request.setMethod(Request.INVITE);
+
+ URI remoteUri = createUri(targetUri);
+ request.setRequestURI(remoteUri);
+ request.setFrom(
+ sHeaderFactory.createFromHeader(
+ sAddressFactory.createAddress(preferredUri),
+ Utils.getInstance().generateTag()));
+ request.setTo(
+ sHeaderFactory.createToHeader(sAddressFactory.createAddress(remoteUri), null));
+
+ ViaHeader viaHeader = null;
+
+ try {
+ // Set a default Max-Forwards header.
+ request.setMaxForwards(sHeaderFactory.createMaxForwardsHeader(70));
+ request.setCSeq(sHeaderFactory.createCSeqHeader(1L, Request.INVITE));
+ viaHeader =
+ sHeaderFactory.createViaHeader(
+ address, port, transport, Utils.getInstance().generateBranchId());
+ request.setVia(ImmutableList.of(viaHeader));
+
+ // Set a default Session-Expires header.
+ SessionExpires sessionExpires = new SessionExpires();
+ sessionExpires.setRefresher("uac");
+ sessionExpires.setExpires(1800);
+ request.setHeader(sessionExpires);
+
+ // Set a Contact header.
+ request.setHeader(generateContactHeader(configuration));
+
+ // Set PANI and PLANI if exists
+ if (configuration.getPaniHeader() != null) {
+ request.setHeader(
+ sHeaderFactory.createHeader(PANI_HEADER_NAME,
+ configuration.getPaniHeader()));
+ }
+ if (configuration.getPlaniHeader() != null) {
+ request.setHeader(
+ sHeaderFactory.createHeader(PLANI_HEADER_NAME,
+ configuration.getPlaniHeader()));
+ }
+ } catch (InvalidArgumentException e) {
+ // Nothing to do here
+ Log.e(TAG, e.getMessage());
+ }
+
+ request.setCallId(UUID.randomUUID().toString());
+ request.setHeader(sHeaderFactory.createHeader(CONVERSATION_ID_HEADER_NAME, conversationId));
+ request.setHeader(
+ sHeaderFactory.createHeader(CONTRIBUTION_ID_HEADER_NAME,
+ UUID.randomUUID().toString()));
+
+ String acceptContact = "*;" + CPM_SESSION_FEATURE_TAG_FULL_STRING;
+ request.setHeader(sHeaderFactory.createHeader(ACCEPT_CONTACT_HEADER_NAME, acceptContact));
+ request.setHeader(sHeaderFactory.createSupportedHeader(SUPPORTED_TIMER_TAG));
+ request.setHeader(sHeaderFactory.createHeader(PPreferredIdentityHeader.NAME, preferredUri));
+ request.setHeader(
+ sHeaderFactory.createHeader(PPreferredServiceHeader.NAME,
+ CPM_SESSION_SERVICE_NAME));
+
+ // Set a Security-Verify header if exist.
+ String securityVerify = configuration.getSecurityVerifyHeader();
+ if (!TextUtils.isEmpty(securityVerify)) {
+ request.setHeader(
+ sHeaderFactory.createHeader(SecurityVerifyHeader.NAME, securityVerify));
+ }
+
+ // Add Route headers.
+ List<String> serviceRoutes = configuration.getServiceRouteHeaders();
+ if (!serviceRoutes.isEmpty()) {
+ for (String sr : serviceRoutes) {
+ request.addHeader(
+ sHeaderFactory.createRouteHeader(sAddressFactory.createAddress(sr)));
+ }
+ }
+
+ String userAgent = configuration.getUserAgentHeader();
+ userAgent = (userAgent == null) ? USER_AGENT_HEADER : userAgent;
+ request.addHeader(sHeaderFactory.createUserAgentHeader(ImmutableList.of(userAgent)));
+
+ request.setMessageContent(SDP_CONTENT_TYPE, SDP_CONTENT_SUB_TYPE, content);
+
+ if (viaHeader != null && Ascii.equalsIgnoreCase("udp", transport)) {
+ String newTransport =
+ determineTransportBySize(configuration, request.encodeAsBytes("udp").length);
+ if (!Ascii.equalsIgnoreCase(transport, newTransport)) {
+ viaHeader.setTransport(newTransport);
+ }
+ }
+
+ return request;
+ }
+
+ private static ContactHeader generateContactHeader(SipSessionConfiguration configuration)
+ throws ParseException {
+ String host = configuration.getLocalIpAddress();
+ if (isIPv6Address(host)) {
+ host = "[" + host + "]";
+ }
+
+ String userPart = configuration.getContactUser();
+ SipURI uri = sAddressFactory.createSipURI(userPart, host);
+ try {
+ uri.setPort(configuration.getLocalPort());
+ uri.setTransportParam(configuration.getSipTransport());
+ } catch (Exception e) {
+ // Shouldn't be here.
+ }
+
+ ContactHeader contactHeader =
+ sHeaderFactory.createContactHeader(sAddressFactory.createAddress(uri));
+
+ // Add +sip.instance param.
+ String sipInstance = "\"<urn:gsma:imei:" + configuration.getImei() + ">\"";
+ contactHeader.setParameter(SIP_INSTANCE_PARAM_NAME, sipInstance);
+
+ // Add CPM feature tag.
+ uri.setTransportParam(configuration.getSipTransport());
+ contactHeader.setParameter(ICSI_REF_PARAM_NAME, CPM_SESSION_FEATURE_TAG_PARAM_VALUE);
+
+ return contactHeader;
+ }
+
+ /**
+ * Create a SIP BYE request for terminating the chat session.
+ *
+ * @param invite the initial INVITE request of the chat session.
+ */
+ public static SIPRequest buildBye(SIPRequest invite) throws ParseException {
+ SIPRequest request = new SIPRequest();
+ request.setRequestURI(invite.getRequestURI());
+ request.setMethod(Request.BYE);
+ try {
+ long cSeqNumber = invite.getCSeq().getSeqNumber();
+ request.setHeader(sHeaderFactory.createCSeqHeader(cSeqNumber, Request.BYE));
+ } catch (InvalidArgumentException e) {
+ // Nothing to do here
+ }
+
+ request.setCallId(invite.getCallId());
+
+ Via via = (Via) invite.getTopmostVia().clone();
+ via.removeParameter("branch");
+ via.setBranch(Utils.getInstance().generateBranchId());
+ request.addHeader(via);
+ request.addHeader(
+ sHeaderFactory.createFromHeader(invite.getFrom().getAddress(),
+ invite.getFrom().getTag()));
+ request.addHeader(
+ sHeaderFactory.createToHeader(invite.getTo().getAddress(),
+ invite.getTo().getTag()));
+
+ return request;
+ }
+
+ /**
+ * Create SIP INVITE response for a CPM 1:1 chat.
+ *
+ * @param configuration The SipSessionConfiguration instance used for populating SIP headers.
+ * @param invite the initial INVITE request of the chat session.
+ * @param code The status code of the response.
+ */
+ public static SIPResponse buildInviteResponse(
+ SipSessionConfiguration configuration,
+ SIPRequest invite,
+ int code,
+ @Nullable SimpleSdpMessage sdp)
+ throws ParseException {
+ SIPResponse response = invite.createResponse(code);
+ if (code == Response.OK) {
+ response.setMessageContent(SDP_CONTENT_TYPE, SDP_CONTENT_SUB_TYPE, sdp.encode());
+ }
+ response.setToTag(Utils.getInstance().generateTag());
+
+ // Set a Contact header.
+ response.setHeader(generateContactHeader(configuration));
+
+ // Set Conversation-ID and Contribution-ID
+ Header conversationIdHeader = invite.getHeader(CONVERSATION_ID_HEADER_NAME);
+ if (conversationIdHeader != null) {
+ response.setHeader((Header) conversationIdHeader.clone());
+ }
+ Header contributionIdHeader = invite.getHeader(CONTRIBUTION_ID_HEADER_NAME);
+ if (conversationIdHeader != null) {
+ response.setHeader((Header) contributionIdHeader.clone());
+ }
+
+ // Set P-Preferred-Identity
+ List<String> associatedUris = configuration.getAssociatedUris();
+ String preferredUri = Iterables.getFirst(associatedUris,
+ configuration.getPublicUserIdentity());
+ response.setHeader(
+ sHeaderFactory.createHeader(PPreferredIdentityHeader.NAME, preferredUri));
+
+ // Set PANI and PLANI if exists
+ if (configuration.getPaniHeader() != null) {
+ response.setHeader(
+ sHeaderFactory.createHeader(PANI_HEADER_NAME, configuration.getPaniHeader()));
+ }
+ if (configuration.getPlaniHeader() != null) {
+ response.setHeader(
+ sHeaderFactory.createHeader(PLANI_HEADER_NAME, configuration.getPlaniHeader()));
+ }
+ return response;
+ }
+
+ public static boolean isIPv6Address(String address) {
+ return InetAddresses.forString(address) instanceof Inet6Address;
+ }
+
+ /** Return whether the SIP message has a SDP content or not */
+ public static boolean hasSdpContent(SIPMessage message) {
+ ContentType contentType = message.getContentTypeHeader();
+ return contentType != null
+ && TextUtils.equals(contentType.getContentType(), SDP_CONTENT_TYPE)
+ && TextUtils.equals(contentType.getContentSubType(), SDP_CONTENT_SUB_TYPE);
+ }
+
+ private static String determineTransportBySize(SipSessionConfiguration configuration,
+ int size) {
+ if (size > configuration.getMaxPayloadSizeOnUdp()) {
+ return "tcp";
+ }
+ return "udp";
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/ProvisioningController.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/ProvisioningController.java
new file mode 100644
index 0000000..f987c67
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/ProvisioningController.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.provisioning;
+
+import android.telephony.ims.ImsException;
+
+/**
+ * Access to provisioning functionality and data.
+ */
+public interface ProvisioningController {
+
+ /**
+ * Triggers a new provisioning request. If the device is not already provisioned, it requests
+ * the
+ * provisioning flow and sets up callbacks. If the provisioning is already present, it
+ * requests a
+ * new provisioning config from the server.
+ *
+ * @throws ImsException if there is an error.
+ */
+ void triggerProvisioning() throws ImsException;
+
+ /** Is Single-Reg enabled for the default call SIM ? */
+ boolean isRcsVolteSingleRegistrationCapable() throws ImsException;
+
+ void onConfigurationChange(ProvisioningStateChangeCallback cb);
+
+ // Unregister the callback to the framework's provisioning change.
+ void unRegister();
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/ProvisioningControllerImpl.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/ProvisioningControllerImpl.java
new file mode 100644
index 0000000..06d3835
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/ProvisioningControllerImpl.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.provisioning;
+
+
+/**
+ * Actual implementation build upon ProvisioningManager.
+ */
+public class ProvisioningControllerImpl implements ProvisioningController {
+
+ @Override
+ public void triggerProvisioning() {
+ throw new IllegalStateException("Not implemented!");
+ }
+
+ @Override
+ public void onConfigurationChange(ProvisioningStateChangeCallback cb) {
+ throw new IllegalStateException("Not implemented!");
+ }
+
+ @Override
+ public boolean isRcsVolteSingleRegistrationCapable() {
+ throw new IllegalStateException("Not implemented.");
+ }
+
+ @Override
+ public void unRegister() {
+ throw new IllegalStateException("Not implemented.");
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/ProvisioningStateChangeCallback.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/ProvisioningStateChangeCallback.java
new file mode 100644
index 0000000..17a0291
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/ProvisioningStateChangeCallback.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2020 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.provisioning;
+
+/**
+ * A callback for provisioning state change notifications.
+ */
+public interface ProvisioningStateChangeCallback {
+ void notifyConfigChanged(byte[] configXml);
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java
new file mode 100644
index 0000000..b8b1f21
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2020 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.provisioning;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Build.VERSION_CODES;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.ProvisioningManager.RcsProvisioningCallback;
+import android.telephony.ims.RcsClientConfiguration;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RequiresPermission;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.Optional;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * "Fake" provisioning implementation for supplying a static config when testing ProvisioningManager
+ * is unnecessary. State changes are invoked manually.
+ */
+public class StaticConfigProvisioningController implements ProvisioningController {
+
+ private static final String TAG = StaticConfigProvisioningController.class.getSimpleName();
+ private final ProvisioningManager provisioningManager;
+ private final ExecutorService executorService = Executors.newSingleThreadExecutor();
+ private Optional<RcsProvisioningCallback> storedCallback = Optional.empty();
+ private Optional<ProvisioningStateChangeCallback> stateChangeCallback = Optional.empty();
+ private Optional<byte[]> configXmlData = Optional.empty();
+ private Context context;
+
+ private StaticConfigProvisioningController(int subId, Context context) {
+ this.provisioningManager = ProvisioningManager.createForSubscriptionId(subId);
+ this.context = context;
+ }
+
+ @RequiresApi(api = VERSION_CODES.R)
+ public static StaticConfigProvisioningController createWithDefaultSubscriptionId(
+ Context context) {
+ return new StaticConfigProvisioningController(
+ SubscriptionManager.getActiveDataSubscriptionId(), context);
+ }
+
+ /** Create ProvisioningController */
+ public static StaticConfigProvisioningController createForSubscriptionId(int subscriptionId,
+ Context context) {
+ return new StaticConfigProvisioningController(subscriptionId, context);
+ }
+
+ // Static configuration.
+ private RcsClientConfiguration getDefaultClientConfiguration() {
+ SharedPreferences pref = context.getSharedPreferences("CONFIG", context.MODE_PRIVATE);
+
+ return new RcsClientConfiguration(
+ /*rcsVersion=*/ pref.getString("RCS_VERSION", "6.0"),
+ /*rcsProfile=*/ pref.getString("RCS_PROFILE", "UP_1.0"),
+ /*clientVendor=*/ "Goog",
+ /*clientVersion=*/ "RCSAndrd-1.0");
+ }
+
+ @Override
+ @RequiresPermission(value = "Manifest.permission.READ_PRIVILEGED_PHONE_STATE")
+ public void triggerProvisioning() throws ImsException {
+ boolean isRegistered = false;
+ synchronized (this) {
+ isRegistered = storedCallback.isPresent();
+ }
+
+ if (isRegistered) {
+ triggerReconfiguration();
+ } else {
+ register();
+ }
+ }
+
+ @Override
+ public void onConfigurationChange(ProvisioningStateChangeCallback cb) {
+ stateChangeCallback = Optional.of(cb);
+ }
+
+ @RequiresPermission(value = "Manifest.permission.READ_PRIVILEGED_PHONE_STATE")
+ public void register() throws ImsException {
+ register(getDefaultClientConfiguration());
+ }
+
+ @SuppressWarnings("LogConditional")
+ // TODO(b/171976006) Use 'tools:ignore=' in manifest instead.
+ @RequiresPermission(value = "Manifest.permission.READ_PRIVILEGED_PHONE_STATE")
+ public void register(@NonNull RcsClientConfiguration clientConfiguration) throws ImsException {
+ Log.i(TAG, "Using configuration: " + clientConfiguration.toString());
+ provisioningManager.setRcsClientConfiguration(clientConfiguration);
+
+ RcsProvisioningCallback callback =
+ new RcsProvisioningCallback() {
+ @Override
+ public void onConfigurationChanged(@NonNull byte[] configXml) {
+ Log.i(TAG, "RcsProvisioningCallback.onConfigurationChanged called.");
+ synchronized (this) {
+ configXmlData = Optional.of(configXml);
+ }
+ stateChangeCallback.ifPresent(cb -> cb.notifyConfigChanged(configXml));
+ }
+
+ @RequiresApi(api = VERSION_CODES.R)
+ @Override
+ public void onConfigurationReset() {
+ Log.i(TAG, "RcsProvisioningCallback.onConfigurationReset called.");
+ synchronized (this) {
+ configXmlData = Optional.empty();
+ }
+ stateChangeCallback.ifPresent(cb -> cb.notifyConfigChanged(null));
+ }
+
+ @RequiresApi(api = VERSION_CODES.R)
+ @Override
+ public void onRemoved() {
+ Log.i(TAG, "RcsProvisioningCallback.onRemoved called.");
+ synchronized (this) {
+ configXmlData = Optional.empty();
+ }
+ stateChangeCallback.ifPresent(cb -> cb.notifyConfigChanged(null));
+ }
+ };
+
+ Log.i(TAG, "Registering the callback.");
+ synchronized (this) {
+ provisioningManager.registerRcsProvisioningCallback(executorService, callback);
+ storedCallback = Optional.of(callback);
+ }
+ }
+
+ @RequiresPermission(value = "Manifest.permission.READ_PRIVILEGED_PHONE_STATE")
+ public void unRegister() {
+ synchronized (this) {
+ RcsProvisioningCallback callback =
+ storedCallback.orElseThrow(
+ () -> new IllegalStateException("No callback present."));
+ provisioningManager.unregisterRcsProvisioningCallback(callback);
+ storedCallback = Optional.empty();
+ }
+ }
+
+ @Override
+ @RequiresPermission(value = "Manifest.permission.READ_PRIVILEGED_PHONE_STATE")
+ public boolean isRcsVolteSingleRegistrationCapable() throws ImsException {
+ return provisioningManager.isRcsVolteSingleRegistrationCapable();
+ }
+
+ public synchronized byte[] getLatestConfiguration() {
+ return configXmlData.orElseThrow(() -> new IllegalStateException("No config present"));
+ }
+
+ @VisibleForTesting
+ @RequiresPermission(value = "Manifest.permission.READ_PRIVILEGED_PHONE_STATE")
+ void triggerReconfiguration() {
+ provisioningManager.triggerRcsReconfiguration();
+ }
+
+ @VisibleForTesting
+ ProvisioningManager getProvisioningManager() {
+ return provisioningManager;
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/MessageConverter.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/MessageConverter.java
new file mode 100644
index 0000000..e3a091d
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/MessageConverter.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2020 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.registration;
+
+import android.telephony.ims.SipMessage;
+
+import gov.nist.javax.sip.header.SIPHeader;
+import gov.nist.javax.sip.message.SIPMessage;
+import gov.nist.javax.sip.parser.ParseExceptionListener;
+import gov.nist.javax.sip.parser.StringMsgParser;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.text.ParseException;
+import java.util.Iterator;
+
+import javax.sip.header.ContentLengthHeader;
+import javax.sip.message.Message;
+import javax.sip.message.Request;
+import javax.sip.message.Response;
+
+/***
+ * Class responsible of converting an RCS SIP Message
+ * {@link Message} to a Platform SIP message
+ * {@link SipMessage} and vice versa.
+ */
+public final class MessageConverter {
+
+ private MessageConverter() {
+ }
+
+ public static SipMessage toPlatformMessage(Message message) {
+ String startLine;
+ if (message instanceof Request) {
+ startLine = getRequestStartLine((Request) message);
+ } else {
+ startLine = getResponseStartLine((Response) message);
+ }
+
+ StringBuilder headers = new StringBuilder();
+ for (Iterator<SIPHeader> it = ((SIPMessage) message).getHeaders(); it.hasNext(); ) {
+ SIPHeader header = it.next();
+ if (header instanceof ContentLengthHeader) {
+ continue;
+ }
+ headers.append(header);
+ }
+
+ int length = message.getRawContent() != null ? message.getRawContent().length : 0;
+ headers
+ .append(SIPHeader.CONTENT_LENGTH)
+ .append(": ")
+ .append(length)
+ .append("\r\n");
+
+ byte[] rawContent = message.getRawContent();
+ rawContent = rawContent == null ? new byte[0] : message.getRawContent();
+ return new SipMessage(startLine, headers.toString(), rawContent);
+ }
+
+ public static Message toStackMessage(SipMessage message) throws ParseException {
+ // The AOSP version of nist-sip has a parseSIPMessage() method that has a different
+ // contract.
+ // Fallback to parseSIPMessage(byte[] msgBuffer) in case the first attempt fails.
+ Method method;
+ try {
+ method =
+ StringMsgParser.class.getDeclaredMethod(
+ "parseSIPMessage",
+ byte[].class,
+ boolean.class,
+ boolean.class,
+ ParseExceptionListener.class);
+ return (Message)
+ method.invoke(
+ new StringMsgParser(),
+ message.toEncodedMessage(),
+ true,
+ false,
+ (ParseExceptionListener)
+ (ex, sipMessage, headerClass, headerText, messageText) -> {
+ throw ex;
+ });
+ } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+ try {
+ method = StringMsgParser.class.getDeclaredMethod("parseSIPMessage", byte[].class);
+ return (Message) method.invoke(new StringMsgParser(), message.toEncodedMessage());
+ } catch (IllegalAccessException | InvocationTargetException
+ | NoSuchMethodException ex) {
+ ex.printStackTrace();
+ throw new ParseException("Failed to invoke parseSIPMessage", 0);
+ }
+ }
+ }
+
+ private static String getRequestStartLine(Request request) {
+ StringBuilder startLine = new StringBuilder();
+
+ startLine.append(request.getMethod());
+ startLine.append(" ");
+ startLine.append(request.getRequestURI());
+ startLine.append(" SIP/2.0\r\n");
+
+ return startLine.toString();
+ }
+
+ private static String getResponseStartLine(Response response) {
+ StringBuilder startLine = new StringBuilder();
+
+ startLine.append("SIP/2.0 ");
+ startLine.append(response.getStatusCode());
+ startLine.append(" ");
+ startLine.append(response.getReasonPhrase());
+ startLine.append("\r\n");
+
+ return startLine.toString();
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationController.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationController.java
new file mode 100644
index 0000000..8bbe327
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationController.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 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.registration;
+
+import com.android.libraries.rcs.simpleclient.service.ImsService;
+
+/**
+ * Access to registration functionality.
+ */
+public interface RegistrationController {
+
+ /**
+ * Register the given ImsService with the backend and use the callback to return a SipSession
+ * for sending and receiving SIP messages.
+ */
+ void register(ImsService imsService, RegistrationStateChangeCallback callback);
+
+ void deregister();
+
+ void onRegistrationStateChange(RegistrationStateChangeCallback callback);
+}
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
new file mode 100644
index 0000000..6a70f3d
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2020 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.registration;
+
+import android.os.Build.VERSION_CODES;
+import android.telephony.ims.DelegateRegistrationState;
+import android.telephony.ims.DelegateRequest;
+import android.telephony.ims.FeatureTagState;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsManager;
+import android.telephony.ims.SipDelegateConfiguration;
+import android.telephony.ims.SipDelegateConnection;
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.SipMessage;
+import android.telephony.ims.stub.DelegateConnectionMessageCallback;
+import android.telephony.ims.stub.DelegateConnectionStateCallback;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import com.android.libraries.rcs.simpleclient.protocol.sip.SipSession;
+import com.android.libraries.rcs.simpleclient.protocol.sip.SipSessionConfiguration;
+import com.android.libraries.rcs.simpleclient.protocol.sip.SipSessionListener;
+import com.android.libraries.rcs.simpleclient.service.ImsService;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+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;
+import java.util.concurrent.Executor;
+
+import javax.sip.message.Message;
+
+/**
+ * Actual implementation built upon SipDelegateConnection as a SIP transport.
+ * Feature tag registration state changes will trigger callbacks SimpleRcsClient to
+ * enable/disable related ImsServices.
+ */
+@RequiresApi(api = VERSION_CODES.R)
+public class RegistrationControllerImpl implements RegistrationController {
+ private static final String TAG = RegistrationControllerImpl.class.getCanonicalName();
+
+ private final Executor executor;
+ private final int subscriptionId;
+ private SipDelegateManager sipDelegateManager;
+ private RegistrationContext context;
+ private RegistrationStateChangeCallback callback;
+
+ public RegistrationControllerImpl(int subscriptionId, Executor executor,
+ ImsManager imsManager) {
+ this.subscriptionId = subscriptionId;
+ this.executor = executor;
+ this.sipDelegateManager = imsManager.getSipDelegateManager(subscriptionId);
+ }
+
+ @Override
+ public void register(ImsService imsService, RegistrationStateChangeCallback callback) {
+ Log.i(TAG, "register");
+ this.callback = callback;
+ context = new RegistrationContext(this, imsService);
+ context.register();
+ }
+
+ @Override
+ public void deregister() {
+ Log.i(TAG, "deregister");
+ if (context != null && context.sipDelegateConnection != null) {
+ sipDelegateManager.destroySipDelegate(context.sipDelegateConnection,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ }
+ }
+
+ @Override
+ public void onRegistrationStateChange(RegistrationStateChangeCallback callback) {
+ throw new IllegalStateException("Not implemented!");
+ }
+
+ /**
+ * Envelopes the registration data for a single ImsService instance.
+ */
+ private class RegistrationContext implements SipSession, SipSessionConfiguration {
+
+ private final RegistrationControllerImpl controller;
+ private final ImsService imsService;
+ private final SettableFuture<SipSession> sessionFuture = SettableFuture.create();
+
+ protected SipDelegateConnection sipDelegateConnection;
+ private SipDelegateConfiguration mConfiguration;
+ private final DelegateConnectionStateCallback connectionCallback =
+ new DelegateConnectionStateCallback() {
+
+ @Override
+ public void onCreated(SipDelegateConnection c) {
+ sipDelegateConnection = c;
+ }
+
+ @Override
+ public void onConfigurationChanged(
+ SipDelegateConfiguration registeredSipConfig) {
+ Log.d(
+ TAG,
+ "onSipConfigurationChanged: version="
+ + registeredSipConfig.getVersion());
+ Log.i(TAG, "onSipConfigurationChanged: " + registeredSipConfig);
+ dumpConfig(registeredSipConfig);
+ RegistrationContext.this.mConfiguration = registeredSipConfig;
+ }
+
+ @Override
+ public void onFeatureTagStatusChanged(
+ @NonNull DelegateRegistrationState registrationState,
+ @NonNull Set<FeatureTagState> deniedFeatureTags) {
+ dumpFeatureTagState(registrationState, deniedFeatureTags);
+ if (registrationState
+ .getRegisteredFeatureTags()
+ .containsAll(imsService.getFeatureTags())) {
+ // registered;
+ callback.onSuccess(RegistrationContext.this);
+ } else {
+ callback.onFailure("feature tag not registered");
+ }
+ }
+
+ @Override
+ public void onDestroyed(int reason) {
+ Log.d(TAG, "onDestroyed:" + reason);
+ callback.onFailure("delegate destroyed");
+
+ }
+ };
+ private SipSessionListener sipSessionListener;
+ // Callback for incoming messages on the modem connection
+ private final DelegateConnectionMessageCallback messageCallback =
+ new DelegateConnectionMessageCallback() {
+ @Override
+ public void onMessageReceived(@NonNull SipMessage message) {
+ message = repairHeaderSection(message);
+ SipSessionListener listener = sipSessionListener;
+ if (listener != null) {
+ try {
+ listener.onMessageReceived(
+ MessageConverter.toStackMessage(message));
+ } catch (ParseException e) {
+ // TODO: logging here
+ }
+ }
+ }
+
+ @Override
+ public void onMessageSendFailure(@NonNull String viaTransactionId, int reason) {
+ Log.i(TAG, "onMessageSendFailure: viaTransactionId:"
+ + viaTransactionId + ", reason:" + reason);
+ }
+
+ @Override
+ public void onMessageSent(@NonNull String viaTransactionId) {
+ Log.i(TAG, "onMessageSent: viaTransactionId:" + viaTransactionId);
+ }
+
+ };
+
+ public RegistrationContext(RegistrationControllerImpl controller,
+ ImsService imsService) {
+ this.controller = controller;
+ this.imsService = imsService;
+ }
+
+ public ListenableFuture<SipSession> getFuture() {
+ return sessionFuture;
+ }
+
+ @Override
+ public SipSessionConfiguration getSessionConfiguration() {
+ return this;
+ }
+
+ public void register() {
+ Log.i(TAG, "createSipDelegate");
+ DelegateRequest request = new DelegateRequest(imsService.getFeatureTags());
+ try {
+ controller.sipDelegateManager.createSipDelegate(
+ request, controller.executor, connectionCallback, messageCallback);
+ } catch (ImsException e) {
+ // TODO: ...
+ }
+ }
+
+ private void dumpFeatureTagState(DelegateRegistrationState registrationState,
+ Set<FeatureTagState> deniedFeatureTags) {
+ StringBuilder stringBuilder = new StringBuilder(
+ "onFeatureTagStatusChanged ").append(
+ " deniedFeatureTags:[");
+ Iterator<FeatureTagState> iterator = deniedFeatureTags.iterator();
+ while (iterator.hasNext()) {
+ FeatureTagState featureTagState = iterator.next();
+ stringBuilder.append(featureTagState.getFeatureTag()).append(" ").append(
+ featureTagState.getState());
+ }
+ Set<String> registeredFt = registrationState.getRegisteredFeatureTags();
+ Iterator<String> iteratorStr = registeredFt.iterator();
+ stringBuilder.append("] registeredFT:[");
+ while (iteratorStr.hasNext()) {
+ String ft = iteratorStr.next();
+ stringBuilder.append(ft).append(" ");
+ }
+ stringBuilder.append("]");
+ String result = stringBuilder.toString();
+ Log.i(TAG, result);
+ }
+
+ private void dumpConfig(SipDelegateConfiguration config) {
+ String result = "SipDelegateConfiguration{"
+ + "mVersion=" + config.getVersion()
+ + ", \n\tmTransportType=" + config.getTransportType()
+ + ", \n\tmLocalAddr=" + config.getLocalAddress()
+ + ", \n\tmSipServerAddr=" + config.getSipServerAddress()
+ + ", \n\tmIsSipCompactFormEnabled=" + config.isSipCompactFormEnabled()
+ + ", \n\tmIsSipKeepaliveEnabled=" + config.isSipKeepaliveEnabled()
+ + ", \n\tmMaxUdpPayloadSize=" + config.getMaxUdpPayloadSizeBytes()
+ + ", \n\tmPublicUserIdentifier=" + config.getPublicUserIdentifier()
+ + ", \n\tmPrivateUserIdentifier=" + config.getPrivateUserIdentifier()
+ + ", \n\tmHomeDomain=" + config.getHomeDomain()
+ + ", \n\tmImei=" + config.getImei()
+ + ", \n\tmGruu=" + config.getPublicGruuUri()
+ + ", \n\tmSipAuthHeader=" + config.getSipAuthenticationHeader()
+ + ", \n\tmSipAuthNonce=" + config.getSipAuthenticationNonce()
+ + ", \n\tmServiceRouteHeader=" + config.getSipServiceRouteHeader()
+ + ", \n\tmPathHeader=" + config.getSipPathHeader()
+ + ", \n\tmUserAgentHeader=" + config.getSipUserAgentHeader()
+ + ", \n\tmContactUserParam=" + config.getSipContactUserParameter()
+ + ", \n\tmPaniHeader=" + config.getSipPaniHeader()
+ + ", \n\tmPlaniHeader=" + config.getSipPlaniHeader()
+ + ", \n\tmCniHeader=" + config.getSipCniHeader()
+ + ", \n\tmAssociatedUriHeader=" + config.getSipAssociatedUriHeader()
+ + ", \n\tmIpSecConfiguration=" + config.getIpSecConfiguration()
+ + ", \n\tmNatConfiguration=" + config.getNatSocketAddress() + '}';
+ Log.i(TAG, result);
+ }
+
+ @Override
+ public void setSessionListener(SipSessionListener listener) {
+ sipSessionListener = listener;
+ }
+
+ @Override
+ public ListenableFuture<Boolean> send(Message message) {
+ sipDelegateConnection.sendMessage(MessageConverter.toPlatformMessage(message),
+ getVersion());
+ // TODO: check on transaction
+ return Futures.immediateFuture(true);
+ }
+
+ // Config values here.
+
+ @Override
+ public long getVersion() {
+ return mConfiguration.getVersion();
+ }
+
+ @Override
+ public String getOutboundProxyAddr() {
+ return mConfiguration.getSipServerAddress().getAddress().getHostAddress();
+ }
+
+ @Override
+ public int getOutboundProxyPort() {
+ return mConfiguration.getSipServerAddress().getPort();
+ }
+
+ @Override
+ public String getLocalIpAddress() {
+ return mConfiguration.getLocalAddress().getAddress().getHostAddress();
+ }
+
+ @Override
+ public int getLocalPort() {
+ return mConfiguration.getLocalAddress().getPort();
+ }
+
+ @Override
+ public String getSipTransport() {
+ int sipTransport = mConfiguration.getTransportType();
+ return (sipTransport == SipDelegateConfiguration.SIP_TRANSPORT_TCP) ? "TCP" : "UDP";
+ }
+
+ @Override
+ public String getPublicUserIdentity() {
+ return mConfiguration.getPublicUserIdentifier();
+ }
+
+ @Override
+ public String getDomain() {
+ return mConfiguration.getHomeDomain();
+ }
+
+ @Override
+ public List<String> getAssociatedUris() {
+ String associatedUris = mConfiguration.getSipAssociatedUriHeader();
+ if (!TextUtils.isEmpty(associatedUris)) {
+ return Splitter.on(',').trimResults(CharMatcher.anyOf("<>")).splitToList(
+ associatedUris);
+ }
+
+ return ImmutableList.of();
+ }
+
+ @Override
+ public String getSecurityVerifyHeader() {
+ SipDelegateConfiguration.IpSecConfiguration c = mConfiguration.getIpSecConfiguration();
+ if (c == null) {
+ return null;
+ }
+ return c.getSipSecurityVerifyHeader();
+ }
+
+ @Override
+ public List<String> getServiceRouteHeaders() {
+ String serviceRoutes =
+ mConfiguration.getSipServiceRouteHeader();
+ if (TextUtils.isEmpty(serviceRoutes)) {
+ return Collections.emptyList();
+ }
+ return Splitter.on(',').trimResults().splitToList(serviceRoutes);
+ }
+
+ @Override
+ public String getContactUser() {
+ return mConfiguration.getSipContactUserParameter();
+ }
+
+ @Override
+ public String getImei() {
+ return mConfiguration.getImei();
+ }
+
+ @Override
+ public String getPaniHeader() {
+ return mConfiguration.getSipPaniHeader();
+ }
+
+ @Override
+ public String getPlaniHeader() {
+ return mConfiguration.getSipPlaniHeader();
+ }
+
+ @Override
+ public String getUserAgentHeader() {
+ return mConfiguration.getSipUserAgentHeader();
+ }
+
+ @Override
+ public int getMaxPayloadSizeOnUdp() {
+ return mConfiguration.getMaxUdpPayloadSizeBytes() > 0
+ ? mConfiguration.getMaxUdpPayloadSizeBytes() : 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 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/registration/RegistrationStateChangeCallback.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationStateChangeCallback.java
new file mode 100644
index 0000000..570b313
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationStateChangeCallback.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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.registration;
+
+import com.android.libraries.rcs.simpleclient.protocol.sip.SipSession;
+import com.android.libraries.rcs.simpleclient.service.ImsService;
+
+/**
+ * Callback for Registration state changes.
+ */
+public interface RegistrationStateChangeCallback {
+
+ /**
+ * The given feature tags are registered with the backend and the service would be able to
+ * send and receive messages.
+ *
+ * @param imsService the newly registered service.
+ */
+ void notifyRegStateChanged(ImsService imsService);
+
+ /**callback for successful session creation */
+ void onSuccess(SipSession sipSession);
+
+ /**callback for failed session creation. */
+ void onFailure(String reason);
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/ImsService.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/ImsService.java
new file mode 100644
index 0000000..e4dca1a
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/ImsService.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 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.service;
+
+import com.android.libraries.rcs.simpleclient.SimpleRcsClientContext;
+
+import java.util.Set;
+
+/**
+ * Covers service state and feature tag association.
+ */
+public interface ImsService {
+
+ /**
+ * Associated feature tags.
+ * Services will started and stopped according to the registration state of the feature tags.
+ */
+ Set<String> getFeatureTags();
+
+ /**
+ * Services started when their feature tags are enabled from the
+ * {@link com.android.libraries.rcs.simpleclient.registration.RegistrationController}.
+ * Context is made available to the ImsService here.
+ */
+ void start(SimpleRcsClientContext context);
+
+ /**
+ * Services stopped when their feature tags are disabled from
+ * {@link com.android.libraries.rcs.simpleclient.registration.RegistrationController}
+ */
+ void stop();
+
+ /**
+ * Simple callback mechanism for monitoring feature tag/ims service state.
+ */
+ void onStateChange(StateChangeCallback cb);
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/StateChangeCallback.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/StateChangeCallback.java
new file mode 100644
index 0000000..038023a
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/StateChangeCallback.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2020 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.service;
+
+/**
+ * Callback for ImsService state changes.
+ */
+public interface StateChangeCallback {
+
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/ChatServiceException.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/ChatServiceException.java
new file mode 100644
index 0000000..bc2c611
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/ChatServiceException.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 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.service.chat;
+
+import android.text.TextUtils;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class defines an exception that can be thrown during the operation in {@link
+ * MinimalCpmChatService}
+ */
+public final class ChatServiceException extends Exception {
+
+ public static final int CODE_ERROR_UNSPECIFIED = 0;
+ public static final int CODE_ERROR_SEND_MESSAGE_FAILED = 1;
+ private int mCode = CODE_ERROR_UNSPECIFIED;
+
+ /**
+ * A new {@link ChatServiceException} with an unspecified {@link ErrorCode} code.
+ *
+ * @param message an optional message to detail the error condition more specifically.
+ */
+ public ChatServiceException(@Nullable String message) {
+ super(getMessage(message, CODE_ERROR_UNSPECIFIED));
+ }
+
+ /**
+ * A new {@link ChatServiceException} that includes an {@link ErrorCode} error code.
+ *
+ * @param message an optional message to detail the error condition more specifically.
+ */
+ public ChatServiceException(@Nullable String message, @ErrorCode int code) {
+ super(getMessage(message, code));
+ mCode = code;
+ }
+
+ /**
+ * A new {@link ChatServiceException} that includes an {@link ErrorCode} error code and a {@link
+ * Throwable} that contains the original error that was thrown to lead to this Exception.
+ *
+ * @param message an optional message to detail the error condition more specifically.
+ * @param cause the {@link Throwable} that caused this {@link ChatServiceException} to be
+ * created.
+ */
+ public ChatServiceException(
+ @Nullable String message, @ErrorCode int code, @Nullable Throwable cause) {
+ super(getMessage(message, code), cause);
+ mCode = code;
+ }
+
+ private static String getMessage(String message, int code) {
+ StringBuilder builder;
+ if (!TextUtils.isEmpty(message)) {
+ builder = new StringBuilder(message);
+ builder.append(" (code: ");
+ builder.append(code);
+ builder.append(")");
+ return builder.toString();
+ } else {
+ return "code: " + code;
+ }
+ }
+
+ @ErrorCode
+ public int getCode() {
+ return mCode;
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ CODE_ERROR_UNSPECIFIED,
+ })
+ public @interface ErrorCode {
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/ChatServiceListener.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/ChatServiceListener.java
new file mode 100644
index 0000000..5fb9dee
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/ChatServiceListener.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 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.service.chat;
+
+/** Listener for chat service events */
+public interface ChatServiceListener {
+
+ /**
+ * Received a new incoming chat session from the RCS server. The session is ready to exchange
+ * messages since it is already established once this callback is called.
+ */
+ void onIncomingSession(SimpleChatSession session);
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/ChatSessionListener.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/ChatSessionListener.java
new file mode 100644
index 0000000..eab571e
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/ChatSessionListener.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 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.service.chat;
+
+import com.android.libraries.rcs.simpleclient.protocol.cpim.SimpleCpimMessage;
+
+/** Listener for chat session events */
+public interface ChatSessionListener {
+
+ /**
+ * Received a new CPIM message via the {@link SimpleChatSession} associated with this listener.
+ *
+ * @param message Received message in the form of {@link SimpleCpimMessage}
+ */
+ void onMessageReceived(SimpleCpimMessage message);
+}
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
new file mode 100644
index 0000000..4b913f7
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/MinimalCpmChatService.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2020 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.service.chat;
+
+import android.content.Context;
+import android.telephony.ims.SipDelegateConnection;
+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;
+import com.android.libraries.rcs.simpleclient.protocol.sip.SipSessionListener;
+import com.android.libraries.rcs.simpleclient.protocol.sip.SipUtils;
+import com.android.libraries.rcs.simpleclient.service.ImsService;
+import com.android.libraries.rcs.simpleclient.service.StateChangeCallback;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import gov.nist.javax.sip.message.SIPRequest;
+import gov.nist.javax.sip.message.SIPResponse;
+
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.sip.message.Request;
+import javax.sip.message.Response;
+
+/**
+ * Minimal CPM chat session service that provides the interface creating a {@link SimpleChatSession}
+ * instance using {@link SipDelegateConnection}.
+ */
+public class MinimalCpmChatService implements ImsService {
+ public static final String CPM_SESSION_TAG =
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"";
+ private static final String TAG = MinimalCpmChatService.class.getSimpleName();
+ private final Map<String, SimpleChatSession> mTransactions = new HashMap<>();
+ private final Map<String, SimpleChatSession> mDialogs = new HashMap<>();
+
+ private final MsrpManager mMsrpManager;
+ private SimpleRcsClientContext mContext;
+
+ @Nullable
+ private ChatServiceListener mListener;
+
+ private final SipSessionListener mSipSessionListener =
+ sipMessage -> {
+ if (sipMessage instanceof SIPRequest) {
+ handleRequest((SIPRequest) sipMessage);
+ } else if (sipMessage instanceof SIPResponse) {
+ handleResponse((SIPResponse) sipMessage);
+ }
+ };
+
+ public MinimalCpmChatService(Context context) {
+ mMsrpManager = new MsrpManager(context);
+ }
+
+ @Override
+ public Set<String> getFeatureTags() {
+ return ImmutableSet.of(CPM_SESSION_TAG);
+ }
+
+ @Override
+ public void start(SimpleRcsClientContext context) {
+ mContext = context;
+ context.getSipSession().setSessionListener(mSipSessionListener);
+ }
+
+ @Override
+ public void stop() {
+ }
+
+ @Override
+ public void onStateChange(StateChangeCallback cb) {
+ }
+
+ /**
+ * Start an originating 1:1 chat session interacting with the RCS server.
+ *
+ * @param telUriContact The remote contact in the from of TEL URI
+ * @return The future will be completed with SimpleChatSession once the session is established
+ * successfully. If the session fails for any reason, return the failed future with {@link
+ * ChatServiceException}
+ */
+ public ListenableFuture<SimpleChatSession> startOriginatingChatSession(String telUriContact) {
+ Log.i(TAG, "startOriginatingChatSession");
+ SimpleChatSession session = new SimpleChatSession(mContext, this, mMsrpManager);
+ return Futures.transform(
+ session.start(telUriContact), v -> session, MoreExecutors.directExecutor());
+ }
+
+ ListenableFuture<Boolean> sendSipRequest(SIPRequest msg, SimpleChatSession session) {
+ Log.i(TAG, "sendSipRequest:\r\n" + msg);
+ if (!TextUtils.equals(msg.getMethod(), Request.ACK)) {
+ mTransactions.put(msg.getTransactionId(), session);
+ }
+
+ if (TextUtils.equals(msg.getMethod(), Request.BYE)) {
+ mDialogs.remove(msg.getDialogId(/* isServer= */ false));
+ }
+
+ SipSession sipSession = mContext.getSipSession();
+ return sipSession.send(msg);
+ }
+
+ ListenableFuture<Boolean> sendSipResponse(SIPResponse msg, SimpleChatSession session) {
+ Log.i(TAG, "sendSipResponse:\r\n" + msg);
+ if (TextUtils.equals(msg.getCSeq().getMethod(), Request.BYE)) {
+ mDialogs.remove(msg.getDialogId(/* isServer= */ true));
+ } else if (TextUtils.equals(msg.getCSeq().getMethod(), Request.INVITE)
+ && msg.getStatusCode() == Response.OK) {
+ // Cache the dialog in order to route in-dialog request to the corresponding session.
+ mDialogs.put(msg.getDialogId(/* isServer= */ true), session);
+ }
+ SipSession sipSession = mContext.getSipSession();
+ return sipSession.send(msg);
+ }
+
+ private void handleRequest(SIPRequest request) {
+ Log.i(TAG, "handleRequest:\r\n" + request);
+ String dialogId = request.getDialogId(/* isServer= */ true);
+ if (mDialogs.containsKey(dialogId)) {
+ SimpleChatSession session = mDialogs.get(dialogId);
+ session.receiveMessage(request);
+ } else if (TextUtils.equals(request.getMethod(), Request.INVITE)) {
+ SimpleChatSession session = new SimpleChatSession(mContext, this, mMsrpManager);
+ session
+ .start(request)
+ .addListener(
+ () -> {
+ ChatServiceListener listener = mListener;
+ if (listener != null) {
+ listener.onIncomingSession(session);
+ }
+ },
+ MoreExecutors.directExecutor());
+ } else {
+ // Reject non-INVITE request.
+ try {
+ SIPResponse response =
+ SipUtils.buildInviteResponse(
+ mContext.getSipSession().getSessionConfiguration(),
+ request,
+ Response.METHOD_NOT_ALLOWED,
+ null);
+ sendSipResponse(response, /* session= */ null)
+ .addListener(() -> {
+ }, MoreExecutors.directExecutor());
+ } catch (ParseException e) {
+ Log.e(TAG, "Exception while sending response", e);
+ }
+ }
+ }
+
+ private void handleResponse(SIPResponse response) {
+ Log.i(TAG, "handleResponse:\r\n" + response);
+ // catch the exception because abnormal response always causes App to crash.
+ try {
+ SimpleChatSession session = mTransactions.get(response.getTransactionId());
+ if (session != null) {
+ if (response.isFinalResponse()) {
+ mTransactions.remove(response.getTransactionId());
+
+ // Cache the dialog in order to route in-dialog request to the corresponding
+ // session.
+ if (TextUtils.equals(response.getCSeq().getMethod(), Request.INVITE)
+ && response.getStatusCode() == Response.OK) {
+ mDialogs.put(response.getDialogId(/* isServer= */ false), session);
+ }
+ }
+
+ session.receiveMessage(response);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ /** Set new listener for the chat service. */
+ public void setListener(@Nullable ChatServiceListener listener) {
+ mListener = listener;
+ }
+}
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
new file mode 100644
index 0000000..fbeb205
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/SimpleChatSession.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2020 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.service.chat;
+
+import static com.android.libraries.rcs.simpleclient.protocol.cpim.CpimUtils.CPIM_CONTENT_TYPE;
+import static com.android.libraries.rcs.simpleclient.service.chat.ChatServiceException.CODE_ERROR_SEND_MESSAGE_FAILED;
+import static com.android.libraries.rcs.simpleclient.service.chat.ChatServiceException.CODE_ERROR_UNSPECIFIED;
+
+import static java.nio.charset.StandardCharsets.UTF_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.cpim.CpimUtils;
+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;
+import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpUtils;
+import com.android.libraries.rcs.simpleclient.protocol.sdp.SdpUtils;
+import com.android.libraries.rcs.simpleclient.protocol.sdp.SimpleSdpMessage;
+import com.android.libraries.rcs.simpleclient.protocol.sip.SipSessionConfiguration;
+import com.android.libraries.rcs.simpleclient.protocol.sip.SipUtils;
+import com.android.libraries.rcs.simpleclient.service.chat.ChatServiceException.ErrorCode;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+
+import gov.nist.javax.sip.header.To;
+import gov.nist.javax.sip.header.ims.PAssertedIdentityHeader;
+import gov.nist.javax.sip.message.SIPRequest;
+import gov.nist.javax.sip.message.SIPResponse;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.UUID;
+
+import javax.sip.address.URI;
+import javax.sip.message.Message;
+import javax.sip.message.Request;
+import javax.sip.message.Response;
+
+/**
+ * Simple chat session implementation in order to send/receive a text message via SIP/MSRP
+ * connection. Currently, this supports only a outgoing CPM session.
+ */
+public class SimpleChatSession {
+ private static final String TAG = SimpleChatSession.class.getSimpleName();
+ private final SimpleRcsClientContext mContext;
+ private final MinimalCpmChatService mService;
+ private final MsrpManager mMsrpManager;
+ private final String mConversationId = UUID.randomUUID().toString();
+ private SettableFuture<Void> mStartFuture;
+ @Nullable
+ private SIPRequest mInviteRequest;
+ @Nullable
+ private URI mRemoteUri;
+ @Nullable
+ private SimpleSdpMessage mRemoteSdp;
+ @Nullable
+ private SimpleSdpMessage mLocalSdp;
+ @Nullable
+ private MsrpSession mMsrpSession;
+ @Nullable
+ private ChatSessionListener mListener;
+
+
+ public SimpleChatSession(
+ SimpleRcsClientContext context, MinimalCpmChatService service,
+ MsrpManager msrpManager) {
+ mService = service;
+ mContext = context;
+ mMsrpManager = msrpManager;
+ }
+
+ public URI getRemoteUri() {
+ return mRemoteUri;
+ }
+
+ /** Send a text message via MSRP session associated with this session. */
+ public ListenableFuture<Void> sendMessage(String msg) {
+ MsrpSession session = mMsrpSession;
+ if (session == null || mRemoteSdp == null || mLocalSdp == null) {
+ Log.e(TAG, "Session is not established");
+ return Futures.immediateFailedFuture(
+ new IllegalStateException("Session is not established"));
+ }
+
+ // 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()
+ .method(MsrpChunk.Method.SEND)
+ .transactionId(MsrpUtils.generateRandomId())
+ .content(content)
+ .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);
+ return Futures.transformAsync(session.send(msrpChunk), result -> {
+ if (result == null) {
+ return Futures.immediateFailedFuture(
+ new ChatServiceException("Failed to send a chunk",
+ CODE_ERROR_SEND_MESSAGE_FAILED));
+ }
+ if (result.responseCode() != 200) {
+ Log.d(TAG, "Received error response id=" + result.transactionId()
+ + " code=" + result.responseCode());
+ return Futures.immediateFailedFuture(
+ new ChatServiceException("Msrp response code: " + result.responseCode(),
+ CODE_ERROR_SEND_MESSAGE_FAILED));
+ }
+ return Futures.immediateFuture(null);
+ }, MoreExecutors.directExecutor());
+ }
+
+ /** Start outgoing chat session. */
+ ListenableFuture<Void> start(String telUriContact) {
+ if (mStartFuture != null) {
+ return Futures.immediateFailedFuture(
+ new ChatServiceException("Session already started"));
+ }
+
+ SettableFuture<Void> future = SettableFuture.create();
+ mStartFuture = future;
+ mRemoteUri = SipUtils.createUri(telUriContact);
+ try {
+ SipSessionConfiguration configuration =
+ mContext.getSipSession().getSessionConfiguration();
+ SimpleSdpMessage sdp = SdpUtils.createSdpForMsrp(configuration.getLocalIpAddress(),
+ false);
+ SIPRequest invite =
+ SipUtils.buildInvite(
+ mContext.getSipSession().getSessionConfiguration(),
+ telUriContact,
+ mConversationId,
+ sdp.encode().getBytes(UTF_8));
+ mInviteRequest = invite;
+ mLocalSdp = sdp;
+ Futures.addCallback(
+ mService.sendSipRequest(invite, this),
+ new FutureCallback<Boolean>() {
+ @Override
+ public void onSuccess(Boolean result) {
+ Log.i(TAG, "onSuccess:" + result);
+ if (!result) {
+ notifyFailure("Failed to send INVITE", CODE_ERROR_UNSPECIFIED);
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ Log.i(TAG, "onFailure:" + t.getMessage());
+ notifyFailure("Failed to send INVITE", CODE_ERROR_UNSPECIFIED);
+ }
+ },
+ MoreExecutors.directExecutor());
+ } catch (ParseException e) {
+ Log.e(TAG, e.getMessage());
+ e.printStackTrace();
+ return Futures.immediateFailedFuture(
+ new ChatServiceException("Failed to build INVITE"));
+ }
+
+ return future;
+ }
+
+ /** Start incoming chat session. */
+ ListenableFuture<Void> start(SIPRequest invite) {
+ mInviteRequest = invite;
+ int statusCode = Response.OK;
+ if (!SipUtils.hasSdpContent(invite)) {
+ statusCode = Response.NOT_ACCEPTABLE_HERE;
+ } else {
+ try {
+ mRemoteSdp = SimpleSdpMessage.parse(
+ new ByteArrayInputStream(invite.getRawContent()));
+ } catch (ParseException | IOException e) {
+ statusCode = Response.BAD_REQUEST;
+ }
+ }
+
+ 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(configuration, invite, statusCode,
+ sdp);
+ mLocalSdp = sdp;
+ return Futures.transform(
+ mService.sendSipResponse(response, this), result -> null,
+ MoreExecutors.directExecutor());
+ } catch (ParseException e) {
+ Log.e(TAG, "Exception while building response", e);
+ return Futures.immediateFailedFuture(e);
+ }
+ }
+
+ /** Terminate the current SIP session. */
+ public ListenableFuture<Void> terminate() {
+ if (mInviteRequest == null) {
+ return Futures.immediateFuture(null);
+ }
+ try {
+ if (mMsrpSession != null) {
+ mMsrpSession.terminate();
+ }
+ } catch (IOException e) {
+ return Futures.immediateFailedFuture(
+ new ChatServiceException(
+ "Exception while terminating MSRP session", CODE_ERROR_UNSPECIFIED));
+ }
+ try {
+
+ SettableFuture<Void> future = SettableFuture.create();
+ Futures.addCallback(
+ mService.sendSipRequest(SipUtils.buildBye(mInviteRequest), this),
+ new FutureCallback<Boolean>() {
+ @Override
+ public void onSuccess(Boolean result) {
+ future.set(null);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ future.setException(
+ new ChatServiceException("Failed to send BYE",
+ CODE_ERROR_UNSPECIFIED, t));
+ }
+ },
+ MoreExecutors.directExecutor());
+ return future;
+ } catch (ParseException e) {
+ return Futures.immediateFailedFuture(
+ new ChatServiceException("Failed to build BYE", CODE_ERROR_UNSPECIFIED));
+ }
+ }
+
+ void receiveMessage(Message msg) {
+ if (msg instanceof SIPRequest) {
+ handleSipRequest((SIPRequest) msg);
+ } else {
+ handleSipResponse((SIPResponse) msg);
+ }
+ }
+
+ private void handleSipRequest(SIPRequest request) {
+ SIPResponse response;
+ if (TextUtils.equals(request.getMethod(), Request.ACK)) {
+ // Terminating session established, start a msrp session.
+ if (mRemoteSdp != null) {
+ startMsrpSession(mRemoteSdp);
+ }
+ return;
+ }
+
+ if (TextUtils.equals(request.getMethod(), Request.BYE)) {
+ response = request.createResponse(Response.OK);
+ } else {
+ // Currently we support only INVITE and BYE.
+ response = request.createResponse(Response.METHOD_NOT_ALLOWED);
+ }
+ Futures.addCallback(
+ mService.sendSipResponse(response, this),
+ new FutureCallback<Boolean>() {
+ @Override
+ public void onSuccess(Boolean result) {
+ if (result) {
+ Log.d(
+ TAG,
+ "Response to Call-Id: "
+ + response.getCallId().getCallId()
+ + " sent successfully");
+ } else {
+ Log.d(TAG, "Failed to send response");
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ Log.d(TAG, "Exception while sending response: ", t);
+ }
+ },
+ MoreExecutors.directExecutor());
+ }
+
+ private void handleSipResponse(SIPResponse response) {
+ int code = response.getStatusCode();
+
+ // Nothing to do for a provisional response.
+ if (response.isFinalResponse()) {
+ if (code == Response.OK) {
+ handle200OK(response);
+ } else {
+ handleNon200(response);
+ }
+ }
+ }
+
+ private void handleNon200(SIPResponse response) {
+ Log.d(TAG, "Received error response code=" + response.getStatusCode());
+ notifyFailure("Received non-200 INVITE response", CODE_ERROR_UNSPECIFIED);
+ }
+
+ private void handle200OK(SIPResponse response) {
+ if (!SipUtils.hasSdpContent(response)) {
+ notifyFailure("Content is not a SDP", CODE_ERROR_UNSPECIFIED);
+ return;
+ }
+
+ SimpleSdpMessage sdp;
+ try {
+ sdp = SimpleSdpMessage.parse(new ByteArrayInputStream(response.getRawContent()));
+ } catch (ParseException | IOException e) {
+ notifyFailure("Invalid SDP in INVITE", CODE_ERROR_UNSPECIFIED);
+ return;
+ }
+
+ if (mInviteRequest == null) {
+ notifyFailure("No INVITE request sent out", CODE_ERROR_UNSPECIFIED);
+ return;
+ }
+
+ 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);
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ notifyFailure("Failed to send ACK", CODE_ERROR_UNSPECIFIED);
+ }
+ },
+ MoreExecutors.directExecutor());
+ }
+
+ private void notifyFailure(String message, @ErrorCode int code) {
+ 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(), 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"),
+ MoreExecutors.directExecutor());
+ }
+ },
+ MoreExecutors.directExecutor());
+ } else {
+ Log.e(TAG, "Address or port is not present");
+ }
+ }
+
+ 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);
+
+ 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)) {
+ Log.d(TAG, "Received CPIM: " + new String(chunk.content(), UTF_8));
+ 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;
+ }
+
+ private void updateRemoteUri(SIPRequest request) {
+ PAssertedIdentityHeader pAssertedIdentityHeader =
+ (PAssertedIdentityHeader) request.getHeader("P-Asserted-Identity");
+ if (pAssertedIdentityHeader == null) {
+ mRemoteUri = request.getFrom().getAddress().getURI();
+ } else {
+ mRemoteUri = pAssertedIdentityHeader.getAddress().getURI();
+ }
+ }
+}
diff --git a/tests/Android.bp b/tests/Android.bp
index 7ed234e..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",
@@ -33,10 +42,15 @@
instrumentation_for: "TeleService",
static_libs: [
+ "androidx.test.core",
+ "androidx.test.espresso.core",
+ "androidx.test.ext.junit",
"androidx.test.rules",
"mockito-target-minus-junit4",
- "androidx.test.espresso.core",
+ "telephony-common-testing",
+ "testng",
"truth-prebuilt",
+ "testables",
],
test_suites: [
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index d434650..174d22e 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -31,6 +31,7 @@
adb shell am start -n com.android.phone.tests/.CallDialTest
-->
<activity android:name="CallDialTest"
+ android:exported="true"
android:label="@string/callDialTestLabel">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -39,6 +40,7 @@
</activity>
<service android:name="SendInstantTextTestService"
+ android:exported="true"
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
<intent-filter>
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
diff --git a/tests/src/com/android/TelephonyTestBase.java b/tests/src/com/android/TelephonyTestBase.java
index 132d893..09abb15 100644
--- a/tests/src/com/android/TelephonyTestBase.java
+++ b/tests/src/com/android/TelephonyTestBase.java
@@ -27,6 +27,7 @@
import org.mockito.MockitoAnnotations;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
@@ -58,6 +59,21 @@
PhoneConfigurationManager.unregisterAllMultiSimConfigChangeRegistrants();
}
+ protected final boolean waitForExecutorAction(Executor executor, long timeoutMillis) {
+ final CountDownLatch lock = new CountDownLatch(1);
+ executor.execute(() -> {
+ lock.countDown();
+ });
+ while (lock.getCount() > 0) {
+ try {
+ return lock.await(timeoutMillis, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+ }
+ return true;
+ }
+
protected final void waitForHandlerAction(Handler h, long timeoutMillis) {
final CountDownLatch lock = new CountDownLatch(1);
h.post(lock::countDown);
diff --git a/tests/src/com/android/TestContext.java b/tests/src/com/android/TestContext.java
index c5b9b1e..5456469 100644
--- a/tests/src/com/android/TestContext.java
+++ b/tests/src/com/android/TestContext.java
@@ -17,37 +17,61 @@
package com.android;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doAnswer;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Binder;
import android.os.Handler;
+import android.os.Looper;
import android.os.PersistableBundle;
import android.telecom.TelecomManager;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsManager;
import android.test.mock.MockContext;
+import android.util.Log;
+import android.util.SparseArray;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+import java.util.HashSet;
import java.util.concurrent.Executor;
public class TestContext extends MockContext {
+ private static final String TAG = "TestContext";
+ // Stub used to grant all permissions
+ public static final String STUB_PERMISSION_ENABLE_ALL = "stub_permission_enable_all";
+
@Mock CarrierConfigManager mMockCarrierConfigManager;
@Mock TelecomManager mMockTelecomManager;
@Mock TelephonyManager mMockTelephonyManager;
@Mock SubscriptionManager mMockSubscriptionManager;
+ @Mock ImsManager mMockImsManager;
- private PersistableBundle mCarrierConfig = new PersistableBundle();
+ private SparseArray<PersistableBundle> mCarrierConfigs = new SparseArray<>();
+
+ private final HashSet<String> mPermissionTable = new HashSet<>();
public TestContext() {
MockitoAnnotations.initMocks(this);
- doReturn(mCarrierConfig).when(mMockCarrierConfigManager).getConfigForSubId(anyInt());
+ doAnswer((Answer<PersistableBundle>) invocation -> {
+ int subId = (int) invocation.getArguments()[0];
+ if (subId < 0) {
+ return new PersistableBundle();
+ }
+ PersistableBundle b = mCarrierConfigs.get(subId);
+
+ return (b != null ? b : new PersistableBundle());
+ }).when(mMockCarrierConfigManager).getConfigForSubId(anyInt());
}
@Override
@@ -94,6 +118,11 @@
}
@Override
+ public ContentResolver getContentResolver() {
+ return null;
+ }
+
+ @Override
public Object getSystemService(String name) {
switch (name) {
case (Context.CARRIER_CONFIG_SERVICE) : {
@@ -108,6 +137,9 @@
case (Context.TELEPHONY_SUBSCRIPTION_SERVICE) : {
return mMockSubscriptionManager;
}
+ case(Context.TELEPHONY_IMS_SERVICE) : {
+ return mMockImsManager;
+ }
}
return null;
}
@@ -129,7 +161,83 @@
return null;
}
- public PersistableBundle getCarrierConfig() {
- return mCarrierConfig;
+ @Override
+ public Handler getMainThreadHandler() {
+ return new Handler(Looper.getMainLooper());
+ }
+
+ /**
+ * @return CarrierConfig PersistableBundle for the subscription specified.
+ */
+ public PersistableBundle getCarrierConfig(int subId) {
+ PersistableBundle b = mCarrierConfigs.get(subId);
+ if (b == null) {
+ b = new PersistableBundle();
+ mCarrierConfigs.put(subId, b);
+ }
+ return b;
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ if (checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(permission + " denied: " + message);
+ }
+ }
+
+ @Override
+ public void enforcePermission(String permission, int pid, int uid, String message) {
+ enforceCallingOrSelfPermission(permission, message);
+ }
+
+ @Override
+ public void enforceCallingPermission(String permission, String message) {
+ enforceCallingOrSelfPermission(permission, message);
+ }
+
+ @Override
+ public int checkCallingOrSelfPermission(String permission) {
+ return checkPermission(permission, Binder.getCallingPid(), Binder.getCallingUid());
+ }
+
+ @Override
+ public int checkPermission(String permission, int pid, int uid) {
+ synchronized (mPermissionTable) {
+ if (mPermissionTable.contains(permission)
+ || mPermissionTable.contains(STUB_PERMISSION_ENABLE_ALL)) {
+ logd("checkCallingOrSelfPermission: " + permission + " return GRANTED");
+ return PackageManager.PERMISSION_GRANTED;
+ } else {
+ logd("checkCallingOrSelfPermission: " + permission + " return DENIED");
+ return PackageManager.PERMISSION_DENIED;
+ }
+ }
+ }
+
+ public void grantPermission(String permission) {
+ synchronized (mPermissionTable) {
+ if (mPermissionTable != null && permission != null) {
+ mPermissionTable.remove(STUB_PERMISSION_ENABLE_ALL);
+ mPermissionTable.add(permission);
+ }
+ }
+ }
+
+ public void revokePermission(String permission) {
+ synchronized (mPermissionTable) {
+ if (mPermissionTable != null && permission != null) {
+ mPermissionTable.remove(permission);
+ }
+ }
+ }
+
+ public void revokeAllPermissions() {
+ synchronized (mPermissionTable) {
+ mPermissionTable.clear();
+ }
+ }
+
+ private static void logd(String s) {
+ Log.d(TAG, s);
}
}
diff --git a/tests/src/com/android/TestExecutorService.java b/tests/src/com/android/TestExecutorService.java
new file mode 100644
index 0000000..7685c6d
--- /dev/null
+++ b/tests/src/com/android/TestExecutorService.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2020 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;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * An implementation of ExecutorService that just runs the requested task on the thread that it
+ * was called on for testing purposes.
+ */
+public class TestExecutorService implements ScheduledExecutorService {
+
+ private static class CompletedFuture<T> implements Future<T>, ScheduledFuture<T> {
+
+ private final Callable<T> mTask;
+ private final long mDelayMs;
+ // Wrap callable in a CompletableFuture to support delays in execution.
+ private final CompletableFuture<T> mFuture = new CompletableFuture<>();
+
+ CompletedFuture(Callable<T> task) {
+ mTask = task;
+ mDelayMs = 0;
+ }
+
+ CompletedFuture(Callable<T> task, long delayMs) {
+ mTask = task;
+ mDelayMs = delayMs;
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ return mFuture.cancel(mayInterruptIfRunning);
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return mFuture.isCancelled();
+ }
+
+ @Override
+ public boolean isDone() {
+ return mFuture.isDone();
+ }
+
+ @Override
+ public T get() throws InterruptedException, ExecutionException {
+ return mFuture.get();
+ }
+
+ @Override
+ public T get(long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ // delays not implemented, this should complete via completeTask for better control.
+ return mFuture.get(timeout, unit);
+ }
+
+ @Override
+ public long getDelay(TimeUnit unit) {
+ if (unit == TimeUnit.MILLISECONDS) {
+ return mDelayMs;
+ } else {
+ // not implemented
+ return 0;
+ }
+ }
+
+ @Override
+ public int compareTo(Delayed o) {
+ if (o == null) return 1;
+ if (o.getDelay(TimeUnit.MILLISECONDS) > mDelayMs) return -1;
+ if (o.getDelay(TimeUnit.MILLISECONDS) < mDelayMs) return 1;
+ return 0;
+ }
+
+ public void completeTask() {
+ try {
+ mFuture.complete(mTask.call());
+ } catch (Exception e) {
+ mFuture.completeExceptionally(e);
+ }
+ }
+ }
+
+ private final ArrayList<Runnable> mPendingRunnables = new ArrayList<>();
+ private final boolean mWaitToComplete;
+ private boolean mIsShutdown = false;
+
+ public TestExecutorService() {
+ mWaitToComplete = false;
+ }
+
+ /**
+ * Create a test executor service that also allows the constructor to provide a parameter to
+ * control when pending Runnables are executed.
+ * @param waitToComplete If true, this executor will wait to complete any pending Runnables
+ * until {@link #executePending()}} is called.
+ */
+ public TestExecutorService(boolean waitToComplete) {
+ mWaitToComplete = waitToComplete;
+ }
+
+ @Override
+ public void shutdown() {
+ mIsShutdown = true;
+ for (Runnable r : mPendingRunnables) {
+ r.run();
+ }
+ }
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ mIsShutdown = true;
+ List<Runnable> runnables = new ArrayList<>(mPendingRunnables);
+ mPendingRunnables.clear();
+ return runnables;
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return mIsShutdown;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return mIsShutdown;
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit) {
+ shutdown();
+ return true;
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> task) {
+ CompletedFuture<T> f = new CompletedFuture<>(task);
+ onExecute(f::completeTask);
+ return f;
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable task, T result) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public Future<?> submit(Runnable task) {
+ CompletedFuture<Void> f = new CompletedFuture<>(() -> {
+ task.run();
+ return null;
+ });
+ onExecute(f::completeTask);
+ return f;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout,
+ TimeUnit unit) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+ long millisDelay = TimeUnit.MILLISECONDS.convert(delay, unit);
+ CompletedFuture<Void> f = new CompletedFuture<>(() -> {
+ command.run();
+ return null;
+ }, millisDelay);
+ onExecute(f::completeTask);
+ return f;
+ }
+
+ @Override
+ public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+ long millisDelay = TimeUnit.MILLISECONDS.convert(delay, unit);
+ CompletedFuture<V> f = new CompletedFuture<>(callable, millisDelay);
+ onExecute(f::completeTask);
+ return f;
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period,
+ TimeUnit unit) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay,
+ long delay, TimeUnit unit) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ onExecute(command);
+ }
+
+ private void onExecute(Runnable command) {
+ if (mWaitToComplete) {
+ mPendingRunnables.add(command);
+ } else {
+ command.run();
+ }
+ }
+
+ public void executePending() {
+ for (Runnable r : mPendingRunnables) {
+ r.run();
+ }
+ mPendingRunnables.clear();
+ }
+}
diff --git a/tests/src/com/android/phone/CarrierConfigLoaderTest.java b/tests/src/com/android/phone/CarrierConfigLoaderTest.java
new file mode 100644
index 0000000..9c605da
--- /dev/null
+++ b/tests/src/com/android/phone/CarrierConfigLoaderTest.java
@@ -0,0 +1,401 @@
+/*
+ * 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.phone;
+
+import static com.android.TestContext.STUB_PERMISSION_ENABLE_ALL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.service.carrier.CarrierIdentifier;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.testing.TestableLooper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.SubscriptionInfoUpdater;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+
+/**
+ * Unit Test for CarrierConfigLoader.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CarrierConfigLoaderTest extends TelephonyTestBase {
+
+ private static final int DEFAULT_PHONE_ID = 0;
+ private static final int DEFAULT_SUB_ID = SubscriptionManager.getDefaultSubscriptionId();
+ private static final String PLATFORM_CARRIER_CONFIG_PACKAGE = "com.android.carrierconfig";
+ private static final long PLATFORM_CARRIER_CONFIG_PACKAGE_VERSION_CODE = 1;
+ private static final String CARRIER_CONFIG_EXAMPLE_KEY =
+ CarrierConfigManager.KEY_CARRIER_USSD_METHOD_INT;
+ private static final int CARRIER_CONFIG_EXAMPLE_VALUE =
+ CarrierConfigManager.USSD_OVER_CS_PREFERRED;
+
+ @Mock Resources mResources;
+ @Mock PackageManager mPackageManager;
+ @Mock PackageInfo mPackageInfo;
+ @Mock SubscriptionInfoUpdater mSubscriptionInfoUpdater;
+ @Mock SharedPreferences mSharedPreferences;
+
+ private TelephonyManager mTelephonyManager;
+ private CarrierConfigLoader mCarrierConfigLoader;
+ private Handler mHandler;
+ private HandlerThread mHandlerThread;
+ private TestableLooper mTestableLooper;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ doReturn(mSharedPreferences).when(mContext).getSharedPreferences(anyString(), anyInt());
+ doReturn(Build.FINGERPRINT).when(mSharedPreferences).getString(eq("build_fingerprint"),
+ any());
+ doReturn(mPackageManager).when(mContext).getPackageManager();
+ doReturn(mResources).when(mContext).getResources();
+ doReturn(InstrumentationRegistry.getTargetContext().getFilesDir()).when(
+ mContext).getFilesDir();
+ doReturn(PLATFORM_CARRIER_CONFIG_PACKAGE).when(mResources).getString(
+ eq(R.string.platform_carrier_config_package));
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+ doReturn(1).when(mTelephonyManager).getSupportedModemCount();
+ doReturn(1).when(mTelephonyManager).getActiveModemCount();
+ doReturn("spn").when(mTelephonyManager).getSimOperatorNameForPhone(anyInt());
+ doReturn("310260").when(mTelephonyManager).getSimOperatorNumericForPhone(anyInt());
+ doReturn(mPackageInfo).when(mPackageManager).getPackageInfo(
+ eq(PLATFORM_CARRIER_CONFIG_PACKAGE), eq(0) /*flags*/);
+ doReturn(PLATFORM_CARRIER_CONFIG_PACKAGE_VERSION_CODE).when(
+ mPackageInfo).getLongVersionCode();
+
+ mHandlerThread = new HandlerThread("CarrierConfigLoaderTest");
+ mHandlerThread.start();
+
+ mTestableLooper = new TestableLooper(mHandlerThread.getLooper());
+ mCarrierConfigLoader = new CarrierConfigLoader(mContext, mSubscriptionInfoUpdater,
+ mTestableLooper.getLooper());
+ mHandler = mCarrierConfigLoader.getHandler();
+
+ // Clear all configs to have the same starting point.
+ mCarrierConfigLoader.clearConfigForPhone(DEFAULT_PHONE_ID, false);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mContext.revokeAllPermissions();
+ mTestableLooper.destroy();
+ mHandlerThread.quit();
+ super.tearDown();
+ }
+
+ /**
+ * Verifies that SecurityException should throw when call #updateConfigForPhoneId() without
+ * MODIFY_PHONE_STATE permission.
+ */
+ @Test
+ public void testUpdateConfigForPhoneId_noPermission() throws Exception {
+ assertThrows(SecurityException.class,
+ () -> mCarrierConfigLoader.updateConfigForPhoneId(DEFAULT_PHONE_ID,
+ IccCardConstants.INTENT_VALUE_ICC_ABSENT));
+ }
+
+ /**
+ * Verifies that IllegalArgumentException should throw when call #updateConfigForPhoneId() with
+ * invalid phoneId.
+ */
+ @Test
+ public void testUpdateConfigForPhoneId_invalidPhoneId() throws Exception {
+ mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarrierConfigLoader.updateConfigForPhoneId(
+ SubscriptionManager.INVALID_PHONE_INDEX,
+ IccCardConstants.INTENT_VALUE_ICC_ABSENT));
+ }
+
+ /**
+ * Verifies that when call #updateConfigForPhoneId() with SIM absence, both carrier config from
+ * default app and carrier should be cleared but no-sim config should be loaded.
+ */
+ @Test
+ public void testUpdateConfigForPhoneId_simAbsent() throws Exception {
+ // Bypass case if default subId is not supported by device to reduce flakiness
+ if (!SubscriptionManager.isValidPhoneId(SubscriptionManager.getPhoneId(DEFAULT_SUB_ID))) {
+ return;
+ }
+ mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ doNothing().when(mContext).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class));
+
+ // Prepare a cached config to fetch from xml
+ PersistableBundle config = getTestConfig();
+ mCarrierConfigLoader.saveNoSimConfigToXml(PLATFORM_CARRIER_CONFIG_PACKAGE, config);
+ mCarrierConfigLoader.updateConfigForPhoneId(DEFAULT_PHONE_ID,
+ IccCardConstants.INTENT_VALUE_ICC_ABSENT);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mCarrierConfigLoader.getConfigFromDefaultApp(DEFAULT_PHONE_ID)).isNull();
+ assertThat(mCarrierConfigLoader.getConfigFromCarrierApp(DEFAULT_PHONE_ID)).isNull();
+ assertThat(mCarrierConfigLoader.getNoSimConfig().getInt(CARRIER_CONFIG_EXAMPLE_KEY))
+ .isEqualTo(CARRIER_CONFIG_EXAMPLE_VALUE);
+ verify(mContext).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class));
+ }
+
+ /**
+ * Verifies that with cached config in XML, calling #updateConfigForPhoneId() with SIM loaded
+ * will return the right config in the XML.
+ */
+ @Test
+ public void testUpdateConfigForPhoneId_simLoaded_withCachedConfigInXml() throws Exception {
+ // Bypass case if default subId is not supported by device to reduce flakiness
+ if (!SubscriptionManager.isValidPhoneId(SubscriptionManager.getPhoneId(DEFAULT_SUB_ID))) {
+ return;
+ }
+ mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+
+ // Prepare to make sure we can save the config into the XML file which used as cache
+ List<String> carrierPackages = List.of(PLATFORM_CARRIER_CONFIG_PACKAGE);
+ doReturn(carrierPackages).when(mTelephonyManager).getCarrierPackageNamesForIntentAndPhone(
+ nullable(Intent.class), anyInt());
+
+ // Save the sample config into the XML file
+ PersistableBundle config = getTestConfig();
+ CarrierIdentifier carrierId = mCarrierConfigLoader.getCarrierIdentifierForPhoneId(
+ DEFAULT_PHONE_ID);
+ mCarrierConfigLoader.saveConfigToXml(PLATFORM_CARRIER_CONFIG_PACKAGE, "",
+ DEFAULT_PHONE_ID, carrierId, config);
+ mCarrierConfigLoader.updateConfigForPhoneId(DEFAULT_PHONE_ID,
+ IccCardConstants.INTENT_VALUE_ICC_LOADED);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mCarrierConfigLoader.getConfigFromDefaultApp(DEFAULT_PHONE_ID).getInt(
+ CARRIER_CONFIG_EXAMPLE_KEY)).isEqualTo(CARRIER_CONFIG_EXAMPLE_VALUE);
+
+ }
+
+ /**
+ * Verifies that SecurityException should throw if call #overrideConfig() without
+ * MODIFY_PHONE_STATE permission.
+ */
+ @Test
+ public void testOverrideConfig_noPermission() throws Exception {
+ assertThrows(SecurityException.class,
+ () -> mCarrierConfigLoader.overrideConfig(DEFAULT_SUB_ID, PersistableBundle.EMPTY,
+ false));
+ }
+
+ /**
+ * Verifies IllegalArgumentException should throw if call #overrideConfig() with invalid subId.
+ */
+ @Test
+ public void testOverrideConfig_invalidSubId() throws Exception {
+ mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+
+ assertThrows(IllegalArgumentException.class, () -> mCarrierConfigLoader.overrideConfig(
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID, new PersistableBundle(), false));
+ }
+
+ /**
+ * Verifies that override config is not null when calling #overrideConfig with null bundle.
+ */
+ @Test
+ public void testOverrideConfig_withNullBundle() throws Exception {
+ // Bypass case if default subId is not supported by device to reduce flakiness
+ if (!SubscriptionManager.isValidPhoneId(SubscriptionManager.getPhoneId(DEFAULT_SUB_ID))) {
+ return;
+ }
+ mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+
+ mCarrierConfigLoader.overrideConfig(DEFAULT_SUB_ID, null /*overrides*/,
+ false/*persistent*/);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mCarrierConfigLoader.getOverrideConfig(DEFAULT_PHONE_ID).isEmpty()).isTrue();
+ verify(mSubscriptionInfoUpdater).updateSubscriptionByCarrierConfigAndNotifyComplete(
+ eq(DEFAULT_PHONE_ID), eq(PLATFORM_CARRIER_CONFIG_PACKAGE),
+ any(PersistableBundle.class), any(Message.class));
+ }
+
+ /**
+ * Verifies that override config is not null when calling #overrideConfig with non-null bundle.
+ */
+ @Test
+ public void testOverrideConfig_withNonNullBundle() throws Exception {
+ // Bypass case if default subId is not supported by device to reduce flakiness
+ if (!SubscriptionManager.isValidPhoneId(SubscriptionManager.getPhoneId(DEFAULT_SUB_ID))) {
+ return;
+ }
+ mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+
+ PersistableBundle config = getTestConfig();
+ mCarrierConfigLoader.overrideConfig(DEFAULT_SUB_ID, config /*overrides*/,
+ false/*persistent*/);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mCarrierConfigLoader.getOverrideConfig(DEFAULT_PHONE_ID).getInt(
+ CARRIER_CONFIG_EXAMPLE_KEY)).isEqualTo(CARRIER_CONFIG_EXAMPLE_VALUE);
+ verify(mSubscriptionInfoUpdater).updateSubscriptionByCarrierConfigAndNotifyComplete(
+ eq(DEFAULT_PHONE_ID), eq(PLATFORM_CARRIER_CONFIG_PACKAGE),
+ any(PersistableBundle.class), any(Message.class));
+ }
+
+ /**
+ * Verifies that IllegalArgumentException should throw when calling
+ * #notifyConfigChangedForSubId() with invalid subId.
+ */
+ @Test
+ public void testNotifyConfigChangedForSubId_invalidSubId() throws Exception {
+ mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarrierConfigLoader.notifyConfigChangedForSubId(
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID));
+ }
+
+ // TODO(b/184040111): Enable test case when support disabling carrier privilege
+ // Phone/System UID always has carrier privilege (TelephonyPermission#getCarrierPrivilegeStatus)
+ // when running the test here.
+ /**
+ * Verifies that SecurityException should throw when calling notifyConfigChangedForSubId without
+ * MODIFY_PHONE_STATE permission.
+ */
+ @Ignore
+ public void testNotifyConfigChangedForSubId_noPermission() throws Exception {
+ setCarrierPrivilegesForSubId(false, DEFAULT_SUB_ID);
+
+ assertThrows(SecurityException.class,
+ () -> mCarrierConfigLoader.notifyConfigChangedForSubId(DEFAULT_SUB_ID));
+ }
+
+ /**
+ * Verifies that SecurityException should throw when calling getDefaultCarrierServicePackageName
+ * without READ_PRIVILEGED_PHONE_STATE permission.
+ */
+ @Test
+ public void testGetDefaultCarrierServicePackageName_noPermission() {
+ assertThrows(SecurityException.class,
+ () -> mCarrierConfigLoader.getDefaultCarrierServicePackageName());
+ }
+
+ /**
+ * Verifies that the right default carrier service package name is return when calling
+ * getDefaultCarrierServicePackageName with permission.
+ */
+ @Test
+ public void testGetDefaultCarrierServicePackageName_withPermission() {
+ mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+
+ assertThat(mCarrierConfigLoader.getDefaultCarrierServicePackageName())
+ .isEqualTo(PLATFORM_CARRIER_CONFIG_PACKAGE);
+ }
+
+ // TODO(b/184040111): Enable test case when support disabling carrier privilege
+ // Phone/System UID always has carrier privilege (TelephonyPermission#getCarrierPrivilegeStatus)
+ // when running the test here.
+ /**
+ * Verifies that without permission, #getConfigForSubId will return an empty PersistableBundle.
+ */
+ @Ignore
+ public void testGetConfigForSubId_noPermission() {
+ // Bypass case if default subId is not supported by device to reduce flakiness
+ if (!SubscriptionManager.isValidPhoneId(SubscriptionManager.getPhoneId(DEFAULT_SUB_ID))) {
+ return;
+ }
+ setCarrierPrivilegesForSubId(false, DEFAULT_SUB_ID);
+
+ assertThat(mCarrierConfigLoader.getConfigForSubId(DEFAULT_SUB_ID,
+ PLATFORM_CARRIER_CONFIG_PACKAGE)).isEqualTo(PersistableBundle.EMPTY);
+ }
+
+ /**
+ * Verifies that when have no DUMP permission, the #dump() method shows permission denial.
+ */
+ @Test
+ public void testDump_noPermission() {
+ StringWriter stringWriter = new StringWriter();
+ mCarrierConfigLoader.dump(new FileDescriptor(), new PrintWriter(stringWriter),
+ new String[0]);
+ stringWriter.flush();
+
+ assertThat(stringWriter.toString()).contains("Permission Denial:");
+ }
+
+ /**
+ * Verifies that when have DUMP permission, the #dump() method can dump the CarrierConfigLoader.
+ */
+ @Test
+ public void testDump_withPermission() {
+ mContext.grantPermission(android.Manifest.permission.DUMP);
+
+ StringWriter stringWriter = new StringWriter();
+ mCarrierConfigLoader.dump(new FileDescriptor(), new PrintWriter(stringWriter),
+ new String[0]);
+ stringWriter.flush();
+
+ String dumpContent = stringWriter.toString();
+ assertThat(dumpContent).contains("CarrierConfigLoader:");
+ assertThat(dumpContent).doesNotContain("Permission Denial:");
+ }
+
+ private static PersistableBundle getTestConfig() {
+ PersistableBundle config = new PersistableBundle();
+ config.putInt(CARRIER_CONFIG_EXAMPLE_KEY, CARRIER_CONFIG_EXAMPLE_VALUE);
+ return config;
+ }
+
+ private void setCarrierPrivilegesForSubId(boolean hasCarrierPrivileges, int subId) {
+ TelephonyManager mockTelephonyManager = Mockito.mock(TelephonyManager.class);
+ doReturn(mockTelephonyManager).when(mTelephonyManager).createForSubscriptionId(subId);
+ doReturn(hasCarrierPrivileges ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
+ : TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS).when(
+ mockTelephonyManager).getCarrierPrivilegeStatus(anyInt());
+ }
+}
diff --git a/tests/src/com/android/phone/LocationAccessPolicyBuilderTest.java b/tests/src/com/android/phone/LocationAccessPolicyBuilderTest.java
new file mode 100644
index 0000000..4fe2da9
--- /dev/null
+++ b/tests/src/com/android/phone/LocationAccessPolicyBuilderTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 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.phone;
+
+import static org.junit.Assert.fail;
+
+import android.os.Build;
+import android.telephony.LocationAccessPolicy;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class LocationAccessPolicyBuilderTest {
+ @Test
+ public void testBuilderMissingMinCoarse() {
+ try {
+ new LocationAccessPolicy.LocationPermissionQuery.Builder()
+ .setMethod("test")
+ .setCallingPackage("com.android.test")
+ .setCallingFeatureId(null)
+ .setCallingPid(0)
+ .setCallingUid(0)
+ .setMinSdkVersionForFine(Build.VERSION_CODES.N)
+ .build();
+ fail("Should have failed without specifying min version for coarse");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testBuilderMissingMinFine() {
+ try {
+ new LocationAccessPolicy.LocationPermissionQuery.Builder()
+ .setMethod("test")
+ .setCallingPackage("com.android.test")
+ .setCallingFeatureId(null)
+ .setCallingPid(0)
+ .setCallingUid(0)
+ .setMinSdkVersionForCoarse(Build.VERSION_CODES.N)
+ .build();
+ fail("Should have failed without specifying min version for fine");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testBuilderMissingMinEnforcement() {
+ try {
+ new LocationAccessPolicy.LocationPermissionQuery.Builder()
+ .setMethod("test")
+ .setCallingPackage("com.android.test")
+ .setCallingFeatureId(null)
+ .setCallingPid(0)
+ .setCallingUid(0)
+ .setMinSdkVersionForFine(Build.VERSION_CODES.N)
+ .setMinSdkVersionForCoarse(Build.VERSION_CODES.N)
+ .build();
+ fail("Should have failed without specifying min version for any enforcement");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+}
diff --git a/tests/src/com/android/phone/LocationAccessPolicyTest.java b/tests/src/com/android/phone/LocationAccessPolicyTest.java
index b2489e7..84bf619 100644
--- a/tests/src/com/android/phone/LocationAccessPolicyTest.java
+++ b/tests/src/com/android/phone/LocationAccessPolicyTest.java
@@ -242,6 +242,7 @@
.setAppSdkLevel(Build.VERSION_CODES.P)
.setIsDynamicLocationEnabled(false)
.setQuery(getDefaultQueryBuilder()
+ .setMinSdkVersionForEnforcement(Build.VERSION_CODES.N)
.setMinSdkVersionForFine(Build.VERSION_CODES.N)
.setMinSdkVersionForCoarse(Build.VERSION_CODES.N).build())
.setExpectedResult(LocationAccessPolicy.LocationPermissionResult.DENIED_SOFT)
@@ -254,6 +255,7 @@
.setAppSdkLevel(Build.VERSION_CODES.P)
.setIsDynamicLocationEnabled(true)
.setQuery(getDefaultQueryBuilder()
+ .setMinSdkVersionForEnforcement(Build.VERSION_CODES.N)
.setMinSdkVersionForFine(Build.VERSION_CODES.N)
.setMinSdkVersionForCoarse(Build.VERSION_CODES.N).build())
.setExpectedResult(LocationAccessPolicy.LocationPermissionResult.ALLOWED)
@@ -266,6 +268,7 @@
.setAppSdkLevel(Build.VERSION_CODES.JELLY_BEAN)
.setIsDynamicLocationEnabled(true)
.setQuery(getDefaultQueryBuilder()
+ .setMinSdkVersionForEnforcement(Build.VERSION_CODES.JELLY_BEAN)
.setMinSdkVersionForFine(Build.VERSION_CODES.M)
.setMinSdkVersionForCoarse(Build.VERSION_CODES.JELLY_BEAN).build())
.setExpectedResult(LocationAccessPolicy.LocationPermissionResult.ALLOWED)
@@ -278,6 +281,7 @@
.setAppSdkLevel(Build.VERSION_CODES.P)
.setIsDynamicLocationEnabled(true)
.setQuery(getDefaultQueryBuilder()
+ .setMinSdkVersionForEnforcement(Build.VERSION_CODES.N)
.setMinSdkVersionForFine(Build.VERSION_CODES.N)
.setMinSdkVersionForCoarse(Build.VERSION_CODES.N).build())
.setExpectedResult(LocationAccessPolicy.LocationPermissionResult.DENIED_HARD)
@@ -292,6 +296,7 @@
.setQuery(getDefaultQueryBuilder()
.setMinSdkVersionForFine(
LocationAccessPolicy.MAX_SDK_FOR_ANY_ENFORCEMENT + 1)
+ .setMinSdkVersionForEnforcement(Build.VERSION_CODES.N)
.setMinSdkVersionForCoarse(Build.VERSION_CODES.N).build())
.setExpectedResult(LocationAccessPolicy.LocationPermissionResult.ALLOWED)
.build());
@@ -303,6 +308,7 @@
.setAppSdkLevel(Build.VERSION_CODES.P)
.setIsDynamicLocationEnabled(true)
.setQuery(getDefaultQueryBuilder()
+ .setMinSdkVersionForEnforcement(Build.VERSION_CODES.N)
.setMinSdkVersionForFine(Build.VERSION_CODES.P)
.setMinSdkVersionForCoarse(Build.VERSION_CODES.N).build())
.setExpectedResult(LocationAccessPolicy.LocationPermissionResult.DENIED_HARD)
@@ -316,6 +322,7 @@
.setAppSdkLevel(Build.VERSION_CODES.P)
.setIsDynamicLocationEnabled(true)
.setQuery(getDefaultQueryBuilder()
+ .setMinSdkVersionForEnforcement(Build.VERSION_CODES.O)
.setMinSdkVersionForFine(Build.VERSION_CODES.P)
.setMinSdkVersionForCoarse(Build.VERSION_CODES.O).build())
.setExpectedResult(LocationAccessPolicy.LocationPermissionResult.DENIED_HARD)
@@ -326,6 +333,7 @@
.setAppSdkLevel(Build.VERSION_CODES.N)
.setIsDynamicLocationEnabled(true)
.setQuery(getDefaultQueryBuilder()
+ .setMinSdkVersionForEnforcement(Build.VERSION_CODES.O)
.setMinSdkVersionForFine(Build.VERSION_CODES.Q)
.setMinSdkVersionForCoarse(Build.VERSION_CODES.O).build())
.setExpectedResult(LocationAccessPolicy.LocationPermissionResult.ALLOWED)
@@ -338,6 +346,9 @@
.setAppSdkLevel(Build.VERSION_CODES.P)
.setIsDynamicLocationEnabled(true)
.setQuery(getDefaultQueryBuilder()
+ .setMinSdkVersionForEnforcement(Build.VERSION_CODES.P)
+ .setMinSdkVersionForFine(
+ LocationAccessPolicy.MAX_SDK_FOR_ANY_ENFORCEMENT + 1)
.setMinSdkVersionForCoarse(Build.VERSION_CODES.P).build())
.setExpectedResult(LocationAccessPolicy.LocationPermissionResult.ALLOWED)
.build());
diff --git a/tests/src/com/android/phone/PhoneInterfaceManagerTest.java b/tests/src/com/android/phone/PhoneInterfaceManagerTest.java
new file mode 100644
index 0000000..fe5d013
--- /dev/null
+++ b/tests/src/com/android/phone/PhoneInterfaceManagerTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.phone;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.pm.PackageManager;
+import android.telephony.RadioAccessFamily;
+import android.telephony.TelephonyManager;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.RILConstants;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+/**
+ * Unit Test for CarrierConfigLoader.
+ */
+@RunWith(AndroidJUnit4.class)
+public class PhoneInterfaceManagerTest extends TelephonyTestBase {
+ private PhoneInterfaceManager mPhoneInterfaceManager;
+
+ @Mock
+ PhoneGlobals mPhoneGlobals;
+ @Mock
+ Phone mPhone;
+ @Mock
+ PackageManager mPackageManager;
+
+ @Before
+ @UiThreadTest
+ public void setUp() throws Exception {
+ super.setUp();
+ doReturn(mPackageManager).when(mPhoneGlobals).getPackageManager();
+ doReturn(false).when(mPackageManager).hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_IMS);
+ mPhoneInterfaceManager = PhoneInterfaceManager.init(mPhoneGlobals);
+ }
+
+ @Test
+ public void cleanUpAllowedNetworkTypes_validPhoneAndSubId_doSetAllowedNetwork() {
+ long defaultNetworkType = RadioAccessFamily.getRafFromNetworkType(
+ RILConstants.PREFERRED_NETWORK_MODE);
+
+ mPhoneInterfaceManager.cleanUpAllowedNetworkTypes(mPhone, 1);
+
+ verify(mPhone).loadAllowedNetworksFromSubscriptionDatabase();
+ verify(mPhone).setAllowedNetworkTypes(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER,
+ defaultNetworkType, null);
+ }
+
+ @Test
+ public void cleanUpAllowedNetworkTypes_validPhoneAndInvalidSubId_doNotSetAllowedNetwork() {
+ long defaultNetworkType = RadioAccessFamily.getRafFromNetworkType(
+ RILConstants.PREFERRED_NETWORK_MODE);
+
+ mPhoneInterfaceManager.cleanUpAllowedNetworkTypes(mPhone, -1);
+
+ verify(mPhone, never()).loadAllowedNetworksFromSubscriptionDatabase();
+ verify(mPhone, never()).setAllowedNetworkTypes(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER, defaultNetworkType, null);
+ }
+}
diff --git a/tests/src/com/android/phone/RcsProvisioningMonitorTest.java b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
new file mode 100644
index 0000000..4fba922
--- /dev/null
+++ b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
@@ -0,0 +1,803 @@
+/*
+ * Copyright 2020 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.phone;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.provider.Telephony.SimInfo;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyRegistryManager;
+import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.RcsConfig;
+import android.telephony.ims.aidl.IImsConfig;
+import android.telephony.ims.aidl.IRcsConfigCallback;
+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 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;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Unit tests for RcsProvisioningMonitor
+ */
+public class RcsProvisioningMonitorTest {
+ private static final String TAG = "RcsProvisioningMonitorTest";
+ private static final String CONFIG_DEFAULT = "<?xml version=\"1.0\"?>\n"
+ + "<wap-provisioningdoc version=\"1.1\">\n"
+ + "\t<characteristic type=\"APPLICATION\">\n"
+ + "\t\t<parm name=\"AppID\" value=\"urn:oma:mo:ext-3gpp-ims:1.0\"/>\n"
+ + "\t\t<characteristic type=\"3GPP_IMS\">\n"
+ + "\t\t\t<parm name=\"AppID\" value=\"ap2001\"/>\n"
+ + "\t\t\t<parm name=\"Name\" value=\"RCS IMS Settings\"/>\n"
+ + "\t\t\t<characteristic type=\"Ext\">\n"
+ + "\t\t\t\t<characteristic type=\"GSMA\">\n"
+ + "\t\t\t\t\t<parm name=\"AppRef\" value=\"IMS-Setting\"/>\n"
+ + "\t\t\t\t\t<parm name=\"rcsVolteSingleRegistration\" value=\"1\"/>\n"
+ + "\t\t\t\t</characteristic>\n"
+ + "\t\t\t</characteristic>\n"
+ + "\t\t</characteristic>\n"
+ + "\t\t<characteristic type=\"SERVICES\">\n"
+ + "\t\t\t<parm name=\"SupportedRCSProfileVersions\" value=\"UP2.3\"/>\n"
+ + "\t\t\t<parm name=\"ChatAuth\" value=\"1\"/>\n"
+ + "\t\t\t<parm name=\"GroupChatAuth\" value=\"1\"/>\n"
+ + "\t\t\t<parm name=\"ftAuth\" value=\"1\"/>\n"
+ + "\t\t\t<parm name=\"standaloneMsgAuth\" value=\"1\"/>\n"
+ + "\t\t\t<parm name=\"geolocPushAuth\" value=\"1\"/>\n"
+ + "\t\t\t<characteristic type=\"Ext\">\n"
+ + "\t\t\t\t<characteristic type=\"DataOff\">\n"
+ + "\t\t\t\t\t<parm name=\"rcsMessagingDataOff\" value=\"1\"/>\n"
+ + "\t\t\t\t\t<parm name=\"fileTransferDataOff\" value=\"1\"/>\n"
+ + "\t\t\t\t\t<parm name=\"mmsDataOff\" value=\"1\"/>\n"
+ + "\t\t\t\t\t<parm name=\"syncDataOff\" value=\"1\"/>\n"
+ + "\t\t\t\t\t<characteristic type=\"Ext\"/>\n"
+ + "\t\t\t\t</characteristic>\n"
+ + "\t\t\t</characteristic>\n"
+ + "\t\t</characteristic>\n"
+ + "\t</characteristic>\n"
+ + "</wap-provisioningdoc>\n";
+
+ private static final String CONFIG_SINGLE_REGISTRATION_DISABLED = "<?xml version=\"1.0\"?>\n"
+ + "<wap-provisioningdoc version=\"1.1\">\n"
+ + "\t<characteristic type=\"APPLICATION\">\n"
+ + "\t\t<parm name=\"AppID\" value=\"urn:oma:mo:ext-3gpp-ims:1.0\"/>\n"
+ + "\t\t<characteristic type=\"3GPP_IMS\">\n"
+ + "\t\t\t<parm name=\"AppID\" value=\"ap2001\"/>\n"
+ + "\t\t\t<parm name=\"Name\" value=\"RCS IMS Settings\"/>\n"
+ + "\t\t\t<characteristic type=\"Ext\">\n"
+ + "\t\t\t\t<characteristic type=\"GSMA\">\n"
+ + "\t\t\t\t\t<parm name=\"AppRef\" value=\"IMS-Setting\"/>\n"
+ + "\t\t\t\t\t<parm name=\"rcsVolteSingleRegistration\" value=\"0\"/>\n"
+ + "\t\t\t\t</characteristic>\n"
+ + "\t\t\t</characteristic>\n"
+ + "\t\t</characteristic>\n"
+ + "\t</characteristic>\n"
+ + "</wap-provisioningdoc>\n";
+ private static final int FAKE_SUB_ID_BASE = 0x0FFFFFF0;
+ private static final String DEFAULT_MESSAGING_APP1 = "DMA1";
+ private static final String DEFAULT_MESSAGING_APP2 = "DMA2";
+
+ private RcsProvisioningMonitor mRcsProvisioningMonitor;
+ private Handler mHandler;
+ private HandlerThread mHandlerThread;
+ private TestableLooper mLooper;
+ private PersistableBundle mBundle;
+ private MockContentResolver mContentResolver = new MockContentResolver();
+ private SimInfoContentProvider mProvider;
+ private BroadcastReceiver mReceiver;
+ @Mock
+ private Cursor mCursor;
+ @Mock
+ private SubscriptionManager mSubscriptionManager;
+ private SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener;
+ @Mock
+ private TelephonyRegistryManager mTelephonyRegistryManager;
+ @Mock
+ private CarrierConfigManager mCarrierConfigManager;
+ private OnRoleHoldersChangedListener mRoleHolderChangedListener;
+ @Mock
+ private RcsProvisioningMonitor.RoleManagerAdapter mRoleManager;
+ @Mock
+ private ITelephony.Stub mITelephony;
+ @Mock
+ 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
+ private Resources mResources;
+ @Mock
+ private PhoneGlobals mPhone;
+ @Mock
+ private IRcsConfigCallback mCallback;
+ @Mock
+ private PackageManager mPackageManager;
+
+ private Executor mExecutor = new Executor() {
+ @Override
+ public void execute(Runnable r) {
+ r.run();
+ }
+ };
+
+ private class SimInfoContentProvider extends MockContentProvider {
+ private Cursor mCursor;
+ private ContentValues mValues;
+
+ SimInfoContentProvider(Context context) {
+ super(context);
+ }
+
+ public void setCursor(Cursor cursor) {
+ mCursor = cursor;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ return mCursor;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values,
+ String selection, String[] selectionArgs) {
+ mValues = values;
+ return 1;
+ }
+
+ ContentValues getContentValues() {
+ return mValues;
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ when(mPhone.getResources()).thenReturn(mResources);
+ when(mPhone.getPackageManager()).thenReturn(mPackageManager);
+ when(mPackageManager.hasSystemFeature(
+ eq(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION))).thenReturn(true);
+ when(mPhone.getMainExecutor()).thenReturn(mExecutor);
+ when(mPhone.getSystemServiceName(eq(CarrierConfigManager.class)))
+ .thenReturn(Context.CARRIER_CONFIG_SERVICE);
+ when(mPhone.getSystemServiceName(eq(SubscriptionManager.class)))
+ .thenReturn(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ when(mPhone.getSystemServiceName(eq(TelephonyRegistryManager.class)))
+ .thenReturn(Context.TELEPHONY_REGISTRY_SERVICE);
+ when(mPhone.getSystemServiceName(eq(RoleManager.class)))
+ .thenReturn(Context.ROLE_SERVICE);
+ when(mPhone.getSystemService(eq(Context.CARRIER_CONFIG_SERVICE)))
+ .thenReturn(mCarrierConfigManager);
+ when(mPhone.getSystemService(eq(Context.TELEPHONY_SUBSCRIPTION_SERVICE)))
+ .thenReturn(mSubscriptionManager);
+ when(mPhone.getSystemService(eq(Context.TELEPHONY_REGISTRY_SERVICE)))
+ .thenReturn(mTelephonyRegistryManager);
+
+ mBundle = new PersistableBundle();
+ when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mBundle);
+
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ mReceiver = (BroadcastReceiver) invocation.getArguments()[0];
+ return null;
+ }
+ }).when(mPhone).registerReceiver(any(BroadcastReceiver.class), any());
+
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ mSubChangedListener = (SubscriptionManager.OnSubscriptionsChangedListener)
+ invocation.getArguments()[0];
+ return null;
+ }
+ }).when(mTelephonyRegistryManager).addOnSubscriptionsChangedListener(
+ any(SubscriptionManager.OnSubscriptionsChangedListener.class),
+ any());
+
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ mRoleHolderChangedListener = (OnRoleHoldersChangedListener)
+ invocation.getArguments()[1];
+ return null;
+ }
+ }).when(mRoleManager).addOnRoleHoldersChangedListenerAsUser(any(Executor.class),
+ any(OnRoleHoldersChangedListener.class), any(UserHandle.class));
+ List<String> dmas = new ArrayList<>();
+ dmas.add(DEFAULT_MESSAGING_APP1);
+ when(mRoleManager.getRoleHolders(eq(RoleManager.ROLE_SMS))).thenReturn(dmas);
+
+ mProvider = new SimInfoContentProvider(mPhone);
+ mProvider.setCursor(mCursor);
+ mContentResolver.addProvider(SimInfo.CONTENT_URI.getAuthority(), mProvider);
+ when(mPhone.getContentResolver()).thenReturn(mContentResolver);
+ when(mCursor.moveToFirst()).thenReturn(true);
+ when(mCursor.getColumnIndexOrThrow(any())).thenReturn(1);
+ when(mCursor.getBlob(anyInt())).thenReturn(
+ RcsConfig.compressGzip(CONFIG_DEFAULT.getBytes()));
+
+ mHandlerThread = new HandlerThread("RcsProvisioningMonitorTest");
+ mHandlerThread.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mRcsProvisioningMonitor != null) {
+ mRcsProvisioningMonitor.destroy();
+ mRcsProvisioningMonitor = null;
+ }
+
+ if (mLooper != null) {
+ mLooper.destroy();
+ mLooper = null;
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testInitWithSavedConfig() throws Exception {
+ ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+ createMonitor(3);
+
+ for (int i = 0; i < 3; i++) {
+ assertTrue(Arrays.equals(CONFIG_DEFAULT.getBytes(),
+ mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE + i)));
+ }
+
+ verify(mPhone, times(3)).sendBroadcast(captorIntent.capture(), any());
+ Intent capturedIntent = captorIntent.getAllValues().get(1);
+ assertEquals(ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE,
+ capturedIntent.getAction());
+ verify(mIImsConfig, times(3)).notifyRcsAutoConfigurationReceived(any(), anyBoolean());
+ }
+
+ @Test
+ @SmallTest
+ public void testInitWithoutSavedConfig() throws Exception {
+ when(mCursor.getBlob(anyInt())).thenReturn(null);
+ ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+ createMonitor(3);
+
+ verify(mPhone, times(3)).sendBroadcast(captorIntent.capture(), any());
+ Intent capturedIntent = captorIntent.getAllValues().get(1);
+
+ assertEquals(ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE,
+ capturedIntent.getAction());
+ //Should not notify null config
+ verify(mIImsConfig, never()).notifyRcsAutoConfigurationReceived(any(), anyBoolean());
+ }
+
+ @Test
+ @SmallTest
+ public void testSubInfoChanged() throws Exception {
+ createMonitor(3);
+ ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+
+ for (int i = 0; i < 3; i++) {
+ assertTrue(Arrays.equals(CONFIG_DEFAULT.getBytes(),
+ mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE + i)));
+ }
+ verify(mPhone, times(3)).sendBroadcast(captorIntent.capture(), any());
+ Intent capturedIntent = captorIntent.getAllValues().get(1);
+ assertEquals(ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE,
+ capturedIntent.getAction());
+ verify(mIImsConfig, times(3)).notifyRcsAutoConfigurationReceived(any(), anyBoolean());
+
+ makeFakeActiveSubIds(1);
+ mExecutor.execute(() -> mSubChangedListener.onSubscriptionsChanged());
+ processAllMessages();
+
+ for (int i = 1; i < 3; i++) {
+ assertNull(mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE + i));
+ }
+ verify(mIImsConfig, times(2)).notifyRcsAutoConfigurationRemoved();
+ }
+
+ @Test
+ @SmallTest
+ public void testDefaultMessagingApplicationChangedWithAcs() throws Exception {
+ createMonitor(1);
+ updateDefaultMessageApplication(DEFAULT_MESSAGING_APP2);
+ mBundle.putBoolean(CarrierConfigManager.KEY_USE_ACS_FOR_RCS_BOOL, true);
+ processAllMessages();
+ byte[] configCached = mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE);
+
+ assertNull(configCached);
+ assertNull(mProvider.getContentValues().get(SimInfo.COLUMN_RCS_CONFIG));
+ verify(mIImsConfig, atLeastOnce()).notifyRcsAutoConfigurationRemoved();
+ verify(mIImsConfig, atLeastOnce()).triggerRcsReconfiguration();
+ // The api should only be called when monitor is initilized.
+ verify(mIImsConfig, times(1))
+ .notifyRcsAutoConfigurationReceived(any(), anyBoolean());
+ }
+
+ @Test
+ @SmallTest
+ public void testDefaultMessagingApplicationChangedWithoutAcs() throws Exception {
+ createMonitor(1);
+ updateDefaultMessageApplication(DEFAULT_MESSAGING_APP2);
+ mBundle.putBoolean(CarrierConfigManager.KEY_USE_ACS_FOR_RCS_BOOL, false);
+ processAllMessages();
+ byte[] configCached = mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE);
+
+ assertTrue(Arrays.equals(CONFIG_DEFAULT.getBytes(), configCached));
+ 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))
+ .notifyRcsAutoConfigurationReceived(any(), anyBoolean());
+ }
+
+ @Test
+ @SmallTest
+ public void testCarrierConfigChanged() throws Exception {
+ createMonitor(1);
+ when(mPackageManager.hasSystemFeature(
+ eq(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION))).thenReturn(true);
+ ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+ mBundle.putBoolean(
+ CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
+ broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+ processAllMessages();
+ verify(mPhone, atLeastOnce()).sendBroadcast(captorIntent.capture(), any());
+ Intent capturedIntent = captorIntent.getValue();
+ assertEquals(capturedIntent.getAction(),
+ ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE);
+ assertEquals(FAKE_SUB_ID_BASE, capturedIntent.getIntExtra(
+ ProvisioningManager.EXTRA_SUBSCRIPTION_ID, -1));
+ assertEquals(ProvisioningManager.STATUS_CAPABLE,
+ capturedIntent.getIntExtra(ProvisioningManager.EXTRA_STATUS, -1));
+
+ mBundle.putBoolean(
+ CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, false);
+ broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+ processAllMessages();
+ verify(mPhone, atLeastOnce()).sendBroadcast(captorIntent.capture(), any());
+ capturedIntent = captorIntent.getValue();
+ assertEquals(capturedIntent.getAction(),
+ ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE);
+ assertEquals(FAKE_SUB_ID_BASE, capturedIntent.getIntExtra(
+ ProvisioningManager.EXTRA_SUBSCRIPTION_ID, -1));
+ assertEquals(ProvisioningManager.STATUS_CARRIER_NOT_CAPABLE,
+ capturedIntent.getIntExtra(ProvisioningManager.EXTRA_STATUS, -1));
+
+
+ when(mPackageManager.hasSystemFeature(
+ eq(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION))).thenReturn(false);
+ broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+ processAllMessages();
+ verify(mPhone, atLeastOnce()).sendBroadcast(captorIntent.capture(), any());
+ capturedIntent = captorIntent.getValue();
+ assertEquals(capturedIntent.getAction(),
+ ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE);
+ assertEquals(FAKE_SUB_ID_BASE, capturedIntent.getIntExtra(
+ ProvisioningManager.EXTRA_SUBSCRIPTION_ID, -1));
+ assertEquals(ProvisioningManager.STATUS_CARRIER_NOT_CAPABLE
+ | ProvisioningManager.STATUS_DEVICE_NOT_CAPABLE,
+ capturedIntent.getIntExtra(ProvisioningManager.EXTRA_STATUS, -1));
+ }
+
+ @Test
+ @SmallTest
+ public void testUpdateConfig() throws Exception {
+ createMonitor(1);
+ final ArgumentCaptor<byte[]> argumentBytes = ArgumentCaptor.forClass(byte[].class);
+
+ mRcsProvisioningMonitor.updateConfig(FAKE_SUB_ID_BASE, CONFIG_DEFAULT.getBytes(), false);
+ processAllMessages();
+
+ verify(mIImsConfig, atLeastOnce()).notifyRcsAutoConfigurationReceived(
+ argumentBytes.capture(), eq(false));
+ assertTrue(Arrays.equals(CONFIG_DEFAULT.getBytes(), argumentBytes.getValue()));
+ }
+
+ @Test
+ @SmallTest
+ public void testRequestReconfig() throws Exception {
+ createMonitor(1);
+
+ mRcsProvisioningMonitor.requestReconfig(FAKE_SUB_ID_BASE);
+ processAllMessages();
+
+ verify(mIImsConfig, times(1)).notifyRcsAutoConfigurationRemoved();
+ verify(mIImsConfig, times(1)).triggerRcsReconfiguration();
+ }
+
+ @Test
+ @SmallTest
+ public void testIsRcsVolteSingleRegistrationEnabled() throws Exception {
+ createMonitor(1);
+
+ when(mPackageManager.hasSystemFeature(
+ eq(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION))).thenReturn(false);
+ mBundle.putBoolean(
+ CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, false);
+ broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+ processAllMessages();
+ assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+ when(mPackageManager.hasSystemFeature(
+ eq(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION))).thenReturn(true);
+ mBundle.putBoolean(
+ CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, false);
+ broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+ processAllMessages();
+ assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+
+ when(mPackageManager.hasSystemFeature(
+ eq(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION))).thenReturn(false);
+ mBundle.putBoolean(
+ CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
+ broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+ processAllMessages();
+ assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+ when(mPackageManager.hasSystemFeature(
+ eq(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION))).thenReturn(true);
+ mBundle.putBoolean(
+ CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
+ broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+ processAllMessages();
+ assertTrue(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+ mRcsProvisioningMonitor.updateConfig(FAKE_SUB_ID_BASE, null, false);
+ processAllMessages();
+ assertTrue(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+ mRcsProvisioningMonitor.updateConfig(FAKE_SUB_ID_BASE, CONFIG_DEFAULT.getBytes(), false);
+ processAllMessages();
+ assertTrue(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+ mRcsProvisioningMonitor.updateConfig(FAKE_SUB_ID_BASE,
+ CONFIG_SINGLE_REGISTRATION_DISABLED.getBytes(), false);
+ processAllMessages();
+ assertTrue(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+ assertNull(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(
+ FAKE_SUB_ID_BASE + 1));
+ }
+
+ @Test
+ @SmallTest
+ public void testRegisterThenUnregisterCallback() throws Exception {
+ createMonitor(1);
+
+ boolean result = mRcsProvisioningMonitor.registerRcsProvisioningCallback(
+ FAKE_SUB_ID_BASE, mCallback);
+
+ assertTrue(result);
+ verify(mIImsConfig, times(1)).addRcsConfigCallback(eq(mCallback));
+
+ result = mRcsProvisioningMonitor.unregisterRcsProvisioningCallback(
+ FAKE_SUB_ID_BASE, mCallback);
+
+ assertTrue(result);
+ verify(mIImsConfig, times(1)).removeRcsConfigCallback(eq(mCallback));
+ verify(mCallback, times(1)).onRemoved();
+ }
+
+ @Test
+ @SmallTest
+ public void testCallbackRemovedWhenSubInfoChanged() throws Exception {
+ createMonitor(1);
+
+ boolean result = mRcsProvisioningMonitor.registerRcsProvisioningCallback(
+ FAKE_SUB_ID_BASE, mCallback);
+ makeFakeActiveSubIds(0);
+ mExecutor.execute(() -> mSubChangedListener.onSubscriptionsChanged());
+ processAllMessages();
+
+ assertTrue(result);
+ verify(mIImsConfig, times(1)).removeRcsConfigCallback(eq(mCallback));
+ verify(mCallback, times(1)).onRemoved();
+ }
+
+ @Test
+ @SmallTest
+ public void testCallbackRemovedWhenDmaChanged() throws Exception {
+ createMonitor(1);
+
+ boolean result = mRcsProvisioningMonitor.registerRcsProvisioningCallback(
+ FAKE_SUB_ID_BASE, mCallback);
+ updateDefaultMessageApplication(DEFAULT_MESSAGING_APP2);
+ processAllMessages();
+
+ assertTrue(result);
+ verify(mIImsConfig, times(1)).removeRcsConfigCallback(eq(mCallback));
+ verify(mCallback, times(1)).onRemoved();
+ }
+
+ @Test
+ @SmallTest
+ public void testSendBroadcastWhenDmaChanged() throws Exception {
+ createMonitor(3);
+ verify(mPhone, times(3)).sendBroadcast(any(), any());
+
+ updateDefaultMessageApplication(DEFAULT_MESSAGING_APP2);
+ processAllMessages();
+
+ verify(mPhone, times(6)).sendBroadcast(any(), any());
+ }
+
+ @Test
+ @SmallTest
+ public void testRcsConnectedAndDisconnected() throws Exception {
+ createMonitor(1);
+ mRcsProvisioningMonitor.registerRcsProvisioningCallback(
+ 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 testOverrideDeviceSingleRegistrationEnabled() throws Exception {
+ createMonitor(1);
+
+ when(mPackageManager.hasSystemFeature(
+ eq(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION))).thenReturn(true);
+ mBundle.putBoolean(
+ CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
+ broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+ processAllMessages();
+ assertTrue(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+ mRcsProvisioningMonitor.overrideDeviceSingleRegistrationEnabled(false);
+ processAllMessages();
+ assertFalse(mRcsProvisioningMonitor.getDeviceSingleRegistrationEnabled());
+ assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+ mRcsProvisioningMonitor.overrideDeviceSingleRegistrationEnabled(null);
+ processAllMessages();
+ assertTrue(mRcsProvisioningMonitor.getDeviceSingleRegistrationEnabled());
+ assertTrue(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+ when(mPackageManager.hasSystemFeature(
+ eq(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION))).thenReturn(false);
+ //use carrier config change to refresh the value as system feature is static
+ mBundle.putBoolean(
+ CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
+ broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+ processAllMessages();
+
+ assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+ mRcsProvisioningMonitor.overrideDeviceSingleRegistrationEnabled(true);
+ processAllMessages();
+ assertTrue(mRcsProvisioningMonitor.getDeviceSingleRegistrationEnabled());
+ assertTrue(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+ mRcsProvisioningMonitor.overrideDeviceSingleRegistrationEnabled(null);
+ processAllMessages();
+ assertFalse(mRcsProvisioningMonitor.getDeviceSingleRegistrationEnabled());
+ assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+ }
+
+ @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, CONFIG_DEFAULT.getBytes(), false);
+ processAllMessages();
+
+ //config cahced in monitor should be updated, but db should not
+ assertNull(mProvider.getContentValues());
+ assertTrue(Arrays.equals(CONFIG_DEFAULT.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, CONFIG_DEFAULT.getBytes(), false);
+ processAllMessages();
+
+ assertTrue(Arrays.equals(CONFIG_DEFAULT.getBytes(),
+ mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE)));
+ assertTrue(Arrays.equals(RcsConfig.compressGzip(CONFIG_DEFAULT.getBytes()),
+ (byte[]) mProvider.getContentValues().get(SimInfo.COLUMN_RCS_CONFIG)));
+ }
+
+ @Test
+ @SmallTest
+ public void testOverrideCarrierSingleRegistrationEnabled() throws Exception {
+ createMonitor(1);
+
+ when(mPackageManager.hasSystemFeature(
+ eq(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION))).thenReturn(true);
+ mBundle.putBoolean(
+ CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
+ broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+ processAllMessages();
+ assertTrue(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+ mRcsProvisioningMonitor
+ .overrideCarrierSingleRegistrationEnabled(FAKE_SUB_ID_BASE, false);
+ processAllMessages();
+ assertFalse(mRcsProvisioningMonitor.getCarrierSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+ assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+ mRcsProvisioningMonitor
+ .overrideCarrierSingleRegistrationEnabled(FAKE_SUB_ID_BASE, null);
+ processAllMessages();
+ assertTrue(mRcsProvisioningMonitor.getCarrierSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+ assertTrue(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+ mBundle.putBoolean(
+ CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, false);
+ broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+ processAllMessages();
+ assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+ mRcsProvisioningMonitor
+ .overrideCarrierSingleRegistrationEnabled(FAKE_SUB_ID_BASE, true);
+ processAllMessages();
+ assertTrue(mRcsProvisioningMonitor.getCarrierSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+ assertTrue(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+
+ mRcsProvisioningMonitor
+ .overrideCarrierSingleRegistrationEnabled(FAKE_SUB_ID_BASE, null);
+ processAllMessages();
+ assertFalse(mRcsProvisioningMonitor.getCarrierSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+ assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+ }
+
+ @Test
+ @SmallTest
+ public void testOverrideImsFeatureValidation() throws Exception {
+ createMonitor(1);
+
+ mRcsProvisioningMonitor.overrideImsFeatureValidation(FAKE_SUB_ID_BASE, false);
+ assertFalse(mRcsProvisioningMonitor.getImsFeatureValidationOverride(FAKE_SUB_ID_BASE));
+
+ mRcsProvisioningMonitor.overrideImsFeatureValidation(FAKE_SUB_ID_BASE, true);
+ assertTrue(mRcsProvisioningMonitor.getImsFeatureValidationOverride(FAKE_SUB_ID_BASE));
+
+ mRcsProvisioningMonitor.overrideImsFeatureValidation(FAKE_SUB_ID_BASE, null);
+ assertNull(mRcsProvisioningMonitor.getImsFeatureValidationOverride(FAKE_SUB_ID_BASE));
+ }
+
+ 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, 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) {
+ Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+ intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
+ mExecutor.execute(() -> {
+ mReceiver.onReceive(mPhone, intent);
+ });
+ }
+
+ private void makeFakeActiveSubIds(int count) {
+ final int[] subIds = new int[count];
+ for (int i = 0; i < count; i++) {
+ subIds[i] = FAKE_SUB_ID_BASE + i;
+ }
+ when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(subIds);
+ }
+
+ private void updateDefaultMessageApplication(String packageName) {
+ List<String> dmas = new ArrayList<>();
+ dmas.add(packageName);
+ when(mRoleManager.getRoleHolders(eq(RoleManager.ROLE_SMS))).thenReturn(dmas);
+ mExecutor.execute(() -> mRoleHolderChangedListener.onRoleHoldersChanged(
+ RoleManager.ROLE_SMS, UserHandle.ALL));
+ }
+
+ private void processAllMessages() {
+ while (!mLooper.getLooper().getQueue().isIdle()) {
+ mLooper.processAllMessages();
+ }
+ }
+
+ private static void logd(String str) {
+ Log.d(TAG, str);
+ }
+}
diff --git a/tests/src/com/android/phone/ServiceStateProviderTest.java b/tests/src/com/android/phone/ServiceStateProviderTest.java
index 32e5f26..cde584f 100644
--- a/tests/src/com/android/phone/ServiceStateProviderTest.java
+++ b/tests/src/com/android/phone/ServiceStateProviderTest.java
@@ -16,89 +16,118 @@
package com.android.phone;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.provider.Telephony.ServiceStateTable;
+import static android.provider.Telephony.ServiceStateTable.DATA_NETWORK_TYPE;
+import static android.provider.Telephony.ServiceStateTable.DATA_REG_STATE;
+import static android.provider.Telephony.ServiceStateTable.DUPLEX_MODE;
+import static android.provider.Telephony.ServiceStateTable.VOICE_OPERATOR_NUMERIC;
+import static android.provider.Telephony.ServiceStateTable.VOICE_REG_STATE;
import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionId;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import android.Manifest;
+import android.app.AppOpsManager;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.database.ContentObserver;
import android.database.Cursor;
+import android.location.LocationManager;
import android.net.Uri;
+import android.os.Build;
+import android.os.UserHandle;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import android.test.mock.MockContentResolver;
import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
/**
* Tests for simple queries of ServiceStateProvider.
*
* Build, install and run the tests by running the commands below:
- * runtest --path <dir or file>
- * runtest --path <dir or file> --test-method <testMethodName>
- * e.g.)
- * runtest --path tests/src/com/android/phone/ServiceStateProviderTest.java \
- * --test-method testGetServiceState
+ * atest ServiceStateProviderTest
*/
+@RunWith(AndroidJUnit4.class)
public class ServiceStateProviderTest {
private static final String TAG = "ServiceStateProviderTest";
+ private static final int TEST_NETWORK_ID = 123;
+ private static final int TEST_SYSTEM_ID = 123;
- private Context mContext;
private MockContentResolver mContentResolver;
private ServiceState mTestServiceState;
private ServiceState mTestServiceStateForSubId1;
- private final String[] mTestProjection =
- {
- ServiceStateTable.VOICE_REG_STATE,
- ServiceStateProvider.DATA_REG_STATE,
- ServiceStateProvider.VOICE_OPERATOR_ALPHA_LONG,
- ServiceStateProvider.VOICE_OPERATOR_ALPHA_SHORT,
- ServiceStateTable.VOICE_OPERATOR_NUMERIC,
- ServiceStateProvider.DATA_OPERATOR_ALPHA_LONG,
- ServiceStateProvider.DATA_OPERATOR_ALPHA_SHORT,
- ServiceStateProvider.DATA_OPERATOR_NUMERIC,
- ServiceStateTable.IS_MANUAL_NETWORK_SELECTION,
- ServiceStateProvider.RIL_VOICE_RADIO_TECHNOLOGY,
- ServiceStateProvider.RIL_DATA_RADIO_TECHNOLOGY,
- ServiceStateProvider.CSS_INDICATOR,
- ServiceStateProvider.NETWORK_ID,
- ServiceStateProvider.SYSTEM_ID,
- ServiceStateProvider.CDMA_ROAMING_INDICATOR,
- ServiceStateProvider.CDMA_DEFAULT_ROAMING_INDICATOR,
- ServiceStateProvider.CDMA_ERI_ICON_INDEX,
- ServiceStateProvider.CDMA_ERI_ICON_MODE,
- ServiceStateProvider.IS_EMERGENCY_ONLY,
- ServiceStateProvider.IS_USING_CARRIER_AGGREGATION,
- ServiceStateProvider.OPERATOR_ALPHA_LONG_RAW,
- ServiceStateProvider.OPERATOR_ALPHA_SHORT_RAW,
- };
+ @Mock Context mContext;
+ @Mock AppOpsManager mAppOpsManager;
+ @Mock LocationManager mLocationManager;
+ @Mock PackageManager mPackageManager;
+
+ // Exception used internally to verify if the Resolver#notifyChange has been called.
+ private class TestNotifierException extends RuntimeException {
+ TestNotifierException() {
+ super();
+ }
+ }
@Before
public void setUp() throws Exception {
- mContext = mock(Context.class);
+ MockitoAnnotations.initMocks(this);
+ mockSystemService(AppOpsManager.class, mAppOpsManager, Context.APP_OPS_SERVICE);
+ mockSystemService(LocationManager.class, mLocationManager, Context.LOCATION_SERVICE);
+ doReturn(mPackageManager).when(mContext).getPackageManager();
+
mContentResolver = new MockContentResolver() {
@Override
public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
- throw new RuntimeException("notifyChange!");
+ throw new TestNotifierException();
}
};
doReturn(mContentResolver).when(mContext).getContentResolver();
mTestServiceState = new ServiceState();
mTestServiceState.setStateOutOfService();
+ mTestServiceState.setCdmaSystemAndNetworkId(TEST_SYSTEM_ID, TEST_NETWORK_ID);
mTestServiceStateForSubId1 = new ServiceState();
mTestServiceStateForSubId1.setStateOff();
+ // Add NRI to trigger SS with non-default values (e.g. duplex mode)
+ NetworkRegistrationInfo nriWwan = new NetworkRegistrationInfo.Builder()
+ .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+ .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+ .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+ .build();
+ mTestServiceStateForSubId1.addNetworkRegistrationInfo(nriWwan);
+ mTestServiceStateForSubId1.setChannelNumber(65536); // EutranBand.BAND_65, DUPLEX_MODE_FDD
+
// Mock out the actual phone state
ServiceStateProvider provider = new ServiceStateProvider() {
@Override
@@ -119,62 +148,230 @@
providerInfo.authority = "service-state";
provider.attachInfoForTesting(mContext, providerInfo);
mContentResolver.addProvider("service-state", provider);
+
+ // By default, test with app target R, no READ_PRIVILEGED_PHONE_STATE permission
+ setTargetSdkVersion(Build.VERSION_CODES.R);
+ setCanReadPrivilegedPhoneState(false);
+
+ // TODO(b/191995565): Turn on all ignored cases once location access is allow to be off
+ // Do not allow phone process to always access location so we can test various scenarios
+ // LocationAccessPolicy.alwaysAllowPrivilegedProcessToAccessLocationForTesting(false);
}
- @Test
- @SmallTest
- public void testQueryServiceStateWithNoSubId() {
- // Verify that when calling query with no subId in the uri the default ServiceState is
- // returned.
- // In this case the subId is set to 0 and the expected service state is
- // mTestServiceState.
- verifyServiceStateForSubId(ServiceStateTable.CONTENT_URI, mTestServiceState);
- }
-
- @Test
- @SmallTest
- public void testGetServiceStateWithDefaultSubId() {
- // Verify that when calling with the DEFAULT_SUBSCRIPTION_ID the correct ServiceState is
- // returned
- // In this case the subId is set to 0 and the expected service state is
- // mTestServiceState.
- verifyServiceStateForSubId(
- getUriForSubscriptionId(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID),
- mTestServiceState);
+ @After
+ public void tearDown() throws Exception {
+ // LocationAccessPolicy.alwaysAllowPrivilegedProcessToAccessLocationForTesting(true);
}
/**
- * Test querying the service state for a given subId
+ * Verify that when calling query with no subId in the uri the default ServiceState is returned.
+ * In this case the subId is set to 0 and the expected service state is mTestServiceState.
+ */
+ // TODO(b/191995565): Turn this on when location access can be off
+ @Ignore
+ @SmallTest
+ public void testQueryServiceState_withNoSubId_withoutLocation() {
+ setLocationPermissions(false);
+
+ verifyServiceStateForSubId(ServiceStateTable.CONTENT_URI, mTestServiceState,
+ false /*hasLocation*/);
+ }
+
+ @Test
+ @SmallTest
+ public void testQueryServiceState_withNoSubId_withLocation() {
+ setLocationPermissions(true);
+
+ verifyServiceStateForSubId(ServiceStateTable.CONTENT_URI, mTestServiceState,
+ true /*hasLocation*/);
+ }
+
+ /**
+ * Verify that when calling with the DEFAULT_SUBSCRIPTION_ID the correct ServiceState is
+ * returned. In this case the subId is set to 0 and the expected service state is
+ * mTestServiceState.
+ */
+ // TODO(b/191995565): Turn case on when location access can be off
+ @Ignore
+ @SmallTest
+ public void testGetServiceState_withDefaultSubId_withoutLocation() {
+ setLocationPermissions(false);
+
+ verifyServiceStateForSubId(
+ getUriForSubscriptionId(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID),
+ mTestServiceState, false /*hasLocation*/);
+ }
+
+ @Test
+ @SmallTest
+ public void testGetServiceState_withDefaultSubId_withLocation() {
+ setLocationPermissions(true);
+
+ verifyServiceStateForSubId(
+ getUriForSubscriptionId(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID),
+ mTestServiceState, true /*hasLocation*/);
+ }
+
+ /**
+ * Verify that when calling with a specific subId the correct ServiceState is returned. In this
+ * case the subId is set to 1 and the expected service state is mTestServiceStateForSubId1
*/
@Test
@SmallTest
- public void testGetServiceStateForSubId() {
- // Verify that when calling with a specific subId the correct ServiceState is returned
- // In this case the subId is set to 1 and the expected service state is
- // mTestServiceStateForSubId1
- verifyServiceStateForSubId(getUriForSubscriptionId(1), mTestServiceStateForSubId1);
+ public void testGetServiceStateForSubId_withoutLocation() {
+ setLocationPermissions(false);
+
+ verifyServiceStateForSubId(getUriForSubscriptionId(1), mTestServiceStateForSubId1,
+ false /*hasLocation*/);
}
- private void verifyServiceStateForSubId(Uri uri, ServiceState ss) {
- Cursor cursor = mContentResolver.query(uri, mTestProjection, "",
+ @Test
+ @SmallTest
+ public void testGetServiceStateForSubId_withLocation() {
+ setLocationPermissions(true);
+
+ verifyServiceStateForSubId(getUriForSubscriptionId(1), mTestServiceStateForSubId1,
+ true /*hasLocation*/);
+ }
+
+ /**
+ * Verify that apps target S+ without READ_PRIVILEGED_PHONE_STATE permission can access the
+ * public columns of ServiceStateTable.
+ */
+ @Test
+ public void query_publicColumns_targetS_noReadPrivilege_getPublicColumns() {
+ setTargetSdkVersion(Build.VERSION_CODES.S);
+ setCanReadPrivilegedPhoneState(false);
+
+ verifyServiceStateWithPublicColumns(mTestServiceState, null /*projection*/);
+ }
+
+ /**
+ * Verify that apps target S+ without READ_PRIVILEGED_PHONE_STATE permission try to access
+ * non-public columns should throw IllegalArgumentException.
+ */
+ @Test
+ public void query_hideColumn_targetS_noReadPrivilege_throwIllegalArgumentException() {
+ setTargetSdkVersion(Build.VERSION_CODES.S);
+ setCanReadPrivilegedPhoneState(false);
+
+ // DATA_ROAMING_TYPE is a non-public column
+ String[] projection = new String[]{"data_roaming_type"};
+
+ assertThrows(IllegalArgumentException.class,
+ () -> verifyServiceStateWithPublicColumns(mTestServiceState, projection));
+ }
+
+ /**
+ * Verify that apps target S+ with READ_PRIVILEGED_PHONE_STATE and location permissions should
+ * be able to access all columns.
+ */
+ @Test
+ public void query_allColumn_targetS_withReadPrivilegedAndLocation_getAllStateUnredacted() {
+ setTargetSdkVersion(Build.VERSION_CODES.S);
+ setCanReadPrivilegedPhoneState(true);
+ setLocationPermissions(true);
+
+ verifyServiceStateForSubId(
+ getUriForSubscriptionId(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID),
+ mTestServiceState, true /*hasPermission*/);
+ }
+
+ /**
+ * Verify that apps target S+ with READ_PRIVILEGED_PHONE_STATE permission but no location
+ * permission, try to access location sensitive columns should throw SecurityException.
+ */
+ // TODO(b/191995565): Turn this on once b/191995565 is integrated
+ @Ignore
+ public void query_locationColumn_targetS_withReadPrivilegeNoLocation_throwSecurityExecption() {
+ setTargetSdkVersion(Build.VERSION_CODES.S);
+ setCanReadPrivilegedPhoneState(true);
+ setLocationPermissions(false);
+
+ // NETWORK_ID is a location-sensitive column
+ String[] projection = new String[]{"network_id"};
+
+ assertThrows(SecurityException.class,
+ () -> verifyServiceStateWithLocationColumns(mTestServiceState, projection));
+ }
+
+ /**
+ * Verify that apps target R- with location permissions should be able to access all columns.
+ */
+ @Test
+ public void query_allColumn_targetR_withLocation_getAllStateUnredacted() {
+ setTargetSdkVersion(Build.VERSION_CODES.R);
+ setLocationPermissions(true);
+
+ verifyServiceStateForSubId(
+ getUriForSubscriptionId(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID),
+ mTestServiceState, true /*hasPermission*/);
+ }
+
+ /**
+ * Verify that apps target R- w/o location permissions should be able to access all columns but
+ * with redacted ServiceState.
+ */
+ // TODO(b/191995565): Turn case on when location access can be off
+ @Ignore
+ public void query_allColumn_targetR_noLocation_getRedacted() {
+ setTargetSdkVersion(Build.VERSION_CODES.R);
+ setLocationPermissions(false);
+
+ verifyServiceStateForSubId(
+ getUriForSubscriptionId(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID),
+ ServiceStateProvider.getLocationRedactedServiceState(mTestServiceState),
+ true /*hasPermission*/);
+ }
+
+ private void verifyServiceStateWithLocationColumns(ServiceState ss, String[] projection) {
+ try (Cursor cursor = mContentResolver.query(ServiceStateTable.CONTENT_URI, projection, null,
+ null)) {
+ assertNotNull(cursor);
+ }
+ }
+
+ private void verifyServiceStateWithPublicColumns(ServiceState ss, String[] projection) {
+ try (Cursor cursor = mContentResolver.query(ServiceStateTable.CONTENT_URI, projection, null,
+ null)) {
+ assertNotNull(cursor);
+ assertEquals(cursor.getColumnCount(), ServiceStateProvider.PUBLIC_COLUMNS.length);
+
+ cursor.moveToFirst();
+ assertEquals(ss.getVoiceRegState(),
+ cursor.getInt(cursor.getColumnIndex(VOICE_REG_STATE)));
+ assertEquals(ss.getDataRegistrationState(),
+ cursor.getInt(cursor.getColumnIndex(DATA_REG_STATE)));
+ assertEquals(ss.getOperatorNumeric(),
+ cursor.getString(cursor.getColumnIndex(VOICE_OPERATOR_NUMERIC)));
+ assertEquals(ss.getDataNetworkType(),
+ cursor.getInt(cursor.getColumnIndex(DATA_NETWORK_TYPE)));
+ assertEquals(ss.getDuplexMode(), cursor.getInt(cursor.getColumnIndex(DUPLEX_MODE)));
+ }
+ }
+
+ private void verifyServiceStateForSubId(Uri uri, ServiceState ss, boolean hasLocation) {
+ Cursor cursor = mContentResolver.query(uri, ServiceStateProvider.ALL_COLUMNS, "",
null, null);
assertNotNull(cursor);
cursor.moveToFirst();
final int voiceRegState = ss.getState();
final int dataRegState = ss.getDataRegistrationState();
- final String voiceOperatorAlphaLong = ss.getOperatorAlphaLong();
- final String voiceOperatorAlphaShort = ss.getOperatorAlphaShort();
- final String voiceOperatorNumeric = ss.getOperatorNumeric();
- final String dataOperatorAlphaLong = ss.getOperatorAlphaLong();
- final String dataOperatorAlphaShort = ss.getOperatorAlphaShort();
- final String dataOperatorNumeric = ss.getOperatorNumeric();
+ final int voiceRoamingType = ss.getVoiceRoamingType();
+ final int dataRoamingType = ss.getDataRoamingType();
+ final String voiceOperatorAlphaLong = hasLocation ? ss.getOperatorAlphaLong() : null;
+ final String voiceOperatorAlphaShort = hasLocation ? ss.getOperatorAlphaShort() : null;
+ final String voiceOperatorNumeric = hasLocation ? ss.getOperatorNumeric() : null;
+ final String dataOperatorAlphaLong = hasLocation ? ss.getOperatorAlphaLong() : null;
+ final String dataOperatorAlphaShort = hasLocation ? ss.getOperatorAlphaShort() : null;
+ final String dataOperatorNumeric = hasLocation ? ss.getOperatorNumeric() : null;
final int isManualNetworkSelection = (ss.getIsManualSelection()) ? 1 : 0;
final int rilVoiceRadioTechnology = ss.getRilVoiceRadioTechnology();
final int rilDataRadioTechnology = ss.getRilDataRadioTechnology();
final int cssIndicator = ss.getCssIndicator();
- final int networkId = ss.getCdmaNetworkId();
- final int systemId = ss.getCdmaSystemId();
+ final int networkId = hasLocation ? ss.getCdmaNetworkId() : ServiceState.UNKNOWN_ID;
+ final int systemId = hasLocation ? ss.getCdmaSystemId() : ServiceState.UNKNOWN_ID;
final int cdmaRoamingIndicator = ss.getCdmaRoamingIndicator();
final int cdmaDefaultRoamingIndicator = ss.getCdmaDefaultRoamingIndicator();
final int cdmaEriIconIndex = ss.getCdmaEriIconIndex();
@@ -183,29 +380,35 @@
final int isUsingCarrierAggregation = (ss.isUsingCarrierAggregation()) ? 1 : 0;
final String operatorAlphaLongRaw = ss.getOperatorAlphaLongRaw();
final String operatorAlphaShortRaw = ss.getOperatorAlphaShortRaw();
+ final int dataNetworkType = ss.getDataNetworkType();
+ final int duplexMode = ss.getDuplexMode();
assertEquals(voiceRegState, cursor.getInt(0));
assertEquals(dataRegState, cursor.getInt(1));
- assertEquals(voiceOperatorAlphaLong, cursor.getString(2));
- assertEquals(voiceOperatorAlphaShort, cursor.getString(3));
- assertEquals(voiceOperatorNumeric, cursor.getString(4));
- assertEquals(dataOperatorAlphaLong, cursor.getString(5));
- assertEquals(dataOperatorAlphaShort, cursor.getString(6));
- assertEquals(dataOperatorNumeric, cursor.getString(7));
- assertEquals(isManualNetworkSelection, cursor.getInt(8));
- assertEquals(rilVoiceRadioTechnology, cursor.getInt(9));
- assertEquals(rilDataRadioTechnology, cursor.getInt(10));
- assertEquals(cssIndicator, cursor.getInt(11));
- assertEquals(networkId, cursor.getInt(12));
- assertEquals(systemId, cursor.getInt(13));
- assertEquals(cdmaRoamingIndicator, cursor.getInt(14));
- assertEquals(cdmaDefaultRoamingIndicator, cursor.getInt(15));
- assertEquals(cdmaEriIconIndex, cursor.getInt(16));
- assertEquals(cdmaEriIconMode, cursor.getInt(17));
- assertEquals(isEmergencyOnly, cursor.getInt(18));
- assertEquals(isUsingCarrierAggregation, cursor.getInt(19));
- assertEquals(operatorAlphaLongRaw, cursor.getString(20));
- assertEquals(operatorAlphaShortRaw, cursor.getString(21));
+ assertEquals(voiceRoamingType, cursor.getInt(2));
+ assertEquals(dataRoamingType, cursor.getInt(3));
+ assertEquals(voiceOperatorAlphaLong, cursor.getString(4));
+ assertEquals(voiceOperatorAlphaShort, cursor.getString(5));
+ assertEquals(voiceOperatorNumeric, cursor.getString(6));
+ assertEquals(dataOperatorAlphaLong, cursor.getString(7));
+ assertEquals(dataOperatorAlphaShort, cursor.getString(8));
+ assertEquals(dataOperatorNumeric, cursor.getString(9));
+ assertEquals(isManualNetworkSelection, cursor.getInt(10));
+ assertEquals(rilVoiceRadioTechnology, cursor.getInt(11));
+ assertEquals(rilDataRadioTechnology, cursor.getInt(12));
+ assertEquals(cssIndicator, cursor.getInt(13));
+ assertEquals(networkId, cursor.getInt(14));
+ assertEquals(systemId, cursor.getInt(15));
+ assertEquals(cdmaRoamingIndicator, cursor.getInt(16));
+ assertEquals(cdmaDefaultRoamingIndicator, cursor.getInt(17));
+ assertEquals(cdmaEriIconIndex, cursor.getInt(18));
+ assertEquals(cdmaEriIconMode, cursor.getInt(19));
+ assertEquals(isEmergencyOnly, cursor.getInt(20));
+ assertEquals(isUsingCarrierAggregation, cursor.getInt(21));
+ assertEquals(operatorAlphaLongRaw, cursor.getString(22));
+ assertEquals(operatorAlphaShortRaw, cursor.getString(23));
+ assertEquals(dataNetworkType, cursor.getInt(24));
+ assertEquals(duplexMode, cursor.getInt(25));
}
/**
@@ -226,21 +429,12 @@
newSS.setCdmaSystemAndNetworkId(0, 0);
// Test that notifyChange is not called for these fields
- boolean notifyChangeWasCalled = false;
- try {
- ServiceStateProvider.notifyChangeForSubIdAndField(mContext, oldSS, newSS, subId);
- } catch (RuntimeException e) {
- final String message = e.getMessage();
- if (message != null && message.equals("notifyChange!")) {
- notifyChangeWasCalled = true;
- }
- }
- assertFalse(notifyChangeWasCalled);
+ assertFalse(notifyChangeCalledForSubIdAndField(oldSS, newSS, subId));
}
@Test
@SmallTest
- public void testNotifyChanged() {
+ public void testNotifyChanged_noStateUpdated() {
int subId = 0;
ServiceState oldSS = new ServiceState();
@@ -251,57 +445,152 @@
copyOfOldSS.setStateOutOfService();
copyOfOldSS.setVoiceRegState(ServiceState.STATE_OUT_OF_SERVICE);
+ // Test that notifyChange is not called with no change in notifyChangeForSubIdAndField
+ assertFalse(notifyChangeCalledForSubId(oldSS, copyOfOldSS, subId));
+
+ // Test that notifyChange is not called with no change in notifyChangeForSubId
+ assertFalse(notifyChangeCalledForSubIdAndField(oldSS, copyOfOldSS, subId));
+ }
+
+ @Test
+ @SmallTest
+ public void testNotifyChanged_voiceRegStateUpdated() {
+ int subId = 0;
+
+ ServiceState oldSS = new ServiceState();
+ oldSS.setStateOutOfService();
+ oldSS.setVoiceRegState(ServiceState.STATE_OUT_OF_SERVICE);
+
ServiceState newSS = new ServiceState();
newSS.setStateOutOfService();
newSS.setVoiceRegState(ServiceState.STATE_POWER_OFF);
- // Test that notifyChange is not called with no change in notifyChangeForSubIdAndField
- boolean notifyChangeWasCalled = false;
- try {
- ServiceStateProvider.notifyChangeForSubIdAndField(mContext, oldSS, copyOfOldSS, subId);
- } catch (RuntimeException e) {
- final String message = e.getMessage();
- if (message != null && message.equals("notifyChange!")) {
- notifyChangeWasCalled = true;
- }
- }
- assertFalse(notifyChangeWasCalled);
-
- // Test that notifyChange is not called with no change in notifyChangeForSubId
- notifyChangeWasCalled = false;
- try {
- ServiceStateProvider.notifyChangeForSubId(mContext, oldSS, copyOfOldSS, subId);
- } catch (RuntimeException e) {
- final String message = e.getMessage();
- if (message != null && message.equals("notifyChange!")) {
- notifyChangeWasCalled = true;
- }
- }
- assertFalse(notifyChangeWasCalled);
-
// Test that notifyChange is called by notifyChangeForSubIdAndField when the voice_reg_state
// changes
- notifyChangeWasCalled = false;
- try {
- ServiceStateProvider.notifyChangeForSubIdAndField(mContext, oldSS, newSS, subId);
- } catch (RuntimeException e) {
- final String message = e.getMessage();
- if (message != null && message.equals("notifyChange!")) {
- notifyChangeWasCalled = true;
- }
- }
- assertTrue(notifyChangeWasCalled);
+ assertTrue(notifyChangeCalledForSubId(oldSS, newSS, subId));
// Test that notifyChange is called by notifyChangeForSubId when the voice_reg_state changes
- notifyChangeWasCalled = false;
+ assertTrue(notifyChangeCalledForSubIdAndField(oldSS, newSS, subId));
+ }
+
+ @Test
+ @SmallTest
+ public void testNotifyChanged_dataNetworkTypeUpdated() {
+ int subId = 0;
+
+ // While we don't have a method to directly set dataNetworkType, we emulate a ServiceState
+ // change that will trigger the change of dataNetworkType, according to the logic in
+ // ServiceState#getDataNetworkType
+ ServiceState oldSS = new ServiceState();
+ oldSS.setStateOutOfService();
+
+ ServiceState newSS = new ServiceState();
+ newSS.setStateOutOfService();
+
+ NetworkRegistrationInfo nriWwan = new NetworkRegistrationInfo.Builder()
+ .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+ .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+ .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+ .setRegistrationState(REGISTRATION_STATE_HOME)
+ .build();
+ newSS.addNetworkRegistrationInfo(nriWwan);
+
+ // Test that notifyChange is called by notifyChangeForSubId when the
+ // data_network_type changes
+ assertTrue(notifyChangeCalledForSubId(oldSS, newSS, subId));
+
+ // Test that notifyChange is called by notifyChangeForSubIdAndField when the
+ // data_network_type changes
+ assertTrue(notifyChangeCalledForSubIdAndField(oldSS, newSS, subId));
+ }
+
+ @Test
+ @SmallTest
+ public void testNotifyChanged_dataRegStateUpdated() {
+ int subId = 0;
+
+ ServiceState oldSS = new ServiceState();
+ oldSS.setStateOutOfService();
+ oldSS.setDataRegState(ServiceState.STATE_OUT_OF_SERVICE);
+
+ ServiceState newSS = new ServiceState();
+ newSS.setStateOutOfService();
+ newSS.setDataRegState(ServiceState.STATE_POWER_OFF);
+
+ // Test that notifyChange is called by notifyChangeForSubId
+ // when the data_reg_state changes
+ assertTrue(notifyChangeCalledForSubId(oldSS, newSS, subId));
+
+ // Test that notifyChange is called by notifyChangeForSubIdAndField
+ // when the data_reg_state changes
+ assertTrue(notifyChangeCalledForSubIdAndField(oldSS, newSS, subId));
+ }
+
+ // Check if notifyChange was called by notifyChangeForSubId
+ private boolean notifyChangeCalledForSubId(ServiceState oldSS,
+ ServiceState newSS, int subId) {
try {
ServiceStateProvider.notifyChangeForSubId(mContext, oldSS, newSS, subId);
- } catch (RuntimeException e) {
- final String message = e.getMessage();
- if (message != null && message.equals("notifyChange!")) {
- notifyChangeWasCalled = true;
- }
+ } catch (TestNotifierException e) {
+ return true;
}
- assertTrue(notifyChangeWasCalled);
+ return false;
+ }
+
+ // Check if notifyChange was called by notifyChangeForSubIdAndField
+ private boolean notifyChangeCalledForSubIdAndField(ServiceState oldSS,
+ ServiceState newSS, int subId) {
+ try {
+ ServiceStateProvider.notifyChangeForSubIdAndField(mContext, oldSS, newSS, subId);
+ } catch (TestNotifierException e) {
+ return true;
+ }
+ return false;
+ }
+
+ private void setLocationPermissions(boolean hasPermission) {
+ if (!hasPermission) {
+ // System location off, LocationAccessPolicy#checkLocationPermission returns DENIED_SOFT
+ when(mLocationManager.isLocationEnabledForUser(any(UserHandle.class)))
+ .thenReturn(false);
+ } else {
+ // Turn on all to let LocationAccessPolicy#checkLocationPermission returns ALLOWED
+ when(mContext.checkPermission(eq(Manifest.permission.ACCESS_FINE_LOCATION),
+ anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ when(mContext.checkPermission(eq(Manifest.permission.ACCESS_COARSE_LOCATION),
+ anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ when(mAppOpsManager.noteOpNoThrow(eq(AppOpsManager.OPSTR_FINE_LOCATION),
+ anyInt(), anyString(), nullable(String.class), nullable(String.class)))
+ .thenReturn(AppOpsManager.MODE_ALLOWED);
+ when(mAppOpsManager.noteOpNoThrow(eq(AppOpsManager.OPSTR_COARSE_LOCATION),
+ anyInt(), anyString(), nullable(String.class), nullable(String.class)))
+ .thenReturn(AppOpsManager.MODE_ALLOWED);
+
+ when(mLocationManager.isLocationEnabledForUser(any(UserHandle.class))).thenReturn(true);
+ when(mContext.checkPermission(eq(Manifest.permission.INTERACT_ACROSS_USERS_FULL),
+ anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+ }
+ }
+
+ private <T> void mockSystemService(Class<T> clazz , T obj, String serviceName) {
+ when(mContext.getSystemServiceName(eq(clazz))).thenReturn(serviceName);
+ when(mContext.getSystemService(eq(serviceName))).thenReturn(obj);
+ }
+
+ private void setTargetSdkVersion(int version) {
+ ApplicationInfo testAppInfo = new ApplicationInfo();
+ testAppInfo.targetSdkVersion = version;
+ try {
+ when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any()))
+ .thenReturn(testAppInfo);
+ } catch (Exception ignored) {
+ }
+ }
+
+ private void setCanReadPrivilegedPhoneState(boolean granted) {
+ doReturn(granted ? PERMISSION_GRANTED : PERMISSION_DENIED).when(mContext)
+ .checkCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
}
}
diff --git a/tests/src/com/android/phone/SimPhonebookProviderTest.java b/tests/src/com/android/phone/SimPhonebookProviderTest.java
new file mode 100644
index 0000000..f897fac
--- /dev/null
+++ b/tests/src/com/android/phone/SimPhonebookProviderTest.java
@@ -0,0 +1,1496 @@
+/*
+ * Copyright (C) 2020 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.phone;
+
+import static android.provider.SimPhonebookContract.ElementaryFiles.EF_ADN;
+import static android.provider.SimPhonebookContract.ElementaryFiles.EF_FDN;
+import static android.provider.SimPhonebookContract.ElementaryFiles.EF_SDN;
+
+import static com.android.internal.telephony.testing.CursorSubject.assertThat;
+import static com.android.internal.telephony.testing.TelephonyAssertions.assertThrows;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.SimPhonebookContract;
+import android.provider.SimPhonebookContract.ElementaryFiles;
+import android.provider.SimPhonebookContract.SimRecords;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.util.Pair;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.provider.ProviderTestRule;
+
+import com.android.internal.telephony.IIccPhoneBook;
+import com.android.internal.telephony.uicc.AdnCapacity;
+import com.android.internal.telephony.uicc.AdnRecord;
+import com.android.internal.telephony.uicc.IccConstants;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.truth.Correspondence;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.AdditionalAnswers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+@RunWith(AndroidJUnit4.class)
+public final class SimPhonebookProviderTest {
+
+ private static final String EMOJI = new String(Character.toChars(0x1F642));
+ private static final Correspondence<AdnRecord, AdnRecord> ADN_RECORD_IS_EQUAL =
+ Correspondence.from(AdnRecord::isEqual, "isEqual");
+
+ @Rule
+ public final ProviderTestRule mProviderRule = new ProviderTestRule.Builder(
+ TestableSimPhonebookProvider.class, SimPhonebookContract.AUTHORITY).build();
+
+ private ContentResolver mResolver;
+ private FakeIccPhoneBook mIccPhoneBook;
+ private SubscriptionManager mMockSubscriptionManager;
+
+ private static List<SubscriptionInfo> createSubscriptionsWithIds(int... subscriptionIds) {
+ ImmutableList.Builder<SubscriptionInfo> builder = ImmutableList.builderWithExpectedSize(
+ subscriptionIds.length);
+ for (int i = 0; i < subscriptionIds.length; i++) {
+ builder.add(createSubscriptionInfo(i, subscriptionIds[i]));
+ }
+ return builder.build();
+ }
+
+ private static SubscriptionInfo createSubscriptionInfo(int slotIndex, int subscriptiondId) {
+ return new SubscriptionInfo(
+ subscriptiondId, "", slotIndex, null, null, 0, 0, null, 0, null, null, null, null,
+ false, null, null);
+ }
+
+ @Before
+ public void setUp() {
+ mMockSubscriptionManager = spy(
+ Objects.requireNonNull(ApplicationProvider.getApplicationContext()
+ .getSystemService(SubscriptionManager.class)));
+ mIccPhoneBook = new FakeIccPhoneBook();
+ mResolver = mProviderRule.getResolver();
+
+ TestableSimPhonebookProvider.setup(mResolver, mMockSubscriptionManager, mIccPhoneBook);
+ }
+
+ @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)) {
+ assertThat(cursor).hasColumnNames();
+ }
+
+ // Single column
+ try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, new String[]{
+ ElementaryFiles.EF_TYPE
+ }, null, null)) {
+ assertThat(cursor).hasColumnNames(ElementaryFiles.EF_TYPE);
+ }
+
+ // Duplicate column
+ try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, new String[]{
+ ElementaryFiles.SUBSCRIPTION_ID, ElementaryFiles.SUBSCRIPTION_ID
+ }, null, null)) {
+ assertThat(cursor).hasColumnNames(ElementaryFiles.SUBSCRIPTION_ID,
+ ElementaryFiles.SUBSCRIPTION_ID);
+ }
+
+ // Random order of all columns
+ String[] projection = Arrays.copyOf(
+ SimPhonebookProvider.ELEMENTARY_FILES_ALL_COLUMNS,
+ SimPhonebookProvider.ELEMENTARY_FILES_ALL_COLUMNS.length);
+ Collections.shuffle(Arrays.asList(projection));
+ try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, projection, null, null)) {
+ assertThat(cursor).hasColumnNames(projection);
+ }
+ }
+
+ @Test
+ public void query_entityFiles_unrecognizedColumn_throwsIllegalArgumentException() {
+ assertThrows(IllegalArgumentException.class, () ->
+ mResolver.query(ElementaryFiles.CONTENT_URI, new String[]{"invalid_column"}, null,
+ null));
+ }
+
+ @Test
+ public void query_entityFiles_noSim_returnsEmptyCursor() {
+ when(mMockSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
+ ImmutableList.of());
+
+ try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, null, null, null)) {
+ assertThat(cursor).hasCount(0);
+ }
+ }
+
+ @Test
+ public void query_entityFiles_multiSim_returnsCursorWithRowForEachSimEf() {
+ setupSimsWithSubscriptionIds(2, 3, 7);
+
+ mIccPhoneBook.setRecordsSize(2, IccConstants.EF_ADN, 10, 25);
+ mIccPhoneBook.setRecordsSize(2, IccConstants.EF_FDN, 5, 20);
+ mIccPhoneBook.setRecordsSize(2, IccConstants.EF_SDN, 15, 20);
+ mIccPhoneBook.setRecordsSize(3, IccConstants.EF_ADN, 100, 30);
+ // These Will be omitted from results because zero size indicates the EF is not supported.
+ mIccPhoneBook.setRecordsSize(3, IccConstants.EF_FDN, 0, 0);
+ mIccPhoneBook.setRecordsSize(3, IccConstants.EF_SDN, 0, 0);
+ mIccPhoneBook.setRecordsSize(7, IccConstants.EF_ADN, 0, 0);
+ mIccPhoneBook.setRecordsSize(7, IccConstants.EF_FDN, 0, 0);
+ mIccPhoneBook.setRecordsSize(7, IccConstants.EF_SDN, 0, 0);
+
+ String[] projection = {
+ ElementaryFiles.SLOT_INDEX, ElementaryFiles.SUBSCRIPTION_ID,
+ ElementaryFiles.EF_TYPE, ElementaryFiles.MAX_RECORDS,
+ ElementaryFiles.NAME_MAX_LENGTH, ElementaryFiles.PHONE_NUMBER_MAX_LENGTH
+ };
+ try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, projection, null, null)) {
+ assertThat(cursor).hasColumnNames(projection);
+
+ assertThat(cursor)
+ .atRow(0).hasRowValues(0, 2, ElementaryFiles.EF_ADN, 10, 11, 20)
+ .atRow(1).hasRowValues(0, 2, ElementaryFiles.EF_FDN, 5, 6, 20)
+ .atRow(2).hasRowValues(0, 2, ElementaryFiles.EF_SDN, 15, 6, 20)
+ .atRow(3).hasRowValues(1, 3, ElementaryFiles.EF_ADN, 100, 16, 20);
+ }
+ }
+
+ @Test
+ public void query_entityFiles_simWithZeroSizes_returnsEmptyCursor() {
+ setupSimsWithSubscriptionIds(1);
+
+ mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 0, 0);
+ mIccPhoneBook.setRecordsSize(1, IccConstants.EF_FDN, 0, 0);
+ mIccPhoneBook.setRecordsSize(1, IccConstants.EF_SDN, 0, 0);
+
+ try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, null, null, null)) {
+ assertThat(cursor).hasCount(0);
+ }
+ }
+
+ @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[]{
+ SimRecords.PHONE_NUMBER
+ }, null, null)) {
+ assertThat(cursor).hasColumnNames(SimRecords.PHONE_NUMBER);
+ }
+
+ // Duplicate column
+ try (Cursor cursor = mResolver.query(contentAdn, new String[]{
+ SimRecords.PHONE_NUMBER, SimRecords.PHONE_NUMBER
+ }, null, null)) {
+ assertThat(cursor).hasColumnNames(SimRecords.PHONE_NUMBER, SimRecords.PHONE_NUMBER);
+ }
+
+ // Random order of all columns
+ String[] projection = Arrays.copyOf(
+ SimPhonebookProvider.SIM_RECORDS_ALL_COLUMNS,
+ SimPhonebookProvider.SIM_RECORDS_ALL_COLUMNS.length);
+ Collections.shuffle(Arrays.asList(projection));
+ try (Cursor cursor = mResolver.query(contentAdn, projection, null, null)) {
+ assertThat(cursor).hasColumnNames(projection);
+ }
+ }
+
+ @Test
+ public void query_adnRecords_noRecords_returnsEmptyCursor() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+
+ try (Cursor cursor = mResolver.query(SimRecords.getContentUri(1, EF_ADN), null, null,
+ null)) {
+ assertThat(cursor).hasCount(0);
+ }
+ }
+
+ @Test
+ public void query_simRecords_nullRecordList_returnsEmptyCursor() throws Exception {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+ // Use a mock so that a null list can be returned
+ IIccPhoneBook mockIccPhoneBook = mock(
+ IIccPhoneBook.class, AdditionalAnswers.delegatesTo(mIccPhoneBook));
+ when(mockIccPhoneBook.getAdnRecordsInEfForSubscriber(anyInt(), anyInt())).thenReturn(null);
+ TestableSimPhonebookProvider.setup(mResolver, mMockSubscriptionManager, mockIccPhoneBook);
+
+ try (Cursor adnCursor = mResolver.query(SimRecords.getContentUri(1, EF_ADN), null, null,
+ null);
+ Cursor fdnCursor = mResolver.query(SimRecords.getContentUri(1, EF_FDN), null, null,
+ null);
+ Cursor sdnCursor = mResolver.query(SimRecords.getContentUri(1, EF_SDN), null, null,
+ null)
+ ) {
+ assertThat(adnCursor).hasCount(0);
+ assertThat(fdnCursor).hasCount(0);
+ assertThat(sdnCursor).hasCount(0);
+ }
+ }
+
+ @Test
+ public void query_simRecords_singleSim_returnsDataForCorrectEf() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Person Adn1", "8005550101");
+ mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Person Adn2", "8005550102");
+ mIccPhoneBook.addRecord(1, IccConstants.EF_FDN, "Person Fdn", "8005550103");
+ mIccPhoneBook.addRecord(1, IccConstants.EF_SDN, "Person Sdn", "8005550104");
+ mIccPhoneBook.setDefaultSubscriptionId(1);
+
+ String[] projection = {
+ SimRecords.SUBSCRIPTION_ID,
+ SimRecords.ELEMENTARY_FILE_TYPE,
+ SimRecords.RECORD_NUMBER,
+ SimRecords.NAME,
+ SimRecords.PHONE_NUMBER
+ };
+ try (Cursor adnCursor = mResolver.query(SimRecords.getContentUri(1, EF_ADN),
+ projection, null, null);
+ Cursor fdnCursor = mResolver.query(SimRecords.getContentUri(1, EF_FDN),
+ projection, null, null);
+ Cursor sdnCursor = mResolver.query(SimRecords.getContentUri(1, EF_SDN),
+ projection, null, null)
+ ) {
+
+ assertThat(adnCursor)
+ .atRow(0).hasRowValues(1, ElementaryFiles.EF_ADN, 1, "Person Adn1",
+ "8005550101")
+ .atRow(1).hasRowValues(1, ElementaryFiles.EF_ADN, 2, "Person Adn2",
+ "8005550102");
+ assertThat(fdnCursor)
+ .atRow(0).hasRowValues(1, ElementaryFiles.EF_FDN, 1, "Person Fdn",
+ "8005550103");
+ assertThat(sdnCursor)
+ .atRow(0).hasRowValues(1, ElementaryFiles.EF_SDN, 1, "Person Sdn",
+ "8005550104");
+ }
+ }
+
+ @Test
+ public void query_adnRecords_returnsAdnData() {
+ setupSimsWithSubscriptionIds(1, 2, 4);
+ mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Person Sim1", "8005550101");
+ mIccPhoneBook.addRecord(1, IccConstants.EF_FDN, "Omitted Sim1", "8005550199");
+ mIccPhoneBook.addRecord(2, IccConstants.EF_ADN, "Person Sim2a", "8005550103");
+ mIccPhoneBook.addRecord(2, IccConstants.EF_ADN, "Person Sim2b", "8005550104");
+ mIccPhoneBook.addRecord(2, IccConstants.EF_ADN, "Person Sim2c", "8005550105");
+ mIccPhoneBook.addRecord(2, IccConstants.EF_SDN, "Omitted Sim2", "8005550198");
+ mIccPhoneBook.addRecord(4, IccConstants.EF_ADN, "Person Sim4", "8005550106");
+ mIccPhoneBook.setDefaultSubscriptionId(1);
+
+ String[] projection = {
+ SimRecords.SUBSCRIPTION_ID,
+ SimRecords.ELEMENTARY_FILE_TYPE,
+ SimRecords.RECORD_NUMBER,
+ SimRecords.NAME,
+ SimRecords.PHONE_NUMBER
+ };
+ try (Cursor cursorSim1 = mResolver.query(SimRecords.getContentUri(1, EF_ADN),
+ projection, null, null);
+ Cursor cursorSim2 = mResolver.query(SimRecords.getContentUri(2, EF_ADN),
+ projection, null, null);
+ Cursor cursorSim4 = mResolver.query(SimRecords.getContentUri(4, EF_ADN),
+ projection, null, null)
+ ) {
+
+ assertThat(cursorSim1).hasData(new Object[][]{
+ {1, ElementaryFiles.EF_ADN, 1, "Person Sim1", "8005550101"},
+ });
+ assertThat(cursorSim2).hasData(new Object[][]{
+ {2, ElementaryFiles.EF_ADN, 1, "Person Sim2a", "8005550103"},
+ {2, ElementaryFiles.EF_ADN, 2, "Person Sim2b", "8005550104"},
+ {2, ElementaryFiles.EF_ADN, 3, "Person Sim2c", "8005550105"},
+ });
+ assertThat(cursorSim4).hasData(new Object[][]{
+ {4, ElementaryFiles.EF_ADN, 1, "Person Sim4", "8005550106"},
+ });
+ }
+ }
+
+ @Test
+ public void query_fdnRecords_returnsFdnData() {
+ setupSimsWithSubscriptionIds(1, 2, 4);
+ mIccPhoneBook.makeAllEfsSupported(1, 2, 4);
+ mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Person Sim1", "8005550101");
+ mIccPhoneBook.addRecord(2, IccConstants.EF_ADN, "Person Sim2a", "8005550103");
+ mIccPhoneBook.addRecord(2, IccConstants.EF_FDN, "Person Sim2b", "8005550104");
+ mIccPhoneBook.addRecord(2, IccConstants.EF_FDN, "Person Sim2c", "8005550105");
+ mIccPhoneBook.addRecord(4, IccConstants.EF_SDN, "Person Sim4", "8005550106");
+ mIccPhoneBook.setDefaultSubscriptionId(1);
+
+ String[] projection = {
+ SimRecords.SUBSCRIPTION_ID,
+ SimRecords.ELEMENTARY_FILE_TYPE,
+ SimRecords.RECORD_NUMBER,
+ SimRecords.NAME,
+ SimRecords.PHONE_NUMBER
+ };
+ try (Cursor cursorSim1Fdn = mResolver.query(SimRecords.getContentUri(1, EF_FDN),
+ projection, null, null);
+ Cursor cursorSim2Fdn = mResolver.query(SimRecords.getContentUri(2, EF_FDN),
+ projection, null, null);
+ Cursor cursorSim4Fdn = mResolver.query(SimRecords.getContentUri(4, EF_FDN),
+ projection, null, null)
+ ) {
+
+ assertThat(cursorSim1Fdn).hasCount(0);
+ assertThat(cursorSim2Fdn).hasData(new Object[][]{
+ {2, ElementaryFiles.EF_FDN, 1, "Person Sim2b", "8005550104"},
+ {2, ElementaryFiles.EF_FDN, 2, "Person Sim2c", "8005550105"},
+ });
+ assertThat(cursorSim4Fdn).hasCount(0);
+ }
+ }
+
+ @Test
+ public void query_sdnRecords_returnsSdnData() {
+ setupSimsWithSubscriptionIds(1, 2, 4);
+ mIccPhoneBook.makeAllEfsSupported(1, 2, 4);
+ mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Person Adn1", "8005550101");
+ mIccPhoneBook.addRecord(1, IccConstants.EF_FDN, "Person Fdn1", "8005550102");
+ mIccPhoneBook.addRecord(1, IccConstants.EF_SDN, "Person Sdn1", "8005550103");
+ mIccPhoneBook.addRecord(2, IccConstants.EF_ADN, "Person Adn2a", "8005550104");
+ mIccPhoneBook.addRecord(2, IccConstants.EF_FDN, "Person Fdn2b", "8005550105");
+ mIccPhoneBook.addRecord(4, IccConstants.EF_SDN, "Person Sdn4a", "8005550106");
+ mIccPhoneBook.addRecord(4, IccConstants.EF_SDN, "Person Sdn4b", "8005550107");
+ mIccPhoneBook.setDefaultSubscriptionId(1);
+
+ String[] projection = {
+ SimRecords.SUBSCRIPTION_ID,
+ SimRecords.ELEMENTARY_FILE_TYPE,
+ SimRecords.RECORD_NUMBER,
+ SimRecords.NAME,
+ SimRecords.PHONE_NUMBER
+ };
+ try (Cursor cursorSim1Sdn = mResolver.query(SimRecords.getContentUri(1, EF_SDN),
+ projection, null, null);
+ Cursor cursorSim2Sdn = mResolver.query(SimRecords.getContentUri(2, EF_SDN),
+ projection, null, null);
+ Cursor cursorSim4Sdn = mResolver.query(SimRecords.getContentUri(4, EF_SDN),
+ projection, null, null)
+ ) {
+
+ assertThat(cursorSim1Sdn)
+ .atRow(0).hasRowValues(1, ElementaryFiles.EF_SDN, 1, "Person Sdn1",
+ "8005550103");
+ assertThat(cursorSim2Sdn).hasCount(0);
+ assertThat(cursorSim4Sdn)
+ .atRow(0).hasRowValues(4, ElementaryFiles.EF_SDN, 1, "Person Sdn4a",
+ "8005550106")
+ .atRow(1).hasRowValues(4, ElementaryFiles.EF_SDN, 2, "Person Sdn4b",
+ "8005550107");
+ }
+ }
+
+ @Test
+ public void query_adnRecords_nonExistentSim_throwsCorrectException() {
+ setupSimsWithSubscriptionIds(1);
+
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mResolver.query(SimRecords.getContentUri(123, EF_ADN), null, null, null));
+ assertThat(e).hasMessageThat().isEqualTo("No active SIM with subscription ID 123");
+ }
+
+ @Test
+ public void insert_nonExistentSim_throwsCorrectException() {
+ setupSimsWithSubscriptionIds(1);
+ ContentValues values = new ContentValues();
+ values.put(SimRecords.NAME, "Name");
+ values.put(SimRecords.PHONE_NUMBER, "123");
+
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mResolver.insert(SimRecords.getContentUri(123, EF_ADN), values));
+ assertThat(e).hasMessageThat().isEqualTo("No active SIM with subscription ID 123");
+ }
+
+ @Test
+ public void update_nonExistentSim_throwsCorrectException() {
+ setupSimsWithSubscriptionIds(1);
+ ContentValues values = new ContentValues();
+ values.put(SimRecords.NAME, "Name");
+ values.put(SimRecords.PHONE_NUMBER, "123");
+
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mResolver.update(SimRecords.getItemUri(123, EF_ADN, 1), values, null));
+ assertThat(e).hasMessageThat().isEqualTo("No active SIM with subscription ID 123");
+ }
+
+ @Test
+ public void delete_nonExistentSim_throwsCorrectException() {
+ setupSimsWithSubscriptionIds(1);
+
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mResolver.delete(SimRecords.getItemUri(123, EF_ADN, 1), null));
+ assertThat(e).hasMessageThat().isEqualTo("No active SIM with subscription ID 123");
+ }
+
+ @Test
+ public void query_adnRecords_zeroSizeEf_throwsCorrectException() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 0, 0);
+
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mResolver.query(SimRecords.getContentUri(1, EF_ADN), null, null, null));
+ assertThat(e).hasMessageThat().isEqualTo(
+ "adn is not supported for SIM with subscription ID 1");
+ }
+
+ @Test
+ public void query_itemUri_returnsCorrectRow() {
+ setupSimsWithSubscriptionIds(1, 2);
+ mIccPhoneBook.addRecord(1,
+ new AdnRecord(IccConstants.EF_ADN, 1, "Name@Adn1[1]", "8005550101"));
+ mIccPhoneBook.addRecord(1,
+ new AdnRecord(IccConstants.EF_ADN, 2, "Name@Adn1[2]", "8005550102"));
+ mIccPhoneBook.addRecord(1,
+ new AdnRecord(IccConstants.EF_ADN, 3, "Name@Adn1[3]", "8005550103"));
+ mIccPhoneBook.addRecord(2,
+ new AdnRecord(IccConstants.EF_ADN, 3, "Name@Adn2[3]", "8005550104"));
+ mIccPhoneBook.addRecord(1,
+ new AdnRecord(IccConstants.EF_FDN, 1, "Name@Fdn1[1]", "8005550105"));
+ mIccPhoneBook.addRecord(2,
+ new AdnRecord(IccConstants.EF_SDN, 1, "Name@Sdn2[1]", "8005550106"));
+
+ String[] projection = {
+ SimRecords.SUBSCRIPTION_ID, SimRecords.ELEMENTARY_FILE_TYPE,
+ SimRecords.RECORD_NUMBER, SimRecords.NAME, SimRecords.PHONE_NUMBER
+ };
+ try (Cursor item1 = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1),
+ projection, null, null);
+ Cursor item2 = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 3),
+ projection, null, null);
+ Cursor item3 = mResolver.query(SimRecords.getItemUri(2, ElementaryFiles.EF_ADN, 3),
+ projection, null, null);
+ Cursor item4 = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_FDN, 1),
+ projection, null, null);
+ Cursor item5 = mResolver.query(SimRecords.getItemUri(2, ElementaryFiles.EF_SDN, 1),
+ projection, null, null)
+ ) {
+ assertThat(item1).hasSingleRow(1, ElementaryFiles.EF_ADN, 1, "Name@Adn1[1]",
+ "8005550101");
+ assertThat(item2).hasSingleRow(1, ElementaryFiles.EF_ADN, 3, "Name@Adn1[3]",
+ "8005550103");
+ assertThat(item3).hasSingleRow(2, ElementaryFiles.EF_ADN, 3, "Name@Adn2[3]",
+ "8005550104");
+ assertThat(item4).hasSingleRow(1, ElementaryFiles.EF_FDN, 1, "Name@Fdn1[1]",
+ "8005550105");
+ assertThat(item5).hasSingleRow(2, ElementaryFiles.EF_SDN, 1, "Name@Sdn2[1]",
+ "8005550106");
+ }
+ }
+
+ @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);
+ mIccPhoneBook.setRecordsSize(1, IccConstants.EF_FDN, 1, 30);
+ mIccPhoneBook.setRecordsSize(1, IccConstants.EF_SDN, 1, 30);
+
+ try (Cursor adnItem = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1),
+ null, null, null);
+ Cursor fdnItem = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_FDN, 1),
+ null, null, null);
+ Cursor sdnItem = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_SDN, 1),
+ null, null, null)
+ ) {
+ assertThat(adnItem).hasCount(0);
+ assertThat(fdnItem).hasCount(0);
+ assertThat(sdnItem).hasCount(0);
+ }
+ }
+
+ @Test
+ public void query_itemUriIndexExceedsMax_returnsEmptyCursor() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 30);
+ mIccPhoneBook.setRecordsSize(1, IccConstants.EF_FDN, 1, 30);
+ mIccPhoneBook.setRecordsSize(1, IccConstants.EF_SDN, 1, 30);
+
+ try (Cursor adnItem = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 2),
+ null, null, null);
+ Cursor fdnItem = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_FDN, 2),
+ null, null, null);
+ Cursor sdnItem = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_SDN, 2),
+ null, null, null)
+ ) {
+ assertThat(adnItem).hasCount(0);
+ assertThat(fdnItem).hasCount(0);
+ assertThat(sdnItem).hasCount(0);
+ }
+ }
+
+ @Test
+ public void query_invalidItemIndex_throwsIllegalArgumentException() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+
+ assertThrows(IllegalArgumentException.class, () ->
+ mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, -1),
+ null, null, null));
+ assertThrows(IllegalArgumentException.class, () ->
+ mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_FDN, -1),
+ null, null, null));
+ assertThrows(IllegalArgumentException.class, () ->
+ mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_SDN, -1),
+ null, null, null));
+ assertThrows(IllegalArgumentException.class, () ->
+ mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 0),
+ null, null, null));
+ assertThrows(IllegalArgumentException.class, () ->
+ mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_FDN, 0),
+ null, null, null));
+ assertThrows(IllegalArgumentException.class, () ->
+ mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_SDN, 0),
+ null, null, null));
+ }
+
+ @Test
+ public void insert_adnRecord_addsAdnRecordAndReturnsUriForNewRecord() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+
+ ContentValues values = new ContentValues();
+ values.put(SimRecords.NAME, "First Last");
+ 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, "First Last", "8005550101"));
+
+ assertThat(uri).isEqualTo(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1));
+ }
+
+ @Test
+ public void insert_adnRecordWithExistingRecords_returnsUriWithCorrectIndex() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.setDefaultSubscriptionId(1);
+ mIccPhoneBook.addRecord(new AdnRecord(IccConstants.EF_ADN, 2, "Existing1", "8005550101"));
+ mIccPhoneBook.addRecord(new AdnRecord(IccConstants.EF_ADN, 3, "Existing2", "8005550102"));
+ mIccPhoneBook.addRecord(new AdnRecord(IccConstants.EF_ADN, 5, "Existing3", "8005550103"));
+
+ ContentValues values = new ContentValues();
+ values.put(SimRecords.NAME, "New1");
+ values.put(SimRecords.PHONE_NUMBER, "8005550104");
+
+ Uri insert1 = mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values);
+ values.put(SimRecords.NAME, "New2");
+ values.put(SimRecords.PHONE_NUMBER, "8005550105");
+ Uri insert2 = mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values);
+ values.put(SimRecords.NAME, "New3");
+ values.put(SimRecords.PHONE_NUMBER, "8005550106");
+ Uri insert3 = mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values);
+
+ assertThat(
+ mIccPhoneBook.getAdnRecordsInEfForSubscriber(1, IccConstants.EF_ADN).subList(0, 6))
+ .comparingElementsUsing(ADN_RECORD_IS_EQUAL)
+ .containsExactly(
+ new AdnRecord(IccConstants.EF_ADN, 1, "New1", "8005550104"),
+ new AdnRecord(IccConstants.EF_ADN, 2, "Existing1", "8005550101"),
+ new AdnRecord(IccConstants.EF_ADN, 3, "Existing2", "8005550102"),
+ new AdnRecord(IccConstants.EF_ADN, 4, "New2", "8005550105"),
+ new AdnRecord(IccConstants.EF_ADN, 5, "Existing3", "8005550103"),
+ new AdnRecord(IccConstants.EF_ADN, 6, "New3", "8005550106"));
+ assertThat(insert1).isEqualTo(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1));
+ assertThat(insert2).isEqualTo(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 4));
+ assertThat(insert3).isEqualTo(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 6));
+ }
+
+ @Test
+ public void insert_efFull_throwsCorrectException() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 30);
+ mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Existing", "8005550101");
+
+ ContentValues values = new ContentValues();
+ values.put(SimRecords.NAME, "New");
+ values.put(SimRecords.PHONE_NUMBER, "8005550102");
+
+ Uri uri = SimRecords.getContentUri(1, EF_ADN);
+ IllegalStateException e = assertThrows(IllegalStateException.class,
+ () -> mResolver.insert(uri, values));
+ assertThat(e).hasMessageThat().isEqualTo(
+ uri + " is full. Please delete records to add new ones.");
+ }
+
+ @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);
+
+ Uri result = mResolver.insert(SimRecords.getContentUri(1, EF_ADN), null);
+
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void update_nullValues_returnsZero() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+ mIccPhoneBook.addAdnRecord(1, "Name", "5550101");
+
+ int result = mResolver.update(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1), null,
+ null);
+
+ assertThat(result).isEqualTo(0);
+ }
+
+ @Test
+ public void insert_emptyValues_returnsNull() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+
+ Uri result = mResolver.insert(SimRecords.getContentUri(1, EF_ADN), new ContentValues());
+
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void insert_nameOmitted_createsRecordWithJustPhoneNumber() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+
+ ContentValues values = new ContentValues();
+ // No name
+ values.put(SimRecords.PHONE_NUMBER, "18005550101");
+
+ mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values);
+
+ // Null name
+ values.putNull(SimRecords.NAME);
+ values.put(SimRecords.PHONE_NUMBER, "18005550102");
+ mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values);
+
+ // Empty name
+ values.put(SimRecords.NAME, "");
+ values.put(SimRecords.PHONE_NUMBER, "18005550103");
+ mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values);
+
+ assertThat(mIccPhoneBook.getAllValidRecords())
+ .comparingElementsUsing(ADN_RECORD_IS_EQUAL)
+ .containsExactly(
+ new AdnRecord(IccConstants.EF_ADN, 1, "", "18005550101"),
+ new AdnRecord(IccConstants.EF_ADN, 2, "", "18005550102"),
+ new AdnRecord(IccConstants.EF_ADN, 3, "", "18005550103"));
+ }
+
+ @Test
+ public void insert_phoneNumberOmitted_throwsCorrectException() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 25);
+
+ ContentValues values = new ContentValues();
+ values.put(SimRecords.NAME, "Name");
+
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values));
+ assertThat(e).hasMessageThat().isEqualTo(SimRecords.PHONE_NUMBER + " is required.");
+ }
+
+ @Test
+ public void insert_nameTooLong_throwsCorrectException() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 25);
+
+ ContentValues values = new ContentValues();
+ // Name is limited to 11 characters when the max record size is 25
+ values.put(SimRecords.NAME, "1234567890ab");
+ values.put(SimRecords.PHONE_NUMBER, "8005550102");
+
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> 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
+ public void insert_phoneNumberTooLong_throwsCorrectException() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 25);
+
+ ContentValues values = new ContentValues();
+ values.put(SimRecords.NAME, "Name");
+ // 21 digits is longer than max of 20
+ values.put(SimRecords.PHONE_NUMBER, "123456789012345678901");
+
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values));
+
+ assertThat(e).hasMessageThat().isEqualTo(SimRecords.PHONE_NUMBER + " is too long.");
+ }
+
+ @Test
+ 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, "(800)555-0190 x7777");
+
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mResolver.insert(SimRecords.getContentUri(1, ElementaryFiles.EF_ADN),
+ values,
+ null));
+ assertThat(e).hasMessageThat().isEqualTo(
+ SimRecords.PHONE_NUMBER + " contains unsupported characters.");
+
+ // The insert didn't actually change the data.
+ assertThat(mIccPhoneBook.getAllValidRecords()).isEmpty();
+ }
+
+ @Test
+ public void insert_unsupportedColumn_throwsCorrectException() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 25);
+
+ ContentValues values = new ContentValues();
+ values.put(SimRecords.NAME, "Name");
+ values.put(SimRecords.PHONE_NUMBER, "18005550101");
+ values.put(SimRecords.RECORD_NUMBER, 8);
+ values.put("extra_phone2", "18005550102");
+
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values));
+ assertThat(e).hasMessageThat().isEqualTo("Unsupported columns: "
+ + SimRecords.RECORD_NUMBER + ",extra_phone2");
+ }
+
+ @Test
+ public void update_existingRecord_updatesRecord() {
+ setupSimsWithSubscriptionIds(1, 2);
+ AdnRecord[] unchanged = new AdnRecord[]{
+ new AdnRecord(IccConstants.EF_ADN, 3, "Other1", "8005550102"),
+ new AdnRecord(IccConstants.EF_ADN, 2, "Other2", "8005550103"),
+ new AdnRecord(IccConstants.EF_FDN, 2, "Other3", "8005550104")
+ };
+ mIccPhoneBook.addRecord(1, unchanged[0]);
+ mIccPhoneBook.addRecord(2, unchanged[1]);
+ mIccPhoneBook.addRecord(2, unchanged[2]);
+ mIccPhoneBook.addRecord(1,
+ new AdnRecord(IccConstants.EF_ADN, 2, "Initial Name", "8005550101"));
+
+ ContentValues values = new ContentValues();
+ values.put(SimRecords.NAME, "Updated Name");
+ values.put(SimRecords.PHONE_NUMBER, "8005550105");
+
+ int result = mResolver.update(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 2), values,
+ null);
+
+ assertThat(result).isEqualTo(1);
+
+ List<AdnRecord> finalRecords = mIccPhoneBook.getAllValidRecords();
+
+ assertThat(finalRecords).comparingElementsUsing(ADN_RECORD_IS_EQUAL)
+ .containsAtLeastElementsIn(unchanged);
+ assertThat(finalRecords).comparingElementsUsing(ADN_RECORD_IS_EQUAL)
+ .doesNotContain(
+ new AdnRecord(IccConstants.EF_ADN, 2, "Initial Name", "80005550101"));
+ assertThat(finalRecords).comparingElementsUsing(ADN_RECORD_IS_EQUAL)
+ .contains(new AdnRecord(IccConstants.EF_ADN, 2, "Updated Name", "8005550105"));
+ }
+
+ @Test
+ public void update_emptyRecord_updatesRecord() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+
+ ContentValues values = new ContentValues();
+ values.put(SimRecords.NAME, "name");
+ values.put(SimRecords.PHONE_NUMBER, "18005550101");
+ // No record actually exists with record number 10 but we allow clients to update it
+ // as a way to set the information at a specific record number.
+ int result = mResolver.update(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 10),
+ values, null);
+
+ assertThat(result).isEqualTo(1);
+ List<AdnRecord> finalRecords = mIccPhoneBook.getAllValidRecords();
+ assertThat(finalRecords).comparingElementsUsing(ADN_RECORD_IS_EQUAL)
+ .containsExactly(new AdnRecord(IccConstants.EF_ADN, 10, "name", "18005550101"));
+ }
+
+ @Test
+ public void delete_existingRecord_deletesRecord() {
+ setupSimsWithSubscriptionIds(1, 2);
+ AdnRecord[] unchanged = new AdnRecord[]{
+ new AdnRecord(IccConstants.EF_ADN, 3, "Other1", "8005550102"),
+ new AdnRecord(IccConstants.EF_ADN, 2, "Other2", "8005550103"),
+ new AdnRecord(IccConstants.EF_FDN, 2, "Other3", "8005550104")
+ };
+ mIccPhoneBook.addRecord(1,
+ new AdnRecord(IccConstants.EF_ADN, 2, "Initial Name", "8005550101"));
+ mIccPhoneBook.addRecord(1, unchanged[0]);
+ mIccPhoneBook.addRecord(2, unchanged[1]);
+ mIccPhoneBook.addRecord(2, unchanged[2]);
+
+ int result = mResolver.delete(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 2), null);
+
+ assertThat(result).isEqualTo(1);
+
+ assertThat(mIccPhoneBook.getAllValidRecords()).comparingElementsUsing(ADN_RECORD_IS_EQUAL)
+ .containsExactlyElementsIn(unchanged);
+ }
+
+ @Test
+ public void update_indexExceedingMax_returnsZero() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 30);
+
+ ContentValues values = new ContentValues();
+ values.put(SimRecords.NAME, "name");
+ values.put(SimRecords.PHONE_NUMBER, "18005551010");
+ int result = mResolver.update(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 2),
+ values, null);
+
+ assertThat(result).isEqualTo(0);
+ }
+
+ @Test
+ public void update_indexOverflow_throwsIllegalArgumentException() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+
+ ContentValues values = new ContentValues();
+ values.put(SimRecords.NAME, "name");
+ values.put(SimRecords.PHONE_NUMBER, "18005551010");
+ assertThrows(IllegalArgumentException.class, () -> mResolver.update(
+ SimRecords.getContentUri(1, EF_ADN).buildUpon().appendPath(
+ String.valueOf((Long.MAX_VALUE))).build(),
+ values, null));
+ }
+
+ @Test
+ public void delete_emptyRecord_returnsZero() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+
+ int result = mResolver.delete(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 2), null);
+
+ assertThat(result).isEqualTo(0);
+ }
+
+ @Test
+ public void delete_indexExceedingMax_returnsZero() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+ mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 30);
+
+ int result = mResolver.delete(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 2), null);
+
+ assertThat(result).isEqualTo(0);
+ }
+
+ @Test
+ public void delete_indexOverflow_throwsIllegalArgumentException() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+
+ assertThrows(IllegalArgumentException.class, () -> mResolver.delete(
+ SimRecords.getContentUri(1, EF_ADN).buildUpon().appendPath(
+ String.valueOf((Long.MAX_VALUE))).build(),
+ null));
+ }
+
+ @Test
+ public void update_nameOrNumberTooLong_throwsCorrectException() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 25);
+ mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Initial", "8005550101");
+
+ ContentValues values = new ContentValues();
+ // Name is limited to 11 characters
+ values.put(SimRecords.NAME, "1234567890ab");
+ values.put(SimRecords.PHONE_NUMBER, "8005550102");
+
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mResolver.update(SimRecords.getItemUri(
+ 1, ElementaryFiles.EF_ADN, 1), values, null));
+ assertThat(e).hasMessageThat().isEqualTo(SimRecords.NAME + " is too long.");
+
+ values.put(SimRecords.NAME, "abc");
+ values.put(SimRecords.PHONE_NUMBER, "123456789012345678901");
+
+ e = assertThrows(IllegalArgumentException.class,
+ () -> mResolver.update(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1),
+ values,
+ null));
+ assertThat(e).hasMessageThat().isEqualTo(SimRecords.PHONE_NUMBER + " is too long.");
+ // The updates didn't actually change the data
+ assertThat(mIccPhoneBook.getAllValidRecords())
+ .comparingElementsUsing(Correspondence.from(AdnRecord::isEqual, "isEqual"))
+ .containsExactly(new AdnRecord(IccConstants.EF_ADN, 1, "Initial", "8005550101"));
+ }
+
+ @Test
+ public void update_numberWithInvalidCharacters_throwsCorrectException() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 32);
+ mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Initial", "8005550101");
+
+ ContentValues values = new ContentValues();
+ values.put(SimRecords.NAME, "Name");
+ values.put(SimRecords.PHONE_NUMBER, "(800)555-0190 x7777");
+
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mResolver.update(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1),
+ values,
+ null));
+ assertThat(e).hasMessageThat().isEqualTo(
+ SimRecords.PHONE_NUMBER + " contains unsupported characters.");
+
+ // 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"));
+ }
+
+ @Test
+ public void insert_nonAdnDirUris_throwsUnsupportedOperationException() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+
+ ContentValues values = new ContentValues();
+ values.put(SimRecords.NAME, "Name");
+ values.put(SimRecords.PHONE_NUMBER, "8005550101");
+
+ assertThrows(UnsupportedOperationException.class, () ->
+ mResolver.insert(ElementaryFiles.CONTENT_URI, values));
+ assertThrows(UnsupportedOperationException.class,
+ () -> mResolver.insert(SimRecords.getContentUri(1, EF_FDN), values));
+ assertThrows(UnsupportedOperationException.class,
+ () -> mResolver.insert(SimRecords.getContentUri(1, EF_SDN), values));
+ assertThrows(UnsupportedOperationException.class, () ->
+ mResolver.insert(SimRecords.getItemUri(1, ElementaryFiles.EF_FDN, 1), values));
+ assertThrows(UnsupportedOperationException.class, () ->
+ mResolver.insert(SimRecords.getItemUri(1, ElementaryFiles.EF_SDN, 1), values));
+ }
+
+ @Test
+ public void update_nonAdnDirUris_throwsUnsupportedOperationException() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+
+ ContentValues values = new ContentValues();
+ values.put(SimRecords.NAME, "Name");
+ values.put(SimRecords.PHONE_NUMBER, "8005550101");
+
+ assertThrows(UnsupportedOperationException.class, () ->
+ mResolver.update(ElementaryFiles.CONTENT_URI, values, null));
+ assertThrows(UnsupportedOperationException.class,
+ () -> mResolver.update(SimRecords.getContentUri(1, EF_FDN), values, null));
+ assertThrows(UnsupportedOperationException.class,
+ () -> mResolver.update(SimRecords.getContentUri(1, EF_SDN), values, null));
+ assertThrows(UnsupportedOperationException.class,
+ () -> mResolver.update(SimRecords.getContentUri(1, EF_SDN), values, null));
+ assertThrows(UnsupportedOperationException.class, () ->
+ mResolver.update(SimRecords.getItemUri(1, ElementaryFiles.EF_FDN, 1), values,
+ null));
+ assertThrows(UnsupportedOperationException.class, () ->
+ mResolver.update(SimRecords.getItemUri(1, ElementaryFiles.EF_SDN, 1), values,
+ null));
+ }
+
+ @Test
+ public void delete_nonAdnDirUris_throwsUnsupportedOperationException() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+
+ ContentValues values = new ContentValues();
+ values.put(SimRecords.NAME, "Name");
+ values.put(SimRecords.PHONE_NUMBER, "8005550101");
+
+ assertThrows(UnsupportedOperationException.class, () ->
+ mResolver.delete(ElementaryFiles.CONTENT_URI, null));
+ assertThrows(UnsupportedOperationException.class,
+ () -> mResolver.delete(SimRecords.getContentUri(1, EF_FDN), null));
+ assertThrows(UnsupportedOperationException.class,
+ () -> mResolver.delete(SimRecords.getContentUri(1, EF_SDN), null));
+ assertThrows(UnsupportedOperationException.class,
+ () -> mResolver.delete(SimRecords.getContentUri(1, EF_SDN), null));
+ assertThrows(UnsupportedOperationException.class, () ->
+ mResolver.delete(SimRecords.getItemUri(1, ElementaryFiles.EF_FDN, 1), null));
+ assertThrows(UnsupportedOperationException.class, () ->
+ mResolver.delete(SimRecords.getItemUri(1, ElementaryFiles.EF_SDN, 1), null));
+ }
+
+ @Test
+ public void subscriptionsChange_callsNotifyChange() {
+ // Clear invocations that happened in setUp
+ Mockito.reset(mMockSubscriptionManager);
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+ SimPhonebookProvider.ContentNotifier mockNotifier = mock(
+ SimPhonebookProvider.ContentNotifier.class);
+ ArgumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener> listenerCaptor =
+ ArgumentCaptor.forClass(SubscriptionManager.OnSubscriptionsChangedListener.class);
+
+ TestableSimPhonebookProvider.setup(
+ mResolver, mMockSubscriptionManager, mIccPhoneBook, mockNotifier);
+ verify(mMockSubscriptionManager).addOnSubscriptionsChangedListener(
+ any(), listenerCaptor.capture());
+ listenerCaptor.getValue().onSubscriptionsChanged();
+ setupSimsWithSubscriptionIds(1, 2);
+ listenerCaptor.getValue().onSubscriptionsChanged();
+ listenerCaptor.getValue().onSubscriptionsChanged();
+
+ verify(mockNotifier, times(2)).notifyChange(eq(SimPhonebookContract.AUTHORITY_URI));
+ }
+
+ @Test
+ public void insert_callsNotifyChange() {
+ // Clear invocations that happened in setUp
+ Mockito.reset(mMockSubscriptionManager);
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+ SimPhonebookProvider.ContentNotifier mockNotifier = mock(
+ SimPhonebookProvider.ContentNotifier.class);
+
+ TestableSimPhonebookProvider.setup(
+ mResolver, mMockSubscriptionManager, mIccPhoneBook, mockNotifier);
+
+ ContentValues values = new ContentValues();
+ values.put(SimRecords.NAME, "name");
+ values.put(SimRecords.PHONE_NUMBER, "5550101");
+ mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values);
+
+ verify(mockNotifier).notifyChange(eq(SimPhonebookContract.AUTHORITY_URI));
+ }
+
+ @Test
+ public void update_callsNotifyChange() {
+ // Clear invocations that happened in setUp
+ Mockito.reset(mMockSubscriptionManager);
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.addAdnRecord(1, "Initial", "5550101");
+ SimPhonebookProvider.ContentNotifier mockNotifier = mock(
+ SimPhonebookProvider.ContentNotifier.class);
+
+ TestableSimPhonebookProvider.setup(
+ mResolver, mMockSubscriptionManager, mIccPhoneBook, mockNotifier);
+
+ ContentValues values = new ContentValues();
+ values.put(SimRecords.NAME, "Updated");
+ values.put(SimRecords.PHONE_NUMBER, "5550102");
+ mResolver.update(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1), values, null);
+
+ verify(mockNotifier).notifyChange(eq(SimPhonebookContract.AUTHORITY_URI));
+ }
+
+ @Test
+ public void delete_callsNotifyChange() {
+ // Clear invocations that happened in setUp
+ Mockito.reset(mMockSubscriptionManager);
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.addAdnRecord(1, "Initial", "5550101");
+ SimPhonebookProvider.ContentNotifier mockNotifier = mock(
+ SimPhonebookProvider.ContentNotifier.class);
+
+ TestableSimPhonebookProvider.setup(
+ mResolver, mMockSubscriptionManager, mIccPhoneBook, mockNotifier);
+
+ mResolver.delete(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1), null);
+
+ verify(mockNotifier).notifyChange(eq(SimPhonebookContract.AUTHORITY_URI));
+ }
+
+ @Test
+ public void getEncodedNameLength_returnsValueIsCorrect() {
+ String name = "";
+ int length = SimRecords.getEncodedNameLength(mResolver, name);
+ assertThat(length).isEqualTo(0);
+
+ name = "First Last";
+ length = SimRecords.getEncodedNameLength(mResolver, name);
+ assertThat(length).isEqualTo(name.length());
+
+ name = "日本";
+ length = SimRecords.getEncodedNameLength(mResolver, name);
+ assertThat(length).isEqualTo(name.length() * 2 + 1);
+
+ name = EMOJI;
+ length = SimRecords.getEncodedNameLength(mResolver, name);
+ assertThat(length).isEqualTo(name.length() * 2 + 1);
+
+ name = "abc日本" + EMOJI;
+ length = SimRecords.getEncodedNameLength(mResolver, name);
+ assertThat(length).isEqualTo(name.length() * 2 + 1);
+ }
+
+ private void setupSimsWithSubscriptionIds(int... subscriptionIds) {
+ when(mMockSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(subscriptionIds);
+ when(mMockSubscriptionManager.getActiveSubscriptionInfoCount())
+ .thenReturn(subscriptionIds.length);
+ List<SubscriptionInfo> subscriptions = createSubscriptionsWithIds(subscriptionIds);
+ when(mMockSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(subscriptions);
+ for (SubscriptionInfo info : subscriptions) {
+ when(mMockSubscriptionManager.getActiveSubscriptionInfo(info.getSubscriptionId()))
+ .thenReturn(info);
+ }
+ }
+
+ public static class FakeIccPhoneBook extends IIccPhoneBook.Default {
+
+ private static final int DEFAULT_RECORD_SIZE = 30;
+ private static final int DEFAULT_RECORDS_COUNT = 100;
+
+ // The key for both maps is the (subscription ID, efid)
+ private Map<Pair<Integer, Integer>, AdnRecord[]> mRecords = new HashMap<>();
+ // The value is the single record size
+ private Map<Pair<Integer, Integer>, Integer> mRecordSizes = new HashMap<>();
+
+ private int mDefaultSubscriptionId = 101;
+
+ private void addRecord(Pair<Integer, Integer> key, AdnRecord record) {
+ // Assume that if records are being added then the test wants it to be a valid
+ // elementary file so set sizes as well.
+ if (!mRecordSizes.containsKey(key)) {
+ setRecordsSize(key.first, key.second,
+ Math.max(record.getRecId(), DEFAULT_RECORDS_COUNT), DEFAULT_RECORD_SIZE);
+ }
+ mRecords.get(key)[record.getRecId() - 1] = record;
+ }
+
+ public void addRecord(AdnRecord record) {
+ addRecord(Pair.create(mDefaultSubscriptionId, record.getEfid()), record);
+ }
+
+ public void addRecord(int subscriptionId, AdnRecord record) {
+ addRecord(Pair.create(subscriptionId, record.getEfid()), record);
+ }
+
+ public void addRecord(int subscriptionId, int efId, String name, String phoneNumber) {
+ Pair<Integer, Integer> key = Pair.create(subscriptionId, efId);
+ AdnRecord[] records = mRecords.computeIfAbsent(key, unused ->
+ createEmptyRecords(efId, 100));
+ int recordIndex = -1;
+ for (int i = 0; i < records.length; i++) {
+ if (records[i].isEmpty()) {
+ recordIndex = i;
+ break;
+ }
+ }
+ if (recordIndex == -1) {
+ throw new IllegalStateException("");
+ }
+ addRecord(key, new AdnRecord(efId, recordIndex + 1, name, phoneNumber));
+ }
+
+ public void addAdnRecord(int subscriptionId, String name, String phoneNumber) {
+ addRecord(subscriptionId, IccConstants.EF_ADN, name, phoneNumber);
+ }
+
+ public void addAdnRecord(String name, String phoneNumber) {
+ addRecord(mDefaultSubscriptionId, IccConstants.EF_ADN, name, phoneNumber);
+ }
+
+ public List<AdnRecord> getAllValidRecords() {
+ List<AdnRecord> result = new ArrayList<>();
+ for (AdnRecord[] records : mRecords.values()) {
+ for (AdnRecord record : records) {
+ if (!record.isEmpty()) {
+ result.add(record);
+ }
+ }
+ }
+ return result;
+ }
+
+ public void makeAllEfsSupported() {
+ makeAllEfsSupported(mDefaultSubscriptionId);
+ }
+
+ /**
+ * Sets up the fake to return valid records size for all elementary files for the provided
+ * subscription IDs.
+ */
+ public void makeAllEfsSupported(int... subscriptionIds) {
+ for (int subId : subscriptionIds) {
+ makeAllEfsSupported(subId);
+ }
+ }
+
+ /**
+ * Sets up the fake to return valid records size for all elementary files for the provided
+ * subscription IDs.
+ */
+ public void makeAllEfsSupported(int subscriptionId) {
+ setRecordsSize(subscriptionId, IccConstants.EF_ADN, DEFAULT_RECORDS_COUNT,
+ DEFAULT_RECORD_SIZE);
+ setRecordsSize(subscriptionId, IccConstants.EF_FDN, DEFAULT_RECORDS_COUNT,
+ DEFAULT_RECORD_SIZE);
+ setRecordsSize(subscriptionId, IccConstants.EF_SDN, DEFAULT_RECORDS_COUNT,
+ DEFAULT_RECORD_SIZE);
+ }
+
+ public void setRecordsSize(int subscriptionId, int efid, int maxRecordCount,
+ int maxRecordSize) {
+ Pair<Integer, Integer> key = Pair.create(subscriptionId, efid);
+ mRecordSizes.put(key, maxRecordSize);
+ AdnRecord[] records = mRecords.computeIfAbsent(key, unused ->
+ createEmptyRecords(efid, maxRecordCount));
+ if (records.length < maxRecordCount) {
+ throw new IllegalStateException("Records already initialized with a smaller size");
+ }
+ }
+
+ private AdnRecord[] createEmptyRecords(int efid, int count) {
+ AdnRecord[] records = new AdnRecord[count];
+ for (int i = 0; i < records.length; i++) {
+ if (records[i] == null) {
+ records[i] = new AdnRecord(efid, i + 1, "", "");
+ }
+ }
+ return records;
+ }
+
+ public void setDefaultSubscriptionId(int defaultSubscriptionId) {
+ mDefaultSubscriptionId = defaultSubscriptionId;
+ }
+
+ public void reset() {
+ mRecords.clear();
+ mRecordSizes.clear();
+ }
+
+ @Override
+ public List<AdnRecord> getAdnRecordsInEf(int efid) {
+ return getAdnRecordsInEfForSubscriber(mDefaultSubscriptionId, efid);
+ }
+
+ @Override
+ public List<AdnRecord> getAdnRecordsInEfForSubscriber(int subId, int efid) {
+ return Arrays.asList(
+ mRecords.getOrDefault(Pair.create(subId, efid), new AdnRecord[0]));
+ }
+
+ @Override
+ public boolean updateAdnRecordsInEfBySearchForSubscriber(int subId, int efid,
+ ContentValues values, String pin2) {
+ final String oldTag = values.getAsString(IccProvider.STR_TAG);
+ final String oldPhoneNumber = values.getAsString(IccProvider.STR_NUMBER);
+ final String newTag = values.getAsString(IccProvider.STR_NEW_TAG);
+ final String newPhoneNumber = values.getAsString(IccProvider.STR_NEW_NUMBER);
+ return updateAdnRecordsInEfBySearchForSubscriber(subId, efid, oldTag, oldPhoneNumber,
+ newTag, newPhoneNumber, pin2);
+
+ }
+
+ private boolean updateAdnRecordsInEfBySearchForSubscriber(int subId, int efid, String oldTag,
+ String oldPhoneNumber, String newTag, String newPhoneNumber, String pin2) {
+ if (!oldTag.isEmpty() || !oldPhoneNumber.isEmpty()) {
+ throw new IllegalArgumentException(
+ "updateAdnRecordsInEfBySearchForSubscriber only supports insert");
+ }
+ addRecord(subId, efid, newTag, newPhoneNumber);
+ return true;
+ }
+
+ @Override
+ public boolean updateAdnRecordsInEfByIndexForSubscriber(int subId, int efid,
+ ContentValues values, int index, String pin2) {
+ final String newTag = values.getAsString(IccProvider.STR_NEW_TAG);
+ final String newPhoneNumber = values.getAsString(IccProvider.STR_NEW_NUMBER);
+ return updateAdnRecordsInEfByIndexForSubscriber(subId, efid, newTag, newPhoneNumber,
+ index, pin2);
+
+ }
+
+ private boolean updateAdnRecordsInEfByIndexForSubscriber(int subId, int efid, String newTag,
+ String newPhoneNumber, int index, String pin2) {
+ AdnRecord[] records = mRecords.computeIfAbsent(Pair.create(subId, efid), unused ->
+ createEmptyRecords(efid, 100));
+ records[index - 1] = new AdnRecord(efid, index, newTag, newPhoneNumber);
+ return true;
+ }
+
+ @Override
+ public int[] getAdnRecordsSize(int efid) {
+ return getAdnRecordsSizeForSubscriber(mDefaultSubscriptionId, efid);
+ }
+
+ @Override
+ public int[] getAdnRecordsSizeForSubscriber(int subId, int efid) {
+ Pair<Integer, Integer> key = Pair.create(subId, efid);
+ Integer recordSize = mRecordSizes.get(key);
+ if (recordSize == null) {
+ return new int[]{0, 0, 0};
+ }
+ int count = mRecords.get(key).length;
+ return new int[]{recordSize, recordSize * count, count};
+ }
+
+ @Override
+ public AdnCapacity getAdnRecordsCapacityForSubscriber(int subId) {
+ return new AdnCapacity(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ }
+ }
+
+ /**
+ * Implementation of SimPhonebookProvider that allows test-doubles to be injected.
+ *
+ * <p>The ProviderTestRule doesn't seem to allow a better way to do this since it just
+ * invokes the constructor.
+ */
+ public static class TestableSimPhonebookProvider extends SimPhonebookProvider {
+
+ public static void setup(
+ ContentResolver resolver,
+ SubscriptionManager subscriptionManager,
+ IIccPhoneBook iccPhoneBook) {
+ setup(resolver, subscriptionManager, iccPhoneBook, uri -> {
+ });
+ }
+
+ public static void setup(
+ ContentResolver resolver,
+ SubscriptionManager subscriptionManager,
+ IIccPhoneBook iccPhoneBook,
+ ContentNotifier notifier) {
+ TestableSimPhonebookProvider provider =
+ (TestableSimPhonebookProvider) Objects.requireNonNull(
+ resolver.acquireContentProviderClient(
+ SimPhonebookContract.AUTHORITY))
+ .getLocalContentProvider();
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() ->
+ provider.onCreate(subscriptionManager, () -> iccPhoneBook, notifier));
+ }
+
+ @Override
+ public boolean onCreate() {
+ // We stub super.onCreate because it initializes services which causes an
+ // IllegalArgumentException because of the context used for the test.
+ return true;
+ }
+ }
+}
diff --git a/tests/src/com/android/phone/callcomposer/CallComposerAuthTest.java b/tests/src/com/android/phone/callcomposer/CallComposerAuthTest.java
new file mode 100644
index 0000000..b503790
--- /dev/null
+++ b/tests/src/com/android/phone/callcomposer/CallComposerAuthTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.phone.callcomposer;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.phone.callcomposer.DigestAuthUtils;
+
+import org.junit.Test;
+
+public class CallComposerAuthTest {
+ @Test
+ public void testResponseGeneration() {
+ String username = "test1";
+ String realm = "test@test.com";
+ byte[] password = "12345678".getBytes();
+ String sNonce = "aaaabbbbcccc";
+ String cNonce = "ccccbbbbaaaa";
+ String ncValue = "00000001";
+ String method = "POST";
+ String uri = "/test/test1?a=b";
+ String qop = "auth";
+
+ String response = DigestAuthUtils.computeResponse(sNonce, cNonce, qop, username,
+ realm, password, method, uri);
+ // precomputed response value from a known-good implementation
+ assertEquals("744d63d6fb11aa132dc906ec95306960", response);
+ }
+}
diff --git a/tests/src/com/android/phone/callcomposer/PictureManagerTest.java b/tests/src/com/android/phone/callcomposer/PictureManagerTest.java
new file mode 100644
index 0000000..f1ce3b8
--- /dev/null
+++ b/tests/src/com/android/phone/callcomposer/PictureManagerTest.java
@@ -0,0 +1,324 @@
+/*
+ * 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.phone.callcomposer;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.OutcomeReceiver;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.provider.CallLog;
+import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
+import android.telephony.gba.TlsParams;
+import android.telephony.gba.UaSecurityProtocolIdentifier;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.InputStream;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class PictureManagerTest {
+ private static final String FAKE_URL_BASE = "https://www.example.com";
+ private static final String FAKE_URL = "https://www.example.com/AAAAA";
+ private static final long TIMEOUT_MILLIS = 1000;
+ private static final Uri FAKE_CALLLOG_URI = Uri.parse("content://asdf");
+
+ @Mock CallComposerPictureManager.CallLogProxy mockCallLogProxy;
+ @Mock CallComposerPictureTransfer mockPictureTransfer;
+ @Mock Context context;
+ @Mock TelephonyManager telephonyManager;
+
+ private boolean originalTestMode = false;
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ originalTestMode = CallComposerPictureManager.sTestMode;
+ // Even though this is a test, we want test mode off so we can actually exercise the logic
+ // in the class.
+ CallComposerPictureManager.sTestMode = false;
+ when(context.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(telephonyManager);
+ when(context.getSystemServiceName(TelephonyManager.class))
+ .thenReturn(Context.TELEPHONY_SERVICE);
+ when(telephonyManager.createForSubscriptionId(anyInt())).thenReturn(telephonyManager);
+ PersistableBundle b = new PersistableBundle();
+ b.putString(CarrierConfigManager.KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING,
+ FAKE_URL_BASE);
+ b.putInt(CarrierConfigManager.KEY_GBA_MODE_INT,
+ CarrierConfigManager.GBA_ME);
+ b.putInt(CarrierConfigManager.KEY_GBA_UA_SECURITY_ORGANIZATION_INT,
+ UaSecurityProtocolIdentifier.ORG_3GPP);
+ b.putInt(CarrierConfigManager.KEY_GBA_UA_SECURITY_PROTOCOL_INT,
+ UaSecurityProtocolIdentifier.UA_SECURITY_PROTOCOL_3GPP_TLS_DEFAULT);
+ b.putInt(CarrierConfigManager.KEY_GBA_UA_TLS_CIPHER_SUITE_INT,
+ TlsParams.TLS_RSA_WITH_AES_128_CBC_SHA);
+ when(telephonyManager.getCarrierConfig()).thenReturn(b);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ CallComposerPictureManager.sTestMode = originalTestMode;
+ CallComposerPictureManager.clearInstances();
+ }
+
+ @Test
+ public void testPictureUpload() throws Exception {
+ CallComposerPictureManager manager = CallComposerPictureManager.getInstance(context, 0);
+ manager.setCallLogProxy(mockCallLogProxy);
+ ImageData imageData = new ImageData(new byte[] {1,2,3,4},
+ "image/png", null);
+
+ CompletableFuture<UUID> uploadedUuidFuture = new CompletableFuture<>();
+ manager.handleUploadToServer(new CallComposerPictureTransfer.Factory() {
+ @Override
+ public CallComposerPictureTransfer create(Context context, int subscriptionId,
+ String url, ExecutorService executorService) {
+ return mockPictureTransfer;
+ }
+ }, imageData, (pair) -> uploadedUuidFuture.complete(pair.first));
+
+ // Get the callback for later manipulation
+ ArgumentCaptor<CallComposerPictureTransfer.PictureCallback> callbackCaptor =
+ ArgumentCaptor.forClass(CallComposerPictureTransfer.PictureCallback.class);
+ verify(mockPictureTransfer).setCallback(callbackCaptor.capture());
+
+ // Make sure the upload method is called
+ ArgumentCaptor<GbaCredentialsSupplier> credSupplierCaptor =
+ ArgumentCaptor.forClass(GbaCredentialsSupplier.class);
+ ArgumentCaptor<ImageData> imageDataCaptor =
+ ArgumentCaptor.forClass(ImageData.class);
+ verify(mockPictureTransfer).uploadPicture(imageDataCaptor.capture(),
+ credSupplierCaptor.capture());
+
+ // Make sure the id field on the image data got filled in
+ ImageData sentData = imageDataCaptor.getValue();
+ assertArrayEquals(imageData.getImageBytes(), sentData.getImageBytes());
+ assertNotNull(sentData.getId());
+ String imageId = sentData.getId();
+
+ testGbaCredLookup(credSupplierCaptor.getValue(), false);
+
+ // Trigger upload success, make sure that the internal state is consistent after the upload.
+ callbackCaptor.getValue().onUploadSuccessful(FAKE_URL);
+ UUID id = uploadedUuidFuture.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ assertEquals(imageId, id.toString());
+ assertEquals(FAKE_URL, manager.getServerUrlForImageId(id));
+
+ // Test the call log upload
+ CompletableFuture<Uri> callLogUriFuture = new CompletableFuture<>();
+ manager.storeUploadedPictureToCallLog(id, callLogUriFuture::complete);
+
+ ArgumentCaptor<OutcomeReceiver<Uri, CallLog.CallComposerLoggingException>>
+ callLogCallbackCaptor = ArgumentCaptor.forClass(OutcomeReceiver.class);
+
+ verify(mockCallLogProxy).storeCallComposerPictureAsUser(nullable(Context.class),
+ nullable(UserHandle.class), nullable(InputStream.class), nullable(Executor.class),
+ callLogCallbackCaptor.capture());
+ callLogCallbackCaptor.getValue().onResult(FAKE_CALLLOG_URI);
+ Uri receivedUri = callLogUriFuture.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ assertEquals(FAKE_CALLLOG_URI, receivedUri);
+ }
+
+ @Test
+ public void testPictureUploadWithAuthRefresh() throws Exception {
+ CallComposerPictureManager manager = CallComposerPictureManager.getInstance(context, 0);
+ manager.setCallLogProxy(mockCallLogProxy);
+ ImageData imageData = new ImageData(new byte[] {1,2,3,4},
+ "image/png", null);
+
+ CompletableFuture<UUID> uploadedUuidFuture = new CompletableFuture<>();
+ manager.handleUploadToServer(new CallComposerPictureTransfer.Factory() {
+ @Override
+ public CallComposerPictureTransfer create(Context context, int subscriptionId,
+ String url, ExecutorService executorService) {
+ return mockPictureTransfer;
+ }
+ }, imageData, (pair) -> uploadedUuidFuture.complete(pair.first));
+
+ // Get the callback for later manipulation
+ ArgumentCaptor<CallComposerPictureTransfer.PictureCallback> callbackCaptor =
+ ArgumentCaptor.forClass(CallComposerPictureTransfer.PictureCallback.class);
+ verify(mockPictureTransfer).setCallback(callbackCaptor.capture());
+
+ // Make sure the upload method is called
+ verify(mockPictureTransfer).uploadPicture(nullable(ImageData.class),
+ nullable(GbaCredentialsSupplier.class));
+
+ // Simulate a auth-needed retry request
+ callbackCaptor.getValue().onRetryNeeded(true, 0);
+ waitForExecutorAction(CallComposerPictureManager.getExecutor(), TIMEOUT_MILLIS);
+
+ // Make sure upload gets called again immediately, and make sure that the new GBA creds
+ // are requested with a force-refresh.
+ ArgumentCaptor<GbaCredentialsSupplier> credSupplierCaptor =
+ ArgumentCaptor.forClass(GbaCredentialsSupplier.class);
+ verify(mockPictureTransfer, times(2)).uploadPicture(nullable(ImageData.class),
+ credSupplierCaptor.capture());
+
+ testGbaCredLookup(credSupplierCaptor.getValue(), true);
+ }
+
+ @Test
+ public void testPictureDownload() throws Exception {
+ ImageData imageData = new ImageData(new byte[] {1,2,3,4},
+ "image/png", null);
+ CallComposerPictureManager manager = CallComposerPictureManager.getInstance(context, 0);
+ manager.setCallLogProxy(mockCallLogProxy);
+
+ CompletableFuture<Uri> callLogUriFuture = new CompletableFuture<>();
+ manager.handleDownloadFromServer(new CallComposerPictureTransfer.Factory() {
+ @Override
+ public CallComposerPictureTransfer create(Context context, int subscriptionId,
+ String url, ExecutorService executorService) {
+ return mockPictureTransfer;
+ }
+ }, FAKE_URL, (p) -> callLogUriFuture.complete(p.first));
+
+ // Get the callback for later manipulation
+ ArgumentCaptor<CallComposerPictureTransfer.PictureCallback> callbackCaptor =
+ ArgumentCaptor.forClass(CallComposerPictureTransfer.PictureCallback.class);
+ verify(mockPictureTransfer).setCallback(callbackCaptor.capture());
+
+ // Make sure the download method is called
+ ArgumentCaptor<GbaCredentialsSupplier> credSupplierCaptor =
+ ArgumentCaptor.forClass(GbaCredentialsSupplier.class);
+ verify(mockPictureTransfer).downloadPicture(credSupplierCaptor.capture());
+
+ testGbaCredLookup(credSupplierCaptor.getValue(), false);
+
+ // Trigger download success, make sure that the call log is called into next.
+ callbackCaptor.getValue().onDownloadSuccessful(imageData);
+ ArgumentCaptor<OutcomeReceiver<Uri, CallLog.CallComposerLoggingException>>
+ callLogCallbackCaptor = ArgumentCaptor.forClass(OutcomeReceiver.class);
+ verify(mockCallLogProxy).storeCallComposerPictureAsUser(nullable(Context.class),
+ nullable(UserHandle.class), nullable(InputStream.class), nullable(Executor.class),
+ callLogCallbackCaptor.capture());
+
+ callLogCallbackCaptor.getValue().onResult(FAKE_CALLLOG_URI);
+ Uri receivedUri = callLogUriFuture.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ assertEquals(FAKE_CALLLOG_URI, receivedUri);
+ }
+
+ @Test
+ public void testPictureDownloadWithAuthRefresh() throws Exception {
+ CallComposerPictureManager manager = CallComposerPictureManager.getInstance(context, 0);
+ manager.setCallLogProxy(mockCallLogProxy);
+
+ CompletableFuture<Uri> callLogUriFuture = new CompletableFuture<>();
+ manager.handleDownloadFromServer(new CallComposerPictureTransfer.Factory() {
+ @Override
+ public CallComposerPictureTransfer create(Context context, int subscriptionId,
+ String url, ExecutorService executorService) {
+ return mockPictureTransfer;
+ }
+ }, FAKE_URL, (p) -> callLogUriFuture.complete(p.first));
+
+ // Get the callback for later manipulation
+ ArgumentCaptor<CallComposerPictureTransfer.PictureCallback> callbackCaptor =
+ ArgumentCaptor.forClass(CallComposerPictureTransfer.PictureCallback.class);
+ verify(mockPictureTransfer).setCallback(callbackCaptor.capture());
+
+ // Make sure the download method is called
+ verify(mockPictureTransfer).downloadPicture(nullable(GbaCredentialsSupplier.class));
+
+ // Simulate a auth-needed retry request
+ callbackCaptor.getValue().onRetryNeeded(true, 0);
+ waitForExecutorAction(CallComposerPictureManager.getExecutor(), TIMEOUT_MILLIS);
+
+ // Make sure download gets called again immediately, and make sure that the new GBA creds
+ // are requested with a force-refresh.
+ ArgumentCaptor<GbaCredentialsSupplier> credSupplierCaptor =
+ ArgumentCaptor.forClass(GbaCredentialsSupplier.class);
+ verify(mockPictureTransfer, times(2)).downloadPicture(credSupplierCaptor.capture());
+
+ testGbaCredLookup(credSupplierCaptor.getValue(), true);
+ }
+
+
+ public void testGbaCredLookup(GbaCredentialsSupplier supplier, boolean forceExpected)
+ throws Exception {
+ String fakeNafId = "https://3GPP-bootstrapping@www.example.com";
+ byte[] fakeKey = new byte[] {1, 2, 3, 4, 5};
+ String fakeTxId = "89sdfjggf";
+
+ ArgumentCaptor<TelephonyManager.BootstrapAuthenticationCallback> authCallbackCaptor =
+ ArgumentCaptor.forClass(TelephonyManager.BootstrapAuthenticationCallback.class);
+
+ CompletableFuture<GbaCredentials> credsFuture =
+ supplier.getCredentials(fakeNafId, CallComposerPictureManager.getExecutor());
+ verify(telephonyManager).bootstrapAuthenticationRequest(anyInt(),
+ eq(Uri.parse(fakeNafId)),
+ nullable(UaSecurityProtocolIdentifier.class), eq(forceExpected),
+ nullable(Executor.class),
+ authCallbackCaptor.capture());
+ authCallbackCaptor.getValue().onKeysAvailable(fakeKey, fakeTxId);
+ GbaCredentials creds = credsFuture.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ assertEquals(fakeTxId, creds.getTransactionId());
+ assertArrayEquals(fakeKey, creds.getKey());
+
+
+ // Do it again and see if we make another request, then make sure that matches up with what
+ // we expected.
+ CompletableFuture<GbaCredentials> credsFuture1 =
+ supplier.getCredentials(fakeNafId, CallComposerPictureManager.getExecutor());
+ verify(telephonyManager, times(forceExpected ? 2 : 1))
+ .bootstrapAuthenticationRequest(anyInt(), eq(Uri.parse(fakeNafId)),
+ nullable(UaSecurityProtocolIdentifier.class),
+ eq(forceExpected),
+ nullable(Executor.class),
+ authCallbackCaptor.capture());
+ authCallbackCaptor.getValue().onKeysAvailable(fakeKey, fakeTxId);
+ GbaCredentials creds1 = credsFuture1.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ assertEquals(fakeTxId, creds1.getTransactionId());
+ assertArrayEquals(fakeKey, creds1.getKey());
+ }
+
+ private static boolean waitForExecutorAction(
+ ExecutorService executorService, long timeoutMillis) {
+ CompletableFuture<Void> f = new CompletableFuture<>();
+ executorService.execute(() -> f.complete(null));
+ try {
+ f.get(timeoutMillis, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ return false;
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ return true;
+ }
+}
diff --git a/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java b/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java
index edfc34f..33c8b8a 100644
--- a/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java
+++ b/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java
@@ -27,8 +27,6 @@
import android.os.Looper;
import android.test.suitebuilder.annotation.SmallTest;
-import com.android.internal.telephony.PhoneConstants;
-
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
@@ -112,12 +110,10 @@
@Test
@SmallTest
public void testMergeMultiPartyCalls() {
- when(mTestTelephonyConnectionA.mMockRadioConnection.getPhoneType())
- .thenReturn(PhoneConstants.PHONE_TYPE_IMS);
- when(mTestTelephonyConnectionB.mMockRadioConnection.getPhoneType())
- .thenReturn(PhoneConstants.PHONE_TYPE_IMS);
- when(mTestTelephonyConnectionA.mMockRadioConnection.isMultiparty()).thenReturn(true);
- when(mTestTelephonyConnectionB.mMockRadioConnection.isMultiparty()).thenReturn(true);
+ mTestTelephonyConnectionA.setIsImsConnection(true);
+ mTestTelephonyConnectionB.setIsImsConnection(true);
+ when(mTestTelephonyConnectionA.mImsPhoneConnection.isMultiparty()).thenReturn(true);
+ when(mTestTelephonyConnectionB.mImsPhoneConnection.isMultiparty()).thenReturn(true);
mControllerTest.add(mTestTelephonyConnectionB);
mControllerTest.add(mTestTelephonyConnectionA);
diff --git a/tests/src/com/android/services/telephony/ImsConferenceTest.java b/tests/src/com/android/services/telephony/ImsConferenceTest.java
index 7e6488d..3bc5ee8 100644
--- a/tests/src/com/android/services/telephony/ImsConferenceTest.java
+++ b/tests/src/com/android/services/telephony/ImsConferenceTest.java
@@ -577,6 +577,51 @@
}
/**
+ * Similar to {@link #testLocalDisconnectOnEmptyConference()}, except tests the case where the
+ * conference first drops to a single participant, triggering single party conference emulation.
+ * Ensure that we will still recognize this and disconnect the conference.
+ * @throws Exception
+ */
+ @Test
+ @SmallTest
+ public void testLocalDisconnectOnEmptySinglePartyConference() throws Exception {
+ when(mMockTelecomAccountRegistry.isUsingSimCallManager(any(PhoneAccountHandle.class)))
+ .thenReturn(false);
+
+ ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
+ mMockTelephonyConnectionServiceProxy, mConferenceHost,
+ null /* phoneAccountHandle */, () -> false /* featureFlagProxy */,
+ new ImsConference.CarrierConfiguration.Builder()
+ .setShouldLocalDisconnectEmptyConference(true)
+ .build());
+
+ ConferenceParticipant participant1 = new ConferenceParticipant(
+ Uri.parse("tel:6505551212"),
+ "A",
+ Uri.parse("sip:6505551212@testims.com"),
+ Connection.STATE_ACTIVE,
+ Call.Details.DIRECTION_INCOMING);
+ ConferenceParticipant participant2 = new ConferenceParticipant(
+ Uri.parse("tel:6505551213"),
+ "A",
+ Uri.parse("sip:6505551213@testims.com"),
+ Connection.STATE_ACTIVE,
+ Call.Details.DIRECTION_INCOMING);
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1, participant2));
+ assertEquals(2, imsConference.getNumberOfParticipants());
+
+ // Drop to 1 participant which enters single party mode.
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1));
+
+ // Drop to 0 participants; should have a hangup request.
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost, Collections.emptyList());
+ assertEquals(0, imsConference.getNumberOfParticipants());
+ verify(mConferenceHost.mMockCall).hangup();
+ }
+
+ /**
* Verifies that an ImsConference can handle SIP and TEL URIs for both the P-Associated-Uri and
* conference event package identities.
*/
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
index 2060e6f..4ce94ca 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
@@ -26,10 +26,10 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -42,8 +42,6 @@
import android.os.AsyncResult;
import android.os.Bundle;
import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
import android.telecom.ConnectionRequest;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccountHandle;
@@ -58,6 +56,8 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.TelephonyTestBase;
+import com.android.internal.telecom.IConnectionService;
+import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.Phone;
@@ -84,6 +84,23 @@
@RunWith(AndroidJUnit4.class)
public class TelephonyConnectionServiceTest extends TelephonyTestBase {
+ /**
+ * Unlike {@link TestTelephonyConnection}, a bare minimal {@link TelephonyConnection} impl
+ * that does not try to configure anything.
+ */
+ public static class SimpleTelephonyConnection extends TelephonyConnection {
+ public boolean wasDisconnected = false;
+
+ @Override
+ public TelephonyConnection cloneConnection() {
+ return null;
+ }
+
+ @Override
+ public void hangup(int telephonyDisconnectCode) {
+ wasDisconnected = true;
+ }
+ }
private static final long TIMEOUT_MS = 100;
private static final int SLOT_0_PHONE_ID = 0;
@@ -107,13 +124,18 @@
@Mock TelephonyConnectionService.PhoneSwitcherProxy mPhoneSwitcherProxy;
@Mock TelephonyConnectionService.PhoneNumberUtilsProxy mPhoneNumberUtilsProxy;
@Mock TelephonyConnectionService.PhoneUtilsProxy mPhoneUtilsProxy;
- @Mock TelephonyConnectionService.HandlerFactory mHandlerFactory;
@Mock TelephonyConnectionService.DisconnectCauseFactory mDisconnectCauseFactory;
@Mock Handler mMockHandler;
@Mock EmergencyNumberTracker mEmergencyNumberTracker;
@Mock PhoneSwitcher mPhoneSwitcher;
@Mock RadioOnHelper mRadioOnHelper;
@Mock ServiceStateTracker mSST;
+ @Mock Call mCall;
+ @Mock Call mCall2;
+ @Mock com.android.internal.telephony.Connection mInternalConnection;
+ @Mock com.android.internal.telephony.Connection mInternalConnection2;
+ private Phone mPhone0;
+ private Phone mPhone1;
private static class TestTelephonyConnectionService extends TelephonyConnectionService {
@@ -132,6 +154,7 @@
}
private TelephonyConnectionService mTestConnectionService;
+ private IConnectionService.Stub mBinderStub;
@Before
public void setUp() throws Exception {
@@ -147,11 +170,6 @@
.thenAnswer(invocation -> invocation.getArgument(1));
mTestConnectionService.setPhoneNumberUtilsProxy(mPhoneNumberUtilsProxy);
mTestConnectionService.setPhoneUtilsProxy(mPhoneUtilsProxy);
- HandlerThread mockHandlerThread = mock(HandlerThread.class);
- doReturn(mockHandlerThread).when(mHandlerFactory).createHandlerThread(anyString());
- doReturn(null).when(mockHandlerThread).getLooper();
- doReturn(mMockHandler).when(mHandlerFactory).createHandler(any());
- mTestConnectionService.setHandlerFactory(mHandlerFactory);
mTestConnectionService.setDeviceState(mDeviceState);
mTestConnectionService.setRadioOnHelper(mRadioOnHelper);
doReturn(new DisconnectCause(DisconnectCause.UNKNOWN)).when(mDisconnectCauseFactory)
@@ -161,6 +179,7 @@
mTestConnectionService.setDisconnectCauseFactory(mDisconnectCauseFactory);
mTestConnectionService.onCreate();
mTestConnectionService.setTelephonyManagerProxy(mTelephonyManagerProxy);
+ mBinderStub = (IConnectionService.Stub) mTestConnectionService.onBind(null);
}
@After
@@ -648,7 +667,7 @@
// was redialed on the same slot
assertEquals(0, c.getNotifyPhoneAccountChangedCount());
try {
- verify(slot0Phone).dial(anyString(), any());
+ verify(slot0Phone).dial(anyString(), any(), any());
} catch (CallStateException e) {
// This shouldn't happen
fail();
@@ -679,7 +698,7 @@
// was never redialed
assertEquals(0, c.getNotifyPhoneAccountChangedCount());
try {
- verify(slot0Phone, never()).dial(anyString(), any());
+ verify(slot0Phone, never()).dial(anyString(), any(), any());
} catch (CallStateException e) {
// This shouldn't happen
fail();
@@ -722,7 +741,7 @@
// redialed on another slot
assertEquals(1, c.getNotifyPhoneAccountChangedCount());
try {
- verify(slot1Phone).dial(anyString(), any());
+ verify(slot1Phone).dial(anyString(), any(), any());
} catch (CallStateException e) {
// This shouldn't happen
fail();
@@ -763,7 +782,7 @@
// redialed on another slot
assertEquals(1, c.getNotifyPhoneAccountChangedCount());
try {
- verify(slot1Phone).dial(anyString(), any());
+ verify(slot1Phone).dial(anyString(), any(), any());
} catch (CallStateException e) {
// This shouldn't happen
fail();
@@ -817,8 +836,8 @@
// redialed on another slot
assertEquals(2, c.getNotifyPhoneAccountChangedCount());
try {
- verify(slot0Phone).dial(anyString(), any());
- verify(slot1Phone).dial(anyString(), any());
+ verify(slot0Phone).dial(anyString(), any(), any());
+ verify(slot1Phone).dial(anyString(), any(), any());
} catch (CallStateException e) {
// This shouldn't happen
fail();
@@ -870,8 +889,8 @@
// redialed on another slot
assertEquals(1, c.getNotifyPhoneAccountChangedCount());
try {
- verify(slot1Phone).dial(anyString(), any());
- verify(slot0Phone, never()).dial(anyString(), any());
+ verify(slot1Phone).dial(anyString(), any(), any());
+ verify(slot0Phone, never()).dial(anyString(), any(), any());
} catch (CallStateException e) {
// This shouldn't happen
fail();
@@ -931,22 +950,20 @@
@Test
@SmallTest
public void testCreateOutgoingEmergencyConnection_delayDial_carrierconfig_dds() {
- Phone testPhone = setupConnectionServiceForDelayDial();
- Runnable delayDialRunnable = verifyRunnablePosted();
-
// Setup test to not support SUPL on the non-DDS subscription
doReturn(true).when(mDeviceState).isSuplDdsSwitchRequiredForEmergencyCall(any());
- getTestContext().getCarrierConfig().putStringArray(
+ getTestContext().getCarrierConfig(0 /*subId*/).putStringArray(
CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
null);
- testPhone.getServiceState().setRoaming(false);
- getTestContext().getCarrierConfig().putInt(
+ getTestContext().getCarrierConfig(0 /*subId*/).putInt(
CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY);
- getTestContext().getCarrierConfig().putString(
+ getTestContext().getCarrierConfig(0 /*subId*/).putString(
CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "150");
- delayDialRunnable.run();
+ Phone testPhone = setupConnectionServiceForDelayDial(
+ false /* isRoaming */, false /* setOperatorName */, null /* operator long name*/,
+ null /* operator short name */, null /* operator numeric name */);
verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(0) /*phoneId*/ ,
eq(150) /*extensionTime*/, any());
}
@@ -964,7 +981,7 @@
ArgumentCaptor<RadioOnStateListener.Callback> callback =
ArgumentCaptor.forClass(RadioOnStateListener.Callback.class);
verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true),
- eq(testPhone));
+ eq(testPhone), eq(false));
assertFalse(callback.getValue().isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE));
when(mSST.isRadioOn()).thenReturn(true);
@@ -990,19 +1007,17 @@
ArgumentCaptor<RadioOnStateListener.Callback> callback =
ArgumentCaptor.forClass(RadioOnStateListener.Callback.class);
verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true),
- eq(testPhone));
+ eq(testPhone), eq(false));
assertFalse(callback.getValue().isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE));
when(mSST.isRadioOn()).thenReturn(true);
assertTrue(callback.getValue().isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE));
callback.getValue().onComplete(null, true);
- Runnable delayDialRunnable = verifyRunnablePosted();
try {
doAnswer(invocation -> null).when(mContext).startActivity(any());
- delayDialRunnable.run();
- verify(testPhone).dial(anyString(), any());
+ verify(testPhone).dial(anyString(), any(), any());
} catch (CallStateException e) {
// This shouldn't happen
fail();
@@ -1016,22 +1031,20 @@
@Test
@SmallTest
public void testCreateOutgoingEmergencyConnection_delayDial_nocarrierconfig() {
- Phone testPhone = setupConnectionServiceForDelayDial();
- Runnable delayDialRunnable = verifyRunnablePosted();
-
// Setup test to not support SUPL on the non-DDS subscription
doReturn(true).when(mDeviceState).isSuplDdsSwitchRequiredForEmergencyCall(any());
- getTestContext().getCarrierConfig().putStringArray(
+ getTestContext().getCarrierConfig(0 /*subId*/).putStringArray(
CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
null);
- testPhone.getServiceState().setRoaming(false);
- getTestContext().getCarrierConfig().putInt(
+ getTestContext().getCarrierConfig(0 /*subId*/).putInt(
CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK);
- getTestContext().getCarrierConfig().putString(
+ getTestContext().getCarrierConfig(0 /*subId*/).putString(
CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0");
- delayDialRunnable.run();
+ Phone testPhone = setupConnectionServiceForDelayDial(
+ false /* isRoaming */, false /* setOperatorName */, null /* operator long name*/,
+ null /* operator short name */, null /* operator numeric name */);
verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any());
}
@@ -1042,22 +1055,20 @@
@Test
@SmallTest
public void testCreateOutgoingEmergencyConnection_delayDial_supportsuplondds() {
- Phone testPhone = setupConnectionServiceForDelayDial();
- Runnable delayDialRunnable = verifyRunnablePosted();
-
// If the non-DDS supports SUPL, dont switch data
doReturn(false).when(mDeviceState).isSuplDdsSwitchRequiredForEmergencyCall(any());
- getTestContext().getCarrierConfig().putStringArray(
+ getTestContext().getCarrierConfig(0 /*subId*/).putStringArray(
CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
null);
- testPhone.getServiceState().setRoaming(false);
- getTestContext().getCarrierConfig().putInt(
+ getTestContext().getCarrierConfig(0 /*subId*/).putInt(
CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY);
- getTestContext().getCarrierConfig().putString(
+ getTestContext().getCarrierConfig(0 /*subId*/).putString(
CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0");
- delayDialRunnable.run();
+ Phone testPhone = setupConnectionServiceForDelayDial(
+ false /* isRoaming */, false /* setOperatorName */, null /* operator long name*/,
+ null /* operator short name */, null /* operator numeric name */);
verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any());
}
@@ -1068,22 +1079,20 @@
@Test
@SmallTest
public void testCreateOutgoingEmergencyConnection_delayDial_roaming_nocarrierconfig() {
- Phone testPhone = setupConnectionServiceForDelayDial();
- Runnable delayDialRunnable = verifyRunnablePosted();
-
// Setup test to not support SUPL on the non-DDS subscription
doReturn(true).when(mDeviceState).isSuplDdsSwitchRequiredForEmergencyCall(any());
- getTestContext().getCarrierConfig().putStringArray(
+ getTestContext().getCarrierConfig(0 /*subId*/).putStringArray(
CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
null);
- testPhone.getServiceState().setRoaming(true);
- getTestContext().getCarrierConfig().putInt(
+ getTestContext().getCarrierConfig(0 /*subId*/).putInt(
CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY);
- getTestContext().getCarrierConfig().putString(
+ getTestContext().getCarrierConfig(0 /*subId*/).putString(
CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0");
- delayDialRunnable.run();
+ Phone testPhone = setupConnectionServiceForDelayDial(
+ true /* isRoaming */, false /* setOperatorName */, null /* operator long name*/,
+ null /* operator short name */, null /* operator numeric name */);
verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any());
}
@@ -1095,28 +1104,25 @@
@Test
@SmallTest
public void testCreateOutgoingEmergencyConnection_delayDial_roamingcarrierconfig() {
- Phone testPhone = setupConnectionServiceForDelayDial();
- Runnable delayDialRunnable = verifyRunnablePosted();
-
+ doReturn(true).when(mDeviceState).isSuplDdsSwitchRequiredForEmergencyCall(any());
// Setup voice roaming scenario
String testRoamingOperator = "001001";
- // In some roaming conditions, we are not technically "roaming"
- testPhone.getServiceState().setRoaming(false);
- testPhone.getServiceState().setOperatorName("TestTel", "TestTel", testRoamingOperator);
// Setup test to not support SUPL on the non-DDS subscription
- doReturn(true).when(mDeviceState).isSuplDdsSwitchRequiredForEmergencyCall(any());
String[] roamingPlmns = new String[1];
roamingPlmns[0] = testRoamingOperator;
- getTestContext().getCarrierConfig().putStringArray(
+ getTestContext().getCarrierConfig(0 /*subId*/).putStringArray(
CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
roamingPlmns);
- getTestContext().getCarrierConfig().putInt(
+ getTestContext().getCarrierConfig(0 /*subId*/).putInt(
CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK);
- getTestContext().getCarrierConfig().putString(
+ getTestContext().getCarrierConfig(0 /*subId*/).putString(
CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0");
- delayDialRunnable.run();
+ Phone testPhone = setupConnectionServiceForDelayDial(
+ false /* isRoaming */, true /* setOperatorName */,
+ "TestTel" /* operator long name*/, "TestTel" /* operator short name */,
+ testRoamingOperator /* operator numeric name */);
verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(0) /*phoneId*/ ,
eq(0) /*extensionTime*/, any());
}
@@ -1129,36 +1135,239 @@
@Test
@SmallTest
public void testCreateOutgoingEmergencyConnection_delayDial__roaming_roamingcarrierconfig() {
- Phone testPhone = setupConnectionServiceForDelayDial();
- Runnable delayDialRunnable = verifyRunnablePosted();
-
- // Setup voice roaming scenario
- String testRoamingOperator = "001001";
- testPhone.getServiceState().setRoaming(true);
- testPhone.getServiceState().setOperatorName("TestTel", "TestTel", testRoamingOperator);
// Setup test to not support SUPL on the non-DDS subscription
doReturn(true).when(mDeviceState).isSuplDdsSwitchRequiredForEmergencyCall(any());
+ // Setup voice roaming scenario
+ String testRoamingOperator = "001001";
String[] roamingPlmns = new String[1];
roamingPlmns[0] = testRoamingOperator;
- getTestContext().getCarrierConfig().putStringArray(
+ getTestContext().getCarrierConfig(0 /*subId*/).putStringArray(
CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
roamingPlmns);
- getTestContext().getCarrierConfig().putInt(
+ getTestContext().getCarrierConfig(0 /*subId*/).putInt(
CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK);
- getTestContext().getCarrierConfig().putString(
+ getTestContext().getCarrierConfig(0 /*subId*/).putString(
CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0");
- delayDialRunnable.run();
+ Phone testPhone = setupConnectionServiceForDelayDial(
+ false /* isRoaming */, true /* setOperatorName */,
+ "TestTel" /* operator long name*/, "TestTel" /* operator short name */,
+ testRoamingOperator /* operator numeric name */);
verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(0) /*phoneId*/ ,
eq(0) /*extensionTime*/, any());
}
/**
+ * Verifies for an incoming call on the same SIM that we don't set
+ * {@link android.telecom.Connection#EXTRA_ANSWERING_DROPS_FG_CALL} on the incoming call extras.
+ * @throws Exception
+ */
+ @Test
+ @SmallTest
+ public void testIncomingDoesntRequestDisconnect() throws Exception {
+ setupForCallTest();
+
+ mBinderStub.createConnection(PHONE_ACCOUNT_HANDLE_1, "TC@1",
+ new ConnectionRequest(PHONE_ACCOUNT_HANDLE_1, Uri.parse("tel:16505551212"),
+ new Bundle()),
+ true, false, null);
+ waitForHandlerAction(mTestConnectionService.getHandler(), TIMEOUT_MS);
+ assertEquals(1, mTestConnectionService.getAllConnections().size());
+
+ // Make sure the extras do not indicate that it answering will disconnect another call.
+ android.telecom.Connection connection = (android.telecom.Connection)
+ mTestConnectionService.getAllConnections().toArray()[0];
+ assertFalse(connection.getExtras() != null && connection.getExtras().containsKey(
+ android.telecom.Connection.EXTRA_ANSWERING_DROPS_FG_CALL));
+ }
+
+ /**
+ * Verifies where there is another call on the same sub, we don't set
+ * {@link android.telecom.Connection#EXTRA_ANSWERING_DROPS_FG_CALL} on the incoming call extras.
+ * @throws Exception
+ */
+ @Test
+ @SmallTest
+ public void testSecondCallSameSubWontDisconnect() throws Exception {
+ // Previous test gets us into a good enough state
+ testIncomingDoesntRequestDisconnect();
+
+ when(mCall.getState()).thenReturn(Call.State.ACTIVE);
+ when(mCall2.getState()).thenReturn(Call.State.WAITING);
+ when(mCall2.getLatestConnection()).thenReturn(mInternalConnection2);
+ when(mPhone0.getRingingCall()).thenReturn(mCall2);
+
+ mBinderStub.createConnection(PHONE_ACCOUNT_HANDLE_1, "TC@2",
+ new ConnectionRequest(PHONE_ACCOUNT_HANDLE_1, Uri.parse("tel:16505551213"),
+ new Bundle()),
+ true, false, null);
+ waitForHandlerAction(mTestConnectionService.getHandler(), TIMEOUT_MS);
+ assertEquals(2, mTestConnectionService.getAllConnections().size());
+
+ // None of the connections should have the extra set.
+ assertEquals(0, mTestConnectionService.getAllConnections().stream()
+ .filter(c -> c.getExtras() != null && c.getExtras().containsKey(
+ android.telecom.Connection.EXTRA_ANSWERING_DROPS_FG_CALL))
+ .count());
+ }
+
+ /**
+ * Verifies where there is another call on the same sub, we don't set
+ * {@link android.telecom.Connection#EXTRA_ANSWERING_DROPS_FG_CALL} on the incoming call extras.
+ * @throws Exception
+ */
+ @Test
+ @SmallTest
+ public void testSecondCallDifferentSubWillDisconnect() throws Exception {
+ // Previous test gets us into a good enough state
+ testIncomingDoesntRequestDisconnect();
+
+ when(mCall.getState()).thenReturn(Call.State.ACTIVE);
+ when(mCall2.getState()).thenReturn(Call.State.WAITING);
+ when(mCall2.getLatestConnection()).thenReturn(mInternalConnection2);
+ // At this point the call is ringing on the second phone.
+ when(mPhone0.getRingingCall()).thenReturn(null);
+ when(mPhone1.getRingingCall()).thenReturn(mCall2);
+
+ mBinderStub.createConnection(PHONE_ACCOUNT_HANDLE_2, "TC@2",
+ new ConnectionRequest(PHONE_ACCOUNT_HANDLE_2, Uri.parse("tel:16505551213"),
+ new Bundle()),
+ true, false, null);
+ waitForHandlerAction(mTestConnectionService.getHandler(), TIMEOUT_MS);
+ assertEquals(2, mTestConnectionService.getAllConnections().size());
+
+ // The incoming connection should have the extra set.
+ assertEquals(1, mTestConnectionService.getAllConnections().stream()
+ .filter(c -> c.getExtras() != null && c.getExtras().containsKey(
+ android.telecom.Connection.EXTRA_ANSWERING_DROPS_FG_CALL))
+ .count());
+ }
+
+ private static final PhoneAccountHandle SUB1_HANDLE = new PhoneAccountHandle(
+ new ComponentName("test", "class"), "1");
+ private static final PhoneAccountHandle SUB2_HANDLE = new PhoneAccountHandle(
+ new ComponentName("test", "class"), "2");
+
+ @Test
+ @SmallTest
+ public void testDontDisconnectSameSub() {
+ ArrayList<android.telecom.Connection> tcs = new ArrayList<>();
+ SimpleTelephonyConnection tc1 = createTestConnection(SUB1_HANDLE, 0, false);
+ tcs.add(tc1);
+ TelephonyConnectionService.maybeDisconnectCallsOnOtherSubs(tcs, SUB1_HANDLE);
+ // Would've preferred to use mockito, but can't mock out TelephonyConnection/Connection
+ // easily.
+ assertFalse(tc1.wasDisconnected);
+ }
+
+ @Test
+ @SmallTest
+ public void testDontDisconnectEmergency() {
+ ArrayList<android.telecom.Connection> tcs = new ArrayList<>();
+ SimpleTelephonyConnection tc1 = createTestConnection(SUB1_HANDLE, 0, true);
+ tcs.add(tc1);
+ TelephonyConnectionService.maybeDisconnectCallsOnOtherSubs(tcs, SUB2_HANDLE);
+ // Other call is an emergency call, so don't disconnect it.
+ assertFalse(tc1.wasDisconnected);
+ }
+
+ @Test
+ @SmallTest
+ public void testDontDisconnectExternal() {
+ ArrayList<android.telecom.Connection> tcs = new ArrayList<>();
+ SimpleTelephonyConnection tc1 = createTestConnection(SUB1_HANDLE,
+ android.telecom.Connection.PROPERTY_IS_EXTERNAL_CALL, false);
+ tcs.add(tc1);
+ TelephonyConnectionService.maybeDisconnectCallsOnOtherSubs(tcs, SUB2_HANDLE);
+ // Other call is an external call, so don't disconnect it.
+ assertFalse(tc1.wasDisconnected);
+ }
+
+ @Test
+ @SmallTest
+ public void testDisconnectDifferentSub() {
+ ArrayList<android.telecom.Connection> tcs = new ArrayList<>();
+ SimpleTelephonyConnection tc1 = createTestConnection(SUB1_HANDLE, 0, false);
+ tcs.add(tc1);
+ TelephonyConnectionService.maybeDisconnectCallsOnOtherSubs(tcs, SUB2_HANDLE);
+ assertTrue(tc1.wasDisconnected);
+ }
+
+ @Test
+ @SmallTest
+ public void testDisconnectDifferentSubTwoCalls() {
+ ArrayList<android.telecom.Connection> tcs = new ArrayList<>();
+ SimpleTelephonyConnection tc1 = createTestConnection(SUB1_HANDLE, 0, false);
+ SimpleTelephonyConnection tc2 = createTestConnection(SUB1_HANDLE, 0, false);
+
+ tcs.add(tc1);
+ tcs.add(tc2);
+ TelephonyConnectionService.maybeDisconnectCallsOnOtherSubs(tcs, SUB2_HANDLE);
+ assertTrue(tc1.wasDisconnected);
+ assertTrue(tc2.wasDisconnected);
+ }
+
+ private SimpleTelephonyConnection createTestConnection(PhoneAccountHandle handle,
+ int properties, boolean isEmergency) {
+ SimpleTelephonyConnection connection = new SimpleTelephonyConnection();
+ connection.setShouldTreatAsEmergencyCall(isEmergency);
+ connection.setConnectionProperties(properties);
+ connection.setPhoneAccountHandle(handle);
+ return connection;
+ }
+
+ /**
+ * Setup the mess of mocks for {@link #testSecondCallSameSubWontDisconnect()} and
+ * {@link #testIncomingDoesntRequestDisconnect()}.
+ */
+ private void setupForCallTest() {
+ // Setup a bunch of stuff. Blech.
+ mTestConnectionService.setReadyForTest();
+ mPhone0 = makeTestPhone(0 /*phoneId*/, ServiceState.STATE_IN_SERVICE,
+ false /*isEmergencyOnly*/);
+ when(mCall.getState()).thenReturn(Call.State.INCOMING);
+ when(mCall.getPhone()).thenReturn(mPhone0);
+ when(mPhone0.getRingingCall()).thenReturn(mCall);
+ mPhone1 = makeTestPhone(1 /*phoneId*/, ServiceState.STATE_IN_SERVICE,
+ false /*isEmergencyOnly*/);
+ when(mCall2.getPhone()).thenReturn(mPhone1);
+ List<Phone> phones = new ArrayList<>(2);
+ doReturn(true).when(mPhone0).isRadioOn();
+ doReturn(true).when(mPhone1).isRadioOn();
+ doReturn(GSM_PHONE).when(mPhone0).getPhoneType();
+ doReturn(GSM_PHONE).when(mPhone1).getPhoneType();
+ phones.add(mPhone0);
+ phones.add(mPhone1);
+ setPhones(phones);
+ when(mPhoneUtilsProxy.getSubIdForPhoneAccountHandle(eq(PHONE_ACCOUNT_HANDLE_1)))
+ .thenReturn(0);
+ when(mSubscriptionManagerProxy.getPhoneId(0)).thenReturn(0);
+ when(mPhoneFactoryProxy.getPhone(eq(0))).thenReturn(mPhone0);
+ when(mPhoneUtilsProxy.getSubIdForPhoneAccountHandle(eq(PHONE_ACCOUNT_HANDLE_2)))
+ .thenReturn(1);
+ when(mSubscriptionManagerProxy.getPhoneId(1)).thenReturn(1);
+ when(mPhoneFactoryProxy.getPhone(eq(1))).thenReturn(mPhone1);
+ setupDeviceConfig(mPhone0, mPhone1, 1);
+
+ when(mInternalConnection.getCall()).thenReturn(mCall);
+ when(mInternalConnection.getState()).thenReturn(Call.State.ACTIVE);
+ when(mInternalConnection2.getCall()).thenReturn(mCall2);
+ when(mInternalConnection2.getState()).thenReturn(Call.State.WAITING);
+ }
+
+ /**
* Set up a mock MSIM device with TEST_ADDRESS set as an emergency number.
+ * @param isRoaming whether it is roaming
+ * @param setOperatorName whether operator name needs to set
+ * @param operatorNameLongName the operator long name if needs to set
+ * @param operatorNameShortName the operator short name if needs to set
+ * @param operatorNameNumeric the operator numeric name if needs to set
* @return the Phone associated with slot 0.
*/
- private Phone setupConnectionServiceForDelayDial() {
+ private Phone setupConnectionServiceForDelayDial(boolean isRoaming, boolean setOperatorName,
+ String operatorNameLongName, String operatorNameShortName,
+ String operatorNameNumeric) {
ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
.setAccountHandle(PHONE_ACCOUNT_HANDLE_1)
.setAddress(TEST_ADDRESS)
@@ -1183,15 +1392,17 @@
emergencyNumbers.put(0 /*subId*/, numbers);
doReturn(emergencyNumbers).when(mTelephonyManagerProxy).getCurrentEmergencyNumberList();
doReturn(2).when(mTelephonyManagerProxy).getPhoneCount();
-
+ testPhone0.getServiceState().setRoaming(isRoaming);
+ if (setOperatorName) {
+ testPhone0.getServiceState().setOperatorName(operatorNameLongName,
+ operatorNameShortName, operatorNameNumeric);
+ }
mConnection = mTestConnectionService.onCreateOutgoingConnection(
PHONE_ACCOUNT_HANDLE_1, connectionRequest);
assertNotNull("test connection was not set up correctly.", mConnection);
-
return testPhone0;
}
-
/**
* Set up a mock MSIM device with TEST_ADDRESS set as an emergency number in airplane mode.
* @return the Phone associated with slot 0.
@@ -1231,15 +1442,6 @@
return testPhone0;
}
- private Runnable verifyRunnablePosted() {
- ArgumentCaptor<Message> runnableCaptor = ArgumentCaptor.forClass(Message.class);
- verify(mMockHandler).sendMessageDelayed(runnableCaptor.capture(), anyLong());
- assertNotNull("Invalid Message created", runnableCaptor.getValue());
- Runnable runnable = runnableCaptor.getValue().getCallback();
- assertNotNull("sendMessageDelayed never occurred.", runnableCaptor);
- return runnable;
- }
-
private EmergencyNumber setupEmergencyNumber(Uri address) {
return new EmergencyNumber(address.getSchemeSpecificPart(), "", "",
EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
@@ -1248,11 +1450,11 @@
EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
}
- private void setupHandleToPhoneMap(PhoneAccountHandle handle, Phone phone) {
+ private void setupHandleToPhoneMap(PhoneAccountHandle handle, Phone phone) {
// use subId 0
- when(mPhoneUtilsProxy.getSubIdForPhoneAccountHandle(handle)).thenReturn(0);
- when(mSubscriptionManagerProxy.getPhoneId(0)).thenReturn(0);
- when(mPhoneFactoryProxy.getPhone(0)).thenReturn(phone);
+ when(mPhoneUtilsProxy.getSubIdForPhoneAccountHandle(eq(handle))).thenReturn(0);
+ when(mSubscriptionManagerProxy.getPhoneId(eq(0))).thenReturn(0);
+ when(mPhoneFactoryProxy.getPhone(eq(0))).thenReturn(phone);
}
private AsyncResult getSuppServiceNotification(int notificationType, int code) {
@@ -1273,6 +1475,8 @@
when(phone.getDefaultPhone()).thenReturn(phone);
when(phone.getEmergencyNumberTracker()).thenReturn(mEmergencyNumberTracker);
when(phone.getServiceStateTracker()).thenReturn(mSST);
+ doNothing().when(phone).registerForPreciseCallStateChanged(any(Handler.class), anyInt(),
+ any(Object.class));
when(mEmergencyNumberTracker.getEmergencyNumber(anyString())).thenReturn(null);
return phone;
}
@@ -1308,7 +1512,7 @@
private void setPhonesDialConnection(Phone phone, Connection c) {
try {
- when(phone.dial(anyString(), any())).thenReturn(c);
+ when(phone.dial(anyString(), any(), any())).thenReturn(c);
} catch (CallStateException e) {
// this shouldn't happen
fail();
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
index 7d15680..388fd29 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
@@ -1,25 +1,215 @@
package com.android.services.telephony;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+import static junit.framework.TestCase.assertFalse;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.os.Bundle;
import android.telecom.Connection;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import android.telephony.CarrierConfigManager;
+import android.telephony.DisconnectCause;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.d2d.DtmfTransport;
+import com.android.internal.telephony.d2d.RtpTransport;
+import com.android.internal.telephony.imsphone.ImsPhoneConnection;
+import com.android.phone.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
@RunWith(AndroidJUnit4.class)
public class TelephonyConnectionTest {
+ @Mock
+ private ImsPhoneConnection mImsPhoneConnection;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ when(mImsPhoneConnection.getState()).thenReturn(Call.State.ACTIVE);
+ when(mImsPhoneConnection.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_IMS);
+ }
+
+ /**
+ * Ensures an Ims connection uses the D2D communicator when it is enabled.
+ */
+ @Test
+ public void testSetupCommunicator() {
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setIsImsConnection(true);
+ // Enable D2D comms.
+ when(c.mMockResources.getBoolean(eq(
+ R.bool.config_use_device_to_device_communication))).thenReturn(true);
+ c.getCarrierConfigBundle().putBoolean(
+ CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL,
+ true);
+
+ c.setOriginalConnection(mImsPhoneConnection);
+ assertNotNull(c.getCommunicator());
+ }
+
+ /**
+ * Ensures an Ims connection does not use the D2D communicator when it is disabled.
+ */
+ @Test
+ public void testDoNotSetupCommunicatorWhenDisabled() {
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setIsImsConnection(true);
+ // Disable D2D comms.
+ when(c.mMockResources.getBoolean(eq(
+ R.bool.config_use_device_to_device_communication))).thenReturn(false);
+
+ c.setOriginalConnection(mImsPhoneConnection);
+ assertNull(c.getCommunicator());
+ }
+
+ /**
+ * Ensures an Ims connection does not use the D2D communicator for a non-IMS call.
+ */
+ @Test
+ public void testDoNotSetupCommunicatorForNonIms() {
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setIsImsConnection(false);
+ // Disable D2D comms.
+ when(c.mMockResources.getBoolean(eq(
+ R.bool.config_use_device_to_device_communication))).thenReturn(true);
+
+ c.setOriginalConnection(mImsPhoneConnection);
+ assertNull(c.getCommunicator());
+ }
+
+ /**
+ * Ensures an Ims connection does not use the D2D communicator when it is disabled.
+ */
+ @Test
+ public void testDoNotSetupCommunicatorNoTransports() {
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setIsImsConnection(true);
+ // Enable D2D comms.
+ when(c.mMockResources.getBoolean(eq(
+ R.bool.config_use_device_to_device_communication))).thenReturn(true);
+ // But carrier disables transports. Womp.
+ c.getCarrierConfigBundle().putBoolean(
+ CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL,
+ false);
+ c.getCarrierConfigBundle().putBoolean(
+ CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL,
+ false);
+ c.setOriginalConnection(mImsPhoneConnection);
+ assertNull(c.getCommunicator());
+ }
+
+ @Test
+ public void testSetupRtpOnly() {
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setIsImsConnection(true);
+ // Enable D2D comms.
+ when(c.mMockResources.getBoolean(eq(
+ R.bool.config_use_device_to_device_communication))).thenReturn(true);
+ // But carrier disables transports. Womp.
+ c.getCarrierConfigBundle().putBoolean(
+ CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL,
+ false);
+ c.getCarrierConfigBundle().putBoolean(
+ CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL,
+ true);
+ c.setOriginalConnection(mImsPhoneConnection);
+ assertNotNull(c.getCommunicator());
+ assertEquals(1, c.getCommunicator().getTransportProtocols().size());
+ assertTrue(c.getCommunicator().getTransportProtocols()
+ .stream().anyMatch(p -> p instanceof RtpTransport));
+ }
+
+ @Test
+ public void testHangupAfterRedial() throws Exception {
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.hangup(DisconnectCause.LOCAL);
+ verify(c.mMockRadioConnection).hangup();
+
+ // hangup failed because redial was in progress... The new original connection has been sent
+ // to the TelephonyConnection
+ com.android.internal.telephony.Connection newMockRadioConnection =
+ mock(com.android.internal.telephony.Connection.class);
+ doReturn("5551212").when(c.mMockRadioConnection).getAddress();
+ doReturn("5551212").when(newMockRadioConnection).getAddress();
+ doReturn(Call.State.DIALING).when(newMockRadioConnection).getState();
+ c.onOriginalConnectionRedialed(newMockRadioConnection);
+ verify(newMockRadioConnection).hangup();
+ }
+
+ @Test
+ public void testSetupDtmfOnly() {
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setIsImsConnection(true);
+ // Enable D2D comms.
+ when(c.mMockResources.getBoolean(eq(
+ R.bool.config_use_device_to_device_communication))).thenReturn(true);
+ // But carrier disables transports. Womp.
+ c.getCarrierConfigBundle().putBoolean(
+ CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL,
+ true);
+ c.getCarrierConfigBundle().putBoolean(
+ CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL,
+ false);
+ c.setOriginalConnection(mImsPhoneConnection);
+ assertNotNull(c.getCommunicator());
+ assertEquals(1, c.getCommunicator().getTransportProtocols().size());
+ assertTrue(c.getCommunicator().getTransportProtocols()
+ .stream().anyMatch(p -> p instanceof DtmfTransport));
+ }
@Test
public void testCodecInIms() {
TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setIsImsConnection(true);
c.updateState();
Bundle extras = c.getExtras();
int codec = extras.getInt(Connection.EXTRA_AUDIO_CODEC, Connection.AUDIO_CODEC_NONE);
assertEquals(codec, Connection.AUDIO_CODEC_AMR);
}
+ @Test
+ public void testConferenceNotSupportedForDownGradedVideoCall() {
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setIsImsConnection(true);
+ c.setIsVideoCall(false);
+ c.setWasVideoCall(true);
+ c.setDownGradeVideoCall(true);
+ c.refreshConferenceSupported();
+ assertFalse(c.isConferenceSupported());
+ c.setDownGradeVideoCall(false);
+ c.refreshConferenceSupported();
+ assertTrue(c.isConferenceSupported());
+ }
+
+ /**
+ * Tests to ensure that the presence of an ImsExternalConnection does not cause a crash in
+ * TelephonyConnection due to an illegal cast.
+ */
+ @Test
+ public void testImsExternalConnectionOnRefreshConference() {
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setIsImsConnection(true);
+ c.setIsImsExternalConnection(true);
+ try {
+ c.refreshConferenceSupported();
+ } catch (ClassCastException e) {
+ fail("refreshConferenceSupported threw ClassCastException");
+ }
+ }
}
diff --git a/tests/src/com/android/services/telephony/TelephonyManagerTest.java b/tests/src/com/android/services/telephony/TelephonyManagerTest.java
new file mode 100644
index 0000000..2202bc7
--- /dev/null
+++ b/tests/src/com/android/services/telephony/TelephonyManagerTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2020 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.services.telephony;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
+import android.test.mock.MockContext;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.telephony.ITelephony;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Unit tests for {@link TelephonyManager}. */
+@RunWith(AndroidJUnit4.class)
+public class TelephonyManagerTest {
+ private static final String PKG_NAME = "Unittest.TelephonyManagerTest";
+ private static final String TAG = "TelephonyManagerTest";
+
+ private ITelephony mMockITelephony;
+ private SubscriptionManager mMockSubscriptionManager;
+ private Context mMockContext;
+
+ private TelephonyManager mTelephonyManager;
+
+ private final MockContext mContext =
+ new MockContext() {
+ @Override
+ public String getOpPackageName() {
+ return PKG_NAME;
+ }
+ @Override
+ public String getAttributionTag() {
+ return TAG;
+ }
+ @Override
+ public Context getApplicationContext() {
+ return null;
+ }
+ @Override
+ public Object getSystemService(String name) {
+ switch (name) {
+ case (Context.TELEPHONY_SUBSCRIPTION_SERVICE) : {
+ return mMockSubscriptionManager;
+ }
+ }
+ return null;
+ }
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ mMockITelephony = mock(ITelephony.class);
+ mMockSubscriptionManager = mock(SubscriptionManager.class);
+ mMockContext = mock(Context.class);
+ when(mMockContext.getSystemService(eq(Context.TELEPHONY_SUBSCRIPTION_SERVICE)))
+ .thenReturn(mMockSubscriptionManager);
+
+ mTelephonyManager = new TelephonyManager(mContext);
+ TelephonyManager.setupITelephonyForTest(mMockITelephony);
+ TelephonyManager.enableServiceHandleCaching();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ TelephonyManager.setupITelephonyForTest(null);
+ TelephonyManager.disableServiceHandleCaching();
+ }
+
+ @Test
+ public void testFilterEmergencyNumbersByCategories() throws Exception {
+ Map<Integer, List<EmergencyNumber>> emergencyNumberLists = new HashMap<>();
+ List<EmergencyNumber> emergencyNumberList = new ArrayList<>();
+ EmergencyNumber number_police = new EmergencyNumber(
+ "911",
+ "us",
+ "30",
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE,
+ new ArrayList<String>(),
+ EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+ EmergencyNumber number_fire = new EmergencyNumber(
+ "912",
+ "us",
+ "30",
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE,
+ new ArrayList<String>(),
+ EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+ emergencyNumberList.add(number_police);
+ emergencyNumberList.add(number_fire);
+ final int test_sub_id = 1;
+ emergencyNumberLists.put(test_sub_id, emergencyNumberList);
+
+ Map<Integer, List<EmergencyNumber>> returnedEmergencyNumberLists =
+ mTelephonyManager.filterEmergencyNumbersByCategories(emergencyNumberLists,
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE);
+
+ // Verify the returned number list contains only the police number(s)
+ List<EmergencyNumber> returnedEmergencyNumberList = returnedEmergencyNumberLists.get(
+ test_sub_id);
+ for (EmergencyNumber num : returnedEmergencyNumberList) {
+ assertTrue(num.isInEmergencyServiceCategories(
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE));
+ }
+ }
+
+ @Test
+ public void testGetEmergencyNumberListForCategories() throws Exception {
+ Map<Integer, List<EmergencyNumber>> emergencyNumberLists = new HashMap<>();
+ List<EmergencyNumber> emergencyNumberList = new ArrayList<>();
+ EmergencyNumber number_police = new EmergencyNumber(
+ "911",
+ "us",
+ "30",
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE,
+ new ArrayList<String>(),
+ EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+ EmergencyNumber number_fire = new EmergencyNumber(
+ "912",
+ "us",
+ "30",
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE,
+ new ArrayList<String>(),
+ EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+ emergencyNumberList.add(number_police);
+ emergencyNumberList.add(number_fire);
+ final int test_sub_id = 1;
+ emergencyNumberLists.put(test_sub_id, emergencyNumberList);
+ when(mMockITelephony.getEmergencyNumberList(eq(PKG_NAME), eq(TAG))).thenReturn(
+ emergencyNumberLists);
+
+ // Call TelephonyManager.getEmergencyNumberList(Category)
+ Map<Integer, List<EmergencyNumber>> returnedEmergencyNumberLists =
+ mTelephonyManager.getEmergencyNumberList(
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE);
+
+ // Verify the ITelephony service is called
+ verify(mMockITelephony, times(1)).getEmergencyNumberList(eq(PKG_NAME), eq(TAG));
+
+ // Verify the returned number list contains only the police number(s)
+ List<EmergencyNumber> returnedEmergencyNumberList = returnedEmergencyNumberLists.get(
+ test_sub_id);
+ for (EmergencyNumber num : returnedEmergencyNumberList) {
+ assertTrue(num.isInEmergencyServiceCategories(
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE));
+ }
+ }
+}
diff --git a/tests/src/com/android/services/telephony/TestTelephonyConnection.java b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
index 09cec17..817e9a7 100644
--- a/tests/src/com/android/services/telephony/TestTelephonyConnection.java
+++ b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
@@ -16,28 +16,42 @@
package com.android.services.telephony;
+import android.content.AttributionSource;
+import android.content.ContentResolver;
+import android.os.Process;
+import android.os.UserHandle;
+import android.telephony.TelephonyManager;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.telecom.PhoneAccountHandle;
+import android.telecom.VideoProfile;
+import android.telephony.CarrierConfigManager;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
+import com.android.ims.ImsCall;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.emergency.EmergencyNumberTracker;
+import com.android.internal.telephony.imsphone.ImsExternalConnection;
+import com.android.internal.telephony.imsphone.ImsPhoneConnection;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.List;
@@ -58,19 +72,51 @@
Context mMockContext;
@Mock
+ ContentResolver mMockContentResolver;
+
+ @Mock
Resources mMockResources;
@Mock
+ TelephonyManager mMockTelephonyManager;
+
+ @Mock
EmergencyNumberTracker mEmergencyNumberTracker;
+ @Mock
+ ImsPhoneConnection mImsPhoneConnection;
+
+ @Mock
+ ImsExternalConnection mImsExternalConnection;
+
+ @Mock
+ ImsCall mImsCall;
+
+ @Mock
+ TelecomAccountRegistry mTelecomAccountRegistry;
+
+ @Mock
+ CarrierConfigManager mCarrierConfigManager;
+
+ private boolean mIsImsConnection;
+ private boolean mIsImsExternalConnection;
+ private boolean mIsConferenceSupported = true;
private Phone mMockPhone;
private int mNotifyPhoneAccountChangedCount = 0;
private List<String> mLastConnectionEvents = new ArrayList<>();
private List<Bundle> mLastConnectionEventExtras = new ArrayList<>();
+ private Object mLock = new Object();
+ private PersistableBundle mCarrierConfig = new PersistableBundle();
@Override
public com.android.internal.telephony.Connection getOriginalConnection() {
- return mMockRadioConnection;
+ if (mIsImsExternalConnection) {
+ return mImsExternalConnection;
+ } else if (mIsImsConnection) {
+ return mImsPhoneConnection;
+ } else {
+ return mMockRadioConnection;
+ }
}
@Override
@@ -82,16 +128,25 @@
super(null, null, android.telecom.Call.Details.DIRECTION_INCOMING);
MockitoAnnotations.initMocks(this);
+ AttributionSource attributionSource = new AttributionSource.Builder(
+ Process.myUid()).build();
+
+ mIsImsConnection = false;
+ mIsImsExternalConnection = false;
mMockPhone = mock(Phone.class);
mMockContext = mock(Context.class);
+ mMockTelephonyManager = mock(TelephonyManager.class);
mOriginalConnection = mMockRadioConnection;
// Set up mMockRadioConnection and mMockPhone to contain an active call
when(mMockRadioConnection.getState()).thenReturn(Call.State.ACTIVE);
when(mOriginalConnection.getState()).thenReturn(Call.State.ACTIVE);
when(mMockRadioConnection.getAudioCodec()).thenReturn(
android.telecom.Connection.AUDIO_CODEC_AMR);
+ when(mImsPhoneConnection.getAudioCodec()).thenReturn(
+ android.telecom.Connection.AUDIO_CODEC_AMR);
when(mMockRadioConnection.getCall()).thenReturn(mMockCall);
when(mMockRadioConnection.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_IMS);
+ when(mImsPhoneConnection.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_IMS);
doNothing().when(mMockRadioConnection).addListener(any(Connection.Listener.class));
doNothing().when(mMockRadioConnection).addPostDialListener(
any(Connection.PostDialListener.class));
@@ -101,11 +156,28 @@
when(mMockPhone.getContext()).thenReturn(mMockContext);
when(mMockPhone.getCurrentSubscriberUris()).thenReturn(null);
when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
+ when(mMockContext.getSystemService(Context.TELEPHONY_SERVICE))
+ .thenReturn(mMockTelephonyManager);
+ when(mMockContext.getAttributionSource()).thenReturn(attributionSource);
+ when(mMockContentResolver.getUserId()).thenReturn(UserHandle.USER_CURRENT);
+ when(mMockContentResolver.getAttributionSource()).thenReturn(attributionSource);
when(mMockResources.getBoolean(anyInt())).thenReturn(false);
when(mMockPhone.getDefaultPhone()).thenReturn(mMockPhone);
when(mMockPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_IMS);
when(mMockCall.getState()).thenReturn(Call.State.ACTIVE);
when(mMockCall.getPhone()).thenReturn(mMockPhone);
+ when(mMockPhone.getDefaultPhone()).thenReturn(mMockPhone);
+ when(mImsPhoneConnection.getImsCall()).thenReturn(mImsCall);
+ when(mTelecomAccountRegistry.isMergeCallSupported(notNull(PhoneAccountHandle.class)))
+ .thenReturn(mIsConferenceSupported);
+ when(mTelecomAccountRegistry.isMergeImsCallSupported(notNull(PhoneAccountHandle.class)))
+ .thenReturn(mIsImsConnection);
+ when(mTelecomAccountRegistry
+ .isVideoConferencingSupported(notNull(PhoneAccountHandle.class))).thenReturn(false);
+ when(mTelecomAccountRegistry
+ .isMergeOfWifiCallsAllowedWhenVoWifiOff(notNull(PhoneAccountHandle.class)))
+ .thenReturn(false);
try {
doNothing().when(mMockCall).hangup();
} catch (CallStateException e) {
@@ -113,11 +185,6 @@
}
}
- @Override
- public boolean isConferenceSupported() {
- return true;
- }
-
public void setMockPhone(Phone newPhone) {
mMockPhone = newPhone;
}
@@ -151,7 +218,14 @@
public PersistableBundle getCarrierConfig() {
// Depends on PhoneGlobals for context in TelephonyConnection, do not implement during
// testing.
- return new PersistableBundle();
+ return mCarrierConfig;
+ }
+
+ @Override
+ public void refreshConferenceSupported() {
+ if (mIsImsConnection) {
+ super.refreshConferenceSupported();
+ }
}
@Override
@@ -165,8 +239,36 @@
}
@Override
- void refreshConferenceSupported() {
- // Requires ImsManager dependencies, do not implement during testing.
+ public void setConferenceSupported(boolean conferenceSupported) {
+ mIsConferenceSupported = conferenceSupported;
+ }
+
+ @Override
+ public boolean isConferenceSupported() {
+ return mIsConferenceSupported;
+ }
+
+ @Override
+ public TelecomAccountRegistry getTelecomAccountRegistry(Context context) {
+ return mTelecomAccountRegistry;
+ }
+
+ public void setIsVideoCall(boolean isVideoCall) {
+ if (isVideoCall) {
+ setVideoState(VideoProfile.STATE_TX_ENABLED);
+ } else {
+ setVideoState(VideoProfile.STATE_AUDIO_ONLY);
+ }
+ }
+
+ public void setWasVideoCall(boolean wasVideoCall) {
+ when(mImsCall.wasVideoCall()).thenReturn(wasVideoCall);
+ }
+
+ @Override
+ boolean isWfcEnabled(Phone phone) {
+ // Requires ImsManager dependencies, mock for test.
+ return true;
}
public int getNotifyPhoneAccountChangedCount() {
@@ -180,4 +282,27 @@
public List<Bundle> getLastConnectionEventExtras() {
return mLastConnectionEventExtras;
}
+
+ public void setIsImsConnection(boolean isImsConnection) {
+ mIsImsConnection = isImsConnection;
+ when(mTelecomAccountRegistry.isMergeImsCallSupported(notNull(PhoneAccountHandle.class)))
+ .thenReturn(isImsConnection && mIsConferenceSupported);
+ }
+
+ public void setIsImsExternalConnection(boolean isExternalConnection) {
+ mIsImsExternalConnection = isExternalConnection;
+ }
+
+ public void setDownGradeVideoCall(boolean downgrade) {
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putBoolean(CarrierConfigManager.KEY_TREAT_DOWNGRADED_VIDEO_CALLS_AS_VIDEO_CALLS_BOOL,
+ downgrade);
+ when(mMockContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
+ .thenReturn(mCarrierConfigManager);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+ }
+
+ public PersistableBundle getCarrierConfigBundle() {
+ return mCarrierConfig;
+ }
}
diff --git a/tests/src/com/android/services/telephony/rcs/DelegateStateTrackerTest.java b/tests/src/com/android/services/telephony/rcs/DelegateStateTrackerTest.java
new file mode 100644
index 0000000..8236f44
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/DelegateStateTrackerTest.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2020 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.services.telephony.rcs;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.net.InetAddresses;
+import android.telephony.ims.DelegateRegistrationState;
+import android.telephony.ims.FeatureTagState;
+import android.telephony.ims.SipDelegateConfiguration;
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.aidl.ISipDelegate;
+import android.telephony.ims.aidl.ISipDelegateConnectionStateCallback;
+import android.util.ArraySet;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+public class DelegateStateTrackerTest extends TelephonyTestBase {
+ private static final int TEST_SUB_ID = 1;
+
+ @Mock private ISipDelegate mSipDelegate;
+ @Mock private ISipDelegateConnectionStateCallback mAppCallback;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * When an underlying SipDelegate is created, the app should only receive one onCreated callback
+ * independent of how many times sipDelegateConnected is called. Once created, registration
+ * and IMS configuration events should propagate up to the app as well.
+ */
+ @SmallTest
+ @Test
+ public void testDelegateCreated() throws Exception {
+ DelegateStateTracker stateTracker = new DelegateStateTracker(TEST_SUB_ID, mAppCallback,
+ mSipDelegate);
+ Set<FeatureTagState> deniedTags = getMmTelDeniedTag();
+ stateTracker.sipDelegateConnected(deniedTags);
+ // Calling connected multiple times should not generate multiple onCreated events.
+ stateTracker.sipDelegateConnected(deniedTags);
+ verify(mAppCallback).onCreated(mSipDelegate);
+
+ // Ensure status updates are sent to app as expected.
+ DelegateRegistrationState regState = new DelegateRegistrationState.Builder()
+ .addRegisteredFeatureTag(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG)
+ .build();
+ InetSocketAddress localAddr = new InetSocketAddress(
+ InetAddresses.parseNumericAddress("1.1.1.1"), 80);
+ InetSocketAddress serverAddr = new InetSocketAddress(
+ InetAddresses.parseNumericAddress("2.2.2.2"), 81);
+ SipDelegateConfiguration c = new SipDelegateConfiguration.Builder(1,
+ SipDelegateConfiguration.SIP_TRANSPORT_TCP, localAddr, serverAddr).build();
+ stateTracker.onRegistrationStateChanged(regState);
+ stateTracker.onConfigurationChanged(c);
+ verify(mAppCallback).onFeatureTagStatusChanged(eq(regState),
+ eq(new ArrayList<>(deniedTags)));
+ verify(mAppCallback).onConfigurationChanged(c);
+
+ verify(mAppCallback, never()).onDestroyed(anyInt());
+ }
+
+ /**
+ * onDestroyed should be called when sipDelegateDestroyed is called.
+ */
+ @SmallTest
+ @Test
+ public void testDelegateDestroyed() throws Exception {
+ DelegateStateTracker stateTracker = new DelegateStateTracker(TEST_SUB_ID, mAppCallback,
+ mSipDelegate);
+ Set<FeatureTagState> deniedTags = getMmTelDeniedTag();
+ stateTracker.sipDelegateConnected(deniedTags);
+
+ stateTracker.sipDelegateDestroyed(
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ verify(mAppCallback).onDestroyed(
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ }
+
+ /**
+ * When a SipDelegate is created and then an event occurs that will destroy->create a new
+ * SipDelegate underneath, we need to move the state of the features that are reporting
+ * registered to DEREGISTERING_REASON_FEATURE_TAGS_CHANGING so that the app can close dialogs on
+ * it. Once the new underlying SipDelegate is created, we must verify that the new registration
+ * is propagated up without any overrides.
+ */
+ @SmallTest
+ @Test
+ public void testDelegateChangingRegisteredTagsOverride() throws Exception {
+ DelegateStateTracker stateTracker = new DelegateStateTracker(TEST_SUB_ID, mAppCallback,
+ mSipDelegate);
+ Set<FeatureTagState> deniedTags = getMmTelDeniedTag();
+ stateTracker.sipDelegateConnected(deniedTags);
+ // SipDelegate created
+ verify(mAppCallback).onCreated(mSipDelegate);
+ DelegateRegistrationState regState = new DelegateRegistrationState.Builder()
+ .addRegisteredFeatureTag(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG)
+ .addDeregisteringFeatureTag(ImsSignallingUtils.FILE_TRANSFER_HTTP_TAG,
+ DelegateRegistrationState.DEREGISTERING_REASON_PROVISIONING_CHANGE)
+ .addDeregisteredFeatureTag(ImsSignallingUtils.GROUP_CHAT_TAG,
+ DelegateRegistrationState.DEREGISTERED_REASON_NOT_PROVISIONED)
+ .build();
+ stateTracker.onRegistrationStateChanged(regState);
+ // Simulate underlying SipDelegate switch
+ stateTracker.sipDelegateChanging(
+ DelegateRegistrationState.DEREGISTERING_REASON_FEATURE_TAGS_CHANGING);
+ // onFeatureTagStatusChanged should now be called with registered features overridden with
+ // DEREGISTERING_REASON_FEATURE_TAGS_CHANGING
+ DelegateRegistrationState overrideRegState = new DelegateRegistrationState.Builder()
+ .addDeregisteringFeatureTag(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG,
+ DelegateRegistrationState.DEREGISTERING_REASON_FEATURE_TAGS_CHANGING)
+ // Already Deregistering/Deregistered tags should not be overridden.
+ .addDeregisteringFeatureTag(ImsSignallingUtils.FILE_TRANSFER_HTTP_TAG,
+ DelegateRegistrationState.DEREGISTERING_REASON_PROVISIONING_CHANGE)
+ .addDeregisteredFeatureTag(ImsSignallingUtils.GROUP_CHAT_TAG,
+ DelegateRegistrationState.DEREGISTERED_REASON_NOT_PROVISIONED)
+ .build();
+ // new underlying SipDelegate created
+ stateTracker.sipDelegateConnected(deniedTags);
+ stateTracker.onRegistrationStateChanged(regState);
+
+ // Verify registration state through the process:
+ ArgumentCaptor<DelegateRegistrationState> regCaptor =
+ ArgumentCaptor.forClass(DelegateRegistrationState.class);
+ verify(mAppCallback, times(3)).onFeatureTagStatusChanged(
+ regCaptor.capture(), eq(new ArrayList<>(deniedTags)));
+ List<DelegateRegistrationState> testStates = regCaptor.getAllValues();
+ // feature tags should first be registered
+ assertEquals(regState, testStates.get(0));
+ // registered feature tags should have moved to deregistering
+ assertEquals(overrideRegState, testStates.get(1));
+ // and then moved back to registered after underlying FT change done.
+ assertEquals(regState, testStates.get(2));
+
+ //onCreate should only have been called once and onDestroy should have never been called.
+ verify(mAppCallback).onCreated(mSipDelegate);
+ verify(mAppCallback, never()).onDestroyed(anyInt());
+ }
+
+ /**
+ * Test the case that when the underlying Denied tags change in the SipDelegate, the change is
+ * properly shown in the registration update event.
+ */
+ @SmallTest
+ @Test
+ public void testDelegateChangingDeniedTagsChanged() throws Exception {
+ DelegateStateTracker stateTracker = new DelegateStateTracker(TEST_SUB_ID, mAppCallback,
+ mSipDelegate);
+ Set<FeatureTagState> deniedTags = getMmTelDeniedTag();
+ stateTracker.sipDelegateConnected(deniedTags);
+ // SipDelegate created
+ verify(mAppCallback).onCreated(mSipDelegate);
+ DelegateRegistrationState regState = new DelegateRegistrationState.Builder()
+ .addRegisteredFeatureTag(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG)
+ .build();
+ stateTracker.onRegistrationStateChanged(regState);
+ // Simulate underlying SipDelegate switch
+ stateTracker.sipDelegateChanging(
+ DelegateRegistrationState.DEREGISTERING_REASON_FEATURE_TAGS_CHANGING);
+ // onFeatureTagStatusChanged should now be called with registered features overridden with
+ // DEREGISTERING_REASON_FEATURE_TAGS_CHANGING
+ DelegateRegistrationState overrideRegState = new DelegateRegistrationState.Builder()
+ .addDeregisteringFeatureTag(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG,
+ DelegateRegistrationState.DEREGISTERING_REASON_FEATURE_TAGS_CHANGING)
+ .build();
+ // Verify registration state so far.
+ ArgumentCaptor<DelegateRegistrationState> regCaptor =
+ ArgumentCaptor.forClass(DelegateRegistrationState.class);
+ verify(mAppCallback, times(2)).onFeatureTagStatusChanged(
+ regCaptor.capture(), eq(new ArrayList<>(deniedTags)));
+ List<DelegateRegistrationState> testStates = regCaptor.getAllValues();
+ assertEquals(2, testStates.size());
+ // feature tags should first be registered
+ assertEquals(regState, testStates.get(0));
+ // registered feature tags should have moved to deregistering
+ assertEquals(overrideRegState, testStates.get(1));
+
+ // new underlying SipDelegate created, but SipDelegate denied one to one chat
+ deniedTags.add(new FeatureTagState(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG,
+ SipDelegateManager.DENIED_REASON_NOT_ALLOWED));
+ stateTracker.sipDelegateConnected(deniedTags);
+ DelegateRegistrationState fullyDeniedRegState = new DelegateRegistrationState.Builder()
+ .build();
+ // In this special case, it will be the SipDelegateConnectionBase that will trigger
+ // reg state change.
+ stateTracker.onRegistrationStateChanged(fullyDeniedRegState);
+ verify(mAppCallback).onFeatureTagStatusChanged(regCaptor.capture(),
+ eq(new ArrayList<>(deniedTags)));
+ // now all feature tags denied, so we should see only denied tags.
+ assertEquals(fullyDeniedRegState, regCaptor.getValue());
+
+ //onCreate should only have been called once and onDestroy should have never been called.
+ verify(mAppCallback).onCreated(mSipDelegate);
+ verify(mAppCallback, never()).onDestroyed(anyInt());
+ }
+
+ /**
+ * Test that when we move from changing tags state to the delegate being destroyed, we get the
+ * correct onDestroy event sent to the app.
+ */
+ @SmallTest
+ @Test
+ public void testDelegateChangingDeniedTagsChangingToDestroy() throws Exception {
+ DelegateStateTracker stateTracker = new DelegateStateTracker(TEST_SUB_ID, mAppCallback,
+ mSipDelegate);
+ Set<FeatureTagState> deniedTags = getMmTelDeniedTag();
+ stateTracker.sipDelegateConnected(deniedTags);
+ // SipDelegate created
+ verify(mAppCallback).onCreated(mSipDelegate);
+ DelegateRegistrationState regState = new DelegateRegistrationState.Builder()
+ .addRegisteredFeatureTag(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG)
+ .addDeregisteredFeatureTag(ImsSignallingUtils.GROUP_CHAT_TAG,
+ DelegateRegistrationState.DEREGISTERED_REASON_NOT_PROVISIONED)
+ .build();
+ stateTracker.onRegistrationStateChanged(regState);
+ verify(mAppCallback).onFeatureTagStatusChanged(any(),
+ eq(new ArrayList<>(deniedTags)));
+ // Simulate underlying SipDelegate switch
+ stateTracker.sipDelegateChanging(
+ DelegateRegistrationState.DEREGISTERING_REASON_DESTROY_PENDING);
+ // Destroy
+ stateTracker.sipDelegateDestroyed(
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+
+ // onFeatureTagStatusChanged should now be called with registered features overridden with
+ // DEREGISTERING_REASON_DESTROY_PENDING
+ DelegateRegistrationState overrideRegState = new DelegateRegistrationState.Builder()
+ .addDeregisteringFeatureTag(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG,
+ DelegateRegistrationState.DEREGISTERING_REASON_DESTROY_PENDING)
+ // Deregistered should stay the same.
+ .addDeregisteredFeatureTag(ImsSignallingUtils.GROUP_CHAT_TAG,
+ DelegateRegistrationState.DEREGISTERED_REASON_NOT_PROVISIONED)
+ .build();
+ // Verify registration state through process:
+ ArgumentCaptor<DelegateRegistrationState> regCaptor =
+ ArgumentCaptor.forClass(DelegateRegistrationState.class);
+ verify(mAppCallback, times(2)).onFeatureTagStatusChanged(regCaptor.capture(),
+ eq(new ArrayList<>(deniedTags)));
+ List<DelegateRegistrationState> testStates = regCaptor.getAllValues();
+ assertEquals(2, testStates.size());
+ // feature tags should first be registered
+ assertEquals(regState, testStates.get(0));
+ // registered feature tags should have moved to deregistering
+ assertEquals(overrideRegState, testStates.get(1));
+ //onCreate/onDestroy should only be called once.
+ verify(mAppCallback).onCreated(mSipDelegate);
+ verify(mAppCallback).onDestroyed(
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ }
+
+ private Set<FeatureTagState> getMmTelDeniedTag() {
+ Set<FeatureTagState> deniedTags = new ArraySet<>();
+ deniedTags.add(new FeatureTagState(ImsSignallingUtils.MMTEL_TAG,
+ SipDelegateManager.DENIED_REASON_NOT_ALLOWED));
+ return deniedTags;
+ }
+}
diff --git a/tests/src/com/android/services/telephony/rcs/ImsSignallingUtils.java b/tests/src/com/android/services/telephony/rcs/ImsSignallingUtils.java
new file mode 100644
index 0000000..d607f6d
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/ImsSignallingUtils.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 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.services.telephony.rcs;
+
+/**
+ * Various definitions and utilities related to IMS Signalling.
+ */
+public class ImsSignallingUtils {
+ public static final String MMTEL_TAG =
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel\"";
+ public static final String ONE_TO_ONE_CHAT_TAG =
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gppservice.ims.icsi.oma.cpm.msg\"";
+ public static final String GROUP_CHAT_TAG =
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gppservice.ims.icsi.oma.cpm.session\"";
+ public static final String FILE_TRANSFER_HTTP_TAG =
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gppapplication.ims.iari.rcs.fthttp\"";
+}
diff --git a/tests/src/com/android/services/telephony/rcs/MessageTransportWrapperTest.java b/tests/src/com/android/services/telephony/rcs/MessageTransportWrapperTest.java
new file mode 100644
index 0000000..5ced75c
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/MessageTransportWrapperTest.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2020 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.services.telephony.rcs;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.net.InetAddresses;
+import android.os.RemoteException;
+import android.telephony.ims.DelegateRegistrationState;
+import android.telephony.ims.FeatureTagState;
+import android.telephony.ims.SipDelegateConfiguration;
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.SipMessage;
+import android.telephony.ims.aidl.ISipDelegate;
+import android.telephony.ims.aidl.ISipDelegateMessageCallback;
+import android.util.ArraySet;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+import com.android.TestExecutorService;
+import com.android.services.telephony.rcs.validator.ValidationResult;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.function.Consumer;
+
+@RunWith(AndroidJUnit4.class)
+public class MessageTransportWrapperTest extends TelephonyTestBase {
+ private static final int TEST_SUB_ID = 1;
+
+ private static final SipMessage TEST_MESSAGE = new SipMessage(
+ "INVITE sip:callee@ex.domain.com SIP/2.0",
+ "Via: SIP/2.0/UDP ex.place.com;branch=z9hG4bK776asdhds",
+ new byte[0]);
+
+ // Derived from TEST_MESSAGE above.
+ private static final String TEST_TRANSACTION_ID = "z9hG4bK776asdhds";
+
+ @Mock private ISipDelegateMessageCallback mDelegateMessageCallback;
+ @Mock private TransportSipMessageValidator mTransportSipSessionValidator;
+ @Mock private ISipDelegate mISipDelegate;
+
+ // Test executor that just calls run on the Runnable provided in execute.
+ private ScheduledExecutorService mExecutor = new TestExecutorService();
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @SmallTest
+ @Test
+ public void testImsConfigurationChanged() {
+ MessageTransportWrapper tracker = createTestMessageTransportWrapper();
+ InetSocketAddress localAddr = new InetSocketAddress(
+ InetAddresses.parseNumericAddress("1.1.1.1"), 80);
+ InetSocketAddress serverAddr = new InetSocketAddress(
+ InetAddresses.parseNumericAddress("2.2.2.2"), 81);
+ SipDelegateConfiguration c = new SipDelegateConfiguration.Builder(1,
+ SipDelegateConfiguration.SIP_TRANSPORT_TCP, localAddr, serverAddr).build();
+ // Ensure IMS config changes are propagated to the message tracker.
+ tracker.onConfigurationChanged(c);
+ verify(mTransportSipSessionValidator).onConfigurationChanged(c);
+ }
+
+ @SmallTest
+ @Test
+ public void testOpenTransport() {
+ HashSet<String> allowedTags = new HashSet<>(1);
+ allowedTags.add("testTag");
+ HashSet<FeatureTagState> deniedTags = new HashSet<>(1);
+ deniedTags.add(new FeatureTagState("testBadTag",
+ SipDelegateManager.DENIED_REASON_INVALID));
+ MessageTransportWrapper tracker = createTestMessageTransportWrapper();
+ // Ensure openTransport passes denied tags to the session tracker
+ tracker.openTransport(mISipDelegate, allowedTags, deniedTags);
+ verify(mTransportSipSessionValidator).onTransportOpened(allowedTags, deniedTags);
+ }
+
+ @SmallTest
+ @Test
+ public void testRegistrationStateChanged() throws Exception {
+ ArraySet<String> callIds = new ArraySet<>(2);
+ callIds.add("callId1");
+ callIds.add("callId2");
+ // empty registration state for testing
+ DelegateRegistrationState state = new DelegateRegistrationState.Builder().build();
+ MessageTransportWrapper tracker = createTestMessageTransportWrapper();
+ tracker.openTransport(mISipDelegate, Collections.emptySet(), Collections.emptySet());
+
+ Consumer<Set<String>> callIdConsumer = trackerRegStateChanged(tracker, state);
+ callIdConsumer.accept(callIds);
+ // Verify that the pending call IDs are closed properly.
+ for (String callId : callIds) {
+ verify(mTransportSipSessionValidator).onSipSessionCleanup(callId);
+ verify(mISipDelegate).cleanupSession(callId);
+ }
+ }
+
+ @SmallTest
+ @Test
+ public void testCloseGracefully() throws Exception {
+ int closingReason = DelegateRegistrationState.DEREGISTERING_REASON_PROVISIONING_CHANGE;
+ int closedReason = DelegateRegistrationState.DEREGISTERED_REASON_NOT_PROVISIONED;
+ MessageTransportWrapper tracker = createTestMessageTransportWrapper();
+
+ Boolean[] result = new Boolean[1];
+ Consumer<Set<String>> callIdConsumer = closeTrackerGracefully(tracker, closingReason,
+ closedReason, (r) -> result[0] = r);
+ callIdConsumer.accept(Collections.emptySet());
+ // Verify that the pending call IDs are closed properly.
+ verify(mTransportSipSessionValidator, never()).onSipSessionCleanup(anyString());
+ verify(mISipDelegate, never()).cleanupSession(anyString());
+ // Result is true in the case that all call IDs were successfully closed.
+ assertTrue(result[0]);
+ }
+
+ @SmallTest
+ @Test
+ public void testCloseGracefullyForceCloseCallIds() throws Exception {
+ ArraySet<String> callIds = new ArraySet<>(2);
+ callIds.add("callId1");
+ callIds.add("callId2");
+ int closingReason = DelegateRegistrationState.DEREGISTERING_REASON_PROVISIONING_CHANGE;
+ int closedReason = DelegateRegistrationState.DEREGISTERED_REASON_NOT_PROVISIONED;
+ MessageTransportWrapper tracker = createTestMessageTransportWrapper();
+ tracker.openTransport(mISipDelegate, Collections.emptySet(), Collections.emptySet());
+
+ Boolean[] result = new Boolean[1];
+ Consumer<Set<String>> callIdConsumer = closeTrackerGracefully(tracker, closingReason,
+ closedReason, (r) -> result[0] = r);
+ callIdConsumer.accept(callIds);
+ // Verify that the pending call IDs are closed properly.
+ for (String callId : callIds) {
+ verify(mTransportSipSessionValidator).onSipSessionCleanup(callId);
+ verify(mISipDelegate).cleanupSession(callId);
+ }
+ // Result is false in this case because there were still callIds left that were not
+ // successfully closed.
+ assertFalse(result[0]);
+ }
+
+ @SmallTest
+ @Test
+ public void testClose() throws Exception {
+ ArraySet<String> callIds = new ArraySet<>(2);
+ callIds.add("callId1");
+ callIds.add("callId2");
+ int closedReason = SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED;
+ doReturn(callIds).when(mTransportSipSessionValidator).closeSessions(closedReason);
+ MessageTransportWrapper tracker = createTestMessageTransportWrapper();
+ tracker.openTransport(mISipDelegate, Collections.emptySet(), Collections.emptySet());
+
+ tracker.close(closedReason);
+ // Verify that the pending call IDs are closed properly.
+ for (String callId : callIds) {
+ verify(mTransportSipSessionValidator).onSipSessionCleanup(callId);
+ verify(mISipDelegate).cleanupSession(callId);
+ }
+ }
+
+ @SmallTest
+ @Test
+ public void testDelegateConnectionSendOutgoingMessage() throws Exception {
+ MessageTransportWrapper tracker = createTestMessageTransportWrapper();
+
+ tracker.openTransport(mISipDelegate, Collections.emptySet(), Collections.emptySet());
+ doReturn(ValidationResult.SUCCESS)
+ .when(mTransportSipSessionValidator)
+ .verifyOutgoingMessage(TEST_MESSAGE, 1 /*version*/);
+ tracker.getDelegateConnection().sendMessage(TEST_MESSAGE, 1 /*version*/);
+ verify(mISipDelegate).sendMessage(TEST_MESSAGE, 1 /*version*/);
+
+ doThrow(new RemoteException()).when(mISipDelegate).sendMessage(any(), anyLong());
+ tracker.getDelegateConnection().sendMessage(TEST_MESSAGE, 1 /*version*/);
+ verify(mDelegateMessageCallback).onMessageSendFailure(any(),
+ eq(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD));
+
+ doReturn(new ValidationResult(
+ SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED, ""))
+ .when(mTransportSipSessionValidator)
+ .verifyOutgoingMessage(TEST_MESSAGE, 1 /*version*/);
+ tracker.getDelegateConnection().sendMessage(TEST_MESSAGE, 1 /*version*/);
+ verify(mDelegateMessageCallback).onMessageSendFailure(TEST_TRANSACTION_ID,
+ SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED);
+ }
+
+ @SmallTest
+ @Test
+ public void testDelegateConnectionNotifyMessageReceived() throws Exception {
+ MessageTransportWrapper tracker = createTestMessageTransportWrapper();
+ tracker.openTransport(mISipDelegate, Collections.emptySet(), Collections.emptySet());
+ tracker.getDelegateConnection().notifyMessageReceived(TEST_TRANSACTION_ID);
+ verify(mISipDelegate).notifyMessageReceived(TEST_TRANSACTION_ID);
+ verify(mTransportSipSessionValidator).acknowledgePendingMessage(TEST_TRANSACTION_ID);
+ }
+
+ @SmallTest
+ @Test
+ public void testDelegateConnectionNotifyMessageReceiveError() throws Exception {
+ MessageTransportWrapper tracker = createTestMessageTransportWrapper();
+ tracker.openTransport(mISipDelegate, Collections.emptySet(), Collections.emptySet());
+ tracker.getDelegateConnection().notifyMessageReceiveError(TEST_TRANSACTION_ID,
+ SipDelegateManager.MESSAGE_FAILURE_REASON_NETWORK_NOT_AVAILABLE);
+ verify(mISipDelegate).notifyMessageReceiveError(TEST_TRANSACTION_ID,
+ SipDelegateManager.MESSAGE_FAILURE_REASON_NETWORK_NOT_AVAILABLE);
+ verify(mTransportSipSessionValidator).notifyPendingMessageFailed(TEST_TRANSACTION_ID);
+ }
+
+ @SmallTest
+ @Test
+ public void testDelegateConnectionCloseSession() throws Exception {
+ MessageTransportWrapper tracker = createTestMessageTransportWrapper();
+ tracker.openTransport(mISipDelegate, Collections.emptySet(), Collections.emptySet());
+ tracker.getDelegateConnection().cleanupSession("testCallId");
+ verify(mISipDelegate).cleanupSession("testCallId");
+ verify(mTransportSipSessionValidator).onSipSessionCleanup("testCallId");
+ }
+
+ @SmallTest
+ @Test
+ public void testDelegateOnMessageReceived() throws Exception {
+ MessageTransportWrapper tracker = createTestMessageTransportWrapper();
+ tracker.openTransport(mISipDelegate, Collections.emptySet(), Collections.emptySet());
+
+ doReturn(ValidationResult.SUCCESS)
+ .when(mTransportSipSessionValidator).verifyIncomingMessage(TEST_MESSAGE);
+ tracker.getMessageCallback().onMessageReceived(TEST_MESSAGE);
+ verify(mDelegateMessageCallback).onMessageReceived(TEST_MESSAGE);
+
+ doThrow(new RemoteException()).when(mDelegateMessageCallback).onMessageReceived(any());
+ tracker.getMessageCallback().onMessageReceived(TEST_MESSAGE);
+ verify(mISipDelegate).notifyMessageReceiveError(TEST_TRANSACTION_ID,
+ SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD);
+
+ doReturn(new ValidationResult(
+ SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD, ""))
+ .when(mTransportSipSessionValidator).verifyIncomingMessage(TEST_MESSAGE);
+ tracker.getMessageCallback().onMessageReceived(TEST_MESSAGE);
+ verify(mISipDelegate, times(2)).notifyMessageReceiveError(TEST_TRANSACTION_ID,
+ SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD);
+ }
+
+ @SmallTest
+ @Test
+ public void testDelegateOnMessageSent() throws Exception {
+ MessageTransportWrapper tracker = createTestMessageTransportWrapper();
+ tracker.openTransport(mISipDelegate, Collections.emptySet(), Collections.emptySet());
+ tracker.getMessageCallback().onMessageSent(TEST_TRANSACTION_ID);
+ verify(mTransportSipSessionValidator).acknowledgePendingMessage(TEST_TRANSACTION_ID);
+ verify(mDelegateMessageCallback).onMessageSent(TEST_TRANSACTION_ID);
+ }
+
+ @SmallTest
+ @Test
+ public void testDelegateOnMessageSendFailure() throws Exception {
+ MessageTransportWrapper tracker = createTestMessageTransportWrapper();
+ tracker.openTransport(mISipDelegate, Collections.emptySet(), Collections.emptySet());
+ tracker.getMessageCallback().onMessageSendFailure(TEST_TRANSACTION_ID,
+ SipDelegateManager.MESSAGE_FAILURE_REASON_NETWORK_NOT_AVAILABLE);
+ verify(mTransportSipSessionValidator).notifyPendingMessageFailed(TEST_TRANSACTION_ID);
+ verify(mDelegateMessageCallback).onMessageSendFailure(TEST_TRANSACTION_ID,
+ SipDelegateManager.MESSAGE_FAILURE_REASON_NETWORK_NOT_AVAILABLE);
+ }
+
+ private MessageTransportWrapper createTestMessageTransportWrapper() {
+ return new MessageTransportWrapper(TEST_SUB_ID,
+ mExecutor, mDelegateMessageCallback, mTransportSipSessionValidator);
+ }
+
+ private Consumer<Set<String>> trackerRegStateChanged(MessageTransportWrapper tracker,
+ DelegateRegistrationState state) {
+ ArrayList<Consumer<Set<String>>> consumerCaptor = new ArrayList<>(1);
+ Mockito.doAnswer(it -> {
+ // Capture the consumer here.
+ consumerCaptor.add(it.getArgument(0));
+ return null;
+ }).when(mTransportSipSessionValidator).onRegistrationStateChanged(any(), eq(state));
+ tracker.onRegistrationStateChanged(state);
+ verify(mTransportSipSessionValidator).onRegistrationStateChanged(any(), eq(state));
+ assertFalse(consumerCaptor.isEmpty());
+ return consumerCaptor.get(0);
+ }
+
+ private Consumer<Set<String>> closeTrackerGracefully(MessageTransportWrapper tracker,
+ int closingReason, int closedReason, Consumer<Boolean> resultConsumer) {
+ ArrayList<Consumer<Set<String>>> consumerCaptor = new ArrayList<>(1);
+ Mockito.doAnswer(it -> {
+ // Capture the consumer here.
+ consumerCaptor.add(it.getArgument(0));
+ return null;
+ }).when(mTransportSipSessionValidator).closeSessionsGracefully(any(), eq(closingReason),
+ eq(closedReason));
+ tracker.closeGracefully(closingReason, closedReason, resultConsumer);
+ verify(mTransportSipSessionValidator).closeSessionsGracefully(any(), eq(closingReason),
+ eq(closedReason));
+ assertFalse(consumerCaptor.isEmpty());
+ return consumerCaptor.get(0);
+ }
+}
diff --git a/tests/src/com/android/services/telephony/rcs/RcsFeatureControllerTest.java b/tests/src/com/android/services/telephony/rcs/RcsFeatureControllerTest.java
index fbb270d..da614fc 100644
--- a/tests/src/com/android/services/telephony/rcs/RcsFeatureControllerTest.java
+++ b/tests/src/com/android/services/telephony/rcs/RcsFeatureControllerTest.java
@@ -31,6 +31,7 @@
import android.telephony.AccessNetworkConstants;
import android.telephony.ims.ImsException;
import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
import android.telephony.ims.RegistrationManager;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.IImsRegistrationCallback;
@@ -57,6 +58,8 @@
@RunWith(AndroidJUnit4.class)
public class RcsFeatureControllerTest extends TelephonyTestBase {
+ private static final int TEST_SUB_ID = 1;
+
private static final ImsReasonInfo REASON_DISCONNECTED = new ImsReasonInfo(
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN, 0, "test");
@@ -95,12 +98,13 @@
// Connect the RcsFeatureManager
mConnectorListener.getValue().connectionReady(mFeatureManager);
- verify(mFeatureManager).updateCapabilities();
+ verify(mFeatureManager).updateCapabilities(TEST_SUB_ID);
verify(mFeatureManager).registerImsRegistrationCallback(any());
verify(mMockFeature).onRcsConnected(mFeatureManager);
// Disconnect
- mConnectorListener.getValue().connectionUnavailable();
+ mConnectorListener.getValue().connectionUnavailable(
+ FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
verify(mFeatureManager).unregisterImsRegistrationCallback(any());
verify(mMockFeature, times(2)).onRcsDisconnected();
@@ -130,29 +134,31 @@
mConnectorListener.getValue().connectionReady(mFeatureManager);
try {
- controller.registerImsRegistrationCallback(0 /*subId*/, regCb);
- controller.registerRcsAvailabilityCallback(0 /*subId*/, capCb);
+ controller.registerImsRegistrationCallback(TEST_SUB_ID, regCb);
+ controller.registerRcsAvailabilityCallback(TEST_SUB_ID, capCb);
controller.isCapable(RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE,
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
- controller.isAvailable(RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE);
+ controller.isAvailable(RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE,
+ ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
controller.getRegistrationTech(integer -> {
});
- verify(mFeatureManager).registerImsRegistrationCallback(0, regCb);
- verify(mFeatureManager).registerRcsAvailabilityCallback(0, capCb);
+ verify(mFeatureManager).registerImsRegistrationCallback(TEST_SUB_ID, regCb);
+ verify(mFeatureManager).registerRcsAvailabilityCallback(TEST_SUB_ID, capCb);
verify(mFeatureManager).isCapable(
RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE,
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
verify(mFeatureManager).isAvailable(
- RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE);
+ RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE,
+ ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
verify(mFeatureManager).getImsRegistrationTech(any());
} catch (ImsException e) {
fail("ImsException not expected.");
}
- controller.unregisterImsRegistrationCallback(0, regCb);
- controller.unregisterRcsAvailabilityCallback(0, capCb);
- verify(mFeatureManager).unregisterImsRegistrationCallback(0, regCb);
- verify(mFeatureManager).unregisterRcsAvailabilityCallback(0, capCb);
+ controller.unregisterImsRegistrationCallback(TEST_SUB_ID, regCb);
+ controller.unregisterRcsAvailabilityCallback(TEST_SUB_ID, capCb);
+ verify(mFeatureManager).unregisterImsRegistrationCallback(TEST_SUB_ID, regCb);
+ verify(mFeatureManager).unregisterRcsAvailabilityCallback(TEST_SUB_ID, capCb);
}
@Test
@@ -172,7 +178,9 @@
});
verify(mRegistrationCallback).handleImsUnregistered(REASON_DISCONNECTED);
- captor.getValue().onRegistering(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+ ImsRegistrationAttributes attr = new ImsRegistrationAttributes.Builder(
+ ImsRegistrationImplBase.REGISTRATION_TECH_LTE).build();
+ captor.getValue().onRegistering(attr);
controller.getRegistrationState(result -> {
assertNotNull(result);
assertEquals(RegistrationManager.REGISTRATION_STATE_REGISTERING, result.intValue());
@@ -180,7 +188,7 @@
verify(mRegistrationCallback).handleImsRegistering(
AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
- captor.getValue().onRegistered(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+ captor.getValue().onRegistered(attr);
controller.getRegistrationState(result -> {
assertNotNull(result);
assertEquals(RegistrationManager.REGISTRATION_STATE_REGISTERED, result.intValue());
@@ -193,7 +201,8 @@
public void testFeatureManagerDisconnectedAddFeature() {
RcsFeatureController controller = createFeatureController();
// Disconnect the RcsFeatureManager
- mConnectorListener.getValue().connectionUnavailable();
+ mConnectorListener.getValue().connectionUnavailable(
+ FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
controller.addFeature(mMockFeature, RcsFeatureController.Feature.class);
verify(mMockFeature).onRcsDisconnected();
@@ -205,16 +214,17 @@
IImsRegistrationCallback regCb = mock(IImsRegistrationCallback.class);
IImsCapabilityCallback capCb = mock(IImsCapabilityCallback.class);
// Disconnect the RcsFeatureManager
- mConnectorListener.getValue().connectionUnavailable();
+ mConnectorListener.getValue().connectionUnavailable(
+ FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
try {
- controller.registerImsRegistrationCallback(0 /*subId*/, null /*callback*/);
+ controller.registerImsRegistrationCallback(TEST_SUB_ID, null /*callback*/);
fail("ImsException expected for IMS registration.");
} catch (ImsException e) {
//expected
}
try {
- controller.registerRcsAvailabilityCallback(0 /*subId*/, null /*callback*/);
+ controller.registerRcsAvailabilityCallback(TEST_SUB_ID, null /*callback*/);
fail("ImsException expected for availability");
} catch (ImsException e) {
//expected
@@ -227,7 +237,8 @@
//expected
}
try {
- controller.isAvailable(RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE);
+ controller.isAvailable(RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE,
+ ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
fail("ImsException expected for availability check");
} catch (ImsException e) {
//expected
@@ -236,10 +247,25 @@
assertNotNull(integer);
assertEquals(ImsRegistrationImplBase.REGISTRATION_TECH_NONE, integer.intValue());
});
- controller.unregisterImsRegistrationCallback(0, regCb);
- controller.unregisterRcsAvailabilityCallback(0, capCb);
- verify(mFeatureManager, never()).unregisterImsRegistrationCallback(0, regCb);
- verify(mFeatureManager, never()).unregisterRcsAvailabilityCallback(0, capCb);
+ controller.unregisterImsRegistrationCallback(TEST_SUB_ID, regCb);
+ controller.unregisterRcsAvailabilityCallback(TEST_SUB_ID, capCb);
+ verify(mFeatureManager, never()).unregisterImsRegistrationCallback(TEST_SUB_ID, regCb);
+ verify(mFeatureManager, never()).unregisterRcsAvailabilityCallback(TEST_SUB_ID, capCb);
+ }
+
+ @Test
+ public void testCarrierConfigChanged() throws Exception {
+ RcsFeatureController controller = createFeatureController();
+ // Connect the RcsFeatureManager
+ mConnectorListener.getValue().connectionReady(mFeatureManager);
+ verify(mFeatureManager).updateCapabilities(TEST_SUB_ID);
+ controller.addFeature(mMockFeature, RcsFeatureController.Feature.class);
+
+ controller.onCarrierConfigChangedForSubscription();
+
+ verify(mFeatureManager, times(2)).updateCapabilities(TEST_SUB_ID);
+ verify(mMockFeature).onCarrierConfigChanged();
+ verify(mMockFeature, never()).onAssociatedSubscriptionUpdated(anyInt());
}
@Test
@@ -247,13 +273,13 @@
RcsFeatureController controller = createFeatureController();
// Connect the RcsFeatureManager
mConnectorListener.getValue().connectionReady(mFeatureManager);
- verify(mFeatureManager).updateCapabilities();
+ verify(mFeatureManager).updateCapabilities(TEST_SUB_ID);
controller.addFeature(mMockFeature, RcsFeatureController.Feature.class);
- controller.updateAssociatedSubscription(1 /*new sub id*/);
+ controller.updateAssociatedSubscription(2 /*new subId*/);
- verify(mFeatureManager, times(2)).updateCapabilities();
- verify(mMockFeature).onAssociatedSubscriptionUpdated(1 /*new sub id*/);
+ verify(mFeatureManager).updateCapabilities(2 /*new subId*/);
+ verify(mMockFeature).onAssociatedSubscriptionUpdated(2 /*new subId*/);
}
@Test
@@ -272,7 +298,7 @@
private RcsFeatureController createFeatureController() {
RcsFeatureController controller = new RcsFeatureController(mContext, 0 /*slotId*/,
- mRegistrationFactory);
+ TEST_SUB_ID, mRegistrationFactory);
controller.setFeatureConnectorFactory(mFeatureFactory);
doReturn(mFeatureConnector).when(mFeatureFactory).create(any(), anyInt(),
mConnectorListener.capture(), any(), any());
diff --git a/tests/src/com/android/services/telephony/rcs/SipDelegateBinderConnectionTest.java b/tests/src/com/android/services/telephony/rcs/SipDelegateBinderConnectionTest.java
new file mode 100644
index 0000000..3d8e94b
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/SipDelegateBinderConnectionTest.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2020 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.services.telephony.rcs;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
+
+import android.net.InetAddresses;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.telephony.ims.DelegateRegistrationState;
+import android.telephony.ims.DelegateRequest;
+import android.telephony.ims.FeatureTagState;
+import android.telephony.ims.SipDelegateConfiguration;
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.aidl.IImsRegistration;
+import android.telephony.ims.aidl.ISipDelegate;
+import android.telephony.ims.aidl.ISipDelegateMessageCallback;
+import android.telephony.ims.aidl.ISipDelegateStateCallback;
+import android.telephony.ims.aidl.ISipTransport;
+import android.util.ArraySet;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+@RunWith(AndroidJUnit4.class)
+public class SipDelegateBinderConnectionTest extends TelephonyTestBase {
+ private static final int TEST_SUB_ID = 1;
+
+ @Mock private ISipDelegate mMockDelegate;
+ @Mock private ISipTransport mMockTransport;
+ @Mock private IImsRegistration mMockRegistration;
+ @Mock private IBinder mTransportBinder;
+ @Mock private ISipDelegateMessageCallback mMessageCallback;
+ @Mock private DelegateBinderStateManager.StateCallback mMockStateCallback;
+ @Mock private BiConsumer<ISipDelegate, Set<FeatureTagState>> mMockCreatedCallback;
+ @Mock private Consumer<Integer> mMockDestroyedCallback;
+
+ private ArrayList<SipDelegateBinderConnection.StateCallback> mStateCallbackList;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ doReturn(mTransportBinder).when(mMockTransport).asBinder();
+ mStateCallbackList = new ArrayList<>(1);
+ mStateCallbackList.add(mMockStateCallback);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @SmallTest
+ @Test
+ public void testBaseImpl() throws Exception {
+ DelegateBinderStateManager baseConnection = new SipDelegateBinderConnectionStub(
+ getMmTelDeniedTag(), Runnable::run, mStateCallbackList);
+
+ baseConnection.create(null /*message cb*/, mMockCreatedCallback);
+ // Verify the stub simulates onCreated + on registration state callback.
+ verify(mMockCreatedCallback).accept(any(), eq(getMmTelDeniedTag()));
+ verify(mMockStateCallback).onRegistrationStateChanged(
+ new DelegateRegistrationState.Builder().build());
+
+ // Verify onDestroyed is called correctly.
+ baseConnection.destroy(SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP,
+ mMockDestroyedCallback);
+ verify(mMockDestroyedCallback).accept(
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ }
+
+ @SmallTest
+ @Test
+ public void testCreateConnection() throws Exception {
+ DelegateRequest request = getDelegateRequest();
+ ArraySet<FeatureTagState> deniedTags = getMmTelDeniedTag();
+ SipDelegateBinderConnection connection = new SipDelegateBinderConnection(TEST_SUB_ID,
+ mMockTransport, mMockRegistration, request, deniedTags, Runnable::run,
+ mStateCallbackList);
+ ISipDelegateStateCallback cb = createDelegateCaptureStateCallback(request, connection);
+
+ // Send onCreated callback from SipDelegate
+ ArrayList<FeatureTagState> delegateDeniedTags = new ArrayList<>(1);
+ delegateDeniedTags.add(new FeatureTagState(ImsSignallingUtils.GROUP_CHAT_TAG,
+ SipDelegateManager.DENIED_REASON_IN_USE_BY_ANOTHER_DELEGATE));
+ assertNotNull(cb);
+ cb.onCreated(mMockDelegate, delegateDeniedTags);
+
+ ArraySet<FeatureTagState> totalDeniedTags = new ArraySet<>(deniedTags);
+ // Add the tags denied by the controller as well.
+ totalDeniedTags.addAll(delegateDeniedTags);
+ // The callback should contain the controller and delegate denied tags in the callback.
+ verify(mMockCreatedCallback).accept(mMockDelegate, totalDeniedTags);
+ }
+
+ @SmallTest
+ @Test
+ public void testCreateConnectionServiceDead() throws Exception {
+ DelegateRequest request = getDelegateRequest();
+ ArraySet<FeatureTagState> deniedTags = getMmTelDeniedTag();
+ SipDelegateBinderConnection connection = new SipDelegateBinderConnection(TEST_SUB_ID,
+ mMockTransport, mMockRegistration, request, deniedTags, Runnable::run,
+ mStateCallbackList);
+ doThrow(new RemoteException()).when(mMockTransport).createSipDelegate(eq(TEST_SUB_ID),
+ any(), any(), any());
+ ISipDelegateStateCallback cb = createDelegateCaptureStateCallback(request, connection);
+ assertNull(cb);
+ }
+
+ @SmallTest
+ @Test
+ public void testDestroyConnection() throws Exception {
+ DelegateRequest request = getDelegateRequest();
+ ArraySet<FeatureTagState> deniedTags = getMmTelDeniedTag();
+ SipDelegateBinderConnection connection = new SipDelegateBinderConnection(TEST_SUB_ID,
+ mMockTransport, mMockRegistration, request, deniedTags, Runnable::run,
+ mStateCallbackList);
+ ISipDelegateStateCallback cb = createDelegateCaptureStateCallback(request, connection);
+ assertNotNull(cb);
+ cb.onCreated(mMockDelegate, null /*denied*/);
+ verify(mMockCreatedCallback).accept(mMockDelegate, deniedTags);
+
+ // call Destroy on the SipDelegate
+ destroy(connection, SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ cb.onDestroyed(SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ verify(mMockDestroyedCallback).accept(
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ }
+
+ @SmallTest
+ @Test
+ public void testDestroyConnectionDead() throws Exception {
+ DelegateRequest request = getDelegateRequest();
+ ArraySet<FeatureTagState> deniedTags = getMmTelDeniedTag();
+ SipDelegateBinderConnection connection = new SipDelegateBinderConnection(TEST_SUB_ID,
+ mMockTransport, mMockRegistration, request, deniedTags, Runnable::run,
+ mStateCallbackList);
+ ISipDelegateStateCallback cb = createDelegateCaptureStateCallback(request, connection);
+ assertNotNull(cb);
+ cb.onCreated(mMockDelegate, null /*denied*/);
+ verify(mMockCreatedCallback).accept(mMockDelegate, deniedTags);
+
+ // try to destroy when dead and ensure callback is still called.
+ doThrow(new RemoteException()).when(mMockTransport).destroySipDelegate(any(), anyInt());
+ destroy(connection, SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ verify(mMockDestroyedCallback).accept(
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ }
+
+ @SmallTest
+ @Test
+ public void testStateCallback() throws Exception {
+ DelegateRequest request = getDelegateRequest();
+ ArraySet<FeatureTagState> deniedTags = getMmTelDeniedTag();
+ SipDelegateBinderConnection connection = new SipDelegateBinderConnection(TEST_SUB_ID,
+ mMockTransport, mMockRegistration, request, deniedTags, Runnable::run,
+ mStateCallbackList);
+ ISipDelegateStateCallback cb = createDelegateCaptureStateCallback(request, connection);
+ assertNotNull(cb);
+ cb.onCreated(mMockDelegate, new ArrayList<>(deniedTags));
+ verify(mMockCreatedCallback).accept(mMockDelegate, deniedTags);
+
+ InetSocketAddress localAddr = new InetSocketAddress(
+ InetAddresses.parseNumericAddress("1.1.1.1"), 80);
+ InetSocketAddress serverAddr = new InetSocketAddress(
+ InetAddresses.parseNumericAddress("2.2.2.2"), 81);
+ SipDelegateConfiguration c = new SipDelegateConfiguration.Builder(1,
+ SipDelegateConfiguration.SIP_TRANSPORT_TCP, localAddr, serverAddr).build();
+ cb.onConfigurationChanged(c);
+ verify(mMockStateCallback).onConfigurationChanged(c);
+
+ DelegateRegistrationState regState = new DelegateRegistrationState.Builder()
+ .addRegisteredFeatureTags(request.getFeatureTags()).build();
+ cb.onFeatureTagRegistrationChanged(regState);
+ verify(mMockStateCallback).onRegistrationStateChanged(regState);
+ }
+
+ private ISipDelegateStateCallback createDelegateCaptureStateCallback(
+ DelegateRequest r, SipDelegateBinderConnection c) throws Exception {
+ boolean isCreating = c.create(mMessageCallback, mMockCreatedCallback);
+ if (!isCreating) return null;
+ ArgumentCaptor<ISipDelegateStateCallback> stateCaptor =
+ ArgumentCaptor.forClass(ISipDelegateStateCallback.class);
+ verify(mMockTransport).createSipDelegate(eq(TEST_SUB_ID), eq(r), stateCaptor.capture(),
+ eq(mMessageCallback));
+ assertNotNull(stateCaptor.getValue());
+ return stateCaptor.getValue();
+ }
+
+ private void destroy(SipDelegateBinderConnection c, int reason) throws Exception {
+ c.destroy(reason, mMockDestroyedCallback);
+ verify(mMockTransport).destroySipDelegate(mMockDelegate, reason);
+ }
+
+ private DelegateRequest getDelegateRequest() {
+ ArraySet<String> featureTags = new ArraySet<>(2);
+ featureTags.add(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG);
+ featureTags.add(ImsSignallingUtils.GROUP_CHAT_TAG);
+ return new DelegateRequest(featureTags);
+ }
+
+ private ArraySet<FeatureTagState> getMmTelDeniedTag() {
+ ArraySet<FeatureTagState> deniedTags = new ArraySet<>();
+ deniedTags.add(new FeatureTagState(ImsSignallingUtils.MMTEL_TAG,
+ SipDelegateManager.DENIED_REASON_NOT_ALLOWED));
+ return deniedTags;
+ }
+}
diff --git a/tests/src/com/android/services/telephony/rcs/SipDelegateControllerTest.java b/tests/src/com/android/services/telephony/rcs/SipDelegateControllerTest.java
new file mode 100644
index 0000000..5b0e7c5
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/SipDelegateControllerTest.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2020 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.services.telephony.rcs;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.telephony.ims.DelegateRegistrationState;
+import android.telephony.ims.DelegateRequest;
+import android.telephony.ims.FeatureTagState;
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.aidl.ISipDelegate;
+import android.telephony.ims.aidl.ISipDelegateMessageCallback;
+import android.util.ArraySet;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+import com.android.TestExecutorService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+@RunWith(AndroidJUnit4.class)
+public class SipDelegateControllerTest extends TelephonyTestBase {
+ private static final int TEST_SUB_ID = 1;
+
+ @Mock private ISipDelegate mMockSipDelegate;
+ @Mock private MessageTransportWrapper mMockMessageTracker;
+ @Mock private ISipDelegateMessageCallback mMockMessageCallback;
+ @Mock private DelegateStateTracker mMockDelegateStateTracker;
+ @Mock private DelegateBinderStateManager mMockBinderConnection;
+ @Captor private ArgumentCaptor<BiConsumer<ISipDelegate, Set<FeatureTagState>>> mCreatedCaptor;
+ @Captor private ArgumentCaptor<Consumer<Boolean>> mBooleanConsumerCaptor;
+ @Captor private ArgumentCaptor<Consumer<Integer>> mIntegerConsumerCaptor;
+
+ private ScheduledExecutorService mExecutorService;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(mMockMessageTracker.getMessageCallback()).thenReturn(mMockMessageCallback);
+ mExecutorService = new TestExecutorService();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mExecutorService.shutdownNow();
+ super.tearDown();
+ }
+
+ @SmallTest
+ @Test
+ public void testCreateDelegate() throws Exception {
+ DelegateRequest request = getBaseDelegateRequest();
+ SipDelegateController controller = getTestDelegateController(request,
+ Collections.emptySet());
+
+ doReturn(true).when(mMockBinderConnection).create(eq(mMockMessageCallback), any());
+ CompletableFuture<Boolean> future = controller.create(request.getFeatureTags(),
+ Collections.emptySet() /*denied tags*/);
+ BiConsumer<ISipDelegate, Set<FeatureTagState>> consumer =
+ verifyConnectionCreated(1);
+ assertNotNull(consumer);
+
+ assertFalse(future.isDone());
+ consumer.accept(mMockSipDelegate, Collections.emptySet());
+ assertTrue(future.get());
+ verify(mMockMessageTracker).openTransport(mMockSipDelegate, request.getFeatureTags(),
+ Collections.emptySet());
+ verify(mMockDelegateStateTracker).sipDelegateConnected(Collections.emptySet());
+ }
+
+ @SmallTest
+ @Test
+ public void testCreateDeniedFeatures() throws Exception {
+ DelegateRequest request = getLargeDelegateRequest();
+ ArraySet<FeatureTagState> deniedTags = new ArraySet<>(1);
+ deniedTags.add(new FeatureTagState(ImsSignallingUtils.GROUP_CHAT_TAG,
+ SipDelegateManager.DENIED_REASON_NOT_ALLOWED));
+ SipDelegateController controller = getTestDelegateController(request,
+ deniedTags);
+
+ doReturn(true).when(mMockBinderConnection).create(eq(mMockMessageCallback), any());
+ CompletableFuture<Boolean> future = controller.create(request.getFeatureTags(),
+ deniedTags);
+ BiConsumer<ISipDelegate, Set<FeatureTagState>> consumer =
+ verifyConnectionCreated(1);
+ assertNotNull(consumer);
+
+ assertFalse(future.isDone());
+ // Send in additional tags denied by the service
+ deniedTags.add(new FeatureTagState(ImsSignallingUtils.FILE_TRANSFER_HTTP_TAG,
+ SipDelegateManager.DENIED_REASON_NOT_ALLOWED));
+ consumer.accept(mMockSipDelegate, deniedTags);
+ assertTrue(future.get());
+ // Allowed tags should be initial request set - denied tags
+ ArraySet<String> allowedTags = new ArraySet<>(request.getFeatureTags());
+ allowedTags.removeAll(deniedTags.stream().map(FeatureTagState::getFeatureTag)
+ .collect(Collectors.toSet()));
+ verify(mMockMessageTracker).openTransport(mMockSipDelegate, allowedTags, deniedTags);
+ verify(mMockDelegateStateTracker).sipDelegateConnected(deniedTags);
+ }
+
+ @SmallTest
+ @Test
+ public void testCreateDelegateTransportDied() throws Exception {
+ DelegateRequest request = getBaseDelegateRequest();
+ SipDelegateController controller = getTestDelegateController(request,
+ Collections.emptySet());
+
+ //Create operation fails
+ doReturn(false).when(mMockBinderConnection).create(eq(mMockMessageCallback), any());
+ CompletableFuture<Boolean> future = controller.create(request.getFeatureTags(),
+ Collections.emptySet() /*denied tags*/);
+
+ assertFalse(future.get());
+ }
+
+ @SmallTest
+ @Test
+ public void testDestroyDelegate() throws Exception {
+ DelegateRequest request = getBaseDelegateRequest();
+ SipDelegateController controller = getTestDelegateController(request,
+ Collections.emptySet());
+ createSipDelegate(request, controller);
+
+ CompletableFuture<Integer> pendingDestroy = controller.destroy(false /*force*/,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ assertFalse(pendingDestroy.isDone());
+ Consumer<Boolean> pendingClosedConsumer = verifyMessageTrackerCloseGracefully();
+ verify(mMockDelegateStateTracker).sipDelegateChanging(
+ DelegateRegistrationState.DEREGISTERING_REASON_DESTROY_PENDING);
+
+ // verify we do not call destroy on the delegate until the message tracker releases the
+ // transport.
+ verify(mMockBinderConnection, never()).destroy(anyInt(), any());
+ pendingClosedConsumer.accept(true);
+ Consumer<Integer> pendingDestroyedConsumer = verifyBinderConnectionDestroy();
+ pendingDestroyedConsumer.accept(
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ verify(mMockDelegateStateTracker).sipDelegateDestroyed(
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ assertTrue(pendingDestroy.isDone());
+ assertEquals(SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP,
+ pendingDestroy.get().intValue());
+ }
+
+ @SmallTest
+ @Test
+ public void testDestroyDelegateForce() throws Exception {
+ DelegateRequest request = getBaseDelegateRequest();
+ SipDelegateController controller = getTestDelegateController(request,
+ Collections.emptySet());
+ createSipDelegate(request, controller);
+
+ CompletableFuture<Integer> pendingDestroy = controller.destroy(true /*force*/,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ assertFalse(pendingDestroy.isDone());
+ // Do not wait for message transport close in this case.
+ verify(mMockMessageTracker).close(
+ SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED);
+ verify(mMockDelegateStateTracker, never()).sipDelegateChanging(
+ DelegateRegistrationState.DEREGISTERING_REASON_DESTROY_PENDING);
+
+ //verify destroy is called
+ Consumer<Integer> pendingDestroyedConsumer = verifyBinderConnectionDestroy();
+ pendingDestroyedConsumer.accept(
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ verify(mMockDelegateStateTracker).sipDelegateDestroyed(
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ assertTrue(pendingDestroy.isDone());
+ assertEquals(SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP,
+ pendingDestroy.get().intValue());
+ }
+
+ @SmallTest
+ @Test
+ public void testChangeSupportedFeatures() throws Exception {
+ DelegateRequest request = getBaseDelegateRequest();
+ SipDelegateController controller = getTestDelegateController(request,
+ Collections.emptySet());
+ createSipDelegate(request, controller);
+
+ Set<String> newFts = getBaseFTSet();
+ newFts.add(ImsSignallingUtils.GROUP_CHAT_TAG);
+ CompletableFuture<Boolean> pendingChange = controller.changeSupportedFeatureTags(
+ newFts, Collections.emptySet());
+ assertFalse(pendingChange.isDone());
+ // message tracker should close gracefully.
+ Consumer<Boolean> pendingClosedConsumer = verifyMessageTrackerCloseGracefully();
+ verify(mMockDelegateStateTracker).sipDelegateChanging(
+ DelegateRegistrationState.DEREGISTERING_REASON_FEATURE_TAGS_CHANGING);
+ verify(mMockBinderConnection, never()).destroy(anyInt(), any());
+ pendingClosedConsumer.accept(true);
+ Consumer<Integer> pendingDestroyedConsumer = verifyBinderConnectionDestroy();
+ pendingDestroyedConsumer.accept(
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ verify(mMockDelegateStateTracker, never()).sipDelegateDestroyed(anyInt());
+
+ // This will cause any exceptions to be printed if something completed exceptionally.
+ assertNull(pendingChange.getNow(null));
+ BiConsumer<ISipDelegate, Set<FeatureTagState>> consumer =
+ verifyConnectionCreated(2);
+ assertNotNull(consumer);
+ consumer.accept(mMockSipDelegate, Collections.emptySet());
+ assertTrue(pendingChange.get());
+
+ verify(mMockMessageTracker).openTransport(mMockSipDelegate, request.getFeatureTags(),
+ Collections.emptySet());
+ verify(mMockMessageTracker).openTransport(mMockSipDelegate, newFts,
+ Collections.emptySet());
+ verify(mMockDelegateStateTracker, times(2)).sipDelegateConnected(Collections.emptySet());
+ }
+
+ private void createSipDelegate(DelegateRequest request, SipDelegateController controller)
+ throws Exception {
+ doReturn(true).when(mMockBinderConnection).create(eq(mMockMessageCallback), any());
+ CompletableFuture<Boolean> future = controller.create(request.getFeatureTags(),
+ Collections.emptySet() /*denied tags*/);
+ BiConsumer<ISipDelegate, Set<FeatureTagState>> consumer =
+ verifyConnectionCreated(1);
+ assertNotNull(consumer);
+ consumer.accept(mMockSipDelegate, Collections.emptySet());
+ assertTrue(future.get());
+ }
+
+ private ArraySet<String> getBaseFTSet() {
+ ArraySet<String> request = new ArraySet<>();
+ request.add(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG);
+ return request;
+ }
+
+ private ArraySet<String> getLargeFTSet() {
+ ArraySet<String> request = new ArraySet<>();
+ request.add(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG);
+ request.add(ImsSignallingUtils.GROUP_CHAT_TAG);
+ request.add(ImsSignallingUtils.FILE_TRANSFER_HTTP_TAG);
+ return request;
+ }
+
+ private DelegateRequest getBaseDelegateRequest() {
+ return new DelegateRequest(getBaseFTSet());
+ }
+
+ private DelegateRequest getLargeDelegateRequest() {
+ return new DelegateRequest(getLargeFTSet());
+ }
+
+ private SipDelegateController getTestDelegateController(DelegateRequest request,
+ Set<FeatureTagState> deniedSet) {
+ return new SipDelegateController(TEST_SUB_ID, request, "", mExecutorService,
+ mMockMessageTracker, mMockDelegateStateTracker,
+ (a, b, deniedFeatureSet, d, e) -> {
+ assertEquals(deniedSet, deniedFeatureSet);
+ return mMockBinderConnection;
+ });
+ }
+
+ private BiConsumer<ISipDelegate, Set<FeatureTagState>> verifyConnectionCreated(int numTimes) {
+ verify(mMockBinderConnection, times(numTimes)).create(eq(mMockMessageCallback),
+ mCreatedCaptor.capture());
+ return mCreatedCaptor.getValue();
+ }
+
+ private Consumer<Boolean> verifyMessageTrackerCloseGracefully() {
+ verify(mMockMessageTracker).closeGracefully(anyInt(), anyInt(),
+ mBooleanConsumerCaptor.capture());
+ return mBooleanConsumerCaptor.getValue();
+ }
+ private Consumer<Integer> verifyBinderConnectionDestroy() {
+ verify(mMockBinderConnection).destroy(anyInt(), mIntegerConsumerCaptor.capture());
+ return mIntegerConsumerCaptor.getValue();
+ }
+
+}
diff --git a/tests/src/com/android/services/telephony/rcs/SipDialogTest.java b/tests/src/com/android/services/telephony/rcs/SipDialogTest.java
new file mode 100644
index 0000000..17830c5
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/SipDialogTest.java
@@ -0,0 +1,209 @@
+/*
+ * 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.services.telephony.rcs;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import android.telephony.ims.SipMessage;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SipDialogTest {
+
+ private static final String SIP_URI_ALICE = "sip:alice@client.example.com";
+ private static final String BASE_CONTACT_URI_ALICE = "Alice <" + SIP_URI_ALICE + ">";
+ private static final String SIP_URI_BOB = "sip:bob@client.example.com";
+ private static final String BASE_CONTACT_URI_BOB = "Bob <" + SIP_URI_BOB + ">";
+
+ @Test
+ public void testCreateEarlyDialog() {
+ String branchId = "testBranchId";
+ String fromTag = "abcd";
+ String callId = "testCallId";
+ SipMessage inviteRequest = SipMessageUtils.generateSipRequest(
+ SipMessageUtils.INVITE_SIP_METHOD,
+ BASE_CONTACT_URI_ALICE, BASE_CONTACT_URI_BOB, SIP_URI_BOB, branchId, callId,
+ fromTag, null /*toTag*/);
+ SipDialog dialog = SipDialog.fromSipMessage(inviteRequest);
+ assertNotNull(dialog);
+ assertEquals(SipDialog.STATE_EARLY, dialog.getState());
+ assertEquals(callId, dialog.getCallId());
+ assertNull(dialog.getToTag());
+ // receive an earlyResponse
+ String toTag = "testToTag";
+ dialog.earlyResponse(toTag);
+ assertEquals(toTag, dialog.getToTag());
+ }
+
+ @Test
+ public void testIsResponseAssociated() {
+ String branchId = "testBranchId";
+ String fromTag = "abcd";
+ String callId = "testCallId";
+ SipMessage inviteRequest = SipMessageUtils.generateSipRequest(
+ SipMessageUtils.INVITE_SIP_METHOD,
+ BASE_CONTACT_URI_ALICE, BASE_CONTACT_URI_BOB, SIP_URI_BOB, branchId, callId,
+ fromTag, null /*toTag*/);
+ SipDialog dialog = SipDialog.fromSipMessage(inviteRequest);
+ assertNotNull(dialog);
+
+ // A response with no to tag should match
+ SipMessage inviteTrying = SipMessageUtils.generateSipResponse("100", "Trying",
+ BASE_CONTACT_URI_ALICE, BASE_CONTACT_URI_BOB, branchId, callId, fromTag,
+ null /*toTag*/);
+ assertTrue(dialog.isResponseAssociatedWithDialog(inviteTrying));
+ // A response with a different to tag should match
+ inviteTrying = SipMessageUtils.generateSipResponse("100", "Trying",
+ BASE_CONTACT_URI_ALICE, BASE_CONTACT_URI_BOB, branchId, callId, fromTag,
+ "testToTag");
+ assertTrue(dialog.isResponseAssociatedWithDialog(inviteTrying));
+ // A response with a different from tag shouldn't match.
+ String fromTag2 = "testFromTag2";
+ inviteTrying = SipMessageUtils.generateSipResponse("100", "Trying",
+ BASE_CONTACT_URI_ALICE, BASE_CONTACT_URI_BOB, branchId, callId, fromTag2,
+ null /*toTag*/);
+ assertFalse(dialog.isResponseAssociatedWithDialog(inviteTrying));
+ // A response with a different branch ID shouldn't match.
+ String branchId2 = "testBranchId2";
+ inviteTrying = SipMessageUtils.generateSipResponse("100", "Trying",
+ BASE_CONTACT_URI_ALICE, BASE_CONTACT_URI_BOB, branchId2, callId, fromTag,
+ null /*toTag*/);
+ assertFalse(dialog.isResponseAssociatedWithDialog(inviteTrying));
+ // A response with a different call id shouldn't match.
+ String callId2 = "testCallId2";
+ inviteTrying = SipMessageUtils.generateSipResponse("100", "Trying",
+ BASE_CONTACT_URI_ALICE, BASE_CONTACT_URI_BOB, branchId, callId2, fromTag,
+ null /*toTag*/);
+ assertFalse(dialog.isResponseAssociatedWithDialog(inviteTrying));
+ }
+
+ @Test
+ public void testFork() {
+ String branchId = "testBranchId";
+ String fromTag = "abcd";
+ String callId = "testCallId";
+ SipMessage inviteRequest = SipMessageUtils.generateSipRequest(
+ SipMessageUtils.INVITE_SIP_METHOD,
+ BASE_CONTACT_URI_ALICE, BASE_CONTACT_URI_BOB, SIP_URI_BOB, branchId, callId,
+ fromTag, null /*toTag*/);
+ SipDialog dialog = SipDialog.fromSipMessage(inviteRequest);
+ assertNotNull(dialog);
+ assertEquals(SipDialog.STATE_EARLY, dialog.getState());
+ assertEquals(callId, dialog.getCallId());
+ // receive an earlyResponse
+ dialog.earlyResponse("testToTag");
+ assertEquals(SipDialog.STATE_EARLY, dialog.getState());
+ // fork dialog
+ SipDialog dialogFork = dialog.forkDialog();
+ assertEquals(SipDialog.STATE_EARLY, dialogFork.getState());
+ assertEquals(callId, dialogFork.getCallId());
+ assertNull(dialogFork.getToTag());
+ }
+
+ @Test
+ public void testConfirmDialog() {
+ String branchId = "testBranchId";
+ String fromTag = "abcd";
+ String callId = "testCallId";
+ SipMessage inviteRequest = SipMessageUtils.generateSipRequest(
+ SipMessageUtils.INVITE_SIP_METHOD,
+ BASE_CONTACT_URI_ALICE, BASE_CONTACT_URI_BOB, SIP_URI_BOB, branchId, callId,
+ fromTag, null /*toTag*/);
+ SipDialog dialog = SipDialog.fromSipMessage(inviteRequest);
+ assertNotNull(dialog);
+ assertEquals(SipDialog.STATE_EARLY, dialog.getState());
+ assertEquals(callId, dialog.getCallId());
+ // receive a confirm response
+ dialog.confirm("testToTag");
+ assertEquals(SipDialog.STATE_CONFIRMED, dialog.getState());
+ assertEquals(callId, dialog.getCallId());
+ }
+
+ @Test
+ public void testIsRequestAssociated() {
+ String branchId = "testBranchId";
+ String fromTag = "testFromTag";
+ String callId = "testCallId";
+ String toTag = "testToTag";
+ SipMessage inviteRequest = SipMessageUtils.generateSipRequest(
+ SipMessageUtils.INVITE_SIP_METHOD,
+ BASE_CONTACT_URI_ALICE, BASE_CONTACT_URI_BOB, SIP_URI_BOB, branchId, callId,
+ fromTag, null /*toTag*/);
+ SipDialog dialog = SipDialog.fromSipMessage(inviteRequest);
+ assertNotNull(dialog);
+ dialog.earlyResponse(toTag);
+
+ SipMessage cancelRequest = SipMessageUtils.generateSipRequest(
+ SipMessageUtils.CANCEL_SIP_METHOD, BASE_CONTACT_URI_ALICE, BASE_CONTACT_URI_BOB,
+ SIP_URI_BOB, branchId, callId, fromTag, toTag);
+ assertTrue(dialog.isRequestAssociatedWithDialog(cancelRequest));
+ // cancel request with no toTag should fail
+ cancelRequest = SipMessageUtils.generateSipRequest(
+ SipMessageUtils.INVITE_SIP_METHOD, BASE_CONTACT_URI_ALICE, BASE_CONTACT_URI_BOB,
+ SIP_URI_BOB, branchId, callId, fromTag, null /*toTag*/);
+ assertFalse(dialog.isRequestAssociatedWithDialog(cancelRequest));
+ // cancel request to a different dialog in the same session should fail
+ String toTag2 = "testToTag2";
+ cancelRequest = SipMessageUtils.generateSipRequest(
+ SipMessageUtils.INVITE_SIP_METHOD, BASE_CONTACT_URI_ALICE, BASE_CONTACT_URI_BOB,
+ SIP_URI_BOB, branchId, callId, fromTag, toTag2);
+ assertFalse(dialog.isRequestAssociatedWithDialog(cancelRequest));
+ // cancel request to a different session should fail (even with the same from/to)
+ String callId2 = "testCallId2";
+ cancelRequest = SipMessageUtils.generateSipRequest(
+ SipMessageUtils.INVITE_SIP_METHOD, BASE_CONTACT_URI_ALICE, BASE_CONTACT_URI_BOB,
+ SIP_URI_BOB, branchId, callId2, fromTag, toTag);
+ assertFalse(dialog.isRequestAssociatedWithDialog(cancelRequest));
+ // Same call id but different from and to (although not really possible) should fail.
+ String fromTag3 = "testFromTag3";
+ String toTag3 = "testToTag3";
+ cancelRequest = SipMessageUtils.generateSipRequest(
+ SipMessageUtils.INVITE_SIP_METHOD, BASE_CONTACT_URI_ALICE, BASE_CONTACT_URI_BOB,
+ SIP_URI_BOB, branchId, callId, fromTag3, toTag3);
+ assertFalse(dialog.isRequestAssociatedWithDialog(cancelRequest));
+ }
+
+ @Test
+ public void testCloseDialog() {
+ String branchId = "testBranchId";
+ String fromTag = "abcd";
+ String callId = "testCallId";
+ SipMessage inviteRequest = SipMessageUtils.generateSipRequest(
+ SipMessageUtils.INVITE_SIP_METHOD,
+ BASE_CONTACT_URI_ALICE, BASE_CONTACT_URI_BOB, SIP_URI_BOB, branchId, callId,
+ fromTag, null /*toTag*/);
+ SipDialog dialog = SipDialog.fromSipMessage(inviteRequest);
+ assertNotNull(dialog);
+ assertEquals(SipDialog.STATE_EARLY, dialog.getState());
+ assertEquals(callId, dialog.getCallId());
+
+ // receive a confirm response
+ dialog.confirm("testToTag");
+ assertEquals(SipDialog.STATE_CONFIRMED, dialog.getState());
+
+ dialog.close();
+ assertEquals(SipDialog.STATE_CLOSED, dialog.getState());
+ }
+}
diff --git a/tests/src/com/android/services/telephony/rcs/SipMessageParsingUtilsTest.java b/tests/src/com/android/services/telephony/rcs/SipMessageParsingUtilsTest.java
new file mode 100644
index 0000000..ff96eb6
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/SipMessageParsingUtilsTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.services.telephony.rcs;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import android.util.ArraySet;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.telephony.SipMessageParsingUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+public class SipMessageParsingUtilsTest {
+
+ @Test
+ public void testNoAcceptContactHeader() {
+ String header = "Via: SIP/2.0/UDP ex.place.com;branch=z9hG4bK776asdhds";
+ assertTrue(SipMessageParsingUtils.getAcceptContactFeatureTags(header).isEmpty());
+ }
+
+ @Test
+ public void testAcceptContactHeaderMultipleValues() {
+ Set<String> testFeatures = new ArraySet<>(2);
+ // "+a="b,c" should be split into +a="b" and +a="c"
+ testFeatures.add("+a=\"b,c\"");
+ testFeatures.add("+d");
+ testFeatures.add("+e=\"f\"");
+ // These non-feature tags should be filtered out.
+ testFeatures.add("video");
+ testFeatures.add("blah=BLAH");
+ String header = "Via: SIP/2.0/UDP ex.place.com;branch=z9hG4bK776asdhds";
+ header = addFeatures(header, testFeatures);
+ Set<String> features = SipMessageParsingUtils.getAcceptContactFeatureTags(header);
+ assertNotNull(features);
+ assertEquals(4, features.size());
+ assertTrue(features.containsAll(Arrays.asList("+a=\"b\"", "+a=\"c\"", "+d", "+e=\"f\"")));
+ }
+
+ private String addFeatures(String headers, Set<String> features) {
+ StringBuilder newHeader = new StringBuilder(headers + "\n");
+ newHeader.append("Accept-Contact:*");
+ for (String feature : features) {
+ newHeader.append(";").append(feature);
+ }
+ return newHeader.toString();
+ }
+}
diff --git a/tests/src/com/android/services/telephony/rcs/SipMessageUtils.java b/tests/src/com/android/services/telephony/rcs/SipMessageUtils.java
new file mode 100644
index 0000000..8ad0f2a
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/SipMessageUtils.java
@@ -0,0 +1,75 @@
+/*
+ * 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.services.telephony.rcs;
+
+import android.telephony.ims.SipMessage;
+
+public class SipMessageUtils {
+
+ public static final String INVITE_SIP_METHOD = "INVITE";
+ public static final String CANCEL_SIP_METHOD = "CANCEL";
+ public static final String BYE_SIP_METHOD = "BYE";
+ public static final String ACK_SIP_METHOD = "ACK";
+ public static final String BASE_ADDRESS = "client.example.com";
+
+ private static final String PARAM_SEPARATOR = ";";
+ private static final String PARAM_KEY_VALUE_SEPARATOR = "=";
+ private static final String BASE_VIA_HEADER_VALUE = "SIP/2.0/TCP " + BASE_ADDRESS + ":5060";
+
+ /**
+ * @return A new SIP request from the given parameters.
+ */
+ public static SipMessage generateSipRequest(String requestMethod, String fromContact,
+ String toContact, String toUri, String branchId, String callId, String fromTag,
+ String toTag) {
+ String header = "Via: " + addParamToHeader(BASE_VIA_HEADER_VALUE, "branch", branchId);
+ header += "\n";
+ header += "From: " + addParamToHeader(fromContact, "tag", fromTag);
+ header += "\n";
+ // To tag is optional
+ header += "To: " + ((toTag != null)
+ ? addParamToHeader(toContact, "tag", toTag) : toContact);
+ header += "\n";
+ header += "Call-ID: " + callId;
+ return new SipMessage(requestMethod + " " + toUri + " SIP/2.0", header, new byte[0]);
+ }
+
+ /**
+ * @return Generates a SIP response.
+ */
+ public static SipMessage generateSipResponse(String statusCode, String statusString,
+ String fromContact, String toContact, String branchId, String callId, String fromTag,
+ String toTag) {
+ String header = "Via: " + addParamToHeader(BASE_VIA_HEADER_VALUE, "branch", branchId);
+ header += "\n";
+ header += "From: " + addParamToHeader(fromContact, "tag", fromTag);
+ header += "\n";
+ // To tag is optional
+ header += "To: " + ((toTag != null)
+ ? addParamToHeader(toContact, "tag", toTag) : toContact);
+ header += "\n";
+ header += "Call-ID: " + callId;
+ return new SipMessage("SIP/2.0 " + statusCode + " " + statusString, header,
+ new byte[0]);
+ }
+
+ private static String addParamToHeader(String headerValue, String paramKey, String paramValue) {
+ headerValue += PARAM_SEPARATOR + paramKey.trim() + PARAM_KEY_VALUE_SEPARATOR
+ + paramValue.trim();
+ return headerValue;
+ }
+}
diff --git a/tests/src/com/android/services/telephony/rcs/SipSessionTrackerTest.java b/tests/src/com/android/services/telephony/rcs/SipSessionTrackerTest.java
new file mode 100644
index 0000000..823a8be
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/SipSessionTrackerTest.java
@@ -0,0 +1,377 @@
+/*
+ * 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.services.telephony.rcs;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import android.net.Uri;
+import android.telephony.ims.SipMessage;
+import android.util.Base64;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@RunWith(AndroidJUnit4.class)
+public class SipSessionTrackerTest {
+
+ private class DialogAttributes {
+ public final String branchId;
+ public final String callId;
+ public final String fromHeader;
+ public final String fromTag;
+ public final String toUri;
+ public final String toHeader;
+ private final String mFromUri;
+ // This may be populated later.
+ public String toTag;
+
+ DialogAttributes() {
+ branchId = getNextString();
+ callId = getNextString();
+ mFromUri = generateRandomSipUri();
+ fromHeader = generateContactUri(mFromUri);
+ fromTag = getNextString();
+ toUri = generateRandomSipUri();
+ toHeader = generateContactUri(toUri);
+ }
+
+ private DialogAttributes(String branchId, String callId, String fromUri,
+ String fromTag, String toUri, String toTag) {
+ this.branchId = branchId;
+ this.callId = callId;
+ this.mFromUri = fromUri;
+ this.fromHeader = generateContactUri(fromUri);
+ this.fromTag = fromTag;
+ this.toUri = toUri;
+ this.toHeader = generateContactUri(toUri);
+ this.toTag = toTag;
+ }
+
+ public void setToTag() {
+ if (toTag == null) {
+ toTag = getNextString();
+ }
+ }
+
+ public DialogAttributes fromExisting() {
+ return new DialogAttributes(branchId, callId, mFromUri, fromTag, toUri, null);
+ }
+
+ public DialogAttributes invertFromTo() {
+ return new DialogAttributes(branchId, callId, toUri, fromTag, mFromUri, toTag);
+ }
+ }
+
+ // Keep track of the string entry so we can generate unique strings.
+ private int mStringEntryCounter = 0;
+ private SipSessionTracker mTrackerUT;
+
+ @Before
+ public void setUp() {
+ mStringEntryCounter = 0;
+ mTrackerUT = new SipSessionTracker();
+ }
+
+ @Test
+ public void testEarlyDialogToConfirmed() {
+ DialogAttributes attr = new DialogAttributes();
+ // INVITE A -> B
+ SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr);
+ filterMessage(inviteRequest, attr);
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr);
+ // 100 TRYING A <- proxy
+ SipMessage inviteTrying = generateSipResponse("100", "Trying", attr);
+ filterMessage(inviteTrying, attr);
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr);
+ // INVITE proxy -> B
+ // (BOB generates To tag)
+ attr.setToTag();
+ // 180 RINGING proxy <- B
+ // 180 RINGING A <- proxy
+ SipMessage inviteRinging = generateSipResponse("180", "Ringing", attr);
+ filterMessage(inviteRinging, attr);
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr);
+ // User answers phone
+ // 200 OK proxy <- B
+ // 200 OK A <- proxy
+ SipMessage inviteConfirm = generateSipResponse("200", "OK", attr);
+ filterMessage(inviteConfirm, attr);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
+ }
+
+ @Test
+ public void testForkDialog() {
+ DialogAttributes attrB1 = new DialogAttributes();
+ // INVITE A -> B
+ SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attrB1);
+ filterMessage(inviteRequest, attrB1);
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attrB1);
+ // INVITE proxy -> B
+ // (BOB generates To tag)
+ attrB1.setToTag();
+ // 180 RINGING proxy <- B1
+ // 180 RINGING A <- proxy
+ SipMessage inviteRingingB1 = generateSipResponse("180", "Ringing", attrB1);
+ filterMessage(inviteRingingB1, attrB1);
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attrB1);
+ // Now get another RINGING indication from another device associated with the same user.
+ // 180 RINGING proxy <- B2
+ // 180 RINGING A <- proxy
+ DialogAttributes attrB2 = attrB1.fromExisting();
+ // set different To tag
+ attrB2.setToTag();
+ SipMessage inviteRingingB2 = generateSipResponse("180", "Ringing", attrB2);
+ filterMessage(inviteRingingB2, attrB2);
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attrB1, attrB2);
+ // User answers B1
+ // 200 OK proxy <- B1
+ // 200 OK A <- proxy
+ SipMessage inviteConfirm = generateSipResponse("200", "OK", attrB1);
+ filterMessage(inviteConfirm, attrB1);
+ verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attrB2);
+ verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attrB1);
+ // Receive indication that B2 is terminated because user answered on B1
+ // 487 A <- proxy
+ SipMessage terminatedResponse = generateSipResponse("487",
+ "Request Terminated", attrB2);
+ filterMessage(terminatedResponse, attrB2);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attrB1);
+ verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attrB2);
+ SipMessage byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attrB1);
+ // Send BYE request for the open dialog.
+ filterMessage(byeRequest, attrB1);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attrB1, attrB2);
+ // Clean up the session and ensure the close dialog is completely removed from the tracker.
+ mTrackerUT.cleanupSession(attrB1.callId);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
+ }
+
+ @Test
+ public void testCloseLocalDialog() {
+ DialogAttributes attr = new DialogAttributes();
+ attr.setToTag();
+ createConfirmedDialog(attr);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
+
+ // Send BYE request for a dialog that was started locally and ensure that we see the call id
+ // move to the closed list.
+ SipMessage byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr);
+ filterMessage(byeRequest, attr);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr);
+ // Clean up the session and ensure the close dialog is completely removed from the tracker.
+ mTrackerUT.cleanupSession(attr.callId);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
+ }
+
+ @Test
+ public void testAcceptContactFts() {
+ DialogAttributes attr = new DialogAttributes();
+ attr.setToTag();
+ SipMessage inviteRequest = generateSipRequest(
+ SipMessageUtils.INVITE_SIP_METHOD,
+ attr);
+ // add accept contact header
+ inviteRequest = new SipMessage(inviteRequest.getStartLine(),
+ inviteRequest.getHeaderSection() + "\nAccept-Contact:*;+test",
+ new byte[0]);
+ filterMessage(inviteRequest, attr);
+ assertTrue(mTrackerUT.getCallIdsAssociatedWithFeatureTag(Collections.singleton("+test"))
+ .contains(attr.callId));
+ }
+
+ @Test
+ public void testCloseRemoteDialog() {
+ DialogAttributes remoteAttr = new DialogAttributes();
+ remoteAttr.setToTag();
+ createConfirmedDialog(remoteAttr);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), remoteAttr);
+
+ // Send BYE request on a dialog that was started from the remote party.
+ DialogAttributes localAttr = remoteAttr.invertFromTo();
+ SipMessage byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, localAttr);
+ filterMessage(byeRequest, localAttr);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getClosedDialogs(), remoteAttr);
+ // Clean up the session and ensure the dialog is completely removed from the tracker.
+ mTrackerUT.cleanupSession(remoteAttr.callId);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
+ }
+
+ @Test
+ public void testCleanupConfirmedDialog() {
+ DialogAttributes attr = new DialogAttributes();
+ attr.setToTag();
+ createConfirmedDialog(attr);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
+ // Clean up the session and ensure the dialog is completely removed from the tracker.
+ mTrackerUT.cleanupSession(attr.callId);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
+ }
+
+ @Test
+ public void testMultipleDialogs() {
+ DialogAttributes attr1 = new DialogAttributes();
+ createConfirmedDialog(attr1);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1);
+ // add a second dialog
+ DialogAttributes attr2 = new DialogAttributes();
+ createConfirmedDialog(attr2);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1, attr2);
+ // Send BYE request on dialogs
+ SipMessage byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr1);
+ filterMessage(byeRequest, attr1);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr2);
+ verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr1);
+ mTrackerUT.cleanupSession(attr1.callId);
+ // Send BYE request on dialogs
+ byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr2);
+ filterMessage(byeRequest, attr2);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr2);
+ mTrackerUT.cleanupSession(attr2.callId);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
+ }
+
+ @Test
+ public void testAcknowledgeMessageFailed() {
+ DialogAttributes attr = new DialogAttributes();
+ SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr);
+ mTrackerUT.filterSipMessage(inviteRequest);
+ // Do not acknowledge the request and ensure that the operation has not been applied yet.
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ // send message ack failed event, the operation shouldn't have been applied
+ mTrackerUT.pendingMessageFailed(attr.branchId);
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ }
+
+ @Test
+ public void testAcknowledgeBatchEvents() {
+ DialogAttributes attr = new DialogAttributes();
+ SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr);
+ attr.setToTag();
+ SipMessage inviteConfirm = generateSipResponse("200", "OK", attr);
+ // We unexpectedly received two filter requests for the same branchId without
+ // acknowledgePendingMessage being called in between. Ensure that when it is called, it
+ // applies both operations.
+ mTrackerUT.filterSipMessage(inviteRequest);
+ mTrackerUT.filterSipMessage(inviteConfirm);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ // we should skip right to confirmed as both operations run back-to-back
+ mTrackerUT.acknowledgePendingMessage(attr.branchId);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
+ }
+
+ private void filterMessage(SipMessage m, DialogAttributes attr) {
+ mTrackerUT.filterSipMessage(m);
+ mTrackerUT.acknowledgePendingMessage(attr.branchId);
+ }
+ private void verifyContainsCallIds(Set<SipDialog> callIdSet, DialogAttributes... attrs) {
+ Set<String> callIds = Arrays.stream(attrs).map(a -> a.callId).collect(
+ Collectors.toSet());
+ assertTrue(callIdSet.stream().map(SipDialog::getCallId).collect(Collectors.toSet())
+ .containsAll(callIds));
+ }
+
+ private SipMessage generateSipRequest(String requestMethod,
+ DialogAttributes attr) {
+ return SipMessageUtils.generateSipRequest(requestMethod, attr.fromHeader, attr.toHeader,
+ attr.toUri, attr.branchId, attr.callId, attr.fromTag, attr.toTag);
+ }
+ private SipMessage generateSipResponse(String statusCode, String statusString,
+ DialogAttributes attr) {
+ return SipMessageUtils.generateSipResponse(statusCode, statusString, attr.fromHeader,
+ attr.toHeader, attr.branchId, attr.callId, attr.fromTag, attr.toTag);
+ }
+
+ private String generateContactUri(String sipUri) {
+ Uri uri = Uri.parse(sipUri);
+ assertNotNull(uri);
+ String[] user = uri.getSchemeSpecificPart().split("@", 2);
+ assertNotNull(user);
+ assertEquals(2, user.length);
+ return user[0] + " <" + sipUri + ">";
+ }
+
+ private String generateRandomSipUri() {
+ return "sip:" + getNextString() + "@" + SipMessageUtils.BASE_ADDRESS;
+ }
+
+ private void createConfirmedDialog(DialogAttributes attr) {
+ // INVITE ALICE -> BOB
+ SipMessage inviteRequest = generateSipRequest(
+ SipMessageUtils.INVITE_SIP_METHOD,
+ attr);
+ filterMessage(inviteRequest, attr);
+ attr.setToTag();
+ // skip to confirmed state for test.
+ SipMessage inviteConfirm = generateSipResponse("200", "OK",
+ attr);
+ filterMessage(inviteConfirm, attr);
+ }
+
+ private String getNextString() {
+ // Get a string representation of the entry counter
+ byte[] idByteArray = ByteBuffer.allocate(4).putInt(mStringEntryCounter++).array();
+ return Base64.encodeToString(idByteArray,
+ Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE);
+ }
+}
diff --git a/tests/src/com/android/services/telephony/rcs/SipTransportControllerTest.java b/tests/src/com/android/services/telephony/rcs/SipTransportControllerTest.java
new file mode 100644
index 0000000..0f139f8
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/SipTransportControllerTest.java
@@ -0,0 +1,1014 @@
+/*
+ * Copyright (C) 2020 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.services.telephony.rcs;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.role.RoleManager;
+import android.os.IBinder;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.ims.DelegateRequest;
+import android.telephony.ims.FeatureTagState;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.aidl.IImsRegistration;
+import android.telephony.ims.aidl.ISipDelegate;
+import android.telephony.ims.aidl.ISipDelegateConnectionStateCallback;
+import android.telephony.ims.aidl.ISipDelegateMessageCallback;
+import android.telephony.ims.aidl.ISipTransport;
+import android.util.ArraySet;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+import com.android.TestExecutorService;
+import com.android.ims.RcsFeatureManager;
+import com.android.phone.RcsProvisioningMonitor;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+@RunWith(AndroidJUnit4.class)
+public class SipTransportControllerTest extends TelephonyTestBase {
+ private static final int TEST_SUB_ID = 1;
+ private static final String TEST_PACKAGE_NAME = "com.test_pkg";
+ private static final String TEST_PACKAGE_NAME_2 = "com.test_pkg2";
+ private static final int TIMEOUT_MS = 200;
+ private static final int THROTTLE_MS = 50;
+
+ private class SipDelegateControllerContainer {
+ public final int subId;
+ public final String packageName;
+ public final DelegateRequest delegateRequest;
+ public final SipDelegateController delegateController;
+ private final ISipDelegate mMockDelegate;
+ public final IBinder mockDelegateBinder;
+ public final ISipDelegateConnectionStateCallback mockDelegateConnectionCallback;
+ public final ISipDelegateMessageCallback mockMessageCallback;
+ public final IBinder mockMessageCallbackBinder;
+
+ SipDelegateControllerContainer(int id, String name, DelegateRequest request) {
+ delegateController = mock(SipDelegateController.class);
+ mMockDelegate = mock(ISipDelegate.class);
+ mockDelegateBinder = mock(IBinder.class);
+ doReturn(mockDelegateBinder).when(mMockDelegate).asBinder();
+ mockDelegateConnectionCallback = mock(ISipDelegateConnectionStateCallback.class);
+ mockMessageCallback = mock(ISipDelegateMessageCallback.class);
+ mockMessageCallbackBinder = mock(IBinder.class);
+ doReturn(mockMessageCallbackBinder).when(mockMessageCallback).asBinder();
+ doReturn(name).when(delegateController).getPackageName();
+ doReturn(request).when(delegateController).getInitialRequest();
+ doReturn(mMockDelegate).when(delegateController).getSipDelegateInterface();
+ doReturn(mockMessageCallback).when(delegateController).getAppMessageCallback();
+ subId = id;
+ packageName = name;
+ delegateRequest = request;
+ }
+ }
+
+ @Mock private RcsFeatureManager mRcsManager;
+ @Mock private ISipTransport mSipTransport;
+ @Mock private IImsRegistration mImsRegistration;
+ @Mock private SipTransportController.SipDelegateControllerFactory
+ mMockDelegateControllerFactory;
+ @Mock private SipTransportController.RoleManagerAdapter mMockRoleManager;
+
+ private ScheduledExecutorService mExecutorService = null;
+ private final ArrayList<SipDelegateControllerContainer> mMockControllers = new ArrayList<>();
+ private final ArrayList<String> mSmsPackageName = new ArrayList<>(1);
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ doReturn(mSmsPackageName).when(mMockRoleManager).getRoleHolders(RoleManager.ROLE_SMS);
+ doReturn(mImsRegistration).when(mRcsManager).getImsRegistration();
+ mSmsPackageName.add(TEST_PACKAGE_NAME);
+ doAnswer(invocation -> {
+ Integer subId = invocation.getArgument(0);
+ String packageName = invocation.getArgument(2);
+ DelegateRequest request = invocation.getArgument(1);
+ SipDelegateController c = getMockDelegateController(subId, packageName, request);
+ assertNotNull("create called with no corresponding controller set up", c);
+ return c;
+ }).when(mMockDelegateControllerFactory).create(anyInt(), any(), anyString(), any(), any(),
+ any(), any(), any());
+ setFeatureAllowedConfig(TEST_SUB_ID, new String[]{ImsSignallingUtils.MMTEL_TAG,
+ ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG, ImsSignallingUtils.GROUP_CHAT_TAG,
+ ImsSignallingUtils.FILE_TRANSFER_HTTP_TAG});
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ boolean isShutdown = mExecutorService == null || mExecutorService.isShutdown();
+ if (!isShutdown) {
+ mExecutorService.shutdownNow();
+ }
+ RcsProvisioningMonitor.getInstance().overrideImsFeatureValidation(TEST_SUB_ID, null);
+ }
+
+ @SmallTest
+ @Test
+ public void isSupportedRcsNotConnected() {
+ SipTransportController controller = createController(new TestExecutorService());
+ try {
+ controller.isSupported(TEST_SUB_ID);
+ fail();
+ } catch (ImsException e) {
+ assertEquals(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE, e.getCode());
+ }
+ }
+
+ @SmallTest
+ @Test
+ public void isSupportedInvalidSubId() {
+ SipTransportController controller = createController(new TestExecutorService());
+ try {
+ controller.isSupported(TEST_SUB_ID + 1);
+ fail();
+ } catch (ImsException e) {
+ assertEquals(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION, e.getCode());
+ }
+ }
+
+ @SmallTest
+ @Test
+ public void isSupportedSubIdChanged() {
+ SipTransportController controller = createController(new TestExecutorService());
+ controller.onAssociatedSubscriptionUpdated(TEST_SUB_ID + 1);
+ try {
+ controller.isSupported(TEST_SUB_ID);
+ fail();
+ } catch (ImsException e) {
+ assertEquals(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION, e.getCode());
+ }
+ }
+
+ @SmallTest
+ @Test
+ public void isSupportedSipTransportAvailableRcsConnected() throws Exception {
+ SipTransportController controller = createController(new TestExecutorService());
+ doReturn(mSipTransport).when(mRcsManager).getSipTransport();
+ controller.onRcsConnected(mRcsManager);
+ try {
+ assertTrue(controller.isSupported(TEST_SUB_ID));
+ } catch (ImsException e) {
+ fail();
+ }
+ }
+
+ @SmallTest
+ @Test
+ public void isSupportedSipTransportNotAvailableRcsDisconnected() throws Exception {
+ SipTransportController controller = createController(new TestExecutorService());
+ doReturn(mSipTransport).when(mRcsManager).getSipTransport();
+ controller.onRcsConnected(mRcsManager);
+ controller.onRcsDisconnected();
+ try {
+ controller.isSupported(TEST_SUB_ID);
+ fail();
+ } catch (ImsException e) {
+ assertEquals(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE, e.getCode());
+ }
+ }
+
+ @SmallTest
+ @Test
+ public void isSupportedSipTransportNotAvailableRcsConnected() throws Exception {
+ SipTransportController controller = createController(new TestExecutorService());
+ doReturn(null).when(mRcsManager).getSipTransport();
+ controller.onRcsConnected(mRcsManager);
+ try {
+ assertFalse(controller.isSupported(TEST_SUB_ID));
+ } catch (ImsException e) {
+ fail();
+ }
+ }
+
+ @SmallTest
+ @Test
+ public void isSupportedImsServiceNotAvailableRcsConnected() throws Exception {
+ SipTransportController controller = createController(new TestExecutorService());
+ doThrow(new ImsException("", ImsException.CODE_ERROR_SERVICE_UNAVAILABLE))
+ .when(mRcsManager).getSipTransport();
+ controller.onRcsConnected(mRcsManager);
+ try {
+ controller.isSupported(TEST_SUB_ID);
+ fail();
+ } catch (ImsException e) {
+ assertEquals(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE, e.getCode());
+ }
+ }
+
+ @SmallTest
+ @Test
+ public void createImsServiceAvailableSubIdIncorrect() throws Exception {
+ SipTransportController controller = createController(new TestExecutorService());
+ doReturn(mSipTransport).when(mRcsManager).getSipTransport();
+ controller.onRcsConnected(mRcsManager);
+ try {
+ controller.createSipDelegate(TEST_SUB_ID + 1,
+ new DelegateRequest(Collections.emptySet()), TEST_PACKAGE_NAME,
+ mock(ISipDelegateConnectionStateCallback.class),
+ mock(ISipDelegateMessageCallback.class));
+ fail();
+ } catch (ImsException e) {
+ assertEquals(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION, e.getCode());
+ }
+ }
+
+ @SmallTest
+ @Test
+ public void createImsServiceDoesntSupportTransport() throws Exception {
+ SipTransportController controller = createController(new TestExecutorService());
+ doReturn(null).when(mRcsManager).getSipTransport();
+ controller.onRcsConnected(mRcsManager);
+ try {
+ controller.createSipDelegate(TEST_SUB_ID,
+ new DelegateRequest(Collections.emptySet()), TEST_PACKAGE_NAME,
+ mock(ISipDelegateConnectionStateCallback.class),
+ mock(ISipDelegateMessageCallback.class));
+ fail();
+ } catch (ImsException e) {
+ assertEquals(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION, e.getCode());
+ }
+ }
+
+ @SmallTest
+ @Test
+ public void createImsServiceNotAvailable() throws Exception {
+ SipTransportController controller = createController(new TestExecutorService());
+ doThrow(new ImsException("", ImsException.CODE_ERROR_SERVICE_UNAVAILABLE))
+ .when(mRcsManager).getSipTransport();
+ // No RCS connected message
+ try {
+ controller.createSipDelegate(TEST_SUB_ID,
+ new DelegateRequest(Collections.emptySet()), TEST_PACKAGE_NAME,
+ mock(ISipDelegateConnectionStateCallback.class),
+ mock(ISipDelegateMessageCallback.class));
+ fail();
+ } catch (ImsException e) {
+ assertEquals(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE, e.getCode());
+ }
+ }
+
+ @SmallTest
+ @Test
+ public void basicCreate() throws Exception {
+ SipTransportController controller = setupLiveTransportController();
+
+ DelegateRequest r = getBaseDelegateRequest();
+
+ SipDelegateControllerContainer c = injectMockDelegateController(TEST_PACKAGE_NAME, r);
+ createDelegateAndVerify(controller, c, r.getFeatureTags(), Collections.emptySet());
+ verifyDelegateRegistrationChangedEvent(1 /*times*/, 0 /*waitMs*/);
+ triggerFullNetworkRegistrationAndVerify(controller, c);
+ }
+
+ @SmallTest
+ @Test
+ public void basicCreateDestroy() throws Exception {
+ SipTransportController controller = setupLiveTransportController();
+
+ DelegateRequest r = getBaseDelegateRequest();
+ SipDelegateControllerContainer c = injectMockDelegateController(TEST_PACKAGE_NAME, r);
+ createDelegateAndVerify(controller, c, r.getFeatureTags(), Collections.emptySet());
+ verify(c.mockMessageCallbackBinder).linkToDeath(any(), anyInt());
+ verifyDelegateRegistrationChangedEvent(1, 0 /*throttle*/);
+
+ destroyDelegateAndVerify(controller, c, false,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ verify(c.mockMessageCallbackBinder).unlinkToDeath(any(), anyInt());
+ verifyDelegateRegistrationChangedEvent(2 /*times*/, 0 /*waitMs*/);
+ triggerFullNetworkRegistrationAndVerifyNever(controller, c);
+ }
+
+ @SmallTest
+ @Test
+ public void createDestroyAppDied() throws Exception {
+ SipTransportController controller = setupLiveTransportController();
+ DelegateRequest r = getBaseDelegateRequest();
+ SipDelegateControllerContainer c = injectMockDelegateController(TEST_PACKAGE_NAME, r);
+ createDelegateAndVerify(controller, c, r.getFeatureTags(), Collections.emptySet());
+ ArgumentCaptor<IBinder.DeathRecipient> captor =
+ ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
+ verify(c.mockMessageCallbackBinder).linkToDeath(captor.capture(), anyInt());
+ IBinder.DeathRecipient deathRecipient = captor.getValue();
+ assertNotNull(deathRecipient);
+ verifyDelegateRegistrationChangedEvent(1, 0 /*throttle*/);
+
+ CompletableFuture<Integer> pendingDestroy = setDestroyFuture(c.delegateController, true,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD);
+ // Simulate app dying
+ deathRecipient.binderDied();
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ verifyDestroyDelegate(c.delegateController, pendingDestroy, true,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD);
+ // If binderDied is called, then unregister does not lead to unlinkToDeath
+ triggerFullNetworkRegistrationAndVerifyNever(controller, c);
+ }
+
+ @SmallTest
+ @Test
+ public void testCreateButNotInRole() throws Exception {
+ SipTransportController controller = setupLiveTransportController();
+
+ DelegateRequest r = getBaseDelegateRequest();
+ Set<FeatureTagState> getDeniedTags = getDeniedTagsForReason(r.getFeatureTags(),
+ SipDelegateManager.DENIED_REASON_NOT_ALLOWED);
+
+ // Try to create a SipDelegate for a package that is not the default sms role.
+ SipDelegateControllerContainer c = injectMockDelegateController(TEST_PACKAGE_NAME_2, r);
+ createDelegateAndVerify(controller, c, Collections.emptySet(), getDeniedTags);
+ }
+
+ @SmallTest
+ @Test
+ public void createTwoAndDenyOverlappingTags() throws Exception {
+ SipTransportController controller = setupLiveTransportController(0 /*reeval*/,
+ THROTTLE_MS);
+
+ // First delegate requests RCS message + File transfer
+ ArraySet<String> firstDelegate = new ArraySet<>(getBaseDelegateRequest().getFeatureTags());
+ firstDelegate.remove(ImsSignallingUtils.GROUP_CHAT_TAG);
+ DelegateRequest firstDelegateRequest = new DelegateRequest(firstDelegate);
+ SipDelegateControllerContainer c1 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ firstDelegateRequest);
+ createDelegateAndVerify(controller, c1, firstDelegate, Collections.emptySet());
+ // there is a delay in the indication to update reg, so it should not happen yet.
+ verifyNoDelegateRegistrationChangedEvent();
+
+ // First delegate requests RCS message + Group RCS message. For this delegate, single RCS
+ // message should be denied.
+ ArraySet<String> secondDelegate = new ArraySet<>(getBaseDelegateRequest().getFeatureTags());
+ secondDelegate.remove(ImsSignallingUtils.FILE_TRANSFER_HTTP_TAG);
+ DelegateRequest secondDelegateRequest = new DelegateRequest(secondDelegate);
+ Pair<Set<String>, Set<FeatureTagState>> grantedAndDenied = getAllowedAndDeniedTagsForConfig(
+ secondDelegateRequest, SipDelegateManager.DENIED_REASON_IN_USE_BY_ANOTHER_DELEGATE,
+ firstDelegate);
+ SipDelegateControllerContainer c2 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ secondDelegateRequest);
+ createDelegateAndVerify(controller, c2, grantedAndDenied.first,
+ grantedAndDenied.second, 1);
+ // a reg changed event should happen after wait.
+ verifyDelegateRegistrationChangedEvent(1, 2 * THROTTLE_MS);
+ }
+
+ @SmallTest
+ @Test
+ public void createTwoAndTriggerRoleChange() throws Exception {
+ SipTransportController controller = setupLiveTransportController(0 /*reeval*/, THROTTLE_MS);
+
+ DelegateRequest firstDelegateRequest = getBaseDelegateRequest();
+ Set<FeatureTagState> firstDeniedTags = getDeniedTagsForReason(
+ firstDelegateRequest.getFeatureTags(),
+ SipDelegateManager.DENIED_REASON_NOT_ALLOWED);
+ SipDelegateControllerContainer c1 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ firstDelegateRequest);
+ createDelegateAndVerify(controller, c1, firstDelegateRequest.getFeatureTags(),
+ Collections.emptySet());
+ verifyDelegateRegistrationChangedEvent(1 /*times*/, THROTTLE_MS);
+
+ DelegateRequest secondDelegateRequest = getBaseDelegateRequest();
+ Set<FeatureTagState> secondDeniedTags = getDeniedTagsForReason(
+ secondDelegateRequest.getFeatureTags(),
+ SipDelegateManager.DENIED_REASON_NOT_ALLOWED);
+ // Try to create a SipDelegate for a package that is not the default sms role.
+ SipDelegateControllerContainer c2 = injectMockDelegateController(TEST_PACKAGE_NAME_2,
+ secondDelegateRequest);
+ createDelegateAndVerify(controller, c2, Collections.emptySet(), secondDeniedTags, 1);
+
+ // now swap the SMS role.
+ CompletableFuture<Boolean> pendingC1Change = setChangeSupportedFeatureTagsFuture(
+ c1.delegateController, Collections.emptySet(), firstDeniedTags);
+ CompletableFuture<Boolean> pendingC2Change = setChangeSupportedFeatureTagsFuture(
+ c2.delegateController, secondDelegateRequest.getFeatureTags(),
+ Collections.emptySet());
+ setSmsRoleAndEvaluate(controller, TEST_PACKAGE_NAME_2);
+ // swapping roles should trigger a deregistration event on the ImsService side.
+ verifyDelegateDeregistrationEvent();
+ // there should also not be any new registration changed events
+ verifyDelegateRegistrationChangedEvent(1 /*times*/, THROTTLE_MS);
+ // trigger completion stage to run
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ verify(c1.delegateController).changeSupportedFeatureTags(Collections.emptySet(),
+ firstDeniedTags);
+ // we should not get a change for c2 until pendingC1Change completes.
+ verify(c2.delegateController, never()).changeSupportedFeatureTags(
+ secondDelegateRequest.getFeatureTags(), Collections.emptySet());
+ // ensure we are not blocking executor here
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ completePendingChange(pendingC1Change, true);
+ // trigger completion stage to run
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ verify(c2.delegateController).changeSupportedFeatureTags(
+ secondDelegateRequest.getFeatureTags(), Collections.emptySet());
+ // ensure we are not blocking executor here
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ completePendingChange(pendingC2Change, true);
+ // verify we now get a second registration changed event
+ verifyDelegateRegistrationChangedEvent(2 /*times*/, THROTTLE_MS);
+ }
+
+ @SmallTest
+ @Test
+ public void createTwoAndDestroyOlder() throws Exception {
+ SipTransportController controller = setupLiveTransportController(0 /*reeval*/, THROTTLE_MS);
+
+ // First delegate requests RCS message + File transfer
+ ArraySet<String> firstDelegate = new ArraySet<>(getBaseDelegateRequest().getFeatureTags());
+ firstDelegate.remove(ImsSignallingUtils.GROUP_CHAT_TAG);
+ DelegateRequest firstDelegateRequest = new DelegateRequest(firstDelegate);
+ SipDelegateControllerContainer c1 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ firstDelegateRequest);
+ createDelegateAndVerify(controller, c1, firstDelegate, Collections.emptySet());
+ verifyNoDelegateRegistrationChangedEvent();
+
+ // First delegate requests RCS message + Group RCS message. For this delegate, single RCS
+ // message should be denied.
+ ArraySet<String> secondDelegate = new ArraySet<>(getBaseDelegateRequest().getFeatureTags());
+ secondDelegate.remove(ImsSignallingUtils.FILE_TRANSFER_HTTP_TAG);
+ DelegateRequest secondDelegateRequest = new DelegateRequest(secondDelegate);
+ Pair<Set<String>, Set<FeatureTagState>> grantedAndDenied = getAllowedAndDeniedTagsForConfig(
+ secondDelegateRequest, SipDelegateManager.DENIED_REASON_IN_USE_BY_ANOTHER_DELEGATE,
+ firstDelegate);
+ SipDelegateControllerContainer c2 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ secondDelegateRequest);
+ createDelegateAndVerify(controller, c2, grantedAndDenied.first, grantedAndDenied.second, 1);
+ verifyNoDelegateRegistrationChangedEvent();
+
+ // Destroy the firstDelegate, which should now cause all previously denied tags to be
+ // granted to the new delegate.
+ CompletableFuture<Boolean> pendingC2Change = setChangeSupportedFeatureTagsFuture(
+ c2.delegateController, secondDelegate, Collections.emptySet());
+ destroyDelegateAndVerify(controller, c1, false /*force*/,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ // wait for create to be processed.
+ assertTrue(waitForExecutorAction(mExecutorService, TIMEOUT_MS));
+ verify(c2.delegateController).changeSupportedFeatureTags(secondDelegate,
+ Collections.emptySet());
+ completePendingChange(pendingC2Change, true);
+
+ verifyDelegateRegistrationChangedEvent(1 /*times*/, THROTTLE_MS);
+ }
+
+ @SmallTest
+ @Test
+ public void testThrottling() throws Exception {
+ SipTransportController controller = setupLiveTransportController(THROTTLE_MS, THROTTLE_MS);
+
+ // First delegate requests RCS message + File transfer
+ ArraySet<String> firstDelegate = new ArraySet<>(getBaseDelegateRequest().getFeatureTags());
+ firstDelegate.remove(ImsSignallingUtils.GROUP_CHAT_TAG);
+ DelegateRequest firstDelegateRequest = new DelegateRequest(firstDelegate);
+ SipDelegateControllerContainer c1 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ firstDelegateRequest);
+ CompletableFuture<Boolean> pendingC1Change = createDelegate(controller, c1,
+ firstDelegate, Collections.emptySet());
+
+ // Request RCS message + group RCS Message. For this delegate, single RCS message should be
+ // denied.
+ ArraySet<String> secondDelegate = new ArraySet<>(getBaseDelegateRequest().getFeatureTags());
+ secondDelegate.remove(ImsSignallingUtils.FILE_TRANSFER_HTTP_TAG);
+ DelegateRequest secondDelegateRequest = new DelegateRequest(secondDelegate);
+ Pair<Set<String>, Set<FeatureTagState>> grantedAndDeniedC2 =
+ getAllowedAndDeniedTagsForConfig(secondDelegateRequest,
+ SipDelegateManager.DENIED_REASON_IN_USE_BY_ANOTHER_DELEGATE, firstDelegate);
+ SipDelegateControllerContainer c2 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ secondDelegateRequest);
+ CompletableFuture<Boolean> pendingC2Change = createDelegate(controller, c2,
+ grantedAndDeniedC2.first, grantedAndDeniedC2.second);
+
+ // Request group RCS message + file transfer. All should be denied at first
+ ArraySet<String> thirdDelegate = new ArraySet<>(getBaseDelegateRequest().getFeatureTags());
+ thirdDelegate.remove(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG);
+ DelegateRequest thirdDelegateRequest = new DelegateRequest(thirdDelegate);
+ Pair<Set<String>, Set<FeatureTagState>> grantedAndDeniedC3 =
+ getAllowedAndDeniedTagsForConfig(thirdDelegateRequest,
+ SipDelegateManager.DENIED_REASON_IN_USE_BY_ANOTHER_DELEGATE, firstDelegate,
+ grantedAndDeniedC2.first);
+ SipDelegateControllerContainer c3 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ thirdDelegateRequest);
+ CompletableFuture<Boolean> pendingC3Change = createDelegate(controller, c3,
+ grantedAndDeniedC3.first, grantedAndDeniedC3.second);
+
+ verifyNoDelegateRegistrationChangedEvent();
+ assertTrue(scheduleDelayedWait(2 * THROTTLE_MS));
+ verifyDelegateChanged(c1.delegateController, pendingC1Change, firstDelegate,
+ Collections.emptySet(), 0);
+ verifyDelegateChanged(c2.delegateController, pendingC2Change, grantedAndDeniedC2.first,
+ grantedAndDeniedC2.second, 0);
+ verifyDelegateChanged(c3.delegateController, pendingC3Change, grantedAndDeniedC3.first,
+ grantedAndDeniedC3.second, 0);
+ verifyDelegateRegistrationChangedEvent(1, 2 * THROTTLE_MS);
+
+ // Destroy the first and second controller in quick succession, this should only generate
+ // one reevaluate for the third controller.
+ CompletableFuture<Boolean> pendingChangeC3 = setChangeSupportedFeatureTagsFuture(
+ c3.delegateController, thirdDelegate, Collections.emptySet());
+ CompletableFuture<Integer> pendingDestroyC1 = destroyDelegate(controller,
+ c1.delegateController, false /*force*/,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ CompletableFuture<Integer> pendingDestroyC2 = destroyDelegate(controller,
+ c2.delegateController, false /*force*/,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ assertTrue(scheduleDelayedWait(2 * THROTTLE_MS));
+ verifyDestroyDelegate(c1.delegateController, pendingDestroyC1, false /*force*/,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ verifyDestroyDelegate(c2.delegateController, pendingDestroyC2, false /*force*/,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+
+ // All requested features should now be granted
+ completePendingChange(pendingChangeC3, true);
+ verify(c3.delegateController)
+ .changeSupportedFeatureTags(thirdDelegate, Collections.emptySet());
+ // In total reeval should have only been called twice.
+ verify(c3.delegateController, times(2))
+ .changeSupportedFeatureTags(any(), any());
+ verifyDelegateRegistrationChangedEvent(2 /*times*/, 2 * THROTTLE_MS);
+ }
+
+ @SmallTest
+ @Test
+ public void testSubIdChangeDestroyTriggered() throws Exception {
+ SipTransportController controller = setupLiveTransportController();
+
+ ArraySet<String> firstDelegate = new ArraySet<>(getBaseDelegateRequest().getFeatureTags());
+ DelegateRequest firstDelegateRequest = new DelegateRequest(firstDelegate);
+ SipDelegateControllerContainer c = injectMockDelegateController(TEST_PACKAGE_NAME,
+ firstDelegateRequest);
+ createDelegateAndVerify(controller, c, firstDelegate, Collections.emptySet());
+ verifyDelegateRegistrationChangedEvent(1 /*times*/, 0 /*waitMs*/);
+
+ CompletableFuture<Integer> pendingDestroy = setDestroyFuture(c.delegateController, true,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SUBSCRIPTION_TORN_DOWN);
+ controller.onAssociatedSubscriptionUpdated(TEST_SUB_ID + 1);
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ verifyDestroyDelegate(c.delegateController, pendingDestroy, true /*force*/,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SUBSCRIPTION_TORN_DOWN);
+ verifyDelegateRegistrationChangedEvent(2 /*times*/, 0 /*waitMs*/);
+ }
+
+ @SmallTest
+ @Test
+ public void testRcsManagerGoneDestroyTriggered() throws Exception {
+ SipTransportController controller = setupLiveTransportController();
+
+ ArraySet<String> firstDelegate = new ArraySet<>(getBaseDelegateRequest().getFeatureTags());
+ DelegateRequest firstDelegateRequest = new DelegateRequest(firstDelegate);
+ SipDelegateControllerContainer c = injectMockDelegateController(TEST_PACKAGE_NAME,
+ firstDelegateRequest);
+ createDelegateAndVerify(controller, c, firstDelegate, Collections.emptySet());
+
+ CompletableFuture<Integer> pendingDestroy = setDestroyFuture(c.delegateController, true,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD);
+ controller.onRcsDisconnected();
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ verifyDestroyDelegate(c.delegateController, pendingDestroy, true /*force*/,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD);
+ verifyDelegateRegistrationChangedEvent(1, 0 /*waitMs*/);
+ }
+
+ @SmallTest
+ @Test
+ public void testDestroyTriggered() throws Exception {
+ SipTransportController controller = setupLiveTransportController();
+
+ ArraySet<String> firstDelegate = new ArraySet<>(getBaseDelegateRequest().getFeatureTags());
+ DelegateRequest firstDelegateRequest = new DelegateRequest(firstDelegate);
+ SipDelegateControllerContainer c = injectMockDelegateController(TEST_PACKAGE_NAME,
+ firstDelegateRequest);
+ createDelegateAndVerify(controller, c, firstDelegate, Collections.emptySet());
+
+ CompletableFuture<Integer> pendingDestroy = setDestroyFuture(c.delegateController,
+ true, SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SUBSCRIPTION_TORN_DOWN);
+ controller.onDestroy();
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ verifyDelegateDeregistrationEvent();
+ // verify change was called.
+ verify(c.delegateController).destroy(true /*force*/,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SUBSCRIPTION_TORN_DOWN);
+ // ensure thread is not blocked while waiting for pending complete.
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ completePendingDestroy(pendingDestroy,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SUBSCRIPTION_TORN_DOWN);
+ }
+
+ @SmallTest
+ @Test
+ public void testTimingSubIdChangedAndCreateNewSubId() throws Exception {
+ SipTransportController controller = setupLiveTransportController(THROTTLE_MS, 0);
+ setFeatureAllowedConfig(TEST_SUB_ID + 1, new String[]{ImsSignallingUtils.MMTEL_TAG,
+ ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG, ImsSignallingUtils.GROUP_CHAT_TAG,
+ ImsSignallingUtils.FILE_TRANSFER_HTTP_TAG});
+
+ ArraySet<String> firstDelegate = new ArraySet<>(getBaseDelegateRequest().getFeatureTags());
+ DelegateRequest firstDelegateRequest = new DelegateRequest(firstDelegate);
+ SipDelegateControllerContainer c1 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ firstDelegateRequest);
+ CompletableFuture<Boolean> pendingC1Change = createDelegate(controller, c1,
+ firstDelegate, Collections.emptySet());
+ assertTrue(scheduleDelayedWait(2 * THROTTLE_MS));
+ verifyDelegateChanged(c1.delegateController, pendingC1Change, firstDelegate,
+ Collections.emptySet(), 0);
+
+
+ CompletableFuture<Integer> pendingDestroy = setDestroyFuture(c1.delegateController,
+ true, SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SUBSCRIPTION_TORN_DOWN);
+ // triggers reeval now.
+ controller.onAssociatedSubscriptionUpdated(TEST_SUB_ID + 1);
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+
+ // mock a second delegate with the new subId associated with the slot.
+ ArraySet<String> secondDelegate = new ArraySet<>();
+ secondDelegate.add(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG);
+ secondDelegate.add(ImsSignallingUtils.FILE_TRANSFER_HTTP_TAG);
+ DelegateRequest secondDelegateRequest = new DelegateRequest(secondDelegate);
+ SipDelegateControllerContainer c2 = injectMockDelegateController(TEST_SUB_ID + 1,
+ TEST_PACKAGE_NAME, secondDelegateRequest);
+ CompletableFuture<Boolean> pendingC2Change = createDelegate(controller, c2, secondDelegate,
+ Collections.emptySet());
+ assertTrue(scheduleDelayedWait(THROTTLE_MS));
+
+ //trigger destroyed event
+ verifyDestroyDelegate(c1.delegateController, pendingDestroy, true /*force*/,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SUBSCRIPTION_TORN_DOWN);
+ assertTrue(scheduleDelayedWait(2 * THROTTLE_MS));
+ verifyDelegateChanged(c2.delegateController, pendingC2Change, secondDelegate,
+ Collections.emptySet(), 0);
+ }
+
+ @SmallTest
+ @Test
+ public void testFeatureTagsDeniedByConfig() throws Exception {
+ setFeatureAllowedConfig(TEST_SUB_ID, new String[]{ImsSignallingUtils.GROUP_CHAT_TAG,
+ ImsSignallingUtils.FILE_TRANSFER_HTTP_TAG});
+ SipTransportController controller = setupLiveTransportController(THROTTLE_MS, 0);
+
+ ArraySet<String> requestTags = new ArraySet<>(getBaseDelegateRequest().getFeatureTags());
+ ArraySet<String> allowedTags = new ArraySet<>(requestTags);
+ ArraySet<String> deniedTags = new ArraySet<>();
+ DelegateRequest delegateRequest = new DelegateRequest(requestTags);
+ SipDelegateControllerContainer c = injectMockDelegateController(TEST_PACKAGE_NAME,
+ delegateRequest);
+ CompletableFuture<Boolean> pendingScChange = createDelegate(controller, c, requestTags,
+ Collections.emptySet());
+ allowedTags.remove(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG);
+ deniedTags.add(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG);
+
+ assertTrue(scheduleDelayedWait(2 * THROTTLE_MS));
+ verifyDelegateChanged(c.delegateController, pendingScChange, allowedTags,
+ getDeniedTagsForReason(deniedTags,
+ SipDelegateManager.DENIED_REASON_NOT_ALLOWED), 0);
+ }
+
+ @SmallTest
+ @Test
+ public void testFeatureTagsDeniedByOverride() throws Exception {
+ RcsProvisioningMonitor.getInstance().overrideImsFeatureValidation(TEST_SUB_ID, false);
+ SipTransportController controller = setupLiveTransportController(THROTTLE_MS, 0);
+
+ ArraySet<String> requestTags = new ArraySet<>(getBaseDelegateRequest().getFeatureTags());
+ ArraySet<String> deniedTags = new ArraySet<>(requestTags);
+ DelegateRequest delegateRequest = new DelegateRequest(requestTags);
+ SipDelegateControllerContainer c = injectMockDelegateController(TEST_PACKAGE_NAME,
+ delegateRequest);
+ CompletableFuture<Boolean> pendingScChange = createDelegate(controller, c, requestTags,
+ Collections.emptySet());
+
+ assertTrue(scheduleDelayedWait(2 * THROTTLE_MS));
+ verifyDelegateChanged(c.delegateController, pendingScChange, Collections.emptySet(),
+ getDeniedTagsForReason(deniedTags,
+ SipDelegateManager.DENIED_REASON_NOT_ALLOWED), 0);
+ }
+
+ @SmallTest
+ @Test
+ public void testFeatureTagsDeniedByConfigAllowedByOverride() throws Exception {
+ setFeatureAllowedConfig(TEST_SUB_ID, new String[]{});
+ RcsProvisioningMonitor.getInstance().overrideImsFeatureValidation(TEST_SUB_ID, true);
+ SipTransportController controller = setupLiveTransportController(THROTTLE_MS, 0);
+
+ ArraySet<String> requestTags = new ArraySet<>(getBaseDelegateRequest().getFeatureTags());
+ ArraySet<String> allowedTags = new ArraySet<>(requestTags);
+ DelegateRequest delegateRequest = new DelegateRequest(requestTags);
+ SipDelegateControllerContainer controllerContainer =
+ injectMockDelegateController(TEST_PACKAGE_NAME, delegateRequest);
+ CompletableFuture<Boolean> pendingScChange = createDelegate(controller, controllerContainer,
+ requestTags, Collections.emptySet());
+
+ assertTrue(scheduleDelayedWait(2 * THROTTLE_MS));
+ verifyDelegateChanged(controllerContainer.delegateController, pendingScChange, allowedTags,
+ Collections.emptySet(), 0);
+ }
+
+ @SafeVarargs
+ private final Pair<Set<String>, Set<FeatureTagState>> getAllowedAndDeniedTagsForConfig(
+ DelegateRequest r, int denyReason, Set<String>... previousRequestedTagSets) {
+ ArraySet<String> rejectedTags = new ArraySet<>(r.getFeatureTags());
+ ArraySet<String> grantedTags = new ArraySet<>(r.getFeatureTags());
+ Set<String> previousRequestedTags = new ArraySet<>();
+ for (Set<String> s : previousRequestedTagSets) {
+ previousRequestedTags.addAll(s);
+ }
+ rejectedTags.retainAll(previousRequestedTags);
+ grantedTags.removeAll(previousRequestedTags);
+ Set<FeatureTagState> deniedTags = getDeniedTagsForReason(rejectedTags, denyReason);
+ return new Pair<>(grantedTags, deniedTags);
+ }
+
+ private void completePendingChange(CompletableFuture<Boolean> change, boolean result) {
+ mExecutorService.execute(() -> change.complete(result));
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ }
+
+ private void completePendingDestroy(CompletableFuture<Integer> destroy, int result) {
+ mExecutorService.execute(() -> destroy.complete(result));
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ }
+
+ private SipTransportController setupLiveTransportController() throws Exception {
+ return setupLiveTransportController(0 /*throttleMs*/, 0 /*regDelayMs*/);
+ }
+
+ private SipTransportController setupLiveTransportController(int throttleMs, int regDelayMs)
+ throws Exception {
+ mExecutorService = Executors.newSingleThreadScheduledExecutor();
+ SipTransportController controller = createControllerAndThrottle(mExecutorService,
+ throttleMs, regDelayMs);
+ doReturn(mSipTransport).when(mRcsManager).getSipTransport();
+ controller.onAssociatedSubscriptionUpdated(TEST_SUB_ID);
+ controller.onRcsConnected(mRcsManager);
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ return controller;
+ }
+
+ private void createDelegateAndVerify(SipTransportController controller,
+ SipDelegateControllerContainer controllerContainer, Set<String> allowedTags,
+ Set<FeatureTagState> deniedTags, int numPreviousChanges) {
+
+ CompletableFuture<Boolean> pendingChange = createDelegate(controller, controllerContainer,
+ allowedTags, deniedTags);
+ verifyDelegateChanged(controllerContainer.delegateController, pendingChange, allowedTags,
+ deniedTags, numPreviousChanges);
+ }
+
+ private void createDelegateAndVerify(SipTransportController controller,
+ SipDelegateControllerContainer controllerContainer, Set<String> allowedTags,
+ Set<FeatureTagState> deniedTags) {
+ createDelegateAndVerify(controller, controllerContainer, allowedTags, deniedTags, 0);
+ }
+
+ private CompletableFuture<Boolean> createDelegate(SipTransportController controller,
+ SipDelegateControllerContainer delegateControllerContainer, Set<String> allowedTags,
+ Set<FeatureTagState> deniedTags) {
+ CompletableFuture<Boolean> pendingChange = setChangeSupportedFeatureTagsFuture(
+ delegateControllerContainer.delegateController, allowedTags, deniedTags);
+ try {
+ controller.createSipDelegate(delegateControllerContainer.subId,
+ delegateControllerContainer.delegateRequest,
+ delegateControllerContainer.packageName,
+ delegateControllerContainer.mockDelegateConnectionCallback,
+ delegateControllerContainer.mockMessageCallback);
+ } catch (ImsException e) {
+ fail("ImsException thrown:" + e);
+ }
+ // move to internal & schedule eval
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ // reeval
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ return pendingChange;
+ }
+
+ private void verifyDelegateChanged(SipDelegateController delegateController,
+ CompletableFuture<Boolean> pendingChange, Set<String> allowedTags,
+ Set<FeatureTagState> deniedTags, int numPreviousChangeStages) {
+ // empty the queue of pending changeSupportedFeatureTags before running the one we are
+ // interested in, since the reevaluate waits for one stage to complete before moving to the
+ // next.
+ for (int i = 0; i < numPreviousChangeStages + 1; i++) {
+ assertTrue(waitForExecutorAction(mExecutorService, TIMEOUT_MS));
+ }
+ // verify change was called.
+ verify(delegateController).changeSupportedFeatureTags(allowedTags, deniedTags);
+ // ensure thread is not blocked while waiting for pending complete.
+ assertTrue(waitForExecutorAction(mExecutorService, TIMEOUT_MS));
+ completePendingChange(pendingChange, true);
+ // process pending change.
+ assertTrue(waitForExecutorAction(mExecutorService, TIMEOUT_MS));
+ }
+
+ private void destroyDelegateAndVerify(SipTransportController controller,
+ SipDelegateControllerContainer controllerContainer, boolean force, int reason) {
+ SipDelegateController delegateController = controllerContainer.delegateController;
+ CompletableFuture<Integer> pendingDestroy = destroyDelegate(controller, delegateController,
+ force, reason);
+ verifyDestroyDelegate(delegateController, pendingDestroy, force, reason);
+ }
+
+ private CompletableFuture<Integer> destroyDelegate(SipTransportController controller,
+ SipDelegateController delegateController, boolean force, int reason) {
+ CompletableFuture<Integer> pendingDestroy = setDestroyFuture(delegateController, force,
+ reason);
+ controller.destroySipDelegate(TEST_SUB_ID, delegateController.getSipDelegateInterface(),
+ reason);
+ // move to internal & schedule eval
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ // reeval
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ return pendingDestroy;
+ }
+
+ private void verifyDestroyDelegate(SipDelegateController delegateController,
+ CompletableFuture<Integer> pendingDestroy, boolean force, int reason) {
+ // verify destroy was called.
+ verify(delegateController).destroy(force, reason);
+ // ensure thread is not blocked while waiting for pending complete.
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ completePendingDestroy(pendingDestroy, reason);
+ }
+
+ private void triggerFullNetworkRegistrationAndVerify(SipTransportController controller,
+ SipDelegateControllerContainer controllerContainer) {
+ SipDelegateController delegateController = controllerContainer.delegateController;
+ controller.triggerFullNetworkRegistration(TEST_SUB_ID,
+ delegateController.getSipDelegateInterface(), 403, "forbidden");
+ // move to internal & trigger event
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ verify(delegateController).triggerFullNetworkRegistration(403, "forbidden");
+ }
+
+ private void triggerFullNetworkRegistrationAndVerifyNever(SipTransportController controller,
+ SipDelegateControllerContainer controllerContainer) {
+ SipDelegateController delegateController = controllerContainer.delegateController;
+ controller.triggerFullNetworkRegistration(TEST_SUB_ID,
+ delegateController.getSipDelegateInterface(), 403, "forbidden");
+ // move to internal & potentially trigger event
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ verify(delegateController, never()).triggerFullNetworkRegistration(anyInt(), anyString());
+ }
+
+ private DelegateRequest getBaseDelegateRequest() {
+ Set<String> featureTags = new ArraySet<>();
+ featureTags.add(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG);
+ featureTags.add(ImsSignallingUtils.GROUP_CHAT_TAG);
+ featureTags.add(ImsSignallingUtils.FILE_TRANSFER_HTTP_TAG);
+ return new DelegateRequest(featureTags);
+ }
+
+ private Set<FeatureTagState> getDeniedTagsForReason(Set<String> deniedTags, int reason) {
+ return deniedTags.stream().map(t -> new FeatureTagState(t, reason))
+ .collect(Collectors.toSet());
+ }
+
+ private SipDelegateControllerContainer injectMockDelegateController(String packageName,
+ DelegateRequest r) {
+ return injectMockDelegateController(TEST_SUB_ID, packageName, r);
+ }
+
+ private SipDelegateControllerContainer injectMockDelegateController(int subId,
+ String packageName, DelegateRequest r) {
+ SipDelegateControllerContainer c = new SipDelegateControllerContainer(subId,
+ packageName, r);
+ mMockControllers.add(c);
+ return c;
+ }
+
+ private SipDelegateController getMockDelegateController(int subId, String packageName,
+ DelegateRequest r) {
+ return mMockControllers.stream()
+ .filter(c -> c.subId == subId && c.packageName.equals(packageName)
+ && c.delegateRequest.equals(r))
+ .map(c -> c.delegateController).findFirst().orElse(null);
+ }
+
+ private CompletableFuture<Boolean> setChangeSupportedFeatureTagsFuture(SipDelegateController c,
+ Set<String> supportedSet, Set<FeatureTagState> deniedSet) {
+ CompletableFuture<Boolean> result = new CompletableFuture<>();
+ doReturn(result).when(c).changeSupportedFeatureTags(eq(supportedSet), eq(deniedSet));
+ return result;
+ }
+
+ private CompletableFuture<Integer> setDestroyFuture(SipDelegateController c, boolean force,
+ int destroyReason) {
+ CompletableFuture<Integer> result = new CompletableFuture<>();
+ doReturn(result).when(c).destroy(force, destroyReason);
+ return result;
+ }
+
+ private void setSmsRoleAndEvaluate(SipTransportController c, String packageName) {
+ verify(mMockRoleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any());
+ mSmsPackageName.clear();
+ mSmsPackageName.add(packageName);
+ c.onRoleHoldersChanged(RoleManager.ROLE_SMS, UserHandle.SYSTEM);
+ // finish internal throttled re-evaluate
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ }
+
+ private void verifyNoDelegateRegistrationChangedEvent() throws Exception {
+ // event is scheduled and then executed.
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ verify(mImsRegistration, never()).triggerUpdateSipDelegateRegistration();
+ }
+
+ private void verifyDelegateRegistrationChangedEvent(int times, int waitMs)
+ throws Exception {
+ // event is scheduled and then executed.
+ assertTrue(scheduleDelayedWait(waitMs));
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ verify(mImsRegistration, times(times)).triggerUpdateSipDelegateRegistration();
+ }
+
+
+ private void verifyDelegateDeregistrationEvent() throws Exception {
+ verify(mImsRegistration).triggerSipDelegateDeregistration();
+ }
+
+ private SipTransportController createController(ScheduledExecutorService e) {
+ return createControllerAndThrottle(e, 0 /*throttleMs*/, 0 /*regDelayMs*/);
+ }
+
+ private SipTransportController createControllerAndThrottle(ScheduledExecutorService e,
+ int throttleMs, int regDelayMs) {
+ return new SipTransportController(mContext, 0 /*slotId*/, TEST_SUB_ID,
+ mMockDelegateControllerFactory, mMockRoleManager,
+ // Remove delays for testing.
+ new SipTransportController.TimerAdapter() {
+ @Override
+ public int getReevaluateThrottleTimerMilliseconds() {
+ return throttleMs;
+ }
+
+ @Override
+ public int getUpdateRegistrationDelayMilliseconds() {
+ return regDelayMs;
+ }
+ }, e);
+ }
+
+ private boolean scheduleDelayedWait(long timeMs) {
+ CountDownLatch l = new CountDownLatch(1);
+ mExecutorService.schedule(l::countDown, timeMs, TimeUnit.MILLISECONDS);
+ while (l.getCount() > 0) {
+ try {
+ return l.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // try again
+ }
+ }
+ return true;
+ }
+
+ private void setFeatureAllowedConfig(int subId, String[] tags) {
+ PersistableBundle bundle = mContext.getCarrierConfig(subId);
+ bundle.putStringArray(
+ CarrierConfigManager.Ims.KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY, tags);
+ }
+}
diff --git a/tests/src/com/android/services/telephony/rcs/TelephonyRcsServiceTest.java b/tests/src/com/android/services/telephony/rcs/TelephonyRcsServiceTest.java
index cfb68b7..39469b6 100644
--- a/tests/src/com/android/services/telephony/rcs/TelephonyRcsServiceTest.java
+++ b/tests/src/com/android/services/telephony/rcs/TelephonyRcsServiceTest.java
@@ -50,8 +50,11 @@
@Captor ArgumentCaptor<BroadcastReceiver> mReceiverCaptor;
@Mock TelephonyRcsService.FeatureFactory mFeatureFactory;
- @Mock UserCapabilityExchangeImpl mMockUceSlot0;
- @Mock UserCapabilityExchangeImpl mMockUceSlot1;
+ @Mock TelephonyRcsService.ResourceProxy mResourceProxy;
+ @Mock UceControllerManager mMockUceSlot0;
+ @Mock UceControllerManager mMockUceSlot1;
+ @Mock SipTransportController mMockSipTransportSlot0;
+ @Mock SipTransportController mMockSipTransportSlot1;
@Mock RcsFeatureController.RegistrationHelperFactory mRegistrationFactory;
@Mock RcsFeatureController.FeatureConnectorFactory<RcsFeatureManager> mFeatureConnectorFactory;
@Mock FeatureConnector<RcsFeatureManager> mFeatureConnector;
@@ -64,14 +67,21 @@
super.setUp();
doReturn(mFeatureConnector).when(mFeatureConnectorFactory).create(any(), anyInt(),
any(), any(), any());
- mFeatureControllerSlot0 = createFeatureController(0 /*slotId*/);
- mFeatureControllerSlot1 = createFeatureController(1 /*slotId*/);
- doReturn(mFeatureControllerSlot0).when(mFeatureFactory).createController(any(), eq(0));
- doReturn(mFeatureControllerSlot1).when(mFeatureFactory).createController(any(), eq(1));
- doReturn(mMockUceSlot0).when(mFeatureFactory).createUserCapabilityExchange(any(), eq(0),
+ mFeatureControllerSlot0 = createFeatureController(0 /*slotId*/, 1 /*subId*/);
+ mFeatureControllerSlot1 = createFeatureController(1 /*slotId*/, 2 /*subId*/);
+ doReturn(mFeatureControllerSlot0).when(mFeatureFactory).createController(any(), eq(0),
anyInt());
- doReturn(mMockUceSlot1).when(mFeatureFactory).createUserCapabilityExchange(any(), eq(1),
+ doReturn(mFeatureControllerSlot1).when(mFeatureFactory).createController(any(), eq(1),
anyInt());
+ doReturn(mMockUceSlot0).when(mFeatureFactory).createUceControllerManager(any(), eq(0),
+ anyInt());
+ doReturn(mMockUceSlot1).when(mFeatureFactory).createUceControllerManager(any(), eq(1),
+ anyInt());
+ doReturn(mMockSipTransportSlot0).when(mFeatureFactory).createSipTransportController(any(),
+ eq(0), anyInt());
+ doReturn(mMockSipTransportSlot1).when(mFeatureFactory).createSipTransportController(any(),
+ eq(1), anyInt());
+ doReturn(true).when(mResourceProxy).getDeviceUceEnabled(any());
//set up default slot-> sub ID mappings.
setSlotToSubIdMapping(0 /*slotId*/, 1/*subId*/);
setSlotToSubIdMapping(1 /*slotId*/, 2/*subId*/);
@@ -83,18 +93,21 @@
}
@Test
- public void testUserCapabilityExchangePresenceConnected() {
- setCarrierConfig(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, true /*isEnabled*/);
+ public void testUceControllerPresenceConnected() {
+ setCarrierConfig(1 /*subId*/,
+ CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL,
+ true /*isEnabled*/);
createRcsService(1 /*numSlots*/);
- verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UserCapabilityExchangeImpl.class);
+ verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UceControllerManager.class);
verify(mFeatureControllerSlot0).connect();
}
@Test
- public void testUserCapabilityExchangeOptionsConnected() {
- setCarrierConfig(CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL, true /*isEnabled*/);
+ public void testUceControllerOptionsConnected() {
+ setCarrierConfig(1 /*subId*/, CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL,
+ true /*isEnabled*/);
createRcsService(1 /*numSlots*/);
- verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UserCapabilityExchangeImpl.class);
+ verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UceControllerManager.class);
verify(mFeatureControllerSlot0).connect();
}
@@ -103,18 +116,67 @@
createRcsService(1 /*numSlots*/);
// No carrier config set for UCE.
verify(mFeatureControllerSlot0, never()).addFeature(mMockUceSlot0,
- UserCapabilityExchangeImpl.class);
+ UceControllerManager.class);
verify(mFeatureControllerSlot0, never()).connect();
}
@Test
+ public void testSipTransportConnected() {
+ createRcsService(1 /*numSlots*/);
+ verify(mFeatureControllerSlot0, never()).addFeature(mMockSipTransportSlot0,
+ SipTransportController.class);
+ verify(mFeatureControllerSlot0, never()).connect();
+
+
+ // Send carrier config update for each slot.
+ setCarrierConfig(1 /*subId*/,
+ CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL,
+ true /*isEnabled*/);
+ sendCarrierConfigChanged(0 /*slotId*/, 1 /*subId*/);
+ verify(mFeatureControllerSlot0).addFeature(mMockSipTransportSlot0,
+ SipTransportController.class);
+ verify(mFeatureControllerSlot0).connect();
+ verify(mFeatureControllerSlot0).updateAssociatedSubscription(1);
+ }
+
+ @Test
+ public void testSipTransportConnectedOneSlot() {
+ createRcsService(2 /*numSlots*/);
+ verify(mFeatureControllerSlot0, never()).addFeature(mMockSipTransportSlot0,
+ SipTransportController.class);
+ verify(mFeatureControllerSlot0, never()).connect();
+ verify(mFeatureControllerSlot0, never()).addFeature(mMockSipTransportSlot1,
+ SipTransportController.class);
+ verify(mFeatureControllerSlot1, never()).connect();
+
+
+ // Send carrier config update for slot 0 only
+ setCarrierConfig(1 /*subId*/,
+ CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL,
+ true /*isEnabled*/);
+ setCarrierConfig(2 /*subId*/,
+ CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL,
+ false /*isEnabled*/);
+ sendCarrierConfigChanged(0 /*slotId*/, 1 /*subId*/);
+ sendCarrierConfigChanged(1 /*slotId*/, 2 /*subId*/);
+ verify(mFeatureControllerSlot0).addFeature(mMockSipTransportSlot0,
+ SipTransportController.class);
+ verify(mFeatureControllerSlot1, never()).addFeature(mMockSipTransportSlot0,
+ SipTransportController.class);
+ verify(mFeatureControllerSlot0).connect();
+ verify(mFeatureControllerSlot1, never()).connect();
+ verify(mFeatureControllerSlot0).updateAssociatedSubscription(1);
+ verify(mFeatureControllerSlot1, never()).updateAssociatedSubscription(1);
+ }
+
+ @Test
public void testNoFeaturesEnabledCarrierConfigChanged() {
createRcsService(1 /*numSlots*/);
// No carrier config set for UCE.
sendCarrierConfigChanged(0, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
verify(mFeatureControllerSlot0, never()).addFeature(mMockUceSlot0,
- UserCapabilityExchangeImpl.class);
+ UceControllerManager.class);
verify(mFeatureControllerSlot0, never()).connect();
verify(mFeatureControllerSlot0, never()).updateAssociatedSubscription(anyInt());
}
@@ -122,27 +184,32 @@
@Test
public void testSlotUpdates() {
- setCarrierConfig(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, true /*isEnabled*/);
+ setCarrierConfig(1 /*subId*/,
+ CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL,
+ true /*isEnabled*/);
+ setCarrierConfig(2 /*subId*/,
+ CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL,
+ true /*isEnabled*/);
TelephonyRcsService service = createRcsService(1 /*numSlots*/);
- verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UserCapabilityExchangeImpl.class);
+ verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UceControllerManager.class);
verify(mFeatureControllerSlot0).connect();
// there should be no changes if the new num slots = old num
service.updateFeatureControllerSize(1 /*newNumSlots*/);
verify(mFeatureControllerSlot0, times(1)).addFeature(mMockUceSlot0,
- UserCapabilityExchangeImpl.class);
+ UceControllerManager.class);
verify(mFeatureControllerSlot0, times(1)).connect();
// Add a new slot.
verify(mFeatureControllerSlot1, never()).addFeature(mMockUceSlot1,
- UserCapabilityExchangeImpl.class);
+ UceControllerManager.class);
verify(mFeatureControllerSlot1, never()).connect();
service.updateFeatureControllerSize(2 /*newNumSlots*/);
// This shouldn't have changed for slot 0.
verify(mFeatureControllerSlot0, times(1)).addFeature(mMockUceSlot0,
- UserCapabilityExchangeImpl.class);
+ UceControllerManager.class);
verify(mFeatureControllerSlot0, times(1)).connect();
- verify(mFeatureControllerSlot1).addFeature(mMockUceSlot1, UserCapabilityExchangeImpl.class);
+ verify(mFeatureControllerSlot1).addFeature(mMockUceSlot1, UceControllerManager.class);
verify(mFeatureControllerSlot1, times(1)).connect();
// Remove a slot.
@@ -151,10 +218,10 @@
service.updateFeatureControllerSize(1 /*newNumSlots*/);
// addFeature/connect shouldn't have been called again
verify(mFeatureControllerSlot0, times(1)).addFeature(mMockUceSlot0,
- UserCapabilityExchangeImpl.class);
+ UceControllerManager.class);
verify(mFeatureControllerSlot0, times(1)).connect();
verify(mFeatureControllerSlot1, times(1)).addFeature(mMockUceSlot1,
- UserCapabilityExchangeImpl.class);
+ UceControllerManager.class);
verify(mFeatureControllerSlot1, times(1)).connect();
// Verify destroy is only called for slot 1.
verify(mFeatureControllerSlot0, never()).destroy();
@@ -162,11 +229,16 @@
}
@Test
- public void testCarrierConfigUpdate() {
- setCarrierConfig(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, true /*isEnabled*/);
+ public void testCarrierConfigUpdateAssociatedSub() {
+ setCarrierConfig(1 /*subId*/,
+ CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL,
+ true /*isEnabled*/);
+ setCarrierConfig(2 /*subId*/,
+ CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL,
+ true /*isEnabled*/);
createRcsService(2 /*numSlots*/);
- verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UserCapabilityExchangeImpl.class);
- verify(mFeatureControllerSlot1).addFeature(mMockUceSlot1, UserCapabilityExchangeImpl.class);
+ verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UceControllerManager.class);
+ verify(mFeatureControllerSlot1).addFeature(mMockUceSlot1, UceControllerManager.class);
verify(mFeatureControllerSlot0).connect();
verify(mFeatureControllerSlot1).connect();
@@ -181,17 +253,61 @@
}
@Test
- public void testCarrierConfigUpdateUceToNoUce() {
- setCarrierConfig(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, true /*isEnabled*/);
+ public void testCarrierConfigNotifyFeatures() {
+ setCarrierConfig(1 /*subId*/,
+ CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL,
+ true /*isEnabled*/);
createRcsService(1 /*numSlots*/);
- verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UserCapabilityExchangeImpl.class);
+ verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UceControllerManager.class);
+ verify(mFeatureControllerSlot0).connect();
+
+
+ // Send carrier config update twice with no update to subId
+ sendCarrierConfigChanged(0 /*slotId*/, 1 /*subId*/);
+ verify(mFeatureControllerSlot0).updateAssociatedSubscription(1);
+ verify(mFeatureControllerSlot0, never()).onCarrierConfigChangedForSubscription();
+ sendCarrierConfigChanged(0 /*slotId*/, 1 /*subId*/);
+ verify(mFeatureControllerSlot0, times(1)).updateAssociatedSubscription(1);
+ // carrier config changed should be sent here
+ verify(mFeatureControllerSlot0).onCarrierConfigChangedForSubscription();
+ }
+
+ @Test
+ public void testCarrierConfigUpdateUceToNoUce() {
+ setCarrierConfig(1 /*subId*/,
+ CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL,
+ true /*isEnabled*/);
+ createRcsService(1 /*numSlots*/);
+ verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UceControllerManager.class);
verify(mFeatureControllerSlot0).connect();
// Send carrier config update for each slot.
- setCarrierConfig(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, false /*isEnabled*/);
+ setCarrierConfig(1 /*subId*/,
+ CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL,
+ false /*isEnabled*/);
sendCarrierConfigChanged(0 /*slotId*/, 1 /*subId*/);
- verify(mFeatureControllerSlot0).removeFeature(UserCapabilityExchangeImpl.class);
+ verify(mFeatureControllerSlot0).removeFeature(UceControllerManager.class);
+ verify(mFeatureControllerSlot0).updateAssociatedSubscription(1);
+ }
+
+ @Test
+ public void testCarrierConfigUpdateTransportToNoTransport() {
+ setCarrierConfig(1 /*subId*/,
+ CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL,
+ true /*isEnabled*/);
+ createRcsService(1 /*numSlots*/);
+ verify(mFeatureControllerSlot0).addFeature(mMockSipTransportSlot0,
+ SipTransportController.class);
+ verify(mFeatureControllerSlot0).connect();
+
+
+ // Send carrier config update for each slot.
+ setCarrierConfig(1 /*subId*/,
+ CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL,
+ false /*isEnabled*/);
+ sendCarrierConfigChanged(0 /*slotId*/, 1 /*subId*/);
+ verify(mFeatureControllerSlot0).removeFeature(SipTransportController.class);
verify(mFeatureControllerSlot0).updateAssociatedSubscription(1);
}
@@ -199,14 +315,16 @@
public void testCarrierConfigUpdateNoUceToUce() {
createRcsService(1 /*numSlots*/);
verify(mFeatureControllerSlot0, never()).addFeature(mMockUceSlot0,
- UserCapabilityExchangeImpl.class);
+ UceControllerManager.class);
verify(mFeatureControllerSlot0, never()).connect();
// Send carrier config update for each slot.
- setCarrierConfig(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, true /*isEnabled*/);
+ setCarrierConfig(1 /*subId*/,
+ CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL,
+ true /*isEnabled*/);
sendCarrierConfigChanged(0 /*slotId*/, 1 /*subId*/);
- verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UserCapabilityExchangeImpl.class);
+ verify(mFeatureControllerSlot0).addFeature(mMockUceSlot0, UceControllerManager.class);
verify(mFeatureControllerSlot0).connect();
verify(mFeatureControllerSlot0).updateAssociatedSubscription(1);
}
@@ -218,8 +336,8 @@
mReceiverCaptor.getValue().onReceive(mContext, intent);
}
- private void setCarrierConfig(String key, boolean value) {
- PersistableBundle bundle = mContext.getCarrierConfig();
+ private void setCarrierConfig(int subId, String key, boolean value) {
+ PersistableBundle bundle = mContext.getCarrierConfig(subId);
bundle.putBoolean(key, value);
}
@@ -231,17 +349,17 @@
}
private TelephonyRcsService createRcsService(int numSlots) {
- TelephonyRcsService service = new TelephonyRcsService(mContext, numSlots);
+ TelephonyRcsService service = new TelephonyRcsService(mContext, numSlots, mResourceProxy);
service.setFeatureFactory(mFeatureFactory);
service.initialize();
verify(mContext).registerReceiver(mReceiverCaptor.capture(), any());
return service;
}
- private RcsFeatureController createFeatureController(int slotId) {
+ private RcsFeatureController createFeatureController(int slotId, int subId) {
// Create a spy instead of a mock because TelephonyRcsService relies on state provided by
// RcsFeatureController.
- RcsFeatureController controller = spy(new RcsFeatureController(mContext, slotId,
+ RcsFeatureController controller = spy(new RcsFeatureController(mContext, slotId, subId,
mRegistrationFactory));
controller.setFeatureConnectorFactory(mFeatureConnectorFactory);
return controller;
diff --git a/tests/src/com/android/services/telephony/rcs/TransportSipMessageValidatorTest.java b/tests/src/com/android/services/telephony/rcs/TransportSipMessageValidatorTest.java
new file mode 100644
index 0000000..4d222e3
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/TransportSipMessageValidatorTest.java
@@ -0,0 +1,487 @@
+/*
+ * 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.services.telephony.rcs;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.net.InetAddresses;
+import android.telephony.ims.DelegateRegistrationState;
+import android.telephony.ims.SipDelegateConfiguration;
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.SipMessage;
+import android.util.ArraySet;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+import com.android.TestExecutorService;
+import com.android.services.telephony.rcs.validator.IncomingTransportStateValidator;
+import com.android.services.telephony.rcs.validator.OutgoingTransportStateValidator;
+import com.android.services.telephony.rcs.validator.ValidationResult;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.net.InetSocketAddress;
+import java.util.Collections;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ScheduledExecutorService;
+
+@RunWith(AndroidJUnit4.class)
+public class TransportSipMessageValidatorTest extends TelephonyTestBase {
+ private static final int TEST_SUB_ID = 1;
+ private static final int TEST_CONFIG_VERSION = 1;
+ private static final SipMessage TEST_MESSAGE = new SipMessage(
+ "INVITE sip:bob@biloxi.com SIP/2.0",
+ // Typical Via
+ "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds\n"
+ + "Max-Forwards: 70\n"
+ + "To: Bob <sip:bob@biloxi.com>\n"
+ + "From: Alice <sip:alice@atlanta.com>;tag=1928301774\n"
+ + "Call-ID: testid\n"
+ + "CSeq: 314159 INVITE\n"
+ + "Contact: <sip:alice@pc33.atlanta.com>\n"
+ + "Content-Type: application/sdp\n"
+ + "Content-Length: 142",
+ new byte[0]);
+
+ @Mock
+ private SipSessionTracker mSipSessionTracker;
+ @Mock
+ private IncomingTransportStateValidator mIncomingStateValidator;
+ @Mock
+ private OutgoingTransportStateValidator mOutgoingStateValidator;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ public void testTransportOpening() {
+ TestExecutorService executor = new TestExecutorService();
+ TransportSipMessageValidator tracker = getTestTracker(executor);
+ tracker.onTransportOpened(Collections.emptySet(), Collections.emptySet());
+ verify(mOutgoingStateValidator).open(Collections.emptySet(), Collections.emptySet());
+ verify(mIncomingStateValidator).open();
+ // Incoming messages are already verified
+ assertTrue(isIncomingTransportOpen(tracker));
+ // IMS config needs to be sent before outgoing messages can be verified.
+ assertFalse(isOutgoingTransportOpen(tracker));
+ tracker.onConfigurationChanged(getConfigBuilder(TEST_CONFIG_VERSION).build());
+ // Incoming messages are already verified
+ // Config set, transport is now open.
+ assertTrue(isIncomingTransportOpen(tracker));
+ assertTrue(isOutgoingTransportOpen(tracker));
+ }
+
+ @Test
+ public void testTransportOpenConfigChange() {
+ TestExecutorService executor = new TestExecutorService();
+ TransportSipMessageValidator tracker = openTransport(executor);
+
+ // Update IMS config version and send a message with an outdated version.
+ tracker.onConfigurationChanged(getConfigBuilder(TEST_CONFIG_VERSION + 1).build());
+ assertEquals(SipDelegateManager.MESSAGE_FAILURE_REASON_STALE_IMS_CONFIGURATION,
+ verifyOutgoingTransportClosed(tracker));
+ }
+
+ @Test
+ public void testSessionTrackerFiltering() {
+ TestExecutorService executor = new TestExecutorService();
+ TransportSipMessageValidator tracker = openTransport(executor);
+ // Since the incoming/outgoing messages were verified, there should have been two calls
+ // to filter the message.
+ verify(mSipSessionTracker, times(2)).filterSipMessage(TEST_MESSAGE);
+ // ensure pass through methods are working
+ tracker.acknowledgePendingMessage("abc");
+ verify(mSipSessionTracker).acknowledgePendingMessage("abc");
+ tracker.notifyPendingMessageFailed("abc");
+ verify(mSipSessionTracker).pendingMessageFailed("abc");
+ tracker.onSipSessionCleanup("abc");
+ verify(mSipSessionTracker).cleanupSession("abc");
+ // Now have validators return a non-successful result for validation and the tracker should
+ // not get the indication to filter the message.
+ doReturn(new ValidationResult(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED,
+ "")).when(mOutgoingStateValidator).validate(any());
+ doReturn(new ValidationResult(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED,
+ "")).when(mIncomingStateValidator).validate(any());
+ assertFalse(tracker.verifyIncomingMessage(TEST_MESSAGE).isValidated);
+ assertFalse(tracker.verifyOutgoingMessage(TEST_MESSAGE, TEST_CONFIG_VERSION).isValidated);
+ // The number of times the filter method was called should still only be two after these
+ // messages were not validated.
+ verify(mSipSessionTracker, times(2)).filterSipMessage(TEST_MESSAGE);
+ }
+
+
+ @Test
+ public void testTransportClosingGracefullyNoPendingSessions() {
+ TestExecutorService executor = new TestExecutorService(true /*wait*/);
+ TransportSipMessageValidator tracker = openTransport(executor);
+
+ doReturn(Collections.emptySet()).when(mSipSessionTracker).getTrackedDialogs();
+ ArraySet<String> pendingCallIds = new ArraySet<>();
+ CountDownLatch l = new CountDownLatch(1);
+ tracker.closeSessionsGracefully((ids) -> {
+ pendingCallIds.addAll(ids);
+ l.countDown();
+ }, SipDelegateManager.MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION,
+ SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED);
+ // Since there are no pending call ids, this should be completed with no call ids pending.
+ assertEquals(0, l.getCount());
+ assertTrue(pendingCallIds.isEmpty());
+ verify(mOutgoingStateValidator).close(anyInt());
+ verify(mIncomingStateValidator).close(anyInt());
+ }
+
+ @Test
+ public void testTransportClosingGracefullyCloseCallIds() {
+ TestExecutorService executor = new TestExecutorService(true /*wait*/);
+ TransportSipMessageValidator tracker = openTransport(executor);
+
+ ArraySet<SipDialog> dialogs = new ArraySet<>();
+ dialogs.add(SipDialog.fromSipMessage(TEST_MESSAGE));
+ doReturn(dialogs).when(mSipSessionTracker).getTrackedDialogs();
+ ArraySet<String> pendingCallIds = new ArraySet<>();
+ CountDownLatch l = new CountDownLatch(1);
+ tracker.closeSessionsGracefully((ids) -> {
+ pendingCallIds.addAll(ids);
+ l.countDown();
+ }, SipDelegateManager.MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION,
+ SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED);
+ // Before executor executes, outgoing messages will be restricted due to pending call ids.
+ assertTrue(l.getCount() >= 1);
+ assertTrue(pendingCallIds.isEmpty());
+ assertTrue(isIncomingTransportOpen(tracker));
+ verify(mOutgoingStateValidator).restrict(anyInt());
+ // pretend a sip message has been acknowledged, which closed pending call id. Since there
+ // are no more pending call ids, the transport should move to closed.
+ dialogs.clear();
+ tracker.acknowledgePendingMessage("blah");
+ assertEquals(0, l.getCount());
+ assertTrue(pendingCallIds.isEmpty());
+ verify(mOutgoingStateValidator).close(anyInt());
+ verify(mIncomingStateValidator).close(anyInt());
+ }
+
+ @Test
+ public void testTransportClosingGracefullyThenForceClose() {
+ TestExecutorService executor = new TestExecutorService(true /*wait*/);
+ TransportSipMessageValidator tracker = openTransport(executor);
+
+ ArraySet<SipDialog> dialogs = new ArraySet<>();
+ dialogs.add(SipDialog.fromSipMessage(TEST_MESSAGE));
+ doReturn(dialogs).when(mSipSessionTracker).getTrackedDialogs();
+ ArraySet<String> pendingCallIds = new ArraySet<>();
+ CountDownLatch l = new CountDownLatch(1);
+ tracker.closeSessionsGracefully((ids) -> {
+ pendingCallIds.addAll(ids);
+ l.countDown();
+ }, SipDelegateManager.MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION,
+ SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED);
+ // Before executor executes, outgoing messages will be restricted due to pending call ids.
+ assertTrue(l.getCount() >= 1);
+ assertTrue(pendingCallIds.isEmpty());
+ assertTrue(isIncomingTransportOpen(tracker));
+ verify(mOutgoingStateValidator).restrict(anyInt());
+ // force close amd ensure pending close is
+ assertTrue(tracker.closeSessions(
+ SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED).isEmpty());
+ assertEquals(0, l.getCount());
+ assertEquals(pendingCallIds, pendingCallIds);
+ verify(mOutgoingStateValidator).close(anyInt());
+ verify(mIncomingStateValidator).close(anyInt());
+ }
+
+ @Test
+ public void testTransportClosingGracefullyTimeout() {
+ TestExecutorService executor = new TestExecutorService(true /*wait*/);
+ TransportSipMessageValidator tracker = openTransport(executor);
+
+ ArraySet<SipDialog> dialogs = new ArraySet<>();
+ dialogs.add(SipDialog.fromSipMessage(TEST_MESSAGE));
+ doReturn(dialogs).when(mSipSessionTracker).getTrackedDialogs();
+ ArraySet<String> pendingCallIds = new ArraySet<>();
+ CountDownLatch l = new CountDownLatch(1);
+ tracker.closeSessionsGracefully((ids) -> {
+ pendingCallIds.addAll(ids);
+ l.countDown();
+ }, SipDelegateManager.MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION,
+ SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED);
+ // Before executor executes, outgoing messages will be restricted due to pending call ids.
+ assertTrue(l.getCount() >= 1);
+ assertTrue(pendingCallIds.isEmpty());
+ assertTrue(isIncomingTransportOpen(tracker));
+ verify(mOutgoingStateValidator).restrict(anyInt());
+ // Process timeout event - pending call id should be passed to transport.
+ executor.executePending();
+ assertEquals(0, l.getCount());
+ assertTrue(pendingCallIds.contains("testid"));
+ verify(mOutgoingStateValidator).close(anyInt());
+ verify(mIncomingStateValidator).close(anyInt());
+ }
+
+ @Test
+ public void testTransportClosingGracefullyCleanup() {
+ TestExecutorService executor = new TestExecutorService(true /*wait*/);
+ TransportSipMessageValidator tracker = openTransport(executor);
+
+ ArraySet<SipDialog> dialogs = new ArraySet<>();
+ dialogs.add(SipDialog.fromSipMessage(TEST_MESSAGE));
+ doReturn(dialogs).when(mSipSessionTracker).getTrackedDialogs();
+ ArraySet<String> pendingCallIds = new ArraySet<>();
+ CountDownLatch l = new CountDownLatch(1);
+ tracker.closeSessionsGracefully((ids) -> {
+ pendingCallIds.addAll(ids);
+ l.countDown();
+ }, SipDelegateManager.MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION,
+ SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED);
+ // Before executor executes, outgoing messages will be restricted due to pending call ids.
+ assertTrue(l.getCount() >= 1);
+ assertTrue(pendingCallIds.isEmpty());
+ assertTrue(isIncomingTransportOpen(tracker));
+ verify(mOutgoingStateValidator).restrict(anyInt());
+ // Mock cleanupSession event was called for pending callId
+ dialogs.clear();
+ tracker.onSipSessionCleanup("abc");
+ assertEquals(0, l.getCount());
+ assertTrue(pendingCallIds.isEmpty());
+ verify(mOutgoingStateValidator).close(anyInt());
+ verify(mIncomingStateValidator).close(anyInt());
+ }
+
+ @Test
+ public void testTransportClosingForcefully() {
+ TestExecutorService executor = new TestExecutorService();
+ TransportSipMessageValidator tracker = openTransport(executor);
+
+ tracker.closeSessions(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED);
+
+ // All messages will be rejected.
+ verify(mOutgoingStateValidator).close(anyInt());
+ verify(mIncomingStateValidator).close(anyInt());
+ }
+
+ @Test
+ public void setRegStateChangedNoPendingCallIds() {
+ TestExecutorService executor = new TestExecutorService(true /*wait*/);
+ TransportSipMessageValidator tracker = openTransport(executor);
+
+ doReturn(Collections.emptySet()).when(mSipSessionTracker)
+ .getCallIdsAssociatedWithFeatureTag(any());
+ ArraySet<String> pendingCallIds = new ArraySet<>();
+ CountDownLatch l = new CountDownLatch(1);
+ // no feature tags are deregistering/deregistered, should return immediately
+ tracker.onRegistrationStateChanged((ids) -> {
+ pendingCallIds.addAll(ids);
+ l.countDown();
+ }, getTestRegistrationState());
+ verify(mOutgoingStateValidator, times(2)).restrictFeatureTags(Collections.emptySet());
+ assertTrue(pendingCallIds.isEmpty());
+ assertEquals(0, l.getCount());
+ }
+
+ @Test
+ public void setRegStateChangedPendingCallIdsMultipleCalls() {
+ TestExecutorService executor = new TestExecutorService(true /*wait*/);
+ TransportSipMessageValidator tracker = openTransport(executor);
+
+ ArraySet<String> callIds = new ArraySet<>(1);
+ callIds.add("abc");
+ doReturn(callIds).when(mSipSessionTracker)
+ .getCallIdsAssociatedWithFeatureTag(any());
+ ArraySet<String> pendingCallIds = new ArraySet<>();
+ CountDownLatch l = new CountDownLatch(2);
+ tracker.onRegistrationStateChanged((ids) -> {
+ pendingCallIds.addAll(ids);
+ l.countDown();
+ }, getTestRegistrationState());
+ assertEquals(2, l.getCount());
+ // If called again, the previous request will complete with no call ids
+ tracker.onRegistrationStateChanged((ids) -> {
+ pendingCallIds.addAll(ids);
+ l.countDown();
+ }, getTestRegistrationState());
+ assertEquals(1, l.getCount());
+ assertTrue(pendingCallIds.isEmpty());
+ // Simulate timeout - we should get callback with the pending call ids.
+ executor.executePending();
+ assertEquals(0, l.getCount());
+ assertEquals(callIds, pendingCallIds);
+ }
+
+ @Test
+ public void setRegStateChangedPendingCallIdsTimeout() {
+ TestExecutorService executor = new TestExecutorService(true /*wait*/);
+ TransportSipMessageValidator tracker = openTransport(executor);
+
+ ArraySet<String> callIds = new ArraySet<>(1);
+ callIds.add("abc");
+ doReturn(callIds).when(mSipSessionTracker)
+ .getCallIdsAssociatedWithFeatureTag(any());
+ ArraySet<String> pendingCallIds = new ArraySet<>();
+ CountDownLatch l = new CountDownLatch(1);
+ tracker.onRegistrationStateChanged((ids) -> {
+ pendingCallIds.addAll(ids);
+ l.countDown();
+ }, getTestRegistrationState());
+ assertEquals(1, l.getCount());
+ assertTrue(pendingCallIds.isEmpty());
+ // Simulate timeout - we should get callback with the pending call ids.
+ executor.executePending();
+ assertEquals(0, l.getCount());
+ assertEquals(callIds, pendingCallIds);
+ }
+
+ @Test
+ public void setRegStateChangedPendingCallIdsResolved() {
+ TestExecutorService executor = new TestExecutorService(true /*wait*/);
+ TransportSipMessageValidator tracker = openTransport(executor);
+
+ ArraySet<String> callIds = new ArraySet<>(1);
+ callIds.add("abc");
+ doReturn(callIds).when(mSipSessionTracker)
+ .getCallIdsAssociatedWithFeatureTag(any());
+ ArraySet<String> pendingCallIds = new ArraySet<>();
+ CountDownLatch l = new CountDownLatch(1);
+ tracker.onRegistrationStateChanged((ids) -> {
+ pendingCallIds.addAll(ids);
+ l.countDown();
+ }, getTestRegistrationState());
+ assertEquals(1, l.getCount());
+ assertTrue(pendingCallIds.isEmpty());
+ // Simulate ack pending SIP session, which has cleared pending call id
+ doReturn(Collections.emptySet()).when(mSipSessionTracker)
+ .getCallIdsAssociatedWithFeatureTag(any());
+ tracker.acknowledgePendingMessage("blah");
+ assertEquals(0, l.getCount());
+ assertEquals(callIds, pendingCallIds);
+ }
+
+ @Test
+ public void setRegStateChangedPendingCallIdsChanged() {
+ TestExecutorService executor = new TestExecutorService(true /*wait*/);
+ TransportSipMessageValidator tracker = openTransport(executor);
+
+ ArraySet<String> callIds = new ArraySet<>(1);
+ callIds.add("abc");
+ doReturn(callIds).when(mSipSessionTracker)
+ .getCallIdsAssociatedWithFeatureTag(any());
+ ArraySet<String> pendingCallIds = new ArraySet<>();
+ CountDownLatch l = new CountDownLatch(1);
+ tracker.onRegistrationStateChanged((ids) -> {
+ pendingCallIds.addAll(ids);
+ l.countDown();
+ }, getTestRegistrationState());
+ assertEquals(1, l.getCount());
+ assertTrue(pendingCallIds.isEmpty());
+ // Simulate pending callIds changed to add another session.
+ callIds.add("def");
+ executor.executePending();
+ assertEquals(0, l.getCount());
+ assertEquals(callIds, pendingCallIds);
+ }
+
+ @Test
+ public void setRegStateRegThenClose() {
+ TestExecutorService executor = new TestExecutorService(true /*wait*/);
+ TransportSipMessageValidator tracker = openTransport(executor);
+
+ ArraySet<String> callIds = new ArraySet<>(1);
+ callIds.add("abc");
+ doReturn(callIds).when(mSipSessionTracker)
+ .getCallIdsAssociatedWithFeatureTag(any());
+ ArraySet<String> pendingCallIds = new ArraySet<>();
+ CountDownLatch l = new CountDownLatch(1);
+ tracker.onRegistrationStateChanged((ids) -> {
+ pendingCallIds.addAll(ids);
+ l.countDown();
+ }, getTestRegistrationState());
+ assertEquals(1, l.getCount());
+ assertTrue(pendingCallIds.isEmpty());
+ // If close is called during pending reg state change, it should be completed with no
+ // pending call IDs (close will take care of closing everything).
+ ArraySet<SipDialog> dialogs = new ArraySet<>();
+ dialogs.add(SipDialog.fromSipMessage(TEST_MESSAGE));
+ doReturn(dialogs).when(mSipSessionTracker).getTrackedDialogs();
+ tracker.closeSessions(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD);
+ assertEquals(0, l.getCount());
+ assertEquals(Collections.emptySet(), pendingCallIds);
+ }
+
+ private SipDelegateConfiguration.Builder getConfigBuilder(int version) {
+ InetSocketAddress localAddr = new InetSocketAddress(
+ InetAddresses.parseNumericAddress("1.1.1.1"), 80);
+ InetSocketAddress serverAddr = new InetSocketAddress(
+ InetAddresses.parseNumericAddress("2.2.2.2"), 81);
+ return new SipDelegateConfiguration.Builder(version,
+ SipDelegateConfiguration.SIP_TRANSPORT_TCP, localAddr, serverAddr);
+ }
+
+ private boolean isIncomingTransportOpen(TransportSipMessageValidator tracker) {
+ return tracker.verifyIncomingMessage(TEST_MESSAGE).isValidated;
+ }
+
+ private boolean isOutgoingTransportOpen(TransportSipMessageValidator tracker) {
+ return tracker.verifyOutgoingMessage(TEST_MESSAGE, TEST_CONFIG_VERSION).isValidated;
+ }
+
+ private int verifyOutgoingTransportClosed(TransportSipMessageValidator tracker) {
+ ValidationResult result = tracker.verifyOutgoingMessage(TEST_MESSAGE, TEST_CONFIG_VERSION);
+ assertFalse(result.isValidated);
+ return result.restrictedReason;
+ }
+
+ private DelegateRegistrationState getTestRegistrationState() {
+ return new DelegateRegistrationState.Builder().build();
+ }
+
+ private TransportSipMessageValidator openTransport(ScheduledExecutorService executor) {
+ TransportSipMessageValidator tracker = getTestTracker(executor);
+ tracker.onTransportOpened(Collections.emptySet(), Collections.emptySet());
+ tracker.onConfigurationChanged(getConfigBuilder(TEST_CONFIG_VERSION).build());
+ tracker.onRegistrationStateChanged((ignore) -> {}, getTestRegistrationState());
+ // Config set + IMS reg state sent, transport is now open.
+ assertTrue(isIncomingTransportOpen(tracker));
+ assertTrue(isOutgoingTransportOpen(tracker));
+ return tracker;
+ }
+
+ private TransportSipMessageValidator getTestTracker(ScheduledExecutorService executor) {
+ doReturn(ValidationResult.SUCCESS).when(mOutgoingStateValidator).validate(any());
+ doReturn(ValidationResult.SUCCESS).when(mIncomingStateValidator).validate(any());
+ doReturn(mIncomingStateValidator).when(mIncomingStateValidator).andThen(any());
+ return new TransportSipMessageValidator(TEST_SUB_ID, executor, mSipSessionTracker,
+ mOutgoingStateValidator, mIncomingStateValidator);
+ }
+}
diff --git a/tests/src/com/android/services/telephony/rcs/UceControllerManagerTest.java b/tests/src/com/android/services/telephony/rcs/UceControllerManagerTest.java
new file mode 100644
index 0000000..8d719fd
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/UceControllerManagerTest.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2020 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.services.telephony.rcs;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.net.Uri;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.aidl.IRcsUceControllerCallback;
+import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+import com.android.TestExecutorService;
+import com.android.ims.RcsFeatureManager;
+import com.android.ims.rcs.uce.UceController;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+
+@RunWith(AndroidJUnit4.class)
+public class UceControllerManagerTest extends TelephonyTestBase {
+
+ @Mock private UceController mUceController;
+ @Mock private RcsFeatureManager mRcsFeatureManager;
+
+ private final ExecutorService mExecutorService = new TestExecutorService();
+
+ private int mSlotId = 1;
+ private int mSubId = 1;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ doReturn(mSubId).when(mUceController).getSubId();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ public void testRcsConnected() throws Exception {
+ UceControllerManager controllerManager = getUceControllerManager();
+
+ controllerManager.onRcsConnected(mRcsFeatureManager);
+
+ verify(mUceController).onRcsConnected(mRcsFeatureManager);
+ }
+
+ @Test
+ public void testRcsDisconnected() throws Exception {
+ UceControllerManager uceCtrlManager = getUceControllerManager();
+
+ uceCtrlManager.onRcsDisconnected();
+
+ verify(mUceController).onRcsDisconnected();
+ }
+
+ @Test
+ public void testDestroy() throws Exception {
+ UceControllerManager uceCtrlManager = getUceControllerManager();
+
+ uceCtrlManager.onDestroy();
+
+ verify(mUceController).onDestroy();
+ }
+
+ @Test
+ public void testSubIdAndCarrierConfigUpdate() throws Exception {
+ UceControllerManager uceCtrlManager = getUceControllerManager();
+
+ // Updates with the same subId should not destroy the UceController
+ uceCtrlManager.onCarrierConfigChanged();
+ verify(mUceController, never()).onDestroy();
+
+ // Updates with different subIds should trigger the creation of a new controller.
+ uceCtrlManager.onAssociatedSubscriptionUpdated(mSubId + 1);
+ verify(mUceController).onDestroy();
+ }
+
+ @Test
+ public void testSubIdAndCarrierConfigUpdateWithInvalidSubId() throws Exception {
+ UceControllerManager uceCtrlManager = getUceControllerManager();
+
+ // Updates with the same subId should not destroy the UceController
+ uceCtrlManager.onCarrierConfigChanged();
+ verify(mUceController, never()).onDestroy();
+
+ // Updates with invalid subscription ID
+ uceCtrlManager.onAssociatedSubscriptionUpdated(-1);
+
+ verify(mUceController).onDestroy();
+ assertNull(uceCtrlManager.getUceController());
+ }
+
+ @Test
+ public void testRequestCapabilitiesWithRcsUnavailable() throws Exception {
+ UceControllerManager uceCtrlManager = getUceControllerManager();
+ doReturn(true).when(mUceController).isUnavailable();
+ uceCtrlManager.onRcsDisconnected();
+
+ try {
+ List<Uri> contacts = Arrays.asList(Uri.fromParts("sip", "00000", null));
+ IRcsUceControllerCallback callback = Mockito.mock(IRcsUceControllerCallback.class);
+
+ uceCtrlManager.requestCapabilities(contacts, callback);
+
+ fail();
+ } catch (ImsException e) {
+ assertEquals(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE, e.getCode());
+ return;
+ }
+ fail();
+ }
+
+ @Test
+ public void testRequestCapabilitiesWithRcsConnected() throws Exception {
+ UceControllerManager uceCtrlManager = getUceControllerManager();
+ doReturn(false).when(mUceController).isUnavailable();
+ uceCtrlManager.onRcsConnected(mRcsFeatureManager);
+
+ try {
+ List<Uri> contacts = Arrays.asList(Uri.fromParts("sip", "00000", null));
+ IRcsUceControllerCallback callback = Mockito.mock(IRcsUceControllerCallback.class);
+
+ uceCtrlManager.requestCapabilities(contacts, callback);
+
+ verify(mUceController).requestCapabilities(contacts, callback);
+ } catch (ImsException e) {
+ fail();
+ }
+ }
+
+ @Test
+ public void testRequestNetworkAvailability() throws Exception {
+ UceControllerManager uceCtrlManager = getUceControllerManager();
+ doReturn(false).when(mUceController).isUnavailable();
+ uceCtrlManager.onRcsConnected(mRcsFeatureManager);
+
+ Uri contact = Uri.fromParts("sip", "00000", null);
+ IRcsUceControllerCallback callback = Mockito.mock(IRcsUceControllerCallback.class);
+
+ uceCtrlManager.requestNetworkAvailability(contact, callback);
+
+ verify(mUceController).requestAvailability(contact, callback);
+ }
+
+ @Test
+ public void testRequestNetworkAvailabilityWithRcsUnavailable() throws Exception {
+ UceControllerManager uceCtrlManager = getUceControllerManager();
+ doReturn(true).when(mUceController).isUnavailable();
+ uceCtrlManager.onRcsDisconnected();
+
+ try {
+ Uri contact = Uri.fromParts("sip", "00000", null);
+ IRcsUceControllerCallback callback = Mockito.mock(IRcsUceControllerCallback.class);
+ uceCtrlManager.requestNetworkAvailability(contact, callback);
+ fail();
+ } catch (ImsException e) {
+ assertEquals(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE, e.getCode());
+ return;
+ }
+ fail();
+ }
+
+ @Test
+ public void testGetPublishState() throws Exception {
+ UceControllerManager uceCtrlManager = getUceControllerManager();
+ doReturn(false).when(mUceController).isUnavailable();
+ uceCtrlManager.onRcsConnected(mRcsFeatureManager);
+
+ uceCtrlManager.getUcePublishState();
+
+ verify(mUceController).getUcePublishState();
+ }
+
+ @Test
+ public void testGetPublishStateWithRcsUnavailable() throws Exception {
+ UceControllerManager uceCtrlManager = getUceControllerManager();
+ doReturn(true).when(mUceController).isUnavailable();
+ uceCtrlManager.onRcsDisconnected();
+
+ try {
+ uceCtrlManager.getUcePublishState();
+ fail();
+ } catch (ImsException e) {
+ assertEquals(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE, e.getCode());
+ return;
+ }
+ fail();
+ }
+
+ @Test
+ public void testRegisterPublishStateCallback() throws Exception {
+ UceControllerManager uceCtrlManager = getUceControllerManager();
+ IRcsUcePublishStateCallback callback = Mockito.mock(IRcsUcePublishStateCallback.class);
+
+ uceCtrlManager.registerPublishStateCallback(callback);
+
+ verify(mUceController).registerPublishStateCallback(callback);
+ }
+
+ @Test
+ public void testRegisterPublishStateCallbackWithRcsUnavailable() throws Exception {
+ UceControllerManager uceCtrlManager = getUceControllerManager();
+ doReturn(true).when(mUceController).isUnavailable();
+ uceCtrlManager.onRcsDisconnected();
+
+ try {
+ IRcsUcePublishStateCallback callback = Mockito.mock(IRcsUcePublishStateCallback.class);
+ uceCtrlManager.registerPublishStateCallback(callback);
+ fail();
+ } catch (ImsException e) {
+ assertEquals(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE, e.getCode());
+ return;
+ }
+ fail();
+ }
+
+ @Test
+ public void testUnregisterPublishStateCallback() throws Exception {
+ UceControllerManager uceCtrlManager = getUceControllerManager();
+ IRcsUcePublishStateCallback callback = Mockito.mock(IRcsUcePublishStateCallback.class);
+
+ uceCtrlManager.unregisterPublishStateCallback(callback);
+
+ verify(mUceController).unregisterPublishStateCallback(callback);
+ }
+
+ private UceControllerManager getUceControllerManager() {
+ UceControllerManager manager = new UceControllerManager(mContext, mSlotId,
+ mExecutorService, mUceController);
+ return manager;
+ }
+}
diff --git a/tests/src/com/android/services/telephony/rcs/validator/IncomingTransportStateValidatorTest.java b/tests/src/com/android/services/telephony/rcs/validator/IncomingTransportStateValidatorTest.java
new file mode 100644
index 0000000..0e7e1be
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/validator/IncomingTransportStateValidatorTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.services.telephony.rcs.validator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.SipMessage;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class IncomingTransportStateValidatorTest {
+ private static final SipMessage TEST_MESSAGE = new SipMessage(
+ "INVITE sip:bob@biloxi.com SIP/2.0",
+ "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds\n"
+ + "Max-Forwards: 70\n"
+ + "To: Bob <sip:bob@biloxi.com>\n"
+ + "From: Alice <sip:alice@atlanta.com>;tag=1928301774\n"
+ + "Call-ID: a84b4c76e66710@pc33.atlanta.com\n"
+ + "CSeq: 314159 INVITE\n"
+ + "Contact: <sip:alice@pc33.atlanta.com>\n"
+ + "Content-Type: application/sdp\n"
+ + "Content-Length: 142",
+ new byte[0]);
+
+ @Test
+ public void testVerifyMessageAndUpdateState() {
+ IncomingTransportStateValidator validator = new IncomingTransportStateValidator();
+ ValidationResult result = validator.validate(TEST_MESSAGE);
+ assertFalse(result.isValidated);
+ assertEquals(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED,
+ result.restrictedReason);
+
+ validator.open();
+ result = validator.validate(TEST_MESSAGE);
+ assertTrue(result.isValidated);
+
+ validator.close(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED);
+ result = validator.validate(TEST_MESSAGE);
+ assertFalse(result.isValidated);
+ assertEquals(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED,
+ result.restrictedReason);
+ }
+}
diff --git a/tests/src/com/android/services/telephony/rcs/validator/MalformedSipMessageValidatorTest.java b/tests/src/com/android/services/telephony/rcs/validator/MalformedSipMessageValidatorTest.java
new file mode 100644
index 0000000..18b37fc
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/validator/MalformedSipMessageValidatorTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.services.telephony.rcs.validator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.SipMessage;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class MalformedSipMessageValidatorTest {
+
+ @Test
+ public void testValidRequest() {
+ SipMessage msg = new SipMessage(
+ "INVITE sip:bob@biloxi.com SIP/2.0",
+ // Typical Via
+ "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK.TeSt\n"
+ + "Max-Forwards: 70\n"
+ + "To: Bob <sip:bob@biloxi.com>\n"
+ + "From: Alice <sip:alice@atlanta.com>;tag=1928301774\n"
+ + "Call-ID: a84b4c76e66710@pc33.atlanta.com\n"
+ + "CSeq: 314159 INVITE\n"
+ + "Contact: <sip:alice@pc33.atlanta.com>\n"
+ + "Content-Type: application/sdp\n"
+ + "Content-Length: 142",
+ new byte[0]);
+ ValidationResult result = new MalformedSipMessageValidator().validate(msg);
+ assertTrue(result.isValidated);
+ }
+
+ @Test
+ public void testInvalidRequest() {
+ SipMessage msg = new SipMessage(
+ "INVITE sip:bob@biloxi.comSIP/2.0",
+ "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds\n"
+ + "Max-Forwards: 70\n"
+ + "To: Bob <sip:bob@biloxi.com>\n"
+ + "From: Alice <sip:alice@atlanta.com>;tag=1928301774\n"
+ + "Call-ID: a84b4c76e66710@pc33.atlanta.com\n"
+ + "CSeq: 314159 INVITE\n"
+ + "Contact: <sip:alice@pc33.atlanta.com>\n"
+ + "Content-Type: application/sdp\n"
+ + "Content-Length: 142",
+ new byte[0]);
+ ValidationResult result = new MalformedSipMessageValidator().validate(msg);
+ assertFalse(result.isValidated);
+ assertEquals(SipDelegateManager.MESSAGE_FAILURE_REASON_INVALID_START_LINE,
+ result.restrictedReason);
+ }
+
+ @Test
+ public void testInvalidResponse() {
+ SipMessage msg = new SipMessage(
+ "SIP/2.0 200OK",
+ "Via: SIP/2.0/TCP terminal.vancouver.example.com;"
+ + "branch=z9hG4bKwYb6QREiCL\n"
+ + "To: <sip:adam-buddies@pres.vancouver.example.com>;tag=zpNctbZq\n"
+ + "From: <sip:adam@vancouver.example.com>;tag=ie4hbb8t\n"
+ + "Call-ID: cdB34qLToC@terminal.vancouver.example.com\n"
+ + "CSeq: 322723822 SUBSCRIBE\n"
+ + "Contact: <sip:pres.vancouver.example.com>\n"
+ + "Expires: 7200\n"
+ + "Require: eventlist\n"
+ + "Content-Length: 0",
+ new byte[0]);
+ ValidationResult result = new MalformedSipMessageValidator().validate(msg);
+ assertFalse(result.isValidated);
+ assertEquals(SipDelegateManager.MESSAGE_FAILURE_REASON_INVALID_START_LINE,
+ result.restrictedReason);
+ }
+}
diff --git a/tests/src/com/android/services/telephony/rcs/validator/OutgoingTransportStateValidatorTest.java b/tests/src/com/android/services/telephony/rcs/validator/OutgoingTransportStateValidatorTest.java
new file mode 100644
index 0000000..8dbeb9b
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/validator/OutgoingTransportStateValidatorTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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.services.telephony.rcs.validator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.doReturn;
+
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.SipMessage;
+import android.util.ArraySet;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+import com.android.services.telephony.rcs.SipDialog;
+import com.android.services.telephony.rcs.SipSessionTracker;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.Random;
+
+@RunWith(AndroidJUnit4.class)
+public class OutgoingTransportStateValidatorTest extends TelephonyTestBase {
+
+ @Mock
+ private SipSessionTracker mMockSessionTracker;
+ private final Random mRandom = new Random();
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ public void testVerifyMessageInOpenCloseState() {
+ SipMessage testMessage = generateSipRequestForCallId("callId1");
+ OutgoingTransportStateValidator validator =
+ new OutgoingTransportStateValidator(mMockSessionTracker);
+ ValidationResult result = validator.validate(testMessage);
+ assertFalse(result.isValidated);
+ assertEquals(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED,
+ result.restrictedReason);
+
+ validator.open(Collections.singleton("+tag"), Collections.emptySet());
+ validator.restrictFeatureTags(Collections.emptySet());
+ result = validator.validate(testMessage);
+ assertTrue(result.isValidated);
+
+ validator.close(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED);
+ result = validator.validate(testMessage);
+ assertFalse(result.isValidated);
+ assertEquals(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED,
+ result.restrictedReason);
+ }
+
+ @Test
+ public void testVerifyMessageRestricted() {
+ String callId1 = "callId1";
+ String callId2 = "callId2";
+ String callId3 = "callId3";
+ SipMessage testInDialogEarlyMessage = generateSipRequestForCallId(callId1);
+ SipMessage testInDialogConfirmedMessage = generateSipRequestForCallId(callId2);
+ SipMessage testOutOfDialogInvite = generateSipRequestForCallId(callId3);
+ SipMessage testStatelessRequest = generateMessageRequest();
+ ArraySet<SipDialog> inDialogEarlyCallIds = new ArraySet<>(1);
+ inDialogEarlyCallIds.add(SipDialog.fromSipMessage(testInDialogEarlyMessage));
+ ArraySet<String> inDialogConfirmedCallIds = new ArraySet<>();
+ inDialogEarlyCallIds.add(SipDialog.fromSipMessage(testInDialogConfirmedMessage));
+ // For the sake of testing, add the same call id to early and confirmed dialogs, since we
+ // will accept requests for both right now.
+ doReturn(inDialogEarlyCallIds).when(mMockSessionTracker).getEarlyDialogs();
+ doReturn(inDialogConfirmedCallIds).when(mMockSessionTracker).getConfirmedDialogs();
+ OutgoingTransportStateValidator validator =
+ new OutgoingTransportStateValidator(mMockSessionTracker);
+ validator.restrict(
+ SipDelegateManager.MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION);
+
+ // ensure a response to a pending request is not restricted, even if it is not for a tracked
+ // call ID.
+ ValidationResult result = validator.validate(generate200OkResponse("callId4"));
+ assertTrue(result.isValidated);
+
+ // confirm in dialog messages are not restricted
+ result = validator.validate(testInDialogEarlyMessage);
+ assertTrue(result.isValidated);
+ result = validator.validate(testInDialogConfirmedMessage);
+ assertTrue(result.isValidated);
+
+ // confirm out-of-dialog requests are restricted.
+ result = validator.validate(testOutOfDialogInvite);
+ assertFalse(result.isValidated);
+ assertEquals(SipDelegateManager.MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION,
+ result.restrictedReason);
+ result = validator.validate(testStatelessRequest);
+ assertFalse(result.isValidated);
+ assertEquals(SipDelegateManager.MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION,
+ result.restrictedReason);
+ }
+
+ @Test
+ public void testDeniedFeatureTag() {
+ SipMessage testMessage = generateSipRequestForCallId("callId1");
+ OutgoingTransportStateValidator validator =
+ new OutgoingTransportStateValidator(mMockSessionTracker);
+ ValidationResult result = validator.validate(testMessage);
+ assertFalse(result.isValidated);
+ assertEquals(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED,
+ result.restrictedReason);
+
+ // Assert that invites associated with denied tags are denied.
+ validator.open(Collections.emptySet(), Collections.singleton("+tag"));
+ validator.restrictFeatureTags(Collections.emptySet());
+ result = validator.validate(testMessage);
+ assertFalse(result.isValidated);
+ assertEquals(SipDelegateManager.MESSAGE_FAILURE_REASON_INVALID_FEATURE_TAG,
+ result.restrictedReason);
+ }
+
+ @Test
+ public void testRestrictedFeatureTag() {
+ SipMessage testMessage = generateSipRequestForCallId("callId1");
+ OutgoingTransportStateValidator validator =
+ new OutgoingTransportStateValidator(mMockSessionTracker);
+ ValidationResult result = validator.validate(testMessage);
+ assertFalse(result.isValidated);
+ assertEquals(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED,
+ result.restrictedReason);
+
+ validator.open(Collections.singleton("+tag"), Collections.emptySet());
+ // Ensure that when supported tags are restricted, the message is not validated.
+ validator.restrictFeatureTags(Collections.singleton("+tag"));
+ result = validator.validate(testMessage);
+ assertFalse(result.isValidated);
+ assertEquals(SipDelegateManager.MESSAGE_FAILURE_REASON_INVALID_FEATURE_TAG,
+ result.restrictedReason);
+ }
+
+ @Test
+ public void testNoSupportedFeatureTag() {
+ SipMessage testMessage = generateSipRequestForCallId("callId1");
+ OutgoingTransportStateValidator validator =
+ new OutgoingTransportStateValidator(mMockSessionTracker);
+ ValidationResult result = validator.validate(testMessage);
+ assertFalse(result.isValidated);
+ assertEquals(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED,
+ result.restrictedReason);
+
+ // Assert if a message doesn't have any related supported tags, it should be denied
+ validator.open(Collections.emptySet(), Collections.emptySet());
+ validator.restrictFeatureTags(Collections.emptySet());
+ result = validator.validate(testMessage);
+ assertFalse(result.isValidated);
+ assertEquals(SipDelegateManager.MESSAGE_FAILURE_REASON_INVALID_FEATURE_TAG,
+ result.restrictedReason);
+
+ // responses and non-dialog starting messages do not matter
+ result = validator.validate(generate200OkResponse("callId2"));
+ assertTrue(result.isValidated);
+ result = validator.validate(generateMessageRequest());
+ assertTrue(result.isValidated);
+ }
+
+ /**
+ * @return A INVITE with the call ID specified. Note: this request is not technically valid, but
+ * only contains the relevant headers for testing.
+ */
+ private SipMessage generateSipRequestForCallId(String callId) {
+ return new SipMessage(
+ "INVITE sip:b@client.example.com SIP/2.0",
+ "Via: SIP/2.0/UDP client.example.com;branch=z9hG4bK776asdhds\n"
+ + "To: B <sip:b@example.com>\n"
+ + "From: A <sip:a@example.com>;tag=1928301774\n"
+ + "Accept-Contact: *;+tag\n"
+ + "Call-ID: " + callId,
+ new byte[0]);
+ }
+
+ /**
+ * @return A MESSAGE request. Note: this request is not technically valid, but only contains the
+ * relevant headers for testing.
+ */
+ private SipMessage generateMessageRequest() {
+ return new SipMessage(
+ "MESSAGE sip:b@client.example.com SIP/2.0",
+ "Via: SIP/2.0/UDP client.example.com;branch=z9hG4bK776asdhds\n"
+ + "To: B <sip:b@example.com>\n"
+ + "From: A <sip:a@example.com>;tag=1928301774\n",
+ new byte[0]);
+ }
+
+ /**
+ * @return A 200 OK associated with the supplied call ID.
+ */
+ private SipMessage generate200OkResponse(String callId) {
+ return new SipMessage(
+ "SIP/2.0 200 OK",
+ "Via: SIP/2.0/UDP client.example.com;branch=z9hG4bK776asdhds\n"
+ + "To: B <sip:b@example.com>\n"
+ + "From: A <sip:a@example.com>;tag=1928301774\n"
+ + "Call-ID: " + callId,
+ new byte[0]);
+ }
+}
diff --git a/tests/src/com/android/services/telephony/rcs/validator/RestrictedOutgoingSipRequestValidatorTest.java b/tests/src/com/android/services/telephony/rcs/validator/RestrictedOutgoingSipRequestValidatorTest.java
new file mode 100644
index 0000000..a90aaeb
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/validator/RestrictedOutgoingSipRequestValidatorTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.services.telephony.rcs.validator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.SipMessage;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RestrictedOutgoingSipRequestValidatorTest {
+
+ @Test
+ public void testRegisterNotAllowed() {
+ SipMessage msg = new SipMessage(
+ "REGISTER sip:bob@biloxi.com SIP/2.0",
+ // Not representative of real REGISTER message, but close enough for validation.
+ "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds\n"
+ + "Max-Forwards: 70\n"
+ + "To: Bob <sip:bob@biloxi.com>\n"
+ + "From: Alice <sip:alice@atlanta.com>;tag=1928301774\n"
+ + "CSeq: 314159 REGISTER\n"
+ + "Contact: <sip:alice@pc33.atlanta.com>\n",
+ new byte[0]);
+ ValidationResult result = new RestrictedOutgoingSipRequestValidator().validate(msg);
+ assertFalse(result.isValidated);
+ assertEquals(SipDelegateManager.MESSAGE_FAILURE_REASON_INVALID_START_LINE,
+ result.restrictedReason);
+ }
+
+ @Test
+ public void testPublishNotAllowed() {
+ SipMessage msg = new SipMessage(
+ "PUBLISH sip:bob@biloxi.com SIP/2.0",
+ // Not representative of real REGISTER message, but close enough for validation.
+ "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds\n"
+ + "Max-Forwards: 70\n"
+ + "To: Bob <sip:bob@biloxi.com>\n"
+ + "From: Alice <sip:alice@atlanta.com>;tag=1928301774\n"
+ + "CSeq: 314159 PUBLISH\n"
+ + "Contact: <sip:alice@pc33.atlanta.com>\n",
+ new byte[0]);
+ ValidationResult result = new RestrictedOutgoingSipRequestValidator().validate(msg);
+ assertFalse(result.isValidated);
+ assertEquals(SipDelegateManager.MESSAGE_FAILURE_REASON_INVALID_START_LINE,
+ result.restrictedReason);
+
+ }
+
+ @Test
+ public void testOptionsNotAllowed() {
+ SipMessage msg = new SipMessage(
+ "OPTIONS sip:bob@biloxi.com SIP/2.0",
+ // Not representative of real REGISTER message, but close enough for validation.
+ "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds\n"
+ + "Max-Forwards: 70\n"
+ + "To: Bob <sip:bob@biloxi.com>\n"
+ + "From: Alice <sip:alice@atlanta.com>;tag=1928301774\n"
+ + "CSeq: 314159 OPTIONS\n"
+ + "Contact: <sip:alice@pc33.atlanta.com>\n",
+ new byte[0]);
+ ValidationResult result = new RestrictedOutgoingSipRequestValidator().validate(msg);
+ assertFalse(result.isValidated);
+ assertEquals(SipDelegateManager.MESSAGE_FAILURE_REASON_INVALID_START_LINE,
+ result.restrictedReason);
+
+ }
+
+ @Test
+ public void testInviteAllowed() {
+ SipMessage msg = new SipMessage(
+ "INVITE sip:bob@biloxi.com SIP/2.0",
+ "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds\n"
+ + "Max-Forwards: 70\n"
+ + "To: Bob <sip:bob@biloxi.com>\n"
+ + "From: Alice <sip:alice@atlanta.com>;tag=1928301774\n"
+ + "Call-ID: a84b4c76e66710@pc33.atlanta.com\n"
+ + "CSeq: 314159 INVITE\n"
+ + "Contact: <sip:alice@pc33.atlanta.com>\n"
+ + "Content-Type: application/sdp\n"
+ + "Content-Length: 142",
+ new byte[0]);
+ ValidationResult result = new RestrictedOutgoingSipRequestValidator().validate(msg);
+ assertTrue(result.isValidated);
+ }
+
+ @Test
+ public void testResponseAllowed() {
+ SipMessage msg = new SipMessage(
+ "SIP/2.0 200 OK",
+ "Via: SIP/2.0/TCP terminal.vancouver.example.com;"
+ + "branch=z9hG4bKwYb6QREiCL\n"
+ + "To: <sip:adam-buddies@pres.vancouver.example.com>;tag=zpNctbZq\n"
+ + "From: <sip:adam@vancouver.example.com>;tag=ie4hbb8t\n"
+ + "Call-ID: cdB34qLToC@terminal.vancouver.example.com\n"
+ + "CSeq: 322723822 SUBSCRIBE\n"
+ + "Contact: <sip:pres.vancouver.example.com>\n"
+ + "Expires: 7200\n"
+ + "Require: eventlist\n"
+ + "Content-Length: 0",
+ new byte[0]);
+ ValidationResult result = new RestrictedOutgoingSipRequestValidator().validate(msg);
+ assertTrue(result.isValidated);
+ }
+}
diff --git a/tests/src/com/android/services/telephony/rcs/validator/RestrictedOutgoingSubscribeValidatorTest.java b/tests/src/com/android/services/telephony/rcs/validator/RestrictedOutgoingSubscribeValidatorTest.java
new file mode 100644
index 0000000..9864872
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/validator/RestrictedOutgoingSubscribeValidatorTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.services.telephony.rcs.validator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.SipMessage;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RestrictedOutgoingSubscribeValidatorTest {
+
+ @Test
+ public void testValidUnrelatedRequest() {
+ SipMessage msg = new SipMessage(
+ "INVITE sip:bob@biloxi.com SIP/2.0",
+ // Typical Via
+ "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK.TeSt\n"
+ + "Max-Forwards: 70\n"
+ + "To: Bob <sip:bob@biloxi.com>\n"
+ + "From: Alice <sip:alice@atlanta.com>;tag=1928301774\n"
+ + "Call-ID: a84b4c76e66710@pc33.atlanta.com\n"
+ + "CSeq: 314159 INVITE\n"
+ + "Contact: <sip:alice@pc33.atlanta.com>\n"
+ + "Content-Type: application/sdp\n"
+ + "Content-Length: 142",
+ new byte[0]);
+ ValidationResult result = new MalformedSipMessageValidator().validate(msg);
+ assertTrue(result.isValidated);
+ }
+
+ @Test
+ public void testValidUnrelatedResponse() {
+ SipMessage msg = new SipMessage(
+ "SIP/2.0 200 OK",
+ "Via: SIP/2.0/TCP terminal.vancouver.example.com;"
+ + "branch=z9hG4bKwYb6QREiCL\n"
+ + "To: <sip:adam-buddies@pres.vancouver.example.com>;tag=zpNctbZq\n"
+ + "From: <sip:adam@vancouver.example.com>;tag=ie4hbb8t\n"
+ + "Call-ID: cdB34qLToC@terminal.vancouver.example.com\n"
+ + "CSeq: 322723822 SUBSCRIBE\n"
+ + "Contact: <sip:pres.vancouver.example.com>\n"
+ + "Expires: 7200\n"
+ + "Require: eventlist\n"
+ + "Content-Length: 0",
+ new byte[0]);
+ ValidationResult result = new MalformedSipMessageValidator().validate(msg);
+ assertTrue(result.isValidated);
+ }
+
+ @Test
+ public void testValidSubscribeRequest() {
+ SipMessage msg = new SipMessage(
+ "SUBSCRIBE sip:joe@example.com SIP/2.0",
+ "Via: SIP/2.0/UDP app.example.com;branch=z9hG4bKnashds7\n"
+ + "From: sip:app.example.com;tag=123aa9\n"
+ + "To: sip:joe@example.com\n"
+ + "Call-ID: 9987@app.example.com\n"
+ + "CSeq: 9887 SUBSCRIBE\n"
+ + "Contact: sip:app.example.com\n"
+ + "Event: conference \n"
+ + "Max-Forwards: 70\n",
+ new byte[0]);
+ ValidationResult result = new RestrictedOutgoingSubscribeValidator().validate(msg);
+ assertTrue(result.isValidated);
+ }
+
+ @Test
+ public void testInvalidSubscribeRequest() {
+ SipMessage msg = new SipMessage(
+ "SUBSCRIBE sip:joe@example.com SIP/2.0",
+ "Via: SIP/2.0/UDP app.example.com;branch=z9hG4bKnashds7\n"
+ + "From: sip:app.example.com;tag=123aa9\n"
+ + "To: sip:joe@example.com\n"
+ + "Call-ID: 9987@app.example.com\n"
+ + "CSeq: 9887 SUBSCRIBE\n"
+ + "Contact: sip:app.example.com\n"
+ + "Event: presence \n"
+ + "Max-Forwards: 70\n",
+ new byte[0]);
+ ValidationResult result = new RestrictedOutgoingSubscribeValidator().validate(msg);
+ assertFalse(result.isValidated);
+ assertEquals(SipDelegateManager.MESSAGE_FAILURE_REASON_INVALID_HEADER_FIELDS,
+ result.restrictedReason);
+
+ }
+}