[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: 41916c2f6b -s ours
am skip reason: subject contains skip directive
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Telephony/+/14295395
Change-Id: Id39d8e844160026248e5b26a2810a78a5ab90453
diff --git a/Android.bp b/Android.bp
index 1887a8a..4357f12 100644
--- a/Android.bp
+++ b/Android.bp
@@ -15,6 +15,23 @@
// Build the Phone app which includes the emergency dialer. See Contacts
// for the 'other' dialer.
+package {
+ default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "packages_services_Telephony_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
android_app {
name: "TeleService",
@@ -35,6 +52,7 @@
"com.android.phone.common-lib",
"guava",
"PlatformProperties",
+ "modules-utils-os",
],
srcs: [
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 340aab7..014fc17 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -40,6 +40,7 @@
<protected-broadcast android:name="android.provider.Telephony.SIM_FULL" />
<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" />
@@ -83,6 +84,7 @@
<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.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 +94,19 @@
<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" />
<!-- 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" />
@@ -211,13 +222,15 @@
<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"/>
<application android:name="PhoneApp"
android:persistent="true"
@@ -236,11 +249,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" />
@@ -268,6 +293,7 @@
android:label="@string/simContacts_title"
android:theme="@style/SimImportTheme"
android:screenOrientation="portrait"
+ android:exported="true"
android:icon="@mipmap/ic_launcher_contacts">
<intent-filter>
@@ -279,6 +305,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" />
@@ -293,6 +320,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" />
@@ -301,6 +329,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" />
@@ -310,6 +339,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" />
@@ -319,6 +359,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" />
@@ -328,6 +369,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" />
@@ -337,6 +379,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" />
@@ -346,6 +389,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" />
@@ -355,6 +399,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" />
@@ -382,6 +427,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" />
@@ -394,6 +440,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" />
@@ -410,6 +457,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" />
@@ -424,6 +472,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" />
@@ -433,6 +482,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">
@@ -453,6 +503,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">
@@ -468,6 +519,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">
@@ -487,6 +539,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">
@@ -500,6 +553,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" />
@@ -512,33 +566,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" />
@@ -551,7 +599,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>
@@ -568,6 +617,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" />
@@ -579,6 +629,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. -->
@@ -596,6 +647,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" />
@@ -638,12 +690,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" />
@@ -653,6 +707,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" />
@@ -662,6 +717,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..e095b89 100644
--- a/OWNERS
+++ b/OWNERS
@@ -13,3 +13,4 @@
dbright@google.com
xiaotonj@google.com
+per-file *SimPhonebookProvider* = file:platform/packages/apps/Contacts:/OWNERS
diff --git a/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/res/layout/radio_info.xml b/res/layout/radio_info.xml
index 71cd32a..72627a3 100644
--- a/res/layout/radio_info.xml
+++ b/res/layout/radio_info.xml
@@ -334,11 +334,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..74169e6 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -95,7 +95,7 @@
<string name="sum_loading_settings" msgid="434063780286688775">"Laai tans instellings..."</string>
<string name="sum_hide_caller_id" msgid="131100328602371933">"Nommer versteek in uitgaande oproepe"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"Nommer wat vertoon in uitgaande oproepe"</string>
- <string name="sum_default_caller_id" msgid="1767070797135682959">"Gebruik verstekbeheerinstellings om my nommer te vertoon in uitgaande oproepe"</string>
+ <string name="sum_default_caller_id" msgid="1767070797135682959">"Gebruik verstek beheerinstellings om my nommer te vertoon in uitgaande oproepe"</string>
<string name="labelCW" msgid="8449327023861428622">"Oproep wag"</string>
<string name="sum_cw_enabled" msgid="3977308526187139996">"Stel my tydens \'n oproep in kennis van inkomende oproepe"</string>
<string name="sum_cw_disabled" msgid="3658094589461768637">"Stel my tydens \'n oproep in kennis van inkomende oproepe"</string>
@@ -141,7 +141,7 @@
<string name="close_dialog" msgid="1074977476136119408">"OK"</string>
<string name="enable" msgid="2636552299455477603">"Skakel aan"</string>
<string name="disable" msgid="1122698860799462116">"Skakel af"</string>
- <string name="change_num" msgid="6982164494063109334">"Dateer op"</string>
+ <string name="change_num" msgid="6982164494063109334">"Opdateer"</string>
<string-array name="clir_display_values">
<item msgid="8477364191403806960">"Netwerkverstek"</item>
<item msgid="6813323051965618926">"Versteek nommer"</item>
@@ -307,7 +307,9 @@
<string name="throttle_time_frame" msgid="1813452485948918791">"Datagebruiksperiode"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Datatempobeleid"</string>
<string name="throttle_help" msgid="2624535757028809735">"Kom meer te wete"</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>٪) van <xliff:g id="USED_2">%3$s</xliff:g> periode se maksimum\nVolgende periode begin oor <xliff:g id="USED_3">%4$d</xliff:g> dae (<xliff:g id="USED_4">%5$s</xliff:g>)"</string>
+ <!-- String.format failed for translation -->
+ <!-- no translation found for throttle_status_subtext (1110276415078236687) -->
+ <skip />
<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>٪) van <xliff:g id="USED_2">%3$s</xliff:g> tydperk se maksimum"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"<xliff:g id="USED_0">%1$s</xliff:g> maksimum oorskry\nDatatempo verminder na <xliff:g id="USED_1">%2$d</xliff:g> Kb/s"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g>٪ van siklus het verloop\nVolgende periode begin oor <xliff:g id="USED_1">%2$d</xliff:g> dae (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -637,12 +639,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Ja"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Nee"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Maak toe"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Die foon is in noodterugbelmodus"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Tot <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">Die foon sal in noodterugbelmodus wees vir <xliff:g id="COUNT_1">%s</xliff:g> minute.\nWil jy nou uitgaan?</item>
- <item quantity="one">Die foon sal in noodterugbelmodus wees vir <xliff:g id="COUNT_0">%s</xliff:g> minuut.\nWil jy nou uitgaan?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Diens"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Opstelling"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Nie gestel nie>"</string>
@@ -898,11 +894,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Herlaai"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Wissel DNS-kontrole"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-spesifieke inligting/instellings"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC is beskikbaar:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR is beperk:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR is beskikbaar:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR-status:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR-frekwensie:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Stel radiobandmodus"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Laai tans bandlys …"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Stel"</string>
diff --git a/res/values-am/arrays.xml b/res/values-am/arrays.xml
index 9d43d58..f5cfebf 100644
--- a/res/values-am/arrays.xml
+++ b/res/values-am/arrays.xml
@@ -18,7 +18,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string-array name="preferred_network_mode_choices_world_mode">
- <item msgid="8359756926680194770">"ዓለምአቀፍ"</item>
+ <item msgid="8359756926680194770">"አለምአቀፍ"</item>
<item msgid="7497128470871581354">"LTE / CDMA"</item>
<item msgid="1732367262765147258">"LTE / GSM / UMTS"</item>
</string-array>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index b9fbebc..cecf2aa 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -74,7 +74,7 @@
<string name="phone_accounts_configure_account_settings" msgid="6622119715253196586">"የመለያ ቅንብሮችን ይውቀሩ"</string>
<string name="phone_accounts_all_calling_accounts" msgid="1609600743500618823">"የሁሉም ደዋይ መለያዎች"</string>
<string name="phone_accounts_all_calling_accounts_summary" msgid="2214134955430107240">"የትኛዎቹ መለያዎች ጥሪዎችን ማድረግ እንደሚችሉ ይምረጡ"</string>
- <string name="wifi_calling" msgid="3650509202851355742">"የWi-Fi ጥሪ"</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>
@@ -236,7 +236,7 @@
<string name="preferred_network_mode_lte_gsm_wcdma_summary" msgid="2217794334331254936">"ተመራጭ የአውታረመረብ ሁኔታ: GSM/WCDMA/LTE"</string>
<string name="preferred_network_mode_lte_cdma_evdo_summary" msgid="5559198623419981805">"ተመራጭ የአውታረመረብ ሁኔታ ፡ CDMA+LTE/EVDO"</string>
<string name="preferred_network_mode_lte_cdma_evdo_gsm_wcdma_summary" msgid="6707224437925495615">"የተመረጠው የአውታረ መረብ ሁነታ፦ LTE/CDMA/EvDo/GSM/WCDMA"</string>
- <string name="preferred_network_mode_global_summary" msgid="3847086258439582411">"ተመራጭ የአውታረመረብ ፡ ዓለምአቀፍ"</string>
+ <string name="preferred_network_mode_global_summary" msgid="3847086258439582411">"ተመራጭ የአውታረመረብ ፡ አለምአቀፍ"</string>
<string name="preferred_network_mode_lte_wcdma_summary" msgid="7001804022020813865">"ተመራጭ የአውታረመረብ ሁኔታ፡ LTE / WCDMA"</string>
<string name="preferred_network_mode_lte_gsm_umts_summary" msgid="6484203890156282179">"ተመራጭ የአውታረ መረብ ሁነታ፦ LTE / GSM / UMTS"</string>
<string name="preferred_network_mode_lte_cdma_summary" msgid="8187929456614068518">"ተመራጭ የአውታረመረብ ፡LTE / CDMA ሁነታ"</string>
@@ -403,7 +403,7 @@
<string name="network_recommended" msgid="3444321100580250926">" (የሚመከር)"</string>
<string name="network_lte" msgid="7206879277095094280">"LTE (የሚመከር)"</string>
<string name="network_4G" msgid="6800527815504223913">"4G (የሚመከር)"</string>
- <string name="network_global" msgid="3289646154407617631">"ዓለምአቀፍ"</string>
+ <string name="network_global" msgid="3289646154407617631">"አለምአቀፍ"</string>
<string name="cdma_system_select_title" msgid="614165233552656431">"የሥርዓት ምርጫ"</string>
<string name="cdma_system_select_summary" msgid="3840420390242060407">"የCDMA በእንቅስቃሴ ላይ ሁኔታ ለውጥ"</string>
<string name="cdma_system_select_dialogtitle" msgid="5524639510676501802">"የሥርዓት ምርጫ"</string>
@@ -560,7 +560,7 @@
<string name="police_type_description" msgid="2819533883972081757">"ፖሊስ"</string>
<string name="ambulance_type_description" msgid="6798237503553180461">"አምቡላንስ"</string>
<string name="fire_type_description" msgid="6565200468934914930">"እሳት"</string>
- <string name="description_concat_format" msgid="2014471565101724088">"%1$s, %2$s"</string>
+ <!-- format error in translation for description_concat_format (2014471565101724088) -->
<string name="dialerKeyboardHintText" msgid="1115266533703764049">"ለመደወል የሰሌዳ ቁልፍ ተጠቀም"</string>
<string name="onscreenHoldText" msgid="4025348842151665191">"ያዝ"</string>
<string name="onscreenEndCallText" msgid="6138725377654842757">"መጨረሻ"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"አዎ"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"አይ"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"አሰናብት"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"ስልኩ የአደጋ ጊዜ ተዘዋዋሪ ጥሪ ሁነታ ውስጥ ነው ያለው።"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"እስከ <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">ስልኩ ለ<xliff:g id="COUNT_1">%s</xliff:g> ደቂቃዎች የአደጋ ጊዜ ተዘዋዋሪ ጥሪ ሁነታ ውስጥ ይሆናል።\nአሁን መውጣት ይፈልጋሉ?</item>
- <item quantity="other">ስልኩ ለ<xliff:g id="COUNT_1">%s</xliff:g> ደቂቃዎች የአደጋ ጊዜ ተዘዋዋሪ ጥሪ ሁነታ ውስጥ ይሆናል።\nአሁን መውጣት ይፈልጋሉ?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"አገልግሎት"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"አዋቅር"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<አልተዘጋጀም >"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"አድስ"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"የDNS ፍተሻን ቀያይር"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-የተወሰነ መረጃ/ቅንብሮች"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC ይገኛል፦"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR ተገድቧል፦"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR ይገኛል፦"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR ሁኔታ፦"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR ድግግሞሽ፦"</string>
<string name="band_mode_title" msgid="7988822920724576842">"የሬዲዮ ባንድ ሁነታን ያቀናጁ"</string>
<string name="band_mode_loading" msgid="795923726636735967">"የባንድ ዝርዝርን በመጫን ላይ…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"አዋቅር"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 8f89ae9..9e0bad9 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -33,7 +33,7 @@
<string name="cancel" msgid="8984206397635155197">"إلغاء"</string>
<string name="enter_input" msgid="6193628663039958990">"يجب أن تتراوح حروف رسالة USSD بين <xliff:g id="MIN_LEN">%1$d</xliff:g> و<xliff:g id="MAX_LEN">%2$d</xliff:g>. يُرجى إعادة المحاولة."</string>
<string name="manageConferenceLabel" msgid="8415044818156353233">"إدارة مكالمة جماعية"</string>
- <string name="ok" msgid="7818974223666140165">"حسنًا"</string>
+ <string name="ok" msgid="7818974223666140165">"موافق"</string>
<string name="audio_mode_speaker" msgid="243689733219312360">"مكبر الصوت"</string>
<string name="audio_mode_earpiece" msgid="2823700267171134282">"سماعة الأذن للهاتف"</string>
<string name="audio_mode_wired_headset" msgid="5028010823105817443">"سماعة رأس سلكية"</string>
@@ -71,7 +71,7 @@
<string name="phone_accounts_choose_accounts" msgid="4748805293314824974">"اختيار الحسابات"</string>
<string name="phone_accounts_selection_header" msgid="2945830843104108440">"حسابات الهاتف"</string>
<string name="phone_accounts_add_sip_account" msgid="1437634802033309305">"إضافة حساب SIP"</string>
- <string name="phone_accounts_configure_account_settings" msgid="6622119715253196586">"ضبط إعدادات الحساب"</string>
+ <string name="phone_accounts_configure_account_settings" msgid="6622119715253196586">"تهيئة إعدادات الحساب"</string>
<string name="phone_accounts_all_calling_accounts" msgid="1609600743500618823">"كل حسابات الاتصال"</string>
<string name="phone_accounts_all_calling_accounts_summary" msgid="2214134955430107240">"حدد الحسابات التي يمكنها إجراء مكالمات"</string>
<string name="wifi_calling" msgid="3650509202851355742">"الاتصال عبر Wi-Fi"</string>
@@ -118,7 +118,7 @@
<string name="sum_cfnry_enabled" msgid="3000500837493854799">"إعادة التوجيه إلى <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnry_disabled" msgid="1990563512406017880">"غير مفعّل"</string>
<string name="disable_cfnry_forbidden" msgid="3174731413216550689">"لا يتيح مشغل شبكة الجوال إيقاف اعادة توجيه المكالمة عند عدم رد هاتفك."</string>
- <string name="labelCFNRc" msgid="4163399350778066013">"عند تعذر الوصول"</string>
+ <string name="labelCFNRc" msgid="4163399350778066013">"عند عدم الوصول"</string>
<string name="messageCFNRc" msgid="6980340731313007250">"الرقم عند عدم الوصول"</string>
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"إعادة التوجيه إلى <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"غير مفعّل"</string>
@@ -128,7 +128,7 @@
<string name="call_settings_with_label" msgid="8460230435361579511">"الإعدادات (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="error_updating_title" msgid="2024290892676808965">"خطأ في إعدادات الاتصال"</string>
<string name="reading_settings" msgid="1605904432450871183">"جارٍ قراءة الإعدادات..."</string>
- <string name="updating_settings" msgid="3650396734816028808">"جارٍ تعديل الإعدادات..."</string>
+ <string name="updating_settings" msgid="3650396734816028808">"جارٍ إعادة تحميل الإعدادات..."</string>
<string name="reverting_settings" msgid="7378668837291012205">"جارٍ إعادة الإعدادات..."</string>
<string name="response_error" msgid="3904481964024543330">"رد غير متوقع من الشبكة."</string>
<string name="exception_error" msgid="330994460090467">"خطأ في الشبكة أو في شريحة SIM"</string>
@@ -136,12 +136,12 @@
<string name="stk_cc_ss_to_ussd_error" msgid="8330749347425752192">"تم تغيير طلب SS إلى طلب USSD"</string>
<string name="stk_cc_ss_to_ss_error" msgid="8297155544652134278">"تم التغيير إلى طلب SS جديد"</string>
<string name="stk_cc_ss_to_dial_video_error" msgid="4255261231466032505">"تم تغيير طلب SS إلى مكالمة فيديو"</string>
- <string name="fdn_check_failure" msgid="1833769746374185247">"تم تفعيل إعداد أرقام الاتصال الثابتة بالتطبيق المثبت على هاتفك. ونتيجة لذلك، لن تعمل بعض الميزات المرتبطة بالمكالمات."</string>
- <string name="radio_off_error" msgid="8321564164914232181">"فعِّل اللاسلكي قبل عرض هذه الإعدادات."</string>
- <string name="close_dialog" msgid="1074977476136119408">"حسنًا"</string>
+ <string name="fdn_check_failure" msgid="1833769746374185247">"تم تشغيل إعداد أرقام الاتصال الثابتة بالتطبيق المثبت على هاتفك. ونتيجة لذلك، لن تعمل بعض الميزات المرتبطة بالمكالمات."</string>
+ <string name="radio_off_error" msgid="8321564164914232181">"شغّل اللاسلكي قبل عرض هذه الإعدادات."</string>
+ <string name="close_dialog" msgid="1074977476136119408">"موافق"</string>
<string name="enable" msgid="2636552299455477603">"تفعيل"</string>
<string name="disable" msgid="1122698860799462116">"إيقاف"</string>
- <string name="change_num" msgid="6982164494063109334">"تحديث"</string>
+ <string name="change_num" msgid="6982164494063109334">"إعادة تحميل"</string>
<string-array name="clir_display_values">
<item msgid="8477364191403806960">"الشبكة التلقائية"</item>
<item msgid="6813323051965618926">"إخفاء الرقم"</item>
@@ -174,7 +174,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>
@@ -294,7 +294,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">"بيانات الجوّال"</string>
<string name="mobile_data_settings_summary" msgid="5012570152029118471">"الوصول إلى البيانات باستخدام شبكة الجوّال"</string>
- <string name="data_usage_disable_mobile" msgid="5669109209055988308">"هل تريد إيقاف بيانات الجوّال؟"</string>
+ <string name="data_usage_disable_mobile" msgid="5669109209055988308">"هل تريد إيقاف تفعيل بيانات الجوّال؟"</string>
<string name="sim_selection_required_pref" msgid="6985901872978341314">"يلزم التحديد"</string>
<string name="sim_change_data_title" msgid="9142726786345906606">"هل تريد تغيير شريحة SIM للبيانات؟"</string>
<string name="sim_change_data_message" msgid="3567358694255933280">"هل تريد استخدام <xliff:g id="NEW_SIM">%1$s</xliff:g> بدلاً من <xliff:g id="OLD_SIM">%2$s</xliff:g> لبيانات الجوّال؟"</string>
@@ -344,7 +344,7 @@
<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>
@@ -459,19 +459,19 @@
<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="fdn_contact_updated" msgid="6876330243323118937">"تم تحديث رقم الاتصال الثابت."</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>
<string name="fdn_contact_deleted" msgid="1680714996763848838">"تم حذف رقم الاتصال الثابت."</string>
- <string name="pin2_invalid" msgid="2313954262684494442">"لم يتم تحديث FDN لأنك كتبت رقم تعريف شخصي غير صحيح."</string>
- <string name="fdn_invalid_number" msgid="9067189814657840439">"لم يتم تحديث FDN نظرًا لأن الرقم يتجاوز طوله <xliff:g id="FDN_NUMBER_LIMIT_LENGTH">%d</xliff:g> رقمًا."</string>
- <string name="pin2_or_fdn_invalid" msgid="7542639487955868181">"لم يتم تحديث FDN. رقم PIN2 غير صحيح، أو تم رفض رقم الهاتف."</string>
+ <string name="pin2_invalid" msgid="2313954262684494442">"لم يتم إعادة تحميل FDN لأنك كتبت رقم تعريف شخصي غير صحيح."</string>
+ <string name="fdn_invalid_number" msgid="9067189814657840439">"لم يتم إعادة تحميل FDN نظرًا لأن الرقم يتجاوز طوله <xliff:g id="FDN_NUMBER_LIMIT_LENGTH">%d</xliff:g> رقمًا."</string>
+ <string name="pin2_or_fdn_invalid" msgid="7542639487955868181">"لم يتم إعادة تحميل FDN. رقم PIN2 غير صحيح، أو تم رفض رقم الهاتف."</string>
<string name="fdn_failed" msgid="216592346853420250">"تعذّر إتمام عملية FDN!"</string>
<string name="simContacts_emptyLoading" msgid="4989040293858675483">"جارٍ القراءة من شريحة SIM..."</string>
<string name="simContacts_empty" msgid="1135632055473689521">"ليس هناك جهات اتصال على شريحة SIM."</string>
<string name="simContacts_title" msgid="2714029230160136647">"حدد جهات اتصال لاستيرادها"</string>
- <string name="simContacts_airplaneMode" msgid="4654884030631503808">"عليك إيقاف وضع الطيران لاستيراد جهات الاتصال من شريحة SIM."</string>
+ <string name="simContacts_airplaneMode" msgid="4654884030631503808">"عليك إيقاف وضع الطائرة لاستيراد جهات الاتصال من شريحة SIM."</string>
<string name="enable_pin" msgid="967674051730845376">"تفعيل/إيقاف رمز PIN لبطاقة SIM"</string>
<string name="change_pin" msgid="3657869530942905790">"تغيير رمز PIN لبطاقة SIM"</string>
<string name="enter_pin_text" msgid="3182311451978663356">"رمز PIN لبطاقة SIM:"</string>
@@ -484,10 +484,10 @@
<string name="disable_sim_pin" msgid="3112303905548613752">"محو رقم التعريف الشخصي لبطاقة SIM"</string>
<string name="enable_sim_pin" msgid="445461050748318980">"تعيين رقم التعريف الشخصي لبطاقة SIM"</string>
<string name="enable_in_progress" msgid="4135305985717272592">"جارٍ تعيين رقم التعريف الشخصي…"</string>
- <string name="enable_pin_ok" msgid="2877428038280804256">"تم ضبط رقم التعريف الشخصي"</string>
+ <string name="enable_pin_ok" msgid="2877428038280804256">"تم تعيين رقم التعريف الشخصي"</string>
<string name="disable_pin_ok" msgid="888505244389647754">"تم محو رقم التعريف الشخصي"</string>
<string name="pin_failed" msgid="4527347792881939652">"رقم التعريف الشخصي غير صحيح"</string>
- <string name="pin_changed" msgid="7291153750090452808">"تم تحديث رقم التعريف الشخصي"</string>
+ <string name="pin_changed" msgid="7291153750090452808">"تمت إعادة تحميل رقم التعريف الشخصي"</string>
<string name="puk_requested" msgid="2061337960609806851">"كلمة المرور غير صحيحة. تم قفل رقم التعريف الشخصي الآن. رمز PUK مطلوب."</string>
<string name="enter_pin2_text" msgid="7266379426804295979">"PIN2"</string>
<string name="oldPin2Label" msgid="4648543187859997203">"PIN2 القديم"</string>
@@ -498,7 +498,7 @@
<string name="mismatchPin2" msgid="4952718725266700631">"رمزا PIN2 غير متطابقين. أعد المحاولة."</string>
<string name="invalidPin2" msgid="6467957903056379343">"أدخل رمز PIN2 المكوّن من 4 إلى 8 أرقام."</string>
<string name="invalidPuk2" msgid="713729511903849544">"أدخل رمز PUK2 المكوّن من 8 أرقام."</string>
- <string name="pin2_changed" msgid="5710551850481287821">"تم تحديث رمز PIN2"</string>
+ <string name="pin2_changed" msgid="5710551850481287821">"تمت إعادة تحميل رمز PIN2"</string>
<string name="label_puk2_code" msgid="2852217004288085562">"أدخل رمز PUK2"</string>
<string name="fdn_enable_puk2_requested" msgid="5793652792131588041">"كلمة المرور غير صحيحة. أصبح PIN2 محظور الآن. لإعادة المحاولة، عليك تغيير PIN2."</string>
<string name="puk2_requested" msgid="6992374450720307514">"كلمة المرور غير صحيحة. تم قفل شريحة SIM الآن. أدخل رمز PUK2."</string>
@@ -522,9 +522,9 @@
<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="default" msgid="8131672264311208673">"عليك إيقاف وضع الطيران لإجراء مكالمة."</string>
- <string name="incall_error_power_off_wfc" msgid="9125661184694727052">"عليك إيقاف وضع الطيران أو الاتصال بشبكة لاسلكية لإجراء مكالمة."</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_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>
@@ -552,7 +552,7 @@
<string name="emergency_call_shortcut_hint" msgid="1290485125107779500">"انقر مجددًا للاتصال بالرقم <xliff:g id="EMERGENCY_NUMBER">%s</xliff:g>"</string>
<string name="emergency_enable_radio_dialog_message" msgid="1695305158151408629">"جارٍ تفعيل اللاسلكي..."</string>
<string name="emergency_enable_radio_dialog_retry" msgid="4329131876852608587">"لا تتوفر خدمة. جارٍ إعادة المحاولة…"</string>
- <string name="radio_off_during_emergency_call" msgid="8011154134040481609">"لا يمكن دخول وضع الطيران أثناء إجراء مكالمة طوارئ."</string>
+ <string name="radio_off_during_emergency_call" msgid="8011154134040481609">"لا يمكن دخول وضع الطائرة أثناء إجراء مكالمة طوارئ."</string>
<string name="dial_emergency_error" msgid="825822413209026039">"يتعذر الاتصال. لا يعد <xliff:g id="NON_EMERGENCY_NUMBER">%s</xliff:g> رقم طوارئ."</string>
<string name="dial_emergency_empty_error" msgid="2785803395047793634">"يتعذر الاتصال. يمكنك طلب رقم طوارئ."</string>
<string name="dial_emergency_calling_not_available" msgid="6485846193794727823">"مكالمة الطوارئ غير متوفرة"</string>
@@ -633,36 +633,26 @@
<item quantity="one">سيكون الهاتف في وضع الرد على مكالمة الطوارئ لمدة <xliff:g id="COUNT_0">%s</xliff:g> دقيقة. وأثناء هذا الوضع، لا يمكن استخدام أي تطبيقات تستخدم اتصال بيانات. هل تريد الخروج الآن؟</item>
</plurals>
<plurals name="alert_dialog_not_avaialble_in_ecm" formatted="false" msgid="1152682528741457004">
- <item quantity="zero">الإجراء المحدد ليس متاحًا أثناء تفعيل وضع الرد على مكالمة الطوارئ. وسيكون الهاتف في هذا الوضع لمدة <xliff:g id="COUNT_1">%s</xliff:g> من الدقائق. هل تريد الخروج الآن؟</item>
- <item quantity="two">الإجراء المحدد ليس متاحًا أثناء تفعيل وضع الرد على مكالمة الطوارئ. وسيكون الهاتف في هذا الوضع لمدة دقيقتين (<xliff:g id="COUNT_1">%s</xliff:g>). هل تريد الخروج الآن؟</item>
- <item quantity="few">الإجراء المحدد ليس متاحًا أثناء تفعيل وضع الرد على مكالمة الطوارئ. وسيكون الهاتف في هذا الوضع لمدة <xliff:g id="COUNT_1">%s</xliff:g> دقائق. هل تريد الخروج الآن؟</item>
- <item quantity="many">الإجراء المحدد ليس متاحًا أثناء تفعيل وضع الرد على مكالمة الطوارئ. وسيكون الهاتف في هذا الوضع لمدة <xliff:g id="COUNT_1">%s</xliff:g> دقيقة. هل تريد الخروج الآن؟</item>
- <item quantity="other">الإجراء المحدد ليس متاحًا أثناء تفعيل وضع الرد على مكالمة الطوارئ. وسيكون الهاتف في هذا الوضع لمدة <xliff:g id="COUNT_1">%s</xliff:g> من الدقائق. هل تريد الخروج الآن؟</item>
- <item quantity="one">الإجراء المحدد ليس متاحًا أثناء تفعيل وضع الرد على مكالمة الطوارئ. وسيكون الهاتف في هذا الوضع لمدة <xliff:g id="COUNT_0">%s</xliff:g> دقيقة. هل تريد الخروج الآن؟</item>
+ <item quantity="zero">الإجراء المحدد ليس متاحًا أثناء تشغيل وضع الرد على مكالمة الطوارئ. وسيكون الهاتف في هذا الوضع لمدة <xliff:g id="COUNT_1">%s</xliff:g> من الدقائق. هل تريد الخروج الآن؟</item>
+ <item quantity="two">الإجراء المحدد ليس متاحًا أثناء تشغيل وضع الرد على مكالمة الطوارئ. وسيكون الهاتف في هذا الوضع لمدة دقيقتين (<xliff:g id="COUNT_1">%s</xliff:g>). هل تريد الخروج الآن؟</item>
+ <item quantity="few">الإجراء المحدد ليس متاحًا أثناء تشغيل وضع الرد على مكالمة الطوارئ. وسيكون الهاتف في هذا الوضع لمدة <xliff:g id="COUNT_1">%s</xliff:g> دقائق. هل تريد الخروج الآن؟</item>
+ <item quantity="many">الإجراء المحدد ليس متاحًا أثناء تشغيل وضع الرد على مكالمة الطوارئ. وسيكون الهاتف في هذا الوضع لمدة <xliff:g id="COUNT_1">%s</xliff:g> دقيقة. هل تريد الخروج الآن؟</item>
+ <item quantity="other">الإجراء المحدد ليس متاحًا أثناء تشغيل وضع الرد على مكالمة الطوارئ. وسيكون الهاتف في هذا الوضع لمدة <xliff:g id="COUNT_1">%s</xliff:g> من الدقائق. هل تريد الخروج الآن؟</item>
+ <item quantity="one">الإجراء المحدد ليس متاحًا أثناء تشغيل وضع الرد على مكالمة الطوارئ. وسيكون الهاتف في هذا الوضع لمدة <xliff:g id="COUNT_0">%s</xliff:g> دقيقة. هل تريد الخروج الآن؟</item>
</plurals>
<string name="alert_dialog_in_ecm_call" msgid="1207545603149771978">"الإجراء المحدد ليس متاحًا أثناء إجراء اتصال بالطوارئ."</string>
<string name="progress_dialog_exiting_ecm" msgid="9159080081676927217">"الخروج من وضع معاودة الاتصال بالطوارئ"</string>
<string name="alert_dialog_yes" msgid="3532525979632841417">"نعم"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"لا"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"استبعاد"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"الهاتف في وضع معاودة الاتصال بالطوارئ."</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"حتى <xliff:g id="COMPLETETIME">%s</xliff:g>"</string>
- <plurals name="alert_dialog_exit_ecm_without_data_restriction_hint" formatted="false" msgid="6477733043040328640">
- <item quantity="zero">سيكون الهاتف في وضع معاودة الاتصال بالطوارئ لمدة <xliff:g id="COUNT_1">%s</xliff:g> دقيقة.\nهل تريد الخروج الآن؟</item>
- <item quantity="two">سيكون الهاتف في وضع معاودة الاتصال بالطوارئ لمدة دقيقتين (<xliff:g id="COUNT_1">%s</xliff:g>).\nهل تريد الخروج الآن؟</item>
- <item quantity="few">سيكون الهاتف في وضع معاودة الاتصال بالطوارئ لمدة <xliff:g id="COUNT_1">%s</xliff:g> دقائق.\nهل تريد الخروج الآن؟</item>
- <item quantity="many">سيكون الهاتف في وضع معاودة الاتصال بالطوارئ لمدة <xliff:g id="COUNT_1">%s</xliff:g> دقيقة.\nهل تريد الخروج الآن؟</item>
- <item quantity="other">سيكون الهاتف في وضع معاودة الاتصال بالطوارئ لمدة <xliff:g id="COUNT_1">%s</xliff:g> دقيقة.\nهل تريد الخروج الآن؟</item>
- <item quantity="one">سيكون الهاتف في وضع معاودة الاتصال بالطوارئ لمدة دقيقة (<xliff:g id="COUNT_0">%s</xliff:g>).\nهل تريد الخروج الآن؟</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"الخدمة"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"الإعداد"</string>
- <string name="voicemail_number_not_set" msgid="8831561283386938155">"<لم يتم الضبط>"</string>
+ <string name="voicemail_number_not_set" msgid="8831561283386938155">"<لم يتم التعيين>"</string>
<string name="other_settings" msgid="8895088007393598447">"اعدادات المكالمات الاخرى"</string>
<string name="calling_via_template" msgid="1791323450703751750">"الاتصال عبر <xliff:g id="PROVIDER_NAME">%s</xliff:g>"</string>
<string name="contactPhoto" msgid="7885089213135154834">"صورة جهة الاتصال"</string>
<string name="goPrivate" msgid="4645108311382209551">"انتقال إلى مكالمة خاصة"</string>
- <string name="selectContact" msgid="1527612842599767382">"اختيار جهة اتصال"</string>
+ <string name="selectContact" msgid="1527612842599767382">"تحديد جهة اتصال"</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>
@@ -672,14 +662,14 @@
<string name="voicemail_change_pin_dialog_title" msgid="4633077715231764435">"تغيير رقم التعريف الشخصي"</string>
<string name="preference_category_ringtone" msgid="8787281191375434976">"نغمة الرنين والاهتزاز"</string>
<string name="pstn_connection_service_label" msgid="9200102709997537069">"بطاقات SIM مدمجة"</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_title" msgid="7246600931634161830">"تشغيل مكالمات الفيديو"</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>
<string name="sim_description_emergency_calls" msgid="5146872803938897296">"مكالمات الطوارئ فقط"</string>
<string name="sim_description_default" msgid="7474671114363724971">"شريحة SIM، المنفذ: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
- <string name="accessibility_settings_activity_title" msgid="7883415189273700298">"إعدادات تمكين الوصول"</string>
+ <string name="accessibility_settings_activity_title" msgid="7883415189273700298">"إمكانية الوصول"</string>
<string name="status_hint_label_incoming_wifi_call" msgid="2606052595898044071">"مكالمة Wi-Fi من"</string>
<string name="status_hint_label_wifi_call" msgid="942993035689809853">"مكالمة عبر Wi-Fi"</string>
<string name="emergency_action_launch_hint" msgid="2762016865340891314">"انقر مرة أخرى للفتح."</string>
@@ -695,17 +685,17 @@
<string name="change_pin_title" msgid="3564254326626797321">"تغيير رقم التعريف الشخصي للبريد الصوتي"</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">"حسنًا"</string>
+ <string name="change_pin_ok_label" msgid="6861082678817785330">"موافق"</string>
<string name="change_pin_enter_old_pin_header" msgid="853151335217594829">"تأكيد رقم التعريف الشخصي القديم"</string>
<string name="change_pin_enter_old_pin_hint" msgid="8801292976275169367">"أدخل رقم التعريف الشخصي لبريدك الصوتي للمتابعة."</string>
<string name="change_pin_enter_new_pin_header" msgid="4739465616733486118">"تعيين رقم تعريف شخصي جديد"</string>
<string name="change_pin_enter_new_pin_hint" msgid="2326038476516364210">"ينبغي لرقم التعريف الشخصي أن يتكون من <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">"تأكيد رقم التعريف الشخصي"</string>
<string name="change_pin_confirm_pins_dont_match" msgid="305164501222587215">"أرقام التعريف الشخصي غير متطابقة"</string>
- <string name="change_pin_succeeded" msgid="2504705600693014403">"تمّ تحديث رقم التعريف الشخصي للبريد الصوتي"</string>
+ <string name="change_pin_succeeded" msgid="2504705600693014403">"تمّ إعادة تحميل رقم التعريف الشخصي للبريد الصوتي"</string>
<string name="change_pin_system_error" msgid="7772788809875146873">"يتعذر تعيين رقم التعريف الشخصي"</string>
- <string name="mobile_data_status_roaming_turned_off_subtext" msgid="6840673347416227054">"تم إيقاف تجوال البيانات"</string>
- <string name="mobile_data_status_roaming_turned_on_subtext" msgid="5615757897768777865">"تم تفعيل تجوال البيانات"</string>
+ <string name="mobile_data_status_roaming_turned_off_subtext" msgid="6840673347416227054">"تم إيقاف تفعيل تجوال البيانات"</string>
+ <string name="mobile_data_status_roaming_turned_on_subtext" msgid="5615757897768777865">"تم تشغيل تجوال البيانات"</string>
<string name="mobile_data_status_roaming_without_plan_subtext" msgid="6536671968072284677">"التجوال قيد التشغيل حاليًا، يتطلب خطة بيانات"</string>
<string name="mobile_data_status_roaming_with_plan_subtext" msgid="2576177169108123095">"التجوال قيد التشغيل حاليًا، خطة البيانات نشطة"</string>
<string name="mobile_data_status_no_plan_subtext" msgid="170331026419263657">"لم تتبق أي بيانات جوال"</string>
@@ -719,7 +709,7 @@
<string name="mobile_data_activate_button" msgid="1139792516354374612">"إضافة بيانات"</string>
<string name="mobile_data_activate_cancel_button" msgid="3530174817572005860">"إلغاء"</string>
<string name="clh_card_title_call_ended_txt" msgid="5977978317527299698">"تم إنهاء الاتصال"</string>
- <string name="clh_callFailed_powerOff_txt" msgid="8279934912560765361">"تفعيل وضع الطيران"</string>
+ <string name="clh_callFailed_powerOff_txt" msgid="8279934912560765361">"تشغيل وضع الطائرة"</string>
<string name="clh_callFailed_simError_txt" msgid="5128538525762326413">"تعذر الوصول إلى شريحة SIM"</string>
<string name="clh_incall_error_out_of_service_txt" msgid="2736010617446749869">"شبكة الجوّال غير متوفرة"</string>
<string name="clh_callFailed_unassigned_number_txt" msgid="141967660286695682">"حدثت مشكلة في رقم الهاتف الذي تحاول الاتصال به. رمز الخطأ 1."</string>
@@ -772,7 +762,7 @@
<string name="clh_callFailed_protocol_Error_unspecified_txt" msgid="9203320572562697755">"تعذر إكمال المكالمة. رمز الخطأ 111."</string>
<string name="clh_callFailed_interworking_unspecified_txt" msgid="7969686413930847182">"تعذر إكمال المكالمة. رمز الخطأ 127."</string>
<string name="labelCallBarring" msgid="4180377113052853173">"منع الاتصال"</string>
- <string name="sum_call_barring_enabled" msgid="5184331188926370824">"مفعّلة"</string>
+ <string name="sum_call_barring_enabled" msgid="5184331188926370824">"تفعيل"</string>
<string name="sum_call_barring_disabled" msgid="5699448000600153096">"متوقف"</string>
<string name="call_barring_baoc" msgid="7400892586336429326">"كل المكالمات الصادرة"</string>
<string name="call_barring_baoc_enabled" msgid="3131509193386668182">"هل تريد إيقاف حظر كل المكالمات الصادرة؟"</string>
@@ -910,11 +900,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"إعادة التحميل"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"تبديل فحص نظام أسماء النطاقات"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"المعلومات/الإعدادات المتعلّقة بالمصنّع الأصلي للجهاز"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC متوفر:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"تم حظر DCNR:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR متوفر:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"حالة NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"تردد NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"تحديد وضع النطاق اللاسلكي"</string>
<string name="band_mode_loading" msgid="795923726636735967">"جارٍ تحميل قائمة النطاقات…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"ضبط"</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 764eb5c..f220afe 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"হয়"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"নহয়"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"অগ্ৰাহ্য কৰক"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"ফ’নটো জৰুৰীকালীন কলবেক ম’ডত আছে"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">ফ’নটো <xliff:g id="COUNT_1">%s</xliff:g> মিনিটৰ বাবে জৰুৰীকালীন কলবেক ম’ডত থাকিব।\nআপুনি এতিয়া বাহিৰ হ’বলৈ বিচাৰে নেকি?</item>
- <item quantity="other">ফ’নটো <xliff:g id="COUNT_1">%s</xliff:g> মিনিটৰ বাবে জৰুৰীকালীন কলবেক ম’ডত থাকিব।\nআপুনি এতিয়া বাহিৰ হ’বলৈ বিচাৰে নেকি?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"সেৱা"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"ছেট আপ কৰক"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<ছেট কৰা হোৱা নাই>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"ৰিফ্ৰেশ্ব কৰক"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS পৰীক্ষা ট’গল কৰক"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM বিশেষক তথ্য/ছেটিংসমূহ"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC উপলব্ধ:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR প্ৰতিবন্ধিত:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR উপলব্ধ:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR স্থিতি:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR ফ্ৰিকুৱেন্সী:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"ৰেডিঅ’ৰ বেণ্ড ম’ড ছেট কৰক"</string>
<string name="band_mode_loading" msgid="795923726636735967">"বেণ্ড সূচীখন ল’ড কৰি থকা হৈছে…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"ছেট কৰক"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 1b73c52..a053ece 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -60,7 +60,7 @@
<string name="labelGsmMore_with_label" msgid="3206015314393246224">"GSM zəng ayarları (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="labelCDMAMore" msgid="7937441382611224632">"CDMA zəng parametrləri"</string>
<string name="labelCdmaMore_with_label" msgid="7759692829160238152">"CDMA zəng ayarları (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
- <string name="apn_settings" msgid="1978652203074756623">"Giriş nöqtəsi adları"</string>
+ <string name="apn_settings" msgid="1978652203074756623">"Çatma Nöqtəsi Adları"</string>
<string name="settings_label" msgid="9101778088412567956">"Şəbəkə ayarları"</string>
<string name="phone_accounts" msgid="1216879437523774604">"Hesabların çağırılması"</string>
<string name="phone_accounts_make_calls_with" msgid="16747814788918145">"Zənglər et"</string>
@@ -84,7 +84,7 @@
<string name="smart_forwarding_settings_menu_summary" msgid="5096947726032885325">"Nömrələrdən biri əlçatan olmadıqda zəngi digər nömrəyə yönləndirin"</string>
<string name="voicemail_notifications_preference_title" msgid="7829238858063382977">"Bildirişlər"</string>
<string name="cell_broadcast_settings" msgid="8135324242541809924">"Təcili yayımlar"</string>
- <string name="call_settings" msgid="3677282690157603818">"Zəng ayarları"</string>
+ <string name="call_settings" msgid="3677282690157603818">"Zəng parametrləri"</string>
<string name="additional_gsm_call_settings" msgid="1561980168685658846">"Əlavə ayarlar"</string>
<string name="additional_gsm_call_settings_with_label" msgid="7973920539979524908">"Əlavə parametrlər (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="sum_gsm_call_settings" msgid="7964692601608878138">"Əlavə GSM yalnız zəng parametrləri"</string>
@@ -123,11 +123,11 @@
<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="updating_title" msgid="6130548922615719689">"Zəng ayarları"</string>
+ <string name="updating_title" msgid="6130548922615719689">"Zəng parametrləri"</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>
<string name="error_updating_title" msgid="2024290892676808965">"Zəng parametrləri xətası"</string>
- <string name="reading_settings" msgid="1605904432450871183">"Oxuma ayarları..."</string>
+ <string name="reading_settings" msgid="1605904432450871183">"Oxuma parametrləri ..."</string>
<string name="updating_settings" msgid="3650396734816028808">"Ayarlar güncəlləşdirilir…"</string>
<string name="reverting_settings" msgid="7378668837291012205">"Ayarlar qaytarılır..."</string>
<string name="response_error" msgid="3904481964024543330">"Şəbəkədən gözlənilməz cavab."</string>
@@ -174,15 +174,15 @@
<string name="connect_later" msgid="1950138106010005425">"Hazırda bu şəbəkəyə qoşulmaq olmur. Sonra təkrar sınayın."</string>
<string name="registration_done" msgid="5337407023566953292">"Şəbəkədə qeydiyyatdan keçib."</string>
<string name="already_auto" msgid="8607068290733079336">"Artıq avtomatik seçimdədir."</string>
- <string name="select_automatically" msgid="779750291257872651">"Şəbəkə avtomatik seçilsin"</string>
+ <string name="select_automatically" msgid="779750291257872651">"Avtomatik olaraq şəbəkə seçin"</string>
<string name="manual_mode_disallowed_summary" msgid="3970048592179890197">"%1$s ünvanına qoşulduqda əlçatan olmur"</string>
<string name="network_select_title" msgid="4117305053881611988">"Şəbəkə"</string>
<string name="register_automatically" msgid="3907580547590554834">"Avtomatik qeydiyyat ..."</string>
- <string name="preferred_network_mode_title" msgid="5253395265169539830">"Şəbəkə növü"</string>
+ <string name="preferred_network_mode_title" msgid="5253395265169539830">"Tərcih edilən şəbəkə növü"</string>
<string name="preferred_network_mode_summary" msgid="3787989000044330064">"Şəbəkə əməliyyat rejimini dəyişin"</string>
- <string name="preferred_network_mode_dialogtitle" msgid="2781447433514459696">"Şəbəkə növü"</string>
+ <string name="preferred_network_mode_dialogtitle" msgid="2781447433514459696">"Tərcih edilən şəbəkə növü"</string>
<string name="forbidden_network" msgid="5081729819561333023">"(qadağandır)"</string>
- <string name="choose_network_title" msgid="5335832663422653082">"Şəbəkə seçin"</string>
+ <string name="choose_network_title" msgid="5335832663422653082">"Şəbəkəni seçin"</string>
<string name="network_disconnected" msgid="8844141106841160825">"Bağlantı kəsildi"</string>
<string name="network_connected" msgid="2760235679963580224">"Qoşuldu"</string>
<string name="network_connecting" msgid="160901383582774987">"Qoşulur..."</string>
@@ -274,26 +274,26 @@
<string name="data_enable_summary" msgid="696860063456536557">"Data istifadəsinə icazə verin"</string>
<string name="dialog_alert_title" msgid="5260471806940268478">"Diqqət"</string>
<string name="roaming" msgid="1576180772877858949">"Rominq"</string>
- <string name="roaming_enable" msgid="6853685214521494819">"Rominq zamanı data xidmətinə qoşulun"</string>
- <string name="roaming_disable" msgid="8856224638624592681">"Rominq zamanı data xidmətinə qoşulun"</string>
+ <string name="roaming_enable" msgid="6853685214521494819">"Rouminq zamanı data xidmətlərinə qoşulun"</string>
+ <string name="roaming_disable" msgid="8856224638624592681">"Rouminq zamanı data xidmətlərinə qoşulun"</string>
<string name="roaming_reenable_message" msgid="1951802463885727915">"Data rominqi deaktivdir. Aktiv etmək üçün klikləyin."</string>
<string name="roaming_enabled_message" msgid="9022249120750897">"Rominq xərcləri çıxıla bilər. Dəyişmək üçün toxunun."</string>
<string name="roaming_notification_title" msgid="3590348480688047320">"Mobil data bağlantısı itdi"</string>
<string name="roaming_on_notification_title" msgid="7451473196411559173">"Data rominqi aktivdir"</string>
<string name="roaming_warning" msgid="7855681468067171971">"Sizə əhəmiyyətli xərclər tətbiq edilə bilər."</string>
<string name="roaming_check_price_warning" msgid="8212484083990570215">"Qiymətləndirmə üçün şəbəkə provayderi ilə yoxlayın."</string>
- <string name="roaming_alert_title" msgid="5689615818220960940">"Data rominqə icazə verilsin?"</string>
+ <string name="roaming_alert_title" msgid="5689615818220960940">"Məlumat rominqinə icazə verilsin?"</string>
<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="data_usage_title" msgid="8438592133893837464">"Tətbiq trafiki"</string>
+ <string name="data_usage_title" msgid="8438592133893837464">"Tətbiq data istifadəsi"</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>
<string name="carrier_settings_euicc" msgid="1190237227261337749">"Mobil Operator"</string>
<string name="keywords_carrier_settings_euicc" msgid="8540160967922063745">"operator, esim, sim, euicc, operatoru dəyişin, operator əlavə edin"</string>
<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">"Mobil data"</string>
- <string name="mobile_data_settings_summary" msgid="5012570152029118471">"Mobil internetə giriş"</string>
+ <string name="mobile_data_settings_summary" msgid="5012570152029118471">"Mobil şəbəkədən istifadə edərək dataya daxil olun"</string>
<string name="data_usage_disable_mobile" msgid="5669109209055988308">"Mobil data söndürülsün?"</string>
<string name="sim_selection_required_pref" msgid="6985901872978341314">"Seçim tələb olunur"</string>
<string name="sim_change_data_title" msgid="9142726786345906606">"Data SİM-i dəyişilsin?"</string>
@@ -454,7 +454,7 @@
<string name="get_pin2" msgid="4221654606863196332">"PIN2 daxil edin"</string>
<string name="name" msgid="1347432469852527784">"Ad"</string>
<string name="number" msgid="1564053487748491000">"Nömrə"</string>
- <string name="save" msgid="983805790346099749">"Yadda saxlayın"</string>
+ <string name="save" msgid="983805790346099749">"Yadda saxla"</string>
<string name="add_fdn_contact" msgid="1169713422306640887">"Sabit yığım nömrəsi əlavə edin"</string>
<string name="adding_fdn_contact" msgid="3112531600824361259">"Sabit yığım nömrəsi əlavə olunur..."</string>
<string name="fdn_contact_added" msgid="2840016151693394596">"Sabit yığım nömrəsi əlavə edildi."</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Bəli"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Xeyr"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Kənarlaşdır"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Telefon fövqəladə geriyə zəng rejimindədir"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<xliff:g id="COMPLETETIME">%s</xliff:g> olana qədər"</string>
- <plurals name="alert_dialog_exit_ecm_without_data_restriction_hint" formatted="false" msgid="6477733043040328640">
- <item quantity="other">Telefon <xliff:g id="COUNT_1">%s</xliff:g> dəqiqəlik fövqəladə geri zəng rejimində olacaq.\nİndi çıxmaq istəyirsiniz?</item>
- <item quantity="one">Telefon <xliff:g id="COUNT_0">%s</xliff:g> dəqiqəlik fövqəladə geri zəng rejimində olacaq.\nİndi çıxmaq istəyirsiniz?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Xidmət"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Quraşdırma"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Ayarlanmayıb>"</string>
@@ -689,7 +683,7 @@
<string name="change_pin_enter_new_pin_header" msgid="4739465616733486118">"Yeni PIN kodu təyin edin"</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> rəqəm olmalıdır."</string>
<string name="change_pin_confirm_pin_header" msgid="2606303906320705726">"PİN kodunuzu təsdiq edin"</string>
- <string name="change_pin_confirm_pins_dont_match" msgid="305164501222587215">"PIN-lər eyni deyil"</string>
+ <string name="change_pin_confirm_pins_dont_match" msgid="305164501222587215">"PIN kodlar üst-üstə düşmür"</string>
<string name="change_pin_succeeded" msgid="2504705600693014403">"Səsli poçtun PIN kodu yeniləndi"</string>
<string name="change_pin_system_error" msgid="7772788809875146873">"PIN kodu ayarlamaq olmur"</string>
<string name="mobile_data_status_roaming_turned_off_subtext" msgid="6840673347416227054">"Data rominq deaktivdir"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Yeniləyin"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS Yoxlanışına keçin"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Orijinal Avadanlıq İstehsalçısının Məlumatı/Ayarlar"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC Əlçatandır:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR Məhdudlaşdırılıb:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR Əlçatandır:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR Statusu:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR Tezliyi:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Radio Diapazon Rejimini Quraşdırın"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Diapazon Siyahısı Yüklənir…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Ayarlayın"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 97087d4..8940af5 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -74,7 +74,7 @@
<string name="phone_accounts_configure_account_settings" msgid="6622119715253196586">"Konfigurisanje podešavanja naloga"</string>
<string name="phone_accounts_all_calling_accounts" msgid="1609600743500618823">"Svi nalozi za pozivanje"</string>
<string name="phone_accounts_all_calling_accounts_summary" msgid="2214134955430107240">"Izaberite koji nalozi mogu da obavljaju pozive"</string>
- <string name="wifi_calling" msgid="3650509202851355742">"Pozivanje preko WiFi-a"</string>
+ <string name="wifi_calling" msgid="3650509202851355742">"Pozivanje preko Wi-Fi-ja"</string>
<string name="connection_service_default_label" msgid="7332739049855715584">"Ugrađena usluga povezivanja"</string>
<string name="voicemail" msgid="7697769412804195032">"Govorna pošta"</string>
<string name="voicemail_settings_with_label" msgid="4228431668214894138">"Glasovna pošta (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -118,7 +118,7 @@
<string name="sum_cfnry_enabled" msgid="3000500837493854799">"Prosleđuje se na <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnry_disabled" msgid="1990563512406017880">"Isključeno"</string>
<string name="disable_cfnry_forbidden" msgid="3174731413216550689">"Operater ne podržava onemogućavanje preusmeravanja poziva kada se na poziv ne odgovori."</string>
- <string name="labelCFNRc" msgid="4163399350778066013">"Kad sam nedostupan/na"</string>
+ <string name="labelCFNRc" msgid="4163399350778066013">"Kad sam nedostupan/a"</string>
<string name="messageCFNRc" msgid="6980340731313007250">"Broj kad je nedostupno"</string>
<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>
@@ -298,7 +298,7 @@
<string name="sim_selection_required_pref" msgid="6985901872978341314">"Potrebno je da izaberete nešto"</string>
<string name="sim_change_data_title" msgid="9142726786345906606">"Da promenimo SIM za podatke?"</string>
<string name="sim_change_data_message" msgid="3567358694255933280">"Želite li da za mobilne podatke koristite <xliff:g id="NEW_SIM">%1$s</xliff:g> umesto <xliff:g id="OLD_SIM">%2$s</xliff:g>?"</string>
- <string name="wifi_calling_settings_title" msgid="5800018845662016507">"Pozivanje preko WiFi-a"</string>
+ <string name="wifi_calling_settings_title" msgid="5800018845662016507">"Pozivanje preko Wi-Fi-ja"</string>
<string name="video_calling_settings_title" msgid="342829454913266078">"Video pozivanje preko operatera"</string>
<string name="gsm_umts_options" msgid="4968446771519376808">"Opcije za GSM/UMTS"</string>
<string name="cdma_options" msgid="3669592472226145665">"CDMA opcije"</string>
@@ -472,9 +472,9 @@
<string name="simContacts_empty" msgid="1135632055473689521">"Nema kontakata na SIM kartici."</string>
<string name="simContacts_title" msgid="2714029230160136647">"Izbor kontakata za uvoz"</string>
<string name="simContacts_airplaneMode" msgid="4654884030631503808">"Isključite režim rada u avionu da biste uvezli kontakte sa SIM kartice."</string>
- <string name="enable_pin" msgid="967674051730845376">"Omogućavanje/onemogućavanje PIN-a za SIM"</string>
- <string name="change_pin" msgid="3657869530942905790">"Promena PIN-a za SIM"</string>
- <string name="enter_pin_text" msgid="3182311451978663356">"PIN za SIM:"</string>
+ <string name="enable_pin" msgid="967674051730845376">"Omogućavanje/onemogućavanje SIM PIN-a"</string>
+ <string name="change_pin" msgid="3657869530942905790">"Promena SIM PIN-a"</string>
+ <string name="enter_pin_text" msgid="3182311451978663356">"SIM PIN:"</string>
<string name="oldPinLabel" msgid="8618515202411987721">"Stari PIN"</string>
<string name="newPinLabel" msgid="3585899083055354732">"Novi PIN"</string>
<string name="confirmPinLabel" msgid="7783531218662473778">"Potvrdite novi PIN"</string>
@@ -542,7 +542,7 @@
<string name="incall_error_supp_service_hangup" msgid="836524952243836735">"Uspostavljanje poziva nije uspelo."</string>
<string name="incall_error_supp_service_hold" msgid="8535056414643540997">"Nije moguće stavljati pozive na čekanje."</string>
<string name="incall_error_wfc_only_no_wireless_network" msgid="5860742792811400109">"Povežite se na bežičnu mrežu da biste uputili poziv."</string>
- <string name="incall_error_promote_wfc" msgid="9164896813931363415">"Omogućite pozivanje preko WiFi-a da biste uputili poziv."</string>
+ <string name="incall_error_promote_wfc" msgid="9164896813931363415">"Omogućite pozivanje preko Wi-Fi-ja da biste uputili poziv."</string>
<string name="emergency_information_hint" msgid="9208897544917793012">"Informacije za hitne slučajeve"</string>
<string name="emergency_information_owner_hint" msgid="6256909888049185316">"Vlasnik"</string>
<string name="emergency_information_confirm_hint" msgid="5109017615894918914">"Dodirnite ponovo da biste videli informacije"</string>
@@ -564,7 +564,7 @@
<string name="dialerKeyboardHintText" msgid="1115266533703764049">"Koristite tastaturu za pozivanje"</string>
<string name="onscreenHoldText" msgid="4025348842151665191">"Čekanje"</string>
<string name="onscreenEndCallText" msgid="6138725377654842757">"Završi"</string>
- <string name="onscreenShowDialpadText" msgid="658465753816164079">"Brojčanik"</string>
+ <string name="onscreenShowDialpadText" msgid="658465753816164079">"Numerička tastatura"</string>
<string name="onscreenMuteText" msgid="5470306116733843621">"Isključi zvuk"</string>
<string name="onscreenAddCallText" msgid="9075675082903611677">"Dodaj poziv"</string>
<string name="onscreenMergeCallsText" msgid="3692389519611225407">"Objedini pozive"</string>
@@ -604,7 +604,7 @@
<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_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_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 Wi-Fi 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>
<string name="ota_activate" msgid="7939695753665438357">"Aktiviraj"</string>
<string name="ota_title_activate_success" msgid="1272135024761004889">"Telefon je aktiviran."</string>
@@ -639,13 +639,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Da"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Ne"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Odbaci"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Telefon je u režimu za hitan povratni poziv"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Do <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">Telefon će <xliff:g id="COUNT_1">%s</xliff:g> minut biti u režimu za hitan povratni poziv.\nŽelite sad da izađete iz njega?</item>
- <item quantity="few">Telefon će <xliff:g id="COUNT_1">%s</xliff:g> minuta biti u režimu za hitan povratni poziv.\nŽelite sad da izađete iz njega?</item>
- <item quantity="other">Telefon će <xliff:g id="COUNT_1">%s</xliff:g> minuta biti u režimu za hitan povratni poziv.\nŽelite sad da izađete iz njega?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Usluga"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Podešavanje"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Nije podešeno>"</string>
@@ -657,7 +650,7 @@
<string name="not_voice_capable" msgid="2819996734252084253">"Audio pozivi nisu podržani"</string>
<string name="description_dial_button" msgid="8614631902795087259">"biranje"</string>
<string name="description_dialpad_button" msgid="7395114120463883623">"prikažite numeričku tastaturu"</string>
- <string name="pane_title_emergency_dialpad" msgid="3627372514638694401">"Brojčanik za hitne pozive"</string>
+ <string name="pane_title_emergency_dialpad" msgid="3627372514638694401">"Numerička tastatura za hitne pozive"</string>
<string name="voicemail_visual_voicemail_switch_title" msgid="6610414098912832120">"Vizuelna govorna pošta"</string>
<string name="voicemail_set_pin_dialog_title" msgid="7005128605986960003">"Podesite PIN"</string>
<string name="voicemail_change_pin_dialog_title" msgid="4633077715231764435">"Promenite PIN"</string>
@@ -671,18 +664,18 @@
<string name="sim_description_emergency_calls" msgid="5146872803938897296">"Samo za hitne pozive"</string>
<string name="sim_description_default" msgid="7474671114363724971">"SIM kartica, otvor: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
<string name="accessibility_settings_activity_title" msgid="7883415189273700298">"Pristupačnost"</string>
- <string name="status_hint_label_incoming_wifi_call" msgid="2606052595898044071">"WiFi poziv od"</string>
- <string name="status_hint_label_wifi_call" msgid="942993035689809853">"WiFi poziv"</string>
+ <string name="status_hint_label_incoming_wifi_call" msgid="2606052595898044071">"Wi-Fi poziv od"</string>
+ <string name="status_hint_label_wifi_call" msgid="942993035689809853">"Wi-Fi poziv"</string>
<string name="emergency_action_launch_hint" msgid="2762016865340891314">"Dodirnite ponovo da biste otvorili"</string>
<string name="message_decode_error" msgid="1061856591500290887">"Došlo je do greške pri dekodiranju poruke."</string>
<string name="callFailed_cdma_activation" msgid="5392057031552253550">"SIM kartica je aktivirala uslugu i ažurirala funkcije rominga na telefonu."</string>
<string name="callFailed_cdma_call_limit" msgid="1074219746093031412">"Ima previše aktivnih poziva. Završite ili objedinite postojeće pozive pre nego što uputite novi."</string>
<string name="callFailed_imei_not_accepted" msgid="7257903653685147251">"Povezivanje nije uspelo, ubacite važeću SIM karticu."</string>
- <string name="callFailed_wifi_lost" msgid="1788036730589163141">"WiFi veza je prekinuta. Poziv je završen."</string>
+ <string name="callFailed_wifi_lost" msgid="1788036730589163141">"Wi-Fi veza je prekinuta. Poziv je završen."</string>
<string name="dialFailed_low_battery" msgid="6857904237423407056">"Ne možete da uputite poziv jer je baterija skoro prazna."</string>
<string name="callFailed_low_battery" msgid="4056828320214416182">"Video poziv je prekinut jer je baterija skoro prazna."</string>
- <string name="callFailed_emergency_call_over_wfc_not_available" msgid="5944309590693432042">"Hitni pozivi pomoću funkcije Pozivanje preko WiFi-a nisu dostupni na ovoj lokaciji."</string>
- <string name="callFailed_wfc_service_not_available_in_this_location" msgid="3624536608369524988">"Pozivanje preko WiFi-a nije dostupno na ovoj lokaciji."</string>
+ <string name="callFailed_emergency_call_over_wfc_not_available" msgid="5944309590693432042">"Hitni pozivi pomoću funkcije Pozivanje preko Wi-Fi-ja nisu dostupni na ovoj lokaciji."</string>
+ <string name="callFailed_wfc_service_not_available_in_this_location" msgid="3624536608369524988">"Pozivanje preko Wi-Fi-ja nije dostupno na ovoj lokaciji."</string>
<string name="change_pin_title" msgid="3564254326626797321">"Promenite PIN kôd govorne pošte"</string>
<string name="change_pin_continue_label" msgid="5177011752453506371">"Nastavi"</string>
<string name="change_pin_cancel_label" msgid="2301711566758827936">"Otkaži"</string>
@@ -827,7 +820,7 @@
<string name="radio_info_data_connection_disable" msgid="6404751291511368706">"Onemogući vezu za prenos podataka"</string>
<string name="volte_provisioned_switch_string" msgid="4812874990480336178">"Dodeljeno za VoLTE"</string>
<string name="vt_provisioned_switch_string" msgid="8295542122512195979">"Video pozivi su dodeljeni"</string>
- <string name="wfc_provisioned_switch_string" msgid="3835004640321078988">"Pozivanje preko WiFi-a je dodeljeno"</string>
+ <string name="wfc_provisioned_switch_string" msgid="3835004640321078988">"Pozivanje preko Wi-Fi-ja je dodeljeno"</string>
<string name="eab_provisioned_switch_string" msgid="4449676720736033035">"Dodeljen je EAB/prisustvo"</string>
<string name="cbrs_data_switch_string" msgid="6060356430838077653">"Podaci CBRS-a"</string>
<string name="dsds_switch_string" msgid="7564769822086764796">"Omogući DSDS"</string>
@@ -845,7 +838,7 @@
<string name="radio_info_ims_reg_status_not_registered" msgid="8045821447288876085">"Nije registrovano"</string>
<string name="radio_info_ims_feature_status_available" msgid="6493200914756969292">"Dostupno"</string>
<string name="radio_info_ims_feature_status_unavailable" msgid="8930391136839759778">"Nije dostupno"</string>
- <string name="radio_info_ims_reg_status" msgid="25582845222446390">"Registracija IMS-a: <xliff:g id="STATUS">%1$s</xliff:g>\nGlas preko LTE-a: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nGlas preko WiFi-a: <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nVideo poziv: <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">"Registracija IMS-a: <xliff:g id="STATUS">%1$s</xliff:g>\nGlas preko LTE-a: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nGlas preko Wi-Fi-ja: <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nVideo poziv: <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">"Radi"</string>
<string name="radioInfo_service_out" msgid="287972405416142312">"Ne radi"</string>
<string name="radioInfo_service_emergency" msgid="4763879891415016848">"Samo hitni pozivi"</string>
@@ -901,11 +894,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Osveži"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Uključi/isključi proveru DNS-a"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Informacije/podešavanja specifična za proizvođača originalne opreme"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC dostupno:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR ograničeno:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR dostupno:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR stanje:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR učestalost:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Podesite režim radijskog opsega"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Učitava se lista opsega…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Podesi"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index e9ebdea..ea3af2c 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -30,7 +30,7 @@
<string name="mmiStarted" msgid="9212975136944568623">"Пачалося выкананне MMI кода"</string>
<string name="ussdRunning" msgid="1163586813106772717">"Запускаецца USSD-код..."</string>
<string name="mmiCancelled" msgid="5339191899200678272">"Код MMI адменены"</string>
- <string name="cancel" msgid="8984206397635155197">"Скасаваць"</string>
+ <string name="cancel" msgid="8984206397635155197">"Адмяніць"</string>
<string name="enter_input" msgid="6193628663039958990">"Паведамленне USSD павінна складацца з наступнай колькасцi знакаў: ад <xliff:g id="MIN_LEN">%1$d</xliff:g> да <xliff:g id="MAX_LEN">%2$d</xliff:g>. Паспрабуйце яшчэ раз."</string>
<string name="manageConferenceLabel" msgid="8415044818156353233">"Кіраванне канферэнц-выклікам"</string>
<string name="ok" msgid="7818974223666140165">"ОК"</string>
@@ -302,12 +302,12 @@
<string name="video_calling_settings_title" msgid="342829454913266078">"Відэавыклікі праз аператара"</string>
<string name="gsm_umts_options" msgid="4968446771519376808">"Параметры GSM/UMTS"</string>
<string name="cdma_options" msgid="3669592472226145665">"Параметры CDMA"</string>
- <string name="throttle_data_usage" msgid="1944145350660420711">"Выкарыстанне трафіка"</string>
+ <string name="throttle_data_usage" msgid="1944145350660420711">"Выкарыстанне трафіку"</string>
<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_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_help" msgid="2624535757028809735">"Дадатковая iнфармацыя"</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>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g>?? цыклу прайшло\nНаступны перыяд пачнецца праз <xliff:g id="USED_1">%2$d</xliff:g> д. (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -442,7 +442,7 @@
<string name="sum_fdn_manage_list" msgid="3311397063233992907">"Кіраванне спісам тэлефонных нумароў"</string>
<string name="voice_privacy" msgid="7346935172372181951">"Палiтыка прыватнасцi Voice"</string>
<string name="voice_privacy_summary" msgid="3556460926168473346">"Уключыць пашыраны рэжым прыватнасці"</string>
- <string name="tty_mode_option_title" msgid="3843817710032641703">"Рэжым TTY"</string>
+ <string name="tty_mode_option_title" msgid="3843817710032641703">"Рэжым тэлетайпа"</string>
<string name="tty_mode_option_summary" msgid="4770510287236494371">"Задаць рэжым TTY"</string>
<string name="auto_retry_mode_title" msgid="2985801935424422340">"Аўтаматычны паўтор"</string>
<string name="auto_retry_mode_summary" msgid="2863919925349511402">"Уключыць рэжым аўтаматычнага паўтору"</string>
@@ -525,7 +525,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_ecm_emergency_only" msgid="5622379058883722080">"Каб зрабіць звычайны выклік, выйдзіце з рэжыму экстранных зваротных выклікаў."</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>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Мабільная сетка недаступная. Падлучыцеся да бесправадной сеткі, каб зрабіць выклік."</string>
@@ -586,10 +586,10 @@
<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">
- <item msgid="3238070884803849303">"TTY выключаны"</item>
- <item msgid="1449091874731375214">"Поўнафункцыянальны TTY"</item>
- <item msgid="1044179293199519425">"TTY з магчымасцю чуць суразмоўніка"</item>
- <item msgid="2131559553795606483">"TTY з магчымасцю перадачы голасу"</item>
+ <item msgid="3238070884803849303">"Тэлетайп выключаны"</item>
+ <item msgid="1449091874731375214">"Поўнафункцыянальны тэлетайп"</item>
+ <item msgid="1044179293199519425">"Тэлетайп HCO"</item>
+ <item msgid="2131559553795606483">"Тэлетайп VCO"</item>
</string-array>
<string name="dtmf_tones_title" msgid="7874845461117175236">"Сігналы DTMF"</string>
<string name="dtmf_tones_summary" msgid="2294822239899471201">"Задаць даўжыню тонаў DTMF"</string>
@@ -617,38 +617,30 @@
<string name="ota_unsuccessful" msgid="8531037653803955754">"Ваш тэлефон не актываваны. \nМагчыма, вам спатрэбіцца знайсці вобласць з лепшым пакрыццём (каля акна ці на вуліцы). \n\nПаўтарыце спробу ці звярнiцеся ў цэнтр абслугоўвання кліентаў, каб атрымаць дадатковую інфармацыю."</string>
<string name="ota_spc_failure" msgid="904092035241370080">"ПАМЫЛКІ ПЕРАВЫШЭННЯ SPC"</string>
<string name="ota_call_end" msgid="8657746378290737034">"Назад"</string>
- <string name="ota_try_again" msgid="6914781945599998550">"Паўтарыць спробу"</string>
+ <string name="ota_try_again" msgid="6914781945599998550">"Паспрабаваць яшчэ раз"</string>
<string name="ota_next" msgid="2041016619313475914">"Далей"</string>
<string name="ecm_exit_dialog" msgid="4200691880721429078">"EcmExitDialog"</string>
- <string name="phone_entered_ecm_text" msgid="8431238297843035842">"У рэжыме экстранных зваротных выклікаў"</string>
- <string name="phone_in_ecm_notification_title" msgid="6825016389926367946">"Рэжым экстранных зваротных выклікаў"</string>
+ <string name="phone_entered_ecm_text" msgid="8431238297843035842">"У рэжыме аварыйнага зваротнага выкліку"</string>
+ <string name="phone_in_ecm_notification_title" msgid="6825016389926367946">"Рэжым аварыйнага зваротнага выкліку"</string>
<string name="phone_in_ecm_call_notification_text" msgid="653972232922670335">"Падлучэнне для перадачы дадзеных адключана"</string>
<string name="phone_in_ecm_notification_complete_time" msgid="7341624337163082759">"Няма злучэння для перадачы даных да <xliff:g id="COMPLETETIME">%s</xliff:g>"</string>
<plurals name="alert_dialog_exit_ecm" formatted="false" msgid="5425906903766466743">
- <item quantity="one">Тэлефон будзе заставацца ў рэжыме экстранных зваротных выклікаў <xliff:g id="COUNT_1">%s</xliff:g> хвіліну. У гэтым рэжыме недаступныя праграмы, якія выкарыстоўваюць перадачу даных. Выйсці зараз?</item>
- <item quantity="few">Тэлефон будзе заставацца ў рэжыме экстранных зваротных выклікаў <xliff:g id="COUNT_1">%s</xliff:g> хвіліны. У гэтым рэжыме недаступныя праграмы, якія выкарыстоўваюць перадачу даных. Выйсці зараз?</item>
- <item quantity="many">Тэлефон будзе заставацца ў рэжыме экстранных зваротных выклікаў <xliff:g id="COUNT_1">%s</xliff:g> хвілін. У гэтым рэжыме недаступныя праграмы, якія выкарыстоўваюць перадачу даных. Выйсці зараз?</item>
- <item quantity="other">Тэлефон будзе заставацца ў рэжыме экстранных зваротных выклікаў <xliff:g id="COUNT_1">%s</xliff:g> хвіліны. У гэтым рэжыме недаступныя праграмы, якія выкарыстоўваюць перадачу даных. Выйсці зараз?</item>
+ <item quantity="one">Тэлефон пяройдзе ў рэжым экстраннага зваротнага выкліку на <xliff:g id="COUNT_1">%s</xliff:g> хвіліну. У гэтым рэжыме немагчыма карыстацца праграмамі, якія выкарыстоўваюць злучэнне для перадачы даных. Жадаеце выйсці зараз?</item>
+ <item quantity="few">Тэлефон пяройдзе ў рэжым экстраннага зваротнага выкліку на <xliff:g id="COUNT_1">%s</xliff:g> хвіліны. У гэтым рэжыме немагчыма карыстацца праграмамі, якія выкарыстоўваюць злучэнне для перадачы даных. Жадаеце выйсці зараз?</item>
+ <item quantity="many">Тэлефон пяройдзе ў рэжым экстраннага зваротнага выкліку на <xliff:g id="COUNT_1">%s</xliff:g> хвілін. У гэтым рэжыме немагчыма карыстацца праграмамі, якія выкарыстоўваюць злучэнне для перадачы даных. Жадаеце выйсці зараз?</item>
+ <item quantity="other">Тэлефон пяройдзе ў рэжым экстраннага зваротнага выкліку на <xliff:g id="COUNT_1">%s</xliff:g> хвіліны. У гэтым рэжыме немагчыма карыстацца праграмамі, якія выкарыстоўваюць злучэнне для перадачы даных. Жадаеце выйсці зараз?</item>
</plurals>
<plurals name="alert_dialog_not_avaialble_in_ecm" formatted="false" msgid="1152682528741457004">
- <item quantity="one">Выбранае дзеянне недаступнае ў рэжыме экстранных зваротных выклікаў. Тэлефон будзе заставацца ў гэтым рэжыме <xliff:g id="COUNT_1">%s</xliff:g> хвіліну. Выйсці зараз?</item>
- <item quantity="few">Выбранае дзеянне недаступнае ў рэжыме экстранных зваротных выклікаў. Тэлефон будзе заставацца ў гэтым рэжыме <xliff:g id="COUNT_1">%s</xliff:g> хвіліны. Выйсці зараз?</item>
- <item quantity="many">Выбранае дзеянне недаступнае ў рэжыме экстранных зваротных выклікаў. Тэлефон будзе заставацца ў гэтым рэжыме <xliff:g id="COUNT_1">%s</xliff:g> хвілін. Выйсці зараз?</item>
- <item quantity="other">Выбранае дзеянне недаступнае ў рэжыме экстранных зваротных выклікаў. Тэлефон будзе заставацца ў гэтым рэжыме <xliff:g id="COUNT_1">%s</xliff:g> хвіліны. Выйсці зараз?</item>
+ <item quantity="one">Выбранае дзеянне недаступнае ў рэжыме экстраннага зваротнага выкліку. Тэлефон застанецца ў гэтым рэжыме на <xliff:g id="COUNT_1">%s</xliff:g> хвіліну. Жадаеце выйсці зараз?</item>
+ <item quantity="few">Выбранае дзеянне недаступнае ў рэжыме экстраннага зваротнага выкліку. Тэлефон застанецца ў гэтым рэжыме на <xliff:g id="COUNT_1">%s</xliff:g> хвіліны. Жадаеце выйсці зараз?</item>
+ <item quantity="many">Выбранае дзеянне недаступнае ў рэжыме экстраннага зваротнага выкліку. Тэлефон застанецца ў гэтым рэжыме на <xliff:g id="COUNT_1">%s</xliff:g> хвілін. Жадаеце выйсці зараз?</item>
+ <item quantity="other">Выбранае дзеянне недаступнае ў рэжыме экстраннага зваротнага выкліку. Тэлефон застанецца ў гэтым рэжыме на <xliff:g id="COUNT_1">%s</xliff:g> хвіліны. Жадаеце выйсці зараз?</item>
</plurals>
<string name="alert_dialog_in_ecm_call" msgid="1207545603149771978">"Выбранае дзеянне недаступнае падчас экстранага выкліку."</string>
- <string name="progress_dialog_exiting_ecm" msgid="9159080081676927217">"Выконваецца выхад з рэжыму экстранных зваротных выклікаў"</string>
+ <string name="progress_dialog_exiting_ecm" msgid="9159080081676927217">"Выхад з рэжыму экстранага выкліку"</string>
<string name="alert_dialog_yes" msgid="3532525979632841417">"Так"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Не"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Адхіліць"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Тэлефон знаходзіцца ў рэжыме экстранных зваротных выклікаў"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Да <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">Тэлефон будзе заставацца ў рэжыме экстранных зваротных выклікаў <xliff:g id="COUNT_1">%s</xliff:g> хвіліну.\nВыйсці зараз?</item>
- <item quantity="few">Тэлефон будзе заставацца ў рэжыме экстранных зваротных выклікаў <xliff:g id="COUNT_1">%s</xliff:g> хвіліны.\nВыйсці зараз?</item>
- <item quantity="many">Тэлефон будзе заставацца ў рэжыме экстранных зваротных выклікаў <xliff:g id="COUNT_1">%s</xliff:g> хвілін.\nВыйсці зараз?</item>
- <item quantity="other">Тэлефон будзе заставацца ў рэжыме экстранных зваротных выклікаў <xliff:g id="COUNT_1">%s</xliff:g> хвіліны.\nВыйсці зараз?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Сэрвіс"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Наладка"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Не зададзены>"</string>
@@ -904,11 +896,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Абнавіць"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Уключыць/выключыць праверку DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Інфармацыя/налады пастаўшчыка"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"Даступнасць EN-DC:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"Абмежаванне DCNR:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"Даступнасць NR:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Стан NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Частата NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Задаць рэжым радыёдыяпазону"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Загружаецца спіс дыяпазонаў…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Задаць"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index d5a1a53..3a3d43d 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -185,7 +185,7 @@
<string name="choose_network_title" msgid="5335832663422653082">"Избиране на мрежа"</string>
<string name="network_disconnected" msgid="8844141106841160825">"Връзката е прекратена"</string>
<string name="network_connected" msgid="2760235679963580224">"Има връзка"</string>
- <string name="network_connecting" msgid="160901383582774987">"Свързване…"</string>
+ <string name="network_connecting" msgid="160901383582774987">"Свързва се…"</string>
<string name="network_could_not_connect" msgid="6547460848093727998">"Не можа да се установи връзка"</string>
<string-array name="preferred_network_mode_choices">
<item msgid="4531933377509551889">"Предпочита се GSM/WCDMA"</item>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Да"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Не"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Отхвърляне"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Телефонът е в режим на спешно обратно обаждане"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"До <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">Телефонът ще бъде в режим на спешно обратно обаждане в продължение на <xliff:g id="COUNT_1">%s</xliff:g> минути.\nИскате ли да излезете сега?</item>
- <item quantity="one">Телефонът ще бъде в режим на спешно обратно обаждане в продължение на <xliff:g id="COUNT_0">%s</xliff:g> минута.\nИскате ли да излезете сега?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Услуга"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Настройка"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Не е зададено>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Опресняване"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Превключване на проверката на DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Информация/настройки, специфични за ОЕМ"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"Налично EN-DC:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"Ограничено DCNR:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"Налично NR:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Състояние на NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Честота за NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Задаване на режима за радиодиапазона"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Списъкът с диапазони се зарежда…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Задаване"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 7410e96..db4be42 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"হ্যাঁ"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"না"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"খারিজ করুন"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"ফোনটি জরুরি কলব্যাক মোডে থাকবে"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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"><xliff:g id="COUNT_1">%s</xliff:g> মিনিটের জন্য ফোনটি জরুরি কলব্যাক মোডে থাকবে।\nআপনি কি এখন বেরিয়ে আসতে চান?</item>
- <item quantity="other"><xliff:g id="COUNT_1">%s</xliff:g> মিনিটের জন্য ফোনটি জরুরি কলব্যাক মোডে থাকবে।\nআপনি কি এখন বেরিয়ে আসতে চান?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"পরিষেবা"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"সেটআপ"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<সেট করা নেই>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"রিফ্রেশ"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS চেক টগল করুন"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-নির্দিষ্ট তথ্য/সেটিংস"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC উপলভ্য:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR সীমাবদ্ধ করা আছে:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR উপলভ্য:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR স্ট্যাটাস:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR ফ্রিকোয়েন্সি:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"রেডিও ব্যান্ড মোড সেট করুন"</string>
<string name="band_mode_loading" msgid="795923726636735967">"ব্যান্ড তালিকা লোড হচ্ছে..."</string>
<string name="band_mode_set" msgid="6657819412803771421">"সেট করুন"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 2dd8b0c..7256c32 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -105,7 +105,7 @@
<string name="labelCFU" msgid="8870170873036279706">"Uvijek proslijedi"</string>
<string name="messageCFU" msgid="1361806450979589744">"Uvijek koristi ovaj broj"</string>
<string name="sum_cfu_enabled_indicator" msgid="9030139213402432776">"Prosljeđivanje svih poziva"</string>
- <string name="sum_cfu_enabled" msgid="5806923046528144526">"Prosljeđivanje svih poziva na <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
+ <string name="sum_cfu_enabled" msgid="5806923046528144526">"Preusmjeravaju se svi pozivi na <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfu_enabled_no_number" msgid="7287752761743377930">"Broj je nedostupan"</string>
<string name="sum_cfu_disabled" msgid="5010617134210809853">"Isključeno"</string>
<string name="labelCFB" msgid="615265213360512768">"Kada je zauzeto"</string>
@@ -127,8 +127,8 @@
<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>
<string name="error_updating_title" msgid="2024290892676808965">"Greška u postavkama poziva"</string>
- <string name="reading_settings" msgid="1605904432450871183">"Čitanje postavki…"</string>
- <string name="updating_settings" msgid="3650396734816028808">"Ažuriranje postavki…"</string>
+ <string name="reading_settings" msgid="1605904432450871183">"Čitanje postavki u toku…"</string>
+ <string name="updating_settings" msgid="3650396734816028808">"Ažuriranje postavki u toku…"</string>
<string name="reverting_settings" msgid="7378668837291012205">"Vraćanje postavki u toku…"</string>
<string name="response_error" msgid="3904481964024543330">"Neočekivani odgovor mreže."</string>
<string name="exception_error" msgid="330994460090467">"Greška na mreži ili SIM kartici."</string>
@@ -296,7 +296,7 @@
<string name="mobile_data_settings_summary" msgid="5012570152029118471">"Pristup prijenosu podataka mobilnom mrežom"</string>
<string name="data_usage_disable_mobile" msgid="5669109209055988308">"Isključiti prijenos podataka na mobilnoj mreži?"</string>
<string name="sim_selection_required_pref" msgid="6985901872978341314">"Potreban izbor"</string>
- <string name="sim_change_data_title" msgid="9142726786345906606">"Promijeniti SIM za prijenos podataka?"</string>
+ <string name="sim_change_data_title" msgid="9142726786345906606">"Promijeniti podatkovni SIM?"</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>
@@ -522,13 +522,13 @@
<string name="notification_voicemail_no_vm_number" msgid="3423686009815186750">"Nepoznat broj govorne pošte"</string>
<string name="notification_network_selection_title" msgid="255595526707809121">"Nema mreže"</string>
<string name="notification_network_selection_text" msgid="553288408722427659">"Odabrana mreža (<xliff:g id="OPERATOR_NAME">%s</xliff:g>) je nedostupna"</string>
- <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" product="watch" msgid="7191184639454113633">"Uključite mobilnu mrežu, isključite Način rada u avionu ili isključite Uštedu baterije da uputite poziv."</string>
+ <string name="incall_error_power_off" product="default" msgid="8131672264311208673">"Isključite način rada u avionu da uputite poziv."</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 uputite poziv."</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>
- <string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobilna mreža nije dostupna. Povežite se na bežičnu mrežu da pozovete."</string>
+ <string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobilna mreža nije dostupna. Povežite se na bežičnu mrežu da uputite poziv."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Da uputite poziv, upišite važeći broj."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Poziv nije uspio."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Trenutno nije moguće dodati poziv. Možete pokušati poslati poruku."</string>
@@ -541,8 +541,8 @@
<string name="incall_error_supp_service_reject" msgid="3044363092441655912">"Nije moguće odbiti poziv."</string>
<string name="incall_error_supp_service_hangup" msgid="836524952243836735">"Nije moguće uputiti poziv(e)."</string>
<string name="incall_error_supp_service_hold" msgid="8535056414643540997">"Nije moguće staviti pozive na čekanje."</string>
- <string name="incall_error_wfc_only_no_wireless_network" msgid="5860742792811400109">"Povežite se na bežičnu mrežu da pozovete."</string>
- <string name="incall_error_promote_wfc" msgid="9164896813931363415">"Omogućite pozivanje putem WiFi-ja da pozovete."</string>
+ <string name="incall_error_wfc_only_no_wireless_network" msgid="5860742792811400109">"Povežite se na bežičnu mrežu da uputite poziv."</string>
+ <string name="incall_error_promote_wfc" msgid="9164896813931363415">"Omogućite pozivanje putem WiFi-ja da uputite poziv."</string>
<string name="emergency_information_hint" msgid="9208897544917793012">"Informacije za hitne slučajeve"</string>
<string name="emergency_information_owner_hint" msgid="6256909888049185316">"Vlasnik"</string>
<string name="emergency_information_confirm_hint" msgid="5109017615894918914">"Dodirnite ponovo da pogledate informacije"</string>
@@ -604,7 +604,7 @@
<string name="ota_hfa_activation_title" msgid="3300556778212729671">"Aktivacija u toku..."</string>
<string name="ota_hfa_activation_dialog_message" msgid="7921718445773342996">"Telefon aktivira uslugu prijenosa mobilnih podataka.\n\nTo može potrajati do 5 minuta."</string>
<string name="ota_skip_activation_dialog_title" msgid="7666611236789203797">"Preskočiti aktivaciju?"</string>
- <string name="ota_skip_activation_dialog_message" msgid="6691722887019708713">"Ako preskočite aktivaciju, nećete moći upućivati pozive niti se povezati na mobilne podatkovne mreže (iako se možete povezati s WiFi mrežama). Dok ne aktivirate telefon, prikazivat će se upit za aktivaciju svaki put kada upalite telefon."</string>
+ <string name="ota_skip_activation_dialog_message" msgid="6691722887019708713">"Ako preskočite aktivaciju, nećete moći upućivati pozive niti se povezati na mobilne podatkovne mreže (iako se možete povezati na WiFi mreže). Dok ne aktivirate telefon, prikazivat će se upit za aktivaciju svaki put kada upalite telefon."</string>
<string name="ota_skip_activation_dialog_skip_label" msgid="5908029466817825633">"Preskoči"</string>
<string name="ota_activate" msgid="7939695753665438357">"Aktiviraj"</string>
<string name="ota_title_activate_success" msgid="1272135024761004889">"Telefon je aktiviran."</string>
@@ -639,13 +639,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Da"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Ne"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Odbaci"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Telefon je u načinu rada za hitni povratni poziv"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Do <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">Telefon će biti u načinu rada za hitni povratni poziv <xliff:g id="COUNT_1">%s</xliff:g> minutu.\nŽelite li izaći iz ovog načina rada sada?</item>
- <item quantity="few">Telefon će biti u načinu rada za hitni povratni poziv <xliff:g id="COUNT_1">%s</xliff:g> minute.\nŽelite li izaći iz ovog načina rada sada?</item>
- <item quantity="other">Telefon će biti u načinu rada za hitni povratni poziv <xliff:g id="COUNT_1">%s</xliff:g> minuta.\nŽelite li izaći iz ovog načina rada sada?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Usluga"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Postavljanje"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Nije postavljeno>"</string>
@@ -664,7 +657,7 @@
<string name="preference_category_ringtone" msgid="8787281191375434976">"Melodija zvona i vibracija"</string>
<string name="pstn_connection_service_label" msgid="9200102709997537069">"Ugrađene SIM kartice"</string>
<string name="enable_video_calling_title" msgid="7246600931634161830">"Uključi video pozive"</string>
- <string name="enable_video_calling_dialog_msg" msgid="7141478720386203540">"Da uključite video pozive, omogućite poboljšani 4G LTE način rada u postavkama mreže."</string>
+ <string name="enable_video_calling_dialog_msg" msgid="7141478720386203540">"Da uključite video pozive, omogućite Poboljšani 4G LTE način rada u postavkama mreže."</string>
<string name="enable_video_calling_dialog_settings" msgid="8697890611305307110">"Postavke mreže"</string>
<string name="enable_video_calling_dialog_close" msgid="4298929725917045270">"Zatvori"</string>
<string name="sim_label_emergency_calls" msgid="9078241989421522310">"Hitni pozivi"</string>
@@ -858,7 +851,7 @@
<string name="radioInfo_data_disconnected" msgid="8085447971880814541">"Veza je prekinuta"</string>
<string name="radioInfo_data_connecting" msgid="925092271092152472">"Povezivanje"</string>
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Povezano"</string>
- <string name="radioInfo_data_suspended" msgid="8695262782642002785">"Obustavljeno"</string>
+ <string name="radioInfo_data_suspended" msgid="8695262782642002785">"Suspendirano"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Nepoznato"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"paketi"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bajtova"</string>
@@ -867,7 +860,7 @@
<string name="radioInfo_lac" msgid="3892986460272607013">"LAC"</string>
<string name="radioInfo_cid" msgid="1423185536264406705">"CID"</string>
<string name="radio_info_subid" msgid="6839966868621703203">"Trenutni pomoćni ID:"</string>
- <string name="radio_info_dds" msgid="1122593144425697126">"Pomoćni ID za zadani SIM za prijenos podataka:"</string>
+ <string name="radio_info_dds" msgid="1122593144425697126">"Pomoćni ID za zadani podatkovni SIM:"</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>
@@ -901,11 +894,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Osvježi"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Uključi/isključi provjeru DNS-a"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-specifične informacije/postavke"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC dostupno:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR ograničeno:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR dostupno:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR stanje:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR učestalost:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Postavite način radijskog opsega"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Učitavanje liste opsega…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Postavi"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 5d3f3dd..a99de70 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -62,7 +62,7 @@
<string name="labelCdmaMore_with_label" msgid="7759692829160238152">"Configuració de trucades CDMA (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="apn_settings" msgid="1978652203074756623">"Noms dels punts d\'accés"</string>
<string name="settings_label" msgid="9101778088412567956">"Configuració de xarxa"</string>
- <string name="phone_accounts" msgid="1216879437523774604">"Comptes de trucada"</string>
+ <string name="phone_accounts" msgid="1216879437523774604">"Comptes de trucades"</string>
<string name="phone_accounts_make_calls_with" msgid="16747814788918145">"Truca amb"</string>
<string name="phone_accounts_make_sip_calls_with" msgid="4691221006731847255">"Fes trucades SIP amb"</string>
<string name="phone_accounts_ask_every_time" msgid="6192347582666047168">"Pregunta primer"</string>
@@ -95,7 +95,7 @@
<string name="sum_loading_settings" msgid="434063780286688775">"Carregant la configuració..."</string>
<string name="sum_hide_caller_id" msgid="131100328602371933">"Número amagat en trucades sortints"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"Número mostrat a les trucades de sortida"</string>
- <string name="sum_default_caller_id" msgid="1767070797135682959">"Utilitza la configuració predeterminada de l\'operador per mostrar el meu número a les trucades sortints"</string>
+ <string name="sum_default_caller_id" msgid="1767070797135682959">"Utilitza la configuració predeterminada de l\'operador per mostrar el meu número a les trucades de sortida"</string>
<string name="labelCW" msgid="8449327023861428622">"Trucada en espera"</string>
<string name="sum_cw_enabled" msgid="3977308526187139996">"Durant una trucada, avisa\'m de les trucades entrants"</string>
<string name="sum_cw_disabled" msgid="3658094589461768637">"Durant una trucada, avisa\'m de les trucades entrants"</string>
@@ -307,7 +307,7 @@
<string name="throttle_time_frame" msgid="1813452485948918791">"Període d\'ús de dades"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Política de velocitat de dades"</string>
<string name="throttle_help" msgid="2624535757028809735">"Més informació"</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>٪) del període màxim de <xliff:g id="USED_2">%3$s</xliff:g>.\nEl període següent comença d\'aquí a <xliff:g id="USED_3">%4$d</xliff:g> dies (<xliff:g id="USED_4">%5$s</xliff:g>)"</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>٪) del període <xliff:g id="USED_2">%3$s</xliff:g> màxim\nEl període següent comença d\'aquí a <xliff:g id="USED_3">%4$d</xliff:g> dies (<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>٪) de període <xliff:g id="USED_2">%3$s</xliff:g> màxim"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"<xliff:g id="USED_0">%1$s</xliff:g> màxim superat\nVelocitat de dades reduïda a <xliff:g id="USED_1">%2$d</xliff:g> kbps"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g>٪ del cicle transcorregut\nEl període següent comença d\'aquí a <xliff:g id="USED_1">%2$d</xliff:g> dies (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -583,7 +583,7 @@
<string name="hac_mode_summary" msgid="7774989500136009881">"Activa la compatibilitat amb audiòfons"</string>
<string name="rtt_mode_title" msgid="3075948111362818043">"Trucada de text en temps real (RTT)"</string>
<string name="rtt_mode_summary" msgid="8631541375609989562">"Permet l\'ús de missatges en una trucada de veu"</string>
- <string name="rtt_mode_more_information" msgid="587500128658756318">"La funció RTT ofereix assistència als usuaris que són sords, tenen deficiències auditives o alteracions de la parla, o bé que necessiten un text a banda de la veu.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>Més informació</a>\n <br><br> - Les trucades RTT es desen en forma de transcripció.\n <br> - Aquesta funció no està disponible per a videotrucades."</string>
+ <string name="rtt_mode_more_information" msgid="587500128658756318">"La funció RTT ofereix assistència als usuaris que són sords, tenen problemes d\'audició o alteracions de la parla, o bé que necessiten un text a banda de la veu.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>Més informació</a>\n <br><br> - Les trucades RTT es desen en forma de transcripció.\n <br> - Aquesta funció no està disponible per a videotrucades."</string>
<string name="no_rtt_when_roaming" msgid="5268008247378355389">"Nota: la funció RTT no està disponible en itinerància"</string>
<string-array name="tty_mode_entries">
<item msgid="3238070884803849303">"TTY desactivat"</item>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Sí"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"No"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Ignora"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"El telèfon està en el mode de devolució de trucada d\'emergència"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Fins a les <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">El telèfon estarà en el mode de devolució de trucada d\'emergència durant <xliff:g id="COUNT_1">%s</xliff:g> minuts.\nVols sortir-ne ara?</item>
- <item quantity="one">El telèfon estarà en el mode de devolució de trucada d\'emergència durant <xliff:g id="COUNT_0">%s</xliff:g> minut.\nVols sortir-ne ara?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Servei"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Configuració"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<No definit>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Actualitza"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Activa o desactiva la comprovació de DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Informació/configuració específica d\'OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC disponible:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR amb restriccions:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR disponible:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Estat d\'NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Freqüència d\'NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Configura el mode de banda de senyal mòbil"</string>
<string name="band_mode_loading" msgid="795923726636735967">"S\'està carregant la llista de bandes…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Defineix"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 31119ee..7ce0a25 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -141,7 +141,7 @@
<string name="close_dialog" msgid="1074977476136119408">"OK"</string>
<string name="enable" msgid="2636552299455477603">"Zapnout"</string>
<string name="disable" msgid="1122698860799462116">"Vypnout"</string>
- <string name="change_num" msgid="6982164494063109334">"Aktualizovat"</string>
+ <string name="change_num" msgid="6982164494063109334">"Aktualiz."</string>
<string-array name="clir_display_values">
<item msgid="8477364191403806960">"Výchozí nastavení sítě"</item>
<item msgid="6813323051965618926">"Skrýt číslo"</item>
@@ -293,7 +293,7 @@
<string name="keywords_carrier_settings_euicc" msgid="8540160967922063745">"operátor, esim, sim, euicc, přepnout operátora, přidat operátora"</string>
<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">"Mobilní data"</string>
- <string name="mobile_data_settings_summary" msgid="5012570152029118471">"Používat datové připojení přes mobilní síť"</string>
+ <string name="mobile_data_settings_summary" msgid="5012570152029118471">"Přistupovat k datům přes mobilní síť"</string>
<string name="data_usage_disable_mobile" msgid="5669109209055988308">"Vypnout mobilní data?"</string>
<string name="sim_selection_required_pref" msgid="6985901872978341314">"Vyžadován výběr"</string>
<string name="sim_change_data_title" msgid="9142726786345906606">"Změnit SIM kartu pro data?"</string>
@@ -641,14 +641,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Ano"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Ne"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Zavřít"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Telefon je v režimu tísňového zpětného volání"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Do <xliff:g id="COMPLETETIME">%s</xliff:g>"</string>
- <plurals name="alert_dialog_exit_ecm_without_data_restriction_hint" formatted="false" msgid="6477733043040328640">
- <item quantity="few">Telefon bude v režimu tísňového zpětného volání po dobu <xliff:g id="COUNT_1">%s</xliff:g> minut.\nChcete tento režim ukončit?</item>
- <item quantity="many">Telefon bude v režimu tísňového zpětného volání po dobu <xliff:g id="COUNT_1">%s</xliff:g> minuty.\nChcete tento režim ukončit?</item>
- <item quantity="other">Telefon bude v režimu tísňového zpětného volání po dobu <xliff:g id="COUNT_1">%s</xliff:g> minut.\nChcete tento režim ukončit?</item>
- <item quantity="one">Telefon bude v režimu tísňového zpětného volání po dobu <xliff:g id="COUNT_0">%s</xliff:g> minuty.\nChcete tento režim ukončit?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Služba"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Konfigurace"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Nenastaveno>"</string>
@@ -904,11 +896,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Obnovit"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Přepnout kontrolu DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Informace a nastavení specifické pro výrobce OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC k dispozici:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR zakázáno:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR k dispozici:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Stav NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Frekvence NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Nastavit režim pásma bezdrátového modulu"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Načítání seznamu pásem…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Nastavit"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 857e9e4..c8a1c34 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -168,7 +168,7 @@
<string name="label_available" msgid="1316084116670821258">"Tilgængelige netværk"</string>
<string name="load_networks_progress" msgid="4051433047717401683">"Søger..."</string>
<string name="empty_networks_list" msgid="9216418268008582342">"Der blev ikke fundet nogen netværk."</string>
- <string name="network_query_error" msgid="3862515805115145124">"Der blev ikke fundet netværk. Prøv igen."</string>
+ <string name="network_query_error" msgid="3862515805115145124">"Der kunne ikke findes netværk. Prøv igen."</string>
<string name="register_on_network" msgid="4194770527833960423">"Registrerer på <xliff:g id="NETWORK">%s</xliff:g> ..."</string>
<string name="not_allowed" msgid="8541221928746104798">"Dit SIM-kort tillader ikke en forbindelse til dette netværk."</string>
<string name="connect_later" msgid="1950138106010005425">"Der kan ikke oprettes forbindelse til dette netværk lige nu. Prøv igen senere."</string>
@@ -307,7 +307,9 @@
<string name="throttle_time_frame" msgid="1813452485948918791">"Periode for databrug"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Politik om datahastighed"</string>
<string name="throttle_help" msgid="2624535757028809735">"Flere oplysninger"</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> %%) af <xliff:g id="USED_2">%3$s</xliff:g> periodens maksimum\nNæste periode om <xliff:g id="USED_3">%4$d</xliff:g> dage (<xliff:g id="USED_4">%5$s</xliff:g>)"</string>
+ <!-- String.format failed for translation -->
+ <!-- no translation found for throttle_status_subtext (1110276415078236687) -->
+ <skip />
<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> ٪) af <xliff:g id="USED_2">%3$s</xliff:g> periodens maksimum"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"<xliff:g id="USED_0">%1$s</xliff:g> maksimum er overskredet\nDatahastigheden er nedsat til <xliff:g id="USED_1">%2$d</xliff:g> Kb/s"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g> ٪ af forløbet er gennemført\nNæste periode om <xliff:g id="USED_1">%2$d</xliff:g> dage (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -570,7 +572,7 @@
<string name="onscreenMergeCallsText" msgid="3692389519611225407">"Slå opkald sammen"</string>
<string name="onscreenSwapCallsText" msgid="2682542150803377991">"Skift"</string>
<string name="onscreenManageCallsText" msgid="1162047856081836469">"Administrer opkald"</string>
- <string name="onscreenManageConferenceText" msgid="4700574060601755137">"Admin. møde"</string>
+ <string name="onscreenManageConferenceText" msgid="4700574060601755137">"Admin. konference"</string>
<string name="onscreenAudioText" msgid="7224226735052019986">"Lyd"</string>
<string name="onscreenVideoCallText" msgid="1743992456126258698">"Videoopkald"</string>
<string name="importSimEntry" msgid="3892354284082689894">"Importer"</string>
@@ -637,12 +639,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Ja"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Nej"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Luk"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Telefonen er i nødtilbagekaldstilstand"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Indtil <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">Telefonen er i nødtilbagekaldstilstand i <xliff:g id="COUNT_1">%s</xliff:g> minut.\nVil du lukke nu?</item>
- <item quantity="other">Telefonen er i nødtilbagekaldstilstand i <xliff:g id="COUNT_1">%s</xliff:g> minutter.\nVil du lukke nu?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Tjeneste"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Konfiguration"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Ikke angivet>"</string>
@@ -804,7 +800,7 @@
<string name="supp_service_additional_call_forwarded" msgid="8772753260008398632">"Det andet opkald er videresendt."</string>
<string name="supp_service_additional_ect_connected" msgid="8525934162945220237">"Opkaldet blev tilsluttet ECT."</string>
<string name="supp_service_additional_ect_connecting" msgid="7046240728781222753">"Opkaldet tilsluttes ECT."</string>
- <string name="supp_service_call_on_hold" msgid="2836811319594503059">"Opkald på hold."</string>
+ <string name="supp_service_call_on_hold" msgid="2836811319594503059">"Parkeret opkald."</string>
<string name="supp_service_call_resumed" msgid="3786864005920743546">"Opkaldet er genoptaget."</string>
<string name="supp_service_deflected_call" msgid="7565979024562921707">"Opkaldet blev viderestillet."</string>
<string name="supp_service_forwarded_call" msgid="6475776013771821457">"Videresendt opkald."</string>
@@ -898,11 +894,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Opdater"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Skift DNS-kontrol"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-specifikke oplysninger/indstillinger"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"Tilgængelig for EN-DC:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"Begrænset til DCNR"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"Tilgængelig for NR:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Status for NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Frekvens for NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Konfigurer radiobåndstilstand"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Indlæser båndliste…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Angiv"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index dec144a..ac9859c 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -49,12 +49,12 @@
<string name="add_vm_number_str" msgid="7368168964435881637">"Nummer hinzufügen"</string>
<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_ndp" msgid="7617392683877410341">"PIN zur Entsperrung des SIM-Netzwerks"</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>
- <string name="unlock_failed" msgid="7103543844840661366">"Entsperranforderung für netzgebundenes Gerät war nicht erfolgreich."</string>
- <string name="unlock_success" msgid="32681089371067565">"Entsperrung des netzgebundenen Geräts war nicht erfolgreich."</string>
+ <string name="requesting_unlock" msgid="930512210309437741">"Netzwerkentsperrung wird angefordert..."</string>
+ <string name="unlock_failed" msgid="7103543844840661366">"Anfrage für Entsperrung des Netzwerks war nicht erfolgreich."</string>
+ <string name="unlock_success" msgid="32681089371067565">"Entsperrung des Netzwerks nicht erfolgreich."</string>
<string name="mobile_network_settings_not_available" msgid="8678168497517090039">"Mobile Netzwerkeinstellungen sind für diesen Nutzer nicht verfügbar."</string>
<string name="labelGSMMore" msgid="7354182269461281543">"GSM-Anrufeinstellungen"</string>
<string name="labelGsmMore_with_label" msgid="3206015314393246224">"GSM-Anrufeinstellungen (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -307,7 +307,9 @@
<string name="throttle_time_frame" msgid="1813452485948918791">"Zeitraum des Datenverbrauchs"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Richtlinien zur Datenrate"</string>
<string name="throttle_help" msgid="2624535757028809735">"Weitere Informationen"</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> %%) des maximalen Zeitraums von <xliff:g id="USED_2">%3$s</xliff:g>.\nDer nächste Zeitraum beginnt in <xliff:g id="USED_3">%4$d</xliff:g> Tagen (<xliff:g id="USED_4">%5$s</xliff:g>)."</string>
+ <!-- String.format failed for translation -->
+ <!-- no translation found for throttle_status_subtext (1110276415078236687) -->
+ <skip />
<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> ٪) des maximalen Zeitraums von <xliff:g id="USED_2">%3$s</xliff:g>"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"Maximum von <xliff:g id="USED_0">%1$s</xliff:g> wurde überschritten.\nDatenrate wurde auf <xliff:g id="USED_1">%2$d</xliff:g> kbit/s reduziert."</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g> ٪ des Zyklus sind verstrichen. \nDer nächste Zeitraum beginnt in <xliff:g id="USED_1">%2$d</xliff:g> Tagen (<xliff:g id="USED_2">%3$s</xliff:g>)."</string>
@@ -637,12 +639,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Ja"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Nein"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Verwerfen"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Das Smartphone ist im Notfallrückrufmodus"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Bis <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">Das Smartphone bleibt <xliff:g id="COUNT_1">%s</xliff:g> Minuten im Notfallrückrufmodus.\nMöchtest du den Vorgang jetzt beenden?</item>
- <item quantity="one">Das Smartphone bleibt <xliff:g id="COUNT_0">%s</xliff:g> Minute im Notfallrückrufmodus.\nMöchtest du den Vorgang jetzt beenden?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Dienst"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Einrichtung"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Nicht festgelegt>"</string>
@@ -898,11 +894,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Aktualisieren"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS-Überprüfung ein-/ausschalten"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-spezifische Infos/Einstellungen"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC verfügbar:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR eingeschränkt:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR verfügbar:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR-Status:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR-Frequenz:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Frequenzbereichmodus festlegen"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Frequenzliste wird geladen…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Festlegen"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index e7af2ec..9a4e243 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -307,7 +307,7 @@
<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_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_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> Kb/s"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"Έχει περάσει το <xliff:g id="USED_0">%1$d</xliff:g>٪ του κύκλου\nΗ επόμενη περίοδος ξεκινά σε <xliff:g id="USED_1">%2$d</xliff:g> ημέρες (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Ναι"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Όχι"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Απόρριψη"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Το τηλέφωνο βρίσκεται σε λειτουργία επιστροφής κλήσης έκτακτης ανάγκης"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Έως <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">Το τηλέφωνο θα βρίσκεται σε λειτουργία επιστροφής κλήσης έκτακτης ανάγκης για <xliff:g id="COUNT_1">%s</xliff:g> λεπτά.\nΕπιθυμείτε έξοδο τώρα;</item>
- <item quantity="one">Το τηλέφωνο θα βρίσκεται σε λειτουργία επιστροφής κλήσης έκτακτης ανάγκης για <xliff:g id="COUNT_0">%s</xliff:g> λεπτό.\nΕπιθυμείτε έξοδο τώρα;</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Υπηρεσία"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Ρυθμίσεις"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Δεν έχει οριστεί>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Ανανέωση"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Εναλλαγή ελέγχου DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Πληροφορίες/ρυθμίσεις για OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC διαθέσιμο:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR με περιορισμό:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR διαθέσιμο:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Κατάσταση NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Συχνότητα NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Ρύθμιση λειτουργίας ζώνης συχνοτήτων πομπού"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Φόρτωση λίστας ζωνών…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Ορισμός"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index aacdc53..b732508 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -74,7 +74,7 @@
<string name="phone_accounts_configure_account_settings" msgid="6622119715253196586">"Configure account settings"</string>
<string name="phone_accounts_all_calling_accounts" msgid="1609600743500618823">"All calling accounts"</string>
<string name="phone_accounts_all_calling_accounts_summary" msgid="2214134955430107240">"Select which accounts can make calls"</string>
- <string name="wifi_calling" msgid="3650509202851355742">"Wi-Fi calling"</string>
+ <string name="wifi_calling" msgid="3650509202851355742">"Wi-Fi Calling"</string>
<string name="connection_service_default_label" msgid="7332739049855715584">"Built-in connection service"</string>
<string name="voicemail" msgid="7697769412804195032">"Voicemail"</string>
<string name="voicemail_settings_with_label" msgid="4228431668214894138">"Voicemail (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Yes"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"No"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Dismiss"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"The phone is in emergency callback mode"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Until <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">The phone will be in emergency callback mode for <xliff:g id="COUNT_1">%s</xliff:g> minutes.\nDo you want to exit now?</item>
- <item quantity="one">The phone will be in emergency callback mode for <xliff:g id="COUNT_0">%s</xliff:g> minute.\nDo you want to exit now?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Service"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Setup"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Not set>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Refresh"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Toggle DNS check"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-specific info/settings"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC available:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR restricted:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR available:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR status:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR frequency:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Set radio band mode"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Loading band list…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Set"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index 16b1df4..4cd63be 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -74,7 +74,7 @@
<string name="phone_accounts_configure_account_settings" msgid="6622119715253196586">"Configure account settings"</string>
<string name="phone_accounts_all_calling_accounts" msgid="1609600743500618823">"All calling accounts"</string>
<string name="phone_accounts_all_calling_accounts_summary" msgid="2214134955430107240">"Select which accounts can make calls"</string>
- <string name="wifi_calling" msgid="3650509202851355742">"Wi-Fi calling"</string>
+ <string name="wifi_calling" msgid="3650509202851355742">"Wi-Fi Calling"</string>
<string name="connection_service_default_label" msgid="7332739049855715584">"Built-in connection service"</string>
<string name="voicemail" msgid="7697769412804195032">"Voicemail"</string>
<string name="voicemail_settings_with_label" msgid="4228431668214894138">"Voicemail (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -471,7 +471,7 @@
<string name="simContacts_emptyLoading" msgid="4989040293858675483">"Reading from SIM card…"</string>
<string name="simContacts_empty" msgid="1135632055473689521">"No contacts on your SIM card."</string>
<string name="simContacts_title" msgid="2714029230160136647">"Select contacts to import"</string>
- <string name="simContacts_airplaneMode" msgid="4654884030631503808">"Turn off Airplane mode to import contacts from the SIM card."</string>
+ <string name="simContacts_airplaneMode" msgid="4654884030631503808">"Turn off aeroplane mode to import contacts from the SIM card."</string>
<string name="enable_pin" msgid="967674051730845376">"Enable/disable SIM PIN"</string>
<string name="change_pin" msgid="3657869530942905790">"Change SIM PIN"</string>
<string name="enter_pin_text" msgid="3182311451978663356">"SIM PIN:"</string>
@@ -523,8 +523,8 @@
<string name="notification_network_selection_title" msgid="255595526707809121">"No service"</string>
<string name="notification_network_selection_text" msgid="553288408722427659">"Selected network<xliff:g id="OPERATOR_NAME">%s</xliff:g> unavailable"</string>
<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" 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_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>
@@ -552,7 +552,7 @@
<string name="emergency_call_shortcut_hint" msgid="1290485125107779500">"Tap again to call <xliff:g id="EMERGENCY_NUMBER">%s</xliff:g>"</string>
<string name="emergency_enable_radio_dialog_message" msgid="1695305158151408629">"Turning on radio…"</string>
<string name="emergency_enable_radio_dialog_retry" msgid="4329131876852608587">"No service. Trying again…"</string>
- <string name="radio_off_during_emergency_call" msgid="8011154134040481609">"Cannot enter Airplane mode during an emergency call."</string>
+ <string name="radio_off_during_emergency_call" msgid="8011154134040481609">"Cannot enter aeroplane mode during an emergency call."</string>
<string name="dial_emergency_error" msgid="825822413209026039">"Can\'t call. <xliff:g id="NON_EMERGENCY_NUMBER">%s</xliff:g> is not an emergency number."</string>
<string name="dial_emergency_empty_error" msgid="2785803395047793634">"Can\'t call. Dial an emergency number."</string>
<string name="dial_emergency_calling_not_available" msgid="6485846193794727823">"Emergency calling not available"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Yes"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"No"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Dismiss"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"The phone is in emergency callback mode"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Until <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">The phone will be in emergency callback mode for <xliff:g id="COUNT_1">%s</xliff:g> minutes.\nDo you want to exit now?</item>
- <item quantity="one">The phone will be in emergency callback mode for <xliff:g id="COUNT_0">%s</xliff:g> minute.\nDo you want to exit now?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Service"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Setup"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Not set>"</string>
@@ -707,7 +701,7 @@
<string name="mobile_data_activate_button" msgid="1139792516354374612">"ADD DATA"</string>
<string name="mobile_data_activate_cancel_button" msgid="3530174817572005860">"CANCEL"</string>
<string name="clh_card_title_call_ended_txt" msgid="5977978317527299698">"Call ended"</string>
- <string name="clh_callFailed_powerOff_txt" msgid="8279934912560765361">"Airplane mode is on"</string>
+ <string name="clh_callFailed_powerOff_txt" msgid="8279934912560765361">"Aeroplane mode is on"</string>
<string name="clh_callFailed_simError_txt" msgid="5128538525762326413">"Can\'t access SIM card"</string>
<string name="clh_incall_error_out_of_service_txt" msgid="2736010617446749869">"Mobile network not available"</string>
<string name="clh_callFailed_unassigned_number_txt" msgid="141967660286695682">"Issue with phone number you are trying to dial. Error code 1."</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Refresh"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Toggle DNS check"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-specific info/settings"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC available:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR restricted:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR available:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR status:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR frequency:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Set radio band mode"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Loading band list…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Set"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index aacdc53..b732508 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -74,7 +74,7 @@
<string name="phone_accounts_configure_account_settings" msgid="6622119715253196586">"Configure account settings"</string>
<string name="phone_accounts_all_calling_accounts" msgid="1609600743500618823">"All calling accounts"</string>
<string name="phone_accounts_all_calling_accounts_summary" msgid="2214134955430107240">"Select which accounts can make calls"</string>
- <string name="wifi_calling" msgid="3650509202851355742">"Wi-Fi calling"</string>
+ <string name="wifi_calling" msgid="3650509202851355742">"Wi-Fi Calling"</string>
<string name="connection_service_default_label" msgid="7332739049855715584">"Built-in connection service"</string>
<string name="voicemail" msgid="7697769412804195032">"Voicemail"</string>
<string name="voicemail_settings_with_label" msgid="4228431668214894138">"Voicemail (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Yes"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"No"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Dismiss"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"The phone is in emergency callback mode"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Until <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">The phone will be in emergency callback mode for <xliff:g id="COUNT_1">%s</xliff:g> minutes.\nDo you want to exit now?</item>
- <item quantity="one">The phone will be in emergency callback mode for <xliff:g id="COUNT_0">%s</xliff:g> minute.\nDo you want to exit now?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Service"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Setup"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Not set>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Refresh"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Toggle DNS check"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-specific info/settings"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC available:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR restricted:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR available:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR status:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR frequency:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Set radio band mode"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Loading band list…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Set"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index aacdc53..b732508 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -74,7 +74,7 @@
<string name="phone_accounts_configure_account_settings" msgid="6622119715253196586">"Configure account settings"</string>
<string name="phone_accounts_all_calling_accounts" msgid="1609600743500618823">"All calling accounts"</string>
<string name="phone_accounts_all_calling_accounts_summary" msgid="2214134955430107240">"Select which accounts can make calls"</string>
- <string name="wifi_calling" msgid="3650509202851355742">"Wi-Fi calling"</string>
+ <string name="wifi_calling" msgid="3650509202851355742">"Wi-Fi Calling"</string>
<string name="connection_service_default_label" msgid="7332739049855715584">"Built-in connection service"</string>
<string name="voicemail" msgid="7697769412804195032">"Voicemail"</string>
<string name="voicemail_settings_with_label" msgid="4228431668214894138">"Voicemail (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Yes"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"No"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Dismiss"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"The phone is in emergency callback mode"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Until <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">The phone will be in emergency callback mode for <xliff:g id="COUNT_1">%s</xliff:g> minutes.\nDo you want to exit now?</item>
- <item quantity="one">The phone will be in emergency callback mode for <xliff:g id="COUNT_0">%s</xliff:g> minute.\nDo you want to exit now?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Service"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Setup"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Not set>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Refresh"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Toggle DNS check"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-specific info/settings"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC available:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR restricted:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR available:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR status:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR frequency:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Set radio band mode"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Loading band list…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Set"</string>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 91835c5..3f04696 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Yes"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"No"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Dismiss"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"The phone is in emergency callback mode"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Until <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">The phone will be in emergency callback mode for <xliff:g id="COUNT_1">%s</xliff:g> minutes.\nDo you want to exit now?</item>
- <item quantity="one">The phone will be in emergency callback mode for <xliff:g id="COUNT_0">%s</xliff:g> minute.\nDo you want to exit now?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Service"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Setup"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Not set>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Refresh"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Toggle DNS Check"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-specific Info/Settings"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC Available:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR Restricted:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR Available:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR State:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR Frequency:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Set Radio Band Mode"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Loading Band List…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Set"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index d2f0d94..6a052dc 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -95,12 +95,12 @@
<string name="sum_loading_settings" msgid="434063780286688775">"Cargando la configuración…"</string>
<string name="sum_hide_caller_id" msgid="131100328602371933">"Número escondido en llamadas salientes"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"Número visualizado en llamadas salientes"</string>
- <string name="sum_default_caller_id" msgid="1767070797135682959">"Utilizar configuración predeterminada del operador para visualizar mi número en las llamadas salientes"</string>
+ <string name="sum_default_caller_id" msgid="1767070797135682959">"Utilizar configuración del operador predeterminada para visualizar mi número en las llamadas salientes"</string>
<string name="labelCW" msgid="8449327023861428622">"Llamada en espera"</string>
<string name="sum_cw_enabled" msgid="3977308526187139996">"Durante una llamada, notificarme sobre las llamadas entrantes"</string>
<string name="sum_cw_disabled" msgid="3658094589461768637">"Durante una llamada, notificarme sobre las llamadas entrantes"</string>
<string name="call_forwarding_settings" msgid="8937130467468257671">"Desvío de llamadas"</string>
- <string name="call_forwarding_settings_with_label" msgid="2345432813399564272">"Configuración de desvío de llamadas (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
+ <string name="call_forwarding_settings_with_label" msgid="2345432813399564272">"Configuración de desvío de llamada (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="labelCF" msgid="3578719437928476078">"Desvío de llamadas"</string>
<string name="labelCFU" msgid="8870170873036279706">"Desviar siempre"</string>
<string name="messageCFU" msgid="1361806450979589744">"Usar siempre este número"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Sí"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"No"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Descartar"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"El teléfono está en modo de devolución de llamadas de emergencia"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Hasta las <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">El teléfono estará en modo de devolución de llamada de emergencia durante <xliff:g id="COUNT_1">%s</xliff:g> minutos.\n¿Quieres salir ahora?</item>
- <item quantity="one">El teléfono estará en modo de devolución de llamada de emergencia durante <xliff:g id="COUNT_0">%s</xliff:g> minuto.\n¿Quieres salir ahora?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Servicio"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Configuración"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Sin configurar>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Actualizar"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Activar o desactivar la comprobación de DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Configuración/Datos específicos del OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC disponible:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR restringida:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR disponible:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Estado de NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Frecuencia de NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Establecer modo de banda de radio"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Cargando lista de bandas…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Establecer"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index c230ba7..e8a92c4 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -43,7 +43,7 @@
<string name="send_button" msgid="5070379600779031932">"Enviar"</string>
<string name="pause_prompt_yes" msgid="8184132073048369575">"Sí"</string>
<string name="pause_prompt_no" msgid="2145264674774138579">"No"</string>
- <string name="wild_prompt_str" msgid="5858910969703305375">"Reemplazar el carácter comodín por"</string>
+ <string name="wild_prompt_str" msgid="5858910969703305375">"Sustituir el carácter comodín por"</string>
<string name="no_vm_number" msgid="6623853880546176930">"Falta el número del buzón de voz."</string>
<string name="no_vm_number_msg" msgid="5165161462411372504">"No se ha almacenado ningún número de buzón de voz en la tarjeta SIM."</string>
<string name="add_vm_number_str" msgid="7368168964435881637">"Añadir número"</string>
@@ -62,7 +62,7 @@
<string name="labelCdmaMore_with_label" msgid="7759692829160238152">"Ajustes de llamada de CDMA (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="apn_settings" msgid="1978652203074756623">"Nombres de puntos de acceso"</string>
<string name="settings_label" msgid="9101778088412567956">"Ajustes de red"</string>
- <string name="phone_accounts" msgid="1216879437523774604">"Cuentas de llamada"</string>
+ <string name="phone_accounts" msgid="1216879437523774604">"Cuentas de llamadas"</string>
<string name="phone_accounts_make_calls_with" msgid="16747814788918145">"Realizar llamadas con"</string>
<string name="phone_accounts_make_sip_calls_with" msgid="4691221006731847255">"Realizar llamadas SIP con"</string>
<string name="phone_accounts_ask_every_time" msgid="6192347582666047168">"Preguntar primero"</string>
@@ -174,7 +174,7 @@
<string name="connect_later" msgid="1950138106010005425">"No se puede conectar a la red en este momento. Inténtalo de nuevo más tarde."</string>
<string name="registration_done" msgid="5337407023566953292">"Conexión con la red establecida"</string>
<string name="already_auto" msgid="8607068290733079336">"Ya estás en la selección automática."</string>
- <string name="select_automatically" msgid="779750291257872651">"Seleccionar red automáticamente"</string>
+ <string name="select_automatically" msgid="779750291257872651">"Seleccionar una red automáticamente"</string>
<string name="manual_mode_disallowed_summary" msgid="3970048592179890197">"No disponible cuando se ha conectado con %1$s"</string>
<string name="network_select_title" msgid="4117305053881611988">"Red"</string>
<string name="register_automatically" msgid="3907580547590554834">"Registro automático..."</string>
@@ -184,7 +184,7 @@
<string name="forbidden_network" msgid="5081729819561333023">"(prohibida)"</string>
<string name="choose_network_title" msgid="5335832663422653082">"Elegir red"</string>
<string name="network_disconnected" msgid="8844141106841160825">"Desconectado"</string>
- <string name="network_connected" msgid="2760235679963580224">"Conectado"</string>
+ <string name="network_connected" msgid="2760235679963580224">"Conectada"</string>
<string name="network_connecting" msgid="160901383582774987">"Conectando..."</string>
<string name="network_could_not_connect" msgid="6547460848093727998">"No se ha podido conectar"</string>
<string-array name="preferred_network_mode_choices">
@@ -273,31 +273,31 @@
<string name="data_enabled" msgid="22525832097434368">"Habilitar datos"</string>
<string name="data_enable_summary" msgid="696860063456536557">"Permitir uso de datos"</string>
<string name="dialog_alert_title" msgid="5260471806940268478">"Atención"</string>
- <string name="roaming" msgid="1576180772877858949">"Roaming"</string>
- <string name="roaming_enable" msgid="6853685214521494819">"Conectarse a servicios de datos en roaming"</string>
- <string name="roaming_disable" msgid="8856224638624592681">"Conectarse a servicios de datos en roaming"</string>
- <string name="roaming_reenable_message" msgid="1951802463885727915">"El roaming de datos está desactivado. Toca para activarla."</string>
- <string name="roaming_enabled_message" msgid="9022249120750897">"Pueden aplicarse cargos de roaming. Toca para modificar este ajuste."</string>
+ <string name="roaming" msgid="1576180772877858949">"Itinerancia"</string>
+ <string name="roaming_enable" msgid="6853685214521494819">"Conectarse a servicios de datos en itinerancia"</string>
+ <string name="roaming_disable" msgid="8856224638624592681">"Conectarse a servicios de datos en itinerancia"</string>
+ <string name="roaming_reenable_message" msgid="1951802463885727915">"La itinerancia de datos está desactivada. Toca para activarla."</string>
+ <string name="roaming_enabled_message" msgid="9022249120750897">"Pueden aplicarse cargos de itinerancia. Toca para modificar este ajuste."</string>
<string name="roaming_notification_title" msgid="3590348480688047320">"Se ha perdido la conexión de datos móviles"</string>
- <string name="roaming_on_notification_title" msgid="7451473196411559173">"El roaming de datos está activado"</string>
+ <string name="roaming_on_notification_title" msgid="7451473196411559173">"La itinerancia de datos está activada"</string>
<string name="roaming_warning" msgid="7855681468067171971">"El coste de este servicio puede ser significativo."</string>
<string name="roaming_check_price_warning" msgid="8212484083990570215">"Ponte en contacto con tu proveedor de red para consultar el precio."</string>
- <string name="roaming_alert_title" msgid="5689615818220960940">"¿Permitir roaming de datos?"</string>
+ <string name="roaming_alert_title" msgid="5689615818220960940">"¿Permitir itinerancia de datos?"</string>
<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="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>
+ <string name="advanced_options_title" msgid="9208195294513520934">"Avanzado"</string>
<string name="carrier_settings_euicc" msgid="1190237227261337749">"Operador"</string>
<string name="keywords_carrier_settings_euicc" msgid="8540160967922063745">"operador, esim, sim, euicc, cambiar de operador, añadir operador"</string>
<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">"Datos móviles"</string>
- <string name="mobile_data_settings_summary" msgid="5012570152029118471">"Acceder a datos con la red móvil"</string>
+ <string name="mobile_data_settings_summary" msgid="5012570152029118471">"Acceder a los datos con la red móvil"</string>
<string name="data_usage_disable_mobile" msgid="5669109209055988308">"¿Quieres desactivar los datos móviles?"</string>
<string name="sim_selection_required_pref" msgid="6985901872978341314">"Debes seleccionar una opción"</string>
<string name="sim_change_data_title" msgid="9142726786345906606">"¿Cambiar la SIM de datos?"</string>
- <string name="sim_change_data_message" msgid="3567358694255933280">"¿Usar la tarjeta <xliff:g id="NEW_SIM">%1$s</xliff:g> en lugar de <xliff:g id="OLD_SIM">%2$s</xliff:g> para los datos móviles?"</string>
+ <string name="sim_change_data_message" msgid="3567358694255933280">"¿Quieres usar la tarjeta <xliff:g id="NEW_SIM">%1$s</xliff:g> en lugar de <xliff:g id="OLD_SIM">%2$s</xliff:g> para los datos móviles?"</string>
<string name="wifi_calling_settings_title" msgid="5800018845662016507">"Llamadas por Wi-Fi"</string>
<string name="video_calling_settings_title" msgid="342829454913266078">"Videollamadas a través del operador"</string>
<string name="gsm_umts_options" msgid="4968446771519376808">"Opciones GSM/UMTS"</string>
@@ -307,7 +307,9 @@
<string name="throttle_time_frame" msgid="1813452485948918791">"Período uso datos"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Política velocidad datos"</string>
<string name="throttle_help" msgid="2624535757028809735">"Más información"</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>٪) de periodo máximo de <xliff:g id="USED_2">%3$s</xliff:g>.\nPróx periodo en <xliff:g id="USED_3">%4$d</xliff:g> días (<xliff:g id="USED_4">%5$s</xliff:g>)"</string>
+ <!-- String.format failed for translation -->
+ <!-- no translation found for throttle_status_subtext (1110276415078236687) -->
+ <skip />
<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>٪) de periodo máx de <xliff:g id="USED_2">%3$s</xliff:g>"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"Máx de <xliff:g id="USED_0">%1$s</xliff:g> superado.\nFrec datos reducida a <xliff:g id="USED_1">%2$d</xliff:g> Kb/s"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g>٪ del ciclo transcurrido.\nPróx periodo en <xliff:g id="USED_1">%2$d</xliff:g> días (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -405,7 +407,7 @@
<string name="network_4G" msgid="6800527815504223913">"4G (recomendado)"</string>
<string name="network_global" msgid="3289646154407617631">"Mundial"</string>
<string name="cdma_system_select_title" msgid="614165233552656431">"Selección del sistema"</string>
- <string name="cdma_system_select_summary" msgid="3840420390242060407">"Cambiar el modo de roaming CDMA"</string>
+ <string name="cdma_system_select_summary" msgid="3840420390242060407">"Cambiar el modo de itinerancia CDMA"</string>
<string name="cdma_system_select_dialogtitle" msgid="5524639510676501802">"Selección del sistema"</string>
<string-array name="cdma_system_select_choices">
<item msgid="462340042928284921">"Solo sistema doméstico"</item>
@@ -472,7 +474,7 @@
<string name="simContacts_empty" msgid="1135632055473689521">"No hay ningún contacto en la tarjeta SIM."</string>
<string name="simContacts_title" msgid="2714029230160136647">"Seleccionar contactos para importar"</string>
<string name="simContacts_airplaneMode" msgid="4654884030631503808">"Desactiva el modo avión para importar contactos de la tarjeta SIM."</string>
- <string name="enable_pin" msgid="967674051730845376">"Habilitar/Inhabilitar PIN de tarjeta SIM"</string>
+ <string name="enable_pin" msgid="967674051730845376">"Habilitar/inhabilitar PIN de tarjeta SIM"</string>
<string name="change_pin" msgid="3657869530942905790">"Cambiar PIN de tarjeta SIM"</string>
<string name="enter_pin_text" msgid="3182311451978663356">"PIN de la tarjeta SIM:"</string>
<string name="oldPinLabel" msgid="8618515202411987721">"PIN antiguo"</string>
@@ -506,7 +508,7 @@
<string name="pin2_attempts" msgid="5625178102026453023">\n"Quedan <xliff:g id="NUMBER">%d</xliff:g> intentos."</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>
+ <string name="doneButton" msgid="7371209609238460207">"Listo"</string>
<string name="voicemail_settings_number_label" msgid="1265118640154688162">"Número del buzón de voz"</string>
<string name="card_title_dialing" msgid="8742182654254431781">"Llamando"</string>
<string name="card_title_redialing" msgid="18130232613559964">"Marcando otra vez"</string>
@@ -547,7 +549,7 @@
<string name="emergency_information_owner_hint" msgid="6256909888049185316">"Propietario"</string>
<string name="emergency_information_confirm_hint" msgid="5109017615894918914">"Tocar de nuevo para ver la información"</string>
<string name="emergency_enable_radio_dialog_title" msgid="2667568200755388829">"Llamada de emergencia"</string>
- <string name="single_emergency_number_title" msgid="8413371079579067196">"Número de emergencia"</string>
+ <string name="single_emergency_number_title" msgid="8413371079579067196">"Llamada de emergencia"</string>
<string name="numerous_emergency_numbers_title" msgid="8972398932506755510">"Números de emergencia"</string>
<string name="emergency_call_shortcut_hint" msgid="1290485125107779500">"Tocar de nuevo para llamar al <xliff:g id="EMERGENCY_NUMBER">%s</xliff:g>"</string>
<string name="emergency_enable_radio_dialog_message" msgid="1695305158151408629">"Activando señal móvil…"</string>
@@ -567,7 +569,7 @@
<string name="onscreenShowDialpadText" msgid="658465753816164079">"Teclado"</string>
<string name="onscreenMuteText" msgid="5470306116733843621">"Silenciar"</string>
<string name="onscreenAddCallText" msgid="9075675082903611677">"Añadir llamada"</string>
- <string name="onscreenMergeCallsText" msgid="3692389519611225407">"Combinar llamadas"</string>
+ <string name="onscreenMergeCallsText" msgid="3692389519611225407">"Llamada a tres"</string>
<string name="onscreenSwapCallsText" msgid="2682542150803377991">"Cambiar"</string>
<string name="onscreenManageCallsText" msgid="1162047856081836469">"Administrar llamadas"</string>
<string name="onscreenManageConferenceText" msgid="4700574060601755137">"Gestionar llamada de conferencia"</string>
@@ -584,7 +586,7 @@
<string name="rtt_mode_title" msgid="3075948111362818043">"Llamada de texto en tiempo real (TTR)"</string>
<string name="rtt_mode_summary" msgid="8631541375609989562">"Permitir mensajes en una llamada de voz"</string>
<string name="rtt_mode_more_information" msgid="587500128658756318">"La función TTR (texto en tiempo real) ofrece asistencia a las personas sordas o con discapacidades auditivas, que tienen algún trastorno del habla o que necesitan una transcripción además de la voz para comunicarse.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>Más información</a>\n <br><br> - Las llamadas de TTR se guardan como la transcripción de un mensaje.\n <br> - La función TTR no está disponible en videollamadas."</string>
- <string name="no_rtt_when_roaming" msgid="5268008247378355389">"Nota: TTR no está disponible en roaming"</string>
+ <string name="no_rtt_when_roaming" msgid="5268008247378355389">"Nota: TTR no está disponible en itinerancia"</string>
<string-array name="tty_mode_entries">
<item msgid="3238070884803849303">"TTY desactivado"</item>
<item msgid="1449091874731375214">"TTY completo"</item>
@@ -617,7 +619,7 @@
<string name="ota_unsuccessful" msgid="8531037653803955754">"El teléfono no se ha activado. \nEs posible que tengas que situarte en una zona con más cobertura (cerca de una ventana o en el exterior). \n\nInténtalo de nuevo o ponte en contacto con el servicio de atención al cliente para consultar otras opciones."</string>
<string name="ota_spc_failure" msgid="904092035241370080">"EXCESO DE ERRORES DE SPC"</string>
<string name="ota_call_end" msgid="8657746378290737034">"Atrás"</string>
- <string name="ota_try_again" msgid="6914781945599998550">"Reintentar"</string>
+ <string name="ota_try_again" msgid="6914781945599998550">"Volver a intentarlo"</string>
<string name="ota_next" msgid="2041016619313475914">"Siguiente"</string>
<string name="ecm_exit_dialog" msgid="4200691880721429078">"EcmExitDialog"</string>
<string name="phone_entered_ecm_text" msgid="8431238297843035842">"Se ha activado el modo de devolución de llamada de emergencia."</string>
@@ -637,15 +639,9 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Sí"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"No"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Cerrar"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"El teléfono está en modo de devolución de llamada de emergencia"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Hasta las <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">El teléfono estará en modo de devolución de llamada de emergencia durante <xliff:g id="COUNT_1">%s</xliff:g> minutos.\n¿Quieres salir ahora?</item>
- <item quantity="one">El teléfono estará en modo de devolución de llamada de emergencia durante <xliff:g id="COUNT_0">%s</xliff:g> minuto.\n¿Quieres salir ahora?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Servicio"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Configuración"</string>
- <string name="voicemail_number_not_set" msgid="8831561283386938155">"<Sin establecer>"</string>
+ <string name="voicemail_number_not_set" msgid="8831561283386938155">"<No definido>"</string>
<string name="other_settings" msgid="8895088007393598447">"Otras opciones de llamada"</string>
<string name="calling_via_template" msgid="1791323450703751750">"Llamada vía <xliff:g id="PROVIDER_NAME">%s</xliff:g>"</string>
<string name="contactPhoto" msgid="7885089213135154834">"foto de contacto"</string>
@@ -672,7 +668,7 @@
<string name="status_hint_label_wifi_call" msgid="942993035689809853">"Llamada Wi-Fi"</string>
<string name="emergency_action_launch_hint" msgid="2762016865340891314">"Toca de nuevo para abrir"</string>
<string name="message_decode_error" msgid="1061856591500290887">"Se ha producido un error al decodificar el mensaje."</string>
- <string name="callFailed_cdma_activation" msgid="5392057031552253550">"Una tarjeta SIM ha activado tu servicio y actualizado la función de roaming del teléfono."</string>
+ <string name="callFailed_cdma_activation" msgid="5392057031552253550">"Una tarjeta SIM ha activado tu servicio y actualizado la función de itinerancia del teléfono."</string>
<string name="callFailed_cdma_call_limit" msgid="1074219746093031412">"Hay demasiadas llamadas activas. Finaliza o combina las llamadas que tienes antes de iniciar otra."</string>
<string name="callFailed_imei_not_accepted" msgid="7257903653685147251">"No se puede establecer la conexión. Inserta una tarjeta SIM válida."</string>
<string name="callFailed_wifi_lost" msgid="1788036730589163141">"Se ha perdido la conexión Wi-Fi. La llamada ha finalizado."</string>
@@ -692,16 +688,16 @@
<string name="change_pin_confirm_pins_dont_match" msgid="305164501222587215">"Los PIN no coinciden"</string>
<string name="change_pin_succeeded" msgid="2504705600693014403">"Se ha cambiado el PIN del buzón de voz"</string>
<string name="change_pin_system_error" msgid="7772788809875146873">"No se ha podido configurar el PIN"</string>
- <string name="mobile_data_status_roaming_turned_off_subtext" msgid="6840673347416227054">"Se ha desactivado el roaming de datos"</string>
- <string name="mobile_data_status_roaming_turned_on_subtext" msgid="5615757897768777865">"Se ha activado el roaming de datos"</string>
- <string name="mobile_data_status_roaming_without_plan_subtext" msgid="6536671968072284677">"En roaming actualmente, se necesita un plan de datos"</string>
- <string name="mobile_data_status_roaming_with_plan_subtext" msgid="2576177169108123095">"En roaming actualmente, hay un plan de datos activo"</string>
+ <string name="mobile_data_status_roaming_turned_off_subtext" msgid="6840673347416227054">"Se ha desactivado la itinerancia de datos"</string>
+ <string name="mobile_data_status_roaming_turned_on_subtext" msgid="5615757897768777865">"Se ha activado la itinerancia de datos"</string>
+ <string name="mobile_data_status_roaming_without_plan_subtext" msgid="6536671968072284677">"En itinerancia actualmente, se necesita un plan de datos"</string>
+ <string name="mobile_data_status_roaming_with_plan_subtext" msgid="2576177169108123095">"En itinerancia actualmente, hay un plan de datos activo"</string>
<string name="mobile_data_status_no_plan_subtext" msgid="170331026419263657">"No quedan datos móviles"</string>
<string name="mobile_data_activate_prepaid" msgid="4276738964416795596">"No quedan datos móviles"</string>
<string name="mobile_data_activate_prepaid_summary" msgid="6846085278531605925">"Añadir datos móviles a través de <xliff:g id="PROVIDER_NAME">%s</xliff:g>"</string>
- <string name="mobile_data_activate_roaming_plan" msgid="922290995866269366">"No hay ningún plan de roaming"</string>
- <string name="mobile_data_activate_roaming_plan_summary" msgid="5379228493306235969">"Añadir un plan de roaming a través de <xliff:g id="PROVIDER_NAME">%s</xliff:g>"</string>
- <string name="mobile_data_activate_footer" msgid="7895874069807204548">"Puedes añadir datos móviles o un plan de roaming a través de tu operador, <xliff:g id="PROVIDER_NAME">%s</xliff:g>."</string>
+ <string name="mobile_data_activate_roaming_plan" msgid="922290995866269366">"No hay ningún plan de itinerancia"</string>
+ <string name="mobile_data_activate_roaming_plan_summary" msgid="5379228493306235969">"Añadir un plan de itinerancia a través de <xliff:g id="PROVIDER_NAME">%s</xliff:g>"</string>
+ <string name="mobile_data_activate_footer" msgid="7895874069807204548">"Puedes añadir datos móviles o un plan de itinerancia a través de tu operador, <xliff:g id="PROVIDER_NAME">%s</xliff:g>."</string>
<string name="mobile_data_activate_diag_title" msgid="5401741936224757312">"¿Añadir datos?"</string>
<string name="mobile_data_activate_diag_message" msgid="3527260988020415441">"Puede que tengas que añadir datos a través de <xliff:g id="PROVIDER_NAME">%s</xliff:g>"</string>
<string name="mobile_data_activate_button" msgid="1139792516354374612">"AÑADIR DATOS"</string>
@@ -764,19 +760,19 @@
<string name="sum_call_barring_disabled" msgid="5699448000600153096">"Desactivado"</string>
<string name="call_barring_baoc" msgid="7400892586336429326">"Todas las realizadas"</string>
<string name="call_barring_baoc_enabled" msgid="3131509193386668182">"¿Quieres desbloquear todas las llamadas realizadas?"</string>
- <string name="call_barring_baoc_disabled" msgid="8534224684091141509">"¿Bloquear todas las llamadas realizadas?"</string>
+ <string name="call_barring_baoc_disabled" msgid="8534224684091141509">"¿Quieres bloquear todas las llamadas realizadas?"</string>
<string name="call_barring_baoic" msgid="8668125428666851665">"Internacionales realizadas"</string>
<string name="call_barring_baoic_enabled" msgid="1203758092657630123">"¿Quieres desbloquear las llamadas internacionales realizadas?"</string>
- <string name="call_barring_baoic_disabled" msgid="5656889339002997449">"¿Bloquear las llamadas internacionales realizadas?"</string>
- <string name="call_barring_baoicr" msgid="8566167764432343487">"Roaming de llamadas internacionales realizadas"</string>
- <string name="call_barring_baoicr_enabled" msgid="1615324165512798478">"¿Quieres desbloquear el roaming de las llamadas internacionales realizadas?"</string>
- <string name="call_barring_baoicr_disabled" msgid="172010175248142831">"¿Bloquear el roaming de las llamadas internacionales realizadas?"</string>
+ <string name="call_barring_baoic_disabled" msgid="5656889339002997449">"¿Quieres bloquear las llamadas internacionales realizadas?"</string>
+ <string name="call_barring_baoicr" msgid="8566167764432343487">"Itinerancia de llamadas internacionales realizadas"</string>
+ <string name="call_barring_baoicr_enabled" msgid="1615324165512798478">"¿Quieres desbloquear la itinerancia de las llamadas internacionales realizadas?"</string>
+ <string name="call_barring_baoicr_disabled" msgid="172010175248142831">"¿Quieres bloquear la itinerancia de las llamadas internacionales realizadas?"</string>
<string name="call_barring_baic" msgid="7941393541678658566">"Todas las entrantes"</string>
<string name="call_barring_baic_enabled" msgid="4357332358020337470">"¿Quieres desbloquear todas las llamadas entrantes?"</string>
- <string name="call_barring_baic_disabled" msgid="2355945245938240958">"¿Bloquear todas las llamadas entrantes?"</string>
- <string name="call_barring_baicr" msgid="8712249337313034226">"Roaming de llamadas internacionales entrantes"</string>
- <string name="call_barring_baicr_enabled" msgid="64774270234828175">"¿Quieres desbloquear el roaming de las llamadas internacionales entrantes?"</string>
- <string name="call_barring_baicr_disabled" msgid="3488129262744027262">"¿Bloquear el roaming de las llamadas internacionales entrantes?"</string>
+ <string name="call_barring_baic_disabled" msgid="2355945245938240958">"¿Quieres bloquear todas las llamadas entrantes?"</string>
+ <string name="call_barring_baicr" msgid="8712249337313034226">"Itinerancia de llamadas internacionales entrantes"</string>
+ <string name="call_barring_baicr_enabled" msgid="64774270234828175">"¿Quieres desbloquear la itinerancia de las llamadas internacionales entrantes?"</string>
+ <string name="call_barring_baicr_disabled" msgid="3488129262744027262">"¿Quieres bloquear la itinerancia de las llamadas internacionales entrantes?"</string>
<string name="call_barring_deactivate_all" msgid="7837931580047157328">"Desactivar todo"</string>
<string name="call_barring_deactivate_all_description" msgid="4474119585042121604">"Desactivar todos los ajustes de bloqueo de llamadas"</string>
<string name="call_barring_deactivate_success" msgid="3545644320298275337">"Se ha desactivado el bloqueo de llamadas"</string>
@@ -816,9 +812,9 @@
<string name="callFailed_calling_disabled" msgid="5010992739401206283">"No se puede llamar porque se han inhabilitado las llamadas mediante la propiedad del sistema ro.telephony.disable-call."</string>
<string name="callFailed_too_many_calls" msgid="5379426826618582180">"No se puede llamar porque ya hay dos llamadas en curso. Interrumpe una de ellas o combínalas en una conferencia para poder llamar de nuevo."</string>
<string name="supp_service_over_ut_precautions" msgid="2145018231396701311">"Para utilizar <xliff:g id="SUPP_SERVICE">%s</xliff:g>, los datos móviles deben estar activados. Puedes cambiar esta opción en los ajustes de red móvil."</string>
- <string name="supp_service_over_ut_precautions_roaming" msgid="670342104569972327">"Para utilizar <xliff:g id="SUPP_SERVICE">%s</xliff:g>, los datos móviles y el roaming de datos deben estar activados. Puedes cambiar estas opciones en los ajustes de red móvil."</string>
+ <string name="supp_service_over_ut_precautions_roaming" msgid="670342104569972327">"Para utilizar <xliff:g id="SUPP_SERVICE">%s</xliff:g>, los datos móviles y la itinerancia de datos deben estar activados. Puedes cambiar estas opciones en los ajustes de red móvil."</string>
<string name="supp_service_over_ut_precautions_dual_sim" msgid="5166866975550910474">"Para utilizar <xliff:g id="SUPP_SERVICE">%1$s</xliff:g>, los datos móviles deben estar activados en la SIM <xliff:g id="SIM_NUMBER">%2$d</xliff:g>. Puedes cambiar esta opción en los ajustes de red móvil."</string>
- <string name="supp_service_over_ut_precautions_roaming_dual_sim" msgid="6627654855191817965">"Para utilizar <xliff:g id="SUPP_SERVICE">%1$s</xliff:g>, los datos móviles y el roaming de datos deben estar activados en la SIM <xliff:g id="SIM_NUMBER">%2$d</xliff:g>. Puedes cambiar estas opciones en los ajustes de red móvil."</string>
+ <string name="supp_service_over_ut_precautions_roaming_dual_sim" msgid="6627654855191817965">"Para utilizar <xliff:g id="SUPP_SERVICE">%1$s</xliff:g>, los datos móviles y la itinerancia de datos deben estar activados en la SIM <xliff:g id="SIM_NUMBER">%2$d</xliff:g>. Puedes cambiar estas opciones en los ajustes de red móvil."</string>
<string name="supp_service_over_ut_precautions_dialog_dismiss" msgid="5934541487903081652">"Cerrar"</string>
<string name="radio_info_data_connection_enable" msgid="6183729739783252840">"Habilitar conexión de datos"</string>
<string name="radio_info_data_connection_disable" msgid="6404751291511368706">"Inhabilitar conexión de datos"</string>
@@ -847,8 +843,8 @@
<string name="radioInfo_service_out" msgid="287972405416142312">"Fuera de servicio"</string>
<string name="radioInfo_service_emergency" msgid="4763879891415016848">"Solo llamadas de emergencia"</string>
<string name="radioInfo_service_off" msgid="3456583511226783064">"Señal móvil desactivada"</string>
- <string name="radioInfo_roaming_in" msgid="3156335577793145965">"Roaming"</string>
- <string name="radioInfo_roaming_not" msgid="1904547918725478110">"Sin roaming"</string>
+ <string name="radioInfo_roaming_in" msgid="3156335577793145965">"Itinerancia"</string>
+ <string name="radioInfo_roaming_not" msgid="1904547918725478110">"Sin itinerancia"</string>
<string name="radioInfo_phone_idle" msgid="2191653783170757819">"Inactivo"</string>
<string name="radioInfo_phone_ringing" msgid="8100354169567413370">"Sonando"</string>
<string name="radioInfo_phone_offhook" msgid="7564601639749936170">"Llamada en curso"</string>
@@ -872,7 +868,7 @@
<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>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Servicio de datos:"</string>
- <string name="radio_info_roaming_label" msgid="6636932886446857120">"Roaming:"</string>
+ <string name="radio_info_roaming_label" msgid="6636932886446857120">"Itinerancia:"</string>
<string name="radio_info_imei_label" msgid="8947899706930120368">"IMEI:"</string>
<string name="radio_info_call_redirect_label" msgid="4526480903023362276">"Redirección de llamadas:"</string>
<string name="radio_info_ppp_resets_label" msgid="9131901102339077661">"Número de PPP restablecido desde el inicio:"</string>
@@ -898,11 +894,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Actualizar"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Activar o desactivar comprobación de DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Ajustes o información específica de OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC disponible:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR restringido:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR disponible:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Estado de NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Frecuencia de NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Establecer modo de banda de señal móvil"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Cargando lista de bandas…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Seleccionar"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 50e54e5..dcbbc23 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -307,7 +307,7 @@
<string name="throttle_time_frame" msgid="1813452485948918791">"Andmete kasutamise periood"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Andmeedastuskiiruse eeskirjad"</string>
<string name="throttle_help" msgid="2624535757028809735">"Lisateave"</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> perioodi maksimumist\nJärgmine periood algab <xliff:g id="USED_3">%4$d</xliff:g> päeva pärast (<xliff:g id="USED_4">%5$s</xliff:g>)"</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> maksimaalne periood\nJärgmine periood algab <xliff:g id="USED_3">%4$d</xliff:g> päeva pärast (<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> maksimaalsest perioodist"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"<xliff:g id="USED_0">%1$s</xliff:g> maksimaalne ületatud\nAndmeedastuskiirus vähendatud määrale <xliff:g id="USED_1">%2$d</xliff:g> kb/s"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g>?? tsüklist möödunud\nJärgmine periood algab <xliff:g id="USED_1">%2$d</xliff:g> päeva pärast (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Jah"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Ei"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Loobu"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Telefon on hädaolukorra tagasihelistusrežiimis"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Kuni <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">Telefon on hädaolukorra tagasihelistusrežiimis <xliff:g id="COUNT_1">%s</xliff:g> minutit.\nKas soovite kohe väljuda?</item>
- <item quantity="one">Telefon on hädaolukorra tagasihelistusrežiimis <xliff:g id="COUNT_0">%s</xliff:g> minut.\nKas soovite kohe väljuda?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Teenus"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Seadistus"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Määramata>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Värskendamine"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS-i kontrolli sisse- või väljalülitamine"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-i teave/seaded"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC saadaval:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR piiratud:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR saadaval:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR-i olek:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR-i sagedus:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Raadio ribarežiimi määramine"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Ribaloendi laadimine …"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Määra"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index a509613..1c75c30 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -38,7 +38,7 @@
<string name="audio_mode_earpiece" msgid="2823700267171134282">"Aurikularrak"</string>
<string name="audio_mode_wired_headset" msgid="5028010823105817443">"Entzungailu kableduna"</string>
<string name="audio_mode_bluetooth" msgid="25732183428018809">"Bluetooth entzungailua"</string>
- <string name="wait_prompt_str" msgid="5136209532150094910">"Tonu hauek bidali nahi dituzu?\n"</string>
+ <string name="wait_prompt_str" msgid="5136209532150094910">"Ondorengo tonuak bidali?\n"</string>
<string name="pause_prompt_str" msgid="2308897950360272213">"Tonuak bidaltzen\n"</string>
<string name="send_button" msgid="5070379600779031932">"Bidali"</string>
<string name="pause_prompt_yes" msgid="8184132073048369575">"Bai"</string>
@@ -74,7 +74,7 @@
<string name="phone_accounts_configure_account_settings" msgid="6622119715253196586">"Konfiguratu kontuaren ezarpenak"</string>
<string name="phone_accounts_all_calling_accounts" msgid="1609600743500618823">"Deiak egiteko kontu guztiak"</string>
<string name="phone_accounts_all_calling_accounts_summary" msgid="2214134955430107240">"Hautatu zein kontutatik egin daitezkeen deiak"</string>
- <string name="wifi_calling" msgid="3650509202851355742">"Wifi bidezko deiak"</string>
+ <string name="wifi_calling" msgid="3650509202851355742">"Wi-Fi bidezko deiak"</string>
<string name="connection_service_default_label" msgid="7332739049855715584">"Konektatzeko zerbitzu integratua"</string>
<string name="voicemail" msgid="7697769412804195032">"Erantzungailua"</string>
<string name="voicemail_settings_with_label" msgid="4228431668214894138">"Erantzungailua (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -95,32 +95,32 @@
<string name="sum_loading_settings" msgid="434063780286688775">"Ezarpenak kargatzen…"</string>
<string name="sum_hide_caller_id" msgid="131100328602371933">"Ezkutatu zenbakia irteerako deietan"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"Erakutsi zenbakia irteerako deietan"</string>
- <string name="sum_default_caller_id" msgid="1767070797135682959">"Erabili operadorearen ezarpen lehenetsiak, egiten ditudan deietan nire zenbakia erakusteko"</string>
+ <string name="sum_default_caller_id" msgid="1767070797135682959">"Erabili operadorearen ezarpen lehenetsiak irteerako deietan nire zenbakia erakusteko"</string>
<string name="labelCW" msgid="8449327023861428622">"Deiak zain"</string>
- <string name="sum_cw_enabled" msgid="3977308526187139996">"Deiak abian dauden bitartean, eman jasotzen ditudan deien berri"</string>
- <string name="sum_cw_disabled" msgid="3658094589461768637">"Deiak abian dauden bitartean, eman jasotzen ditudan deien berri"</string>
+ <string name="sum_cw_enabled" msgid="3977308526187139996">"Deiak abian dauden bitartean, eman sarrerako deien berri"</string>
+ <string name="sum_cw_disabled" msgid="3658094589461768637">"Deiak abian dauden bitartean, eman sarrerako deien berri"</string>
<string name="call_forwarding_settings" msgid="8937130467468257671">"Dei-desbideratzearen ezarpenak"</string>
<string name="call_forwarding_settings_with_label" msgid="2345432813399564272">"Deiak desbideratzeko ezarpenak (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="labelCF" msgid="3578719437928476078">"Dei-desbideratzea"</string>
<string name="labelCFU" msgid="8870170873036279706">"Desbideratu beti"</string>
<string name="messageCFU" msgid="1361806450979589744">"Erabili beti zenbaki hau"</string>
<string name="sum_cfu_enabled_indicator" msgid="9030139213402432776">"Dei guztiak desbideratzen"</string>
- <string name="sum_cfu_enabled" msgid="5806923046528144526">"Dei guztiak <xliff:g id="PHONENUMBER">{0}</xliff:g> zenbakira desbideratzen dira"</string>
+ <string name="sum_cfu_enabled" msgid="5806923046528144526">"Dei guztiak <xliff:g id="PHONENUMBER">{0}</xliff:g> zenbakira desbideratzen"</string>
<string name="sum_cfu_enabled_no_number" msgid="7287752761743377930">"Zenbakia ez dago eskuragarri"</string>
<string name="sum_cfu_disabled" msgid="5010617134210809853">"Desaktibatuta"</string>
<string name="labelCFB" msgid="615265213360512768">"Okupatuta nagoenean"</string>
<string name="messageCFB" msgid="1958017270393563388">"Okupatuta dagoenerako zenbakia"</string>
- <string name="sum_cfb_enabled" msgid="332037613072049492">"<xliff:g id="PHONENUMBER">{0}</xliff:g> zenbakira desbideratzen dira"</string>
+ <string name="sum_cfb_enabled" msgid="332037613072049492">"<xliff:g id="PHONENUMBER">{0}</xliff:g> zenbakira desbideratzen"</string>
<string name="sum_cfb_disabled" msgid="3589913334164866035">"Desaktibatuta"</string>
<string name="disable_cfb_forbidden" msgid="4831494744351633961">"Operadoreak ez du dei-desbideratzeak desgaitzea onartzen telefonoa okupatuta dagoen bitartean."</string>
<string name="labelCFNRy" msgid="3403533792248457946">"Erantzuten ez dudanean"</string>
<string name="messageCFNRy" msgid="7644434155765359009">"Erantzunik jasotzen ez denerako zenbakia"</string>
- <string name="sum_cfnry_enabled" msgid="3000500837493854799">"<xliff:g id="PHONENUMBER">{0}</xliff:g> zenbakira desbideratzen dira"</string>
+ <string name="sum_cfnry_enabled" msgid="3000500837493854799">"<xliff:g id="PHONENUMBER">{0}</xliff:g> zenbakira desbideratzen"</string>
<string name="sum_cfnry_disabled" msgid="1990563512406017880">"Desaktibatuta"</string>
<string name="disable_cfnry_forbidden" msgid="3174731413216550689">"Operadoreak ez du dei-desbideratzeak desgaitzea onartzen telefonoak erantzuten ez duenean."</string>
- <string name="labelCFNRc" msgid="4163399350778066013">"Telefonoa itzalita edo estalduratik at dagoenean"</string>
+ <string name="labelCFNRc" msgid="4163399350778066013">"Konektatu gabe nagoenean"</string>
<string name="messageCFNRc" msgid="6980340731313007250">"Eskuragarri ez dagoenerako zenbakia"</string>
- <string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> zenbakira desbideratzen dira"</string>
+ <string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> zenbakira desbideratzen"</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="updating_title" msgid="6130548922615719689">"Deien ezarpenak"</string>
@@ -298,7 +298,7 @@
<string name="sim_selection_required_pref" msgid="6985901872978341314">"Hautatu egin behar da"</string>
<string name="sim_change_data_title" msgid="9142726786345906606">"Datuetarako SIMa aldatu nahi duzu?"</string>
<string name="sim_change_data_message" msgid="3567358694255933280">"Datu-konexiorako, <xliff:g id="NEW_SIM">%1$s</xliff:g> txartela erabili nahi duzu <xliff:g id="OLD_SIM">%2$s</xliff:g> txartelaren ordez?"</string>
- <string name="wifi_calling_settings_title" msgid="5800018845662016507">"Wifi bidezko deiak"</string>
+ <string name="wifi_calling_settings_title" msgid="5800018845662016507">"Wi-Fi bidezko deiak"</string>
<string name="video_calling_settings_title" msgid="342829454913266078">"Operadorearen bideo-deiak"</string>
<string name="gsm_umts_options" msgid="4968446771519376808">"GSM/UMTS aukerak"</string>
<string name="cdma_options" msgid="3669592472226145665">"CDMA aukerak"</string>
@@ -307,7 +307,9 @@
<string name="throttle_time_frame" msgid="1813452485948918791">"Datuak erabiltzeko epea"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Datu-abiaduraren gidalerroak"</string>
<string name="throttle_help" msgid="2624535757028809735">"Lortu informazio gehiago"</string>
- <string name="throttle_status_subtext" msgid="1110276415078236687">"<xliff:g id="USED_0">%1$s</xliff:g>/<xliff:g id="USED_2">%3$s</xliff:g> erabilita (%% <xliff:g id="USED_1">%2$d</xliff:g>) muga-epean\nHurrengo epea <xliff:g id="USED_3">%4$d</xliff:g> egun barru hasiko da (<xliff:g id="USED_4">%5$s</xliff:g>)"</string>
+ <!-- String.format failed for translation -->
+ <!-- no translation found for throttle_status_subtext (1110276415078236687) -->
+ <skip />
<string name="throttle_data_usage_subtext" msgid="3185429653996709840">"<xliff:g id="USED_0">%1$s</xliff:g>/<xliff:g id="USED_2">%3$s</xliff:g> erabilita (٪ <xliff:g id="USED_1">%2$d</xliff:g>) muga-epean"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"<xliff:g id="USED_0">%1$s</xliff:g> muga gainditu duzu\nDatu-abiadura murriztu zaizu. <xliff:g id="USED_1">%2$d</xliff:g> Kb/s duzu orain."</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"Zikloaren ٪ <xliff:g id="USED_0">%1$d</xliff:g> igaro da\nHurrengo aldia <xliff:g id="USED_1">%2$d</xliff:g> egun barru hasten da (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -348,7 +350,7 @@
<string-array name="list_language_entries">
<item msgid="2347238508726934281">"Ingelesa"</item>
<item msgid="5172468397620875174">"Frantsesa"</item>
- <item msgid="3978110664146015398">"Gaztelania"</item>
+ <item msgid="3978110664146015398">"Espainiera"</item>
<item msgid="2637764545851526369">"Japoniera"</item>
<item msgid="6103816221834932751">"Koreera"</item>
<item msgid="3127367370005168399">"Txinera"</item>
@@ -430,7 +432,7 @@
<string name="manage_fdn_list" msgid="3341716430375195441">"Markatze finkoko zenbakien zerrenda"</string>
<string name="fdn_list_with_label" msgid="1409655283510382556">"Markatze finkoko zenbakiak (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="fdn_activation" msgid="2178637004710435895">"Markatze finkoko zenbakiaren aktibazioa"</string>
- <string name="fdn_enabled" msgid="7017355494808056447">"Markatze finkoko zenbakiak gaituta"</string>
+ <string name="fdn_enabled" msgid="7017355494808056447">"Markatze finkoko zenbakiak gaituta daude"</string>
<string name="fdn_disabled" msgid="6696468878037736600">"Markatze finkoko zenbakiak desgaituta daude"</string>
<string name="enable_fdn" msgid="4830555730418033723">"Gaitu markatze finkoko zenbakiak"</string>
<string name="disable_fdn" msgid="3918794950264647541">"Desgaitu markatze finkoko zenbakia"</string>
@@ -525,7 +527,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_ecm_emergency_only" msgid="5622379058883722080">"Larrialdikoak ez diren deiak egiteko, irten larrialdi-zerbitzuen deiak jasotzeko modutik."</string>
+ <string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Larrialdikoak ez diren deiak egiteko, irten larrialdi-deiak soilik 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>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Sare mugikorra ez dago erabilgarri. Deia egiteko, konektatu haririk gabeko sare batera."</string>
@@ -542,7 +544,7 @@
<string name="incall_error_supp_service_hangup" msgid="836524952243836735">"Ezin dira deiak bereizi."</string>
<string name="incall_error_supp_service_hold" msgid="8535056414643540997">"Ezin dira zain utzi deiak."</string>
<string name="incall_error_wfc_only_no_wireless_network" msgid="5860742792811400109">"Deia egiteko, konektatu haririk gabeko sare batera."</string>
- <string name="incall_error_promote_wfc" msgid="9164896813931363415">"Deia egiteko, gaitu wifi bidezko deiak."</string>
+ <string name="incall_error_promote_wfc" msgid="9164896813931363415">"Deia egiteko, gaitu Wi-Fi bidezko deiak."</string>
<string name="emergency_information_hint" msgid="9208897544917793012">"Larrialdietarako informazioa"</string>
<string name="emergency_information_owner_hint" msgid="6256909888049185316">"Jabea"</string>
<string name="emergency_information_confirm_hint" msgid="5109017615894918914">"Informazioa ikusteko, sakatu berriro"</string>
@@ -566,7 +568,7 @@
<string name="onscreenEndCallText" msgid="6138725377654842757">"Amaitu"</string>
<string name="onscreenShowDialpadText" msgid="658465753816164079">"Markagailua"</string>
<string name="onscreenMuteText" msgid="5470306116733843621">"Desaktibatu audioa"</string>
- <string name="onscreenAddCallText" msgid="9075675082903611677">"Gehitu dei bat"</string>
+ <string name="onscreenAddCallText" msgid="9075675082903611677">"Gehitu deia"</string>
<string name="onscreenMergeCallsText" msgid="3692389519611225407">"Bateratu deiak"</string>
<string name="onscreenSwapCallsText" msgid="2682542150803377991">"Trukatu"</string>
<string name="onscreenManageCallsText" msgid="1162047856081836469">"Kudeatu deiak"</string>
@@ -614,35 +616,29 @@
<string name="ota_progress" msgid="8837259285255700132">"Telefonoa programatzen…"</string>
<string name="ota_failure" msgid="5674217489921481576">"Ezin izan da programatu telefonoa"</string>
<string name="ota_successful" msgid="1106825981548107774">"Telefonoa aktibatu egin da. Zerbitzua hasteko 15 minutu ere behar izan daitezke."</string>
- <string name="ota_unsuccessful" msgid="8531037653803955754">"Ez duzu telefonoa aktibatu.\nBaliteke estaldura gehiago duen eremu bat aurkitu behar izatea (leiho baten ondoan edo kanpoan).\n\nSaiatu berriro edo, aukera gehiago ezagutzeko, deitu bezeroarentzako laguntza-zerbitzura."</string>
+ <string name="ota_unsuccessful" msgid="8531037653803955754">"Ez duzu telefonoa aktibatu.\nBaliteke estaldura gehiago duen eremu bat aurkitu behar izatea (leiho baten ondoan edo kanpoan).\n\nSaiatu berriro edo, aukera gehiago ezagutzeko, deitu bezeroarentzako zerbitzura."</string>
<string name="ota_spc_failure" msgid="904092035241370080">"SPC HUTSEGITE GEHIEGI"</string>
<string name="ota_call_end" msgid="8657746378290737034">"Atzera"</string>
<string name="ota_try_again" msgid="6914781945599998550">"Saiatu berriro"</string>
<string name="ota_next" msgid="2041016619313475914">"Hurrengoa"</string>
<string name="ecm_exit_dialog" msgid="4200691880721429078">"EcmExitDialog"</string>
- <string name="phone_entered_ecm_text" msgid="8431238297843035842">"Larrialdi-zerbitzuen deiak jasotzeko modua aktibatu da"</string>
- <string name="phone_in_ecm_notification_title" msgid="6825016389926367946">"Larrialdi-zerbitzuen deiak jasotzeko modua"</string>
+ <string name="phone_entered_ecm_text" msgid="8431238297843035842">"Larrialdi-deiak soilik jasotzeko modua aktibatu da"</string>
+ <string name="phone_in_ecm_notification_title" msgid="6825016389926367946">"Larrialdi-deiak soilik jasotzeko modua"</string>
<string name="phone_in_ecm_call_notification_text" msgid="653972232922670335">"Datu-konexioa desgaituta"</string>
<string name="phone_in_ecm_notification_complete_time" msgid="7341624337163082759">"Ez duzu izango datu-konexiorik ordu honetara arte: <xliff:g id="COMPLETETIME">%s</xliff:g>"</string>
<plurals name="alert_dialog_exit_ecm" formatted="false" msgid="5425906903766466743">
- <item quantity="other">Telefonoa larrialdi-zerbitzuen deiak jasotzeko moduan egongo da <xliff:g id="COUNT_1">%s</xliff:g> minutuz. Modu horretan dagoen bitartean, ezingo duzu datu-konexioa behar duen aplikaziorik erabili. Irten nahi duzu?</item>
- <item quantity="one">Telefonoa larrialdi-zerbitzuen deiak jasotzeko moduan egongo da <xliff:g id="COUNT_0">%s</xliff:g> minutuz. Modu horretan dagoen bitartean, ezingo duzu datu-konexioa behar duen aplikaziorik erabili. Irten nahi duzu?</item>
+ <item quantity="other">Telefonoa larrialdi-deiak soilik jasotzeko moduan egongo da <xliff:g id="COUNT_1">%s</xliff:g> minutuz. Modu horretan dagoen bitartean, ezingo duzu datu-konexioa behar duen aplikaziorik erabili. Irten nahi duzu?</item>
+ <item quantity="one">Telefonoa larrialdi-deiak soilik jasotzeko moduan egongo da <xliff:g id="COUNT_0">%s</xliff:g> minutuz. Modu horretan dagoen bitartean, ezingo duzu datu-konexioa behar duen aplikaziorik erabili. Irten nahi duzu?</item>
</plurals>
<plurals name="alert_dialog_not_avaialble_in_ecm" formatted="false" msgid="1152682528741457004">
- <item quantity="other">Hautatutako ekintza ez dago larrialdi-zerbitzuen deiak jasotzeko moduan erabilgarri. Telefonoa <xliff:g id="COUNT_1">%s</xliff:g> minutuz egongo da modu horretan. Irten nahi duzu?</item>
- <item quantity="one">Hautatutako ekintza ez dago larrialdi-zerbitzuen deiak jasotzeko moduan erabilgarri. Telefonoa <xliff:g id="COUNT_0">%s</xliff:g> minutuz egongo da modu horretan. Irten nahi duzu?</item>
+ <item quantity="other">Hautatutako ekintza ez dago larrialdi-deiak soilik jasotzeko moduan erabilgarri. Telefonoa <xliff:g id="COUNT_1">%s</xliff:g> minutuz egongo da modu horretan. Irten nahi duzu?</item>
+ <item quantity="one">Hautatutako ekintza ez dago larrialdi-deiak soilik jasotzeko moduan erabilgarri. Telefonoa <xliff:g id="COUNT_0">%s</xliff:g> minutuz egongo da modu horretan. Irten nahi duzu?</item>
</plurals>
<string name="alert_dialog_in_ecm_call" msgid="1207545603149771978">"Hautatutako ekintza ez dago erabilgarri larrialdi-deia egiten ari den bitartean."</string>
- <string name="progress_dialog_exiting_ecm" msgid="9159080081676927217">"Larrialdi-zerbitzuen deiak jasotzeko modutik irteten"</string>
+ <string name="progress_dialog_exiting_ecm" msgid="9159080081676927217">"Larrialdi-deiak soilik jasotzeko modutik irteten"</string>
<string name="alert_dialog_yes" msgid="3532525979632841417">"Bai"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Ez"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Baztertu"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Larrialdi-zerbitzuen deiak jasotzeko moduan dago telefonoa"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<xliff:g id="COMPLETETIME">%s</xliff:g> arte"</string>
- <plurals name="alert_dialog_exit_ecm_without_data_restriction_hint" formatted="false" msgid="6477733043040328640">
- <item quantity="other">Telefonoa <xliff:g id="COUNT_1">%s</xliff:g> minutuz egongo da larrialdi-zerbitzuen deiak jasotzeko moduan.\nIrten nahi duzu?</item>
- <item quantity="one">Telefonoa <xliff:g id="COUNT_0">%s</xliff:g> minutuz egongo da larrialdi-zerbitzuen deiak jasotzeko moduan.\nIrten nahi duzu?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Zerbitzua"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Konfigurazioa"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Ezarri gabe>"</string>
@@ -675,7 +671,7 @@
<string name="callFailed_cdma_activation" msgid="5392057031552253550">"SIM txartel batek zerbitzua aktibatu du eta telefonoaren ibiltaritza-gaitasunak eguneratu ditu."</string>
<string name="callFailed_cdma_call_limit" msgid="1074219746093031412">"Dei gehiegi daude aktibo. Amaitu edo bateratu abian diren deiak beste bat egin aurretik."</string>
<string name="callFailed_imei_not_accepted" msgid="7257903653685147251">"Ezin da konektatu. Erabili balio duen SIM txartel bat."</string>
- <string name="callFailed_wifi_lost" msgid="1788036730589163141">"Galdu egin da wifi-konexioa. Amaitu da deia."</string>
+ <string name="callFailed_wifi_lost" msgid="1788036730589163141">"Galdu egin da wifi bidezko konexioa. Amaitu da deia."</string>
<string name="dialFailed_low_battery" msgid="6857904237423407056">"Ezin da egin deia, bateria gutxi gelditzen delako."</string>
<string name="callFailed_low_battery" msgid="4056828320214416182">"Bideo-deia amaitu egin da bateria gutxi gelditzen delako."</string>
<string name="callFailed_emergency_call_over_wfc_not_available" msgid="5944309590693432042">"Ez daude erabilgarri wifi bidezko larrialdi-deiak kokapen honetan."</string>
@@ -689,7 +685,7 @@
<string name="change_pin_enter_new_pin_header" msgid="4739465616733486118">"Ezarri PIN kode berria"</string>
<string name="change_pin_enter_new_pin_hint" msgid="2326038476516364210">"<xliff:g id="MIN">%1$d</xliff:g> eta <xliff:g id="MAX">%2$d</xliff:g> digituren artean izan behar ditu PIN kodeak."</string>
<string name="change_pin_confirm_pin_header" msgid="2606303906320705726">"Berretsi PIN kodea"</string>
- <string name="change_pin_confirm_pins_dont_match" msgid="305164501222587215">"PINak ez datoz bat"</string>
+ <string name="change_pin_confirm_pins_dont_match" msgid="305164501222587215">"Ez datoz bat PIN kodeak"</string>
<string name="change_pin_succeeded" msgid="2504705600693014403">"Eguneratu da erantzungailuaren PIN kodea"</string>
<string name="change_pin_system_error" msgid="7772788809875146873">"Ezin da ezarri PIN kodea"</string>
<string name="mobile_data_status_roaming_turned_off_subtext" msgid="6840673347416227054">"Desaktibatuta daude ibiltaritzako datuak"</string>
@@ -863,7 +859,7 @@
<string name="radioInfo_display_asu" msgid="2247752203249646956">"asu"</string>
<string name="radioInfo_lac" msgid="3892986460272607013">"LAC"</string>
<string name="radioInfo_cid" msgid="1423185536264406705">"CID"</string>
- <string name="radio_info_subid" msgid="6839966868621703203">"Oraingo azpiIDa:"</string>
+ <string name="radio_info_subid" msgid="6839966868621703203">"Uneko azpiIDa:"</string>
<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>
@@ -876,7 +872,7 @@
<string name="radio_info_imei_label" msgid="8947899706930120368">"IMEI:"</string>
<string name="radio_info_call_redirect_label" msgid="4526480903023362276">"Dei-desbideratzea:"</string>
<string name="radio_info_ppp_resets_label" msgid="9131901102339077661">"PPP berrezarpen kopurua abiarazi ezkero:"</string>
- <string name="radio_info_current_network_label" msgid="3052098695239642450">"Oraingo sarea:"</string>
+ <string name="radio_info_current_network_label" msgid="3052098695239642450">"Uneko sarea:"</string>
<string name="radio_info_ppp_received_label" msgid="5753592451640644889">"Jasotako datuak:"</string>
<string name="radio_info_gsm_service_label" msgid="6443348321714241328">"Ahots-deien zerbitzua:"</string>
<string name="radio_info_signal_strength_label" msgid="5545444702102543260">"Seinalearen indarra:"</string>
@@ -898,11 +894,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Freskatu"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Aldatu DNS egiaztapenaren egoera"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Jatorrizko fabrikatzailearen berariazko informazioa edota ezarpenak"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC erabilgarri:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR mugatuta:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR erabilgarri:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR estatua:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR maiztasuna:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Ezarri irrati-bandaren modua"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Banden zerrenda kargatzen…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Ezarri"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 56059c8..5e029f0 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -135,7 +135,7 @@
<string name="stk_cc_ss_to_dial_error" msgid="5147693491690618704">"درخواست SS به تماس معمولی تغییر کرد"</string>
<string name="stk_cc_ss_to_ussd_error" msgid="8330749347425752192">"درخواست SS بهدرخواست USSD تغییر کرد"</string>
<string name="stk_cc_ss_to_ss_error" msgid="8297155544652134278">"بهدرخواست SS جدید تغییر کرد"</string>
- <string name="stk_cc_ss_to_dial_video_error" msgid="4255261231466032505">"درخواست SS به تماس تصویری تغییر کرد"</string>
+ <string name="stk_cc_ss_to_dial_video_error" msgid="4255261231466032505">"درخواست SS به تماس ویدیویی تغییر کرد"</string>
<string name="fdn_check_failure" msgid="1833769746374185247">"تنظیم اعداد شمارهگیری ثابت برنامههای تلفن شما غیرفعال است. درنتیجه، برخی از ویژگیهای مربوط به تماس کار نمیکند."</string>
<string name="radio_off_error" msgid="8321564164914232181">"قبل از مشاهدهٔ این تنظیمات، رادیو را روشن کنید."</string>
<string name="close_dialog" msgid="1074977476136119408">"تأیید"</string>
@@ -178,9 +178,9 @@
<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>
- <string name="preferred_network_mode_title" msgid="5253395265169539830">"نوع شبکه ترجیحی"</string>
+ <string name="preferred_network_mode_title" msgid="5253395265169539830">"نوع شبکه برگزیده"</string>
<string name="preferred_network_mode_summary" msgid="3787989000044330064">"تغییر حالت عملکرد شبکه"</string>
- <string name="preferred_network_mode_dialogtitle" msgid="2781447433514459696">"نوع شبکه ترجیحی"</string>
+ <string name="preferred_network_mode_dialogtitle" msgid="2781447433514459696">"نوع شبکه برگزیده"</string>
<string name="forbidden_network" msgid="5081729819561333023">"(ممنوع است)"</string>
<string name="choose_network_title" msgid="5335832663422653082">"انتخاب شبکه"</string>
<string name="network_disconnected" msgid="8844141106841160825">"قطع اتصال"</string>
@@ -223,44 +223,44 @@
<item msgid="3869566732842046032">"NR/LTE/TDSCDMA/GSM/WCDMA"</item>
<item msgid="3942770927563146543">"NR/LTE/TDSCDMA/CDMA/EvDo/GSM/WCDMA"</item>
</string-array>
- <string name="preferred_network_mode_wcdma_perf_summary" msgid="7851493369130750126">"حالت شبکه ترجیحی: WCDMA برگزیده"</string>
- <string name="preferred_network_mode_gsm_only_summary" msgid="4323367929994392830">"حالت شبکه ترجیحی: فقط GSM"</string>
- <string name="preferred_network_mode_wcdma_only_summary" msgid="3585482191951442207">"حالت شبکه ترجیحی: فقط WCDMA"</string>
- <string name="preferred_network_mode_gsm_wcdma_summary" msgid="2988950751948316810">"حالت شبکه ترجیحی: GSM / WCDMA"</string>
+ <string name="preferred_network_mode_wcdma_perf_summary" msgid="7851493369130750126">"حالت شبکه برگزیده: WCDMA برگزیده"</string>
+ <string name="preferred_network_mode_gsm_only_summary" msgid="4323367929994392830">"حالت شبکه برگزیده: فقط GSM"</string>
+ <string name="preferred_network_mode_wcdma_only_summary" msgid="3585482191951442207">"حالت شبکه برگزیده: فقط WCDMA"</string>
+ <string name="preferred_network_mode_gsm_wcdma_summary" msgid="2988950751948316810">"حالت شبکه برگزیده: GSM / WCDMA"</string>
<string name="preferred_network_mode_cdma_summary" msgid="9127141320343936911">"حالت شبکه ترجیحی: CDMA"</string>
<string name="preferred_network_mode_cdma_evdo_summary" msgid="3629440709757307077">"حالت شبکه ترجیحی: CDMA / EvDo"</string>
- <string name="preferred_network_mode_cdma_only_summary" msgid="211164451887102568">"حالت شبکه ترجیحی: فقط CDMA"</string>
- <string name="preferred_network_mode_evdo_only_summary" msgid="939116631952132878">"حالت شبکه ترجیحی: فقط EvDo"</string>
- <string name="preferred_network_mode_cdma_evdo_gsm_wcdma_summary" msgid="7891131456022601976">"حالت شبکه ترجیحی: CDMA/EvDo/GSM/WCDMA"</string>
- <string name="preferred_network_mode_lte_summary" msgid="8050539466545797149">"حالت شبکه ترجیحی: LTE"</string>
- <string name="preferred_network_mode_lte_gsm_wcdma_summary" msgid="2217794334331254936">"حالت شبکه ترجیحی: GSM/WCDMA/LTE"</string>
- <string name="preferred_network_mode_lte_cdma_evdo_summary" msgid="5559198623419981805">"حالت شبکه ترجیحی: CDMA+LTE/EVDO"</string>
- <string name="preferred_network_mode_lte_cdma_evdo_gsm_wcdma_summary" msgid="6707224437925495615">"حالت شبکه ترجیحی: LTE/CDMA/EvDo/GSM/WCDMA"</string>
- <string name="preferred_network_mode_global_summary" msgid="3847086258439582411">"حالت شبکه ترجیحی: سراسری"</string>
- <string name="preferred_network_mode_lte_wcdma_summary" msgid="7001804022020813865">"حالت شبکه ترجیحی: LTE / WCDMA"</string>
- <string name="preferred_network_mode_lte_gsm_umts_summary" msgid="6484203890156282179">"حالت شبکه ترجیحی: LTE / GSM / UMTS"</string>
- <string name="preferred_network_mode_lte_cdma_summary" msgid="8187929456614068518">"حالت شبکه ترجیحی: LTE / CDMA"</string>
- <string name="preferred_network_mode_tdscdma_summary" msgid="3602127224234207206">"حالت شبکه ترجیحی: TDSCDMA"</string>
- <string name="preferred_network_mode_tdscdma_wcdma_summary" msgid="7076968749402201123">"حالت شبکه ترجیحی: TDSCDMA / WCDMA"</string>
- <string name="preferred_network_mode_lte_tdscdma_summary" msgid="3001058390866953624">"حالت شبکه ترجیحی: LTE / TDSCDMA"</string>
- <string name="preferred_network_mode_tdscdma_gsm_summary" msgid="1716983444872465309">"حالت شبکه ترجیحی: TDSCDMA / GSM"</string>
- <string name="preferred_network_mode_lte_tdscdma_gsm_summary" msgid="1349057007230669585">"حالت شبکه ترجیحی: LTE/GSM/TDSCDMA"</string>
- <string name="preferred_network_mode_tdscdma_gsm_wcdma_summary" msgid="2092262901885164194">"حالت شبکه ترجیحی: TDSCDMA/GSM/WCDMA"</string>
- <string name="preferred_network_mode_lte_tdscdma_wcdma_summary" msgid="56422129430744466">"حالت شبکه ترجیحی: LTE/TDSCDMA/WCDMA"</string>
- <string name="preferred_network_mode_lte_tdscdma_gsm_wcdma_summary" msgid="2993923113350341106">"حالت شبکه ترجیحی: LTE/TDSCDMA/GSM/WCDMA"</string>
- <string name="preferred_network_mode_tdscdma_cdma_evdo_gsm_wcdma_summary" msgid="2779089629254220257">"حالت شبکه ترجیحی: TDSCDMA/CDMA/EvDo/GSM/WCDMA"</string>
- <string name="preferred_network_mode_lte_tdscdma_cdma_evdo_gsm_wcdma_summary" msgid="9065672185435798587">"حالت شبکه ترجیحی: LTE/TDSCDMA/CDMA/EvDo/GSM/WCDMA"</string>
- <string name="preferred_network_mode_nr_only_summary" msgid="1467452233297987391">"حالت شبکه ترجیحی: فقط NR"</string>
- <string name="preferred_network_mode_nr_lte_summary" msgid="5890170406507535976">"حالت شبکه ترجیحی: NR / LTE"</string>
- <string name="preferred_network_mode_nr_lte_cdma_evdo_summary" msgid="5507940227264296616">"حالت شبکه ترجیحی: NR/LTE/CDMA/EvDo"</string>
- <string name="preferred_network_mode_nr_lte_gsm_wcdma_summary" msgid="2811179121638665248">"حالت شبکه ترجیحی: NR/LTE/GSM/WCDMA"</string>
- <string name="preferred_network_mode_nr_lte_cdma_evdo_gsm_wcdma_summary" msgid="7631365223836621902">"حالت شبکه ترجیحی: NR/LTE/CDMA/EvDo/GSM/WCDMA"</string>
- <string name="preferred_network_mode_nr_lte_wcdma_summary" msgid="8696016062943591864">"حالت شبکه ترجیحی: NR/LTE/WCDMA"</string>
- <string name="preferred_network_mode_nr_lte_tdscdma_summary" msgid="1236182344680726751">"حالت شبکه ترجیحی: NR/LTE/TDSCDMA"</string>
- <string name="preferred_network_mode_nr_lte_tdscdma_gsm_summary" msgid="8384454155773415993">"حالت شبکه ترجیحی: NR/LTE/TDSCDMA/GSM"</string>
- <string name="preferred_network_mode_nr_lte_tdscdma_wcdma_summary" msgid="5912457779733343522">"حالت شبکه ترجیحی: NR/LTE/TDSCDMA/WCDMA"</string>
- <string name="preferred_network_mode_nr_lte_tdscdma_gsm_wcdma_summary" msgid="6769797110309412576">"حالت شبکه ترجیحی: NR/LTE/TDSCDMA/GSM/WCDMA"</string>
- <string name="preferred_network_mode_nr_lte_tdscdma_cdma_evdo_gsm_wcdma_summary" msgid="4260661428277578573">"حالت شبکه ترجیحی: NR/LTE/TDSCDMA/CDMA/EvDo/GSM/WCDMA"</string>
+ <string name="preferred_network_mode_cdma_only_summary" msgid="211164451887102568">"حالت شبکه برگزیده: فقط CDMA"</string>
+ <string name="preferred_network_mode_evdo_only_summary" msgid="939116631952132878">"حالت شبکه برگزیده: فقط EvDo"</string>
+ <string name="preferred_network_mode_cdma_evdo_gsm_wcdma_summary" msgid="7891131456022601976">"حالت شبکه برگزیده: CDMA/EvDo/GSM/WCDMA"</string>
+ <string name="preferred_network_mode_lte_summary" msgid="8050539466545797149">"حالت شبکه برگزیده: LTE"</string>
+ <string name="preferred_network_mode_lte_gsm_wcdma_summary" msgid="2217794334331254936">"حالت شبکه برگزیده: GSM/WCDMA/LTE"</string>
+ <string name="preferred_network_mode_lte_cdma_evdo_summary" msgid="5559198623419981805">"حالت شبکه برگزیده: CDMA+LTE/EVDO"</string>
+ <string name="preferred_network_mode_lte_cdma_evdo_gsm_wcdma_summary" msgid="6707224437925495615">"حالت شبکه برگزیده: LTE/CDMA/EvDo/GSM/WCDMA"</string>
+ <string name="preferred_network_mode_global_summary" msgid="3847086258439582411">"حالت شبکه برگزیده: سراسری"</string>
+ <string name="preferred_network_mode_lte_wcdma_summary" msgid="7001804022020813865">"حالت شبکه برگزیده: LTE / WCDMA"</string>
+ <string name="preferred_network_mode_lte_gsm_umts_summary" msgid="6484203890156282179">"حالت شبکه برگزیده: LTE / GSM / UMTS"</string>
+ <string name="preferred_network_mode_lte_cdma_summary" msgid="8187929456614068518">"حالت شبکه برگزیده: LTE / CDMA"</string>
+ <string name="preferred_network_mode_tdscdma_summary" msgid="3602127224234207206">"حالت شبکه برگزیده: TDSCDMA"</string>
+ <string name="preferred_network_mode_tdscdma_wcdma_summary" msgid="7076968749402201123">"حالت شبکه برگزیده: TDSCDMA / WCDMA"</string>
+ <string name="preferred_network_mode_lte_tdscdma_summary" msgid="3001058390866953624">"حالت شبکه برگزیده: LTE / TDSCDMA"</string>
+ <string name="preferred_network_mode_tdscdma_gsm_summary" msgid="1716983444872465309">"حالت شبکه برگزیده: TDSCDMA / GSM"</string>
+ <string name="preferred_network_mode_lte_tdscdma_gsm_summary" msgid="1349057007230669585">"حالت شبکه برگزیده: LTE/GSM/TDSCDMA"</string>
+ <string name="preferred_network_mode_tdscdma_gsm_wcdma_summary" msgid="2092262901885164194">"حالت شبکه برگزیده: TDSCDMA/GSM/WCDMA"</string>
+ <string name="preferred_network_mode_lte_tdscdma_wcdma_summary" msgid="56422129430744466">"حالت شبکه برگزیده: LTE/TDSCDMA/WCDMA"</string>
+ <string name="preferred_network_mode_lte_tdscdma_gsm_wcdma_summary" msgid="2993923113350341106">"حالت شبکه برگزیده: LTE/TDSCDMA/GSM/WCDMA"</string>
+ <string name="preferred_network_mode_tdscdma_cdma_evdo_gsm_wcdma_summary" msgid="2779089629254220257">"حالت شبکه برگزیده: TDSCDMA/CDMA/EvDo/GSM/WCDMA"</string>
+ <string name="preferred_network_mode_lte_tdscdma_cdma_evdo_gsm_wcdma_summary" msgid="9065672185435798587">"حالت شبکه برگزیده: LTE/TDSCDMA/CDMA/EvDo/GSM/WCDMA"</string>
+ <string name="preferred_network_mode_nr_only_summary" msgid="1467452233297987391">"حالت شبکه برگزیده: فقط NR"</string>
+ <string name="preferred_network_mode_nr_lte_summary" msgid="5890170406507535976">"حالت شبکه برگزیده: NR / LTE"</string>
+ <string name="preferred_network_mode_nr_lte_cdma_evdo_summary" msgid="5507940227264296616">"حالت شبکه برگزیده: NR/LTE/CDMA/EvDo"</string>
+ <string name="preferred_network_mode_nr_lte_gsm_wcdma_summary" msgid="2811179121638665248">"حالت شبکه برگزیده: NR/LTE/GSM/WCDMA"</string>
+ <string name="preferred_network_mode_nr_lte_cdma_evdo_gsm_wcdma_summary" msgid="7631365223836621902">"حالت شبکه برگزیده: NR/LTE/CDMA/EvDo/GSM/WCDMA"</string>
+ <string name="preferred_network_mode_nr_lte_wcdma_summary" msgid="8696016062943591864">"حالت شبکه برگزیده: NR/LTE/WCDMA"</string>
+ <string name="preferred_network_mode_nr_lte_tdscdma_summary" msgid="1236182344680726751">"حالت شبکه برگزیده: NR/LTE/TDSCDMA"</string>
+ <string name="preferred_network_mode_nr_lte_tdscdma_gsm_summary" msgid="8384454155773415993">"حالت شبکه برگزیده: NR/LTE/TDSCDMA/GSM"</string>
+ <string name="preferred_network_mode_nr_lte_tdscdma_wcdma_summary" msgid="5912457779733343522">"حالت شبکه برگزیده: NR/LTE/TDSCDMA/WCDMA"</string>
+ <string name="preferred_network_mode_nr_lte_tdscdma_gsm_wcdma_summary" msgid="6769797110309412576">"حالت شبکه برگزیده: NR/LTE/TDSCDMA/GSM/WCDMA"</string>
+ <string name="preferred_network_mode_nr_lte_tdscdma_cdma_evdo_gsm_wcdma_summary" msgid="4260661428277578573">"حالت شبکه برگزیده: NR/LTE/TDSCDMA/CDMA/EvDo/GSM/WCDMA"</string>
<string name="call_category" msgid="4394703838833058138">"درحال تماس"</string>
<string name="network_operator_category" msgid="4992217193732304680">"شبکه"</string>
<string name="enhanced_4g_lte_mode_title" msgid="4213420368777080540">"حالت پیشرفته 4G LTE"</string>
@@ -299,7 +299,7 @@
<string name="sim_change_data_title" msgid="9142726786345906606">"سیمکارت داده تغییر کند؟"</string>
<string name="sim_change_data_message" msgid="3567358694255933280">"برای داده تلفن همراه، از <xliff:g id="NEW_SIM">%1$s</xliff:g> بهجای <xliff:g id="OLD_SIM">%2$s</xliff:g> استفاده شود؟"</string>
<string name="wifi_calling_settings_title" msgid="5800018845662016507">"تماس ازطریق Wi-Fi"</string>
- <string name="video_calling_settings_title" msgid="342829454913266078">"تماس تصویری با شرکت مخابراتی"</string>
+ <string name="video_calling_settings_title" msgid="342829454913266078">"تماس ویدیویی با شرکت مخابراتی"</string>
<string name="gsm_umts_options" msgid="4968446771519376808">"گزینههای GSM/UMTS"</string>
<string name="cdma_options" msgid="3669592472226145665">"گزینههای CDMA"</string>
<string name="throttle_data_usage" msgid="1944145350660420711">"مصرف داده"</string>
@@ -440,13 +440,13 @@
<string name="sum_fdn" msgid="6152246141642323582">"مدیریت شمارههای شمارهگیری ثابت"</string>
<string name="sum_fdn_change_pin" msgid="3510994280557335727">"تغییر پین برای دسترسی FDN"</string>
<string name="sum_fdn_manage_list" msgid="3311397063233992907">"مدیریت فهرست شماره تلفن"</string>
- <string name="voice_privacy" msgid="7346935172372181951">"حریمخصوصی صوتی"</string>
+ <string name="voice_privacy" msgid="7346935172372181951">"حریم خصوصی صوتی"</string>
<string name="voice_privacy_summary" msgid="3556460926168473346">"فعال کردن حالت رازداری پیشرفته"</string>
<string name="tty_mode_option_title" msgid="3843817710032641703">"حالت TTY"</string>
<string name="tty_mode_option_summary" msgid="4770510287236494371">"تنظیم حالت TTY"</string>
<string name="auto_retry_mode_title" msgid="2985801935424422340">"سعی مجدد خودکار"</string>
<string name="auto_retry_mode_summary" msgid="2863919925349511402">"فعال کردن حالت سعی مجدد خودکار"</string>
- <string name="tty_mode_not_allowed_video_call" msgid="6551976083652752815">"تغییر حالت TTY در طول تماس تصویری مجاز نیست"</string>
+ <string name="tty_mode_not_allowed_video_call" msgid="6551976083652752815">"تغییر حالت TTY در طول تماس ویدئویی مجاز نیست"</string>
<string name="menu_add" msgid="5616487894975773141">"افزودن مخاطب"</string>
<string name="menu_edit" msgid="3593856941552460706">"ویرایش مخاطب"</string>
<string name="menu_delete" msgid="6326861853830546488">"حذف مخاطب"</string>
@@ -572,7 +572,7 @@
<string name="onscreenManageCallsText" msgid="1162047856081836469">"مدیریت تماسها"</string>
<string name="onscreenManageConferenceText" msgid="4700574060601755137">"مدیریت کنفرانس"</string>
<string name="onscreenAudioText" msgid="7224226735052019986">"صوتی"</string>
- <string name="onscreenVideoCallText" msgid="1743992456126258698">"تماس تصویری"</string>
+ <string name="onscreenVideoCallText" msgid="1743992456126258698">"تماس ویدئویی"</string>
<string name="importSimEntry" msgid="3892354284082689894">"وارد کردن"</string>
<string name="importAllSimEntries" msgid="2628391505643564007">"وارد کردن همه"</string>
<string name="importingSimContacts" msgid="4995457122107888932">"وارد کردن مخاطبین سیم"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"بله"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"نه"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"رد کردن"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"تلفن در حالت «تماس اضطراری» است"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"تا<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">تلفن به مدت <xliff:g id="COUNT_1">%s</xliff:g> دقیقه در حالت «تماس اضطراری» خواهد بود.\nمیخواهید اکنون خارج شوید؟</item>
- <item quantity="other">تلفن به مدت <xliff:g id="COUNT_1">%s</xliff:g> دقیقه در حالت «تماس اضطراری» خواهد بود.\nمیخواهید اکنون خارج شوید؟</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"سرویس"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"تنظیم"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<تنظیم نشده>"</string>
@@ -660,8 +654,8 @@
<string name="voicemail_change_pin_dialog_title" msgid="4633077715231764435">"تغییر پین"</string>
<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_title" msgid="7246600931634161830">"روشن کردن تماس ویدئویی"</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>
@@ -677,7 +671,7 @@
<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="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">"تغییر پین پست صوتی"</string>
@@ -842,7 +836,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>\nصدا ازطریق LTE: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nصدا ازطریق WiFi: <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nتماس تصویری: <xliff:g id="AVAILABILITY_2">%4$s</xliff:g>\nواسط UT: <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>\nصدا ازطریق LTE: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nصدا ازطریق WiFi: <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nتماس ویدیویی: <xliff:g id="AVAILABILITY_2">%4$s</xliff:g>\nواسط UT: <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>
@@ -888,7 +882,7 @@
<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>
+ <string name="radio_info_set_perferred_label" msgid="7408131389363136210">"تنظیم نوع شبکه برگزیده:"</string>
<string name="radio_info_ping_hostname_v4" msgid="6951237885381284790">"پینگ کردن نام میزبان (www.google.com) IPv4:"</string>
<string name="radio_info_ping_hostname_v6" msgid="2748637889486554603">"پینگ کردن نام میزبان (www.google.com) IPv6:"</string>
<string name="radio_info_http_client_test" msgid="1329583721088428238">"آزمایش کارخواه HTTP:"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"بازخوانی"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"تغییر وضعیت علامت DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"تنظیمات/اطلاعات خاص OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC دردسترس است:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR محدود شده است:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR دردسترس است:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"وضعیت NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"فرکانس NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"تنظیم حالت باند رادیو"</string>
<string name="band_mode_loading" msgid="795923726636735967">"درحال بار کردن فهرست باند…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"تنظیم"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index dd192ec..e9ac919 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -30,7 +30,7 @@
<string name="mmiStarted" msgid="9212975136944568623">"MMI-koodi aloitettu"</string>
<string name="ussdRunning" msgid="1163586813106772717">"USSD-koodi käytössä..."</string>
<string name="mmiCancelled" msgid="5339191899200678272">"MMI-koodi peruutettu"</string>
- <string name="cancel" msgid="8984206397635155197">"Peru"</string>
+ <string name="cancel" msgid="8984206397635155197">"Peruuta"</string>
<string name="enter_input" msgid="6193628663039958990">"USSD-viestin pituuden täytyy olla <xliff:g id="MIN_LEN">%1$d</xliff:g>–<xliff:g id="MAX_LEN">%2$d</xliff:g> merkkiä. Yritä uudelleen."</string>
<string name="manageConferenceLabel" msgid="8415044818156353233">"Hallinnoi puhelinneuvottelua"</string>
<string name="ok" msgid="7818974223666140165">"OK"</string>
@@ -107,21 +107,21 @@
<string name="sum_cfu_enabled_indicator" msgid="9030139213402432776">"Kaikki puhelut siirretään"</string>
<string name="sum_cfu_enabled" msgid="5806923046528144526">"Siirretään kaikki puhelut numeroon <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfu_enabled_no_number" msgid="7287752761743377930">"Numeroon ei saada yhteyttä"</string>
- <string name="sum_cfu_disabled" msgid="5010617134210809853">"Ei päällä"</string>
+ <string name="sum_cfu_disabled" msgid="5010617134210809853">"Ei käytössä"</string>
<string name="labelCFB" msgid="615265213360512768">"Kun olen varattu"</string>
<string name="messageCFB" msgid="1958017270393563388">"Soita, kun numero on varattu:"</string>
<string name="sum_cfb_enabled" msgid="332037613072049492">"Puhelu siirretään numeroon <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
- <string name="sum_cfb_disabled" msgid="3589913334164866035">"Ei päällä"</string>
+ <string name="sum_cfb_disabled" msgid="3589913334164866035">"Ei käytössä"</string>
<string name="disable_cfb_forbidden" msgid="4831494744351633961">"Operaattorisi ei tue soitonsiirtojen poistamista käytöstä, kun puhelimesi on varattuna."</string>
<string name="labelCFNRy" msgid="3403533792248457946">"Kun en vastaa"</string>
<string name="messageCFNRy" msgid="7644434155765359009">"Soita, kun numero ei vastaa:"</string>
<string name="sum_cfnry_enabled" msgid="3000500837493854799">"Puhelu siirretään numeroon <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
- <string name="sum_cfnry_disabled" msgid="1990563512406017880">"Ei päällä"</string>
+ <string name="sum_cfnry_disabled" msgid="1990563512406017880">"Ei käytössä"</string>
<string name="disable_cfnry_forbidden" msgid="3174731413216550689">"Operaattorisi ei tue soitonsiirtojen poistamista käytöstä, kun puhelimesi ei vastaa."</string>
<string name="labelCFNRc" msgid="4163399350778066013">"Kun en ole tavoitettavissa"</string>
<string name="messageCFNRc" msgid="6980340731313007250">"Soita, kun numeroon ei saada yhteyttä:"</string>
<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="sum_cfnrc_disabled" msgid="739289696796917683">"Ei käytössä"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Operaattorisi ei tue soitonsiirtojen poistamista käytöstä puhelimesi ollessa saavuttamattomissa."</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>
@@ -274,8 +274,8 @@
<string name="data_enable_summary" msgid="696860063456536557">"Salli tiedonsiirto"</string>
<string name="dialog_alert_title" msgid="5260471806940268478">"Huomio"</string>
<string name="roaming" msgid="1576180772877858949">"Roaming"</string>
- <string name="roaming_enable" msgid="6853685214521494819">"Yhdistä verkkoon roaming-tilassa"</string>
- <string name="roaming_disable" msgid="8856224638624592681">"Yhdistä verkkoon roaming-tilassa"</string>
+ <string name="roaming_enable" msgid="6853685214521494819">"Yhdistä verkkoon roaming-tilassa."</string>
+ <string name="roaming_disable" msgid="8856224638624592681">"Yhdistä verkkoon roaming-tilassa."</string>
<string name="roaming_reenable_message" msgid="1951802463885727915">"Roaming ei ole käytössä. Ota käyttöön napauttamalla."</string>
<string name="roaming_enabled_message" msgid="9022249120750897">"Puhelusta saatetaan periä roaming-maksuja. Muokkaa napauttamalla."</string>
<string name="roaming_notification_title" msgid="3590348480688047320">"Mobiilidatayhteys katkesi"</string>
@@ -294,7 +294,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">"Mobiilidata"</string>
<string name="mobile_data_settings_summary" msgid="5012570152029118471">"Käytä dataa mobiiliverkon kautta."</string>
- <string name="data_usage_disable_mobile" msgid="5669109209055988308">"Laitetaanko mobiilidata pois päältä?"</string>
+ <string name="data_usage_disable_mobile" msgid="5669109209055988308">"Poistetaanko mobiilidata käytöstä?"</string>
<string name="sim_selection_required_pref" msgid="6985901872978341314">"Valinta on pakollinen"</string>
<string name="sim_change_data_title" msgid="9142726786345906606">"Vaihdetaanko tied.siirto-SIM?"</string>
<string name="sim_change_data_message" msgid="3567358694255933280">"Käytetäänkö SIM-kortin <xliff:g id="NEW_SIM">%1$s</xliff:g> mobiilidataa kortin <xliff:g id="OLD_SIM">%2$s</xliff:g> sijaan?"</string>
@@ -307,7 +307,9 @@
<string name="throttle_time_frame" msgid="1813452485948918791">"Tietojen käyttöjakso"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Tiedonsiirtonopeuskäytäntö"</string>
<string name="throttle_help" msgid="2624535757028809735">"Lisätietoja"</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> jakson enimmäismäärästä\nSeuraava kausi alkaa <xliff:g id="USED_3">%4$d</xliff:g> päivän kuluttua (<xliff:g id="USED_4">%5$s</xliff:g>)"</string>
+ <!-- String.format failed for translation -->
+ <!-- no translation found for throttle_status_subtext (1110276415078236687) -->
+ <skip />
<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> jakson enimmäismäärästä"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"<xliff:g id="USED_0">%1$s</xliff:g> enimmäismäärä ylitetty\nTiedonsiirtonopeus vähennetty nopeuteen<xliff:g id="USED_1">%2$d</xliff:g> kt/s"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g>٪ kierrosta valmis\nSeuraava jakso alkaa <xliff:g id="USED_1">%2$d</xliff:g> päivän kuluttua (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -543,7 +545,7 @@
<string name="incall_error_supp_service_hold" msgid="8535056414643540997">"Puhelujen pito ei onnistu."</string>
<string name="incall_error_wfc_only_no_wireless_network" msgid="5860742792811400109">"Yhdistä langattomaan verkkoon, jos haluat soittaa puhelun."</string>
<string name="incall_error_promote_wfc" msgid="9164896813931363415">"Ota Wi-Fi-puhelut käyttöön soittaaksesi."</string>
- <string name="emergency_information_hint" msgid="9208897544917793012">"Vaaratiedot"</string>
+ <string name="emergency_information_hint" msgid="9208897544917793012">"Hätätilannetiedot"</string>
<string name="emergency_information_owner_hint" msgid="6256909888049185316">"Omistaja"</string>
<string name="emergency_information_confirm_hint" msgid="5109017615894918914">"Katso tiedot napauttamalla uudelleen"</string>
<string name="emergency_enable_radio_dialog_title" msgid="2667568200755388829">"Hätäpuhelu"</string>
@@ -637,18 +639,12 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Kyllä"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Ei"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Hylkää"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Puhelin on hätäpuhelujen takaisinsoittotilassa."</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<xliff:g id="COMPLETETIME">%s</xliff:g> asti"</string>
- <plurals name="alert_dialog_exit_ecm_without_data_restriction_hint" formatted="false" msgid="6477733043040328640">
- <item quantity="other">Puhelin on hätäpuhelujen takaisinsoittotilassa <xliff:g id="COUNT_1">%s</xliff:g> minuutin ajan.\nHaluatko poistua nyt?</item>
- <item quantity="one">Puhelin on hätäpuhelujen takaisinsoittotilassa <xliff:g id="COUNT_0">%s</xliff:g> minuutin ajan.\nHaluatko poistua nyt?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Palveluntarjoaja"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Asetukset"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Ei asetettu>"</string>
<string name="other_settings" msgid="8895088007393598447">"Muut puheluasetukset"</string>
<string name="calling_via_template" msgid="1791323450703751750">"Operaattorilla <xliff:g id="PROVIDER_NAME">%s</xliff:g>"</string>
- <string name="contactPhoto" msgid="7885089213135154834">"yhteyshenkilön kuva"</string>
+ <string name="contactPhoto" msgid="7885089213135154834">"yhteyshenkilön valokuva"</string>
<string name="goPrivate" msgid="4645108311382209551">"muuta yksityiseksi"</string>
<string name="selectContact" msgid="1527612842599767382">"valitse yhteystieto"</string>
<string name="not_voice_capable" msgid="2819996734252084253">"Äänipuheluita ei tueta"</string>
@@ -682,7 +678,7 @@
<string name="callFailed_wfc_service_not_available_in_this_location" msgid="3624536608369524988">"Wi-Fi-puheluja ei voi soittaa tässä paikassa."</string>
<string name="change_pin_title" msgid="3564254326626797321">"Vaihda vastaajan PIN-koodi."</string>
<string name="change_pin_continue_label" msgid="5177011752453506371">"Jatka"</string>
- <string name="change_pin_cancel_label" msgid="2301711566758827936">"Peru"</string>
+ <string name="change_pin_cancel_label" msgid="2301711566758827936">"Peruuta"</string>
<string name="change_pin_ok_label" msgid="6861082678817785330">"OK"</string>
<string name="change_pin_enter_old_pin_header" msgid="853151335217594829">"Vahvista vanha PIN-koodi."</string>
<string name="change_pin_enter_old_pin_hint" msgid="8801292976275169367">"Jatka antamalla vastaajasi PIN-koodi."</string>
@@ -760,8 +756,8 @@
<string name="clh_callFailed_protocol_Error_unspecified_txt" msgid="9203320572562697755">"Soittaminen epäonnistui. Virhekoodi 111."</string>
<string name="clh_callFailed_interworking_unspecified_txt" msgid="7969686413930847182">"Soittaminen epäonnistui. Virhekoodi 127."</string>
<string name="labelCallBarring" msgid="4180377113052853173">"Puhelujen esto"</string>
- <string name="sum_call_barring_enabled" msgid="5184331188926370824">"Päällä"</string>
- <string name="sum_call_barring_disabled" msgid="5699448000600153096">"Pois päältä"</string>
+ <string name="sum_call_barring_enabled" msgid="5184331188926370824">"Käytössä"</string>
+ <string name="sum_call_barring_disabled" msgid="5699448000600153096">"Pois käytöstä"</string>
<string name="call_barring_baoc" msgid="7400892586336429326">"Kaikki lähtevät"</string>
<string name="call_barring_baoc_enabled" msgid="3131509193386668182">"Poistetaanko kaikkien lähtevien puheluiden esto käytöstä?"</string>
<string name="call_barring_baoc_disabled" msgid="8534224684091141509">"Estetäänkö kaikki lähtevät puhelut?"</string>
@@ -831,7 +827,7 @@
<string name="dsds_dialog_title" msgid="8494569893941847575">"Käynnistä uudelleen?"</string>
<string name="dsds_dialog_message" msgid="4047480385678538850">"Laite on käynnistettävä uudelleen asetuksen muuttamiseksi."</string>
<string name="dsds_dialog_confirm" msgid="9032004888134129885">"Käynnistä uudelleen"</string>
- <string name="dsds_dialog_cancel" msgid="3245958947099586655">"Peru"</string>
+ <string name="dsds_dialog_cancel" msgid="3245958947099586655">"Peruuta"</string>
<string name="radio_info_radio_power" msgid="8805595022160471587">"Mobiiliradion voimakkuus"</string>
<string name="radioInfo_menu_viewADN" msgid="4533179730908559846">"Näytä SIM-kortin osoitekirja"</string>
<string name="radioInfo_menu_viewFDN" msgid="1847236480527032061">"Näytä sallitut numerot"</string>
@@ -898,11 +894,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Päivitä"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Ota DNS-tarkistus käyttöön tai poista se käytöstä"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-kohtaiset tiedot/asetukset"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC saatavilla:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR rajoitettu:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR saatavilla:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR:n tila:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR:n taajuus:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Radion taajuustilan valinta"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Ladataan taajuusluetteloa…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Aseta"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 7597f6a..2368721 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -307,7 +307,9 @@
<string name="throttle_time_frame" msgid="1813452485948918791">"Période d\'utilisation des données"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Règles relatives au taux de transfert des données"</string>
<string name="throttle_help" msgid="2624535757028809735">"En savoir plus"</string>
- <string name="throttle_status_subtext" msgid="1110276415078236687">"<xliff:g id="USED_0">%1$s</xliff:g> sur <xliff:g id="USED_2">%3$s</xliff:g> (soit <xliff:g id="USED_1">%2$d</xliff:g> %%) du maximum par période\nLa prochaine période démarre dans <xliff:g id="USED_3">%4$d</xliff:g> jours (<xliff:g id="USED_4">%5$s</xliff:g>)."</string>
+ <!-- String.format failed for translation -->
+ <!-- no translation found for throttle_status_subtext (1110276415078236687) -->
+ <skip />
<string name="throttle_data_usage_subtext" msgid="3185429653996709840">"<xliff:g id="USED_0">%1$s</xliff:g> sur <xliff:g id="USED_2">%3$s</xliff:g> : (<xliff:g id="USED_1">%2$d</xliff:g> ٪) du maximum par période"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"<xliff:g id="USED_0">%1$s</xliff:g> maximum dépassé\nTaux de transfert des données réduit à <xliff:g id="USED_1">%2$d</xliff:g> Ko/s"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g> ٪ du cycle écoulé.\nLa prochaine période démarre dans <xliff:g id="USED_1">%2$d</xliff:g> jours (<xliff:g id="USED_2">%3$s</xliff:g>)."</string>
@@ -522,7 +524,7 @@
<string name="notification_voicemail_no_vm_number" msgid="3423686009815186750">"Numéro de messagerie vocale inconnu"</string>
<string name="notification_network_selection_title" msgid="255595526707809121">"Aucun service"</string>
<string name="notification_network_selection_text" msgid="553288408722427659">"Réseau sélectionné<xliff:g id="OPERATOR_NAME">%s</xliff:g> non disponible"</string>
- <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="watch" msgid="7191184639454113633">"Activez le réseau cellulaire ou désactivez le mode Avion ou le mode Économie d\'énergie 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_ecm_emergency_only" msgid="5622379058883722080">"Quittez le mode de rappel d\'urgence pour effectuer un appel non urgent."</string>
@@ -580,7 +582,7 @@
<string name="singleContactImportedMsg" msgid="3619804066300998934">"Contacts importés"</string>
<string name="failedToImportSingleContactMsg" msgid="228095510489830266">"Impossible d\'importer le contact"</string>
<string name="hac_mode_title" msgid="4127986689621125468">"Assistance auditive"</string>
- <string name="hac_mode_summary" msgid="7774989500136009881">"Activer la compatibilité des prothèses auditives"</string>
+ <string name="hac_mode_summary" msgid="7774989500136009881">"Activer la compatibilité du service d\'assistance auditive"</string>
<string name="rtt_mode_title" msgid="3075948111362818043">"Appel texte en temps réel (TTR)"</string>
<string name="rtt_mode_summary" msgid="8631541375609989562">"Autoriser l\'utilisation de la messagerie lors des appels vocaux"</string>
<string name="rtt_mode_more_information" msgid="587500128658756318">"La fonctionnalité TTR aide les appelants qui sont sourds ou malentendants, qui ont un trouble de la parole ou pour qui la voix ne suffit pas.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>En savoir plus</a>\n <br><br> - Les appels TTR sont enregistrés en tant que transcriptions de messages\n <br> - La fonctionnalité TTR n\'est pas disponible pour les appels vidéo"</string>
@@ -637,12 +639,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Oui"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Non"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Ignorer"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Le téléphone est en mode de rappel d\'urgence"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Jusqu\'à <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">Le mode de rappel d\'urgence sera activé sur le téléphone pour une durée de <xliff:g id="COUNT_1">%s</xliff:g> minute.\nVoulez-vous quitter ce mode maintenant?</item>
- <item quantity="other">Le mode de rappel d\'urgence sera activé sur le téléphone pour une durée de <xliff:g id="COUNT_1">%s</xliff:g> minutes.\nVoulez-vous quitter ce mode maintenant?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Service"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Configuration"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Non défini>"</string>
@@ -867,7 +863,7 @@
<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_signal_location_label" msgid="6188435197086550049">"Données de la position de la cellule (discontinuées) :"</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>
@@ -898,11 +894,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Actualiser"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Basculer la vérification DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Informations/paramètres OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC disponible :"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR restreinte :"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR disponible :"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"État NR :"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Fréquence NR :"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Définir le mode de bande radio"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Chargement de la liste de bandes en cours…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Définir"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 597fdbd..dbaa128 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -36,7 +36,7 @@
<string name="ok" msgid="7818974223666140165">"OK"</string>
<string name="audio_mode_speaker" msgid="243689733219312360">"Haut-parleur"</string>
<string name="audio_mode_earpiece" msgid="2823700267171134282">"Écouteur du combiné"</string>
- <string name="audio_mode_wired_headset" msgid="5028010823105817443">"Casque filaire"</string>
+ <string name="audio_mode_wired_headset" msgid="5028010823105817443">"Écouteurs filaires"</string>
<string name="audio_mode_bluetooth" msgid="25732183428018809">"Bluetooth"</string>
<string name="wait_prompt_str" msgid="5136209532150094910">"Envoyer les tonalités suivantes ?\n"</string>
<string name="pause_prompt_str" msgid="2308897950360272213">"Envoi des tonalités\n"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Oui"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Non"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Ignorer"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Le téléphone est en mode de rappel d\'urgence"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Jusqu\'à <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">Le téléphone sera en mode de rappel d\'urgence pendant <xliff:g id="COUNT_1">%s</xliff:g> minutes.\nSouhaitez-vous quitter ce mode maintenant ?</item>
- <item quantity="other">Le téléphone sera en mode de rappel d\'urgence pendant <xliff:g id="COUNT_1">%s</xliff:g> minutes.\nSouhaitez-vous quitter ce mode maintenant ?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Service"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Configuration"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Non défini>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Actualiser"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Activer/Désactiver le contrôle DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Infos/paramètres OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"Accès EN-DC disponible :"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"Limitation DCNR :"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"Accès NR disponible :"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"État NR :"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Fréquence NR :"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Définir le mode de bande radio"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Chargement de la liste de bandes…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Définir"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 3137f92..58b1d27 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -307,7 +307,7 @@
<string name="throttle_time_frame" msgid="1813452485948918791">"Período de uso de datos"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Política de velocidade de datos"</string>
<string name="throttle_help" msgid="2624535757028809735">"Máis información"</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>%%) de <xliff:g id="USED_2">%3$s</xliff:g> de período máximo\nO seguinte período comeza dentro de <xliff:g id="USED_3">%4$d</xliff:g> días (<xliff:g id="USED_4">%5$s</xliff:g>)"</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>٪) de <xliff:g id="USED_2">%3$s</xliff:g> de período máximo\nO seguinte período comeza dentro de <xliff:g id="USED_3">%4$d</xliff:g> días (<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>٪) de <xliff:g id="USED_2">%3$s</xliff:g> de período máximo"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"<xliff:g id="USED_0">%1$s</xliff:g> máximo superado\nTaxa de datos reducida a <xliff:g id="USED_1">%2$d</xliff:g> kb/s"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g>٪ do ciclo transcorrido\nO seguinte período comeza dentro de <xliff:g id="USED_1">%2$d</xliff:g> días (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Si"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Non"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Rexeitar"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"O teléfono está no modo de devolución de chamadas de emerxencia"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Ata: <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 teléfono estará no modo de devolución de chamadas de emerxencia durante <xliff:g id="COUNT_1">%s</xliff:g> minutos.\nQueres saír agora?</item>
- <item quantity="one">O teléfono estará no modo de devolución de chamadas de emerxencia durante <xliff:g id="COUNT_0">%s</xliff:g> minuto.\nQueres saír agora?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Servizo"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Configuración"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Sen configurar>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Actualizar"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Alternar comprobación de DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Información ou configuración específica de OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC dispoñible:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR con restricións:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR dispoñible:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Estado de NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Frecuencia de NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Definir modo de banda de radio"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Cargando lista de bandas…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Definir"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 9c06bc2..9d706a7 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -62,7 +62,7 @@
<string name="labelCdmaMore_with_label" msgid="7759692829160238152">"CDMA કૉલ સેટિંગ્સ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="apn_settings" msgid="1978652203074756623">"ઍક્સેસ પોઇન્ટનું નામ"</string>
<string name="settings_label" msgid="9101778088412567956">"નેટવર્ક સેટિંગ્સ"</string>
- <string name="phone_accounts" msgid="1216879437523774604">"કૉલ કરવા માટેના એકાઉન્ટ"</string>
+ <string name="phone_accounts" msgid="1216879437523774604">"કૉલિંગ એકાઉન્ટ્સ"</string>
<string name="phone_accounts_make_calls_with" msgid="16747814788918145">"આના વડે કૉલ કરો"</string>
<string name="phone_accounts_make_sip_calls_with" msgid="4691221006731847255">"આના વડે SIP કૉલ્સ કરો"</string>
<string name="phone_accounts_ask_every_time" msgid="6192347582666047168">"પહેલાં પૂછો"</string>
@@ -84,9 +84,9 @@
<string name="smart_forwarding_settings_menu_summary" msgid="5096947726032885325">"એક નંબર પર પહોંચી શકાય તેમ ન હોય, ત્યારે હંમેશાં તમારા અન્ય નંબર પર કૉલ ફૉરવર્ડ કરો"</string>
<string name="voicemail_notifications_preference_title" msgid="7829238858063382977">"નોટિફિકેશન"</string>
<string name="cell_broadcast_settings" msgid="8135324242541809924">"કટોકટીના બ્રોડકાસ્ટ્સ"</string>
- <string name="call_settings" msgid="3677282690157603818">"કૉલ સેટિંગ"</string>
- <string name="additional_gsm_call_settings" msgid="1561980168685658846">"વધારાના સેટિંગ"</string>
- <string name="additional_gsm_call_settings_with_label" msgid="7973920539979524908">"વધારાના સેટિંગ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
+ <string name="call_settings" msgid="3677282690157603818">"કૉલ સેટિંગ્સ"</string>
+ <string name="additional_gsm_call_settings" msgid="1561980168685658846">"વધારાની સેટિંગ્સ"</string>
+ <string name="additional_gsm_call_settings_with_label" msgid="7973920539979524908">"વધારાની સેટિંગ્સ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="sum_gsm_call_settings" msgid="7964692601608878138">"ફક્ત વધારાની GSM કૉલ સેટિંગ્સ"</string>
<string name="additional_cdma_call_settings" msgid="2178016561980611304">"વધારાની CDMA કૉલ સેટિંગ્સ"</string>
<string name="sum_cdma_call_settings" msgid="3185825305136993636">"ફક્ત વધારાની CDMA કૉલ સેટિંગ્સ"</string>
@@ -97,10 +97,10 @@
<string name="sum_show_caller_id" msgid="3571854755324664591">"આઉટગોઇંગ કૉલ્સમાં નંબર પ્રદર્શિત થાય છે"</string>
<string name="sum_default_caller_id" msgid="1767070797135682959">"આઉટગોઇંગ કૉલ્સમાં મારો નંબર પ્રદર્શિત કરવા માટે ડિફોલ્ટ ઓપરેટર સેટિંગ્સનો ઉપયોગ કરો"</string>
<string name="labelCW" msgid="8449327023861428622">"કૉલ પ્રતીક્ષા"</string>
- <string name="sum_cw_enabled" msgid="3977308526187139996">"કૉલ દરમ્યાન, મને ઇનકમિંગ કૉલની સૂચના આપો"</string>
- <string name="sum_cw_disabled" msgid="3658094589461768637">"કૉલ દરમ્યાન, મને ઇનકમિંગ કૉલની સૂચના આપો"</string>
- <string name="call_forwarding_settings" msgid="8937130467468257671">"કૉલ ફોરવર્ડિંગ સેટિંગ"</string>
- <string name="call_forwarding_settings_with_label" msgid="2345432813399564272">"કૉલ ફોરવર્ડિંગ સેટિંગ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
+ <string name="sum_cw_enabled" msgid="3977308526187139996">"કૉલ દરમ્યાન, મને આવનારા કૉલ્સની સૂચના આપો"</string>
+ <string name="sum_cw_disabled" msgid="3658094589461768637">"કૉલ દરમ્યાન, મને આવનારા કૉલ્સની સૂચના આપો"</string>
+ <string name="call_forwarding_settings" msgid="8937130467468257671">"કૉલ ફોરવર્ડિંગ સેટિંગ્સ"</string>
+ <string name="call_forwarding_settings_with_label" msgid="2345432813399564272">"કૉલ ફોરવર્ડિંગ સેટિંગ્સ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="labelCF" msgid="3578719437928476078">"કૉલ ફોરવર્ડિંગ"</string>
<string name="labelCFU" msgid="8870170873036279706">"હંમેશા ફોરવર્ડ કરો"</string>
<string name="messageCFU" msgid="1361806450979589744">"હંમેશાં આ નંબરનો ઉપયોગ કરો"</string>
@@ -123,12 +123,12 @@
<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="updating_title" msgid="6130548922615719689">"કૉલ સેટિંગ"</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>
<string name="error_updating_title" msgid="2024290892676808965">"કૉલ સેટિંગની ભૂલ"</string>
- <string name="reading_settings" msgid="1605904432450871183">"સેટિંગ વાંચી રહ્યાં છે…"</string>
- <string name="updating_settings" msgid="3650396734816028808">"સેટિંગ અપડેટ કરી રહ્યાં છીએ..."</string>
+ <string name="reading_settings" msgid="1605904432450871183">"સેટિંગ્સ વાંચી રહ્યાં છે…"</string>
+ <string name="updating_settings" msgid="3650396734816028808">"સેટિંગ્સ અપડેટ કરી રહ્યાં છે..."</string>
<string name="reverting_settings" msgid="7378668837291012205">"સેટિંગ્સ પાછી ફરાવી રહ્યાં છે…"</string>
<string name="response_error" msgid="3904481964024543330">"નેટવર્ક તરફથી અનપેક્ષિત પ્રતિસાદ."</string>
<string name="exception_error" msgid="330994460090467">"નેટવર્ક અથવા SIM કાર્ડ ભૂલ."</string>
@@ -173,8 +173,8 @@
<string name="not_allowed" msgid="8541221928746104798">"તમારું SIM કાર્ડ આ નેટવર્કથી કનેક્શનને મંજૂરી આપતું નથી."</string>
<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="already_auto" msgid="8607068290733079336">"પહેલેથી જ આપમેળે પસંદગીમાં."</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>
@@ -305,14 +305,14 @@
<string name="throttle_data_usage" msgid="1944145350660420711">"ડેટા વપરાશ"</string>
<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_rate" msgid="7641913901133634905">"માહિતી રેટ નીતિ"</string>
<string name="throttle_help" msgid="2624535757028809735">"વધુ જાણો"</string>
<string name="throttle_status_subtext" msgid="1110276415078236687">"<xliff:g id="USED_2">%3$s</xliff:g> માંથી <xliff:g id="USED_0">%1$s</xliff:g> (<xliff:g id="USED_1">%2$d</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_2">%3$s</xliff:g> માંથી <xliff:g id="USED_0">%1$s</xliff:g> (<xliff:g id="USED_1">%2$d</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> Kb/s સુધી ઘટાડાશે"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g>٪ ચક્ર વીતાવ્યું\nઆગલી અવધિ <xliff:g id="USED_1">%2$d</xliff:g> દિવસમાં પ્રારંભ થાય છે (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
<string name="throttle_rate_subtext" msgid="7221971817325779535">"જો ડેટા સીમા વટાવવામાં આવે તો ડેટા રેટ <xliff:g id="USED">%1$d</xliff:g> Kb/s સુધી ઘટાડવામાં આવે છે"</string>
- <string name="throttle_help_subtext" msgid="2817114897095534807">"તમારા કૅરિઅરની મોબાઇલ નેટવર્ક ડેટા ઉપયોગ પૉલિસી વિશે વધુ માહિતી"</string>
+ <string name="throttle_help_subtext" msgid="2817114897095534807">"તમારા કેરિઅરની મોબાઇલ નેટવર્ક ડેટા ઉપયોગ નીતિ વિશે વધુ માહિતી"</string>
<string name="cell_broadcast_sms" msgid="4053449797289031063">"સેલ બ્રોડકાસ્ટ SMS"</string>
<string name="enable_disable_cell_bc_sms" msgid="4759958924031721350">"સેલ બ્રોડકાસ્ટ SMS"</string>
<string name="cell_bc_sms_enable" msgid="2019708772024632073">"સેલ બ્રોડકાસ્ટ SMS સક્ષમ કરેલ છે"</string>
@@ -546,8 +546,8 @@
<string name="emergency_information_hint" msgid="9208897544917793012">"ઇમર્જન્સીની માહિતી"</string>
<string name="emergency_information_owner_hint" msgid="6256909888049185316">"માલિક"</string>
<string name="emergency_information_confirm_hint" msgid="5109017615894918914">"માહિતી જોવા માટે ફરીથી ટૅપ કરો"</string>
- <string name="emergency_enable_radio_dialog_title" msgid="2667568200755388829">"ઇમર્જન્સી કૉલ"</string>
- <string name="single_emergency_number_title" msgid="8413371079579067196">"ઇમર્જન્સી નંબર"</string>
+ <string name="emergency_enable_radio_dialog_title" msgid="2667568200755388829">"કટોકટીનો કૉલ"</string>
+ <string name="single_emergency_number_title" msgid="8413371079579067196">"કટોકટીનો નંબર"</string>
<string name="numerous_emergency_numbers_title" msgid="8972398932506755510">"કટોકટીના નંબર"</string>
<string name="emergency_call_shortcut_hint" msgid="1290485125107779500">"<xliff:g id="EMERGENCY_NUMBER">%s</xliff:g>ને ફરીથી કૉલ કરવા માટે ટૅપ કરો"</string>
<string name="emergency_enable_radio_dialog_message" msgid="1695305158151408629">"રેડિઓ ચાલુ કરી રહ્યાં છે…"</string>
@@ -572,7 +572,7 @@
<string name="onscreenManageCallsText" msgid="1162047856081836469">"કૉલ્સ સંચાલિત કરો"</string>
<string name="onscreenManageConferenceText" msgid="4700574060601755137">"કોન્ફરન્સ સંચાલિત કરો"</string>
<string name="onscreenAudioText" msgid="7224226735052019986">"ઑડિઓ"</string>
- <string name="onscreenVideoCallText" msgid="1743992456126258698">"વીડિયો કૉલ"</string>
+ <string name="onscreenVideoCallText" msgid="1743992456126258698">"વિડિઓ કૉલ"</string>
<string name="importSimEntry" msgid="3892354284082689894">"આયાત કરો"</string>
<string name="importAllSimEntries" msgid="2628391505643564007">"બધુ આયાત કરો"</string>
<string name="importingSimContacts" msgid="4995457122107888932">"SIM સંપર્કો આયાત કરી રહ્યાં છે"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"હા"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"નહીં"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"છોડી દો"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"ફોન ઇમર્જન્સી કૉલબૅક મોડમાં છે"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">ફોન <xliff:g id="COUNT_1">%s</xliff:g> મિનિટ માટે ઇમર્જન્સી કૉલબૅક મોડમાં રહેશે.\nશું તમે હમણાં બહાર નીકળવા માગો છો?</item>
- <item quantity="other">ફોન <xliff:g id="COUNT_1">%s</xliff:g> મિનિટ માટે ઇમર્જન્સી કૉલબૅક મોડમાં રહેશે.\nશું તમે હમણાં બહાર નીકળવા માગો છો?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"સેવા"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"સેટઅપ"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<સેટ કરેલ નથી>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"રિફ્રેશ કરો"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS તપાસ ટૉગલ કરો"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-વિશિષ્ટ માહિતી/સેટિંગ"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC ઉપલબ્ધ:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR પ્રતિબંધિત:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR ઉપલબ્ધ:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR સ્ટેટસ:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR આવર્તન:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"રેડિયો બૅન્ડ મોડ સેટ કરો"</string>
<string name="band_mode_loading" msgid="795923726636735967">"બૅન્ડની સૂચિ લોડ કરી રહ્યું છે…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"સેટ કરો"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index c472d63..2cdc768 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -307,7 +307,7 @@
<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_status_subtext" msgid="1110276415078236687">"<xliff:g id="USED_2">%3$s</xliff:g> में से <xliff:g id="USED_0">%1$s</xliff:g> (<xliff:g id="USED_1">%2$d</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_status_subtext" msgid="1110276415078236687">"<xliff:g id="USED_2">%3$s</xliff:g> में से <xliff:g id="USED_0">%1$s</xliff:g> (<xliff:g id="USED_1">%2$d</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_2">%3$s</xliff:g> में से <xliff:g id="USED_0">%1$s</xliff:g> (<xliff:g id="USED_1">%2$d</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> Kb/s तक कम हो गई है"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"चक्र का <xliff:g id="USED_0">%1$d</xliff:g>٪ बीत चुका है\nअगली अवधि <xliff:g id="USED_1">%2$d</xliff:g> दिन में आरंभ होगी (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -637,18 +637,12 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"हां"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"नहीं"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"खारिज करें"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"फ़ोन आपातकालीन कॉलबैक मोड में है"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">फ़ोन <xliff:g id="COUNT_1">%s</xliff:g> मिनट के लिए आपातकालीन कॉलबैक मोड में रहेगा.\nक्या आप अभी बाहर निकलना चाहते हैं?</item>
- <item quantity="other">फ़ोन <xliff:g id="COUNT_1">%s</xliff:g> मिनट के लिए आपातकालीन कॉलबैक मोड में रहेगा.\nक्या आप अभी बाहर निकलना चाहते हैं?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"सेवा"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"सेटअप"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<सेट नहीं है>"</string>
<string name="other_settings" msgid="8895088007393598447">"अन्य कॉल सेटिंग"</string>
<string name="calling_via_template" msgid="1791323450703751750">"कॉल <xliff:g id="PROVIDER_NAME">%s</xliff:g> से जा रही है"</string>
- <string name="contactPhoto" msgid="7885089213135154834">"संपर्क की फ़ोटो"</string>
+ <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>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"रीफ़्रेश करें"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"डीएनएस जांच टॉगल करें"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"ओईएम-खास जानकारी/सेटिंग"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC उपलब्ध है:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR प्रतिबंधित है:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR उपलब्ध है:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR की स्थिति:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR की फ़्रीक्वेंसी:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"\'रेडियो बैंड\' मोड सेट करें"</string>
<string name="band_mode_loading" msgid="795923726636735967">"बैंड सूची लोड की जा रही है…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"सेट करें"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 6028212..67cc6de 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -95,17 +95,17 @@
<string name="sum_loading_settings" msgid="434063780286688775">"Učitavanje postavki…"</string>
<string name="sum_hide_caller_id" msgid="131100328602371933">"Broj je skriven u izlaznim pozivima"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"Broj prikazan za izlazne pozive"</string>
- <string name="sum_default_caller_id" msgid="1767070797135682959">"Upotrijebi zadane postavke operatera za prikaz mog broja kod odlaznih poziva"</string>
+ <string name="sum_default_caller_id" msgid="1767070797135682959">"Koristiti zadane postavke operatera za prikaz mog broja kod odlaznih poziva"</string>
<string name="labelCW" msgid="8449327023861428622">"Poziv na čekanju"</string>
- <string name="sum_cw_enabled" msgid="3977308526187139996">"Obavijesti me o dolaznim pozivima tijekom poziva"</string>
- <string name="sum_cw_disabled" msgid="3658094589461768637">"Obavijesti me o dolaznim pozivima tijekom poziva"</string>
+ <string name="sum_cw_enabled" msgid="3977308526187139996">"Obavijesti me tijekom poziva o dolaznim pozivima"</string>
+ <string name="sum_cw_disabled" msgid="3658094589461768637">"Obavijesti me tijekom poziva o dolaznim pozivima"</string>
<string name="call_forwarding_settings" msgid="8937130467468257671">"Postavke preusmjeravanja poziva"</string>
<string name="call_forwarding_settings_with_label" msgid="2345432813399564272">"Prosljeđivanje poziva (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="labelCF" msgid="3578719437928476078">"Preusmjeravanje poziva"</string>
<string name="labelCFU" msgid="8870170873036279706">"Uvijek preusmjeri"</string>
<string name="messageCFU" msgid="1361806450979589744">"Uvijek koristi ovaj broj"</string>
<string name="sum_cfu_enabled_indicator" msgid="9030139213402432776">"Preusmjeravanje svih poziva"</string>
- <string name="sum_cfu_enabled" msgid="5806923046528144526">"Preusmjeravanje svih poziva na <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
+ <string name="sum_cfu_enabled" msgid="5806923046528144526">"Preusmjeravanje poziva na <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfu_enabled_no_number" msgid="7287752761743377930">"Broj je nedostupan"</string>
<string name="sum_cfu_disabled" msgid="5010617134210809853">"Isključeno"</string>
<string name="labelCFB" msgid="615265213360512768">"Kad je broj zauzet"</string>
@@ -639,13 +639,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Da"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Ne"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Odbaci"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Telefon je u načinu rada hitnog povratnog poziva"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Do <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">Telefon će biti u načinu rada hitnog povratnog poziva <xliff:g id="COUNT_1">%s</xliff:g> minutu.\nŽelite li sada izaći?</item>
- <item quantity="few">Telefon će biti u načinu rada hitnog povratnog poziva <xliff:g id="COUNT_1">%s</xliff:g> minute.\nŽelite li sada izaći?</item>
- <item quantity="other">Telefon će biti u načinu rada hitnog povratnog poziva <xliff:g id="COUNT_1">%s</xliff:g> minuta.\nŽelite li sada izaći?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Usluga"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Postavljanje"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Nije postavljeno>"</string>
@@ -901,11 +894,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Osvježi"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Uključi/isključi provjeru DNS-a"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Informacije/postavke koje se posebno odnose na OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"Dostupno za EN-DC:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"Ograničeno za DCNR:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"Dostupno za NR:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Stanje NR-a:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Frekvencija NR-a:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Postavi način radijske frekvencije"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Učitavanje popisa frekvencija…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Postavi"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 75b1783..ff5d11b 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -273,16 +273,16 @@
<string name="data_enabled" msgid="22525832097434368">"Adatok engedélyezése"</string>
<string name="data_enable_summary" msgid="696860063456536557">"Adatforgalom engedélyezése"</string>
<string name="dialog_alert_title" msgid="5260471806940268478">"Figyelem"</string>
- <string name="roaming" msgid="1576180772877858949">"Roaming"</string>
- <string name="roaming_enable" msgid="6853685214521494819">"Csatlakozás adatszolgáltatásokhoz roaming során"</string>
- <string name="roaming_disable" msgid="8856224638624592681">"Csatlakozás adatszolgáltatásokhoz roaming során"</string>
+ <string name="roaming" msgid="1576180772877858949">"Barangolás"</string>
+ <string name="roaming_enable" msgid="6853685214521494819">"Csatlakozás adatszolgáltatásokhoz barangolás során"</string>
+ <string name="roaming_disable" msgid="8856224638624592681">"Csatlakozás adatszolgáltatásokhoz barangolás során"</string>
<string name="roaming_reenable_message" msgid="1951802463885727915">"Az adatbarangolás ki van kapcsolva. Koppintson a bekapcsolásához."</string>
<string name="roaming_enabled_message" msgid="9022249120750897">"A szolgáltató adatroamingdíjat számíthat fel. Koppintson a módosításhoz."</string>
<string name="roaming_notification_title" msgid="3590348480688047320">"Nincs mobiladat-kapcsolat"</string>
<string name="roaming_on_notification_title" msgid="7451473196411559173">"Az adatroaming be van kapcsolva"</string>
<string name="roaming_warning" msgid="7855681468067171971">"Lehet, hogy jelentős összeget számítanak fel érte."</string>
<string name="roaming_check_price_warning" msgid="8212484083990570215">"Az árakat a szolgáltatótól tudhatja meg."</string>
- <string name="roaming_alert_title" msgid="5689615818220960940">"Engedélyezi az adatroamingot?"</string>
+ <string name="roaming_alert_title" msgid="5689615818220960940">"Engedélyezi az adatbarangolást?"</string>
<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>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Igen"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Nem"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Elvetés"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"A telefon sürgősségi visszahívás módban van."</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Eddig: <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">A telefon <xliff:g id="COUNT_1">%s</xliff:g> percig sürgősségi visszahívás módban lesz.\nKilép?</item>
- <item quantity="one">A telefon <xliff:g id="COUNT_0">%s</xliff:g> percig sürgősségi visszahívás módban lesz.\nKilép?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Szolgáltatás"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Beállítás"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Nincs megadva>"</string>
@@ -769,13 +763,13 @@
<string name="call_barring_baoic_enabled" msgid="1203758092657630123">"Megszünteti a kimenő nemzetközi hívások letiltását?"</string>
<string name="call_barring_baoic_disabled" msgid="5656889339002997449">"Letiltja a kimenő nemzetközi hívásokat?"</string>
<string name="call_barring_baoicr" msgid="8566167764432343487">"Kimenő hívások nemzetközi barangoláskor"</string>
- <string name="call_barring_baoicr_enabled" msgid="1615324165512798478">"Megszünteti a nemzetközi roaming során kimenő hívások letiltását?"</string>
+ <string name="call_barring_baoicr_enabled" msgid="1615324165512798478">"Megszünteti a nemzetközi barangolás során kimenő hívások letiltását?"</string>
<string name="call_barring_baoicr_disabled" msgid="172010175248142831">"Letiltja a kimenő hívásokat nemzetközi barangoláskor?"</string>
<string name="call_barring_baic" msgid="7941393541678658566">"Minden bejövő hívás"</string>
<string name="call_barring_baic_enabled" msgid="4357332358020337470">"Megszünteti az összes bejövő hívás letiltását?"</string>
<string name="call_barring_baic_disabled" msgid="2355945245938240958">"Letiltja az összes bejövő hívást?"</string>
<string name="call_barring_baicr" msgid="8712249337313034226">"Bejövő hívások nemzetközi barangoláskor"</string>
- <string name="call_barring_baicr_enabled" msgid="64774270234828175">"Megszünteti a nemzetközi roaming során bejövő összes hívás letiltását?"</string>
+ <string name="call_barring_baicr_enabled" msgid="64774270234828175">"Megszünteti a nemzetközi barangolás során bejövő összes hívás letiltását?"</string>
<string name="call_barring_baicr_disabled" msgid="3488129262744027262">"Letiltja a bejövő hívásokat nemzetközi barangoláskor?"</string>
<string name="call_barring_deactivate_all" msgid="7837931580047157328">"Az összes deaktiválása"</string>
<string name="call_barring_deactivate_all_description" msgid="4474119585042121604">"Az összes híváskorlátozási beállítás deaktiválása"</string>
@@ -872,7 +866,7 @@
<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>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Adatszolgáltatás:"</string>
- <string name="radio_info_roaming_label" msgid="6636932886446857120">"Roaming:"</string>
+ <string name="radio_info_roaming_label" msgid="6636932886446857120">"Barangolás:"</string>
<string name="radio_info_imei_label" msgid="8947899706930120368">"IMEI:"</string>
<string name="radio_info_call_redirect_label" msgid="4526480903023362276">"Hívásátirányítás:"</string>
<string name="radio_info_ppp_resets_label" msgid="9131901102339077661">"PPP-visszaállítások száma a legutolsó rendszerindítás óta:"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Frissítés"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS-ellenőrzés váltása"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-specifikus adatok és beállítások:"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC rendelkezésre áll:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR korlátozva:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR rendelkezésre áll:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR-állapot:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR-frekvencia:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Rádióhullámsáv mód beállítása"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Sávlista betöltése…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Beállítás"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index de96e1b..bc9945e 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -102,7 +102,7 @@
<string name="call_forwarding_settings" msgid="8937130467468257671">"Վերահասցեավորում"</string>
<string name="call_forwarding_settings_with_label" msgid="2345432813399564272">"Զանգի փոխանցման կարգավորումներ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="labelCF" msgid="3578719437928476078">"Զանգի վերահասցեավորում"</string>
- <string name="labelCFU" msgid="8870170873036279706">"Միշտ վերահասցեավորել"</string>
+ <string name="labelCFU" msgid="8870170873036279706">"Միշտ փոխանցել"</string>
<string name="messageCFU" msgid="1361806450979589744">"Միշտ օգտագործել այս համարը"</string>
<string name="sum_cfu_enabled_indicator" msgid="9030139213402432776">"Բոլոր զանգերը վերահասցեավորվում են"</string>
<string name="sum_cfu_enabled" msgid="5806923046528144526">"Բոլոր զանգերը վերահասցեավորվում են <xliff:g id="PHONENUMBER">{0}</xliff:g>-ին"</string>
@@ -113,12 +113,12 @@
<string name="sum_cfb_enabled" msgid="332037613072049492">"Վերահասցեավորվում է դեպի <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfb_disabled" msgid="3589913334164866035">"Անջատված է"</string>
<string name="disable_cfb_forbidden" msgid="4831494744351633961">"Ձեր օպերատորը չի աջակցում զանգի վերահասցեավորման կասեցում, երբ ձեր հեռախոսը զբաղված է:"</string>
- <string name="labelCFNRy" msgid="3403533792248457946">"Երբ չեմ պատասխանում"</string>
+ <string name="labelCFNRy" msgid="3403533792248457946">"Երբ պատասխան չկա"</string>
<string name="messageCFNRy" msgid="7644434155765359009">"Թվել, երբ անպատասխան է"</string>
<string name="sum_cfnry_enabled" msgid="3000500837493854799">"Վերահասցեավորվում է դեպի <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnry_disabled" msgid="1990563512406017880">"Անջատված է"</string>
<string name="disable_cfnry_forbidden" msgid="3174731413216550689">"Ձեր օպերատորը չի աջակցում զանգի վերահասցեավորման կասեցում, երբ ձեր հեռախոսը չի պատասխանում:"</string>
- <string name="labelCFNRc" msgid="4163399350778066013">"Երբ անհասանելի եմ"</string>
+ <string name="labelCFNRc" msgid="4163399350778066013">"Երբ անհասանելի է"</string>
<string name="messageCFNRc" msgid="6980340731313007250">"Թվել, երբ անհասանելի է"</string>
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Վերահասցեավորվում է դեպի <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Անջատված է"</string>
@@ -128,7 +128,7 @@
<string name="call_settings_with_label" msgid="8460230435361579511">"Կարգավորումներ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="error_updating_title" msgid="2024290892676808965">"Զանգի կարգավորումների սխալ"</string>
<string name="reading_settings" msgid="1605904432450871183">"Ընթերցման կարգավորումներ..."</string>
- <string name="updating_settings" msgid="3650396734816028808">"Կարգավորումների թարմացում..."</string>
+ <string name="updating_settings" msgid="3650396734816028808">"Թարմացվում են կարգավորումները..."</string>
<string name="reverting_settings" msgid="7378668837291012205">"Կարգավորւմները հետադարձվում են..."</string>
<string name="response_error" msgid="3904481964024543330">"Անակնկալ պատասխան ցանցից:"</string>
<string name="exception_error" msgid="330994460090467">"Ցանցի կամ SIM քարտի սխալ"</string>
@@ -141,7 +141,7 @@
<string name="close_dialog" msgid="1074977476136119408">"Լավ"</string>
<string name="enable" msgid="2636552299455477603">"Միացնել"</string>
<string name="disable" msgid="1122698860799462116">"Անջատել"</string>
- <string name="change_num" msgid="6982164494063109334">"Թարմացնել"</string>
+ <string name="change_num" msgid="6982164494063109334">"Նորացնել"</string>
<string-array name="clir_display_values">
<item msgid="8477364191403806960">"Կանխադրված ցանց"</item>
<item msgid="6813323051965618926">"Թաքցնել համարը"</item>
@@ -276,7 +276,7 @@
<string name="roaming" msgid="1576180772877858949">"Ռոումինգ"</string>
<string name="roaming_enable" msgid="6853685214521494819">"Միանալ տվյալների փոխանցման ծառայություններին ռոումինգում"</string>
<string name="roaming_disable" msgid="8856224638624592681">"Միանալ տվյալների փոխանցման ծառայություններին ռոումինգում"</string>
- <string name="roaming_reenable_message" msgid="1951802463885727915">"Ռոումինգում բջջային ինտերնետն անջատված է: Հպեք՝ միացնելու համար:"</string>
+ <string name="roaming_reenable_message" msgid="1951802463885727915">"Ռոումինգում բջջային ինտերնետն անջատած է: Հպեք՝ միացնելու համար:"</string>
<string name="roaming_enabled_message" msgid="9022249120750897">"Ռոումինգի համար կարող է գումար գանձվել: Հպեք՝ փոփոխելու համար:"</string>
<string name="roaming_notification_title" msgid="3590348480688047320">"Բջջային ինտերնետը կորավ"</string>
<string name="roaming_on_notification_title" msgid="7451473196411559173">"Ինտերնետ ռոումինգը միացված է"</string>
@@ -307,7 +307,9 @@
<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_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.format failed for translation -->
+ <!-- no translation found for throttle_status_subtext (1110276415078236687) -->
+ <skip />
<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>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"Շրջանի <xliff:g id="USED_0">%1$d</xliff:g>٪ -ը լրացել է\nՀաջորդ ժամանակահատվածը սկսվում է <xliff:g id="USED_1">%2$d</xliff:g> օրից (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -637,12 +639,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Այո"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Ոչ"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Անտեսել"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Հեռախոսը շտապ հետզանգի ռեժիմում է"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Մինչև <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">Շտապ հետզանգի ռեժիմը կգործի դեռ <xliff:g id="COUNT_1">%s</xliff:g> րոպե։\nԴո՞ւրս գալ։</item>
- <item quantity="other">Շտապ հետզանգի ռեժիմը կգործի դեռ <xliff:g id="COUNT_1">%s</xliff:g> րոպե։\nԴո՞ւրս գալ։</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Ծառայություն"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Կարգավորում"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Նշված չէ>"</string>
@@ -898,11 +894,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Թարմացնել"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Փոխարկել DNS ստուգումը"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-հատուկ տեղեկություններ/կարգավորումներ"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC-ն հասանելի է՝"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR-ը սահմանափակված է՝"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR-ը հասանելի է՝"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR-ի կարգավիճակը՝"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR-ի հաճախականությունը՝"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Կարգավորել հաճախությունների շերտի ռեժիմը"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Շերտերի ցուցակը բեռնվում է…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Կարգավորել"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index cdc1f8a..e27ba14 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -135,7 +135,7 @@
<string name="stk_cc_ss_to_dial_error" msgid="5147693491690618704">"Permintaan SS diubah ke panggilan reguler"</string>
<string name="stk_cc_ss_to_ussd_error" msgid="8330749347425752192">"Permintaan SS diubah ke permintaan USSD"</string>
<string name="stk_cc_ss_to_ss_error" msgid="8297155544652134278">"Diubah ke permintaan SS baru"</string>
- <string name="stk_cc_ss_to_dial_video_error" msgid="4255261231466032505">"Permintaan SS diubah ke panggilan video"</string>
+ <string name="stk_cc_ss_to_dial_video_error" msgid="4255261231466032505">"Permintaan SS diubah ke video call"</string>
<string name="fdn_check_failure" msgid="1833769746374185247">"Setelan Nomor Panggilan Tetap aplikasi Ponsel Anda diaktifkan. Akibatnya, beberapa fitur yang terkait dengan panggilan tidak bekerja."</string>
<string name="radio_off_error" msgid="8321564164914232181">"Keluar dari mode pesawat sebelum melihat setelan ini."</string>
<string name="close_dialog" msgid="1074977476136119408">"Oke"</string>
@@ -152,7 +152,7 @@
<string name="fw_change_failed" msgid="9179241823460192148">"Tidak dapat mengubah nomor penerusan.\nHubungi operator Anda jika masalah ini terus berlanjut."</string>
<string name="fw_get_in_vm_failed" msgid="2432678237218183844">"Tidak dapat mengambil dan menyimpan setelan nomor telepon penerusan saat ini.\nTetap beralih ke operator baru?"</string>
<string name="no_change" msgid="3737264882821031892">"Tidak ada perubahan yang dilakukan."</string>
- <string name="sum_voicemail_choose_provider" msgid="6750824719081403773">"Pilih layanan pesan suara"</string>
+ <string name="sum_voicemail_choose_provider" msgid="6750824719081403773">"Pilih layanan kotak pesan"</string>
<string name="voicemail_default" msgid="6427575113775462077">"Operator Anda"</string>
<string name="vm_change_pin_old_pin" msgid="7154951790929009241">"PIN Lama"</string>
<string name="vm_change_pin_new_pin" msgid="2656200418481288069">"PIN Baru"</string>
@@ -185,7 +185,7 @@
<string name="choose_network_title" msgid="5335832663422653082">"Pilih jaringan"</string>
<string name="network_disconnected" msgid="8844141106841160825">"Terputus"</string>
<string name="network_connected" msgid="2760235679963580224">"Terhubung"</string>
- <string name="network_connecting" msgid="160901383582774987">"Menghubungkan..."</string>
+ <string name="network_connecting" msgid="160901383582774987">"Menyambungkan..."</string>
<string name="network_could_not_connect" msgid="6547460848093727998">"Tidak dapat tersambung"</string>
<string-array name="preferred_network_mode_choices">
<item msgid="4531933377509551889">"GSM/WCDMA lebih disukai"</item>
@@ -274,8 +274,8 @@
<string name="data_enable_summary" msgid="696860063456536557">"Izinkan penggunaan data"</string>
<string name="dialog_alert_title" msgid="5260471806940268478">"Perhatian"</string>
<string name="roaming" msgid="1576180772877858949">"Roaming"</string>
- <string name="roaming_enable" msgid="6853685214521494819">"Hubungkan ke layanan data ketika roaming"</string>
- <string name="roaming_disable" msgid="8856224638624592681">"Hubungkan ke layanan data ketika roaming"</string>
+ <string name="roaming_enable" msgid="6853685214521494819">"Sambungkan ke layanan data ketika roaming"</string>
+ <string name="roaming_disable" msgid="8856224638624592681">"Sambungkan ke layanan data ketika roaming"</string>
<string name="roaming_reenable_message" msgid="1951802463885727915">"Roaming data dinonaktifkan. Ketuk untuk mengaktifkan."</string>
<string name="roaming_enabled_message" msgid="9022249120750897">"Biaya roaming dapat berlaku. Ketuk untuk mengubah."</string>
<string name="roaming_notification_title" msgid="3590348480688047320">"Koneksi data seluler terputus"</string>
@@ -286,7 +286,7 @@
<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="data_usage_title" msgid="8438592133893837464">"Penggunaan data aplikasi"</string>
+ <string name="data_usage_title" msgid="8438592133893837464">"Penggunaan kuota 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>
<string name="carrier_settings_euicc" msgid="1190237227261337749">"Operator"</string>
@@ -299,7 +299,7 @@
<string name="sim_change_data_title" msgid="9142726786345906606">"Ubah SIM data?"</string>
<string name="sim_change_data_message" msgid="3567358694255933280">"Gunakan <xliff:g id="NEW_SIM">%1$s</xliff:g> dan bukan <xliff:g id="OLD_SIM">%2$s</xliff:g> untuk data seluler?"</string>
<string name="wifi_calling_settings_title" msgid="5800018845662016507">"Panggilan Wi-Fi"</string>
- <string name="video_calling_settings_title" msgid="342829454913266078">"Panggilan video operator"</string>
+ <string name="video_calling_settings_title" msgid="342829454913266078">"Video call 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>
@@ -446,7 +446,7 @@
<string name="tty_mode_option_summary" msgid="4770510287236494371">"Setel Mode TTY"</string>
<string name="auto_retry_mode_title" msgid="2985801935424422340">"Coba lagi otomatis"</string>
<string name="auto_retry_mode_summary" msgid="2863919925349511402">"Aktifkan mode Coba lagi otomatis"</string>
- <string name="tty_mode_not_allowed_video_call" msgid="6551976083652752815">"Perubahan Mode TTY tidak diizinkan selama panggilan video"</string>
+ <string name="tty_mode_not_allowed_video_call" msgid="6551976083652752815">"Perubahan Mode TTY tidak diizinkan selama video call"</string>
<string name="menu_add" msgid="5616487894975773141">"Tambahkan kontak"</string>
<string name="menu_edit" msgid="3593856941552460706">"Edit kontak"</string>
<string name="menu_delete" msgid="6326861853830546488">"Hapus kontak"</string>
@@ -572,7 +572,7 @@
<string name="onscreenManageCallsText" msgid="1162047856081836469">"Kelola panggilan"</string>
<string name="onscreenManageConferenceText" msgid="4700574060601755137">"Kelola konferensi"</string>
<string name="onscreenAudioText" msgid="7224226735052019986">"Audio"</string>
- <string name="onscreenVideoCallText" msgid="1743992456126258698">"Panggilan video"</string>
+ <string name="onscreenVideoCallText" msgid="1743992456126258698">"Video call"</string>
<string name="importSimEntry" msgid="3892354284082689894">"Impor"</string>
<string name="importAllSimEntries" msgid="2628391505643564007">"Impor semua"</string>
<string name="importingSimContacts" msgid="4995457122107888932">"Mengimpor kontak dari SIM"</string>
@@ -583,7 +583,7 @@
<string name="hac_mode_summary" msgid="7774989500136009881">"Aktifkan kompatibilitas alat bantu dengar"</string>
<string name="rtt_mode_title" msgid="3075948111362818043">"Panggilan pesan real-time (RTT)"</string>
<string name="rtt_mode_summary" msgid="8631541375609989562">"Izinkan pengiriman pesan pada saat panggilan suara berlangsung"</string>
- <string name="rtt_mode_more_information" msgid="587500128658756318">"RTT membantu penelepon yang tunarungu, kesulitan mendengar, memiliki gangguan berbicara, atau memerlukan lebih dari sekadar suara.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>Pelajari lebih lanjut</a>\n <br><br> - Panggilan RTT disimpan sebagai transkrip pesan\n <br> - RTT tidak tersedia untuk panggilan video"</string>
+ <string name="rtt_mode_more_information" msgid="587500128658756318">"RTT membantu penelepon yang tunarungu, kesulitan mendengar, memiliki gangguan berbicara, atau memerlukan lebih dari sekadar suara.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>Pelajari lebih lanjut</a>\n <br><br> - Panggilan RTT disimpan sebagai transkrip pesan\n <br> - RTT tidak tersedia untuk video call"</string>
<string name="no_rtt_when_roaming" msgid="5268008247378355389">"Catatan: RTT tidak tersedia saat melakukan roaming"</string>
<string-array name="tty_mode_entries">
<item msgid="3238070884803849303">"TTY Nonaktif"</item>
@@ -621,28 +621,22 @@
<string name="ota_next" msgid="2041016619313475914">"Berikutnya"</string>
<string name="ecm_exit_dialog" msgid="4200691880721429078">"EcmExitDialog"</string>
<string name="phone_entered_ecm_text" msgid="8431238297843035842">"Masukkan Mode Telepon Balik Darurat"</string>
- <string name="phone_in_ecm_notification_title" msgid="6825016389926367946">"Mode Telepon Balik Darurat"</string>
+ <string name="phone_in_ecm_notification_title" msgid="6825016389926367946">"Mode Panggilan Balik Darurat"</string>
<string name="phone_in_ecm_call_notification_text" msgid="653972232922670335">"Sambungan data dinonaktifkan"</string>
<string name="phone_in_ecm_notification_complete_time" msgid="7341624337163082759">"Tidak ada koneksi data sampai <xliff:g id="COMPLETETIME">%s</xliff:g>"</string>
<plurals name="alert_dialog_exit_ecm" formatted="false" msgid="5425906903766466743">
- <item quantity="other">Ponsel ini akan berada dalam mode Telepon Balik Darurat selama <xliff:g id="COUNT_1">%s</xliff:g> menit. Saat dalam mode ini, aplikasi yang menggunakan sambungan data tidak dapat digunakan. Ingin keluar sekarang?</item>
- <item quantity="one">Ponsel ini akan berada dalam mode Telepon Balik Darurat selama <xliff:g id="COUNT_0">%s</xliff:g> menit. Saat dalam mode ini, aplikasi yang menggunakan sambungan data tidak dapat digunakan. Ingin keluar sekarang?</item>
+ <item quantity="other">Ponsel ini akan berada dalam mode Panggilan Balik Darurat selama <xliff:g id="COUNT_1">%s</xliff:g> menit. Saat dalam mode ini, aplikasi yang menggunakan sambungan data tidak dapat digunakan. Ingin keluar sekarang?</item>
+ <item quantity="one">Ponsel ini akan berada dalam mode Panggilan Balik Darurat selama <xliff:g id="COUNT_0">%s</xliff:g> menit. Saat dalam mode ini, aplikasi yang menggunakan sambungan data tidak dapat digunakan. Ingin keluar sekarang?</item>
</plurals>
<plurals name="alert_dialog_not_avaialble_in_ecm" formatted="false" msgid="1152682528741457004">
- <item quantity="other">Tindakan yang dipilih tidak tersedia saat dalam mode Telepon Balik Darurat. Ponsel ini akan berada dalam mode ini selama <xliff:g id="COUNT_1">%s</xliff:g> menit. Ingin keluar sekarang?</item>
- <item quantity="one">Tindakan yang dipilih tidak tersedia saat dalam mode Telepon Balik Darurat. Ponsel ini akan berada dalam mode ini selama <xliff:g id="COUNT_0">%s</xliff:g> menit. Ingin keluar sekarang?</item>
+ <item quantity="other">Tindakan yang dipilih tidak tersedia saat dalam mode Panggilan Balik Darurat. Ponsel ini akan berada dalam mode ini selama <xliff:g id="COUNT_1">%s</xliff:g> menit. Ingin keluar sekarang?</item>
+ <item quantity="one">Tindakan yang dipilih tidak tersedia saat dalam mode Panggilan Balik Darurat. Ponsel ini akan berada dalam mode ini selama <xliff:g id="COUNT_0">%s</xliff:g> menit. Ingin keluar sekarang?</item>
</plurals>
<string name="alert_dialog_in_ecm_call" msgid="1207545603149771978">"Tindakan yang dipilih tidak tersedia ketika dalam panggilan darurat."</string>
<string name="progress_dialog_exiting_ecm" msgid="9159080081676927217">"Keluar dari mode Telepon Balik Darurat"</string>
<string name="alert_dialog_yes" msgid="3532525979632841417">"Ya"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Tidak"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Tutup"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Ponsel berada dalam mode telepon balik darurat."</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Sampai <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">Ponsel akan berada dalam mode telepon balik darurat selama <xliff:g id="COUNT_1">%s</xliff:g> menit.\nApakah Anda ingin keluar sekarang?</item>
- <item quantity="one">Ponsel akan berada dalam mode telepon balik darurat selama <xliff:g id="COUNT_0">%s</xliff:g> menit.\nApakah Anda ingin keluar sekarang?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Layanan"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Penyiapan"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Tidak disetel>"</string>
@@ -660,8 +654,8 @@
<string name="voicemail_change_pin_dialog_title" msgid="4633077715231764435">"Ubah PIN"</string>
<string name="preference_category_ringtone" msgid="8787281191375434976">"Nada dering & Getar"</string>
<string name="pstn_connection_service_label" msgid="9200102709997537069">"Kartu SIM internal"</string>
- <string name="enable_video_calling_title" msgid="7246600931634161830">"Aktifkan panggilan video"</string>
- <string name="enable_video_calling_dialog_msg" msgid="7141478720386203540">"Untuk mengaktifkan panggilan video, Anda perlu mengaktifkan Mode LTE 4G yang Ditingkatkan di setelan jaringan."</string>
+ <string name="enable_video_calling_title" msgid="7246600931634161830">"Aktifkan video call"</string>
+ <string name="enable_video_calling_dialog_msg" msgid="7141478720386203540">"Untuk mengaktifkan video call, Anda perlu mengaktifkan Mode LTE 4G yang Ditingkatkan di setelan jaringan."</string>
<string name="enable_video_calling_dialog_settings" msgid="8697890611305307110">"Setelan jaringan"</string>
<string name="enable_video_calling_dialog_close" msgid="4298929725917045270">"Tutup"</string>
<string name="sim_label_emergency_calls" msgid="9078241989421522310">"Panggilan darurat"</string>
@@ -676,8 +670,8 @@
<string name="callFailed_cdma_call_limit" msgid="1074219746093031412">"Ada terlalu banyak panggilan aktif. Akhiri atau gabungkan panggilan yang ada sebelum melakukan panggilan baru."</string>
<string name="callFailed_imei_not_accepted" msgid="7257903653685147251">"Tidak dapat tersambung, masukkan kartu SIM yang valid."</string>
<string name="callFailed_wifi_lost" msgid="1788036730589163141">"Sambungan Wi-Fi terputus. Panggilan diakhiri."</string>
- <string name="dialFailed_low_battery" msgid="6857904237423407056">"Panggilan video tidak bisa dilakukan karena daya baterai lemah."</string>
- <string name="callFailed_low_battery" msgid="4056828320214416182">"Panggilan video berakhir karena daya baterai lemah."</string>
+ <string name="dialFailed_low_battery" msgid="6857904237423407056">"Video call tidak bisa dilakukan karena daya baterai lemah."</string>
+ <string name="callFailed_low_battery" msgid="4056828320214416182">"Video call berakhir karena daya baterai lemah."</string>
<string name="callFailed_emergency_call_over_wfc_not_available" msgid="5944309590693432042">"Panggilan darurat melalui panggilan Wi-Fi tidak tersedia di lokasi ini."</string>
<string name="callFailed_wfc_service_not_available_in_this_location" msgid="3624536608369524988">"Panggilan Wi-Fi tidak tersedia di lokasi ini."</string>
<string name="change_pin_title" msgid="3564254326626797321">"Ubah PIN Pesan Suara"</string>
@@ -823,7 +817,7 @@
<string name="radio_info_data_connection_enable" msgid="6183729739783252840">"Aktifkan Koneksi Data"</string>
<string name="radio_info_data_connection_disable" msgid="6404751291511368706">"Nonaktifkan Koneksi Data"</string>
<string name="volte_provisioned_switch_string" msgid="4812874990480336178">"VoLTE Disediakan"</string>
- <string name="vt_provisioned_switch_string" msgid="8295542122512195979">"Panggilan Video Disediakan"</string>
+ <string name="vt_provisioned_switch_string" msgid="8295542122512195979">"Video Call Disediakan"</string>
<string name="wfc_provisioned_switch_string" msgid="3835004640321078988">"Panggilan Wi-Fi Disediakan"</string>
<string name="eab_provisioned_switch_string" msgid="4449676720736033035">"EAB/Presence Disediakan"</string>
<string name="cbrs_data_switch_string" msgid="6060356430838077653">"Data Cbrs"</string>
@@ -842,7 +836,7 @@
<string name="radio_info_ims_reg_status_not_registered" msgid="8045821447288876085">"Tidak Terdaftar"</string>
<string name="radio_info_ims_feature_status_available" msgid="6493200914756969292">"Tersedia"</string>
<string name="radio_info_ims_feature_status_unavailable" msgid="8930391136839759778">"Tidak tersedia"</string>
- <string name="radio_info_ims_reg_status" msgid="25582845222446390">"Pendaftaran IMS: <xliff:g id="STATUS">%1$s</xliff:g>\nSuara melalui LTE: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nSuara melalui Wi-Fi: <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nPanggilan Video: <xliff:g id="AVAILABILITY_2">%4$s</xliff:g>\nAntarmuka UT: <xliff:g id="AVAILABILITY_3">%5$s</xliff:g>"</string>
+ <string name="radio_info_ims_reg_status" msgid="25582845222446390">"Pendaftaran IMS: <xliff:g id="STATUS">%1$s</xliff:g>\nSuara melalui LTE: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nSuara melalui Wi-Fi: <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nVideo Call: <xliff:g id="AVAILABILITY_2">%4$s</xliff:g>\nAntarmuka UT: <xliff:g id="AVAILABILITY_3">%5$s</xliff:g>"</string>
<string name="radioInfo_service_in" msgid="45753418231446400">"Dalam Layanan"</string>
<string name="radioInfo_service_out" msgid="287972405416142312">"Di Luar Area Layanan"</string>
<string name="radioInfo_service_emergency" msgid="4763879891415016848">"Panggilan Darurat Saja"</string>
@@ -875,7 +869,7 @@
<string name="radio_info_roaming_label" msgid="6636932886446857120">"Roaming:"</string>
<string name="radio_info_imei_label" msgid="8947899706930120368">"IMEI"</string>
<string name="radio_info_call_redirect_label" msgid="4526480903023362276">"Pengalihan Panggilan:"</string>
- <string name="radio_info_ppp_resets_label" msgid="9131901102339077661">"Jumlah Reset PPP Sejak Boot:"</string>
+ <string name="radio_info_ppp_resets_label" msgid="9131901102339077661">"Jumlah Setel Ulang PPP Sejak Boot:"</string>
<string name="radio_info_current_network_label" msgid="3052098695239642450">"Jaringan Saat Ini:"</string>
<string name="radio_info_ppp_received_label" msgid="5753592451640644889">"Data Diterima:"</string>
<string name="radio_info_gsm_service_label" msgid="6443348321714241328">"Layanan Suara:"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Perbarui"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Aktifkan Pemeriksaan DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Info spesifik OEM/Setelan"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC Tersedia:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"Dibatasi DCNR:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR Tersedia:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Status NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Frekuensi NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Setel Mode Band Radio"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Memuat Daftar Band…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Setel"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 7521fda..6c46864 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -108,7 +108,7 @@
<string name="sum_cfu_enabled" msgid="5806923046528144526">"Áframsendir öll símtöl í <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfu_enabled_no_number" msgid="7287752761743377930">"Ekki næst í númerið"</string>
<string name="sum_cfu_disabled" msgid="5010617134210809853">"Slökkt"</string>
- <string name="labelCFB" msgid="615265213360512768">"Þegar það er á tali"</string>
+ <string name="labelCFB" msgid="615265213360512768">"Þegar er á tali"</string>
<string name="messageCFB" msgid="1958017270393563388">"Númeraval þegar síminn er á tali"</string>
<string name="sum_cfb_enabled" msgid="332037613072049492">"Áframsendir í <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfb_disabled" msgid="3589913334164866035">"Slökkt"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Já"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Nei"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Hunsa"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Síminn er í stillingu fyrir svarhringingu neyðarsímtala"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Til <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">Síminn verður í stillingu fyrir svarhringingu neyðarsímtala í <xliff:g id="COUNT_1">%s</xliff:g> mínútu.\nViltu hætta núna?</item>
- <item quantity="other">Síminn verður í stillingu fyrir svarhringingu neyðarsímtala í <xliff:g id="COUNT_1">%s</xliff:g> mínútur.\nViltu hætta núna?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Þjónusta"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Uppsetning"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Ekki valið>"</string>
@@ -783,7 +777,7 @@
<string name="call_barring_change_pwd" msgid="1730691950940338387">"Breyta aðgangsorði"</string>
<string name="call_barring_change_pwd_description" msgid="1274245130382054227">"Breyta aðgangsorði fyrir útilokun símtala"</string>
<string name="call_barring_change_pwd_description_disabled" msgid="2911647051915343920">"Ekki var hægt að breyta aðgangsorði fyrir útilokun símtala"</string>
- <string name="call_barring_pwd_not_match" msgid="7638198747579019826">"Aðgangsorðin stemma ekki"</string>
+ <string name="call_barring_pwd_not_match" msgid="7638198747579019826">"Aðgangsorðin stemma ekki."</string>
<string name="call_barring_right_pwd_number" msgid="3860630926460851330">"Sláðu inn aðgangsorð sem inniheldur fjóra tölustafi."</string>
<string name="call_barring_change_pwd_success" msgid="1837437691277936903">"Aðgangsorði breytt"</string>
<string name="call_barring_old_pwd" msgid="5500085633281388281">"Eldra aðgangsorð"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Endurnýja"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Kveikja/slökkva á DNS-prófun"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Upplýsingar/stillingar framleiðanda"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC tiltækt:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR takmarkað:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR tiltækt:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR staða:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR tíðni:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Velja útvarpstíðni"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Hleður lista yfir tíðnisvið…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Stilla"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 987dd0a..e6f6150 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -95,7 +95,7 @@
<string name="sum_loading_settings" msgid="434063780286688775">"Caricamento impostazioni…"</string>
<string name="sum_hide_caller_id" msgid="131100328602371933">"Numero nascosto per chiamate in uscita"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"Numero visualizzato in chiamate in uscita"</string>
- <string name="sum_default_caller_id" msgid="1767070797135682959">"Usa le impostazioni predefinite dell\'operatore per mostrare il mio numero nelle chiamate in uscita"</string>
+ <string name="sum_default_caller_id" msgid="1767070797135682959">"Usa impostazioni dell\'operatore per mostrare il mio numero nelle chiamate in uscita"</string>
<string name="labelCW" msgid="8449327023861428622">"Avviso di chiamata"</string>
<string name="sum_cw_enabled" msgid="3977308526187139996">"Notifica chiamate in entrata durante telefonata"</string>
<string name="sum_cw_disabled" msgid="3658094589461768637">"Notifica chiamate in entrata durante telefonata"</string>
@@ -307,7 +307,9 @@
<string name="throttle_time_frame" msgid="1813452485948918791">"Periodo utilizzo dati"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Norme velocità dati"</string>
<string name="throttle_help" msgid="2624535757028809735">"Ulteriori informazioni"</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>٪) del <xliff:g id="USED_2">%3$s</xliff:g> max periodo\nIl periodo succ. inizia tra <xliff:g id="USED_3">%4$d</xliff:g> gg (<xliff:g id="USED_4">%5$s</xliff:g>)"</string>
+ <!-- String.format failed for translation -->
+ <!-- no translation found for throttle_status_subtext (1110276415078236687) -->
+ <skip />
<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>٪) del <xliff:g id="USED_2">%3$s</xliff:g> max periodo"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"<xliff:g id="USED_0">%1$s</xliff:g> max superato\nVelocità dati ridotta a <xliff:g id="USED_1">%2$d</xliff:g> Kb/s"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g>٪ del ciclo trascorso\nIl periodo succ. inizia tra <xliff:g id="USED_1">%2$d</xliff:g> gg (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -637,12 +639,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Sì"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"No"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Ignora"</string>
- <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="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>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Non impostato>"</string>
@@ -898,11 +894,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Aggiorna"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Attiva o disattiva verifica DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Info/impostazioni specifiche OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC disponibile:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR con limitazioni:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR disponibile:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Stato NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Frequenza NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Imposta modalità banda radio"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Caricamento elenco bande…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Imposta"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 9da01de..4780834 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -307,7 +307,7 @@
<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_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_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> Kb לשנייה"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g>?? מהמחזור חלפו\nהתקופה הבאה מתחילה בעוד <xliff:g id="USED_1">%2$d</xliff:g> ימים (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -606,7 +606,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>
@@ -641,14 +641,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"כן"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"לא"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"סגור"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"הטלפון במצב \'התקשרות בחזרה בחירום\'"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"עד <xliff:g id="COMPLETETIME">%s</xliff:g>"</string>
- <plurals name="alert_dialog_exit_ecm_without_data_restriction_hint" formatted="false" msgid="6477733043040328640">
- <item quantity="two">הטלפון יהיה במצב \'התקשרות בחזרה בחירום\' ל-<xliff:g id="COUNT_1">%s</xliff:g> דקות.\nרוצה לצאת מהמצב הזה?</item>
- <item quantity="many">הטלפון יהיה במצב \'התקשרות בחזרה בחירום\' ל-<xliff:g id="COUNT_1">%s</xliff:g> דקות.\nרוצה לצאת מהמצב הזה?</item>
- <item quantity="other">הטלפון יהיה במצב \'התקשרות בחזרה בחירום\' ל-<xliff:g id="COUNT_1">%s</xliff:g> דקות.\nרוצה לצאת מהמצב הזה?</item>
- <item quantity="one">הטלפון יהיה במצב \'התקשרות בחזרה בחירום\' לדקה אחת (<xliff:g id="COUNT_0">%s</xliff:g>).\nרוצה לצאת מהמצב הזה?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"שירות"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"הגדרות"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<לא מוגדר>"</string>
@@ -904,11 +896,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"רענון"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"החלפת מצב של בדיקת DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"מידע/הגדרות ספציפיים ל-OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"תומך ב-EN-DC:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"לא תומך ב-5G NR:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"תומך ב-5G NR:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"מצב 5G NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"תדירות 5G NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"הגדרת מצב תדרים של רדיו"</string>
<string name="band_mode_loading" msgid="795923726636735967">"טוען רשימת תדרים…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"הגדרה"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 6bfe82e..3177d31 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -49,7 +49,7 @@
<string name="add_vm_number_str" msgid="7368168964435881637">"番号を追加"</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_ndp" msgid="7617392683877410341">"SIMネットワークのロック解除PIN"</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>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"はい"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"いいえ"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"解除"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"スマートフォンが緊急通報待機モードになっています"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">スマートフォンが <xliff:g id="COUNT_1">%s</xliff:g> 分間にわたって緊急通報待機モードになります。\n今すぐ終了してもよろしいですか?</item>
- <item quantity="one">スマートフォンが <xliff:g id="COUNT_0">%s</xliff:g> 分間にわたって緊急通報待機モードになります。\n今すぐ終了してもよろしいですか?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"サービス"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"セットアップ"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<未設定>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"更新"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS チェックを切り替え"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM 固有の情報 / 設定"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC 利用可能:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR 制限あり:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR 利用可能:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR ステータス:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR 周波数:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"無線バンドモードの設定"</string>
<string name="band_mode_loading" msgid="795923726636735967">"バンドリストを読み込み中…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"設定"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 4d26062..3c3249b 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -74,7 +74,7 @@
<string name="phone_accounts_configure_account_settings" msgid="6622119715253196586">"ანგარიშის პარამეტრების კონფიგურაცია"</string>
<string name="phone_accounts_all_calling_accounts" msgid="1609600743500618823">"დარეკვის ყველა ანგარიში"</string>
<string name="phone_accounts_all_calling_accounts_summary" msgid="2214134955430107240">"აირჩიეთ რომელ ანგარიშს შეეძლება ზარების განხორციელება"</string>
- <string name="wifi_calling" msgid="3650509202851355742">"Wi-Fi დარეკვა"</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>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"დიახ"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"არა"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"იგნორირება"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"ტელეფონი გადაუდებელი გადმორეკვის რეჟიმშია"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">ტელეფონი <xliff:g id="COUNT_1">%s</xliff:g> წუთის განმავლობაში გადაუდებელი გადმორეკვის რეჟიმში იქნება.\nგსურთ გასვლა ახლავე?</item>
- <item quantity="one">ტელეფონი <xliff:g id="COUNT_0">%s</xliff:g> წუთის განმავლობაში გადაუდებელი გადმორეკვის რეჟიმში იქნება.\nგსურთ გასვლა ახლავე?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"სერვისი"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"დაყენება"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<არ არის დაყენებული>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"განახლება"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS შემოწმების გადართვა"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"ინფორმაცია/პარამეტრები სპეციალურად OEM-ისთვის"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC ხელმისაწვდომია:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR შეზღუდულია:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR ხელმისაწვდომია:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR-ის მდგომარეობა:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR-ის სიხშირე:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"რადიოდიაპაზონის რეჟიმის დაყენება"</string>
<string name="band_mode_loading" msgid="795923726636735967">"მიმდინარეობს დიაპაზონთა სიის ჩატვირთვა…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"დაყენება"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index b451e36..39fb4c6 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -96,7 +96,7 @@
<string name="sum_hide_caller_id" msgid="131100328602371933">"Шығыс қоңырауларда нөмірді жасыру"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"Шығыс қоңыраулар нөмірді көрсету"</string>
<string name="sum_default_caller_id" msgid="1767070797135682959">"Шығыс қоңырауларында менің нөмірім көрсетілуі үшін бастапқы оператор параметрлерін қолдану"</string>
- <string name="labelCW" msgid="8449327023861428622">"Қоңырауды ұстап тұру"</string>
+ <string name="labelCW" msgid="8449327023861428622">"Күтудегі қоңырау"</string>
<string name="sum_cw_enabled" msgid="3977308526187139996">"Қоңырау кезінде маған келген қоңыраулар жайлы хабарлау"</string>
<string name="sum_cw_disabled" msgid="3658094589461768637">"Қоңырау кезінде маған келген қоңыраулар жайлы хабарлау"</string>
<string name="call_forwarding_settings" msgid="8937130467468257671">"Қоңырауды басқа нөмірге бағыттау параметрлері"</string>
@@ -277,7 +277,7 @@
<string name="roaming_enable" msgid="6853685214521494819">"Роуминг кезінде дерек тасымалдау қызметтеріне қосылу"</string>
<string name="roaming_disable" msgid="8856224638624592681">"Роуминг кезінде дерек тасымалдау қызметтеріне қосылу"</string>
<string name="roaming_reenable_message" msgid="1951802463885727915">"Роуминг өшірулі. Қосу үшін түртіңіз."</string>
- <string name="roaming_enabled_message" msgid="9022249120750897">"Роуминг үшін ақы алынуы мүмкін. Өзгерту үшін түртіңіз."</string>
+ <string name="roaming_enabled_message" msgid="9022249120750897">"Роуминг ақылары алынуы мүмкін. Өзгерту үшін түртіңіз."</string>
<string name="roaming_notification_title" msgid="3590348480688047320">"Ұялы байланыс жоғалып кетті"</string>
<string name="roaming_on_notification_title" msgid="7451473196411559173">"Деректер роумингі қосулы"</string>
<string name="roaming_warning" msgid="7855681468067171971">"Қомақты ақы алынуы мүмкін."</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Иә"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Жоқ"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Бас тарту"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Телефон шұғыл кері қоңырау шалу режимінде"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">Телефон <xliff:g id="COUNT_1">%s</xliff:g> минут бойы шұғыл кері қоңырау шалу режимінде болады.\nҚазір шыққыңыз келе ме?</item>
- <item quantity="one">Телефон <xliff:g id="COUNT_0">%s</xliff:g> минут бойы шұғыл кері қоңырау шалу режимінде болады.\nҚазір шыққыңыз келе ме?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Қызмет"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Орнату"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Реттелген жоқ>"</string>
@@ -664,7 +658,7 @@
<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>
+ <string name="sim_label_emergency_calls" msgid="9078241989421522310">"Жедел қоңыраулар"</string>
<string name="sim_description_emergency_calls" msgid="5146872803938897296">"Тек жедел қоңыраулар"</string>
<string name="sim_description_default" msgid="7474671114363724971">"SIM картасы, ұяшық: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
<string name="accessibility_settings_activity_title" msgid="7883415189273700298">"Арнайы мүмкіндіктер"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Жаңарту"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS тексерісін қосу/өшіру"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Өндірушіге қатысты ақпарат/параметрлер"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC қолжетімді:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR шектеулі:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR қолжетімді:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR күйі:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR жиілігі:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Радиожолақ режимін орнату"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Жолақтар тізімі жүктелуде…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Орнату"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index d8bd447..610a133 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -24,7 +24,7 @@
<string name="unknown" msgid="8279698889921830815">"មិនស្គាល់"</string>
<string name="private_num" msgid="4487990167889159992">"លេខឯកជន"</string>
<string name="payphone" msgid="7936735771836716941">"ទូរស័ព្ទសាធារណៈ"</string>
- <string name="onHold" msgid="6132725550015899006">"កំពុងរង់ចាំ"</string>
+ <string name="onHold" msgid="6132725550015899006">"រង់ចាំ"</string>
<string name="carrier_mmi_msg_title" msgid="6050165242447507034">"សារ <xliff:g id="MMICARRIER">%s</xliff:g>"</string>
<string name="default_carrier_mmi_msg_title" msgid="7754317179938537213">"សារក្រុមហ៊ុនសេវាទូរសព្ទ"</string>
<string name="mmiStarted" msgid="9212975136944568623">"បានចាប់ផ្ដើមកូដ MMI"</string>
@@ -91,7 +91,7 @@
<string name="additional_cdma_call_settings" msgid="2178016561980611304">"កំណត់ការហៅ CDMA បន្ថែម"</string>
<string name="sum_cdma_call_settings" msgid="3185825305136993636">"កំណត់ការហៅតែ CDMA បន្ថែមប៉ុណ្ណោះ"</string>
<string name="labelNwService" msgid="6015891883487125120">"ការកំណត់សេវាបណ្ដាញ"</string>
- <string name="labelCallerId" msgid="2090540744550903172">"អត្តសញ្ញាណអ្នកហៅទូរសព្ទ"</string>
+ <string name="labelCallerId" msgid="2090540744550903172">"លេខសម្គាល់អ្នកហៅ"</string>
<string name="sum_loading_settings" msgid="434063780286688775">"កំពុងផ្ទុកការកំណត់…"</string>
<string name="sum_hide_caller_id" msgid="131100328602371933">"បានលាក់លេខក្នុងការហៅចេញ"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"បានបង្ហាញលេខក្នុងការហៅចេញ"</string>
@@ -99,8 +99,8 @@
<string name="labelCW" msgid="8449327023861428622">"រង់ចាំការហៅ"</string>
<string name="sum_cw_enabled" msgid="3977308526187139996">"ក្នុងអំឡុងពេលហៅ ជូនដំណឹងដល់ខ្ញុំអំពីការហៅចូល"</string>
<string name="sum_cw_disabled" msgid="3658094589461768637">"ក្នុងអំឡុងពេលហៅ ជូនដំណឹងដល់ខ្ញុំអំពីការហៅចូល"</string>
- <string name="call_forwarding_settings" msgid="8937130467468257671">"ការកំណត់ការបញ្ជូនបន្តការហៅទូរសព្ទ"</string>
- <string name="call_forwarding_settings_with_label" msgid="2345432813399564272">"ការកំណត់ការបញ្ជូនបន្តការហៅទូរសព្ទ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
+ <string name="call_forwarding_settings" msgid="8937130467468257671">"កំណត់ការហៅបញ្ជូនបន្ត"</string>
+ <string name="call_forwarding_settings_with_label" msgid="2345432813399564272">"ការកំណត់ការបញ្ជូនការហៅបន្ត (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="labelCF" msgid="3578719437928476078">"បញ្ជូនការហៅបន្ត"</string>
<string name="labelCFU" msgid="8870170873036279706">"បញ្ជូនបន្តជានិច្ច"</string>
<string name="messageCFU" msgid="1361806450979589744">"ប្រើលេខនេះជានិច្ច"</string>
@@ -128,7 +128,7 @@
<string name="call_settings_with_label" msgid="8460230435361579511">"ការកំណត់ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="error_updating_title" msgid="2024290892676808965">"បញ្ហាការកំណត់ការហៅ"</string>
<string name="reading_settings" msgid="1605904432450871183">"កំពុងអានការកំណត់…"</string>
- <string name="updating_settings" msgid="3650396734816028808">"កំពុងធ្វើបច្ចុប្បន្នភាពការកំណត់…"</string>
+ <string name="updating_settings" msgid="3650396734816028808">"កំណត់ការធ្វើបច្ចុប្បន្នភាព…"</string>
<string name="reverting_settings" msgid="7378668837291012205">"ត្រឡប់ការកំណត់…"</string>
<string name="response_error" msgid="3904481964024543330">"ការឆ្លើយតបដែលមិនរំពឹងទុកពីបណ្ដាញ។"</string>
<string name="exception_error" msgid="330994460090467">"បញ្ហាស៊ីមកាត ឬបណ្ដាញ។"</string>
@@ -298,7 +298,7 @@
<string name="sim_selection_required_pref" msgid="6985901872978341314">"ជម្រើសដែលត្រូវមាន"</string>
<string name="sim_change_data_title" msgid="9142726786345906606">"ប្ដូរស៊ីមទិន្នន័យ?"</string>
<string name="sim_change_data_message" msgid="3567358694255933280">"ប្រើប្រាស់ <xliff:g id="NEW_SIM">%1$s</xliff:g> ជំនួសឲ្យ <xliff:g id="OLD_SIM">%2$s</xliff:g> សម្រាប់ទិន្នន័យទូរសព្ទចល័តដែរឬទេ?"</string>
- <string name="wifi_calling_settings_title" msgid="5800018845662016507">"ការហៅតាម Wi-Fi"</string>
+ <string name="wifi_calling_settings_title" msgid="5800018845662016507">"ការហៅទូរសព្ទតាម Wi-Fi"</string>
<string name="video_calling_settings_title" msgid="342829454913266078">"ការហៅវីដេអូតាមក្រុមផ្ដល់សេវាទូរសព្ទ"</string>
<string name="gsm_umts_options" msgid="4968446771519376808">"ជម្រើស GSM/UMTS"</string>
<string name="cdma_options" msgid="3669592472226145665">"ជម្រើស CDMA"</string>
@@ -513,7 +513,7 @@
<string name="card_title_conf_call" msgid="901197309274457427">"ការហៅជាក្រុម"</string>
<string name="card_title_incoming_call" msgid="881424648458792430">"ការហៅចូល"</string>
<string name="card_title_call_ended" msgid="650223980095026340">"បានបញ្ចប់ការហៅ"</string>
- <string name="card_title_on_hold" msgid="9028319436626975207">"កំពុងរង់ចាំ"</string>
+ <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>
@@ -564,7 +564,7 @@
<string name="dialerKeyboardHintText" msgid="1115266533703764049">"ប្រើក្ដារចុចដើម្បីចុចលេខ"</string>
<string name="onscreenHoldText" msgid="4025348842151665191">"ដាក់ឱ្យរង់ចាំ"</string>
<string name="onscreenEndCallText" msgid="6138725377654842757">"បញ្ចប់"</string>
- <string name="onscreenShowDialpadText" msgid="658465753816164079">"ផ្ទាំងចុចលេខ"</string>
+ <string name="onscreenShowDialpadText" msgid="658465753816164079">"បន្ទះលេខ"</string>
<string name="onscreenMuteText" msgid="5470306116733843621">"ស្ងាត់"</string>
<string name="onscreenAddCallText" msgid="9075675082903611677">"បន្ថែមការហៅ"</string>
<string name="onscreenMergeCallsText" msgid="3692389519611225407">"បញ្ចូលការហៅរួមគ្នា"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"បាទ/ចាស"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"ទេ"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"បោះបង់"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"ទូរសព្ទស្ថិតក្នុងមុខងារហៅទៅវិញពេលមានអាសន្ន"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"រហូតដល់ម៉ោង <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">ទូរសព្ទនឹងស្ថិតក្នុងមុខងារហៅទៅវិញពេលមានអាសន្នរយៈពេល <xliff:g id="COUNT_1">%s</xliff:g> នាទី។\nតើអ្នកចង់ចេញឥឡូវនេះដែរឬទេ?</item>
- <item quantity="one">ទូរសព្ទនឹងស្ថិតក្នុងមុខងារហៅទៅវិញពេលមានអាសន្នរយៈពេល <xliff:g id="COUNT_0">%s</xliff:g> នាទី។\nតើអ្នកចង់ចេញឥឡូវនេះដែរឬទេ?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"សេវា"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"រៀបចំ"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"< មិនកំណត់ >"</string>
@@ -845,7 +839,7 @@
<string name="radio_info_ims_reg_status" msgid="25582845222446390">"ការចុះឈ្មោះ IMS៖ <xliff:g id="STATUS">%1$s</xliff:g>\nការហៅជាសំឡេងតាម LTE៖ <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nការហៅជាសំឡេងតាម WiFi៖ <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nការហៅជាវីដេអូ៖ <xliff:g id="AVAILABILITY_2">%4$s</xliff:g>\nផ្ទៃ UT ៖ <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>
+ <string name="radioInfo_service_emergency" msgid="4763879891415016848">"សម្រាប់តែការហៅទៅលេខសង្គ្រោះបន្ទាន់ប៉ុណ្ណោះ"</string>
<string name="radioInfo_service_off" msgid="3456583511226783064">"វិទ្យុបានបិទ"</string>
<string name="radioInfo_roaming_in" msgid="3156335577793145965">"រ៉ូមីង"</string>
<string name="radioInfo_roaming_not" msgid="1904547918725478110">"មិនរ៉ូមីងទេ"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"ផ្ទុកឡើងវិញ"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"បិទ/បើកការពិនិត្យ DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"ការកំណត់/ព័ត៌មានជាក់លាក់ OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"មាន EN-DC៖"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR ត្រូវបានរឹតត្បិត៖"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"មាន NR៖"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"ស្ថានភាព NR៖"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"កម្រិតញឹកញាប់នៃ NR៖"</string>
<string name="band_mode_title" msgid="7988822920724576842">"កំណត់មុខងារកម្រិតបញ្ជូនវិទ្យុ"</string>
<string name="band_mode_loading" msgid="795923726636735967">"កំពុងដំណើរការបញ្ជីក្រុម…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"កំណត់"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index d2fd35f..5dc6cdd 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -128,7 +128,7 @@
<string name="call_settings_with_label" msgid="8460230435361579511">"ಸೆಟ್ಟಿಂಗ್ಗಳು (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="error_updating_title" msgid="2024290892676808965">"ಕರೆ ಸೆಟ್ಟಿಂಗ್ಗಳ ದೋಷ"</string>
<string name="reading_settings" msgid="1605904432450871183">"ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಓದಲಾಗುತ್ತಿದೆ…"</string>
- <string name="updating_settings" msgid="3650396734816028808">"ಸೆಟ್ಟಿಂಗ್ಗಳು ಅಪ್ಡೇಟ್ ಆಗುತ್ತಿವೆ…"</string>
+ <string name="updating_settings" msgid="3650396734816028808">"ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ನವೀಕರಿಸಲಾಗುತ್ತಿದೆ…"</string>
<string name="reverting_settings" msgid="7378668837291012205">"ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಹಿಂತಿರುಗಿಸಲಾಗುತ್ತಿದೆ…"</string>
<string name="response_error" msgid="3904481964024543330">"ನೆಟ್ವರ್ಕ್ನಿಂದ ಅನಿರೀಕ್ಷಿತ ಪ್ರತಿಕ್ರಿಯೆ."</string>
<string name="exception_error" msgid="330994460090467">"ನೆಟ್ವರ್ಕ್ ಅಥವಾ ಸಿಮ್ ಕಾರ್ಡ್ ದೋಷ."</string>
@@ -141,7 +141,7 @@
<string name="close_dialog" msgid="1074977476136119408">"ಸರಿ"</string>
<string name="enable" msgid="2636552299455477603">"ಆನ್ ಮಾಡಿ"</string>
<string name="disable" msgid="1122698860799462116">"ಆಫ್ ಮಾಡು"</string>
- <string name="change_num" msgid="6982164494063109334">"ಅಪ್ಡೇಟ್ ಮಾಡಿ"</string>
+ <string name="change_num" msgid="6982164494063109334">"ಅಪ್ಡೇಟ್ ಮಾಡು"</string>
<string-array name="clir_display_values">
<item msgid="8477364191403806960">"ನೆಟ್ವರ್ಕ್ ಡಿಫಾಲ್ಟ್"</item>
<item msgid="6813323051965618926">"ಸಂಖ್ಯೆಯನ್ನು ಮರೆಮಾಡು"</item>
@@ -185,7 +185,7 @@
<string name="choose_network_title" msgid="5335832663422653082">"ನೆಟ್ವರ್ಕ್ ಆಯ್ಕೆಮಾಡಿ"</string>
<string name="network_disconnected" msgid="8844141106841160825">"ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="network_connected" msgid="2760235679963580224">"ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string>
- <string name="network_connecting" msgid="160901383582774987">"ಕನೆಕ್ಟ್ ಆಗುತ್ತಿದೆ..."</string>
+ <string name="network_connecting" msgid="160901383582774987">"ಸಂಪರ್ಕಿಸಲಾಗುತ್ತಿದೆ..."</string>
<string name="network_could_not_connect" msgid="6547460848093727998">"ಸಂಪರ್ಕಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ"</string>
<string-array name="preferred_network_mode_choices">
<item msgid="4531933377509551889">"GSM/WCDMA ಗೆ ಪ್ರಾಶಸ್ತ್ಯ ನೀಡಲಾಗಿದೆ"</item>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"ಹೌದು"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"ಇಲ್ಲ"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"ವಜಾಗೊಳಿಸಿ"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"ಫೋನ್, ತುರ್ತು ಕಾಲ್ಬ್ಯಾಕ್ ಮೋಡ್ನಲ್ಲಿದೆ"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">ಫೋನ್ <xliff:g id="COUNT_1">%s</xliff:g> ನಿಮಿಷಗಳವರೆಗೆ ತುರ್ತು ಕಾಲ್ಬ್ಯಾಕ್ ಮೋಡ್ನಲ್ಲಿರುತ್ತದೆ.\n ನೀವು ಈಗಲೇ ನಿರ್ಗಮಿಸಲು ಬಯಸುತ್ತೀರಾ?</item>
- <item quantity="other">ಫೋನ್ <xliff:g id="COUNT_1">%s</xliff:g> ನಿಮಿಷಗಳವರೆಗೆ ತುರ್ತು ಕಾಲ್ಬ್ಯಾಕ್ ಮೋಡ್ನಲ್ಲಿರುತ್ತದೆ.\n ನೀವು ಈಗಲೇ ನಿರ್ಗಮಿಸಲು ಬಯಸುತ್ತೀರಾ?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"ಸೇವೆ"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"ಸೆಟಪ್"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<ಹೊಂದಿಸಿಲ್ಲ>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"ರಿಫ್ರೆಶ್ ಮಾಡಿ"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS ಪರಿಶೀಲನೆ ಟಾಗಲ್ ಮಾಡಿ"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-ನಿರ್ದಿಷ್ಟ ಮಾಹಿತಿ/ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC ಲಭ್ಯವಿದೆ:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"ನಿರ್ಬಂಧಿತ DCNR:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR ಲಭ್ಯವಿದೆ:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR ಸ್ಥಿತಿ:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR ಫ್ರೀಕ್ವೆನ್ಸಿ:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"ರೇಡಿಯೋ ಬ್ಯಾಂಡ್ ಮೋಡ್ ಹೊಂದಿಸಿ"</string>
<string name="band_mode_loading" msgid="795923726636735967">"ಬ್ಯಾಂಡ್ ಪಟ್ಟಿಯನ್ನು ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"ಹೊಂದಿಸಿ"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 462af54..3729c99 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -511,7 +511,7 @@
<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>
- <string name="card_title_incoming_call" msgid="881424648458792430">"수신 전화"</string>
+ <string name="card_title_incoming_call" msgid="881424648458792430">"수신전화"</string>
<string name="card_title_call_ended" msgid="650223980095026340">"통화 종료됨"</string>
<string name="card_title_on_hold" msgid="9028319436626975207">"대기 중"</string>
<string name="card_title_hanging_up" msgid="814874106866647871">"전화 끊는 중"</string>
@@ -572,7 +572,7 @@
<string name="onscreenManageCallsText" msgid="1162047856081836469">"통화 관리"</string>
<string name="onscreenManageConferenceText" msgid="4700574060601755137">"다자간 통화 관리"</string>
<string name="onscreenAudioText" msgid="7224226735052019986">"오디오"</string>
- <string name="onscreenVideoCallText" msgid="1743992456126258698">"영상 통화"</string>
+ <string name="onscreenVideoCallText" msgid="1743992456126258698">"화상 통화"</string>
<string name="importSimEntry" msgid="3892354284082689894">"가져오기"</string>
<string name="importAllSimEntries" msgid="2628391505643564007">"모두 가져오기"</string>
<string name="importingSimContacts" msgid="4995457122107888932">"SIM 주소록 가져오는 중"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"예"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"아니요"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"해제"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"휴대전화가 긴급 콜백 모드입니다."</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">휴대전화가 <xliff:g id="COUNT_1">%s</xliff:g>분간 긴급 콜백 모드로 유지됩니다.\n지금 종료하시겠습니까?</item>
- <item quantity="one">휴대전화가 <xliff:g id="COUNT_0">%s</xliff:g>분간 긴급 콜백 모드로 유지됩니다.\n지금 종료하시겠습니까?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"서비스"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"설정"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<설정 안됨>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"새로고침"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS 확인 전환"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM별 정보/설정"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC 사용 가능:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR 제한됨:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR 사용 가능:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR 상태:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR 빈도:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"무선 주파수 대역 모드 설정"</string>
<string name="band_mode_loading" msgid="795923726636735967">"대역 목록 로드 중…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"설정"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 01454c4..f0804b1 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -48,7 +48,7 @@
<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="puk_unlocked" msgid="4627340655215746511">"SIM картаңыз бөгөттөн чыгарылган. Телефонуңуздун кулпусу ачылууда…"</string>
+ <string name="puk_unlocked" msgid="4627340655215746511">"SIM-картаңыз бөгөттөн чыгарылган. Телефонуңуздун кулпусу ачылууда…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM-карта тармагынын кулпусун ачуучу PIN код"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Кулпусун ачуу"</string>
<string name="sim_ndp_dismiss_text" msgid="89667342248929777">"Этибарга албоо"</string>
@@ -170,7 +170,7 @@
<string name="empty_networks_list" msgid="9216418268008582342">"Эч тармак табылган жок."</string>
<string name="network_query_error" msgid="3862515805115145124">"Тармактар табылбай койду. Кайталап көрүңүз."</string>
<string name="register_on_network" msgid="4194770527833960423">"<xliff:g id="NETWORK">%s</xliff:g> тармагына катталууда…"</string>
- <string name="not_allowed" msgid="8541221928746104798">"Сиздин SIM картаңыз бул түйүнгө кошулганга жол бербейт."</string>
+ <string name="not_allowed" msgid="8541221928746104798">"Сиздин SIM-картаңыз бул түйүнгө кошулганга жол бербейт."</string>
<string name="connect_later" msgid="1950138106010005425">"Бул түйүнгө азыр кошулуу мүмкүн эмес. Бир аздан соң кайра аракеттениңиз."</string>
<string name="registration_done" msgid="5337407023566953292">"Тармакка катталды."</string>
<string name="already_auto" msgid="8607068290733079336">"Мурунтан эле автоматтык түрдө тандоодо."</string>
@@ -238,7 +238,7 @@
<string name="preferred_network_mode_lte_cdma_evdo_gsm_wcdma_summary" msgid="6707224437925495615">"Тандалган тармак режими: LTE/CDMA/EvDo/GSM/WCDMA"</string>
<string name="preferred_network_mode_global_summary" msgid="3847086258439582411">"Тандалган тармак режими: Дүйнө жүзү"</string>
<string name="preferred_network_mode_lte_wcdma_summary" msgid="7001804022020813865">"Артыкчылыктуу желе тартиби: LTE / WCDMA"</string>
- <string name="preferred_network_mode_lte_gsm_umts_summary" msgid="6484203890156282179">"Жаккан режим: LTE / GSM / UMTS"</string>
+ <string name="preferred_network_mode_lte_gsm_umts_summary" msgid="6484203890156282179">"Жактырылган режим: LTE / GSM / UMTS"</string>
<string name="preferred_network_mode_lte_cdma_summary" msgid="8187929456614068518">"Тандалган тармак режими: LTE / CDMA"</string>
<string name="preferred_network_mode_tdscdma_summary" msgid="3602127224234207206">"Тандалган тармак режими: TDSCDMA"</string>
<string name="preferred_network_mode_tdscdma_wcdma_summary" msgid="7076968749402201123">"Тандалган тармак режими: TDSCDMA / WCDMA"</string>
@@ -274,8 +274,8 @@
<string name="data_enable_summary" msgid="696860063456536557">"Дайындарды пайдаланууга уруксат берүү"</string>
<string name="dialog_alert_title" msgid="5260471806940268478">"Көңүл буруңуз"</string>
<string name="roaming" msgid="1576180772877858949">"Роуминг"</string>
- <string name="roaming_enable" msgid="6853685214521494819">"Роуминг учурунда маалыматтарды өткөрүүчү кызматтарга туташасыз"</string>
- <string name="roaming_disable" msgid="8856224638624592681">"Роуминг учурунда маалыматтарды өткөрүүчү кызматтарга туташасыз"</string>
+ <string name="roaming_enable" msgid="6853685214521494819">"Роуминг учурунда мобилдик Интернетке туташат"</string>
+ <string name="roaming_disable" msgid="8856224638624592681">"Роуминг учурунда мобилдик Интернетке туташат"</string>
<string name="roaming_reenable_message" msgid="1951802463885727915">"Интернет-роуминг өчүрүлгөн. Күйгүзүү үчүн басыңыз."</string>
<string name="roaming_enabled_message" msgid="9022249120750897">"Роуминг акысын төлөп калышыңыз мүмкүн. Өзгөртүү үчүн таптап коюңуз."</string>
<string name="roaming_notification_title" msgid="3590348480688047320">"Мобилдик интернет туташуусу үзүлдү"</string>
@@ -285,7 +285,7 @@
<string name="roaming_alert_title" msgid="5689615818220960940">"Интернет-роумингди иштетесизби?"</string>
<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="limited_sim_function_notification_message" msgid="5338638075496721160">"Башка SIM-картаны колднгндо <xliff:g id="CARRIER_NAME">%1$s</xliff:g> чалуулары жана дайындар кызмттары бөгөттлшү мүмкүн."</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>
@@ -400,13 +400,13 @@
<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>
- <string name="network_recommended" msgid="3444321100580250926">" (сунушталат)"</string>
+ <string name="network_recommended" msgid="3444321100580250926">" (сунушталган)"</string>
<string name="network_lte" msgid="7206879277095094280">"LTE (сунушталат)"</string>
<string name="network_4G" msgid="6800527815504223913">"4G (сунушталат)"</string>
<string name="network_global" msgid="3289646154407617631">"Дүйнө жүзү"</string>
- <string name="cdma_system_select_title" msgid="614165233552656431">"Системаны тандоо"</string>
+ <string name="cdma_system_select_title" msgid="614165233552656431">"Тутум тандоо"</string>
<string name="cdma_system_select_summary" msgid="3840420390242060407">"CDMA роуминг тартибин алмаштыруу"</string>
- <string name="cdma_system_select_dialogtitle" msgid="5524639510676501802">"Системаны тандоо"</string>
+ <string name="cdma_system_select_dialogtitle" msgid="5524639510676501802">"Тутум тандоо"</string>
<string-array name="cdma_system_select_choices">
<item msgid="462340042928284921">"Башкы бет гана"</item>
<item msgid="6058010046783562674">"Автоматтык"</item>
@@ -464,9 +464,9 @@
<string name="delete_fdn_contact" msgid="7027405651994507077">"Туруктуу терүү номерин жок кылуу"</string>
<string name="deleting_fdn_contact" msgid="6872320570844460428">"Туруктуу терүү номери жок кылынууда…"</string>
<string name="fdn_contact_deleted" msgid="1680714996763848838">"Туруктуу терүү номери өчүрүлдү."</string>
- <string name="pin2_invalid" msgid="2313954262684494442">"БНТ жаңырган жок, анткени туура эмес PIN-код киргизилди."</string>
- <string name="fdn_invalid_number" msgid="9067189814657840439">"Уруксат берилген номер жаңырган жок, себеби жазылган номердин саны <xliff:g id="FDN_NUMBER_LIMIT_LENGTH">%d</xliff:g> ашпашы керек."</string>
- <string name="pin2_or_fdn_invalid" msgid="7542639487955868181">"БНТ жаңырган жок. PIN2 туура эмес, же телефон номуру жараксыз."</string>
+ <string name="pin2_invalid" msgid="2313954262684494442">"БНТ жаңыртылган жок, анткени туура эмес PIN-код киргизилди."</string>
+ <string name="fdn_invalid_number" msgid="9067189814657840439">"Уруксат берилген номер жаңыртылган жок, себеби жазылган номердин саны <xliff:g id="FDN_NUMBER_LIMIT_LENGTH">%d</xliff:g> ашпашы керек."</string>
+ <string name="pin2_or_fdn_invalid" msgid="7542639487955868181">"БНТ жаңыртылган жок. PIN2 туура эмес, же телефон номуру жараксыз."</string>
<string name="fdn_failed" msgid="216592346853420250">"БНТ иши кыйрады."</string>
<string name="simContacts_emptyLoading" msgid="4989040293858675483">"SIM-картадан окулууда…"</string>
<string name="simContacts_empty" msgid="1135632055473689521">"SIM картаңызда байланыштар жок."</string>
@@ -520,7 +520,7 @@
<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_network_selection_title" msgid="255595526707809121">"Интернет жок"</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="default" msgid="8131672264311208673">"Чалуу үчүн учак режимин өчүрүңүз."</string>
@@ -543,7 +543,7 @@
<string name="incall_error_supp_service_hold" msgid="8535056414643540997">"Чалууну кармап туруу мүмкүн эмес."</string>
<string name="incall_error_wfc_only_no_wireless_network" msgid="5860742792811400109">"Чалуу үчүн зымсыз тармакка туташыңыз."</string>
<string name="incall_error_promote_wfc" msgid="9164896813931363415">"Wi-Fi аркылуу чалыңыз."</string>
- <string name="emergency_information_hint" msgid="9208897544917793012">"Кырсыктаганда керек болчу маалымат"</string>
+ <string name="emergency_information_hint" msgid="9208897544917793012">"Өзгөчө кырдаал маалыматы"</string>
<string name="emergency_information_owner_hint" msgid="6256909888049185316">"Ээси"</string>
<string name="emergency_information_confirm_hint" msgid="5109017615894918914">"Маалыматты көрүү үчүн кайра таптап коюңуз"</string>
<string name="emergency_enable_radio_dialog_title" msgid="2667568200755388829">"Шашылыш чалуу"</string>
@@ -575,7 +575,7 @@
<string name="onscreenVideoCallText" msgid="1743992456126258698">"Видео чалуу"</string>
<string name="importSimEntry" msgid="3892354284082689894">"Импорттоо"</string>
<string name="importAllSimEntries" msgid="2628391505643564007">"Баарын импорттоо"</string>
- <string name="importingSimContacts" msgid="4995457122107888932">"SIM картадагы байланыштардан импорттоо"</string>
+ <string name="importingSimContacts" msgid="4995457122107888932">"SIM-картадагы байланыштардан импорттоо"</string>
<string name="importToFDNfromContacts" msgid="5068664870738407341">"Байланыштардан импорттоо"</string>
<string name="singleContactImportedMsg" msgid="3619804066300998934">"Байланыш импорттолду"</string>
<string name="failedToImportSingleContactMsg" msgid="228095510489830266">"Байланыш импорттолбой калды"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Ооба"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Жок"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Этибарга албоо"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Телефон шашылыш кайра чалуу режиминде"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">Телефон шашылыш кайра чалуу режиминде <xliff:g id="COUNT_1">%s</xliff:g> мүнөт бою болот.\nАзыр чыгып кетесизби?</item>
- <item quantity="one">Телефон шашылыш кайра чалуу режиминде <xliff:g id="COUNT_0">%s</xliff:g> мүнөт бою болот.\nАзыр чыгып кетесизби?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Кызмат"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Жөндөө"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Коюлган эмес>"</string>
@@ -661,7 +655,7 @@
<string name="preference_category_ringtone" msgid="8787281191375434976">"Рингтон жана Титирөө"</string>
<string name="pstn_connection_service_label" msgid="9200102709997537069">"Кыналган SIM карталар"</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>
@@ -674,7 +668,7 @@
<string name="message_decode_error" msgid="1061856591500290887">"Билдирүү дешифрленип жатканда ката кеткен."</string>
<string name="callFailed_cdma_activation" msgid="5392057031552253550">"SIM-карта кызматыңызды жандырып, телефонуңуздун роуминг мүмкүнчүлүктөрүн жаңыртты."</string>
<string name="callFailed_cdma_call_limit" msgid="1074219746093031412">"Учурда жигердүү чалуулар өтө көп. Чалуу үчүн учурдагы чалууларды бүтүрүңүз же бириктириңиз."</string>
- <string name="callFailed_imei_not_accepted" msgid="7257903653685147251">"Байланышуу мумкүн эмес. Жарактуу SIM картаны салыңыз."</string>
+ <string name="callFailed_imei_not_accepted" msgid="7257903653685147251">"Байланышуу мумкүн эмес. Жарактуу SIM-картаны салыңыз."</string>
<string name="callFailed_wifi_lost" msgid="1788036730589163141">"Wi-Fi туташуусу үзүлүп калды. Чалуу аяктады."</string>
<string name="dialFailed_low_battery" msgid="6857904237423407056">"Батареянын заряды төмөн болгондуктан, видео режиминде чала албайсыз."</string>
<string name="callFailed_low_battery" msgid="4056828320214416182">"Батареянын заряды төмөн болгондуктан, видео чалуу аяктады."</string>
@@ -703,7 +697,7 @@
<string name="mobile_data_activate_roaming_plan_summary" msgid="5379228493306235969">"<xliff:g id="PROVIDER_NAME">%s</xliff:g> оператору аркылуу роуминг планын күйгүзүңүз"</string>
<string name="mobile_data_activate_footer" msgid="7895874069807204548">"Сиз мобилдик Интернетти же роуминг планын <xliff:g id="PROVIDER_NAME">%s</xliff:g> операторуңуз аркылуу кошо аласыз."</string>
<string name="mobile_data_activate_diag_title" msgid="5401741936224757312">"Мобилдик Интернет күйгүзүлсүнбү?"</string>
- <string name="mobile_data_activate_diag_message" msgid="3527260988020415441">"Мобилдик интернетти <xliff:g id="PROVIDER_NAME">%s</xliff:g> оператордун жардамы менен, күйгүзүү керек болушу мүмкүн"</string>
+ <string name="mobile_data_activate_diag_message" msgid="3527260988020415441">"Мобилдик интернетти <xliff:g id="PROVIDER_NAME">%s</xliff:g> оператордун жардамы менен күйгүзүү керек болушу мүмкүн"</string>
<string name="mobile_data_activate_button" msgid="1139792516354374612">"ДАЙЫНДАРДЫ КОШУУ"</string>
<string name="mobile_data_activate_cancel_button" msgid="3530174817572005860">"ЖОККО ЧЫГАРУУ"</string>
<string name="clh_card_title_call_ended_txt" msgid="5977978317527299698">"Чалуу аяктады"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Жаңылоо"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS текшерүүнү которуштуруу"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM\'ге тиешелүү Маалымат/Жөндөөлөр"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC жеткиликтүү:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR чектелген:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR жеткиликтүү:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR абалы:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR жыштыгы:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Радио жыштыгынын режимин коюу"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Жыштык режиминин тизмеси жүктөлүүдө…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Жөндөө"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 08947eb..dbfcfd6 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"ຕົກລົງ"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"ບໍ່"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"ປິດໄວ້"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"ໂທລະສັບຢູ່ໃນໂໝດໂທກັບສຸກເສີນ"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"ຈົນຮອດ <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">ໂທລະສັບຈະຢູ່ໃນໂໝດໂທກັບສຸກເສີນເປັນເວລາ <xliff:g id="COUNT_1">%s</xliff:g> ນາທີ.\nທ່ານຕ້ອງການອອກຕອນນີ້ເລີຍບໍ?</item>
- <item quantity="one">ໂທລະສັບຈະຢູ່ໃນໂໝດໂທກັບສຸກເສີນເປັນເວລາ <xliff:g id="COUNT_0">%s</xliff:g> ນາທີ.\nທ່ານຕ້ອງການອອກຕອນນີ້ເລີຍບໍ?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"ບໍລິການ"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"ຕັ້ງຄ່າ"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<ຍັງບໍ່ໄດ້ຕັ້ງ>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"ໂຫຼດຂໍ້ມູນໃໝ່"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"ເປີດ/ປິດ ການກວດ DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"ຂໍ້ມູນ/ການຕັ້ງຄ່າສະເພາະ OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"ມີ EN-DC:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"ຈຳກັດ DCNR ໄວ້ແລ້ວ:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"ມີ NR:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"ສະຖານະ NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"ຄວາມຖີ່ NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"ຕັ້ງໂໝດແຖບຄວາມຖີ່ວິທະຍຸ"</string>
<string name="band_mode_loading" msgid="795923726636735967">"ກຳລັງດາວໂຫລດລາຍຊື່ແຖບຄວາມຖີ່..."</string>
<string name="band_mode_set" msgid="6657819412803771421">"ກຳນົດ"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 4760728..ddb18fb 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -127,7 +127,7 @@
<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>
<string name="error_updating_title" msgid="2024290892676808965">"Skambinimo nustatymų klaida"</string>
- <string name="reading_settings" msgid="1605904432450871183">"Analizuojami nustatymai..."</string>
+ <string name="reading_settings" msgid="1605904432450871183">"Skaitomi nustatymai..."</string>
<string name="updating_settings" msgid="3650396734816028808">"Atnaujinami nustatymai..."</string>
<string name="reverting_settings" msgid="7378668837291012205">"Grąžinami nustatymai…"</string>
<string name="response_error" msgid="3904481964024543330">"Netikėtas atsakas iš tinklo."</string>
@@ -641,14 +641,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Taip"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Ne"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Atsisakyti"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Telefone veikia atgalinio skambinimo pagalbos numeriu režimas"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Iki <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">Telefone <xliff:g id="COUNT_1">%s</xliff:g> minutę veiks atgalinio skambinimo pagalbos numeriu režimas.\nAr norite jį išjungti dabar?</item>
- <item quantity="few">Telefone <xliff:g id="COUNT_1">%s</xliff:g> minutes veiks atgalinio skambinimo pagalbos numeriu režimas.\nAr norite jį išjungti dabar?</item>
- <item quantity="many">Telefone <xliff:g id="COUNT_1">%s</xliff:g> minutės veiks atgalinio skambinimo pagalbos numeriu režimas.\nAr norite jį išjungti dabar?</item>
- <item quantity="other">Telefone <xliff:g id="COUNT_1">%s</xliff:g> minučių veiks atgalinio skambinimo pagalbos numeriu režimas.\nAr norite jį išjungti dabar?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Paslaugos teikėjas"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Sąranka"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Nenustatyta>"</string>
@@ -904,11 +896,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Atnaujinti"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Kaitalioti DNS tikrinimą"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM būdinga informacija / nustatymai"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC (pasiekiama):"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR (apribota):"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR (pasiekiama):"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR būsena:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR dažnis:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Nustatyti radijo dažnių režimą"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Įkeliamas dažnių sąrašas…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Nustatyti"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 0ba2739..8161b61 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -522,7 +522,7 @@
<string name="notification_voicemail_no_vm_number" msgid="3423686009815186750">"Balss pasta numurs nav zināms."</string>
<string name="notification_network_selection_title" msgid="255595526707809121">"Nav pakalpojuma"</string>
<string name="notification_network_selection_text" msgid="553288408722427659">"Atlasītais tīkls (<xliff:g id="OPERATOR_NAME">%s</xliff:g>) nav pieejams"</string>
- <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="watch" msgid="7191184639454113633">"Lai veiktu zvanu, ieslēdziet mobilo tīklu, izslēdziet lidojuma režīmu vai izslēdziet akumulatora jaudas 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_ecm_emergency_only" msgid="5622379058883722080">"Lai veiktu parastu zvanu, izejiet no ārkārtas atzvana režīma."</string>
@@ -639,13 +639,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Jā"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Nē"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Noraidīt"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Tālrunis atrodas ārkārtas atzvana režīmā."</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Līdz plkst. <xliff:g id="COMPLETETIME">%s</xliff:g>"</string>
- <plurals name="alert_dialog_exit_ecm_without_data_restriction_hint" formatted="false" msgid="6477733043040328640">
- <item quantity="zero">Tālrunis atradīsies ārkārtas atzvana režīmā <xliff:g id="COUNT_1">%s</xliff:g> minūtes.\nVai vēlaties iziet tūlīt?</item>
- <item quantity="one">Tālrunis atradīsies ārkārtas atzvana režīmā <xliff:g id="COUNT_1">%s</xliff:g> minūti.\nVai vēlaties iziet tūlīt?</item>
- <item quantity="other">Tālrunis atradīsies ārkārtas atzvana režīmā <xliff:g id="COUNT_1">%s</xliff:g> minūtes.\nVai vēlaties iziet tūlīt?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Pakalpojums"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Iestatīšana"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Nav iestatīts>"</string>
@@ -901,11 +894,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Atsvaidzināt"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Pārslēgt DNS pārbaudi"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM raksturīga informācija/iestatījumi"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC pieejamība:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR ierobežojums:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR pieejamība:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR statuss:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR biežums:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Radio frekvenču joslu režīma iestatīšana"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Notiek joslu saraksta ielāde…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Iestatīt"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 39492e2..1167e3d 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -74,7 +74,7 @@
<string name="phone_accounts_configure_account_settings" msgid="6622119715253196586">"Конфигурирајте ги поставките на сметка"</string>
<string name="phone_accounts_all_calling_accounts" msgid="1609600743500618823">"Сите сметки за повици"</string>
<string name="phone_accounts_all_calling_accounts_summary" msgid="2214134955430107240">"Изберете кои сметки може да повикуваат"</string>
- <string name="wifi_calling" msgid="3650509202851355742">"Повик преку Wi-Fi"</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>
@@ -84,7 +84,7 @@
<string name="smart_forwarding_settings_menu_summary" msgid="5096947726032885325">"Кога едниот број е недостапен, секогаш проследувајте ги повиците на вашиот друг број"</string>
<string name="voicemail_notifications_preference_title" msgid="7829238858063382977">"Известувања"</string>
<string name="cell_broadcast_settings" msgid="8135324242541809924">"Итни емитувања"</string>
- <string name="call_settings" msgid="3677282690157603818">"Поставки за повици"</string>
+ <string name="call_settings" msgid="3677282690157603818">"Поставки на повик"</string>
<string name="additional_gsm_call_settings" msgid="1561980168685658846">"Дополнителни поставки"</string>
<string name="additional_gsm_call_settings_with_label" msgid="7973920539979524908">"Дополнителни поставки (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="sum_gsm_call_settings" msgid="7964692601608878138">"Дополнителни поставки на повик само за GSM"</string>
@@ -123,11 +123,11 @@
<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="updating_title" msgid="6130548922615719689">"Поставки за повици"</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>
<string name="error_updating_title" msgid="2024290892676808965">"Грешка со поставки на повици"</string>
- <string name="reading_settings" msgid="1605904432450871183">"Се читаат поставките..."</string>
+ <string name="reading_settings" msgid="1605904432450871183">"Поставки за читање..."</string>
<string name="updating_settings" msgid="3650396734816028808">"Ажурирање поставки..."</string>
<string name="reverting_settings" msgid="7378668837291012205">"Враќање поставки..."</string>
<string name="response_error" msgid="3904481964024543330">"Неочекуван одговор од мрежата."</string>
@@ -168,7 +168,7 @@
<string name="label_available" msgid="1316084116670821258">"Достапни мрежи"</string>
<string name="load_networks_progress" msgid="4051433047717401683">"Се пребарува..."</string>
<string name="empty_networks_list" msgid="9216418268008582342">"Не се пронајдени мрежи."</string>
- <string name="network_query_error" msgid="3862515805115145124">"Не може да се најдат мрежи. Обидете се повторно."</string>
+ <string name="network_query_error" msgid="3862515805115145124">"Не можеше да се најдат мрежи. Обидете се повторно."</string>
<string name="register_on_network" msgid="4194770527833960423">"Регистрирање на <xliff:g id="NETWORK">%s</xliff:g>..."</string>
<string name="not_allowed" msgid="8541221928746104798">"Вашата SIM картичка не дозволува поврзување со оваа мрежа."</string>
<string name="connect_later" msgid="1950138106010005425">"Не може да се поврзе со оваа мрежа во моментов. Обидете се повторно подоцна."</string>
@@ -183,10 +183,10 @@
<string name="preferred_network_mode_dialogtitle" msgid="2781447433514459696">"Претпочитан тип мрежа"</string>
<string name="forbidden_network" msgid="5081729819561333023">"(забрането)"</string>
<string name="choose_network_title" msgid="5335832663422653082">"Изберете мрежа"</string>
- <string name="network_disconnected" msgid="8844141106841160825">"Не е поврзано"</string>
+ <string name="network_disconnected" msgid="8844141106841160825">"Исклучено"</string>
<string name="network_connected" msgid="2760235679963580224">"Поврзано"</string>
<string name="network_connecting" msgid="160901383582774987">"Се поврзува…"</string>
- <string name="network_could_not_connect" msgid="6547460848093727998">"Не може да се поврзе"</string>
+ <string name="network_could_not_connect" msgid="6547460848093727998">"Не можеше да се поврзе"</string>
<string-array name="preferred_network_mode_choices">
<item msgid="4531933377509551889">"Претпочитан: GSM/WCDMA"</item>
<item msgid="5120532750027435355">"Само GSM"</item>
@@ -286,7 +286,7 @@
<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="data_usage_title" msgid="8438592133893837464">"Потрошен интернет од апликации"</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>
<string name="carrier_settings_euicc" msgid="1190237227261337749">"Оператор"</string>
@@ -298,7 +298,7 @@
<string name="sim_selection_required_pref" msgid="6985901872978341314">"Треба да се избере"</string>
<string name="sim_change_data_title" msgid="9142726786345906606">"Да се промени SIM за интернет?"</string>
<string name="sim_change_data_message" msgid="3567358694255933280">"Да се користи <xliff:g id="NEW_SIM">%1$s</xliff:g> наместо <xliff:g id="OLD_SIM">%2$s</xliff:g> за мобилен интернет?"</string>
- <string name="wifi_calling_settings_title" msgid="5800018845662016507">"Повик преку Wi-Fi"</string>
+ <string name="wifi_calling_settings_title" msgid="5800018845662016507">"Повикување преку Wi-Fi"</string>
<string name="video_calling_settings_title" msgid="342829454913266078">"Видеоповикување преку оператор"</string>
<string name="gsm_umts_options" msgid="4968446771519376808">"Опции за GSM/UMTS"</string>
<string name="cdma_options" msgid="3669592472226145665">"Опции на CDMA"</string>
@@ -307,11 +307,13 @@
<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_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.format failed for translation -->
+ <!-- no translation found for throttle_status_subtext (1110276415078236687) -->
+ <skip />
<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> Kb/s"</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>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g>٪ од циклусот помина\nНаредниот период започнува за <xliff:g id="USED_1">%2$d</xliff:g> дена (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
- <string name="throttle_rate_subtext" msgid="7221971817325779535">"Преносот на податоци се сведува на <xliff:g id="USED">%1$d</xliff:g> Kb/s ако се надмине границата на потрошен интернет"</string>
+ <string name="throttle_rate_subtext" msgid="7221971817325779535">"Преносот на податоци се сведува на <xliff:g id="USED">%1$d</xliff:g> кб/с ако се надмине границата на потрошен интернет"</string>
<string name="throttle_help_subtext" msgid="2817114897095534807">"Повеќе информации за политиката на користање податоци на мобилната мрежа на вашиот оператор"</string>
<string name="cell_broadcast_sms" msgid="4053449797289031063">"SMS за емитување од мобилен"</string>
<string name="enable_disable_cell_bc_sms" msgid="4759958924031721350">"SMS за емитување од мобилен"</string>
@@ -543,7 +545,7 @@
<string name="incall_error_supp_service_hold" msgid="8535056414643540997">"Не може да се задржат повици."</string>
<string name="incall_error_wfc_only_no_wireless_network" msgid="5860742792811400109">"Поврзете се на безжична мрежа за да повикате."</string>
<string name="incall_error_promote_wfc" msgid="9164896813931363415">"Овозможете повикување преку Wi-Fi за воспоставување повик."</string>
- <string name="emergency_information_hint" msgid="9208897544917793012">"Податоци за итни случаи"</string>
+ <string name="emergency_information_hint" msgid="9208897544917793012">"Информации за итни случаи"</string>
<string name="emergency_information_owner_hint" msgid="6256909888049185316">"Сопственик"</string>
<string name="emergency_information_confirm_hint" msgid="5109017615894918914">"Допрете повторно за приказ на информации"</string>
<string name="emergency_enable_radio_dialog_title" msgid="2667568200755388829">"Повик за итни случаи"</string>
@@ -612,7 +614,7 @@
<string name="ota_listen" msgid="2772252405488894280">"Следете ги гласовните упатства додека не слушнете дека активацијата е завршена."</string>
<string name="ota_speaker" msgid="1086766980329820528">"Звучник"</string>
<string name="ota_progress" msgid="8837259285255700132">"Телефонот се програмира…"</string>
- <string name="ota_failure" msgid="5674217489921481576">"Телефонот не може да се програмира"</string>
+ <string name="ota_failure" msgid="5674217489921481576">"Телефонот не можеше да се програмира"</string>
<string name="ota_successful" msgid="1106825981548107774">"Вашиот телефон е сега активиран. Може да поминат и до 15 минути за услугата да започне."</string>
<string name="ota_unsuccessful" msgid="8531037653803955754">"Вашиот телефон не се активираше. \nМожеби ќе треба да најде област со подобра покриеност (во близина на прозорец или надвор). \n\nОбидете се повторно или јавете се во служба за услуги на клиенти за повеќе опции."</string>
<string name="ota_spc_failure" msgid="904092035241370080">"ВИШОК SPC ГРЕШКИ"</string>
@@ -637,12 +639,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Да"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Не"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Отфрли"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Телефонот е во режим на итни повратни повици"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"До <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">Телефонот ќе биде во режим на итни повратни повици <xliff:g id="COUNT_1">%s</xliff:g> минута.\n Дали сакате да излезете сега?</item>
- <item quantity="other">Телефонот ќе биде во режим на итни повратни повици <xliff:g id="COUNT_1">%s</xliff:g> минути.\n Дали сакате да излезете сега?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Услуга"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Поставување"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Не е поставен>"</string>
@@ -711,54 +707,54 @@
<string name="clh_callFailed_simError_txt" msgid="5128538525762326413">"Не може да се пристапи до SIM-картичката"</string>
<string name="clh_incall_error_out_of_service_txt" msgid="2736010617446749869">"Мобилната мрежа не е достапна"</string>
<string name="clh_callFailed_unassigned_number_txt" msgid="141967660286695682">"Проблем со телефонскиот број што се обидувате да го бирате. Код за грешка: 1."</string>
- <string name="clh_callFailed_no_route_to_destination_txt" msgid="4805015149822352308">"Повикот не може да се заврши. Код за грешка: 3."</string>
- <string name="clh_callFailed_channel_unacceptable_txt" msgid="4062754579408613021">"Повикот не може да се заврши. Код за грешка: 6."</string>
- <string name="clh_callFailed_operator_determined_barring_txt" msgid="4202077821465974286">"Повикот не може да се заврши. Код за грешка: 8."</string>
- <string name="clh_callFailed_normal_call_clearing_txt" msgid="5677987959062976462">"Повикот не може да се заврши. Код за грешка: 16."</string>
+ <string name="clh_callFailed_no_route_to_destination_txt" msgid="4805015149822352308">"Повикот не можеше да се заврши. Код за грешка: 3."</string>
+ <string name="clh_callFailed_channel_unacceptable_txt" msgid="4062754579408613021">"Повикот не можеше да се заврши. Код за грешка: 6."</string>
+ <string name="clh_callFailed_operator_determined_barring_txt" msgid="4202077821465974286">"Повикот не можеше да се заврши. Код за грешка: 8."</string>
+ <string name="clh_callFailed_normal_call_clearing_txt" msgid="5677987959062976462">"Повикот не можеше да се заврши. Код за грешка: 16."</string>
<string name="clh_callFailed_user_busy_txt" msgid="8886432858568086854">"Корисникот е зафатен"</string>
<string name="clh_callFailed_no_user_responding_txt" msgid="341100226919865128">"Корисникот не одговара"</string>
- <string name="clh_callFailed_user_alerting_txt" msgid="896082976264427969">"Повикот не може да се заврши. Код за грешка: 19."</string>
+ <string name="clh_callFailed_user_alerting_txt" msgid="896082976264427969">"Повикот не можеше да се заврши. Код за грешка: 19."</string>
<string name="clh_callFailed_call_rejected_txt" msgid="3439435671153341709">"Повикот е одбиен"</string>
<string name="clh_callFailed_number_changed_txt" msgid="2868476949771441667">"Бројот е променет"</string>
- <string name="clh_callFailed_pre_emption_txt" msgid="8887998866342162724">"Повикот не може да се заврши. Код за грешка: 25."</string>
- <string name="clh_callFailed_non_selected_user_clearing_txt" msgid="4804529874810197550">"Повикот не може да се заврши. Код за грешка: 26."</string>
- <string name="clh_callFailed_destination_out_of_order_txt" msgid="1130697076352728824">"Повикот не може да се заврши. Код за грешка: 27."</string>
+ <string name="clh_callFailed_pre_emption_txt" msgid="8887998866342162724">"Повикот не можеше да се заврши. Код за грешка: 25."</string>
+ <string name="clh_callFailed_non_selected_user_clearing_txt" msgid="4804529874810197550">"Повикот не можеше да се заврши. Код за грешка: 26."</string>
+ <string name="clh_callFailed_destination_out_of_order_txt" msgid="1130697076352728824">"Повикот не можеше да се заврши. Код за грешка: 27."</string>
<string name="clh_callFailed_invalid_number_format_txt" msgid="3171016382987224989">"Неважечки формат на број (нецелосен број)"</string>
- <string name="clh_callFailed_facility_rejected_txt" msgid="1054386430010898993">"Повикот не може да се заврши. Код за грешка: 29."</string>
- <string name="clh_callFailed_response_to_STATUS_ENQUIRY_txt" msgid="2763172551412307536">"Повикот не може да се заврши. Код за грешка: 30."</string>
- <string name="clh_callFailed_normal_unspecified_txt" msgid="978119938935737419">"Повикот не може да се заврши. Код за грешка: 31."</string>
- <string name="clh_callFailed_no_circuit_available_txt" msgid="1519684050419134605">"Повикот не може да се заврши. Код за грешка: 34."</string>
- <string name="clh_callFailed_network_out_of_order_txt" msgid="8689826504394592289">"Повикот не може да се заврши. Код за грешка: 38."</string>
- <string name="clh_callFailed_temporary_failure_txt" msgid="5065091554509067874">"Повикот не може да се заврши. Код за грешка: 41."</string>
- <string name="clh_callFailed_switching_equipment_congestion_txt" msgid="8681599376741988769">"Повикот не може да се заврши. Код за грешка: 42."</string>
- <string name="clh_callFailed_access_information_discarded_txt" msgid="2476199425130545428">"Повикот не може да се заврши. Код за грешка: 43."</string>
- <string name="clh_callFailed_requested_circuit_txt" msgid="7497497808928490219">"Повикот не може да се заврши. Код за грешка: 44."</string>
- <string name="clh_callFailed_resources_unavailable_unspecified_txt" msgid="144010529672928445">"Повикот не може да се заврши. Код за грешка: 47."</string>
- <string name="clh_callFailed_quality_of_service_unavailable_txt" msgid="4650329342288289290">"Повикот не може да се заврши. Код за грешка: 49."</string>
- <string name="clh_callFailed_requested_facility_not_subscribed_txt" msgid="9107977008516882170">"Повикот не може да се заврши. Код за грешка: 50."</string>
- <string name="clh_callFailed_incoming_calls_barred_within_the_CUG_txt" msgid="501037491908315591">"Повикот не може да се заврши. Код за грешка: 55."</string>
- <string name="clh_callFailed_bearer_capability_not_authorized_txt" msgid="4344366517528362620">"Повикот не може да се заврши. Код за грешка: 57."</string>
- <string name="clh_callFailed_bearer_capability_not_presently_available_txt" msgid="1436957294571545381">"Повикот не може да се заврши. Код за грешка: 58."</string>
- <string name="clh_callFailed_service_or_option_not_available_unspecified_txt" msgid="2149878874722675428">"Повикот не може да се заврши. Код за грешка: 63."</string>
- <string name="clh_callFailed_bearer_service_not_implemented_txt" msgid="1074983013965612410">"Повикот не може да се заврши. Код за грешка: 65."</string>
- <string name="clh_callFailed_ACM_equal_to_or_greater_than_ACMmax_txt" msgid="7889034195264205333">"Повикот не може да се заврши. Код за грешка: 68."</string>
- <string name="clh_callFailed_requested_facility_not_implemented_txt" msgid="7996646684699167978">"Повикот не може да се заврши. Код за грешка: 69."</string>
- <string name="clh_callFailed_only_restricted_digital_information_bearer_capability_is_available_txt" msgid="2358958110447385682">"Повикот не може да се заврши. Код за грешка: 70."</string>
- <string name="clh_callFailed_service_or_option_not_implemented_unspecified_txt" msgid="3046428509531159481">"Повикот не може да се заврши. Код за грешка: 79."</string>
- <string name="clh_callFailed_invalid_transaction_identifier_value_txt" msgid="1727401871777396619">"Повикот не може да се заврши. Код за грешка: 81."</string>
- <string name="clh_callFailed_user_not_member_of_CUG_txt" msgid="442282135105229307">"Повикот не може да се заврши. Код за грешка: 87."</string>
- <string name="clh_callFailed_incompatible_destination_txt" msgid="5900394706344969020">"Повикот не може да се заврши. Код за грешка: 88."</string>
- <string name="clh_callFailed_invalid_transit_network_selection_txt" msgid="6274621838349037741">"Повикот не може да се заврши. Код за грешка: 91."</string>
- <string name="clh_callFailed_semantically_incorrect_message_txt" msgid="7000705190197981937">"Повикот не може да се заврши. Код за грешка: 95."</string>
- <string name="clh_callFailed_invalid_mandatory_information_txt" msgid="3609204152671052123">"Повикот не може да се заврши. Код за грешка: 96."</string>
- <string name="clh_callFailed_message_type_non_existent_or_not_implemented_txt" msgid="1552110431052032814">"Повикот не може да се заврши. Код за грешка: 97."</string>
- <string name="clh_callFailed_message_type_not_compatible_with_protocol_state_txt" msgid="7717048934226300032">"Повикот не може да се заврши. Код за грешка: 98."</string>
- <string name="clh_callFailed_information_element_non_existent_or_not_implemented_txt" msgid="8931396541061612169">"Повикот не може да се заврши. Код за грешка: 99."</string>
- <string name="clh_callFailed_conditional_IE_error_txt" msgid="4630685477888727741">"Повикот не може да се заврши. Код за грешка: 100."</string>
- <string name="clh_callFailed_message_not_compatible_with_protocol_state_txt" msgid="3014075977395922947">"Повикот не може да се заврши. Код за грешка: 101."</string>
- <string name="clh_callFailed_recovery_on_timer_expiry_txt" msgid="5637581978978731672">"Повикот не може да се заврши. Код за грешка: 102."</string>
- <string name="clh_callFailed_protocol_Error_unspecified_txt" msgid="9203320572562697755">"Повикот не може да се заврши. Код за грешка: 111."</string>
- <string name="clh_callFailed_interworking_unspecified_txt" msgid="7969686413930847182">"Повикот не може да се заврши. Код за грешка: 127."</string>
+ <string name="clh_callFailed_facility_rejected_txt" msgid="1054386430010898993">"Повикот не можеше да се заврши. Код за грешка: 29."</string>
+ <string name="clh_callFailed_response_to_STATUS_ENQUIRY_txt" msgid="2763172551412307536">"Повикот не можеше да се заврши. Код за грешка: 30."</string>
+ <string name="clh_callFailed_normal_unspecified_txt" msgid="978119938935737419">"Повикот не можеше да се заврши. Код за грешка: 31."</string>
+ <string name="clh_callFailed_no_circuit_available_txt" msgid="1519684050419134605">"Повикот не можеше да се заврши. Код за грешка: 34."</string>
+ <string name="clh_callFailed_network_out_of_order_txt" msgid="8689826504394592289">"Повикот не можеше да се заврши. Код за грешка: 38."</string>
+ <string name="clh_callFailed_temporary_failure_txt" msgid="5065091554509067874">"Повикот не можеше да се заврши. Код за грешка: 41."</string>
+ <string name="clh_callFailed_switching_equipment_congestion_txt" msgid="8681599376741988769">"Повикот не можеше да се заврши. Код за грешка: 42."</string>
+ <string name="clh_callFailed_access_information_discarded_txt" msgid="2476199425130545428">"Повикот не можеше да се заврши. Код за грешка: 43."</string>
+ <string name="clh_callFailed_requested_circuit_txt" msgid="7497497808928490219">"Повикот не можеше да се заврши. Код за грешка: 44."</string>
+ <string name="clh_callFailed_resources_unavailable_unspecified_txt" msgid="144010529672928445">"Повикот не можеше да се заврши. Код за грешка: 47."</string>
+ <string name="clh_callFailed_quality_of_service_unavailable_txt" msgid="4650329342288289290">"Повикот не можеше да се заврши. Код за грешка: 49."</string>
+ <string name="clh_callFailed_requested_facility_not_subscribed_txt" msgid="9107977008516882170">"Повикот не можеше да се заврши. Код за грешка: 50."</string>
+ <string name="clh_callFailed_incoming_calls_barred_within_the_CUG_txt" msgid="501037491908315591">"Повикот не можеше да се заврши. Код за грешка: 55."</string>
+ <string name="clh_callFailed_bearer_capability_not_authorized_txt" msgid="4344366517528362620">"Повикот не можеше да се заврши. Код за грешка: 57."</string>
+ <string name="clh_callFailed_bearer_capability_not_presently_available_txt" msgid="1436957294571545381">"Повикот не можеше да се заврши. Код за грешка: 58."</string>
+ <string name="clh_callFailed_service_or_option_not_available_unspecified_txt" msgid="2149878874722675428">"Повикот не можеше да се заврши. Код за грешка: 63."</string>
+ <string name="clh_callFailed_bearer_service_not_implemented_txt" msgid="1074983013965612410">"Повикот не можеше да се заврши. Код за грешка: 65."</string>
+ <string name="clh_callFailed_ACM_equal_to_or_greater_than_ACMmax_txt" msgid="7889034195264205333">"Повикот не можеше да се заврши. Код за грешка: 68."</string>
+ <string name="clh_callFailed_requested_facility_not_implemented_txt" msgid="7996646684699167978">"Повикот не можеше да се заврши. Код за грешка: 69."</string>
+ <string name="clh_callFailed_only_restricted_digital_information_bearer_capability_is_available_txt" msgid="2358958110447385682">"Повикот не можеше да се заврши. Код за грешка: 70."</string>
+ <string name="clh_callFailed_service_or_option_not_implemented_unspecified_txt" msgid="3046428509531159481">"Повикот не можеше да се заврши. Код за грешка: 79."</string>
+ <string name="clh_callFailed_invalid_transaction_identifier_value_txt" msgid="1727401871777396619">"Повикот не можеше да се заврши. Код за грешка: 81."</string>
+ <string name="clh_callFailed_user_not_member_of_CUG_txt" msgid="442282135105229307">"Повикот не можеше да се заврши. Код за грешка: 87."</string>
+ <string name="clh_callFailed_incompatible_destination_txt" msgid="5900394706344969020">"Повикот не можеше да се заврши. Код за грешка: 88."</string>
+ <string name="clh_callFailed_invalid_transit_network_selection_txt" msgid="6274621838349037741">"Повикот не можеше да се заврши. Код за грешка: 91."</string>
+ <string name="clh_callFailed_semantically_incorrect_message_txt" msgid="7000705190197981937">"Повикот не можеше да се заврши. Код за грешка: 95."</string>
+ <string name="clh_callFailed_invalid_mandatory_information_txt" msgid="3609204152671052123">"Повикот не можеше да се заврши. Код за грешка: 96."</string>
+ <string name="clh_callFailed_message_type_non_existent_or_not_implemented_txt" msgid="1552110431052032814">"Повикот не можеше да се заврши. Код за грешка: 97."</string>
+ <string name="clh_callFailed_message_type_not_compatible_with_protocol_state_txt" msgid="7717048934226300032">"Повикот не можеше да се заврши. Код за грешка: 98."</string>
+ <string name="clh_callFailed_information_element_non_existent_or_not_implemented_txt" msgid="8931396541061612169">"Повикот не можеше да се заврши. Код за грешка: 99."</string>
+ <string name="clh_callFailed_conditional_IE_error_txt" msgid="4630685477888727741">"Повикот не можеше да се заврши. Код за грешка: 100."</string>
+ <string name="clh_callFailed_message_not_compatible_with_protocol_state_txt" msgid="3014075977395922947">"Повикот не можеше да се заврши. Код за грешка: 101."</string>
+ <string name="clh_callFailed_recovery_on_timer_expiry_txt" msgid="5637581978978731672">"Повикот не можеше да се заврши. Код за грешка: 102."</string>
+ <string name="clh_callFailed_protocol_Error_unspecified_txt" msgid="9203320572562697755">"Повикот не можеше да се заврши. Код за грешка: 111."</string>
+ <string name="clh_callFailed_interworking_unspecified_txt" msgid="7969686413930847182">"Повикот не можеше да се заврши. Код за грешка: 127."</string>
<string name="labelCallBarring" msgid="4180377113052853173">"Забрана на повик"</string>
<string name="sum_call_barring_enabled" msgid="5184331188926370824">"Вклучено"</string>
<string name="sum_call_barring_disabled" msgid="5699448000600153096">"Исклучено"</string>
@@ -841,7 +837,7 @@
<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>
- <string name="radio_info_ims_feature_status_unavailable" msgid="8930391136839759778">"Недостапно"</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>\nКомуникација преку LTE: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nКомуникација преку Wi-Fi: <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>
@@ -852,7 +848,7 @@
<string name="radioInfo_phone_idle" msgid="2191653783170757819">"Мирување"</string>
<string name="radioInfo_phone_ringing" msgid="8100354169567413370">"Ѕвонење"</string>
<string name="radioInfo_phone_offhook" msgid="7564601639749936170">"Повик во тек"</string>
- <string name="radioInfo_data_disconnected" msgid="8085447971880814541">"Не е поврзано"</string>
+ <string name="radioInfo_data_disconnected" msgid="8085447971880814541">"Исклучен"</string>
<string name="radioInfo_data_connecting" msgid="925092271092152472">"Се поврзува"</string>
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Поврзан"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Суспендиран"</string>
@@ -865,8 +861,8 @@
<string name="radioInfo_cid" msgid="1423185536264406705">"CID"</string>
<string name="radio_info_subid" msgid="6839966868621703203">"Тековен SUBID:"</string>
<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_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>
@@ -898,11 +894,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Освежи"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Префрли на DNS-проверка"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Информации/Поставки карактеристични за ОЕМ"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"Достапно за EN-DC:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"Ограничено на DCNR:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"Достапно за NR:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Состојба на NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Фреквенција на NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Поставете режим на појас на радио"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Се вчитува список на појаси…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Постави"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 8ab18ac..1d65c97 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -95,11 +95,11 @@
<string name="sum_loading_settings" msgid="434063780286688775">"ക്രമീകരണങ്ങൾ ലോഡുചെയ്യുന്നു…"</string>
<string name="sum_hide_caller_id" msgid="131100328602371933">"ഔട്ട്ഗോയിംഗ് കോളുകളിൽ നമ്പർ മറച്ചിരിക്കുന്നു"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"ഔട്ട്ഗോയിംഗ് കോളുകളിൽ നമ്പർ ദൃശ്യമാക്കിയിരിക്കുന്നു"</string>
- <string name="sum_default_caller_id" msgid="1767070797135682959">"ഔട്ട്ഗോയിംഗ് കോളുകളിൽ എന്റെ നമ്പർ കാണിക്കാൻ ഡിഫോൾട്ട് ഓപ്പറേറ്റർ ക്രമീകരണങ്ങൾ ഉപയോഗിക്കുക"</string>
+ <string name="sum_default_caller_id" msgid="1767070797135682959">"ഔട്ട്ഗോയിംഗ് കോളുകളിൽ എന്റെ നമ്പർ ദൃശ്യമാക്കാൻ സ്ഥിര ഓപ്പറേറ്റർ ക്രമീകരണങ്ങൾ ഉപയോഗിക്കുക"</string>
<string name="labelCW" msgid="8449327023861428622">"കോൾ വെയ്റ്റിംഗ്"</string>
<string name="sum_cw_enabled" msgid="3977308526187139996">"ഒരു കോളിനിടയിൽ, ഇൻകമിംഗ് കോളുകളെക്കുറിച്ച് എന്നെ അറിയിക്കുക"</string>
<string name="sum_cw_disabled" msgid="3658094589461768637">"ഒരു കോളിനിടയിൽ, ഇൻകമിംഗ് കോളുകളെക്കുറിച്ച് എന്നെ അറിയിക്കുക"</string>
- <string name="call_forwarding_settings" msgid="8937130467468257671">"കോൾ ഫോർവേഡിംഗ് ക്രമീകരണം"</string>
+ <string name="call_forwarding_settings" msgid="8937130467468257671">"കോൾഫോർവേഡിംഗ് ക്രമീകരണം"</string>
<string name="call_forwarding_settings_with_label" msgid="2345432813399564272">"കോൾഫോർവേഡിംഗ് ക്രമീകരണം (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="labelCF" msgid="3578719437928476078">"കോൾ ഫോർവേഡിംഗ്"</string>
<string name="labelCFU" msgid="8870170873036279706">"എല്ലായ്പ്പോഴും കൈമാറുക"</string>
@@ -127,8 +127,8 @@
<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>
<string name="error_updating_title" msgid="2024290892676808965">"കോൾ ക്രമീകരണ പിശക്"</string>
- <string name="reading_settings" msgid="1605904432450871183">"ക്രമീകരണങ്ങൾ റീഡ് ചെയ്യുന്നു.…"</string>
- <string name="updating_settings" msgid="3650396734816028808">"ക്രമീകരണങ്ങൾ അപ്ഡേറ്റ് ചെയ്യുന്നു…"</string>
+ <string name="reading_settings" msgid="1605904432450871183">"ക്രമീകരണങ്ങൾ റീഡുചെയ്യുന്നു.…"</string>
+ <string name="updating_settings" msgid="3650396734816028808">"ക്രമീകരണങ്ങൾ അപ്ഡേറ്റുചെയ്യുന്നു…"</string>
<string name="reverting_settings" msgid="7378668837291012205">"ക്രമീകരണങ്ങൾ പഴയപടിയാക്കുന്നു…"</string>
<string name="response_error" msgid="3904481964024543330">"നെറ്റ്വർക്കിൽ നിന്നുള്ള അപ്രതീക്ഷിത പ്രതികരണം."</string>
<string name="exception_error" msgid="330994460090467">"നെറ്റ്വർക്ക് അല്ലെങ്കിൽ സിം കാർഡ് പിശക്."</string>
@@ -522,7 +522,7 @@
<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_ecm_emergency_only" msgid="5622379058883722080">"അടിയന്തിരമല്ലാത്ത കോൾ ചെയ്യാൻ അടിയന്തിര കോൾബാക്ക് മോഡിൽ നിന്ന് പുറത്തുകടക്കുക."</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"വേണം"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"വേണ്ട"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"നിരസിക്കുക"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"ഫോൺ അടിയന്തര കോൾബാക്ക് മോഡിലാണ്"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">ഫോൺ <xliff:g id="COUNT_1">%s</xliff:g> മിനിറ്റ്, അടിയന്തര കോൾബാക്ക് മോഡിലായിരിക്കും.\nനിങ്ങൾക്ക് ഇപ്പോൾ പുറത്ത് കടക്കണോ?</item>
- <item quantity="one">ഫോൺ <xliff:g id="COUNT_0">%s</xliff:g> മിനിറ്റ്, അടിയന്തര കോൾബാക്ക് മോഡിലായിരിക്കും.\nനിങ്ങൾക്ക് ഇപ്പോൾ പുറത്ത് കടക്കണോ?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"സേവനം"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"സജ്ജമാക്കുക"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<സജ്ജീകരിച്ചിട്ടില്ല>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"പുതുക്കിയെടുക്കുക"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS പരിശോധന മാറ്റുക"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-നിർദ്ദിഷ്ട വിവരം/ക്രമീകരണം"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC ലഭ്യം:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR നിയന്ത്രിച്ചിരിക്കുന്നു:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR ലഭ്യം:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR നില:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR ഫ്രീക്വൻസി:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"റേഡിയോ ബാൻഡ് മോഡ് സജ്ജീകരിക്കുക"</string>
<string name="band_mode_loading" msgid="795923726636735967">"ബാൻഡ് ലിസ്റ്റ് ലോഡ് ചെയ്യുന്നു…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"സജ്ജീകരിക്കുക"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index da6741b..09e6b64 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -31,7 +31,7 @@
<string name="ussdRunning" msgid="1163586813106772717">"USSD код ажиллаж байна…"</string>
<string name="mmiCancelled" msgid="5339191899200678272">"MMI код цуцлагдсан"</string>
<string name="cancel" msgid="8984206397635155197">"Цуцлах"</string>
- <string name="enter_input" msgid="6193628663039958990">"USSD мессежийн үсгийн тоо <xliff:g id="MIN_LEN">%1$d</xliff:g> болон <xliff:g id="MAX_LEN">%2$d</xliff:g> хооронд байх шаардлагатай. Дахин оролдоно уу."</string>
+ <string name="enter_input" msgid="6193628663039958990">"USSD зурвасын үсгийн тоо <xliff:g id="MIN_LEN">%1$d</xliff:g> болон <xliff:g id="MAX_LEN">%2$d</xliff:g> хооронд байх шаардлагатай. Дахин оролдоно уу."</string>
<string name="manageConferenceLabel" msgid="8415044818156353233">"Хурлын дуудлага удирдах"</string>
<string name="ok" msgid="7818974223666140165">"OK"</string>
<string name="audio_mode_speaker" msgid="243689733219312360">"Чанга яригч"</string>
@@ -62,7 +62,7 @@
<string name="labelCdmaMore_with_label" msgid="7759692829160238152">"CDMA дуудлагын тохиргоо (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="apn_settings" msgid="1978652203074756623">"Хандалтын Цэгийн Нэрс"</string>
<string name="settings_label" msgid="9101778088412567956">"Сүлжээний тохиргоо"</string>
- <string name="phone_accounts" msgid="1216879437523774604">"Дуудлагын бүртгэлүүд"</string>
+ <string name="phone_accounts" msgid="1216879437523774604">"Дуудах бүртгэл"</string>
<string name="phone_accounts_make_calls_with" msgid="16747814788918145">"Дараахаар дуудлага хийх"</string>
<string name="phone_accounts_make_sip_calls_with" msgid="4691221006731847255">"Дараахаар SIP дуудлага хийх"</string>
<string name="phone_accounts_ask_every_time" msgid="6192347582666047168">"Эхлэж хандах"</string>
@@ -95,17 +95,17 @@
<string name="sum_loading_settings" msgid="434063780286688775">"Тохиргоог ачаалж байна…"</string>
<string name="sum_hide_caller_id" msgid="131100328602371933">"Залгасан дуудлагуудад дугаарыг нууцална"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"Залгасан дуудлагуудад дугаарыг харуулна"</string>
- <string name="sum_default_caller_id" msgid="1767070797135682959">"Гарах дуудлагуудад миний дугаарыг харуулахдаа операторын өгөгдмөл тохиргоог ашиглах"</string>
+ <string name="sum_default_caller_id" msgid="1767070797135682959">"Гарах дуудлагуудад миний дугаарыг харуулахдаа операторын үндсэн тохиргоог ашиглах"</string>
<string name="labelCW" msgid="8449327023861428622">"Дуудлага хүлээлгэх"</string>
<string name="sum_cw_enabled" msgid="3977308526187139996">"Дуудлагын дундуур өөр дуудлага ирвэл надад мэдэгдэх"</string>
<string name="sum_cw_disabled" msgid="3658094589461768637">"Дуудлагын дундуур өөр дуудлага ирвэл надад мэдэгдэх"</string>
<string name="call_forwarding_settings" msgid="8937130467468257671">"Дуудлага шилжүүлэх тохиргоо"</string>
<string name="call_forwarding_settings_with_label" msgid="2345432813399564272">"Дуудлага шилжүүлэх тохиргоонууд (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="labelCF" msgid="3578719437928476078">"Дуудлага дамжуулах"</string>
- <string name="labelCFU" msgid="8870170873036279706">"Байнга шилжүүлэх"</string>
+ <string name="labelCFU" msgid="8870170873036279706">"Байнга дамжуулах"</string>
<string name="messageCFU" msgid="1361806450979589744">"Байнга энэ дугаарыг ашиглах"</string>
<string name="sum_cfu_enabled_indicator" msgid="9030139213402432776">"Бүх дуудлагыг дамжуулж байна"</string>
- <string name="sum_cfu_enabled" msgid="5806923046528144526">"Бүх дуудлагыг <xliff:g id="PHONENUMBER">{0}</xliff:g> руу шилжүүлж байна"</string>
+ <string name="sum_cfu_enabled" msgid="5806923046528144526">"Бүх дуудлагыг <xliff:g id="PHONENUMBER">{0}</xliff:g> руу дамжуулж байна"</string>
<string name="sum_cfu_enabled_no_number" msgid="7287752761743377930">"Дугаар холбогдох боломжгүй"</string>
<string name="sum_cfu_disabled" msgid="5010617134210809853">"Идэвхгүй"</string>
<string name="labelCFB" msgid="615265213360512768">"Завгүй бол"</string>
@@ -163,7 +163,7 @@
<string name="vm_change_pin_error_mismatch" msgid="5364847280026257331">"Хуучин PIN таарахгүй байна."</string>
<string name="vm_change_pin_error_invalid" msgid="5230002671175580674">"Шинэ PIN-д буруу тэмдэгт агуулагдаж байна."</string>
<string name="vm_change_pin_error_system_error" msgid="9116483527909681791">"PIN-г өөрчлөх боломжгүй"</string>
- <string name="vvm_unsupported_message_format" msgid="4206402558577739713">"Дэмжигдээгүй мессежийн төрөл, сонсохын тулд <xliff:g id="NUMBER">%s</xliff:g> руу залгана уу."</string>
+ <string name="vvm_unsupported_message_format" msgid="4206402558577739713">"Дэмжигдээгүй зурвасын төрөл, сонсохын тулд <xliff:g id="NUMBER">%s</xliff:g> руу залгана уу."</string>
<string name="network_settings_title" msgid="7560807107123171541">"Мобайл сүлжээ"</string>
<string name="label_available" msgid="1316084116670821258">"Боломжтой сүлжээнүүд"</string>
<string name="load_networks_progress" msgid="4051433047717401683">"Хайж байна..."</string>
@@ -283,9 +283,9 @@
<string name="roaming_warning" msgid="7855681468067171971">"Таны төлбөр өндөр гарах магадлалтайг анхааруулж байна."</string>
<string name="roaming_check_price_warning" msgid="8212484083990570215">"Үнийг нь өөрийн сүлжээний нийлүүлэгчээс шалгана уу."</string>
<string name="roaming_alert_title" msgid="5689615818220960940">"Дата роуминг үйлчилгээг идэвхжүүлэх үү?"</string>
- <string name="limited_sim_function_notification_title" msgid="612715399099846281">"Хязгаарлагдмал SIM-н функц"</string>
+ <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="limited_sim_function_notification_message" msgid="5338638075496721160">"Өөр СИМ ашиглах явцад <xliff:g id="CARRIER_NAME">%1$s</xliff:g>-н дуудлага болон дата үйлчилгээг хориглож болзошгүй."</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>
@@ -425,7 +425,7 @@
<string name="cdma_activate_device" msgid="5914720276140097632">"Төхөөрөмжийг идэвхжүүлэх"</string>
<string name="cdma_lte_data_service" msgid="359786441782404562">"Дата үйлчилгээний тохиргоо"</string>
<string name="carrier_settings_title" msgid="6292869148169850220">"Операторын тохиргоо"</string>
- <string name="fdn" msgid="2545904344666098749">"Тогтсон залгах дугаарууд"</string>
+ <string name="fdn" msgid="2545904344666098749">"Тогтсон Залгах Дугаарууд"</string>
<string name="fdn_with_label" msgid="6412087553365709494">"Тогтсон Дуудлагын Дугаарууд(<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="manage_fdn_list" msgid="3341716430375195441">"FDN жагсаалт"</string>
<string name="fdn_list_with_label" msgid="1409655283510382556">"FDN жагсаалт (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -471,7 +471,7 @@
<string name="simContacts_emptyLoading" msgid="4989040293858675483">"SIM картаас уншиж байна…"</string>
<string name="simContacts_empty" msgid="1135632055473689521">"Таны SIM картанд харилцагчид байхгүй байна."</string>
<string name="simContacts_title" msgid="2714029230160136647">"Оруулах харилцагчдыг сонгоно уу"</string>
- <string name="simContacts_airplaneMode" msgid="4654884030631503808">"SIM картнаас дугаар импортлохын тулд онгоцны горимыг унтраа"</string>
+ <string name="simContacts_airplaneMode" msgid="4654884030631503808">"Сим картнаас дугаар импортлохын тулд онгоцны горимыг унтраа"</string>
<string name="enable_pin" msgid="967674051730845376">"SIM PIN Идэвхжүүлэх/идэвхгүйжүүлэх"</string>
<string name="change_pin" msgid="3657869530942905790">"SIM PIN өөрчлөх"</string>
<string name="enter_pin_text" msgid="3182311451978663356">"SIM PIN:"</string>
@@ -531,7 +531,7 @@
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Мобайл сүлжээнд холбогдох боломжгүй байна. Дуудлага хийхийн тулд утасгүй интернетэд холбогдоно уу."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Дуудлага хийхийн тулд хүчин төгөлдөр дугаар оруулна уу."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Дуудлага амжилтгүй болсон."</string>
- <string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Дуудлагыг энэ удаад нэмэх боломжгүй. Та мессеж илгээн холбоо тогтоохыг оролдох боломжтой."</string>
+ <string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Дуудлагыг энэ удаад нэмэх боломжгүй. Та зурвас илгээн холбоо тогтоохыг оролдох боломжтой."</string>
<string name="incall_error_supp_service_unknown" msgid="8751177117194592623">"Үйлчилгээг дэмждэггүй байна"</string>
<string name="incall_error_supp_service_switch" msgid="5272822448189448479">"Дуудлагыг солих боломжгүй байна."</string>
<string name="incall_error_supp_service_resume" msgid="1276861499306817035">"Дуудлагыг үргэлжлүүлэх боломжгүй."</string>
@@ -562,7 +562,7 @@
<string name="fire_type_description" msgid="6565200468934914930">"Гал"</string>
<string name="description_concat_format" msgid="2014471565101724088">"%1$s, %2$s"</string>
<string name="dialerKeyboardHintText" msgid="1115266533703764049">"Залгахдаа гар ашиглах"</string>
- <string name="onscreenHoldText" msgid="4025348842151665191">"Хүлээлгэх"</string>
+ <string name="onscreenHoldText" msgid="4025348842151665191">"Саатуулах"</string>
<string name="onscreenEndCallText" msgid="6138725377654842757">"Дуусгах"</string>
<string name="onscreenShowDialpadText" msgid="658465753816164079">"Диалпад"</string>
<string name="onscreenMuteText" msgid="5470306116733843621">"Дууг хаах"</string>
@@ -582,8 +582,8 @@
<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_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="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">
<item msgid="3238070884803849303">"TTY Унтраасан"</item>
@@ -597,14 +597,14 @@
<item msgid="2271798469250155310">"Энгийн"</item>
<item msgid="6044210222666533564">"Урт"</item>
</string-array>
- <string name="network_info_message" msgid="7599413947016532355">"Сүлжээний мессеж"</string>
- <string name="network_error_message" msgid="4271579424089326618">"Алдаатай мессеж"</string>
+ <string name="network_info_message" msgid="7599413947016532355">"Сүлжээний зурвас"</string>
+ <string name="network_error_message" msgid="4271579424089326618">"Алдаатай зурвас"</string>
<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_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_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_title_activate_success" msgid="1272135024761004889">"Утсыг идэвхжүүллээ."</string>
@@ -625,8 +625,8 @@
<string name="phone_in_ecm_call_notification_text" msgid="653972232922670335">"Дата холболтыг идэвхгүйжүүлсэн"</string>
<string name="phone_in_ecm_notification_complete_time" msgid="7341624337163082759">"<xliff:g id="COMPLETETIME">%s</xliff:g> болтол дата холболт байхгүй"</string>
<plurals name="alert_dialog_exit_ecm" formatted="false" msgid="5425906903766466743">
- <item quantity="other">Таны гар утас яаралтай түргэн тусламжийн дуудлагын хэлбэрт<xliff:g id="COUNT_1">%s</xliff:g> минут байна. Энэ горимд байгаа тохиолдолд интернет холболт ашигладаг ямар ч программыг ашиглаж үл болно. Та гарахыг хүсэж байна уу?</item>
- <item quantity="one">Таны гар утас яаралтай түргэн тусламжийн дуудлагын хэлбэрт <xliff:g id="COUNT_0">%s</xliff:g> минут байна. Энэ горимд байгаа тохиолдолд интернет холболт ашигладаг ямар ч программыг ашиглаж үл болно. Та гарахыг хүсэж байна уу?</item>
+ <item quantity="other">Таны гар утас яаралтай түргэн тусламжийн дуудлагын хэлбэрт<xliff:g id="COUNT_1">%s</xliff:g> минут байна. Энэ горимд байгаа тохиолдолд интернет холболт ашигладаг ямар ч програмыг ашиглаж үл болно. Та гарахыг хүсэж байна уу?</item>
+ <item quantity="one">Таны гар утас яаралтай түргэн тусламжийн дуудлагын хэлбэрт <xliff:g id="COUNT_0">%s</xliff:g> минут байна. Энэ горимд байгаа тохиолдолд интернет холболт ашигладаг ямар ч програмыг ашиглаж үл болно. Та гарахыг хүсэж байна уу?</item>
</plurals>
<plurals name="alert_dialog_not_avaialble_in_ecm" formatted="false" msgid="1152682528741457004">
<item quantity="other">Сонгосон үйлдлийг яаралтай түргэн тусламжийн горимд байгаа үед ашиглах боломжгүй. Таны гар утас энэ горимд <xliff:g id="COUNT_1">%s</xliff:g> минут байх болно. Та гарахыг хүсэж байна уу?</item>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Тийм"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Үгүй"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Алгасах"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Утас яаралтай буцаж холбоо барих горимд байна"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">Утас <xliff:g id="COUNT_1">%s</xliff:g> минутын турш яаралтай буцаж холбоо барих горимд байх болно.\nТа одоо гарахыг хүсэж байна уу?</item>
- <item quantity="one">Утас <xliff:g id="COUNT_0">%s</xliff:g> минутын турш яаралтай буцаж холбоо барих горимд байх болно.\nТа одоо гарахыг хүсэж байна уу?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Үйлчилгээ"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Тохиргоо"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Тохируулаагүй>"</string>
@@ -655,7 +649,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>
@@ -671,7 +665,7 @@
<string name="status_hint_label_incoming_wifi_call" msgid="2606052595898044071">"Wi-Fi дуудлага"</string>
<string name="status_hint_label_wifi_call" msgid="942993035689809853">"Wi-Fi дуудлага"</string>
<string name="emergency_action_launch_hint" msgid="2762016865340891314">"Нээхийн тулд дахин дарна уу"</string>
- <string name="message_decode_error" msgid="1061856591500290887">"Мессежийн кодыг тайлах явцад алдаа гарсан."</string>
+ <string name="message_decode_error" msgid="1061856591500290887">"Зурвасын кодыг тайлах явцад алдаа гарсан."</string>
<string name="callFailed_cdma_activation" msgid="5392057031552253550">"SIM карт таны үйлчилгээг идэвхжүүлж, утасны роаминг багтаамжийг шинэчиллээ."</string>
<string name="callFailed_cdma_call_limit" msgid="1074219746093031412">"Хэт олон идэвхтэй дуудлага байна. Шинэ дуудлага хийхийн өмнө одоогийн дуудлагуудыг таслах буюу нэгтгэнэ үү."</string>
<string name="callFailed_imei_not_accepted" msgid="7257903653685147251">"Холбох боломжгүй, хүчинтэй SIM карт оруулна уу."</string>
@@ -866,7 +860,7 @@
<string name="radio_info_subid" msgid="6839966868621703203">"Одоогийн subId:"</string>
<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_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>
@@ -882,9 +876,9 @@
<string name="radio_info_signal_strength_label" msgid="5545444702102543260">"Дохионы хүч:"</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_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_band_mode_label" msgid="23480556225515290">"Радио зурвасыг сонгох"</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>
@@ -896,15 +890,10 @@
<string name="radio_info_smsc_label" msgid="3749927072726033763">"SMSC:"</string>
<string name="radio_info_smsc_update_label" msgid="5141996256097115753">"Шинэчлэх"</string>
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Сэргээх"</string>
- <string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS шалгалтыг асаах/унтраах"</string>
+ <string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS шалгалтыг унтраах/асаах"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-тодорхой Мэдээлэл/Тохиргоо"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC боломжтой:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR хязгаарласан:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR боломжтой:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR төлөв:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR давтамж:"</string>
- <string name="band_mode_title" msgid="7988822920724576842">"Радио мессежийн горимыг тохируулах"</string>
- <string name="band_mode_loading" msgid="795923726636735967">"Мессежийн жагсаалтыг ачаалж байна…"</string>
+ <string name="band_mode_title" msgid="7988822920724576842">"Радио зурвасын горимыг тохируулах"</string>
+ <string name="band_mode_loading" msgid="795923726636735967">"Зурвасын жагсаалтыг ачаалж байна…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Тохируулах"</string>
<string name="band_mode_failed" msgid="1707488541847192924">"Амжилтгүй"</string>
<string name="band_mode_succeeded" msgid="2230018000534761063">"Амжилттай"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 4f5476f..210395c 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -16,7 +16,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="phoneAppLabel" product="tablet" msgid="1916019789885839910">"मोबाइल डेटा"</string>
+ <string name="phoneAppLabel" product="tablet" msgid="1916019789885839910">"मोबाईल डेटा"</string>
<string name="phoneAppLabel" product="default" msgid="130465039375347763">"फोन सेवा"</string>
<string name="emergencyDialerIconLabel" msgid="8668005772339436680">"आणीबाणी डायलर"</string>
<string name="phoneIconLabel" msgid="3015941229249651419">"फोन"</string>
@@ -27,17 +27,17 @@
<string name="onHold" msgid="6132725550015899006">"होल्ड वर"</string>
<string name="carrier_mmi_msg_title" msgid="6050165242447507034">"<xliff:g id="MMICARRIER">%s</xliff:g> मेसेज"</string>
<string name="default_carrier_mmi_msg_title" msgid="7754317179938537213">"वाहक मेसेज"</string>
- <string name="mmiStarted" msgid="9212975136944568623">"MMI कोड सुरू केला"</string>
- <string name="ussdRunning" msgid="1163586813106772717">"USSD कोड सुरू…"</string>
+ <string name="mmiStarted" msgid="9212975136944568623">"MMI कोड प्रारंभ केला"</string>
+ <string name="ussdRunning" msgid="1163586813106772717">"USSD कोड चालू…"</string>
<string name="mmiCancelled" msgid="5339191899200678272">"MMI कोड रद्द केला"</string>
<string name="cancel" msgid="8984206397635155197">"रद्द करा"</string>
<string name="enter_input" msgid="6193628663039958990">"USSD मेसेज <xliff:g id="MIN_LEN">%1$d</xliff:g> आणि <xliff:g id="MAX_LEN">%2$d</xliff:g> वर्णांमधील असणे आवश्यक आहे. कृपया पुन्हा प्रयत्न करा."</string>
- <string name="manageConferenceLabel" msgid="8415044818156353233">"कॉंफरन्स कॉल व्यवस्थापित करा"</string>
+ <string name="manageConferenceLabel" msgid="8415044818156353233">"कॉन्फरन्स कॉल व्यवस्थापित करा"</string>
<string name="ok" msgid="7818974223666140165">"ठीक"</string>
<string name="audio_mode_speaker" msgid="243689733219312360">"स्पीकर"</string>
<string name="audio_mode_earpiece" msgid="2823700267171134282">"हँडसेट इअरपीस"</string>
<string name="audio_mode_wired_headset" msgid="5028010823105817443">"वायर्ड हेडसेट"</string>
- <string name="audio_mode_bluetooth" msgid="25732183428018809">"ब्लूटूथ"</string>
+ <string name="audio_mode_bluetooth" msgid="25732183428018809">"ब्लूटुथ"</string>
<string name="wait_prompt_str" msgid="5136209532150094910">"खालील टोन पाठवायचे?\n"</string>
<string name="pause_prompt_str" msgid="2308897950360272213">"टोन पाठवित आहे\n"</string>
<string name="send_button" msgid="5070379600779031932">"पाठवा"</string>
@@ -55,7 +55,7 @@
<string name="requesting_unlock" msgid="930512210309437741">"नेटवर्क अनलॉकची विनंती करत आहे..."</string>
<string name="unlock_failed" msgid="7103543844840661366">"नेटवर्क अनलॉक विनंती अयशस्वी."</string>
<string name="unlock_success" msgid="32681089371067565">"नेटवर्क अनलॉक यशस्वी."</string>
- <string name="mobile_network_settings_not_available" msgid="8678168497517090039">"या वापरकर्त्यासाठी मोबाइल नेटवर्क सेटिंग्ज उपलब्ध नाहीत"</string>
+ <string name="mobile_network_settings_not_available" msgid="8678168497517090039">"या वापरकर्त्यासाठी मोबाईल नेटवर्क सेटिंग्ज उपलब्ध नाहीत"</string>
<string name="labelGSMMore" msgid="7354182269461281543">"GSM कॉल सेटिंग्ज"</string>
<string name="labelGsmMore_with_label" msgid="3206015314393246224">"GSM कॉल सेटिंग्ज (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="labelCDMAMore" msgid="7937441382611224632">"CDMA कॉल सेटिंग्ज"</string>
@@ -104,23 +104,23 @@
<string name="labelCF" msgid="3578719437928476078">"कॉल फॉरवर्डिंग"</string>
<string name="labelCFU" msgid="8870170873036279706">"नेहमी फॉरवर्ड करा"</string>
<string name="messageCFU" msgid="1361806450979589744">"नेहमी हा नंबर वापरा"</string>
- <string name="sum_cfu_enabled_indicator" msgid="9030139213402432776">"सर्व कॉल फॉरवर्ड करत आहे"</string>
- <string name="sum_cfu_enabled" msgid="5806923046528144526">"सर्व कॉल <xliff:g id="PHONENUMBER">{0}</xliff:g> वर फॉरवर्ड करत आहे"</string>
+ <string name="sum_cfu_enabled_indicator" msgid="9030139213402432776">"सर्व कॉल अग्रेषित करत आहे"</string>
+ <string name="sum_cfu_enabled" msgid="5806923046528144526">"सर्व कॉल <xliff:g id="PHONENUMBER">{0}</xliff:g> वर अग्रेषित करत आहे"</string>
<string name="sum_cfu_enabled_no_number" msgid="7287752761743377930">"नंबर अनुपलब्ध आहे"</string>
<string name="sum_cfu_disabled" msgid="5010617134210809853">"बंद"</string>
<string name="labelCFB" msgid="615265213360512768">"व्यस्त असताना"</string>
<string name="messageCFB" msgid="1958017270393563388">"नंबर व्यस्त असताना"</string>
- <string name="sum_cfb_enabled" msgid="332037613072049492">"<xliff:g id="PHONENUMBER">{0}</xliff:g> वर फॉरवर्ड करत आहे"</string>
+ <string name="sum_cfb_enabled" msgid="332037613072049492">"<xliff:g id="PHONENUMBER">{0}</xliff:g> वर अग्रेषित करत आहे"</string>
<string name="sum_cfb_disabled" msgid="3589913334164866035">"बंद"</string>
<string name="disable_cfb_forbidden" msgid="4831494744351633961">"तुमचा फोन व्यस्त असताना तुमचा ऑपरेटर कॉल अग्रेषण करणे अक्षम करण्यास समर्थन करीत नाही."</string>
<string name="labelCFNRy" msgid="3403533792248457946">"उत्तर न दिल्यास"</string>
<string name="messageCFNRy" msgid="7644434155765359009">"नंबर अनुत्तरित असताना"</string>
- <string name="sum_cfnry_enabled" msgid="3000500837493854799">"<xliff:g id="PHONENUMBER">{0}</xliff:g> वर फॉरवर्ड करत आहे"</string>
+ <string name="sum_cfnry_enabled" msgid="3000500837493854799">"<xliff:g id="PHONENUMBER">{0}</xliff:g> वर अग्रेषित करत आहे"</string>
<string name="sum_cfnry_disabled" msgid="1990563512406017880">"बंद"</string>
<string name="disable_cfnry_forbidden" msgid="3174731413216550689">"तुमचा फोन उत्तर देत नसताना तुमचा ऑपरेटर कॉल अग्रेषण करणे अक्षम करण्यास समर्थन करीत नाही."</string>
<string name="labelCFNRc" msgid="4163399350778066013">"आउट ऑफ रीच असताना"</string>
<string name="messageCFNRc" msgid="6980340731313007250">"नंबर पोहचण्यायोग्य नसताना"</string>
- <string name="sum_cfnrc_enabled" msgid="1799069234006073477">"<xliff:g id="PHONENUMBER">{0}</xliff:g> वर फॉरवर्ड करत आहे"</string>
+ <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="updating_title" msgid="6130548922615719689">"कॉल सेटिंग्ज"</string>
@@ -136,10 +136,10 @@
<string name="stk_cc_ss_to_ussd_error" msgid="8330749347425752192">"SS विनंती USSD विनंतीवर बदलली"</string>
<string name="stk_cc_ss_to_ss_error" msgid="8297155544652134278">"नवीन SS विनंतीवर बदलली"</string>
<string name="stk_cc_ss_to_dial_video_error" msgid="4255261231466032505">"SS विनंती व्हिडिओ कॉलवर बदलली"</string>
- <string name="fdn_check_failure" msgid="1833769746374185247">"आपल्या फोन अॅप्सचे निश्चित डायलिंग नंबर सेटिंग सुरू केले. परिणामी, काही कॉल संबंधित वैशिष्ट्ये कार्य करीत नाहीत."</string>
- <string name="radio_off_error" msgid="8321564164914232181">"या सेटिंग्ज पाहण्यापूर्वी रेडिओ सुरू करा."</string>
+ <string name="fdn_check_failure" msgid="1833769746374185247">"आपल्या फोन अॅप्सचे निश्चित डायलिंग नंबर सेटिंग चालू केले. परिणामी, काही कॉल संबंधित वैशिष्ट्ये कार्य करीत नाहीत."</string>
+ <string name="radio_off_error" msgid="8321564164914232181">"या सेटिंग्ज पाहण्यापूर्वी रेडिओ चालू करा."</string>
<string name="close_dialog" msgid="1074977476136119408">"ठीक"</string>
- <string name="enable" msgid="2636552299455477603">"सुरू करा"</string>
+ <string name="enable" msgid="2636552299455477603">"चालू करा"</string>
<string name="disable" msgid="1122698860799462116">"बंद करा"</string>
<string name="change_num" msgid="6982164494063109334">"अपडेट करा"</string>
<string-array name="clir_display_values">
@@ -149,7 +149,7 @@
</string-array>
<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_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>
@@ -307,10 +307,10 @@
<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_status_subtext" msgid="1110276415078236687">"कमाल कालावधी <xliff:g id="USED_2">%3$s</xliff:g> पैकी <xliff:g id="USED_0">%1$s</xliff:g> (<xliff:g id="USED_1">%2$d</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_status_subtext" msgid="1110276415078236687">"कमाल कालावधी <xliff:g id="USED_2">%3$s</xliff:g> पैकी <xliff:g id="USED_0">%1$s</xliff:g> (<xliff:g id="USED_1">%2$d</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_2">%3$s</xliff:g> पैकी <xliff:g id="USED_0">%1$s</xliff:g> (<xliff:g id="USED_1">%2$d</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> Kb/s इतका कमी झाला"</string>
- <string name="throttle_time_frame_subtext" msgid="6462089615392402127">"कालचक्राचा <xliff:g id="USED_0">%1$d</xliff:g>٪ काळ लोटला\nपुढील कालावधी <xliff:g id="USED_1">%2$d</xliff:g> दिवसांमध्ये (<xliff:g id="USED_2">%3$s</xliff:g>) सुरू होतो"</string>
+ <string name="throttle_time_frame_subtext" msgid="6462089615392402127">"कालचक्राचा <xliff:g id="USED_0">%1$d</xliff:g>٪ काळ लोटला\nपुढील कालावधी <xliff:g id="USED_1">%2$d</xliff:g> दिवसांमध्ये (<xliff:g id="USED_2">%3$s</xliff:g>) प्रारंभ होतो"</string>
<string name="throttle_rate_subtext" msgid="7221971817325779535">"डेटा वापर मर्यादा ओलांडल्यास डेटा रेट <xliff:g id="USED">%1$d</xliff:g> Kb/s इतका कमी केला"</string>
<string name="throttle_help_subtext" msgid="2817114897095534807">"आपल्या वाहकाच्या मोबाइल नेटवर्क डेटा वापर धोरणाविषयी अधिक माहिती"</string>
<string name="cell_broadcast_sms" msgid="4053449797289031063">"सेल प्रसारण SMS"</string>
@@ -510,7 +510,7 @@
<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>
+ <string name="card_title_conf_call" msgid="901197309274457427">"कॉन्फरन्स कॉल"</string>
<string name="card_title_incoming_call" msgid="881424648458792430">"येणारे कॉल"</string>
<string name="card_title_call_ended" msgid="650223980095026340">"कॉल संपला"</string>
<string name="card_title_on_hold" msgid="9028319436626975207">"होल्ड वर"</string>
@@ -522,7 +522,7 @@
<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_ecm_emergency_only" msgid="5622379058883722080">"आणीबाणी नसलेला कॉल करण्यासाठी आणीबाणी कॉलबॅक मोडमधून बाहेर पडा."</string>
@@ -540,7 +540,7 @@
<string name="incall_error_supp_service_conference" msgid="27578082433544702">"कॉल विलीन करता आले नाहीत."</string>
<string name="incall_error_supp_service_reject" msgid="3044363092441655912">"कॉल नाकारू शकत नाही."</string>
<string name="incall_error_supp_service_hangup" msgid="836524952243836735">"कॉल रिलीज करू शकत नाही."</string>
- <string name="incall_error_supp_service_hold" msgid="8535056414643540997">"कॉल सुरू ठेवू शकत नाही."</string>
+ <string name="incall_error_supp_service_hold" msgid="8535056414643540997">"कॉल सुरु ठेवू शकत नाही."</string>
<string name="incall_error_wfc_only_no_wireless_network" msgid="5860742792811400109">"कॉल करण्यासाठी वायरलेस नेटवर्कशी कनेक्ट करा."</string>
<string name="incall_error_promote_wfc" msgid="9164896813931363415">"कॉल करण्यासाठी वाय-फाय कॉलिंग सक्षम करा."</string>
<string name="emergency_information_hint" msgid="9208897544917793012">"अतिमहत्त्वाची माहिती"</string>
@@ -550,7 +550,7 @@
<string name="single_emergency_number_title" msgid="8413371079579067196">"आणीबाणी नंबर"</string>
<string name="numerous_emergency_numbers_title" msgid="8972398932506755510">"आणीबाणी नंबर"</string>
<string name="emergency_call_shortcut_hint" msgid="1290485125107779500">"<xliff:g id="EMERGENCY_NUMBER">%s</xliff:g> ला कॉल करण्यासाठी पुन्हा टॅप करा"</string>
- <string name="emergency_enable_radio_dialog_message" msgid="1695305158151408629">"रेडिओ सुरू करत आहे..."</string>
+ <string name="emergency_enable_radio_dialog_message" msgid="1695305158151408629">"रेडिओ चालू करत आहे..."</string>
<string name="emergency_enable_radio_dialog_retry" msgid="4329131876852608587">"सेवा नाही. पुन्हा प्रयत्न करत आहे…"</string>
<string name="radio_off_during_emergency_call" msgid="8011154134040481609">"आणीबाणी कॉलदरम्यान विमान मोडमध्ये प्रवेश करू शकत नाही."</string>
<string name="dial_emergency_error" msgid="825822413209026039">"कॉल करू शकत नाही. <xliff:g id="NON_EMERGENCY_NUMBER">%s</xliff:g> हा आणीबाणी नंबर नाहीये."</string>
@@ -565,7 +565,7 @@
<string name="onscreenHoldText" msgid="4025348842151665191">"होल्ड करा"</string>
<string name="onscreenEndCallText" msgid="6138725377654842757">"शेवट"</string>
<string name="onscreenShowDialpadText" msgid="658465753816164079">"डायलपॅड"</string>
- <string name="onscreenMuteText" msgid="5470306116733843621">"म्यूट करा"</string>
+ <string name="onscreenMuteText" msgid="5470306116733843621">"नि:शब्द करा"</string>
<string name="onscreenAddCallText" msgid="9075675082903611677">"कॉल जोडा"</string>
<string name="onscreenMergeCallsText" msgid="3692389519611225407">"कॉल विलीन करा"</string>
<string name="onscreenSwapCallsText" msgid="2682542150803377991">"अदलाबदल करा"</string>
@@ -580,7 +580,7 @@
<string name="singleContactImportedMsg" msgid="3619804066300998934">"इंपोर्ट केलेला संपर्क"</string>
<string name="failedToImportSingleContactMsg" msgid="228095510489830266">"संपर्क इंपोर्ट करण्यात अयशस्वी"</string>
<string name="hac_mode_title" msgid="4127986689621125468">"श्रवणयंत्रे"</string>
- <string name="hac_mode_summary" msgid="7774989500136009881">"श्रवणयंत्र कंपॅटिबिलिटी सुरू करा"</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_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/> - RTT cकॉल मेसेज प्रतिलेख म्हणून सेव्ह केले आहेत\n <br/> - RTT व्हिडिओ कॉलसाठी उपलब्ध नाही"</string>
@@ -604,7 +604,7 @@
<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_skip_activation_dialog_title" msgid="7666611236789203797">"सक्रिय करणे वगळायचे?"</string>
- <string name="ota_skip_activation_dialog_message" msgid="6691722887019708713">"तुम्ही सक्रिय करणे वगळल्यास, तुम्ही कॉल करू शकत नाही किंवा मोबाइल डेटा नेटवर्कशी कनेक्ट करू शकत नाही (तुम्ही वाय-फाय नेटवर्कशी कनेक्ट करू शकत असला तरीही). तुम्ही तुमचा फोन सक्रिय करेपर्यंत, तुम्ही तो प्रत्येक वेळी सुरू करताना आपल्याला तो सक्रिय करण्यास सांगितले जाईल."</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_title_activate_success" msgid="1272135024761004889">"फोन सक्रिय केला आहे."</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"होय"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"नाही"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"डिसमिस करा"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"फोन आणीबाणी कॉलबॅक मोडमध्ये आहे"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">फोन <xliff:g id="COUNT_1">%s</xliff:g> मिनिटे आणीबाणी कॉलबॅक मोडमध्ये राहील.\nतुम्हाला आता बाहेर पडायचे आहे का?</item>
- <item quantity="one">फोन <xliff:g id="COUNT_0">%s</xliff:g> मिनिट आणीबाणी कॉलबॅक मोडमध्ये राहील.\nतुम्हाला आता बाहेर पडायचे आहे का?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"सेवा"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"सेटअप"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<सेट नाही>"</string>
@@ -660,8 +654,8 @@
<string name="voicemail_change_pin_dialog_title" msgid="4633077715231764435">"पिन बदला"</string>
<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_title" msgid="7246600931634161830">"व्हिडिओ कॉलिंग चालू करा"</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>
@@ -685,7 +679,7 @@
<string name="change_pin_cancel_label" msgid="2301711566758827936">"रद्द करा"</string>
<string name="change_pin_ok_label" msgid="6861082678817785330">"ठीक आहे"</string>
<string name="change_pin_enter_old_pin_header" msgid="853151335217594829">"आपल्या जुन्या पिनची पुष्टी करा"</string>
- <string name="change_pin_enter_old_pin_hint" msgid="8801292976275169367">"सुरू ठेवण्यासाठी तुमचा व्हॉइसमेल पिन प्रविष्ट करा."</string>
+ <string name="change_pin_enter_old_pin_hint" msgid="8801292976275169367">"सुरु ठेवण्यासाठी तुमचा व्हॉइसमेल पिन प्रविष्ट करा."</string>
<string name="change_pin_enter_new_pin_header" msgid="4739465616733486118">"नवीन पिन सेट करा"</string>
<string name="change_pin_enter_new_pin_hint" msgid="2326038476516364210">"पिन <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">"आपल्या पिनची पुष्टी करा"</string>
@@ -693,7 +687,7 @@
<string name="change_pin_succeeded" msgid="2504705600693014403">"व्हॉइसमेल पिन अपडेट केला"</string>
<string name="change_pin_system_error" msgid="7772788809875146873">"पिन सेट करण्यात अक्षम"</string>
<string name="mobile_data_status_roaming_turned_off_subtext" msgid="6840673347416227054">"डेटा रोमिंग बंद केलेले आहे"</string>
- <string name="mobile_data_status_roaming_turned_on_subtext" msgid="5615757897768777865">"डेटा रोमिंग सुरू केलेले आहे"</string>
+ <string name="mobile_data_status_roaming_turned_on_subtext" msgid="5615757897768777865">"डेटा रोमिंग चालू केलेले आहे"</string>
<string name="mobile_data_status_roaming_without_plan_subtext" msgid="6536671968072284677">"सध्या रोमिंग, डेटा योजना आवश्यक आहे"</string>
<string name="mobile_data_status_roaming_with_plan_subtext" msgid="2576177169108123095">"सध्या रोमिंग, डेटा योजना सक्रिय आहे"</string>
<string name="mobile_data_status_no_plan_subtext" msgid="170331026419263657">"मोबाइल डेटा शिल्लक नाही"</string>
@@ -707,7 +701,7 @@
<string name="mobile_data_activate_button" msgid="1139792516354374612">"डेटा जोडा"</string>
<string name="mobile_data_activate_cancel_button" msgid="3530174817572005860">"रद्द करा"</string>
<string name="clh_card_title_call_ended_txt" msgid="5977978317527299698">"कॉल संपला"</string>
- <string name="clh_callFailed_powerOff_txt" msgid="8279934912560765361">"विमान मोड सुरू आहे"</string>
+ <string name="clh_callFailed_powerOff_txt" msgid="8279934912560765361">"विमान मोड चालू आहे"</string>
<string name="clh_callFailed_simError_txt" msgid="5128538525762326413">"सिम कार्ड अॅक्सेस करू शकत नाही"</string>
<string name="clh_incall_error_out_of_service_txt" msgid="2736010617446749869">"मोबाइल नेटवर्क उपलब्ध नाही"</string>
<string name="clh_callFailed_unassigned_number_txt" msgid="141967660286695682">"तुम्ही डायल करायचा प्रयत्न करत असलेल्या फोन नंबरमध्ये समस्या आहे. एरर कोड 1."</string>
@@ -760,7 +754,7 @@
<string name="clh_callFailed_protocol_Error_unspecified_txt" msgid="9203320572562697755">"कॉल पूर्ण करता आला नाही. एरर कोड १११."</string>
<string name="clh_callFailed_interworking_unspecified_txt" msgid="7969686413930847182">"कॉल पूर्ण करता आला नाही. एरर कोड १२७."</string>
<string name="labelCallBarring" msgid="4180377113052853173">"कॉल बारिंग"</string>
- <string name="sum_call_barring_enabled" msgid="5184331188926370824">"सुरू"</string>
+ <string name="sum_call_barring_enabled" msgid="5184331188926370824">"चालू"</string>
<string name="sum_call_barring_disabled" msgid="5699448000600153096">"बंद"</string>
<string name="call_barring_baoc" msgid="7400892586336429326">"सर्व जाणारे"</string>
<string name="call_barring_baoc_enabled" msgid="3131509193386668182">"सर्व जाणारे कॉल ब्लॉक करणे बंद करायचे का?"</string>
@@ -868,7 +862,7 @@
<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_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>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"डेटा सर्व्हिस:"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"रिफ्रेश करा"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS तपासणी टॉगल करा"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-विशिष्ट माहिती/सेटिंग्ज"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC उपलब्ध:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR प्रतिबंधित:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR उपलब्ध:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR स्थिती:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR वारंवारता:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"रेडिओ बँड मोड सेट करा"</string>
<string name="band_mode_loading" msgid="795923726636735967">"बँड सूची लोड करत आहे…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"सेट करा"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 145a6ab..a3fb35e 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -121,7 +121,7 @@
<string name="labelCFNRc" msgid="4163399350778066013">"Jika tidak dapat dihubungi"</string>
<string name="messageCFNRc" msgid="6980340731313007250">"Nombor apabila tidak dapat dihubungi"</string>
<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="sum_cfnrc_disabled" msgid="739289696796917683">"Mati"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Pembawa anda tidak menyokong pelumpuhan pemajuan panggilan semasa telefon anda tidak boleh dihubungi."</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>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Ya"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Tidak"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Ketepikan"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Telefon dalam mod panggil balik kecemasan"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Hingga <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">Telefon akan berada dalam mod panggil balik kecemasan selama <xliff:g id="COUNT_1">%s</xliff:g> minit.\nAdakah anda mahu keluar sekarang?</item>
- <item quantity="one">Telefon akan berada dalam mod panggil balik kecemasan selama <xliff:g id="COUNT_0">%s</xliff:g> minit.\nAdakah anda mahu keluar sekarang?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Perkhidmatan"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Persediaan"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Tidak ditetapkan>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Muat semula"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Togol Semakan DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Maklumat/Tetapan khusus OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC Tersedia:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR Terhad:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR Tersedia:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Keadaan NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Frekuensi NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Tetapkan Mod Jalur Radio"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Memuatkan Senarai Jalur…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Tetapkan"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 78b8faa..91391c7 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -24,7 +24,7 @@
<string name="unknown" msgid="8279698889921830815">"မသိပါ"</string>
<string name="private_num" msgid="4487990167889159992">"လျို့ဝှက် နံပါတ်"</string>
<string name="payphone" msgid="7936735771836716941">"ငွေပေးရသည့်ဖုန်း"</string>
- <string name="onHold" msgid="6132725550015899006">"ဖုန်းကိုင်ထားသည်"</string>
+ <string name="onHold" msgid="6132725550015899006">"ခဏ ကိုင်ထားစဉ်"</string>
<string name="carrier_mmi_msg_title" msgid="6050165242447507034">"<xliff:g id="MMICARRIER">%s</xliff:g> မက်ဆေ့ဂျ်"</string>
<string name="default_carrier_mmi_msg_title" msgid="7754317179938537213">"ဖုန်းလိုင်းဝန်ဆောင်မှု မက်ဆေ့ဂျ်"</string>
<string name="mmiStarted" msgid="9212975136944568623">"MMIကုတ်နံပါတ်ကို စတင်ပြီးပါပြီ"</string>
@@ -74,7 +74,7 @@
<string name="phone_accounts_configure_account_settings" msgid="6622119715253196586">"အကောင့် ချိန်ညှိချက်များ ပြုပြင်မည်"</string>
<string name="phone_accounts_all_calling_accounts" msgid="1609600743500618823">"ခေါ်ဆိုနေသော အကောင့်များ အားလုံး"</string>
<string name="phone_accounts_all_calling_accounts_summary" msgid="2214134955430107240">"ဖုန်းခေါ်ဆိုနိုင်သည့် အကောင့်များအား ရွေးရန်"</string>
- <string name="wifi_calling" msgid="3650509202851355742">"Wi-Fi ခေါ်ဆိုခြင်း"</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>
@@ -91,7 +91,7 @@
<string name="additional_cdma_call_settings" msgid="2178016561980611304">"ပိုမိုသော CDMA ခေါ်ဆိုမှု အပြင်အဆင်"</string>
<string name="sum_cdma_call_settings" msgid="3185825305136993636">"CDMA ခေါ်ဆိုမှုအတွက်သာ ဖြည့်စွက်အပြင်အဆင်"</string>
<string name="labelNwService" msgid="6015891883487125120">"ကွန်ရက် ဝန်ဆောင်မှု ဆက်တင်"</string>
- <string name="labelCallerId" msgid="2090540744550903172">"ခေါ်ဆိုသူ ID"</string>
+ <string name="labelCallerId" msgid="2090540744550903172">"ခေါ်ဆိုသူအိုင်ဒီ"</string>
<string name="sum_loading_settings" msgid="434063780286688775">"ဆက်တင်များကို ကြည့်ရှုရန် လုပ်နေပါသည်…"</string>
<string name="sum_hide_caller_id" msgid="131100328602371933">"အထွက်ဖုန်းများတွင် နံပါတ်ဖျောက်ထားပါ"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"အထွက်ဖုန်းများတွင် နံပါတ်မြင်ရသည်"</string>
@@ -105,7 +105,7 @@
<string name="labelCFU" msgid="8870170873036279706">"အမြဲတမ်း ထပ်ဆင့်ပို့ပါ"</string>
<string name="messageCFU" msgid="1361806450979589744">"ဤနံပါတ်ကို အမြဲသုံးပါ"</string>
<string name="sum_cfu_enabled_indicator" msgid="9030139213402432776">"ခေါ်ဆိုမှုအားလုံးအား တဆင့်ထပ်ပို့နေသည်"</string>
- <string name="sum_cfu_enabled" msgid="5806923046528144526">"ခေါ်ဆိုမှုအားလုံးကို <xliff:g id="PHONENUMBER">{0}</xliff:g> သို့ ထပ်ဆင့်ပို့နေသည်"</string>
+ <string name="sum_cfu_enabled" msgid="5806923046528144526">"<xliff:g id="PHONENUMBER">{0}</xliff:g> သို့ခေါ်ဆိုမှုအားလုံးအား တဆင့်ထပ်ပို့နေသည်"</string>
<string name="sum_cfu_enabled_no_number" msgid="7287752761743377930">"ဖုန်းနံပါတ်မှာ မရှိပါ"</string>
<string name="sum_cfu_disabled" msgid="5010617134210809853">"ပိတ်ထားသည်"</string>
<string name="labelCFB" msgid="615265213360512768">"မအားလပ်ချိန်"</string>
@@ -127,7 +127,7 @@
<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>
<string name="error_updating_title" msgid="2024290892676808965">"ခေါ်ဆိုမှုဆက်တင်အမှား"</string>
- <string name="reading_settings" msgid="1605904432450871183">"ဆက်တင်များကို ဖတ်နေပါသည်…"</string>
+ <string name="reading_settings" msgid="1605904432450871183">"အပြင်အဆင်များကို ဖတ်နေပါသည်…"</string>
<string name="updating_settings" msgid="3650396734816028808">"ဆက်တင်များအား ပြင်နေသည်…"</string>
<string name="reverting_settings" msgid="7378668837291012205">"ဆက်တင်များကို ပြန်ပြောင်းနေစဉ်…"</string>
<string name="response_error" msgid="3904481964024543330">"ကွန်ယက်မှ မထင်မှတ်သောတုံ့ပြန်ချက်"</string>
@@ -140,7 +140,7 @@
<string name="radio_off_error" msgid="8321564164914232181">"ဤအပြင်အဆင်များကို မကြည့်ခင် ရေဒီယိုကို ဖွင့်ပါ"</string>
<string name="close_dialog" msgid="1074977476136119408">"OK"</string>
<string name="enable" msgid="2636552299455477603">"ဖွင့်ထားရန်"</string>
- <string name="disable" msgid="1122698860799462116">"ပိတ်ရန်"</string>
+ <string name="disable" msgid="1122698860799462116">"ပိတ်မည်"</string>
<string name="change_num" msgid="6982164494063109334">"အဆင်မြှင့်ခြင်း"</string>
<string-array name="clir_display_values">
<item msgid="8477364191403806960">"မူရင်း ကွန်ရက်"</item>
@@ -274,8 +274,8 @@
<string name="data_enable_summary" msgid="696860063456536557">"ဒေတာ သုံးစွဲမှု ခွင့်ပြုရန်"</string>
<string name="dialog_alert_title" msgid="5260471806940268478">"သတိပြုရန်"</string>
<string name="roaming" msgid="1576180772877858949">"ပြင်ပကွန်ရက်နှင့် ချိတ်ဆက်ခြင်း"</string>
- <string name="roaming_enable" msgid="6853685214521494819">"ပြင်ပကွန်ရက်သုံးလျှင် ဒေတာဝန်ဆောင်မှုများကို ချိတ်ဆက်ပါ"</string>
- <string name="roaming_disable" msgid="8856224638624592681">"ပြင်ပကွန်ရက်သုံးလျှင် ဒေတာဝန်ဆောင်မှုများကို ချိတ်ဆက်ပါ"</string>
+ <string name="roaming_enable" msgid="6853685214521494819">"ကွန်ရက်ပြင်ပဒေတာကို သုံးနေစဉ် ဒေတာဝန်ဆောင်မှုများကို ချိတ်ဆက်ပါ"</string>
+ <string name="roaming_disable" msgid="8856224638624592681">"ကွန်ရက်ပြင်ပဒေတာကို သုံးနေစဉ် ဒေတာဝန်ဆောင်မှုများကို ချိတ်ဆက်ပါ"</string>
<string name="roaming_reenable_message" msgid="1951802463885727915">"ကွန်ရက်ပြင်ပဒေတာသုံးခြင်းကို ပိတ်ထားပါသည်။ ဖွင့်ရန် တို့ပါ။"</string>
<string name="roaming_enabled_message" msgid="9022249120750897">"ပြင်ပကွန်ရက်နှင့် ချိတ်ဆက်သော အသုံးပြုခများ ကျသင့်နိုင်သည်။ ပြင်ဆင်ရန် တို့ပါ။"</string>
<string name="roaming_notification_title" msgid="3590348480688047320">"မိုဘိုင်းဒေတာ ချိတ်ဆက်မှု မရှိတော့ပါ"</string>
@@ -290,7 +290,7 @@
<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>
<string name="carrier_settings_euicc" msgid="1190237227261337749">"ဝန်ဆောင်မှုပေးသူ"</string>
- <string name="keywords_carrier_settings_euicc" msgid="8540160967922063745">"ဖုန်းကုမ္ပဏီ၊ esim၊ ဆင်းမ်ကဒ်၊ euicc၊ ဖုန်းလိုင်းများ ပြောင်းရန်၊ ဖုန်းလိုင်း ထည့်ရန်"</string>
+ <string name="keywords_carrier_settings_euicc" msgid="8540160967922063745">"ဖုန်းဝန်ဆောင်မှုပေးသူ၊ esim၊ ဆင်းမ်ကဒ်၊ euicc၊ ဖုန်းလိုင်းများ ပြောင်းရန်၊ ဖုန်းလိုင်း ထည့်ရန်"</string>
<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">"မိုဘိုင်းဒေတာ"</string>
<string name="mobile_data_settings_summary" msgid="5012570152029118471">"မိုဘိုင်းကွန်ရက်သုံးပြီး ဒေတာကို ဝင်သုံးခွင့်ပေးပါ"</string>
@@ -307,7 +307,7 @@
<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_status_subtext" msgid="1110276415078236687">"သတ်မှတ်ကာလ အမြင့်ဆုံး <xliff:g id="USED_2">%3$s</xliff:g> မှ <xliff:g id="USED_0">%1$s</xliff:g> (<xliff:g id="USED_1">%2$d</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_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> Kb/s သို့လျှော့ချလိုက်သည်"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g>٪ ကုန်ဆုံးပြီး \n နောက်သတ်မှတ်ကာလစရန် <xliff:g id="USED_1">%2$d</xliff:g> ရက်(<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -513,7 +513,7 @@
<string name="card_title_conf_call" msgid="901197309274457427">"ကွန်းဖရင့်ခေါ်ဆိုမှု"</string>
<string name="card_title_incoming_call" msgid="881424648458792430">"အဝင်ခေါ်ဆိုမှု"</string>
<string name="card_title_call_ended" msgid="650223980095026340">"ဖုန်းခေါ်ဆိုမှု ပြီးဆုံးပါပြီ"</string>
- <string name="card_title_on_hold" msgid="9028319436626975207">"ဖုန်းကိုင်ထားသည်"</string>
+ <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>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Yes"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"No"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"ပယ်ရန်"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"ဤဖုန်းသည် အရေးပေါ် ပြန်ခေါ်ရန်မုဒ်တွင် ရှိနေသည်"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">ဤဖုန်းသည် အရေးပေါ် ပြန်ခေါ်ရန်မုဒ်တွင် <xliff:g id="COUNT_1">%s</xliff:g> မိနစ်ကြာ ရှိနေပါမည်။ \nယခု ထွက်လိုပါသလား။</item>
- <item quantity="one">ဤဖုန်းသည် အရေးပေါ် ပြန်ခေါ်ရန်မုဒ်တွင် <xliff:g id="COUNT_0">%s</xliff:g> မိနစ်ကြာ ရှိနေပါမည်။ \nယခု ထွက်လိုပါသလား။</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"ဝန်ဆောင်မှု"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"စနစ်သတ်မှတ်ခြင်း"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"သတ်မှတ်မထားပါ"</string>
@@ -664,7 +658,7 @@
<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>
+ <string name="sim_label_emergency_calls" msgid="9078241989421522310">"အရေးပေါ် ခေါ်ဆိုမှုများ"</string>
<string name="sim_description_emergency_calls" msgid="5146872803938897296">"အရေးပေါ် ခေါ်ဆိုမှုသာလျှင်"</string>
<string name="sim_description_default" msgid="7474671114363724971">"SIM ကတ်၊ အပေါက်: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
<string name="accessibility_settings_activity_title" msgid="7883415189273700298">"အများသုံးနိုင်မှု"</string>
@@ -760,7 +754,7 @@
<string name="clh_callFailed_protocol_Error_unspecified_txt" msgid="9203320572562697755">"ခေါ်ဆိုမှုကို မပြုလုပ်နိုင်ပါ။ အမှားကုဒ် ၁၁၁။"</string>
<string name="clh_callFailed_interworking_unspecified_txt" msgid="7969686413930847182">"ခေါ်ဆိုမှုကို မပြုလုပ်နိုင်ပါ။ အမှားကုဒ် ၁၂၇။"</string>
<string name="labelCallBarring" msgid="4180377113052853173">"ခေါ်ဆိုမှုကို ပိတ်ပင်ရန်"</string>
- <string name="sum_call_barring_enabled" msgid="5184331188926370824">"ဖွင့်"</string>
+ <string name="sum_call_barring_enabled" msgid="5184331188926370824">"ဖွင့်ထားသည်"</string>
<string name="sum_call_barring_disabled" msgid="5699448000600153096">"ပိတ်ထားသည်"</string>
<string name="call_barring_baoc" msgid="7400892586336429326">"အထွက်ခေါ်ဆိုမှုအားလုံး"</string>
<string name="call_barring_baoc_enabled" msgid="3131509193386668182">"အထွက်ခေါ်ဆိုမှုအားလုံး ပိတ်ထားခြင်းကို ပယ်ဖျက်မလား။"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"ပြန်စရန်"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS စစ်ဆေးမှုခလုတ်ကို နှိပ်ပါ"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"ထုတ်လုပ်သူနှင့် သက်ဆိုင်သော အချက်အလက်/ဆက်တင်များ"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC အသုံးပြုနိုင်သည် -"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR ကန့်သတ်ထားသည် -"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR အသုံးပြုနိုင်သည် -"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR အခြေအနေ -"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR ကြိမ်နှုန်း -"</string>
<string name="band_mode_title" msgid="7988822920724576842">"ရေဒီယိုလိုင်းမုဒ်အဖြစ် သတ်မှတ်ပါ"</string>
<string name="band_mode_loading" msgid="795923726636735967">"ရေဒီယိုလိုင်းစာရင်းကို ဖွင့်နေသည်…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"သတ်မှတ်ရန်"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index aa66631..3495f8a 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -307,8 +307,12 @@
<string name="throttle_time_frame" msgid="1813452485948918791">"Databrukperiode"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Retningslinjer for datahastighet"</string>
<string name="throttle_help" msgid="2624535757028809735">"Les mer"</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> %%) av maksimum <xliff:g id="USED_2">%3$s</xliff:g> for perioden\nNeste periode starter om <xliff:g id="USED_3">%4$d</xliff:g> dager (<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> ٪) av maksimum <xliff:g id="USED_2">%3$s</xliff:g> for perioden"</string>
+ <!-- String.format failed for translation -->
+ <!-- no translation found for throttle_status_subtext (1110276415078236687) -->
+ <skip />
+ <!-- String.format failed for translation -->
+ <!-- no translation found for throttle_data_usage_subtext (3185429653996709840) -->
+ <skip />
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"<xliff:g id="USED_0">%1$s</xliff:g> har overskredet maksimumsgrensen\nDatahastigheten er redusert til <xliff:g id="USED_1">%2$d</xliff:g> Kb/s"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g> ٪ av syklusen er fullført\nNeste periode starter om <xliff:g id="USED_1">%2$d</xliff:g> dager (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
<string name="throttle_rate_subtext" msgid="7221971817325779535">"Datahastigheten reduseres til <xliff:g id="USED">%1$d</xliff:g> Kb/s hvis databruken overskrider grenseverdien"</string>
@@ -637,12 +641,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Ja"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Nei"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Lukk"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Telefonen er i modus for tilbakeringing av nødanrop"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Til <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">Telefonen kommer til å være i modus for tilbakeringing av nødanrop i <xliff:g id="COUNT_1">%s</xliff:g> minutter.\nVil du avslutte nå?</item>
- <item quantity="one">Telefonen kommer til å være i modus for tilbakeringing av nødanrop i <xliff:g id="COUNT_0">%s</xliff:g> minutt.\nVil du avslutte nå?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Tjeneste"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Konfigurering"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Ikke angitt>"</string>
@@ -898,11 +896,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Last inn på nytt"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Slå av/på DNS-sjekk"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Produsentspesfikk informasjon og innstillinger"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC tilgjengelig:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR begrenset:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR tilgjengelig:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR-tilstand:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR-frekvens:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Angi båndmodus for radio"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Laster inn båndlisten …"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Angi"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index b9e4432..7b891fc 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -18,7 +18,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="phoneAppLabel" product="tablet" msgid="1916019789885839910">"मोबाइल डेटा"</string>
<string name="phoneAppLabel" product="default" msgid="130465039375347763">"फोन सेवाहरू"</string>
- <string name="emergencyDialerIconLabel" msgid="8668005772339436680">"आपत्कालीन डायल गर्ने"</string>
+ <string name="emergencyDialerIconLabel" msgid="8668005772339436680">"आपतकालीन डायल गर्ने"</string>
<string name="phoneIconLabel" msgid="3015941229249651419">"फोन"</string>
<string name="fdnListLabel" msgid="4119121875004244097">"FDN सूची"</string>
<string name="unknown" msgid="8279698889921830815">"अज्ञात"</string>
@@ -83,7 +83,7 @@
<string name="smart_forwarding_settings_menu" msgid="8850429887958938540">"स्मार्ट तरिकाले फर्वार्ड गर्ने सुविधा"</string>
<string name="smart_forwarding_settings_menu_summary" msgid="5096947726032885325">"एउटा नम्बर सम्पर्क क्षेत्रबाहिर भएका बेला कल सधैँ आफ्नो अर्को नम्बरमा फर्वार्ड गर्नुहोस्"</string>
<string name="voicemail_notifications_preference_title" msgid="7829238858063382977">"सूचनाहरू"</string>
- <string name="cell_broadcast_settings" msgid="8135324242541809924">"आपत्कालीन प्रसारणहरू"</string>
+ <string name="cell_broadcast_settings" msgid="8135324242541809924">"आपतकालीन प्रसारणहरू"</string>
<string name="call_settings" msgid="3677282690157603818">"कल सेटिङहरू"</string>
<string name="additional_gsm_call_settings" msgid="1561980168685658846">"अतिरिक्त सेटिङहरू"</string>
<string name="additional_gsm_call_settings_with_label" msgid="7973920539979524908">"अतिरिक्त सेटिङ ( <xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g> )"</string>
@@ -95,7 +95,7 @@
<string name="sum_loading_settings" msgid="434063780286688775">"सेटिङहरू लोड हुँदै..."</string>
<string name="sum_hide_caller_id" msgid="131100328602371933">"बहिर्गमन कलहरूमा नम्बर लुकाइएको"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"बहिर्गमन कलहरूमा प्रदर्शित नम्बर"</string>
- <string name="sum_default_caller_id" msgid="1767070797135682959">"बहिर्गमन कलमा मेरो नम्बर देखाउन डिफल्ट अपरेटर सेटिङहरू प्रयोग गर्नुहोस्"</string>
+ <string name="sum_default_caller_id" msgid="1767070797135682959">"बहिर्गमन कलमा मेरो नम्बर देखाउन पूर्वनिर्धारित अपरेटर सेटिङहरू प्रयोग गर्नुहोस्"</string>
<string name="labelCW" msgid="8449327023861428622">"कल प्रतीक्षा गर्दै"</string>
<string name="sum_cw_enabled" msgid="3977308526187139996">"कल गरेको बखत मलाई आगमन कलको जानकारी गराउनुहोस्"</string>
<string name="sum_cw_disabled" msgid="3658094589461768637">"कल गरेको बेलामा आगमन कलहरूको बारेमा मलाई जानकारी गराउनुहोस्"</string>
@@ -136,14 +136,14 @@
<string name="stk_cc_ss_to_ussd_error" msgid="8330749347425752192">"SS अनुरोध USSD अनुरोधमा परिवर्तन गरियो"</string>
<string name="stk_cc_ss_to_ss_error" msgid="8297155544652134278">"नयाँ SS अनुरोधमा परिवर्तन गरियो"</string>
<string name="stk_cc_ss_to_dial_video_error" msgid="4255261231466032505">"SS अनुरोध भिडियो कलमा परिवर्तन गरियो"</string>
- <string name="fdn_check_failure" msgid="1833769746374185247">"तपाईंको फोन एप स्थिर डायल गर्ने नम्बर सेटिङहरू खोलिएको छ। केही कल-सम्बन्धित विशेषताहरूले कार्य गरिरहेका छैनन्।"</string>
+ <string name="fdn_check_failure" msgid="1833769746374185247">"तपाईंको फोन अनुप्रयोग स्थिर डायल गर्ने नम्बर सेटिङहरू खोलिएको छ। केही कल-सम्बन्धित विशेषताहरूले कार्य गरिरहेका छैनन्।"</string>
<string name="radio_off_error" msgid="8321564164914232181">"यी सेटिङहरू हेर्नु पहिले रेडियो खोल्नुहोस्।"</string>
<string name="close_dialog" msgid="1074977476136119408">"ठिक छ"</string>
<string name="enable" msgid="2636552299455477603">"सकृय पार्नुहोस्"</string>
<string name="disable" msgid="1122698860799462116">"निस्कृय पार्नुहोस्"</string>
- <string name="change_num" msgid="6982164494063109334">"अपडेट गर्नुहोस्"</string>
+ <string name="change_num" msgid="6982164494063109334">"अद्यावधिक गर्नुहोस्"</string>
<string-array name="clir_display_values">
- <item msgid="8477364191403806960">"नेटवर्क डिफल्ट"</item>
+ <item msgid="8477364191403806960">"नेटवर्क पूर्वनिर्धारित"</item>
<item msgid="6813323051965618926">"सङ्ख्या लुकाउनुहोस्"</item>
<item msgid="9150034130629852635">"सङ्ख्या देखाउनुहोस्"</item>
</string-array>
@@ -298,7 +298,7 @@
<string name="sim_selection_required_pref" msgid="6985901872978341314">"चयन गर्न आवश्यक छ"</string>
<string name="sim_change_data_title" msgid="9142726786345906606">"डेटा सिम परिवर्तन गर्ने हो?"</string>
<string name="sim_change_data_message" msgid="3567358694255933280">"मोबाइल डेटाका लागि <xliff:g id="OLD_SIM">%2$s</xliff:g> को सट्टा <xliff:g id="NEW_SIM">%1$s</xliff:g> को प्रयोग गर्ने हो?"</string>
- <string name="wifi_calling_settings_title" msgid="5800018845662016507">"Wi-Fi कलिङ"</string>
+ <string name="wifi_calling_settings_title" msgid="5800018845662016507">"Wi-Fi कल"</string>
<string name="video_calling_settings_title" msgid="342829454913266078">"सेवा प्रदायकको भिडियो कल"</string>
<string name="gsm_umts_options" msgid="4968446771519376808">"GSM/UMTS विकल्पहरू"</string>
<string name="cdma_options" msgid="3669592472226145665">"CDMA विकल्पहरू"</string>
@@ -318,9 +318,9 @@
<string name="cell_bc_sms_enable" msgid="2019708772024632073">"सेल प्रसारण SMS सक्षम"</string>
<string name="cell_bc_sms_disable" msgid="1214238639910875347">"सेल प्रसारण SMS अक्षम"</string>
<string name="cb_sms_settings" msgid="6858093721831312036">"सेल प्रसारण SMS सेटिङहरू"</string>
- <string name="enable_disable_emergency_broadcast" msgid="6325655044472196496">"आपत्कालीन प्रसारण"</string>
- <string name="emergency_broadcast_enable" msgid="5759610647771102442">"आपत्कालीन प्रसारण सक्षम भयो"</string>
- <string name="emergency_broadcast_disable" msgid="2844904734469323266">"आपत्कालीन प्रसारण अक्षम भयो"</string>
+ <string name="enable_disable_emergency_broadcast" msgid="6325655044472196496">"आपतकालीन प्रसारण"</string>
+ <string name="emergency_broadcast_enable" msgid="5759610647771102442">"आपतकालीन प्रसारण सक्षम भयो"</string>
+ <string name="emergency_broadcast_disable" msgid="2844904734469323266">"आपतकालीन प्रसारण अक्षम भयो"</string>
<string name="enable_disable_administrative" msgid="7825925366822117961">"प्रशासकीय"</string>
<string name="administrative_enable" msgid="5717963431079532028">"प्रशासनिक सक्षम"</string>
<string name="administrative_disable" msgid="156796633660118691">"प्रशासनिक असक्षम"</string>
@@ -473,7 +473,7 @@
<string name="simContacts_title" msgid="2714029230160136647">"सम्पर्कहरू आयात गर्न चयन गर्नुहोस्"</string>
<string name="simContacts_airplaneMode" msgid="4654884030631503808">"SIM कार्डबाट सम्पर्कहरू आयात गर्न हवाइजहाज मोड बन्द गर्नुहोस्।"</string>
<string name="enable_pin" msgid="967674051730845376">"SIM PIN सक्षम/अक्षम गर्नुहोस्"</string>
- <string name="change_pin" msgid="3657869530942905790">"SIM को PIN परिवर्तन गर्नुहोस्"</string>
+ <string name="change_pin" msgid="3657869530942905790">"SIM PIN परिवर्तन गर्नुहोस्"</string>
<string name="enter_pin_text" msgid="3182311451978663356">"SIM PIN:"</string>
<string name="oldPinLabel" msgid="8618515202411987721">"पुरानो PIN"</string>
<string name="newPinLabel" msgid="3585899083055354732">"नयाँ PIN"</string>
@@ -525,7 +525,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_ecm_emergency_only" msgid="5622379058883722080">"गैर-आपत्कालीन कल गर्न आपत्कालीन कलब्याक मोडबाट निस्कनुहोस्।"</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>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"मोबाइल नेटवर्क उपलब्ध छैन। कल गर्न तारविनाको नेटवर्कमा जडान गर्नुहोस्।"</string>
@@ -543,20 +543,20 @@
<string name="incall_error_supp_service_hold" msgid="8535056414643540997">"कल सञ्चालन गर्न सकिँदैन।"</string>
<string name="incall_error_wfc_only_no_wireless_network" msgid="5860742792811400109">"एक कल गर्न एक ताररहितको सञ्जालमा जडान गर्नुहोस्।"</string>
<string name="incall_error_promote_wfc" msgid="9164896813931363415">"कल गर्नका लागि Wi-Fi कलिङ सक्षम गर्नुहोस्।"</string>
- <string name="emergency_information_hint" msgid="9208897544917793012">"आपत्कालीन जानकारी"</string>
+ <string name="emergency_information_hint" msgid="9208897544917793012">"आपतकालीन जानकारी"</string>
<string name="emergency_information_owner_hint" msgid="6256909888049185316">"मालिक"</string>
<string name="emergency_information_confirm_hint" msgid="5109017615894918914">"जानकारी हेर्न पुनः ट्याप गर्नुहोस्"</string>
- <string name="emergency_enable_radio_dialog_title" msgid="2667568200755388829">"आपत्कालीन कल"</string>
- <string name="single_emergency_number_title" msgid="8413371079579067196">"आपत्कालीन नम्बर"</string>
- <string name="numerous_emergency_numbers_title" msgid="8972398932506755510">"आपत्कालीन नम्बरहरू"</string>
+ <string name="emergency_enable_radio_dialog_title" msgid="2667568200755388829">"आपतकालीन कल"</string>
+ <string name="single_emergency_number_title" msgid="8413371079579067196">"आपतकालीन नम्बर"</string>
+ <string name="numerous_emergency_numbers_title" msgid="8972398932506755510">"आपतकालीन नम्बरहरू"</string>
<string name="emergency_call_shortcut_hint" msgid="1290485125107779500">"<xliff:g id="EMERGENCY_NUMBER">%s</xliff:g> मा कल गर्न फेरि ट्याप गर्नुहोस्"</string>
<string name="emergency_enable_radio_dialog_message" msgid="1695305158151408629">"रेडियो खोल्दै..."</string>
<string name="emergency_enable_radio_dialog_retry" msgid="4329131876852608587">"कुनै सेवा छैन। फेरि प्रयास गर्दै ..."</string>
- <string name="radio_off_during_emergency_call" msgid="8011154134040481609">"आपत्कालीन कलको समयमा हवाइजहाज मोडमा प्रविष्ट गर्न सक्दैन।"</string>
- <string name="dial_emergency_error" msgid="825822413209026039">"कल गर्न सकिँदैन। <xliff:g id="NON_EMERGENCY_NUMBER">%s</xliff:g> आपत्कालीन नम्बर होइन।"</string>
- <string name="dial_emergency_empty_error" msgid="2785803395047793634">"कल गर्न सकिँदैन। आपत्कालीन नम्बर डायल गर्नुहोस्।"</string>
- <string name="dial_emergency_calling_not_available" msgid="6485846193794727823">"आपत्कालीन कल सेवा उपलब्ध छैन"</string>
- <string name="pin_puk_system_user_only" msgid="1045147220686867922">"डिभाइसका मालिक मात्र PIN/PUK कोडहरू प्रविष्टि गर्न सक्नुहुन्छ।"</string>
+ <string name="radio_off_during_emergency_call" msgid="8011154134040481609">"आपतकालीन कलको समयमा हवाइजहाज मोडमा प्रविष्ट गर्न सक्दैन।"</string>
+ <string name="dial_emergency_error" msgid="825822413209026039">"कल गर्न सकिँदैन। <xliff:g id="NON_EMERGENCY_NUMBER">%s</xliff:g> आपतकालीन नम्बर होइन।"</string>
+ <string name="dial_emergency_empty_error" msgid="2785803395047793634">"कल गर्न सकिँदैन। आपतकालीन नम्बर डायल गर्नुहोस्।"</string>
+ <string name="dial_emergency_calling_not_available" msgid="6485846193794727823">"आपतकालीन कल सेवा उपलब्ध छैन"</string>
+ <string name="pin_puk_system_user_only" msgid="1045147220686867922">"यन्त्रका मालिक मात्र PIN/PUK कोडहरू प्रविष्टि गर्न सक्नुहुन्छ।"</string>
<string name="police_type_description" msgid="2819533883972081757">"प्रहरी"</string>
<string name="ambulance_type_description" msgid="6798237503553180461">"एम्बुलेन्स सेवा"</string>
<string name="fire_type_description" msgid="6565200468934914930">"दमकल सेवा"</string>
@@ -581,7 +581,7 @@
<string name="failedToImportSingleContactMsg" msgid="228095510489830266">"सम्पर्क आयात गर्न असफल"</string>
<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_title" msgid="3075948111362818043">"द्रुत पाठ सन्देश (RTT) कल"</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>
@@ -620,41 +620,35 @@
<string name="ota_try_again" msgid="6914781945599998550">"फेरि प्रयास गर्नुहोस्"</string>
<string name="ota_next" msgid="2041016619313475914">"अर्को"</string>
<string name="ecm_exit_dialog" msgid="4200691880721429078">"EcmExitDialog"</string>
- <string name="phone_entered_ecm_text" msgid="8431238297843035842">"आपत्कालीन कलब्याक मोड प्रविष्टि गरियो"</string>
- <string name="phone_in_ecm_notification_title" msgid="6825016389926367946">"आपत्कालीन कलब्याक मोड"</string>
+ <string name="phone_entered_ecm_text" msgid="8431238297843035842">"आपतकालीन कलब्याक मोड प्रविष्टि गरियो"</string>
+ <string name="phone_in_ecm_notification_title" msgid="6825016389926367946">"आपतकालीन कलब्याक मोड"</string>
<string name="phone_in_ecm_call_notification_text" msgid="653972232922670335">"डेटा जडान अक्षम भयो"</string>
- <string name="phone_in_ecm_notification_complete_time" msgid="7341624337163082759">"<xliff:g id="COMPLETETIME">%s</xliff:g> सम्म डेटा कनेक्ट भएको छैन"</string>
+ <string name="phone_in_ecm_notification_complete_time" msgid="7341624337163082759">"<xliff:g id="COMPLETETIME">%s</xliff:g> सम्म डेटा जडान भएको छैन"</string>
<plurals name="alert_dialog_exit_ecm" formatted="false" msgid="5425906903766466743">
- <item quantity="other"> फोन आपत्कालीन कलब्याक मोडमा <xliff:g id="COUNT_1">%s</xliff:g> मिनेटको लागि हुनेछ। यस मोडको अवस्थामा एक डेटा जडान प्रयोग गरेर कुनै पनि एपहरू प्रयोग गर्न सकिँदैन। के तपाईं अहिले निस्कन चाहनुहुन्छ?</item>
- <item quantity="one"> फोन आपत्कालीन कलब्याक मोडमा <xliff:g id="COUNT_0">%s</xliff:g> मिनेटको लागि हुनेछ। यस मोडको समयमा एक डेटा जडान प्रयोग गरेर कुनै पनि एपहरू प्रयोग गर्न सकिँदैन। के तपाईं अहिले निस्कन चाहनुहुन्छ?</item>
+ <item quantity="other"> फोन आपतकालीन कलब्याक मोडमा <xliff:g id="COUNT_1">%s</xliff:g> मिनेटको लागि हुनेछ। यस मोडको अवस्थामा एक डेटा जडान प्रयोग गरेर कुनै पनि अनुप्रयोगहरू प्रयोग गर्न सकिँदैन। के तपाईं अहिले निस्कन चाहनुहुन्छ?</item>
+ <item quantity="one"> फोन आपतकालीन कलब्याक मोडमा <xliff:g id="COUNT_0">%s</xliff:g> मिनेटको लागि हुनेछ। यस मोडको समयमा एक डेटा जडान प्रयोग गरेर कुनै पनि अनुप्रयोगहरू प्रयोग गर्न सकिँदैन। के तपाईं अहिले निस्कन चाहनुहुन्छ?</item>
</plurals>
<plurals name="alert_dialog_not_avaialble_in_ecm" formatted="false" msgid="1152682528741457004">
- <item quantity="other"> आपत्कालीन कलब्याक मोडको समयमा चयनित कार्य उपलब्ध छैन। फोन यस मोडमा <xliff:g id="COUNT_1">%s</xliff:g> मिनेटको लागि हुनेछ। के तपाईं अहिले निस्कन चाहनुहुन्छ?</item>
- <item quantity="one"> आपत्कालीन कलब्याक मोडको समयमा चयनित कार्य उपलब्ध छैन। फोन यस मोडमा <xliff:g id="COUNT_0">%s</xliff:g> मिनेटको लागि हुनेछ। के तपाईं अहिले निस्कन चाहनुहुन्छ?</item>
+ <item quantity="other"> आपतकालीन कलब्याक मोडको समयमा चयनित कार्य उपलब्ध छैन। फोन यस मोडमा <xliff:g id="COUNT_1">%s</xliff:g> मिनेटको लागि हुनेछ। के तपाईं अहिले निस्कन चाहनुहुन्छ?</item>
+ <item quantity="one"> आपतकालीन कलब्याक मोडको समयमा चयनित कार्य उपलब्ध छैन। फोन यस मोडमा <xliff:g id="COUNT_0">%s</xliff:g> मिनेटको लागि हुनेछ। के तपाईं अहिले निस्कन चाहनुहुन्छ?</item>
</plurals>
- <string name="alert_dialog_in_ecm_call" msgid="1207545603149771978">"आपत्कालीन कल हुँदा चयन भएको कार्य उपलब्ध हुँदैन।"</string>
- <string name="progress_dialog_exiting_ecm" msgid="9159080081676927217">"आपत्कालीन कलब्याक मोडबाट निस्कदै"</string>
+ <string name="alert_dialog_in_ecm_call" msgid="1207545603149771978">"आपतकालीन कल हुँदा चयन भएको कार्य उपलब्ध हुँदैन।"</string>
+ <string name="progress_dialog_exiting_ecm" msgid="9159080081676927217">"आपतकालीन कलब्याक मोडबाट निस्कदै"</string>
<string name="alert_dialog_yes" msgid="3532525979632841417">"हो"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"होइन"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"खारेज गर्नुहोस्"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"यो फोन आपत्कालीन कलब्याक मोडमा छ"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">यो फोन <xliff:g id="COUNT_1">%s</xliff:g> मिनेटका लागि आपत्कालीन कलब्याक मोडमा हुने छ।\nतपाईं अहिले नै बाहिर निस्कन चाहनुहुन्छ?</item>
- <item quantity="one">यो फोन <xliff:g id="COUNT_0">%s</xliff:g> मिनेटका लागि आपत्कालीन कलब्याक मोडमा हुने छ।\nतपाईं अहिले नै बाहिर निस्कन चाहनुहुन्छ?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"सेवा"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"सेटअप"</string>
- <string name="voicemail_number_not_set" msgid="8831561283386938155">"सेट गरिएको छैन"</string>
+ <string name="voicemail_number_not_set" msgid="8831561283386938155">"सेट गरेको छैन"</string>
<string name="other_settings" msgid="8895088007393598447">"अन्य कल सेटिङहरू"</string>
<string name="calling_via_template" msgid="1791323450703751750">"<xliff:g id="PROVIDER_NAME">%s</xliff:g> को मार्फत कल गर्दै"</string>
- <string name="contactPhoto" msgid="7885089213135154834">"सम्पर्क फोटो"</string>
+ <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="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="pane_title_emergency_dialpad" msgid="3627372514638694401">"आपतकालीन डायलप्याड"</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>
@@ -678,7 +672,7 @@
<string name="callFailed_wifi_lost" msgid="1788036730589163141">"Wi-Fi जडान विच्छेद भयो। कल समाप्त भयो।"</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">"यो स्थानमा Wi-Fi कलिङमार्फत आपत्कालीन कलहरू गर्ने सुविधा उपलब्ध छैन।"</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_continue_label" msgid="5177011752453506371">"जारी राख्नुहोस्"</string>
@@ -810,7 +804,7 @@
<string name="supp_service_forwarded_call" msgid="6475776013771821457">"कल फर्वार्ड गरियो।"</string>
<string name="supp_service_conference_call" msgid="4004193534408317148">"सम्मेलन कलमा सामेल हुँदै छ।"</string>
<string name="supp_service_held_call_released" msgid="2847835124639112410">"होल्डमा राखिएको कललाई विच्छेद गरियो।"</string>
- <string name="callFailed_otasp_provisioning_in_process" msgid="3345666183602879326">"डिभाइसमा हाल सेवाहरूको व्यवस्था गरिँदै हुनाले कल गर्न सकिँदैन।"</string>
+ <string name="callFailed_otasp_provisioning_in_process" msgid="3345666183602879326">"यन्त्रमा हाल सेवाहरूको व्यवस्था गरिँदै हुनाले कल गर्न सकिँदैन।"</string>
<string name="callFailed_already_dialing" msgid="7250591188960691086">"अर्को बहिर्गमन कल पहिले नै डायल भएका हुनाले कल गर्न सकिँदैन।"</string>
<string name="callFailed_already_ringing" msgid="2376603543544289303">"जवाफ नदिइएको आगमन कल जारी रहेकाले नयाँ कल गर्न सकिँदैन। कुनै नयाँ कल गर्नुअघि आगमन कलको जवाफ दिनुहोस् वा त्यसलाई अस्वीकार गर्नुहोस्।"</string>
<string name="callFailed_calling_disabled" msgid="5010992739401206283">"ro.telephony.disable-call प्रणालीको गुण प्रयोग गरेर कल गर्ने सुविधा असक्षम पारिएको हुनाले कल गर्न सकिँदैन।"</string>
@@ -845,7 +839,7 @@
<string name="radio_info_ims_reg_status" msgid="25582845222446390">"IMS को दर्ता: <xliff:g id="STATUS">%1$s</xliff:g>\nभ्वाइस ओभर LTE: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nभ्वाइस ओभर WiFi: <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>
+ <string name="radioInfo_service_emergency" msgid="4763879891415016848">"आपतकालीन कल मात्र"</string>
<string name="radioInfo_service_off" msgid="3456583511226783064">"रेडियो निष्क्रिय छ"</string>
<string name="radioInfo_roaming_in" msgid="3156335577793145965">"रोमिङ"</string>
<string name="radioInfo_roaming_not" msgid="1904547918725478110">"रोमिङमा छैन"</string>
@@ -864,7 +858,7 @@
<string name="radioInfo_lac" msgid="3892986460272607013">"LAC"</string>
<string name="radioInfo_cid" msgid="1423185536264406705">"CID"</string>
<string name="radio_info_subid" msgid="6839966868621703203">"हालको subId:"</string>
- <string name="radio_info_dds" msgid="1122593144425697126">"डिफल्ट डेटा SIM को SubId:"</string>
+ <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>
@@ -894,15 +888,10 @@
<string name="radio_info_http_client_test" msgid="1329583721088428238">"HTTP क्लाइन्टको परीक्षण:"</string>
<string name="ping_test_label" msgid="448617502935719694">"पिङसम्बन्धी परीक्षण सञ्चालन गर्नुहोस्"</string>
<string name="radio_info_smsc_label" msgid="3749927072726033763">"SMSC:"</string>
- <string name="radio_info_smsc_update_label" msgid="5141996256097115753">"अपडेट गर्नुहोस्"</string>
+ <string name="radio_info_smsc_update_label" msgid="5141996256097115753">"अद्यावधिक गर्नुहोस्"</string>
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"पुनः ताजा गर्नुहोस्"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS को जाँचलाई टगल गर्नुहोस्"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-विशिष्ट जानकारी/सेटिङ"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC उपलब्ध छ:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR प्रतिबन्धित छ:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR उपलब्ध छ:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR को स्थिती:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR फ्रिक्वेन्सी:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"रेडियोको ब्यान्डसम्बन्धी मोडलाई सेट गर्नुहोस्"</string>
<string name="band_mode_loading" msgid="795923726636735967">"ब्यान्डको सूची लोड गर्दै…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"सेट गर्नुहोस्"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index cff7271..5da9697 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -506,7 +506,7 @@
<string name="pin2_attempts" msgid="5625178102026453023">\n"Je hebt nog <xliff:g id="NUMBER">%d</xliff:g> pogingen."</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>
+ <string name="doneButton" msgid="7371209609238460207">"Gereed"</string>
<string name="voicemail_settings_number_label" msgid="1265118640154688162">"Voicemailnummer"</string>
<string name="card_title_dialing" msgid="8742182654254431781">"Kiezen"</string>
<string name="card_title_redialing" msgid="18130232613559964">"Opnieuw bellen"</string>
@@ -579,8 +579,8 @@
<string name="importToFDNfromContacts" msgid="5068664870738407341">"Importeren uit contacten"</string>
<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_title" msgid="4127986689621125468">"Gehoorapparaten"</string>
+ <string name="hac_mode_summary" msgid="7774989500136009881">"Compatibiliteit voor gehoorapparaat inschakelen"</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>
@@ -602,9 +602,9 @@
<string name="ota_title_activate" msgid="4049645324841263423">"Je telefoon activeren"</string>
<string name="ota_touch_activate" msgid="838764494319694754">"Je moet een speciaal gesprek voeren om je telefoonservice te activeren. \n\nNadat je op \'Activeren\' hebt gedrukt, luister je naar de instructies om je telefoon te activeren."</string>
<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_hfa_activation_dialog_message" msgid="7921718445773342996">"De telefoon activeert je mobiele gegevensservice.\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 gegevensnetwerken (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_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>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Ja"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Nee"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Negeren"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"De modus voor noodoproepen is geactiveerd voor de telefoon"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Tot <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">De telefoon blijft <xliff:g id="COUNT_1">%s</xliff:g> minuten in de modus voor noodoproepen.\nWil je nu afsluiten?</item>
- <item quantity="one">De telefoon blijft <xliff:g id="COUNT_0">%s</xliff:g> minuut in de modus voor noodoproepen.\nWil je nu afsluiten?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Service"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Instellen"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Niet ingesteld>"</string>
@@ -898,11 +892,6 @@
<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="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>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR beschikbaar:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR-status:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR-frequentie:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Modus voor radioband instellen"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Bandlijst laden…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Instellen"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 42386df..3b2c3cd 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -29,8 +29,8 @@
<string name="default_carrier_mmi_msg_title" msgid="7754317179938537213">"ବାହକ ମେସେଜ୍"</string>
<string name="mmiStarted" msgid="9212975136944568623">"MMI କୋଡ୍କୁ ଆରମ୍ଭ କରାଯାଇଛି"</string>
<string name="ussdRunning" msgid="1163586813106772717">"USSD କୋଡ୍ ରନ୍ କରୁଛି…"</string>
- <string name="mmiCancelled" msgid="5339191899200678272">"MMI କୋଡ୍କୁ ବାତିଲ୍ କରାଯାଇଛି"</string>
- <string name="cancel" msgid="8984206397635155197">"ବାତିଲ୍ କରନ୍ତୁ"</string>
+ <string name="mmiCancelled" msgid="5339191899200678272">"MMI କୋଡ୍କୁ କ୍ୟାନ୍ସଲ୍ କରାଯାଇଛି"</string>
+ <string name="cancel" msgid="8984206397635155197">"କ୍ୟାନ୍ସଲ୍ କରନ୍ତୁ"</string>
<string name="enter_input" msgid="6193628663039958990">"USSD ମେସେଜ୍ ନିଶ୍ଚିତରୂପେ <xliff:g id="MIN_LEN">%1$d</xliff:g> ଓ <xliff:g id="MAX_LEN">%2$d</xliff:g>ଟି ଅକ୍ଷର ମଧ୍ୟରେ ରହିବ। ଦୟାକରି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
<string name="manageConferenceLabel" msgid="8415044818156353233">"କନଫରେନ୍ସ କଲ୍କୁ ପରିଚାଳନା କରନ୍ତୁ"</string>
<string name="ok" msgid="7818974223666140165">"ଠିକ୍ ଅଛି"</string>
@@ -74,7 +74,7 @@
<string name="phone_accounts_configure_account_settings" msgid="6622119715253196586">"ଆକାଉଣ୍ଟ ସେଟିଙ୍ଗକୁ କନଫିଗର୍ କରନ୍ତୁ"</string>
<string name="phone_accounts_all_calling_accounts" msgid="1609600743500618823">"ସମସ୍ତ କଲିଙ୍ଗ ଆକାଉଣ୍ଟ"</string>
<string name="phone_accounts_all_calling_accounts_summary" msgid="2214134955430107240">"କଲ୍ କରିପାରୁଥିବା ଆକାଉଣ୍ଟକୁ ଚୟନ କରନ୍ତୁ"</string>
- <string name="wifi_calling" msgid="3650509202851355742">"ୱାଇ-ଫାଇ କଲିଂ"</string>
+ <string name="wifi_calling" msgid="3650509202851355742">"ୱାଇ-ଫାଇ କଲିଙ୍ଗ"</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>
@@ -84,7 +84,7 @@
<string name="smart_forwarding_settings_menu_summary" msgid="5096947726032885325">"ଯେତେବେଳେ ଏକ ନମ୍ବର୍ ଅପହଞ୍ଚ ଦୂରରେ ରହିଥାଏ, ସେତେବେଳେ ସର୍ବଦା ଆପଣଙ୍କର ଅନ୍ୟ ନମ୍ବର୍କୁ କଲ୍ଗୁଡ଼ିକ ଫର୍ୱର୍ଡ ହେବ"</string>
<string name="voicemail_notifications_preference_title" msgid="7829238858063382977">"ବିଜ୍ଞପ୍ତି"</string>
<string name="cell_broadcast_settings" msgid="8135324242541809924">"ଜରୁରିକାଳୀନ ପ୍ରସାରଣ"</string>
- <string name="call_settings" msgid="3677282690157603818">"କଲ୍ ସେଟିଂସ୍"</string>
+ <string name="call_settings" msgid="3677282690157603818">"କଲ୍ ସେଟିଙ୍ଗ"</string>
<string name="additional_gsm_call_settings" msgid="1561980168685658846">"ଅତିରିକ୍ତ ସେଟିଙ୍ଗ"</string>
<string name="additional_gsm_call_settings_with_label" msgid="7973920539979524908">"ଅତିରିକ୍ତ ସେଟିଙ୍ଗ(<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="sum_gsm_call_settings" msgid="7964692601608878138">"ଅତିରିକ୍ତ କେବଳ GSM କଲ୍ ସେଟିଙ୍ଗ"</string>
@@ -95,40 +95,40 @@
<string name="sum_loading_settings" msgid="434063780286688775">"ସେଟିଙ୍ଗ ଲୋଡ୍ ହେଉଛି…"</string>
<string name="sum_hide_caller_id" msgid="131100328602371933">"ଆଉଟ୍ଗୋଇଙ୍ଗ କଲ୍ରେ ଲୁଚିଥିବା କଲ୍"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"ଆଉଟ୍ଗୋଇଙ୍ଗ କଲ୍ରେ ନମ୍ବର୍ ଡିସପ୍ଲେ ହୋଇଛି"</string>
- <string name="sum_default_caller_id" msgid="1767070797135682959">"ଆଉଟ୍ଗୋଇଙ୍ଗ କଲ୍ରେ ମୋର ନମ୍ବର୍କୁ ଡିସପ୍ଲେ କରିବା ପାଇଁ ଡିଫଲ୍ଟ ଅପରେଟର୍ ସେଟିଂସକୁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
+ <string name="sum_default_caller_id" msgid="1767070797135682959">"ଆଉଟ୍ଗୋଇଙ୍ଗ କଲ୍ରେ ମୋର ନମ୍ବର୍କୁ ଦେଖାଇବା ପାଇଁ ଡିଫଲ୍ଟ ଅପରେଟର୍ ସେଟିଙ୍ଗକୁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
<string name="labelCW" msgid="8449327023861428622">"କଲ୍ ଅପେକ୍ଷାରେ ଅଛି"</string>
- <string name="sum_cw_enabled" msgid="3977308526187139996">"ଗୋଟିଏ କଲ୍ ସମୟରେ ଇନକମିଙ୍ଗ କଲ୍ ପାଇଁ ମୋତେ ସୂଚିତ କରନ୍ତୁ"</string>
- <string name="sum_cw_disabled" msgid="3658094589461768637">"ଗୋଟିଏ କଲ୍ ସମୟରେ ଇନକମିଙ୍ଗ କଲ୍ ପାଇଁ ମୋତେ ସୂଚିତ କରନ୍ତୁ"</string>
- <string name="call_forwarding_settings" msgid="8937130467468257671">"କଲ୍ ଫରୱାର୍ଡିଂ ସେଟିଂସ୍"</string>
- <string name="call_forwarding_settings_with_label" msgid="2345432813399564272">"କଲ୍ ଫର୍ୱାର୍ଡିଂ ସେଟିଂସ୍ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
+ <string name="sum_cw_enabled" msgid="3977308526187139996">"ଗୋଟିଏ କଲ୍ କରିବା ବେଳେ ଇନକମିଙ୍ଗ କଲ୍ ପାଇଁ ମୋତେ ସୂଚିତ କରନ୍ତୁ"</string>
+ <string name="sum_cw_disabled" msgid="3658094589461768637">"ଗୋଟିଏ କଲ୍ କରିବା ବେଳେ ଇନକମିଙ୍ଗ କଲ୍ ପାଇଁ ମୋତେ ସୂଚିତ କରନ୍ତୁ"</string>
+ <string name="call_forwarding_settings" msgid="8937130467468257671">"କଲ୍ ଫରୱାର୍ଡିଙ୍ଗ ସେଟିଙ୍ଗ"</string>
+ <string name="call_forwarding_settings_with_label" msgid="2345432813399564272">"କଲ୍ ଫର୍ୱାର୍ଡିଙ୍ଗ ସେଟିଙ୍ଗ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="labelCF" msgid="3578719437928476078">"କଲ୍ ଫର୍ୱାର୍ଡିଙ୍ଗ"</string>
<string name="labelCFU" msgid="8870170873036279706">"ସର୍ବଦା ଫର୍ୱାର୍ଡ କରନ୍ତୁ"</string>
<string name="messageCFU" msgid="1361806450979589744">"ସର୍ବଦା ଏହି ନମ୍ବର୍କୁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
<string name="sum_cfu_enabled_indicator" msgid="9030139213402432776">"ସମସ୍ତ କଲ୍ ଫରୱାର୍ଡ କରାଯାଉଛି"</string>
<string name="sum_cfu_enabled" msgid="5806923046528144526">"<xliff:g id="PHONENUMBER">{0}</xliff:g>କୁ ସମସ୍ତ କଲ୍ ଫରୱାର୍ଡ କରାଯାଉଛି"</string>
<string name="sum_cfu_enabled_no_number" msgid="7287752761743377930">"ନମ୍ବର୍ ଉପଲବ୍ଧ ନାହିଁ"</string>
- <string name="sum_cfu_disabled" msgid="5010617134210809853">"ବନ୍ଦ ଅଛି"</string>
+ <string name="sum_cfu_disabled" msgid="5010617134210809853">"ଅଫ୍ ଅଛି"</string>
<string name="labelCFB" msgid="615265213360512768">"ବ୍ୟସ୍ତଥିବା ବେଳେ"</string>
<string name="messageCFB" msgid="1958017270393563388">"ବ୍ୟସ୍ତ ଥିବା ବେଳେ ନମ୍ବର୍"</string>
<string name="sum_cfb_enabled" msgid="332037613072049492">"<xliff:g id="PHONENUMBER">{0}</xliff:g>କୁ ଫରୱାର୍ଡ କରାଯାଉଛି"</string>
- <string name="sum_cfb_disabled" msgid="3589913334164866035">"ବନ୍ଦ ଅଛି"</string>
+ <string name="sum_cfb_disabled" msgid="3589913334164866035">"ଅଫ୍ ଅଛି"</string>
<string name="disable_cfb_forbidden" msgid="4831494744351633961">"ଆପଣଙ୍କର ଫୋନ୍ ବ୍ୟସ୍ତ ଥିବାବେଳେ ଆପଣଙ୍କର ଅପରେଟର୍ କଲ୍ ଫର୍ୱାର୍ଡିଙ୍ଗକୁ ଅକ୍ଷମ କରିବାରେ ସପୋର୍ଟ କରିବେ ନାହିଁ।"</string>
<string name="labelCFNRy" msgid="3403533792248457946">"ଉତ୍ତର ଦେଉନଥିବା ବେଳେ"</string>
<string name="messageCFNRy" msgid="7644434155765359009">"ଉତ୍ତର ଦେଉନଥିବା ବେଳେ ନମ୍ବର୍"</string>
<string name="sum_cfnry_enabled" msgid="3000500837493854799">"<xliff:g id="PHONENUMBER">{0}</xliff:g>କୁ ଫରୱାର୍ଡ କରାଯାଉଛି"</string>
- <string name="sum_cfnry_disabled" msgid="1990563512406017880">"ବନ୍ଦ ଅଛି"</string>
+ <string name="sum_cfnry_disabled" msgid="1990563512406017880">"ଅଫ୍ ଅଛି"</string>
<string name="disable_cfnry_forbidden" msgid="3174731413216550689">"ଆପଣଙ୍କର ଫୋନ୍ ଉତ୍ତର ଦେଇନପାରିବାବେଳେ ଆପଣଙ୍କର ଅପରେଟର୍ କଲ୍ ଫର୍ୱାର୍ଡିଙ୍ଗକୁ ଅକ୍ଷମ କରିବାରେ ସପୋର୍ଟ କରିବେ ନାହିଁ।"</string>
<string name="labelCFNRc" msgid="4163399350778066013">"ଅପହଞ୍ଚ ଦୂରତାରେ ଥିବା ବେଳେ"</string>
<string name="messageCFNRc" msgid="6980340731313007250">"ଅପହଞ୍ଚ ଦୂରତାରେ ଥିବା ବେଳେ ନମ୍ବର୍"</string>
<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="sum_cfnrc_disabled" msgid="739289696796917683">"ଅଫ୍ ଅଛି"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"ଆପଣଙ୍କର ଫୋନ୍ ଅପହଞ୍ଚ ଦୂରତାରେ ଥିବାବେଳେ ଆପଣଙ୍କର କେରିଅର୍ କଲ୍ ଫର୍ୱାର୍ଡିଙ୍ଗକୁ ଅକ୍ଷମ କରିବାରେ ସପୋର୍ଟ କରିବେ ନାହିଁ।"</string>
- <string name="updating_title" msgid="6130548922615719689">"କଲ୍ ସେଟିଂସ୍"</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>
<string name="error_updating_title" msgid="2024290892676808965">"କଲ୍ ସେଟିଙ୍ଗରେ ତ୍ରୁଟି"</string>
- <string name="reading_settings" msgid="1605904432450871183">"ସେଟିଂସକୁ ପଢ଼ାଯାଉଛି…"</string>
- <string name="updating_settings" msgid="3650396734816028808">"ସେଟିଂସ୍ ଅପଡେଟ୍ ହେଉଛି…"</string>
+ <string name="reading_settings" msgid="1605904432450871183">"ସେଟିଙ୍ଗକୁ ପଢ଼ାଯାଉଛି…"</string>
+ <string name="updating_settings" msgid="3650396734816028808">"ସେଟିଙ୍ଗ ଅପଡେଟ୍ ହେଉଛି…"</string>
<string name="reverting_settings" msgid="7378668837291012205">"ସେଟିଙ୍ଗକୁ ଫେରାଇଅଣାଯାଉଛି…"</string>
<string name="response_error" msgid="3904481964024543330">"ନେଟ୍ୱର୍କରୁ ଆଶା କରାଯାଇନଥିବା ଉତ୍ତର ମିଳିଲା।"</string>
<string name="exception_error" msgid="330994460090467">"ନେଟ୍ୱର୍କ କିମ୍ବା SIM କାର୍ଡରେ ତ୍ରୁଟି।"</string>
@@ -288,7 +288,7 @@
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"ଅନ୍ୟ SIM ବ୍ୟବହାର କରିବା ସମୟରେ <xliff:g id="CARRIER_NAME">%1$s</xliff:g> କଲ୍ ଏବଂ ଡାଟା ସେବାଗୁଡ଼ିକ ବ୍ଲକ୍ କରାଯାଇପାରେ।"</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>
+ <string name="advanced_options_title" msgid="9208195294513520934">"ଆଧୁନିକ"</string>
<string name="carrier_settings_euicc" msgid="1190237227261337749">"ନେଟୱର୍କ କ୍ୟାରିଅର"</string>
<string name="keywords_carrier_settings_euicc" msgid="8540160967922063745">"ମୋବାଇଲ୍ ଓ ଇଣ୍ଟରନେଟ୍ ସେବା ପ୍ରଦାନକାରୀ କମ୍ପାନୀ, ଇସିମ୍, ସିମ୍, euicc, ମୋବାଇଲ୍ ଓ ଇଣ୍ଟରନେଟ୍ ସେବା ପ୍ରଦାନକାରୀ କମ୍ପାନୀକୁ ସ୍ଵିଚ୍ କରନ୍ତୁ, ମୋବାଇଲ୍ ଓ ଇଣ୍ଟରନେଟ୍ ସେବା ପ୍ରଦାନକାରୀ କମ୍ପାନୀକୁ ଯୋଡ଼ନ୍ତୁ"</string>
<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>
@@ -298,7 +298,7 @@
<string name="sim_selection_required_pref" msgid="6985901872978341314">"ମନୋନୟନ ଆବଶ୍ୟକ"</string>
<string name="sim_change_data_title" msgid="9142726786345906606">"ଡାଟା ସିମ୍ ବଦଳାଇବେ?"</string>
<string name="sim_change_data_message" msgid="3567358694255933280">"ମୋବାଇଲ୍ ଡାଟା ପାଇଁ <xliff:g id="OLD_SIM">%2$s</xliff:g> ବଦଳରେ <xliff:g id="NEW_SIM">%1$s</xliff:g> ବ୍ୟବହାର କରିବେ?"</string>
- <string name="wifi_calling_settings_title" msgid="5800018845662016507">"ୱାଇ-ଫାଇ କଲିଂ"</string>
+ <string name="wifi_calling_settings_title" msgid="5800018845662016507">"ୱାଇ-ଫାଇ କଲିଙ୍ଗ"</string>
<string name="video_calling_settings_title" msgid="342829454913266078">"କେରିଅର୍ ଭିଡିଓ କଲିଙ୍ଗ"</string>
<string name="gsm_umts_options" msgid="4968446771519376808">"GSM/UMTS ବିକଳ୍ପ"</string>
<string name="cdma_options" msgid="3669592472226145665">"CDMA ବିକଳ୍ପ"</string>
@@ -426,7 +426,7 @@
<string name="cdma_lte_data_service" msgid="359786441782404562">"ଡାଟା ସେବାକୁ ସେଟଅପ୍ କରନ୍ତୁ"</string>
<string name="carrier_settings_title" msgid="6292869148169850220">"କେରିଅର୍ ସେଟିଙ୍ଗ"</string>
<string name="fdn" msgid="2545904344666098749">"ଫିକ୍ସଡ୍ ଡାଏଲିଙ୍ଗ ନମ୍ବର୍"</string>
- <string name="fdn_with_label" msgid="6412087553365709494">"ଫିକ୍ସଡ୍ ଡାଏଲିଂ ନମ୍ବର୍ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
+ <string name="fdn_with_label" msgid="6412087553365709494">"ଫିକ୍ସଡ୍ ଡାଏଲିଙ୍ଗ ନମ୍ବର୍ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="manage_fdn_list" msgid="3341716430375195441">"FDN ତାଲିକା"</string>
<string name="fdn_list_with_label" msgid="1409655283510382556">"FDN ତାଲିକା (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="fdn_activation" msgid="2178637004710435895">"FDNକୁ ସକ୍ରିୟ କରିବା"</string>
@@ -546,7 +546,7 @@
<string name="emergency_information_hint" msgid="9208897544917793012">"ଜରୁରୀକାଳୀନ ସୂଚନା"</string>
<string name="emergency_information_owner_hint" msgid="6256909888049185316">"ମାଲିକ"</string>
<string name="emergency_information_confirm_hint" msgid="5109017615894918914">"ସୂଚନା ଦେଖିବାକୁ ପୁଣିଥରେ ଟାପ୍ କରନ୍ତୁ"</string>
- <string name="emergency_enable_radio_dialog_title" msgid="2667568200755388829">"ଜରୁରୀକାଳୀନ କଲ୍"</string>
+ <string name="emergency_enable_radio_dialog_title" msgid="2667568200755388829">"ଜରୁରୀକାଳିନ କଲ୍"</string>
<string name="single_emergency_number_title" msgid="8413371079579067196">"ଜରୁରୀକାଳୀନ ନମ୍ବର୍"</string>
<string name="numerous_emergency_numbers_title" msgid="8972398932506755510">"ଜରୁରୀକାଳୀନ ନମ୍ବର୍"</string>
<string name="emergency_call_shortcut_hint" msgid="1290485125107779500">"<xliff:g id="EMERGENCY_NUMBER">%s</xliff:g>ରେ କଲ୍ କରନ୍ତୁ"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"ହଁ"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"ନାହିଁ"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"ଖାରଜ କରନ୍ତୁ"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"ଫୋନଟି ଜରୁରୀକାଳୀନ କଲବ୍ୟାକ୍ ମୋଡରେ ଅଛି"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">ଫୋନଟି <xliff:g id="COUNT_1">%s</xliff:g> ମିନିଟ୍ ପାଇଁ ଜରୁରୀକାଳୀନ କଲବ୍ୟାକ୍ ମୋଡରେ ରହିବ।\nଆପଣ ବର୍ତ୍ତମାନ ବାହାରି ଯିବାକୁ ଚାହାଁନ୍ତି କି?</item>
- <item quantity="one">ଫୋନଟି <xliff:g id="COUNT_0">%s</xliff:g> ମିନିଟ୍ ପାଇଁ ଜରୁରୀକାଳୀନ କଲବ୍ୟାକ୍ ମୋଡରେ ରହିବ।\nଆପଣ ବର୍ତ୍ତମାନ ବାହାରି ଯିବାକୁ ଚାହାଁନ୍ତି କି?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"ସେବା"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"ସେଟଅପ୍ କରନ୍ତୁ"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<ସେଟ୍ ହୋଇନାହିଁ>"</string>
@@ -682,7 +676,7 @@
<string name="callFailed_wfc_service_not_available_in_this_location" msgid="3624536608369524988">"ଏହି ଲୋକେସନ୍ରେ ୱାଇ-ଫାଇ କଲିଂ ଉପଲବ୍ଧ ନାହିଁ।"</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_cancel_label" msgid="2301711566758827936">"କ୍ୟାନ୍ସଲ୍ କରନ୍ତୁ"</string>
<string name="change_pin_ok_label" msgid="6861082678817785330">"ଠିକ୍ ଅଛି"</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>
@@ -705,7 +699,7 @@
<string name="mobile_data_activate_diag_title" msgid="5401741936224757312">"ଡାଟାକୁ ଯୋଡ଼ିବେ?"</string>
<string name="mobile_data_activate_diag_message" msgid="3527260988020415441">"ଆପଣଙ୍କୁ <xliff:g id="PROVIDER_NAME">%s</xliff:g> ମାଧ୍ୟମରେ ଡାଟା ଯୋଡ଼ିବାକୁ ପଡ଼ିପାରେ"</string>
<string name="mobile_data_activate_button" msgid="1139792516354374612">"ଡାଟା ଯୋଡ଼ନ୍ତୁ"</string>
- <string name="mobile_data_activate_cancel_button" msgid="3530174817572005860">"ବାତିଲ୍ କରନ୍ତୁ"</string>
+ <string name="mobile_data_activate_cancel_button" msgid="3530174817572005860">"କ୍ୟାନ୍ସଲ୍ କରନ୍ତୁ"</string>
<string name="clh_card_title_call_ended_txt" msgid="5977978317527299698">"କଲ୍ ସମାପ୍ତ ହୋଇଛି"</string>
<string name="clh_callFailed_powerOff_txt" msgid="8279934912560765361">"ଏୟାରପ୍ଲେନ୍ ମୋଡ୍ ଅନ୍ ଅଛି"</string>
<string name="clh_callFailed_simError_txt" msgid="5128538525762326413">"SIM କାର୍ଡକୁ ଆକ୍ସେସ୍ କରିହେଉନାହିଁ"</string>
@@ -879,7 +873,7 @@
<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_signal_strength_label" msgid="5545444702102543260">"ସିଗ୍ନାଲ୍ ଦକ୍ଷତା:"</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_ppp_sent_label" msgid="6542208429356199695">"ଡାଟା ପଠାଯାଇଛି:"</string>
<string name="radio_info_message_waiting_label" msgid="1886549432566952078">"ବାର୍ତ୍ତା ଅପକ୍ଷାରତ:"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"ରିଫ୍ରେସ୍ କରନ୍ତୁ"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS ଯାଞ୍ଚ ଟୋଗଲ୍ କରନ୍ତୁ"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-ନିର୍ଦ୍ଦିଷ୍ଟ ସୂଚନା/ସେଟିଂସ୍"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC ଉପଲବ୍ଧ:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR ପ୍ରତିବନ୍ଧିତ:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR ଉପଲବ୍ଧ:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR ସ୍ଥିତି:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR ଫ୍ରିକ୍ୱେନ୍ସୀ:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"ରେଡିଓ ବ୍ୟାଣ୍ଡ ମୋଡ୍ ସେଟ୍ କରନ୍ତୁ"</string>
<string name="band_mode_loading" msgid="795923726636735967">"ବ୍ୟାଣ୍ଡ ତାଲିକା ଲୋଡ୍ କରାଯାଉଛି…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"ସେଟ୍ କରନ୍ତୁ"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 7e166eb..65df16f 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"ਹਾਂ"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"ਨਹੀਂ"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"ਖਾਰਜ ਕਰੋ"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"ਫ਼ੋਨ ਸੰਕਟਕਾਲੀਨ ਕਾਲ ਮੋਡ ਵਿੱਚ ਹੈ"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">ਫ਼ੋਨ <xliff:g id="COUNT_1">%s</xliff:g> ਮਿੰਟ ਲਈ ਸੰਕਟਕਾਲੀਨ ਕਾਲ ਮੋਡ ਵਿੱਚ ਹੋਵੇਗਾ।\nਕੀ ਤੁਸੀਂ ਹੁਣ ਬਾਹਰ ਜਾਣਾ ਚਾਹੁੰਦੇ ਹੋ?</item>
- <item quantity="other">ਫ਼ੋਨ <xliff:g id="COUNT_1">%s</xliff:g> ਮਿੰਟਾਂ ਲਈ ਸੰਕਟਕਾਲੀਨ ਕਾਲ ਮੋਡ ਵਿੱਚ ਹੋਵੇਗਾ।\nਕੀ ਤੁਸੀਂ ਹੁਣ ਬਾਹਰ ਜਾਣਾ ਚਾਹੁੰਦੇ ਹੋ?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"ਸੇਵਾ"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"ਸੈੱਟਅੱਪ ਕਰੋ"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<ਸੈੱਟ ਨਹੀਂ ਕੀਤਾ>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"ਰਿਫ੍ਰੈਸ਼ ਕਰੋ"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS ਜਾਂਚ ਟੌਗਲ ਕਰੋ"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-ਵਿਸ਼ੇਸ਼ ਜਾਣਕਾਰੀ/ਸੈਟਿੰਗਾਂ"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC ਉਪਲਬਧ:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR ਪ੍ਰਤਿਬੰਧਿਤ:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR ਉਪਲਬਧ:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR ਸਥਿਤੀ:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR ਵਾਰਵਾਰਤਾ:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"ਰੇਡੀਓ ਬੈਂਡ ਮੋਡ ਸੈੱਟ ਕਰੋ"</string>
<string name="band_mode_loading" msgid="795923726636735967">"ਬੈਂਡ ਸੂਚੀ ਲੋਡ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"ਸੈੱਟ ਕਰੋ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index b0044e4..5d4131b 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -307,7 +307,7 @@
<string name="throttle_time_frame" msgid="1813452485948918791">"Okres użycia danych"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Zasada prędkości przesyłu danych"</string>
<string name="throttle_help" msgid="2624535757028809735">"Więcej informacji"</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>٪) z <xliff:g id="USED_2">%3$s</xliff:g> (wartość maksymalna w okresie)\nNastępny okres za <xliff:g id="USED_3">%4$d</xliff:g> dni (<xliff:g id="USED_4">%5$s</xliff:g>)"</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>٪) z <xliff:g id="USED_2">%3$s</xliff:g> (wartość maksymalna w okresie)\nNastępny okres za: <xliff:g id="USED_3">%4$d</xliff:g> dni (<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>٪) z <xliff:g id="USED_2">%3$s</xliff:g> (wartość maksymalna w okresie)"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"Przekroczono limit: <xliff:g id="USED_0">%1$s</xliff:g>\nSzybkość transmisji zmniejszona do <xliff:g id="USED_1">%2$d</xliff:g> Kb/s"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"Minęło <xliff:g id="USED_0">%1$d</xliff:g>٪ cyklu\nNastępny okres za: <xliff:g id="USED_1">%2$d</xliff:g> dni (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -641,14 +641,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Tak"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Nie"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Zamknij"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Telefon jest w trybie alarmowego połączenia zwrotnego"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Do <xliff:g id="COMPLETETIME">%s</xliff:g>"</string>
- <plurals name="alert_dialog_exit_ecm_without_data_restriction_hint" formatted="false" msgid="6477733043040328640">
- <item quantity="few">Telefon będzie w trybie alarmowego połączenia zwrotnego przez <xliff:g id="COUNT_1">%s</xliff:g> minuty.\nCzy chcesz teraz zakończyć ten tryb?</item>
- <item quantity="many">Telefon będzie w trybie alarmowego połączenia zwrotnego przez <xliff:g id="COUNT_1">%s</xliff:g> minut.\nCzy chcesz teraz zakończyć ten tryb?</item>
- <item quantity="other">Telefon będzie w trybie alarmowego połączenia zwrotnego przez <xliff:g id="COUNT_1">%s</xliff:g> minuty.\nCzy chcesz teraz zakończyć ten tryb?</item>
- <item quantity="one">Telefon będzie w trybie alarmowego połączenia zwrotnego przez <xliff:g id="COUNT_0">%s</xliff:g> minutę.\nCzy chcesz teraz zakończyć ten tryb?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Dostawca"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Konfiguracja"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Nie ustawiono>"</string>
@@ -695,7 +687,7 @@
<string name="change_pin_enter_new_pin_header" msgid="4739465616733486118">"Ustaw nowy kod PIN"</string>
<string name="change_pin_enter_new_pin_hint" msgid="2326038476516364210">"Kod PIN musi zawierać od <xliff:g id="MIN">%1$d</xliff:g> do <xliff:g id="MAX">%2$d</xliff:g> cyfr."</string>
<string name="change_pin_confirm_pin_header" msgid="2606303906320705726">"Potwierdź kod PIN"</string>
- <string name="change_pin_confirm_pins_dont_match" msgid="305164501222587215">"Kody PIN nie są takie same"</string>
+ <string name="change_pin_confirm_pins_dont_match" msgid="305164501222587215">"Kody PIN nie są identyczne."</string>
<string name="change_pin_succeeded" msgid="2504705600693014403">"Kod PIN poczty głosowej został zaktualizowany."</string>
<string name="change_pin_system_error" msgid="7772788809875146873">"Nie udało się ustawić kodu PIN"</string>
<string name="mobile_data_status_roaming_turned_off_subtext" msgid="6840673347416227054">"Transmisja danych w roamingu jest wyłączona"</string>
@@ -904,11 +896,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Odśwież"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Przełącz sprawdzanie DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Informacje/ustawienia specyficzne dla producenta OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"Dostępne EN-DC:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"Ograniczenie DCNR:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"Dostępne NR:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Stan NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Częstotliwość NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Ustawianie trybu pasma radiowego"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Ładuję listę pasm…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Ustaw"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 8cbe585..41b01ad 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -95,10 +95,10 @@
<string name="sum_loading_settings" msgid="434063780286688775">"A carregar as definições..."</string>
<string name="sum_hide_caller_id" msgid="131100328602371933">"Ocultar o número em chamadas efectuadas"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"Número apresentado em chamadas efectuadas"</string>
- <string name="sum_default_caller_id" msgid="1767070797135682959">"Utilizar as predefinições do operador para apresentar o meu número nas chamadas efetuadas"</string>
+ <string name="sum_default_caller_id" msgid="1767070797135682959">"Utilizar as predefinições do operador para apresentar o meu número nas chamadas efectuadas"</string>
<string name="labelCW" msgid="8449327023861428622">"Chamada em espera"</string>
- <string name="sum_cw_enabled" msgid="3977308526187139996">"Notificar-me de chamadas em espera durante uma chamada"</string>
- <string name="sum_cw_disabled" msgid="3658094589461768637">"Notificar-me de chamadas em espera durante uma chamada"</string>
+ <string name="sum_cw_enabled" msgid="3977308526187139996">"Notificar-me de chamadas a receber durante uma chamada"</string>
+ <string name="sum_cw_disabled" msgid="3658094589461768637">"Notificar-me de chamadas a receber durante uma chamada"</string>
<string name="call_forwarding_settings" msgid="8937130467468257671">"Definições do encaminhamento de chamadas"</string>
<string name="call_forwarding_settings_with_label" msgid="2345432813399564272">"Definições de encaminhamento de chamadas (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="labelCF" msgid="3578719437928476078">"Encaminhamento de chamadas"</string>
@@ -136,7 +136,7 @@
<string name="stk_cc_ss_to_ussd_error" msgid="8330749347425752192">"Pedido SS alterado para um novo pedido USSD"</string>
<string name="stk_cc_ss_to_ss_error" msgid="8297155544652134278">"Foi alterado para um novo pedido SS"</string>
<string name="stk_cc_ss_to_dial_video_error" msgid="4255261231466032505">"O pedido SS foi alterado para uma videochamada"</string>
- <string name="fdn_check_failure" msgid="1833769746374185247">"A definição Números Autorizados da app do seu Telemóvel está ativada. Por conseguinte, algumas funcionalidades relacionadas com chamadas não estão a funcionar."</string>
+ <string name="fdn_check_failure" msgid="1833769746374185247">"A definição Números Autorizados da aplicação do seu Telemóvel está ativada. Por conseguinte, algumas funcionalidades relacionadas com chamadas não estão a funcionar."</string>
<string name="radio_off_error" msgid="8321564164914232181">"Ative o rádio antes de visualizar estas definições."</string>
<string name="close_dialog" msgid="1074977476136119408">"OK"</string>
<string name="enable" msgid="2636552299455477603">"Ativar"</string>
@@ -286,7 +286,7 @@
<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="data_usage_title" msgid="8438592133893837464">"Utilização de dados da app"</string>
+ <string name="data_usage_title" msgid="8438592133893837464">"Utilização de dados da aplicação"</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>
<string name="carrier_settings_euicc" msgid="1190237227261337749">"Operador"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Sim"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Não"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Ignorar"</string>
- <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>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Serviço"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Configuração"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Não definido>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Atualizar"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Ativar/desativar verificação de DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Informações/definições específicas de OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC disponível:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR restrito:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR disponível:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Estado NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Frequência NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Definir modo de banda de rádio"</string>
<string name="band_mode_loading" msgid="795923726636735967">"A carregar lista de bandas…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Definir"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 2d44e3c..3fd76d4 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -91,14 +91,14 @@
<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="labelNwService" msgid="6015891883487125120">"Configurações do serviço de rede"</string>
- <string name="labelCallerId" msgid="2090540744550903172">"Identificador de chamadas"</string>
+ <string name="labelCallerId" msgid="2090540744550903172">"ID da chamada"</string>
<string name="sum_loading_settings" msgid="434063780286688775">"Carregando configurações…"</string>
<string name="sum_hide_caller_id" msgid="131100328602371933">"Número oculto nas chamadas enviadas"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"Número exibido nas chamadas enviadas"</string>
- <string name="sum_default_caller_id" msgid="1767070797135682959">"Usar configurações padrão da operadora para exibir meu número em chamadas realizadas"</string>
+ <string name="sum_default_caller_id" msgid="1767070797135682959">"Usar configurações padrão da operadora para exibir meu número em chamadas efetuadas"</string>
<string name="labelCW" msgid="8449327023861428622">"Chamada em espera"</string>
- <string name="sum_cw_enabled" msgid="3977308526187139996">"Notificar sobre outras chamadas recebidas durante uma ligação"</string>
- <string name="sum_cw_disabled" msgid="3658094589461768637">"Notificar sobre outras chamadas recebidas durante uma ligação"</string>
+ <string name="sum_cw_enabled" msgid="3977308526187139996">"Notificar sobre a entrada de outras chamadas durante uma ligação"</string>
+ <string name="sum_cw_disabled" msgid="3658094589461768637">"Notificar sobre a entrada de outras chamadas durante uma ligação"</string>
<string name="call_forwarding_settings" msgid="8937130467468257671">"Configurações de encaminhamento de chamada"</string>
<string name="call_forwarding_settings_with_label" msgid="2345432813399564272">"Configurações de encaminhamento de chamada (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="labelCF" msgid="3578719437928476078">"Encaminhamento de chamada"</string>
@@ -136,7 +136,7 @@
<string name="stk_cc_ss_to_ussd_error" msgid="8330749347425752192">"Solicitação SS alterada para solicitação USSD"</string>
<string name="stk_cc_ss_to_ss_error" msgid="8297155544652134278">"Alterada para uma nova solicitação SS"</string>
<string name="stk_cc_ss_to_dial_video_error" msgid="4255261231466032505">"Solicitação SS alterada para videochamada"</string>
- <string name="fdn_check_failure" msgid="1833769746374185247">"A configuração \"N fixa\" do seu app Telefone está ativada. Por isso, alguns recursos relacionados a chamadas não funcionam."</string>
+ <string name="fdn_check_failure" msgid="1833769746374185247">"A configuração \"Números de discagem fixa\" do seu app Telefone está ativada. Por isso, alguns recursos relacionados a chamadas não funcionam."</string>
<string name="radio_off_error" msgid="8321564164914232181">"Ligue o rádio antes de ver essas configurações."</string>
<string name="close_dialog" msgid="1074977476136119408">"OK"</string>
<string name="enable" msgid="2636552299455477603">"Ativar"</string>
@@ -425,8 +425,8 @@
<string name="cdma_activate_device" msgid="5914720276140097632">"Ativar aparelho"</string>
<string name="cdma_lte_data_service" msgid="359786441782404562">"Configurar serviço de dados"</string>
<string name="carrier_settings_title" msgid="6292869148169850220">"Config. da operadora"</string>
- <string name="fdn" msgid="2545904344666098749">"N fixa"</string>
- <string name="fdn_with_label" msgid="6412087553365709494">"N fixa (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
+ <string name="fdn" msgid="2545904344666098749">"Números de discagem fixa"</string>
+ <string name="fdn_with_label" msgid="6412087553365709494">"Números de discagem fixa (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="manage_fdn_list" msgid="3341716430375195441">"Lista FDN"</string>
<string name="fdn_list_with_label" msgid="1409655283510382556">"Lista de FDNs (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="fdn_activation" msgid="2178637004710435895">"Ativação do FDN"</string>
@@ -508,9 +508,9 @@
<string name="pin2_error_exception" msgid="8116103864600823641">"Erro de rede ou do chip"</string>
<string name="doneButton" msgid="7371209609238460207">"Concluído"</string>
<string name="voicemail_settings_number_label" msgid="1265118640154688162">"Número correio de voz"</string>
- <string name="card_title_dialing" msgid="8742182654254431781">"Chamando..."</string>
+ <string name="card_title_dialing" msgid="8742182654254431781">"Discando"</string>
<string name="card_title_redialing" msgid="18130232613559964">"Rediscando"</string>
- <string name="card_title_conf_call" msgid="901197309274457427">"Teleconferência"</string>
+ <string name="card_title_conf_call" msgid="901197309274457427">"teleconferência"</string>
<string name="card_title_incoming_call" msgid="881424648458792430">"Recebendo chamada"</string>
<string name="card_title_call_ended" msgid="650223980095026340">"Chamada encerrada"</string>
<string name="card_title_on_hold" msgid="9028319436626975207">"Em espera"</string>
@@ -554,7 +554,7 @@
<string name="emergency_enable_radio_dialog_retry" msgid="4329131876852608587">"Sem serviço. Tentando novamente..."</string>
<string name="radio_off_during_emergency_call" msgid="8011154134040481609">"Não é possível usar o modo avião durante uma chamada de emergência."</string>
<string name="dial_emergency_error" msgid="825822413209026039">"Não é possível realizar chamadas. <xliff:g id="NON_EMERGENCY_NUMBER">%s</xliff:g> não é um telefone de emergência."</string>
- <string name="dial_emergency_empty_error" msgid="2785803395047793634">"Não é possível realizar chamadas. Ligue para o número de telefone de emergência."</string>
+ <string name="dial_emergency_empty_error" msgid="2785803395047793634">"Não é possível realizar chamadas. Disque o número de telefone de emergência."</string>
<string name="dial_emergency_calling_not_available" msgid="6485846193794727823">"Chamada de emergência indisponível"</string>
<string name="pin_puk_system_user_only" msgid="1045147220686867922">"Somente o proprietário do dispositivo pode digitar os códigos PIN/PUK."</string>
<string name="police_type_description" msgid="2819533883972081757">"Polícia"</string>
@@ -572,7 +572,7 @@
<string name="onscreenManageCallsText" msgid="1162047856081836469">"Gerenciar chamadas"</string>
<string name="onscreenManageConferenceText" msgid="4700574060601755137">"Gerenciar conferência"</string>
<string name="onscreenAudioText" msgid="7224226735052019986">"Áudio"</string>
- <string name="onscreenVideoCallText" msgid="1743992456126258698">"Vídeo"</string>
+ <string name="onscreenVideoCallText" msgid="1743992456126258698">"Videocham."</string>
<string name="importSimEntry" msgid="3892354284082689894">"Importar"</string>
<string name="importAllSimEntries" msgid="2628391505643564007">"Importar tudo"</string>
<string name="importingSimContacts" msgid="4995457122107888932">"Importando contatos do chip"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Sim"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Não"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Descartar"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"O smartphone está no modo de retorno de chamada de emergência"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Até <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">O smartphone ficará no modo de retorno de chamada de emergência por <xliff:g id="COUNT_1">%s</xliff:g> minuto.\nVocê quer sair agora?</item>
- <item quantity="other">O smartphone ficará no modo de retorno de chamada de emergência por <xliff:g id="COUNT_1">%s</xliff:g> minutos.\nVocê quer sair agora?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Serviço"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Configuração"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Não definido>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Atualizar"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Ativar/desativar verificação do DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Informações/configurações específicas de OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC disponível:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR restrita:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR disponível:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Estado do NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Frequência do NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Definir modo de banda de rádio"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Carregando a lista de bandas…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Definir"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 35ee855..1b1f1b7 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -639,13 +639,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Da"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Nu"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Renunțați"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Telefonul va fi în modul Apelare inversă de urgență"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Până la <xliff:g id="COMPLETETIME">%s</xliff:g>"</string>
- <plurals name="alert_dialog_exit_ecm_without_data_restriction_hint" formatted="false" msgid="6477733043040328640">
- <item quantity="few">Telefonul va fi în modul Apelare inversă de urgență timp de <xliff:g id="COUNT_1">%s</xliff:g> minute.\nDoriți să ieșiți acum?</item>
- <item quantity="other">Telefonul va fi în modul Apelare inversă de urgență timp de <xliff:g id="COUNT_1">%s</xliff:g> de minute.\nDoriți să ieșiți acum?</item>
- <item quantity="one">Telefonul va fi în modul Apelare inversă de urgență timp de <xliff:g id="COUNT_0">%s</xliff:g> minut.\nDoriți să ieșiți acum?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Serviciu"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Configurare"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Nesetat>"</string>
@@ -901,11 +894,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Actualizați"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Activați/dezactivați verificarea DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Informații/Setări caracteristice OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"Disponibilă EN-DC:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"Restricționată DCNR:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"Disponibilă NR:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Stare NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Frecvență NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Setați Modul bandă radio"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Se încarcă lista de benzi…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Setați"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index a7980bf..8086351 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -96,7 +96,7 @@
<string name="sum_hide_caller_id" msgid="131100328602371933">"Скрывать номер при исходящих вызовах"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"Номер, отображающийся при исходящих вызовах"</string>
<string name="sum_default_caller_id" msgid="1767070797135682959">"Использовать стандартные настройки оператора для показа моего номера при исходящих вызовах"</string>
- <string name="labelCW" msgid="8449327023861428622">"Ожидание вызова"</string>
+ <string name="labelCW" msgid="8449327023861428622">"Параллельный вызов"</string>
<string name="sum_cw_enabled" msgid="3977308526187139996">"Извещать меня о входящих вызовах во время разговора"</string>
<string name="sum_cw_disabled" msgid="3658094589461768637">"Извещать меня о входящих вызовах во время разговора"</string>
<string name="call_forwarding_settings" msgid="8937130467468257671">"Переадресация"</string>
@@ -310,7 +310,9 @@
<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>
- <string name="throttle_time_frame_subtext" msgid="6462089615392402127">"Пройдено: <xliff:g id="USED_0">%1$d</xliff:g>٪ цикла.\nСледующий период начнется через <xliff:g id="USED_1">%2$d</xliff:g> дн. (<xliff:g id="USED_2">%3$s</xliff:g>)."</string>
+ <!-- String.format failed for translation -->
+ <!-- no translation found for throttle_time_frame_subtext (6462089615392402127) -->
+ <skip />
<string name="throttle_rate_subtext" msgid="7221971817325779535">"Превышение лимита снижает скорость передачи данных до <xliff:g id="USED">%1$d</xliff:g> кбит/с"</string>
<string name="throttle_help_subtext" msgid="2817114897095534807">"Подробнее о политике передачи данных вашего оператора мобильной связи..."</string>
<string name="cell_broadcast_sms" msgid="4053449797289031063">"Широковещательные SMS-службы"</string>
@@ -641,14 +643,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Да"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Нет"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Закрыть"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Телефон находится в режиме экстренных обратных вызовов"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"До <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">Телефон будет оставаться в режиме экстренных обратных вызовов <xliff:g id="COUNT_1">%s</xliff:g> минуту.\nВыйти сейчас?</item>
- <item quantity="few">Телефон будет оставаться в режиме экстренных обратных вызовов <xliff:g id="COUNT_1">%s</xliff:g> минуты.\nВыйти сейчас?</item>
- <item quantity="many">Телефон будет оставаться в режиме экстренных обратных вызовов <xliff:g id="COUNT_1">%s</xliff:g> минут.\nВыйти сейчас?</item>
- <item quantity="other">Телефон будет оставаться в режиме экстренных обратных вызовов <xliff:g id="COUNT_1">%s</xliff:g> минуты.\nВыйти сейчас?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Служба"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Настройка"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Не задано>"</string>
@@ -904,11 +898,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Обновить"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Включить/отключить проверку DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Информация/настройки OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC доступно:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR ограничено:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR доступно:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Состояние NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Частота NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Настроить режим сети"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Загрузка списка частот…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Сохранить"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 45850c8..e92c865 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"ඔව්"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"නැත"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"ඉවතලන්න"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"දුරකථනය හදිසි පසු ඇමතුම් ප්රකාරයේ ඇත"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">දුරකථනය මිනිත්තු <xliff:g id="COUNT_1">%s</xliff:g>ක් තිස්සේ හදිසි පසු ඇමතුම් ප්රකාරයේ තිබෙනු ඇත.\nදැන් ඔබට පිටවීමට අවශ්යද?</item>
- <item quantity="other">දුරකථනය මිනිත්තු <xliff:g id="COUNT_1">%s</xliff:g>ක් තිස්සේ හදිසි පසු ඇමතුම් ප්රකාරයේ තිබෙනු ඇත.\nදැන් ඔබට පිටවීමට අවශ්යද?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"සේවාව"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"ස්ථාපනය කරන්න"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<පිහිටුවා නැත>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"නැවුම් කරන්න"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS පරීක්ෂාව ටොගල කරන්න"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-විශේෂිත තොරතුරු/සැකසීම්"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC ලබා ගත හැකි:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR සීමා කළ:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR ලබා ගත හැකි:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR තත්ත්වය:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR සංඛ්යාතය:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"රේඩියෝ කලාප ප්රකාරය සකසන්න"</string>
<string name="band_mode_loading" msgid="795923726636735967">"කලාප ලැයිස්තුව පූරණය කරමින්…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"සකසන්න"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 5436158..e38b635 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -127,7 +127,7 @@
<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>
<string name="error_updating_title" msgid="2024290892676808965">"Chyba nastavení hovorov"</string>
- <string name="reading_settings" msgid="1605904432450871183">"Nastavenia sa načítavajú…"</string>
+ <string name="reading_settings" msgid="1605904432450871183">"Nastavenia sa načítajú…"</string>
<string name="updating_settings" msgid="3650396734816028808">"Prebieha aktualizácia nastavení..."</string>
<string name="reverting_settings" msgid="7378668837291012205">"Prebieha vrátenie predchádzajúcich nastavení…"</string>
<string name="response_error" msgid="3904481964024543330">"Neočakávaná odpoveď siete."</string>
@@ -171,7 +171,7 @@
<string name="network_query_error" msgid="3862515805115145124">"Nepodarilo sa nájsť siete. Skúste to znova."</string>
<string name="register_on_network" msgid="4194770527833960423">"Prebieha registrácia v sieti <xliff:g id="NETWORK">%s</xliff:g>..."</string>
<string name="not_allowed" msgid="8541221928746104798">"Vaša SIM karta nepovoľuje pripojenie k tejto sieti."</string>
- <string name="connect_later" msgid="1950138106010005425">"V tejto chvíli sa nedá pripojiť k sieti. Skúste to neskôr."</string>
+ <string name="connect_later" msgid="1950138106010005425">"V tejto chvíli sa nedá pripojiť k sieti. Skúste to znova neskôr."</string>
<string name="registration_done" msgid="5337407023566953292">"Prihlásenie k sieti prebehlo úspešne."</string>
<string name="already_auto" msgid="8607068290733079336">"Už v automatickom výbere"</string>
<string name="select_automatically" msgid="779750291257872651">"Vybrať sieť automaticky"</string>
@@ -641,14 +641,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Áno"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Nie"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Zatvoriť"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Telefón je v režime tiesňového spätného volania"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Do <xliff:g id="COMPLETETIME">%s</xliff:g>"</string>
- <plurals name="alert_dialog_exit_ecm_without_data_restriction_hint" formatted="false" msgid="6477733043040328640">
- <item quantity="few">Telefón bude v režime tiesňového spätného volania <xliff:g id="COUNT_1">%s</xliff:g> minúty.\nChcete tento režim ukončiť?</item>
- <item quantity="many">Telefón bude v režime tiesňového spätného volania <xliff:g id="COUNT_1">%s</xliff:g> minúty.\nChcete tento režim ukončiť?</item>
- <item quantity="other">Telefón bude v režime tiesňového spätného volania <xliff:g id="COUNT_1">%s</xliff:g> minút.\nChcete tento režim ukončiť?</item>
- <item quantity="one">Telefón bude v režime tiesňového spätného volania <xliff:g id="COUNT_0">%s</xliff:g> minútu.\nChcete tento režim ukončiť?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Služba"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Nastavenie"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Nenastavené>"</string>
@@ -904,11 +896,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Obnoviť"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Prepnúť kontrolu DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Informácie alebo nastavenia špecifické pre výrobcu OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"Dostupné EN-DC:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"Obmedzené DCNR:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"Dostupné NR:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Stav NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Frekvencia NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Nastaviť režim rádiového pásma"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Načítava sa zoznam pásiem…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Nastaviť"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 40a54b3..0ee96d6 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -641,14 +641,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Da"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Ne"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Opusti"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Telefon je v načinu za povratni klic v sili"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Do <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">Telefon bo v načinu za povratni klic v sili čez <xliff:g id="COUNT_1">%s</xliff:g> minuto.\nAli želite zapustiti ta način?</item>
- <item quantity="two">Telefon bo v načinu za povratni klic v sili čez <xliff:g id="COUNT_1">%s</xliff:g> minuti.\nAli želite zapustiti ta način?</item>
- <item quantity="few">Telefon bo v načinu za povratni klic v sili čez <xliff:g id="COUNT_1">%s</xliff:g> minute.\nAli želite zapustiti ta način?</item>
- <item quantity="other">Telefon bo v načinu za povratni klic v sili čez <xliff:g id="COUNT_1">%s</xliff:g> minut.\nAli želite zapustiti ta način?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Storitev"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Nastavitev"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Ni nastavljeno>"</string>
@@ -904,11 +896,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Osveži"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Preklop preverjanja DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Informacije/nastavitve za OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"Razpoložljivo za EN-DC:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"Omejeno za DCNR:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"Razpoložljivo za NR:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Stanje NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Frekvenca NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Nastavljanje načina radijskega območja"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Nalaganje seznama frekvenčnih pasov …"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Nastavi"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 9e37521..e9a245c 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -74,7 +74,7 @@
<string name="phone_accounts_configure_account_settings" msgid="6622119715253196586">"Konfiguro cilësimet e llogarisë"</string>
<string name="phone_accounts_all_calling_accounts" msgid="1609600743500618823">"Të gjitha llogaritë e telefonatave"</string>
<string name="phone_accounts_all_calling_accounts_summary" msgid="2214134955430107240">"Zgjidh se cilat llogari mund të kryejnë telefonata"</string>
- <string name="wifi_calling" msgid="3650509202851355742">"Telefonata me Wi-Fi"</string>
+ <string name="wifi_calling" msgid="3650509202851355742">"Telefonatë me Wi-Fi"</string>
<string name="connection_service_default_label" msgid="7332739049855715584">"Shërbim i integruar lidhjeje"</string>
<string name="voicemail" msgid="7697769412804195032">"Posta zanore"</string>
<string name="voicemail_settings_with_label" msgid="4228431668214894138">"Posta zanore (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -307,7 +307,7 @@
<string name="throttle_time_frame" msgid="1813452485948918791">"Periudha e përdorimit të të dhënave"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Politika e shpejtësisë së të dhënave"</string>
<string name="throttle_help" msgid="2624535757028809735">"Mëso më shumë"</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>٪) nga maksimumi i periudhës prej <xliff:g id="USED_2">%3$s</xliff:g>\nPeriudha tjetër fillon për <xliff:g id="USED_3">%4$d</xliff:g> ditë (<xliff:g id="USED_4">%5$s</xliff:g>)"</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>٪) nga maksimumi i periudhës prej <xliff:g id="USED_2">%3$s</xliff:g> \nPeriudha tjetër fillon për <xliff:g id="USED_3">%4$d</xliff:g> ditë (<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>٪) nga maksimumi i periudhës prej <xliff:g id="USED_2">%3$s</xliff:g>"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"U kapërcye maksimumi i <xliff:g id="USED_0">%1$s</xliff:g>\nShpejtësia e të dhënave u pakësua deri në <xliff:g id="USED_1">%2$d</xliff:g> Kb/s"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"Ka kaluar <xliff:g id="USED_0">%1$d</xliff:g>٪ të ciklit\nPeriudha tjetër fillon për <xliff:g id="USED_1">%2$d</xliff:g> ditë (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -506,7 +506,7 @@
<string name="pin2_attempts" msgid="5625178102026453023">\n"Të kanë mbetur edhe <xliff:g id="NUMBER">%d</xliff:g> përpjekje."</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>
+ <string name="doneButton" msgid="7371209609238460207">"U krye!"</string>
<string name="voicemail_settings_number_label" msgid="1265118640154688162">"Numri i postës zanore"</string>
<string name="card_title_dialing" msgid="8742182654254431781">"Po telefonon"</string>
<string name="card_title_redialing" msgid="18130232613559964">"Po riformon numrin"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Po"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Jo"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Injoro"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Telefoni është në modalitetin e kthimit të telefonatës së urgjencës"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Deri në <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">Telefoni do të jetë në modalitetin e kthimit të telefonatës së urgjencës për <xliff:g id="COUNT_1">%s</xliff:g> minuta.\nDëshiron të dalësh tani?</item>
- <item quantity="one">Telefoni do të jetë në modalitetin e kthimit të telefonatës së urgjencës për <xliff:g id="COUNT_0">%s</xliff:g> minutë.\nDëshiron të dalësh tani?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Shërbimi"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Konfigurimi"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<I pavendosur>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Rifresko"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Ndrysho kontrollin e DNS-së"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Informacion/cilësime specifike për OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"Ofrohet EN-DC:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR me kufizime:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"Ofrohet NR:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Gjendja e NR-së:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Frekuenca e NR-së:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Cakto modalitetin e brezit të radios"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Po ngarkon listën e brezave…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Cakto"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 53dc93c..69a24f1 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -74,7 +74,7 @@
<string name="phone_accounts_configure_account_settings" msgid="6622119715253196586">"Конфигурисање подешавања налога"</string>
<string name="phone_accounts_all_calling_accounts" msgid="1609600743500618823">"Сви налози за позивање"</string>
<string name="phone_accounts_all_calling_accounts_summary" msgid="2214134955430107240">"Изаберите који налози могу да обављају позиве"</string>
- <string name="wifi_calling" msgid="3650509202851355742">"Позивање преко WiFi-а"</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>
@@ -118,7 +118,7 @@
<string name="sum_cfnry_enabled" msgid="3000500837493854799">"Прослеђује се на <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnry_disabled" msgid="1990563512406017880">"Искључено"</string>
<string name="disable_cfnry_forbidden" msgid="3174731413216550689">"Оператер не подржава онемогућавање преусмеравања позива када се на позив не одговори."</string>
- <string name="labelCFNRc" msgid="4163399350778066013">"Кад сам недоступан/на"</string>
+ <string name="labelCFNRc" msgid="4163399350778066013">"Кад сам недоступан/а"</string>
<string name="messageCFNRc" msgid="6980340731313007250">"Број кад је недоступно"</string>
<string name="sum_cfnrc_enabled" msgid="1799069234006073477">"Прослеђује се на <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Искључено"</string>
@@ -298,7 +298,7 @@
<string name="sim_selection_required_pref" msgid="6985901872978341314">"Потребно је да изаберете нешто"</string>
<string name="sim_change_data_title" msgid="9142726786345906606">"Да променимо SIM за податке?"</string>
<string name="sim_change_data_message" msgid="3567358694255933280">"Желите ли да за мобилне податке користите <xliff:g id="NEW_SIM">%1$s</xliff:g> уместо <xliff:g id="OLD_SIM">%2$s</xliff:g>?"</string>
- <string name="wifi_calling_settings_title" msgid="5800018845662016507">"Позивање преко WiFi-а"</string>
+ <string name="wifi_calling_settings_title" msgid="5800018845662016507">"Позивање преко Wi-Fi-ја"</string>
<string name="video_calling_settings_title" msgid="342829454913266078">"Видео позивање преко оператера"</string>
<string name="gsm_umts_options" msgid="4968446771519376808">"Опције за GSM/UMTS"</string>
<string name="cdma_options" msgid="3669592472226145665">"CDMA опције"</string>
@@ -472,9 +472,9 @@
<string name="simContacts_empty" msgid="1135632055473689521">"Нема контаката на SIM картици."</string>
<string name="simContacts_title" msgid="2714029230160136647">"Избор контаката за увоз"</string>
<string name="simContacts_airplaneMode" msgid="4654884030631503808">"Искључите режим рада у авиону да бисте увезли контакте са SIM картице."</string>
- <string name="enable_pin" msgid="967674051730845376">"Омогућавање/онемогућавање PIN-а за SIM"</string>
- <string name="change_pin" msgid="3657869530942905790">"Промена PIN-а за SIM"</string>
- <string name="enter_pin_text" msgid="3182311451978663356">"PIN за SIM:"</string>
+ <string name="enable_pin" msgid="967674051730845376">"Омогућавање/онемогућавање SIM PIN-а"</string>
+ <string name="change_pin" msgid="3657869530942905790">"Промена SIM PIN-а"</string>
+ <string name="enter_pin_text" msgid="3182311451978663356">"SIM PIN:"</string>
<string name="oldPinLabel" msgid="8618515202411987721">"Стари PIN"</string>
<string name="newPinLabel" msgid="3585899083055354732">"Нови PIN"</string>
<string name="confirmPinLabel" msgid="7783531218662473778">"Потврдите нови PIN"</string>
@@ -542,7 +542,7 @@
<string name="incall_error_supp_service_hangup" msgid="836524952243836735">"Успостављање позива није успело."</string>
<string name="incall_error_supp_service_hold" msgid="8535056414643540997">"Није могуће стављати позиве на чекање."</string>
<string name="incall_error_wfc_only_no_wireless_network" msgid="5860742792811400109">"Повежите се на бежичну мрежу да бисте упутили позив."</string>
- <string name="incall_error_promote_wfc" msgid="9164896813931363415">"Омогућите позивање преко WiFi-а да бисте упутили позив."</string>
+ <string name="incall_error_promote_wfc" msgid="9164896813931363415">"Омогућите позивање преко Wi-Fi-ја да бисте упутили позив."</string>
<string name="emergency_information_hint" msgid="9208897544917793012">"Информације за хитне случајеве"</string>
<string name="emergency_information_owner_hint" msgid="6256909888049185316">"Власник"</string>
<string name="emergency_information_confirm_hint" msgid="5109017615894918914">"Додирните поново да бисте видели информације"</string>
@@ -564,7 +564,7 @@
<string name="dialerKeyboardHintText" msgid="1115266533703764049">"Користите тастатуру за позивање"</string>
<string name="onscreenHoldText" msgid="4025348842151665191">"Чекање"</string>
<string name="onscreenEndCallText" msgid="6138725377654842757">"Заврши"</string>
- <string name="onscreenShowDialpadText" msgid="658465753816164079">"Бројчаник"</string>
+ <string name="onscreenShowDialpadText" msgid="658465753816164079">"Нумеричка тастатура"</string>
<string name="onscreenMuteText" msgid="5470306116733843621">"Искључи звук"</string>
<string name="onscreenAddCallText" msgid="9075675082903611677">"Додај позив"</string>
<string name="onscreenMergeCallsText" msgid="3692389519611225407">"Обједини позиве"</string>
@@ -604,7 +604,7 @@
<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_skip_activation_dialog_title" msgid="7666611236789203797">"Желите ли да прескочите активацију?"</string>
- <string name="ota_skip_activation_dialog_message" msgid="6691722887019708713">"Ако прескочите активацију, не можете да упућујете позиве или да се повезујете са мрежама за мобилни пренос података (иако можете да се повежете са WiFi мрежама). Све док не активирате свој телефон, бићете упитани да то учините сваки пут када га укључите."</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_title_activate_success" msgid="1272135024761004889">"Телефон је активиран."</string>
@@ -639,13 +639,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Да"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Не"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Одбаци"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Телефон је у режиму за хитан повратни позив"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"До <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">Телефон ће <xliff:g id="COUNT_1">%s</xliff:g> минут бити у режиму за хитан повратни позив.\nЖелите сад да изађете из њега?</item>
- <item quantity="few">Телефон ће <xliff:g id="COUNT_1">%s</xliff:g> минута бити у режиму за хитан повратни позив.\nЖелите сад да изађете из њега?</item>
- <item quantity="other">Телефон ће <xliff:g id="COUNT_1">%s</xliff:g> минута бити у режиму за хитан повратни позив.\nЖелите сад да изађете из њега?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Услуга"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Подешавање"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Није подешено>"</string>
@@ -657,7 +650,7 @@
<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>
+ <string name="pane_title_emergency_dialpad" msgid="3627372514638694401">"Нумеричка тастатура за хитне позиве"</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>
@@ -671,18 +664,18 @@
<string name="sim_description_emergency_calls" msgid="5146872803938897296">"Само за хитне позиве"</string>
<string name="sim_description_default" msgid="7474671114363724971">"SIM картица, отвор: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
<string name="accessibility_settings_activity_title" msgid="7883415189273700298">"Приступачност"</string>
- <string name="status_hint_label_incoming_wifi_call" msgid="2606052595898044071">"WiFi позив од"</string>
- <string name="status_hint_label_wifi_call" msgid="942993035689809853">"WiFi позив"</string>
+ <string name="status_hint_label_incoming_wifi_call" msgid="2606052595898044071">"Wi-Fi позив од"</string>
+ <string name="status_hint_label_wifi_call" msgid="942993035689809853">"Wi-Fi позив"</string>
<string name="emergency_action_launch_hint" msgid="2762016865340891314">"Додирните поново да бисте отворили"</string>
<string name="message_decode_error" msgid="1061856591500290887">"Дошло је до грешке при декодирању поруке."</string>
<string name="callFailed_cdma_activation" msgid="5392057031552253550">"SIM картица је активирала услугу и ажурирала функције роминга на телефону."</string>
<string name="callFailed_cdma_call_limit" msgid="1074219746093031412">"Има превише активних позива. Завршите или обједините постојеће позиве пре него што упутите нови."</string>
<string name="callFailed_imei_not_accepted" msgid="7257903653685147251">"Повезивање није успело, убаците важећу SIM картицу."</string>
- <string name="callFailed_wifi_lost" msgid="1788036730589163141">"WiFi веза је прекинута. Позив је завршен."</string>
+ <string name="callFailed_wifi_lost" msgid="1788036730589163141">"Wi-Fi веза је прекинута. Позив је завршен."</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">"Хитни позиви помоћу функције Позивање преко WiFi-а нису доступни на овој локацији."</string>
- <string name="callFailed_wfc_service_not_available_in_this_location" msgid="3624536608369524988">"Позивање преко WiFi-а није доступно на овој локацији."</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_continue_label" msgid="5177011752453506371">"Настави"</string>
<string name="change_pin_cancel_label" msgid="2301711566758827936">"Откажи"</string>
@@ -827,7 +820,7 @@
<string name="radio_info_data_connection_disable" msgid="6404751291511368706">"Онемогући везу за пренос података"</string>
<string name="volte_provisioned_switch_string" msgid="4812874990480336178">"Додељено за VoLTE"</string>
<string name="vt_provisioned_switch_string" msgid="8295542122512195979">"Видео позиви су додељени"</string>
- <string name="wfc_provisioned_switch_string" msgid="3835004640321078988">"Позивање преко WiFi-а је додељено"</string>
+ <string name="wfc_provisioned_switch_string" msgid="3835004640321078988">"Позивање преко Wi-Fi-ја је додељено"</string>
<string name="eab_provisioned_switch_string" msgid="4449676720736033035">"Додељен је EAB/присуство"</string>
<string name="cbrs_data_switch_string" msgid="6060356430838077653">"Подаци CBRS-а"</string>
<string name="dsds_switch_string" msgid="7564769822086764796">"Омогући DSDS"</string>
@@ -845,7 +838,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>\nГлас преко LTE-а: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nГлас преко WiFi-а: <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>\nГлас преко LTE-а: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nГлас преко Wi-Fi-ја: <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>
@@ -901,11 +894,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Освежи"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Укључи/искључи проверу DNS-а"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Информације/подешавања специфична за произвођача оригиналне опреме"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC доступно:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR ограничено:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR доступно:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR стање:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR учесталост:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Подесите режим радијског опсега"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Учитава се листа опсега…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Подеси"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index bf32bfb..6ff09d6 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -307,8 +307,12 @@
<string name="throttle_time_frame" msgid="1813452485948918791">"Dataanvändningsperiod"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Datahastighetspolicy"</string>
<string name="throttle_help" msgid="2624535757028809735">"Läs mer"</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>٪) av högst <xliff:g id="USED_2">%3$s</xliff:g> för perioden\nNästa period börjar om <xliff:g id="USED_3">%4$d</xliff:g> dagar (<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> ٪) av högst <xliff:g id="USED_2">%3$s</xliff:g> för perioden"</string>
+ <!-- String.format failed for translation -->
+ <!-- no translation found for throttle_status_subtext (1110276415078236687) -->
+ <skip />
+ <!-- String.format failed for translation -->
+ <!-- no translation found for throttle_data_usage_subtext (3185429653996709840) -->
+ <skip />
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"<xliff:g id="USED_0">%1$s</xliff:g> maxvärdet har överskridits\nDatahastigheten har sänkts till <xliff:g id="USED_1">%2$d</xliff:g> kbit/s"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g> ٪ av cykeln har gått\nNästa period börjar om <xliff:g id="USED_1">%2$d</xliff:g> dagar (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
<string name="throttle_rate_subtext" msgid="7221971817325779535">"Datahastigheten sänks till <xliff:g id="USED">%1$d</xliff:g> kbit/s om dataanvändningsgränsen överskrids"</string>
@@ -637,12 +641,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Ja"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Nej"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Ta bort permanent"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Telefonen är i läget för återuppringning vid nödsamtal"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Till <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">Telefonen kommer att vara i läget för återuppringning vid nödsamtal i <xliff:g id="COUNT_1">%s</xliff:g> minuter.\nVill du avsluta nu?</item>
- <item quantity="one">Telefonen kommer att vara i läget för återuppringning vid nödsamtal i <xliff:g id="COUNT_0">%s</xliff:g> minut.\nVill du avsluta nu?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Tjänst"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Konfiguration"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Har inte angetts>"</string>
@@ -688,7 +686,7 @@
<string name="change_pin_enter_old_pin_hint" msgid="8801292976275169367">"Ange pinkoden till röstbrevlådan för att fortsätta."</string>
<string name="change_pin_enter_new_pin_header" msgid="4739465616733486118">"Ange en ny pinkod"</string>
<string name="change_pin_enter_new_pin_hint" msgid="2326038476516364210">"Pinkoden måste ha <xliff:g id="MIN">%1$d</xliff:g>-<xliff:g id="MAX">%2$d</xliff:g> siffror."</string>
- <string name="change_pin_confirm_pin_header" msgid="2606303906320705726">"Bekräfta PIN-kod"</string>
+ <string name="change_pin_confirm_pin_header" msgid="2606303906320705726">"Bekräfta pinkod"</string>
<string name="change_pin_confirm_pins_dont_match" msgid="305164501222587215">"Pinkoderna matchar inte"</string>
<string name="change_pin_succeeded" msgid="2504705600693014403">"Röstbrevlådans pinkod bekräftad"</string>
<string name="change_pin_system_error" msgid="7772788809875146873">"Det går inte att ställa in pinkod"</string>
@@ -898,11 +896,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Uppdatera"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Aktivera och inaktivera DNS-kontroll"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-specifik information/inställningar"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC tillgänglig:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR begränsad:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR tillgänglig:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR-status:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR-frekvens:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Konfigurera radiobandsläget"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Läser in bandlista …"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Ange"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index b91d798..f2ac8d8 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -24,7 +24,7 @@
<string name="unknown" msgid="8279698889921830815">"Haijulikani"</string>
<string name="private_num" msgid="4487990167889159992">" Number isiyojulikana"</string>
<string name="payphone" msgid="7936735771836716941">"Simu ya kulipia"</string>
- <string name="onHold" msgid="6132725550015899006">"Imesitishwa"</string>
+ <string name="onHold" msgid="6132725550015899006">"Inangoja"</string>
<string name="carrier_mmi_msg_title" msgid="6050165242447507034">"Ujumbe wa <xliff:g id="MMICARRIER">%s</xliff:g>"</string>
<string name="default_carrier_mmi_msg_title" msgid="7754317179938537213">"Ujumbe wa Mtoa Huduma"</string>
<string name="mmiStarted" msgid="9212975136944568623">"Msimbo wa MMI umeanza"</string>
@@ -91,11 +91,11 @@
<string name="additional_cdma_call_settings" msgid="2178016561980611304">"Mipangilio ya ziada ya simu ya CDMA"</string>
<string name="sum_cdma_call_settings" msgid="3185825305136993636">"Mipangilio ya ziada ya simu ya CDMA tu"</string>
<string name="labelNwService" msgid="6015891883487125120">"Mipangilio ya huduma ya mtandao"</string>
- <string name="labelCallerId" msgid="2090540744550903172">"Kitambulisho cha anayepiga"</string>
+ <string name="labelCallerId" msgid="2090540744550903172">"Kitambulisho cha mpigaji SIM"</string>
<string name="sum_loading_settings" msgid="434063780286688775">"Mipangilio inapakia..."</string>
<string name="sum_hide_caller_id" msgid="131100328602371933">"Nambari imefichwa kwa simu unayopiga"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"Namba inaonekana kwa simu zinazopigwa"</string>
- <string name="sum_default_caller_id" msgid="1767070797135682959">"Tumia mipangilio ya mtoa huduma chaguomsingi kuonyesha nambari kwa simu unazopiga"</string>
+ <string name="sum_default_caller_id" msgid="1767070797135682959">"Tumia mipangilio ya kiendesha chaguomsingi kuonyesha namba kwa simu zinazopigwa"</string>
<string name="labelCW" msgid="8449327023861428622">"Simu inayosubiri kupokewa"</string>
<string name="sum_cw_enabled" msgid="3977308526187139996">"Wakati ninapokea simu, niarifu kuhusu simu zingine zinazoingia"</string>
<string name="sum_cw_disabled" msgid="3658094589461768637">"Wakati ninapokea simu, niarifu kuhusu simu zingine zinazoingia"</string>
@@ -128,7 +128,7 @@
<string name="call_settings_with_label" msgid="8460230435361579511">"Mipangilio (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="error_updating_title" msgid="2024290892676808965">"Hitilafu ya mipangilio ya kupiga simu"</string>
<string name="reading_settings" msgid="1605904432450871183">"Inasoma mipangilio…."</string>
- <string name="updating_settings" msgid="3650396734816028808">"Inasasisha mipangilio…"</string>
+ <string name="updating_settings" msgid="3650396734816028808">"Inaboresha mipangilio…"</string>
<string name="reverting_settings" msgid="7378668837291012205">"Inageuza mipangilio..."</string>
<string name="response_error" msgid="3904481964024543330">"Jibu lisilotarajiwa kutoka kwa mtandao."</string>
<string name="exception_error" msgid="330994460090467">"Hitilafu ya mtandao au SIM kadi."</string>
@@ -307,7 +307,7 @@
<string name="throttle_time_frame" msgid="1813452485948918791">"Muda wa matumizi ya data"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Sera ya kasi ya data"</string>
<string name="throttle_help" msgid="2624535757028809735">"Pata maelezo zaidi"</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>٪) imetumika kati ya kipindi cha juu cha <xliff:g id="USED_2">%3$s</xliff:g>\nKipindi kijacho kinaanza baada ya siku <xliff:g id="USED_3">%4$d</xliff:g> (<xliff:g id="USED_4">%5$s</xliff:g>)"</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>٪) ya<xliff:g id="USED_2">%3$s</xliff:g> muda wa kiwango cha juu\nMuda unaofuata unaanza baada ya siku<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> (asilimia <xliff:g id="USED_1">%2$d</xliff:g>) ya upeo wa muda wa <xliff:g id="USED_2">%3$s</xliff:g>"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"Upeo wa <xliff:g id="USED_0">%1$s</xliff:g> umepitwa\nKasi ya data imepunguzwa hadi kilobaiti <xliff:g id="USED_1">%2$d</xliff:g> kwa sekunde"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g>٪ ya mzunguko imekamilika\nMuda ufuatao unaanza baada ya siku <xliff:g id="USED_1">%2$d</xliff:g> (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -513,7 +513,7 @@
<string name="card_title_conf_call" msgid="901197309274457427">"Simu ya kongamano"</string>
<string name="card_title_incoming_call" msgid="881424648458792430">"Simu inayoingia"</string>
<string name="card_title_call_ended" msgid="650223980095026340">"Simu imekamilika"</string>
- <string name="card_title_on_hold" msgid="9028319436626975207">"Imesitishwa"</string>
+ <string name="card_title_on_hold" msgid="9028319436626975207">"Inangoja"</string>
<string name="card_title_hanging_up" msgid="814874106866647871">"Kukata simu"</string>
<string name="card_title_in_call" msgid="8231896539567594265">"Katika simu"</string>
<string name="notification_voicemail_title" msgid="3932876181831601351">"Ujumbe mpya wa sauti"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Ndiyo"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Hapana"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Ondoa"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Simu hii iko katika hali ya kuomba upigiwe simu ya dharura"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Hadi <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">Simu hii itakuwa katika hali ya kuomba upigiwe simu ya dharura kwa dakika <xliff:g id="COUNT_1">%s</xliff:g>.\nJe, ungependa kuondoka sasa?</item>
- <item quantity="one">Simu hii itakuwa katika hali ya kuomba upigiwe simu ya dharura kwa dakika <xliff:g id="COUNT_0">%s</xliff:g>.\nJe, ungependa kuondoka sasa?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Huduma"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Weka mipangilio"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Haijawekwa>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Onyesha upya"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Geuza Ukaguzi wa DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Maelezo/Mipangilio Mahususi kwa Kampuni Inayotengeneza Vifaa (OEM)"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC Inapatikana:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR Imewekewa Vizuizi:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR Inapatikana:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Hali ya NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Masafa ya NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Weka Hali ya Bendi ya Redio"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Inapakia Orodha ya Bendi…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Weka"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 9c40867..8382af1 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -307,7 +307,7 @@
<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_status_subtext" msgid="1110276415078236687">"அதிகபட்ச சேமிப்பிடம் <xliff:g id="USED_2">%3$s</xliff:g> இல் <xliff:g id="USED_0">%1$s</xliff:g> (<xliff:g id="USED_1">%2$d</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_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> Kb/s க்குத் தரவு கட்டணம் குறைந்தது"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g>٪ சுழற்சி கடந்துவிட்டன\nஅடுத்த காலம் <xliff:g id="USED_1">%2$d</xliff:g> நாட்களில் தொடங்கும் (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -561,7 +561,7 @@
<string name="ambulance_type_description" msgid="6798237503553180461">"ஆம்புலன்ஸ்"</string>
<string name="fire_type_description" msgid="6565200468934914930">"தீயணைப்பு"</string>
<string name="description_concat_format" msgid="2014471565101724088">"%1$s, %2$s"</string>
- <string name="dialerKeyboardHintText" msgid="1115266533703764049">"டயல் செய்வதற்கு கீபோர்டைப் பயன்படுத்துக"</string>
+ <string name="dialerKeyboardHintText" msgid="1115266533703764049">"டயல் செய்வதற்கு விசைப்பலகையைப் பயன்படுத்துக"</string>
<string name="onscreenHoldText" msgid="4025348842151665191">"காத்திரு"</string>
<string name="onscreenEndCallText" msgid="6138725377654842757">"முடி"</string>
<string name="onscreenShowDialpadText" msgid="658465753816164079">"டயல்பேடு"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"ஆம்"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"இல்லை"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"விலக்கு"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"மொபைல் இப்போது அவசரகால திரும்ப அழைக்கும் பயன்முறையில் உள்ளது"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">மொபைல் இன்னும் <xliff:g id="COUNT_1">%s</xliff:g> நிமிடங்களுக்கு அவசரகால திரும்ப அழைக்கும் பயன்முறையில் இருக்கும்.\nஇப்போதே அதை விட்டு வெளியேறவா?</item>
- <item quantity="one">மொபைல் இன்னும் <xliff:g id="COUNT_0">%s</xliff:g> நிமிடத்திற்கு அவசரகால திரும்ப அழைக்கும் பயன்முறையில் இருக்கும்.\nஇப்போதே அதை விட்டு வெளியேறவா?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"சேவை"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"அமைவு"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<அமைக்கப்படவில்லை>"</string>
@@ -842,7 +836,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>\nவாய்ஸ் ஓவர் LTE: <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>\nவாய்ஸ் ஓவர் LTE: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nவாய்ஸ் ஓவர் WiFi: <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>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"புதுப்பி"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS சரிபார்ப்பை நிலைமாற்று"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM சார்ந்த தகவல்/அமைப்புகள்"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC உள்ளது:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR கட்டுப்படுத்தப்பட்டது:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR உள்ளது:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR நிலை:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR அலைவரிசை:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"ரேடியோ பேண்டு பயன்முறையை அமை"</string>
<string name="band_mode_loading" msgid="795923726636735967">"பேண்டு பட்டியலை ஏற்றுகிறது…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"அமை"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 1849581..a4f1660 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -128,7 +128,7 @@
<string name="call_settings_with_label" msgid="8460230435361579511">"సెట్టింగ్లు (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="error_updating_title" msgid="2024290892676808965">"కాల్ సెట్టింగ్ల లోపం"</string>
<string name="reading_settings" msgid="1605904432450871183">"సెట్టింగ్లను చదువుతోంది…"</string>
- <string name="updating_settings" msgid="3650396734816028808">"సెట్టింగ్లను అప్డేట్ చేస్తోంది…"</string>
+ <string name="updating_settings" msgid="3650396734816028808">"సెట్టింగ్లను నవీకరిస్తోంది…"</string>
<string name="reverting_settings" msgid="7378668837291012205">"సెట్టింగ్లను తిరిగి మారుస్తోంది…"</string>
<string name="response_error" msgid="3904481964024543330">"నెట్వర్క్ నుండి ఊహించని ప్రతిస్పందన."</string>
<string name="exception_error" msgid="330994460090467">"నెట్వర్క్ లేదా SIM కార్డు లోపం."</string>
@@ -141,7 +141,7 @@
<string name="close_dialog" msgid="1074977476136119408">"సరే"</string>
<string name="enable" msgid="2636552299455477603">"ఆన్ చేయి"</string>
<string name="disable" msgid="1122698860799462116">"ఆఫ్ చేయి"</string>
- <string name="change_num" msgid="6982164494063109334">"అప్డేట్ చేయి"</string>
+ <string name="change_num" msgid="6982164494063109334">"నవీకరించు"</string>
<string-array name="clir_display_values">
<item msgid="8477364191403806960">"నెట్వర్క్ డిఫాల్ట్"</item>
<item msgid="6813323051965618926">"నంబర్ను దాచు"</item>
@@ -274,8 +274,8 @@
<string name="data_enable_summary" msgid="696860063456536557">"డేటా వినియోగాన్ని అనుమతించు"</string>
<string name="dialog_alert_title" msgid="5260471806940268478">"హెచ్చరిక"</string>
<string name="roaming" msgid="1576180772877858949">"రోమింగ్"</string>
- <string name="roaming_enable" msgid="6853685214521494819">"రోమింగ్లో ఉన్నప్పుడు డేటా సర్వీసులకు కనెక్ట్ చేయండి"</string>
- <string name="roaming_disable" msgid="8856224638624592681">"రోమింగ్లో ఉన్నప్పుడు డేటా సర్వీసులకు కనెక్ట్ చేయండి"</string>
+ <string name="roaming_enable" msgid="6853685214521494819">"రోమింగ్లో ఉన్నప్పుడు డేటా సేవలకు కనెక్ట్ చేయి"</string>
+ <string name="roaming_disable" msgid="8856224638624592681">"రోమింగ్లో ఉన్నప్పుడు డేటా సేవలకు కనెక్ట్ చేయి"</string>
<string name="roaming_reenable_message" msgid="1951802463885727915">"డేటా రోమింగ్ ఆఫ్ చేయబడింది. ఆన్ చేయడానికి నొక్కండి."</string>
<string name="roaming_enabled_message" msgid="9022249120750897">"రోమింగ్ ఛార్జీలు వర్తించవచ్చు. మార్చడానికి నొక్కండి."</string>
<string name="roaming_notification_title" msgid="3590348480688047320">"మొబైల్ డేటా కనెక్షన్ని కోల్పోయారు"</string>
@@ -288,7 +288,7 @@
<string name="limited_sim_function_notification_message" msgid="5338638075496721160">"వేరొక SIMను ఉపయోగిస్తున్నప్పుడు <xliff:g id="CARRIER_NAME">%1$s</xliff:g> కాల్లు మరియు డేటా సేవలు బ్లాక్ చేయబడవచ్చు."</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>
+ <string name="advanced_options_title" msgid="9208195294513520934">"అధునాతనం"</string>
<string name="carrier_settings_euicc" msgid="1190237227261337749">"క్యారియర్"</string>
<string name="keywords_carrier_settings_euicc" msgid="8540160967922063745">"క్యారియర్, ఇసిమ్, సిమ్, ఇయుక్, క్యారియర్లను మార్చు, క్యారియర్ను జోడించు"</string>
<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>
@@ -307,7 +307,7 @@
<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_status_subtext" msgid="1110276415078236687">"గరిష్ఠ వ్యవధి అయిన <xliff:g id="USED_2">%3$s</xliff:g>లో <xliff:g id="USED_0">%1$s</xliff:g> (<xliff:g id="USED_1">%2$d</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_status_subtext" msgid="1110276415078236687">"గరిష్టంగా <xliff:g id="USED_2">%3$s</xliff:g> వ్యవధిలో <xliff:g id="USED_0">%1$s</xliff:g> (<xliff:g id="USED_1">%2$d</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_2">%3$s</xliff:g>లో <xliff:g id="USED_0">%1$s</xliff:g> (<xliff:g id="USED_1">%2$d</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> Kb/sకి తగ్గించబడింది"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"సైకిల్లో <xliff:g id="USED_0">%1$d</xliff:g>٪ గడిచిపోయింది\nతదుపరి వ్యవధి <xliff:g id="USED_1">%2$d</xliff:g> రోజుల్లో (<xliff:g id="USED_2">%3$s</xliff:g>) ప్రారంభమవుతుంది"</string>
@@ -581,7 +581,7 @@
<string name="failedToImportSingleContactMsg" msgid="228095510489830266">"పరిచయాన్ని దిగుమతి చేయడంలో విఫలమైంది"</string>
<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_title" msgid="3075948111362818043">"నిజ సమయ వచనం (RTT) కాల్"</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>
@@ -625,8 +625,8 @@
<string name="phone_in_ecm_call_notification_text" msgid="653972232922670335">"డేటా కనెక్షన్ నిలిపివేయబడింది"</string>
<string name="phone_in_ecm_notification_complete_time" msgid="7341624337163082759">"<xliff:g id="COMPLETETIME">%s</xliff:g> వరకు డేటా కనెక్షన్ ఉండదు"</string>
<plurals name="alert_dialog_exit_ecm" formatted="false" msgid="5425906903766466743">
- <item quantity="other">ఫోన్ <xliff:g id="COUNT_1">%s</xliff:g> నిమిషాల పాటు అత్యవసర కాల్బ్యాక్ మోడ్లో ఉంటుంది. ఈ మోడ్లో ఉన్నప్పుడు, డేటా కనెక్షన్ను ఉపయోగించే యాప్లు ఏవీ ఉపయోగించబడవు. మీరు ఇప్పుడు నిష్క్రమించాలనుకుంటున్నారా?</item>
- <item quantity="one">ఫోన్ <xliff:g id="COUNT_0">%s</xliff:g> నిమిషం పాటు అత్యవసర కాల్బ్యాక్ మోడ్లో ఉంటుంది. ఈ మోడ్లో ఉన్నప్పుడు, డేటా కనెక్షన్ను ఉపయోగించే యాప్లు ఏవీ ఉపయోగించబడవు. మీరు ఇప్పుడు నిష్క్రమించాలనుకుంటున్నారా?</item>
+ <item quantity="other">ఫోన్ <xliff:g id="COUNT_1">%s</xliff:g> నిమిషాల పాటు అత్యవసర కాల్బ్యాక్ మోడ్లో ఉంటుంది. ఈ మోడ్లో ఉన్నప్పుడు, డేటా కనెక్షన్ను ఉపయోగించే అనువర్తనాలు ఏవీ ఉపయోగించబడవు. మీరు ఇప్పుడు నిష్క్రమించాలనుకుంటున్నారా?</item>
+ <item quantity="one">ఫోన్ <xliff:g id="COUNT_0">%s</xliff:g> నిమిషం పాటు అత్యవసర కాల్బ్యాక్ మోడ్లో ఉంటుంది. ఈ మోడ్లో ఉన్నప్పుడు, డేటా కనెక్షన్ను ఉపయోగించే అనువర్తనాలు ఏవీ ఉపయోగించబడవు. మీరు ఇప్పుడు నిష్క్రమించాలనుకుంటున్నారా?</item>
</plurals>
<plurals name="alert_dialog_not_avaialble_in_ecm" formatted="false" msgid="1152682528741457004">
<item quantity="other">అత్యవసర కాల్బ్యాక్ మోడ్లో ఉన్నప్పుడు, ఎంచుకున్న చర్య అందుబాటులో లేదు. ఫోన్ <xliff:g id="COUNT_1">%s</xliff:g> నిమిషాల పాటు ఈ మోడ్లో ఉంటుంది. మీరు ఇప్పుడు నిష్క్రమించాలనుకుంటున్నారా?</item>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"అవును"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"కాదు"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"తీసివేయి"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"ఫోన్ అత్యవసర కాల్బ్యాక్ మోడ్లో ఉంది"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">ఫోన్ అత్యవసర కాల్బ్యాక్ మోడ్లో <xliff:g id="COUNT_1">%s</xliff:g> నిమిషాల పాటు ఉంటుంది.\n మీరు ఇప్పుడే నిష్క్రమించాలనుకుంటున్నారా?</item>
- <item quantity="one">ఫోన్ అత్యవసర కాల్బ్యాక్ మోడ్లో <xliff:g id="COUNT_0">%s</xliff:g> నిమిషం పాటు ఉంటుంది. \n మీరు ఇప్పుడే నిష్క్రమించాలనుకుంటున్నారా?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"సేవ"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"సెటప్"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<సెట్ చేయలేదు>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"రిఫ్రెష్ చేయి"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS తనిఖీని టోగుల్ చేయండి"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-నిర్దిష్ట సమాచారం/సెట్టింగ్లు"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC అందుబాటులో ఉంది:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR నియంత్రించబడింది:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR అందుబాటులో ఉంది:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR స్టేటస్:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR ఫ్రీక్వెన్సీ:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"రేడియో బ్యాండ్ మోడ్ను సెట్ చేయండి"</string>
<string name="band_mode_loading" msgid="795923726636735967">"బ్యాండ్ జాబితాను లోడ్ చేస్తోంది…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"సెట్ చేయి"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index e527e89..e5eee82 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -307,7 +307,7 @@
<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_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_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> Kb/s"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g>٪ ของรอบผ่านไป \n ช่วงถัดไปจะเริ่มในอีก <xliff:g id="USED_1">%2$d</xliff:g> วัน (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -425,8 +425,8 @@
<string name="cdma_activate_device" msgid="5914720276140097632">"เปิดใช้งานอุปกรณ์"</string>
<string name="cdma_lte_data_service" msgid="359786441782404562">"ตั้งค่าบริการข้อมูล"</string>
<string name="carrier_settings_title" msgid="6292869148169850220">"การตั้งค่าของผู้ให้บริการ"</string>
- <string name="fdn" msgid="2545904344666098749">"หมายเลขโทรออกที่กำหนดตายตัว"</string>
- <string name="fdn_with_label" msgid="6412087553365709494">"หมายเลขโทรออกที่กำหนดตายตัว (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
+ <string name="fdn" msgid="2545904344666098749">"การจำกัดหมายเลขโทรออก"</string>
+ <string name="fdn_with_label" msgid="6412087553365709494">"โหมดจำกัดการโทร (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="manage_fdn_list" msgid="3341716430375195441">"รายการ FDN"</string>
<string name="fdn_list_with_label" msgid="1409655283510382556">"รายการ FDN (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="fdn_activation" msgid="2178637004710435895">"การเปิดใช้งาน FDN"</string>
@@ -518,7 +518,7 @@
<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_text_format" msgid="5720947141702312537">"โทร <xliff:g id="VOICEMAIL_NUMBER">%s</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_network_selection_title" msgid="255595526707809121">"ไม่มีบริการ"</string>
<string name="notification_network_selection_text" msgid="553288408722427659">"เครือข่ายที่เลือกไว้<xliff:g id="OPERATOR_NAME">%s</xliff:g> ไม่พร้อมใช้งาน"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"ใช่"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"ไม่"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"เลิกแสดง"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"โทรศัพท์อยู่ในโหมดติดต่อกลับฉุกเฉิน"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"จนถึง <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">โทรศัพท์จะอยู่ในโหมดติดต่อกลับฉุกเฉินเป็นเวลา <xliff:g id="COUNT_1">%s</xliff:g> นาที\nคุณต้องการออกเลยไหม</item>
- <item quantity="one">โทรศัพท์จะอยู่ในโหมดติดต่อกลับฉุกเฉินเป็นเวลา <xliff:g id="COUNT_0">%s</xliff:g> นาที\nคุณต้องการออกเลยไหม</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"บริการ"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"การตั้งค่า"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<ไม่ได้ตั้งค่า>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"รีเฟรช"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"สลับการตรวจสอบ DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"ข้อมูล/การตั้งค่าเฉพาะตาม OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"มี EN-DC:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR ถูกจำกัด:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"มี NR:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"สถานะ NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"ความถี่ NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"ตั้งค่าโหมดย่านความถี่วิทยุ"</string>
<string name="band_mode_loading" msgid="795923726636735967">"กำลังโหลดรายการย่านความถี่…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"ตั้งค่า"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 83b4585..e8b075a 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -74,7 +74,7 @@
<string name="phone_accounts_configure_account_settings" msgid="6622119715253196586">"I-configure ang mga setting ng account"</string>
<string name="phone_accounts_all_calling_accounts" msgid="1609600743500618823">"Lahat ng account sa pagtawag"</string>
<string name="phone_accounts_all_calling_accounts_summary" msgid="2214134955430107240">"Piliin kung aling mga account ang maaaring tumawag"</string>
- <string name="wifi_calling" msgid="3650509202851355742">"Pagtawag gamit ang Wi-Fi"</string>
+ <string name="wifi_calling" msgid="3650509202851355742">"Pagtawag sa pamamagitan ng Wi-Fi"</string>
<string name="connection_service_default_label" msgid="7332739049855715584">"Built-in na serbisyo ng koneksyon"</string>
<string name="voicemail" msgid="7697769412804195032">"Voicemail"</string>
<string name="voicemail_settings_with_label" msgid="4228431668214894138">"Voicemail (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
@@ -306,7 +306,7 @@
<string name="throttle_current_usage" msgid="7483859109708658613">"Ginamit na data sa kasalukuyang panahon"</string>
<string name="throttle_time_frame" msgid="1813452485948918791">"Panahon ng paggamit ng data"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Patakaran ng rate ng data"</string>
- <string name="throttle_help" msgid="2624535757028809735">"Matuto pa"</string>
+ <string name="throttle_help" msgid="2624535757028809735">"Matuto nang higit pa"</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>٪) ng <xliff:g id="USED_2">%3$s</xliff:g> maximum na tagal ng panahon\nMagsisimula ang susunod na tagal ng panahon sa loob ng <xliff:g id="USED_3">%4$d</xliff:g> (na) araw (<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>٪) ng <xliff:g id="USED_2">%3$s</xliff:g> maximum na period"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"Ang <xliff:g id="USED_0">%1$s</xliff:g> na maximum ay lumagpas sa\nBinabaan ang rate ng data sa <xliff:g id="USED_1">%2$d</xliff:g> Kb/s"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Oo"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Hindi"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Balewalain"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Nasa emergency callback mode ang telepono"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Hanggang <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">Nasa emergency callback mode ang telepono nang <xliff:g id="COUNT_1">%s</xliff:g> minuto.\nGusto mo bang lumabas ngayon?</item>
- <item quantity="other">Nasa emergency callback mode ang telepono nang <xliff:g id="COUNT_1">%s</xliff:g> na minuto.\nGusto mo bang lumabas ngayon?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Serbisyo"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Setup"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Hindi nakatakda>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"I-refresh"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"I-toggle ang DNS Check"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Impormasyon/Mga Setting na partikular sa OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"Available ang EN-DC:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"Pinaghihigpitan ang DCNR:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"Available ang NR:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Status ng NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Frequency ng NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Itakda ang Band Mode ng Radyo"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Nilo-load ang Listahan ng Band…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Itakda"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 3a9a099..05412b1 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -546,7 +546,7 @@
<string name="emergency_information_hint" msgid="9208897544917793012">"Acil durum bilgisi"</string>
<string name="emergency_information_owner_hint" msgid="6256909888049185316">"Sahip"</string>
<string name="emergency_information_confirm_hint" msgid="5109017615894918914">"Bilgileri görüntülemek için tekrar dokunun"</string>
- <string name="emergency_enable_radio_dialog_title" msgid="2667568200755388829">"Acil durum araması"</string>
+ <string name="emergency_enable_radio_dialog_title" msgid="2667568200755388829">"Acil durum çağrısı"</string>
<string name="single_emergency_number_title" msgid="8413371079579067196">"Acil durum numarası"</string>
<string name="numerous_emergency_numbers_title" msgid="8972398932506755510">"Acil durum numaraları"</string>
<string name="emergency_call_shortcut_hint" msgid="1290485125107779500">"<xliff:g id="EMERGENCY_NUMBER">%s</xliff:g> numaralı telefonu aramak için tekrar dokunun"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Evet"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Hayır"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Kapat"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Telefon, acil geri arama modunda"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Şu saate kadar: <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">Telefon <xliff:g id="COUNT_1">%s</xliff:g> dakika boyunca acil geri arama modunda olacak.\nŞimdi çıkmak istiyor musunuz?</item>
- <item quantity="one">Telefon <xliff:g id="COUNT_0">%s</xliff:g> dakika boyunca acil geri arama modunda olacak.\nŞimdi çıkmak istiyor musunuz?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Hizmet"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Kurulum"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Ayarlanmadı>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Yenile"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS Denetimini Aç/Kapat"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM\'e Özgü Bilgiler/Ayarlar"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC Kullanılabilir:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR Kısıtlanmış:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR Kullanılabilir:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR Durumu:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR Frekansı:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Radyo Bant Modunu Ayarla"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Bant Listesi Yükleniyor…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Ayarla"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 5cdd5fb..ff4cf88 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -307,7 +307,7 @@
<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_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_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>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g>٪ з циклу здійсн.\nНаст. період почин. через <xliff:g id="USED_1">%2$d</xliff:g> дн. (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -641,14 +641,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Так"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Ні"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Відхилити"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Телефон перебуває в режимі екстреного зворотного виклику"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"До <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">Телефон перебуватиме в режимі екстреного зворотного виклику протягом <xliff:g id="COUNT_1">%s</xliff:g> хвилини.\nВийти зараз?</item>
- <item quantity="few">Телефон перебуватиме в режимі екстреного зворотного виклику протягом <xliff:g id="COUNT_1">%s</xliff:g> хвилин.\nВийти зараз?</item>
- <item quantity="many">Телефон перебуватиме в режимі екстреного зворотного виклику протягом <xliff:g id="COUNT_1">%s</xliff:g> хвилин.\nВийти зараз?</item>
- <item quantity="other">Телефон перебуватиме в режимі екстреного зворотного виклику протягом <xliff:g id="COUNT_1">%s</xliff:g> хвилини.\nВийти зараз?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Обслуговування"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Налаштування"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Не введено>"</string>
@@ -904,11 +896,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Оновити"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Увімк./вимк. перевірку DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Інформація/налаштування OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC доступно:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR обмежено:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR доступно:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Статус NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Частота NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Установити режим радіодіапазону"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Завантаження списку діапазонів частот…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Установити"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 4209b1e..9cfeaa3 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -95,17 +95,17 @@
<string name="sum_loading_settings" msgid="434063780286688775">"ترتیبات لوڈ ہو رہی ہیں…"</string>
<string name="sum_hide_caller_id" msgid="131100328602371933">"باہر جانے والی کالوں میں نمبر پوشیدہ ہے"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"باہر جانے والی کالوں میں ڈسپلے نمبر ہوتا ہے"</string>
- <string name="sum_default_caller_id" msgid="1767070797135682959">"آؤٹ گوئنگ کالوں میں میرا نمبر ڈسپلے کرنے کیلئے ڈیفالٹ آپریٹر کی ترتیبات کا استعمال کریں"</string>
+ <string name="sum_default_caller_id" msgid="1767070797135682959">"باہر جانے والی کالوں میں میرا نمبر ڈسپلے کرنے کیلئے ڈیفالٹ آپریٹر کی ترتیبات کا استعمال کریں"</string>
<string name="labelCW" msgid="8449327023861428622">"کال کا انتظار"</string>
- <string name="sum_cw_enabled" msgid="3977308526187139996">"کال کے دوران، مجھے اِن کمنگ کالوں سے مطلع کریں"</string>
- <string name="sum_cw_disabled" msgid="3658094589461768637">"کال کے دوران، مجھے اِن کمنگ کالوں سے مطلع کریں"</string>
- <string name="call_forwarding_settings" msgid="8937130467468257671">"کال فارورڈنگ کی ترتیبات"</string>
+ <string name="sum_cw_enabled" msgid="3977308526187139996">"کال کے دوران، مجھے آنے والی کالوں سے مطلع کریں"</string>
+ <string name="sum_cw_disabled" msgid="3658094589461768637">"کال کے دوران، مجھے آنے والی کالوں سے مطلع کریں"</string>
+ <string name="call_forwarding_settings" msgid="8937130467468257671">"کال آگے منتقل کرنے کی ترتیبات"</string>
<string name="call_forwarding_settings_with_label" msgid="2345432813399564272">"کال فارورڈنگ ترتیبات (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="labelCF" msgid="3578719437928476078">"کال فارورڈنگ"</string>
- <string name="labelCFU" msgid="8870170873036279706">"ہمیشہ فارورڈ کریں"</string>
+ <string name="labelCFU" msgid="8870170873036279706">"ہمیشہ آگے منتقل کریں"</string>
<string name="messageCFU" msgid="1361806450979589744">"ہمیشہ یہ نمبر استعمال کریں"</string>
<string name="sum_cfu_enabled_indicator" msgid="9030139213402432776">"سبھی کالیں آگے منتقل کر رہا ہے"</string>
- <string name="sum_cfu_enabled" msgid="5806923046528144526">"سبھی کالیں <xliff:g id="PHONENUMBER">{0}</xliff:g> کو فارورڈ ہو رہی ہیں"</string>
+ <string name="sum_cfu_enabled" msgid="5806923046528144526">"سبھی کالیں <xliff:g id="PHONENUMBER">{0}</xliff:g> کو آگے منتقل کر رہا ہے"</string>
<string name="sum_cfu_enabled_no_number" msgid="7287752761743377930">"نمبر دستیاب نہیں ہے"</string>
<string name="sum_cfu_disabled" msgid="5010617134210809853">"آف"</string>
<string name="labelCFB" msgid="615265213360512768">"مصروف ہونے پر"</string>
@@ -159,7 +159,7 @@
<string name="vm_change_pin_progress_message" msgid="626015184502739044">"براہ کرم انتظار کریں۔"</string>
<string name="vm_change_pin_error_too_short" msgid="1789139338449945483">"نیا PIN بہت ہی مختصر ہے۔"</string>
<string name="vm_change_pin_error_too_long" msgid="3634907034310018954">"نیا PIN بہت ہی طویل ہے۔"</string>
- <string name="vm_change_pin_error_too_weak" msgid="8581892952627885719">"نیا PIN بہت ہی کمزور ہے۔ مضبوط پاس ورڈ میں مسلسل ترتیب یا دہرے عدد نہیں ہونے چاہئیں۔"</string>
+ <string name="vm_change_pin_error_too_weak" msgid="8581892952627885719">"نیا PIN بہت ہی کمزور ہے۔ مضبوط پاسورڈ میں مسلسل ترتیب یا دہرے عدد نہیں ہونے چاہئیں۔"</string>
<string name="vm_change_pin_error_mismatch" msgid="5364847280026257331">"پرانا PIN مماثل نہیں ہے۔"</string>
<string name="vm_change_pin_error_invalid" msgid="5230002671175580674">"نئے PIN میں غلط کریکٹرز شامل ہیں۔"</string>
<string name="vm_change_pin_error_system_error" msgid="9116483527909681791">"PIN تبدیل کرنے سے قاصر"</string>
@@ -511,7 +511,7 @@
<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>
- <string name="card_title_incoming_call" msgid="881424648458792430">"اِن کمنگ کال"</string>
+ <string name="card_title_incoming_call" msgid="881424648458792430">"آنے والی کال"</string>
<string name="card_title_call_ended" msgid="650223980095026340">"کال ختم ہوگئی"</string>
<string name="card_title_on_hold" msgid="9028319436626975207">"ہولڈ پر"</string>
<string name="card_title_hanging_up" msgid="814874106866647871">"کال منقطع ہو رہی ہے"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"ہاں"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"نہیں"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"کالعدم کریں"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"فون ہنگامی کال بیک وضع میں ہے"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<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">فون <xliff:g id="COUNT_1">%s</xliff:g> منٹ تک ہنگامی کال بیک وضع میں رہے گا۔\n کیا آپ ابھی باہر نکلنا چاہتے ہیں؟</item>
- <item quantity="one">فون <xliff:g id="COUNT_0">%s</xliff:g> منٹ تک ہنگامی کال بیک وضع میں رہے گا۔\n کیا آپ ابھی باہر نکلنا چاہتے ہیں؟</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"سروس"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"سیٹ اپ"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<سیٹ نہیں ہے>"</string>
@@ -812,7 +806,7 @@
<string name="supp_service_held_call_released" msgid="2847835124639112410">"ہولڈ پر رکھی کال جاری ہو گئی۔"</string>
<string name="callFailed_otasp_provisioning_in_process" msgid="3345666183602879326">"کال نہیں کی جا سکتی کیوںکہ فی الحال آلہ فراہم کیا جا رہا ہے۔"</string>
<string name="callFailed_already_dialing" msgid="7250591188960691086">"کال نہیں کی جا سکتی کیونکہ باہر جانے والی دوسری کال پہلے سے ڈائل کی جا رہی ہے۔"</string>
- <string name="callFailed_already_ringing" msgid="2376603543544289303">"ایک جواب نہ ملنے والی اِن کمنگ کال کی وجہ سے کال نہیں کی جا سکتی۔ نئی کال کرنے کے لیے پہلے اِن کمنگ کال کا جواب دیں یا مسترد کریں۔"</string>
+ <string name="callFailed_already_ringing" msgid="2376603543544289303">"ایک جواب نہ ملنے والی موصول ہونے والی کال کی وجہ سے کال نہیں کی جا سکتی۔ نئی کال کرنے کے لیے پہلے موصول ہونے والی کال کا جواب دیں یا مسترد کریں۔"</string>
<string name="callFailed_calling_disabled" msgid="5010992739401206283">"ro.telephony.disable-call سسٹم کی خصوصیت کے ذریعے کال کرنے کو غیر فعال کر دیے جانے کی وجہ سے کال نہیں کی جا سکتی۔"</string>
<string name="callFailed_too_many_calls" msgid="5379426826618582180">"پہلے سے دو کالز کے پیش رفت میں ہونے کی وجہ سے کال نہیں کی جا سکتی۔ نئی کال کرنے کے لیے پہلے ان میں سے ایک کو غیر منسلک کریں یا انہیں کانفرنس میں ضم کریں۔"</string>
<string name="supp_service_over_ut_precautions" msgid="2145018231396701311">"<xliff:g id="SUPP_SERVICE">%s</xliff:g> استعمال کرنے کے لیے، یقینی بنائيں کہ موبائل ڈیٹا آن ہے۔ آپ موبائل یٹ ورک کی ترتیبات میں اسے تبدیل کر سکتے ہیں۔"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"ریفریش کریں"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS چیک ٹوگل کریں"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-کیلئے مخصوص معلومات/ترتیبات"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC دستیاب ہے:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR محدود کردہ:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR دستیاب ہے:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR ریاست:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR فریکوئنسی:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"ریڈیو بینڈ موڈ سیٹ کریں"</string>
<string name="band_mode_loading" msgid="795923726636735967">"بینڈ کی فہرست لوڈ ہو رہی ہے…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"سیٹ کریں"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 982bef7..25d9c28 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -24,11 +24,11 @@
<string name="unknown" msgid="8279698889921830815">"Noma’lum"</string>
<string name="private_num" msgid="4487990167889159992">"Yashirin raqam"</string>
<string name="payphone" msgid="7936735771836716941">"Taksofon"</string>
- <string name="onHold" msgid="6132725550015899006">"Pauzada"</string>
+ <string name="onHold" msgid="6132725550015899006">"Kutmoqda"</string>
<string name="carrier_mmi_msg_title" msgid="6050165242447507034">"<xliff:g id="MMICARRIER">%s</xliff:g> orqali xabar"</string>
<string name="default_carrier_mmi_msg_title" msgid="7754317179938537213">"Operatordan xabar"</string>
<string name="mmiStarted" msgid="9212975136944568623">"MMI kodi ishga tushirildi"</string>
- <string name="ussdRunning" msgid="1163586813106772717">"USSD kod yuborilmoqda…"</string>
+ <string name="ussdRunning" msgid="1163586813106772717">"USSD so‘rov bajarilmoqda…"</string>
<string name="mmiCancelled" msgid="5339191899200678272">"MMI kodi bekor qilindi"</string>
<string name="cancel" msgid="8984206397635155197">"Bekor qilish"</string>
<string name="enter_input" msgid="6193628663039958990">"USSD xabari <xliff:g id="MIN_LEN">%1$d</xliff:g>dan <xliff:g id="MAX_LEN">%2$d</xliff:g>tagacha belgi bo‘lishi kerak. Yana urinib ko‘ring."</string>
@@ -127,7 +127,7 @@
<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>
<string name="error_updating_title" msgid="2024290892676808965">"Chaqiruv sozlamalarida xato"</string>
- <string name="reading_settings" msgid="1605904432450871183">"Sozlamalar olinmoqda…"</string>
+ <string name="reading_settings" msgid="1605904432450871183">"Sozlamalar o‘qilmoqda…"</string>
<string name="updating_settings" msgid="3650396734816028808">"Sozlamalar yangilanmoqda…"</string>
<string name="reverting_settings" msgid="7378668837291012205">"Sozlamalar tiklanmoqda…"</string>
<string name="response_error" msgid="3904481964024543330">"Tarmoqdan kutilmagan javob."</string>
@@ -140,7 +140,7 @@
<string name="radio_off_error" msgid="8321564164914232181">"Ushbu sozlamalarni ko‘rishdan oldin radioni yoqing."</string>
<string name="close_dialog" msgid="1074977476136119408">"OK"</string>
<string name="enable" msgid="2636552299455477603">"Yoqish"</string>
- <string name="disable" msgid="1122698860799462116">"Faolsizlantirish"</string>
+ <string name="disable" msgid="1122698860799462116">"O‘chirib qo‘yish"</string>
<string name="change_num" msgid="6982164494063109334">"Yangilash"</string>
<string-array name="clir_display_values">
<item msgid="8477364191403806960">"Standart tarmoq sozlamalari"</item>
@@ -307,7 +307,7 @@
<string name="throttle_time_frame" msgid="1813452485948918791">"Ma‘lumotdan foydalanish muddati"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Ma‘lumotlar uzatish tezligi siyosati"</string>
<string name="throttle_help" msgid="2624535757028809735">"Batafsil"</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> (maksimum)\nKeyingi davr <xliff:g id="USED_3">%4$d</xliff:g> kun ichida boshlanadi (<xliff:g id="USED_4">%5$s</xliff:g>)"</string>
+ <string name="throttle_status_subtext" msgid="1110276415078236687">"Maksimal daraja muddati <xliff:g id="USED_2">%3$s</xliff:g>dan <xliff:g id="USED_0">%1$s</xliff:g> (<xliff:g id="USED_1">%2$d</xliff:g>٪) \nKeyingi muddat <xliff:g id="USED_3">%4$d</xliff:g> kunda (<xliff:g id="USED_4">%5$s</xliff:g>) boshlanadi."</string>
<string name="throttle_data_usage_subtext" msgid="3185429653996709840">"Maksimal daraja muddati <xliff:g id="USED_2">%3$s</xliff:g>dan <xliff:g id="USED_0">%1$s</xliff:g> (<xliff:g id="USED_1">%2$d</xliff:g>٪)"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"<xliff:g id="USED_0">%1$s</xliff:g> maksimal darajadan oshib ketdi\nTrafik tezligi <xliff:g id="USED_1">%2$d</xliff:g> Kb/s’ga pasaydi"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g>٪ o‘tgan bosqichdan\nKeyingi bosqich <xliff:g id="USED_1">%2$d</xliff:g> kunda boshlanadi (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -447,7 +447,7 @@
<string name="auto_retry_mode_title" msgid="2985801935424422340">"Avto-qayta urinish"</string>
<string name="auto_retry_mode_summary" msgid="2863919925349511402">"Avto-qayta urinish rejimini yoqish"</string>
<string name="tty_mode_not_allowed_video_call" msgid="6551976083652752815">"Video qo‘ng‘iroq davomida TTY rejimini o‘zgartirish taqiqlangan"</string>
- <string name="menu_add" msgid="5616487894975773141">"Kontaktni saqlash"</string>
+ <string name="menu_add" msgid="5616487894975773141">"Kontakt saqlash"</string>
<string name="menu_edit" msgid="3593856941552460706">"Kontaktni tahrirlash"</string>
<string name="menu_delete" msgid="6326861853830546488">"Kontaktni o‘chirish"</string>
<string name="menu_dial" msgid="4178537318419450012">"Kontaktga qo‘ng‘iroq qilish"</string>
@@ -522,7 +522,7 @@
<string name="notification_voicemail_no_vm_number" msgid="3423686009815186750">"Ovozli pochta raqami noma’lum"</string>
<string name="notification_network_selection_title" msgid="255595526707809121">"Xizmat mavjud emas"</string>
<string name="notification_network_selection_text" msgid="553288408722427659">"Tanlangan tarmoq (<xliff:g id="OPERATOR_NAME">%s</xliff:g>) mavjud emas"</string>
- <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="watch" msgid="7191184639454113633">"Qo‘ng‘iroq qilish uchun mobil tarmoqni yoqing, parvoz yoki quvvat tejash rejimini o‘chirib qo‘ying."</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_ecm_emergency_only" msgid="5622379058883722080">"Odatiy qo‘ng‘iroq qilish uchun favqulodda qayta qo‘ng‘iroq rejimidan chiqing."</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Ha"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Yo‘q"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Rad etish"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Telefon favqulodda teskari chaqiruv rejimida"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"<xliff:g id="COMPLETETIME">%s</xliff:g> gacha"</string>
- <plurals name="alert_dialog_exit_ecm_without_data_restriction_hint" formatted="false" msgid="6477733043040328640">
- <item quantity="other">Telefon favqulodda teskari chaqiruv rejimida <xliff:g id="COUNT_1">%s</xliff:g> daqiqa qoladi.\nBu rejimdan chiqilsinmi?</item>
- <item quantity="one">Telefon favqulodda teskari chaqiruv rejimida <xliff:g id="COUNT_0">%s</xliff:g> daqiqa qoladi.\nBu rejimdan chiqilsinmi?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Xizmat"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Sozlash"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Kiritilmagan>"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Yangilash"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS tekshiruvini yoqish/o‘chirish"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM maxsus axboroti va sozlamalari"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC ochiq:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR cheklangan:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR ochiq:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR holati:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR chastotasi:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Tarmoq rejimini sozlash"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Chastotalar ro‘yxati yuklanmoqda…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Saqlash"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index d34d544..371d0da 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -24,7 +24,7 @@
<string name="unknown" msgid="8279698889921830815">"Không xác định"</string>
<string name="private_num" msgid="4487990167889159992">"Số cá nhân"</string>
<string name="payphone" msgid="7936735771836716941">"Điện thoại công cộng"</string>
- <string name="onHold" msgid="6132725550015899006">"Đang giữ máy"</string>
+ <string name="onHold" msgid="6132725550015899006">"Đang chờ"</string>
<string name="carrier_mmi_msg_title" msgid="6050165242447507034">"Tin nhắn <xliff:g id="MMICARRIER">%s</xliff:g>"</string>
<string name="default_carrier_mmi_msg_title" msgid="7754317179938537213">"Tin nhắn nhà mạng"</string>
<string name="mmiStarted" msgid="9212975136944568623">"Đã bắt đầu mã MMI"</string>
@@ -85,13 +85,13 @@
<string name="voicemail_notifications_preference_title" msgid="7829238858063382977">"Thông báo"</string>
<string name="cell_broadcast_settings" msgid="8135324242541809924">"Phát sóng trong tình huống khẩn cấp"</string>
<string name="call_settings" msgid="3677282690157603818">"Cài đặt cuộc gọi"</string>
- <string name="additional_gsm_call_settings" msgid="1561980168685658846">"Tùy chọn cài đặt bổ sung"</string>
- <string name="additional_gsm_call_settings_with_label" msgid="7973920539979524908">"Tùy chọn cài đặt bổ sung (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
+ <string name="additional_gsm_call_settings" msgid="1561980168685658846">"Cài đặt bổ sung"</string>
+ <string name="additional_gsm_call_settings_with_label" msgid="7973920539979524908">"Cài đặt bổ sung (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="sum_gsm_call_settings" msgid="7964692601608878138">"Cài đặt cuộc gọi chỉ GSM bổ sung"</string>
<string name="additional_cdma_call_settings" msgid="2178016561980611304">"Cài đặt cuộc gọi CDMA bổ sung"</string>
<string name="sum_cdma_call_settings" msgid="3185825305136993636">"Cài đặt cuộc gọi chỉ CDMA bổ sung"</string>
<string name="labelNwService" msgid="6015891883487125120">"Cài đặt dịch vụ mạng"</string>
- <string name="labelCallerId" msgid="2090540744550903172">"Số nhận dạng người gọi"</string>
+ <string name="labelCallerId" msgid="2090540744550903172">"ID người gọi"</string>
<string name="sum_loading_settings" msgid="434063780286688775">"Đang tải cài đặt…"</string>
<string name="sum_hide_caller_id" msgid="131100328602371933">"Số bị ẩn trong cuộc gọi đi"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"Số được hiển thị trong cuộc gọi đi"</string>
@@ -127,8 +127,8 @@
<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>
<string name="error_updating_title" msgid="2024290892676808965">"Lỗi cài đặt cuộc gọi"</string>
- <string name="reading_settings" msgid="1605904432450871183">"Đang đọc các tùy chọn cài đặt…"</string>
- <string name="updating_settings" msgid="3650396734816028808">"Đang cập nhật các tùy chọn cài đặt..."</string>
+ <string name="reading_settings" msgid="1605904432450871183">"Đang đọc cài đặt…"</string>
+ <string name="updating_settings" msgid="3650396734816028808">"Đang cập nhật cài đặt..."</string>
<string name="reverting_settings" msgid="7378668837291012205">"Đang hoàn nguyên cài đặt…"</string>
<string name="response_error" msgid="3904481964024543330">"Phản hồi không mong muốn từ mạng."</string>
<string name="exception_error" msgid="330994460090467">"Lỗi mạng hoặc thẻ SIM."</string>
@@ -277,7 +277,7 @@
<string name="roaming_enable" msgid="6853685214521494819">"Kết nối với dịch vụ dữ liệu khi chuyển vùng"</string>
<string name="roaming_disable" msgid="8856224638624592681">"Kết nối với dịch vụ dữ liệu khi chuyển vùng"</string>
<string name="roaming_reenable_message" msgid="1951802463885727915">"Tính năng Chuyển vùng dữ liệu bị tắt. Nhấn để bật."</string>
- <string name="roaming_enabled_message" msgid="9022249120750897">"Bạn có thể bị tính phí chuyển vùng. Nhấn để sửa đổi."</string>
+ <string name="roaming_enabled_message" msgid="9022249120750897">"Có thể áp dụng phí chuyển vùng. Nhấn để sửa đổi."</string>
<string name="roaming_notification_title" msgid="3590348480688047320">"Đã mất kết nối dữ liệu di động"</string>
<string name="roaming_on_notification_title" msgid="7451473196411559173">"Chuyển vùng dữ liệu đang bật"</string>
<string name="roaming_warning" msgid="7855681468067171971">"Bạn có thể phải trả một khoản phí lớn."</string>
@@ -299,7 +299,7 @@
<string name="sim_change_data_title" msgid="9142726786345906606">"Thay đổi SIM cho dữ liệu di động?"</string>
<string name="sim_change_data_message" msgid="3567358694255933280">"Sử dụng <xliff:g id="NEW_SIM">%1$s</xliff:g> thay vì <xliff:g id="OLD_SIM">%2$s</xliff:g> cho dữ liệu di động?"</string>
<string name="wifi_calling_settings_title" msgid="5800018845662016507">"Gọi qua Wi-Fi"</string>
- <string name="video_calling_settings_title" msgid="342829454913266078">"Tính năng gọi video của nhà cung cấp dịch vụ"</string>
+ <string name="video_calling_settings_title" msgid="342829454913266078">"Tính năng gọi điện video của nhà cung cấp dịch vụ"</string>
<string name="gsm_umts_options" msgid="4968446771519376808">"Tùy chọn GSM/UMTS"</string>
<string name="cdma_options" msgid="3669592472226145665">"Tùy chọn CDMA"</string>
<string name="throttle_data_usage" msgid="1944145350660420711">"Sử dụng dữ liệu"</string>
@@ -307,7 +307,7 @@
<string name="throttle_time_frame" msgid="1813452485948918791">"Thời gian sử dụng dữ liệu"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Chính sách tốc độ dữ liệu"</string>
<string name="throttle_help" msgid="2624535757028809735">"Tìm hiểu thêm"</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>%%) trong tổng <xliff:g id="USED_2">%3$s</xliff:g> thời gian tối đa\nThời gian tiếp theo bắt đầu sau <xliff:g id="USED_3">%4$d</xliff:g> ngày nữa (<xliff:g id="USED_4">%5$s</xliff:g>)"</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>٪) trong tổng số <xliff:g id="USED_2">%3$s</xliff:g> thời gian tối đa\nThời gian tiếp theo bắt đầu sau <xliff:g id="USED_3">%4$d</xliff:g> ngày nữa (<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>٪) trong tổng số <xliff:g id="USED_2">%3$s</xliff:g> thời gian tối đa"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"Đã vượt quá tối đa <xliff:g id="USED_0">%1$s</xliff:g>\nTốc độ dữ liệu bị giảm xuống <xliff:g id="USED_1">%2$d</xliff:g> Kb/giây"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g>٪ của chu kỳ đã qua\nThời gian tiếp theo sẽ bắt đầu trong <xliff:g id="USED_1">%2$d</xliff:g> ngày (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -447,7 +447,7 @@
<string name="auto_retry_mode_title" msgid="2985801935424422340">"Tự động thử lại"</string>
<string name="auto_retry_mode_summary" msgid="2863919925349511402">"Bật chế độ Tự động thử lại"</string>
<string name="tty_mode_not_allowed_video_call" msgid="6551976083652752815">"Không được phép thay đổi Chế độ Máy điện báo đánh chữ (TTY) trong cuộc gọi video"</string>
- <string name="menu_add" msgid="5616487894975773141">"Thêm người liên hệ"</string>
+ <string name="menu_add" msgid="5616487894975773141">"Thêm liên hệ"</string>
<string name="menu_edit" msgid="3593856941552460706">"Chỉnh sửa liên hệ"</string>
<string name="menu_delete" msgid="6326861853830546488">"Xóa liên hệ"</string>
<string name="menu_dial" msgid="4178537318419450012">"Quay số liên hệ"</string>
@@ -513,7 +513,7 @@
<string name="card_title_conf_call" msgid="901197309274457427">"Cuộc gọi hội nghị"</string>
<string name="card_title_incoming_call" msgid="881424648458792430">"Cuộc gọi đến"</string>
<string name="card_title_call_ended" msgid="650223980095026340">"Cuộc gọi đã kết thúc"</string>
- <string name="card_title_on_hold" msgid="9028319436626975207">"Đang giữ máy"</string>
+ <string name="card_title_on_hold" msgid="9028319436626975207">"Đang chờ"</string>
<string name="card_title_hanging_up" msgid="814874106866647871">"Kết thúc cuộc gọi"</string>
<string name="card_title_in_call" msgid="8231896539567594265">"Đang trong cuộc gọi"</string>
<string name="notification_voicemail_title" msgid="3932876181831601351">"Thư thoại mới"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Có"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Không"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Loại bỏ"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Điện thoại đang ở chế độ gọi lại khẩn cấp"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Cho đến <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">Điện thoại sẽ ở chế độ gọi lại khẩn cấp trong <xliff:g id="COUNT_1">%s</xliff:g> phút.\nBạn có muốn thoát ngay không?</item>
- <item quantity="one">Điện thoại sẽ ở chế độ gọi lại khẩn cấp trong <xliff:g id="COUNT_0">%s</xliff:g> phút.\nBạn có muốn thoát ngay không?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Dịch vụ"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Thiết lập"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Chưa đặt>"</string>
@@ -660,8 +654,8 @@
<string name="voicemail_change_pin_dialog_title" msgid="4633077715231764435">"Thay đổi mã PIN"</string>
<string name="preference_category_ringtone" msgid="8787281191375434976">"Nhạc chuông và rung"</string>
<string name="pstn_connection_service_label" msgid="9200102709997537069">"Thẻ SIM tích hợp sẵn"</string>
- <string name="enable_video_calling_title" msgid="7246600931634161830">"Bật gọi video"</string>
- <string name="enable_video_calling_dialog_msg" msgid="7141478720386203540">"Để bật tính năng gọi video, bạn cần bật chế độ 4G LTE tăng cường trong cài đặt mạng."</string>
+ <string name="enable_video_calling_title" msgid="7246600931634161830">"Bật gọi điện video"</string>
+ <string name="enable_video_calling_dialog_msg" msgid="7141478720386203540">"Để bật tính năng gọi điện video, bạn cần bật chế độ 4G LTE tăng cường trong cài đặt mạng."</string>
<string name="enable_video_calling_dialog_settings" msgid="8697890611305307110">"Cài đặt mạng"</string>
<string name="enable_video_calling_dialog_close" msgid="4298929725917045270">"Đóng"</string>
<string name="sim_label_emergency_calls" msgid="9078241989421522310">"Cuộc gọi khẩn cấp"</string>
@@ -804,7 +798,7 @@
<string name="supp_service_additional_call_forwarded" msgid="8772753260008398632">"Đã chuyển tiếp cuộc gọi bổ sung."</string>
<string name="supp_service_additional_ect_connected" msgid="8525934162945220237">"Hoàn tất chuyển cuộc gọi ở chế độ rõ."</string>
<string name="supp_service_additional_ect_connecting" msgid="7046240728781222753">"Đang chuyển cuộc gọi ở chế độ rõ."</string>
- <string name="supp_service_call_on_hold" msgid="2836811319594503059">"Cuộc gọi đang giữ máy."</string>
+ <string name="supp_service_call_on_hold" msgid="2836811319594503059">"Cuộc gọi đang chờ."</string>
<string name="supp_service_call_resumed" msgid="3786864005920743546">"Đã tiếp tục cuộc gọi."</string>
<string name="supp_service_deflected_call" msgid="7565979024562921707">"Cuộc gọi đã bị chuyển hướng."</string>
<string name="supp_service_forwarded_call" msgid="6475776013771821457">"Cuộc gọi đã được chuyển tiếp."</string>
@@ -823,7 +817,7 @@
<string name="radio_info_data_connection_enable" msgid="6183729739783252840">"Bật kết nối dữ liệu"</string>
<string name="radio_info_data_connection_disable" msgid="6404751291511368706">"Tắt kết nối dữ liệu"</string>
<string name="volte_provisioned_switch_string" msgid="4812874990480336178">"Đã cấp phép VoLTE"</string>
- <string name="vt_provisioned_switch_string" msgid="8295542122512195979">"Đã cấp phép gọi video"</string>
+ <string name="vt_provisioned_switch_string" msgid="8295542122512195979">"Đã cấp phép gọi điện video"</string>
<string name="wfc_provisioned_switch_string" msgid="3835004640321078988">"Đã cấp phép Gọi qua Wi-Fi"</string>
<string name="eab_provisioned_switch_string" msgid="4449676720736033035">"Đã cấp phép hiện diện/EAB"</string>
<string name="cbrs_data_switch_string" msgid="6060356430838077653">"Dữ liệu Cbrs"</string>
@@ -842,7 +836,7 @@
<string name="radio_info_ims_reg_status_not_registered" msgid="8045821447288876085">"Chưa đăng ký"</string>
<string name="radio_info_ims_feature_status_available" msgid="6493200914756969292">"Có sẵn"</string>
<string name="radio_info_ims_feature_status_unavailable" msgid="8930391136839759778">"Không có sẵn"</string>
- <string name="radio_info_ims_reg_status" msgid="25582845222446390">"Đăng ký IMS: <xliff:g id="STATUS">%1$s</xliff:g>\nThoại qua LTE: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nThoại qua Wi-Fi: <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nGọi video: <xliff:g id="AVAILABILITY_2">%4$s</xliff:g>\nGiao diện UT: <xliff:g id="AVAILABILITY_3">%5$s</xliff:g>"</string>
+ <string name="radio_info_ims_reg_status" msgid="25582845222446390">"Đăng ký IMS: <xliff:g id="STATUS">%1$s</xliff:g>\nThoại qua LTE: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nThoại qua Wi-Fi: <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nGọi điện video: <xliff:g id="AVAILABILITY_2">%4$s</xliff:g>\nGiao diện UT: <xliff:g id="AVAILABILITY_3">%5$s</xliff:g>"</string>
<string name="radioInfo_service_in" msgid="45753418231446400">"Đang sử dụng"</string>
<string name="radioInfo_service_out" msgid="287972405416142312">"Không có dịch vụ"</string>
<string name="radioInfo_service_emergency" msgid="4763879891415016848">"Chỉ cuộc gọi khẩn cấp"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Làm mới"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Bật/tắt chế độ kiểm tra DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Thông tin/Cài đặt dành riêng cho OEM"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"Hỗ trợ EN-DC:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"Hạn chế DCNR:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"Hỗ trợ NR:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Trạng thái NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Tần số NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Đặt chế độ dải tần số"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Đang tải danh sách băng tần…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Đặt"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index d0d079d..66eae10 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -163,7 +163,7 @@
<string name="vm_change_pin_error_mismatch" msgid="5364847280026257331">"旧的 PIN 码不匹配。"</string>
<string name="vm_change_pin_error_invalid" msgid="5230002671175580674">"新的 PIN 码包含无效字符。"</string>
<string name="vm_change_pin_error_system_error" msgid="9116483527909681791">"无法更改 PIN 码"</string>
- <string name="vvm_unsupported_message_format" msgid="4206402558577739713">"不受支持的语音信息类型,请拨打 <xliff:g id="NUMBER">%s</xliff:g> 收听。"</string>
+ <string name="vvm_unsupported_message_format" msgid="4206402558577739713">"不受支持的语音邮件类型,请拨打 <xliff:g id="NUMBER">%s</xliff:g> 收听。"</string>
<string name="network_settings_title" msgid="7560807107123171541">"移动网络"</string>
<string name="label_available" msgid="1316084116670821258">"可用网络"</string>
<string name="load_networks_progress" msgid="4051433047717401683">"正在搜索..."</string>
@@ -276,13 +276,13 @@
<string name="roaming" msgid="1576180772877858949">"漫游"</string>
<string name="roaming_enable" msgid="6853685214521494819">"漫游时连接到移动数据网络服务"</string>
<string name="roaming_disable" msgid="8856224638624592681">"漫游时连接到移动数据网络服务"</string>
- <string name="roaming_reenable_message" msgid="1951802463885727915">"数据漫游已关闭。点按即可开启。"</string>
+ <string name="roaming_reenable_message" msgid="1951802463885727915">"数据网络漫游已停用。点按即可启用。"</string>
<string name="roaming_enabled_message" msgid="9022249120750897">"可能需要支付漫游费。点按即可修改。"</string>
<string name="roaming_notification_title" msgid="3590348480688047320">"移动网络连接中断"</string>
- <string name="roaming_on_notification_title" msgid="7451473196411559173">"已开启数据漫游功能"</string>
+ <string name="roaming_on_notification_title" msgid="7451473196411559173">"已开启数据网络漫游功能"</string>
<string name="roaming_warning" msgid="7855681468067171971">"这可能会产生高额费用。"</string>
<string name="roaming_check_price_warning" msgid="8212484083990570215">"请与您的网络服务提供商联系以了解定价。"</string>
- <string name="roaming_alert_title" msgid="5689615818220960940">"允许数据漫游?"</string>
+ <string name="roaming_alert_title" msgid="5689615818220960940">"要允许移动数据网络漫游吗?"</string>
<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>
@@ -516,8 +516,8 @@
<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_network_selection_title" msgid="255595526707809121">"无服务"</string>
@@ -637,12 +637,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"是"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"否"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"关闭"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"手机正处于紧急回拨模式"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"直到 <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">手机在 <xliff:g id="COUNT_1">%s</xliff:g> 分钟内都将处于紧急回拨模式。\n您要立即退出吗?</item>
- <item quantity="one">手机在 <xliff:g id="COUNT_0">%s</xliff:g> 分钟内都将处于紧急回拨模式。\n您要立即退出吗?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"服务"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"设置"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<未设置>"</string>
@@ -692,8 +686,8 @@
<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_system_error" msgid="7772788809875146873">"无法设置 PIN 码"</string>
- <string name="mobile_data_status_roaming_turned_off_subtext" msgid="6840673347416227054">"数据漫游服务已关闭"</string>
- <string name="mobile_data_status_roaming_turned_on_subtext" msgid="5615757897768777865">"数据漫游服务已开启"</string>
+ <string name="mobile_data_status_roaming_turned_off_subtext" msgid="6840673347416227054">"数据网络漫游功能已停用"</string>
+ <string name="mobile_data_status_roaming_turned_on_subtext" msgid="5615757897768777865">"数据网络漫游功能已启用"</string>
<string name="mobile_data_status_roaming_without_plan_subtext" msgid="6536671968072284677">"目前处于漫游状态,需要添加数据流量套餐"</string>
<string name="mobile_data_status_roaming_with_plan_subtext" msgid="2576177169108123095">"目前处于漫游状态,数据流量套餐已启用"</string>
<string name="mobile_data_status_no_plan_subtext" msgid="170331026419263657">"无剩余移动数据流量"</string>
@@ -898,11 +892,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"刷新"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"切换 DNS 检查"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"特定 OEM 的信息/设置"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"4G/5G 双连接可用:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"5G 双连接受限:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"5G 可用:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"5G 状态:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"5G 频率:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"设置无线装置频道模式"</string>
<string name="band_mode_loading" msgid="795923726636735967">"正在加载频道列表…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"设置"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index d60fdcd..3920bad 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -307,7 +307,9 @@
<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_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.format failed for translation -->
+ <!-- no translation found for throttle_status_subtext (1110276415078236687) -->
+ <skip />
<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> Kb/s"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"已經過 <xliff:g id="USED_0">%1$d</xliff:g>٪ 的循環週期\n下一週期會在 <xliff:g id="USED_1">%2$d</xliff:g> 天後開始 (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -510,7 +512,7 @@
<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>
+ <string name="card_title_conf_call" msgid="901197309274457427">"視訊會議"</string>
<string name="card_title_incoming_call" msgid="881424648458792430">"來電"</string>
<string name="card_title_call_ended" msgid="650223980095026340">"通話已結束"</string>
<string name="card_title_on_hold" msgid="9028319436626975207">"保留通話"</string>
@@ -637,12 +639,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"是"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"否"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"關閉"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"手機處於緊急回撥模式"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"直至<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">手機在接下來的 <xliff:g id="COUNT_1">%s</xliff:g> 分鐘都將處於緊急回撥模式。\n您要立即退出嗎?</item>
- <item quantity="one">手機在接下來的 <xliff:g id="COUNT_0">%s</xliff:g> 分鐘都將處於緊急回撥模式。\n您要立即退出嗎?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"服務"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"設定"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<未設定>"</string>
@@ -898,11 +894,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"重新整理"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"切換 DNS 檢查"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM 專用資訊/設定"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC 可用:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR 受限:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR 可用:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR 狀態:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR 頻率:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"設定無線電頻段模式"</string>
<string name="band_mode_loading" msgid="795923726636735967">"正在載入頻段清單…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"設定"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 5d9de3c..9a7a0df 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -22,7 +22,7 @@
<string name="phoneIconLabel" msgid="3015941229249651419">"電話"</string>
<string name="fdnListLabel" msgid="4119121875004244097">"固定撥號清單"</string>
<string name="unknown" msgid="8279698889921830815">"不明"</string>
- <string name="private_num" msgid="4487990167889159992">"隱藏號碼"</string>
+ <string name="private_num" msgid="4487990167889159992">"私人號碼"</string>
<string name="payphone" msgid="7936735771836716941">"公用電話"</string>
<string name="onHold" msgid="6132725550015899006">"通話保留"</string>
<string name="carrier_mmi_msg_title" msgid="6050165242447507034">"「<xliff:g id="MMICARRIER">%s</xliff:g>」的訊息"</string>
@@ -96,7 +96,7 @@
<string name="sum_hide_caller_id" msgid="131100328602371933">"隱藏本機號碼"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"撥出電話時顯示本機號碼"</string>
<string name="sum_default_caller_id" msgid="1767070797135682959">"使用預設值,在撥出電話時顯示本機號碼"</string>
- <string name="labelCW" msgid="8449327023861428622">"來電等候"</string>
+ <string name="labelCW" msgid="8449327023861428622">"來電等待"</string>
<string name="sum_cw_enabled" msgid="3977308526187139996">"通話時如有來電請通知我"</string>
<string name="sum_cw_disabled" msgid="3658094589461768637">"通話時如有來電請通知我"</string>
<string name="call_forwarding_settings" msgid="8937130467468257671">"來電轉接設定"</string>
@@ -307,7 +307,9 @@
<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_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.format failed for translation -->
+ <!-- no translation found for throttle_status_subtext (1110276415078236687) -->
+ <skip />
<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> Kb/s"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"已經過 <xliff:g id="USED_0">%1$d</xliff:g> ٪ 的循環週期\n下一週期會在 <xliff:g id="USED_1">%2$d</xliff:g> 天內開始 (<xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -522,7 +524,7 @@
<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_ecm_emergency_only" msgid="5622379058883722080">"結束緊急回撥模式,以便撥打非緊急電話。"</string>
@@ -637,12 +639,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"是"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"否"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"關閉"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"手機目前處於緊急回撥模式"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"結束時間:<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">接下來的 <xliff:g id="COUNT_1">%s</xliff:g> 分鐘內,手機將處於緊急回撥模式。\n要立即退出這個模式嗎?</item>
- <item quantity="one">接下來的 <xliff:g id="COUNT_0">%s</xliff:g> 分鐘內,手機將處於緊急回撥模式。\n要立即退出這個模式嗎?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"服務"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"設定"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<未設定>"</string>
@@ -898,11 +894,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"重新整理"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"切換 DNS 檢查"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"原始設備製造商 (OEM) 專用資訊/設定"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"EN-DC 可供使用:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"DCNR 受限:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"NR 可供使用:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"NR 狀態:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"NR 頻率:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"設定無線電頻帶模式"</string>
<string name="band_mode_loading" msgid="795923726636735967">"正在載入頻帶清單…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"設定"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index f5f5681..8c0a4b8 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -307,7 +307,9 @@
<string name="throttle_time_frame" msgid="1813452485948918791">"Isikhathi sokusebenzisa idatha"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Inqubomgomo yokukala idatha"</string>
<string name="throttle_help" msgid="2624535757028809735">"Funda kabanzi"</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>٪) ye <xliff:g id="USED_2">%3$s</xliff:g> isikhathi esingeqiwe \nIsikhathi esilandelayo siqala ezinsukwini ezingu-<xliff:g id="USED_3">%4$d</xliff:g> (<xliff:g id="USED_4">%5$s</xliff:g>)"</string>
+ <!-- String.format failed for translation -->
+ <!-- no translation found for throttle_status_subtext (1110276415078236687) -->
+ <skip />
<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>٪) ye- <xliff:g id="USED_2">%3$s</xliff:g> isikhathi esiphezulu"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"<xliff:g id="USED_0">%1$s</xliff:g>okuphezulu kufinyelelwe\nIsilinganiso sedatha sehliselwe ku- <xliff:g id="USED_1">%2$d</xliff:g> ama-Kb"</string>
<string name="throttle_time_frame_subtext" msgid="6462089615392402127">"<xliff:g id="USED_0">%1$d</xliff:g>٪ lesikhathi esiphelile\nIsikhathi esilandelayo siqala ezinsukwini <xliff:g id="USED_1">%2$d</xliff:g> ezingu <xliff:g id="USED_2">%3$s</xliff:g>)"</string>
@@ -637,12 +639,6 @@
<string name="alert_dialog_yes" msgid="3532525979632841417">"Yebo"</string>
<string name="alert_dialog_no" msgid="1075632654085988420">"Cha"</string>
<string name="alert_dialog_dismiss" msgid="1336356286354517054">"Susa"</string>
- <string name="phone_in_ecm_call_notification_text_without_data_restriction_hint" msgid="3747860785153531225">"Ifoni ikumodi yokushaya okuphuthumayo"</string>
- <string name="phone_in_ecm_notification_complete_time_without_data_restriction_hint" msgid="3690292264812050858">"Kuze kube ngu-<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">Ifoni izoba kumodi yokushaya kwesimo esiphuthumayo amaminithi angu-<xliff:g id="COUNT_1">%s</xliff:g>.\nIngabe ufuna ukuphuma manje?</item>
- <item quantity="other">Ifoni izoba kumodi yokushaya kwesimo esiphuthumayo amaminithi angu-<xliff:g id="COUNT_1">%s</xliff:g>.\nIngabe ufuna ukuphuma manje?</item>
- </plurals>
<string name="voicemail_provider" msgid="4158806657253745294">"Isevisi"</string>
<string name="voicemail_settings" msgid="4451045613238972776">"Ukumisa"</string>
<string name="voicemail_number_not_set" msgid="8831561283386938155">"<Ayisethiwe>"</string>
@@ -898,11 +894,6 @@
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Vuselela"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Guqula ukuhlola i-DNS"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"Ulwazi oucacile kwe-OEM/Izilungiselelo"</string>
- <string name="radio_info_endc_available" msgid="4410653375290113436">"I-EN-DC Iyatholakala:"</string>
- <string name="radio_info_dcnr_restricted" msgid="2469125498066960807">"Okukhawulelwe kwe-DCNR:"</string>
- <string name="radio_info_nr_available" msgid="1321318331361249997">"I-NR Iyatholakala:"</string>
- <string name="radio_info_nr_state" msgid="1337571996788535356">"Isimo se-NR:"</string>
- <string name="radio_info_nr_frequency" msgid="1201156032796584128">"Imvamisa ye-NR:"</string>
<string name="band_mode_title" msgid="7988822920724576842">"Isetha imodi yebhendi yerediyo"</string>
<string name="band_mode_loading" msgid="795923726636735967">"Ilayisha uhlu lwebhendi…"</string>
<string name="band_mode_set" msgid="6657819412803771421">"Setha"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index 7e71068..9f8cc81 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,25 @@
<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>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6509f13..88d2f1a 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.
@@ -2068,8 +2088,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>
@@ -2164,4 +2182,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-ar/strings.xml b/sip/res/values-ar/strings.xml
index 4cc9450..e971077 100644
--- a/sip/res/values-ar/strings.xml
+++ b/sip/res/values-ar/strings.xml
@@ -34,7 +34,7 @@
<string name="sip_menu_save" msgid="4377112554203123060">"حفظ"</string>
<string name="sip_menu_discard" msgid="1883166691772895243">"إلغاء"</string>
<string name="alert_dialog_close" msgid="1734746505531110706">"إغلاق الملف الشخصي"</string>
- <string name="alert_dialog_ok" msgid="7806760618798687406">"حسنًا"</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_registering" msgid="7986331597809521791">"جارٍ التسجيل…"</string>
@@ -61,9 +61,9 @@
<string name="advanced_settings" msgid="2704644977548662872">"الإعدادات الاختيارية"</string>
<string name="auth_username_title" msgid="9002505242616662698">"اسم المستخدِم للمصادقة"</string>
<string name="auth_username_summary" msgid="6346313945275377230">"اسم المستخدِم المستخدَم للمصادقة"</string>
- <string name="default_preference_summary_username" msgid="8788114717555599222">"<لم يتم الضبط>"</string>
- <string name="default_preference_summary_password" msgid="3695366978153175549">"<لم يتم الضبط>"</string>
- <string name="default_preference_summary_domain_address" msgid="443247296785732364">"<لم يتم الضبط>"</string>
+ <string name="default_preference_summary_username" msgid="8788114717555599222">"<لم يتم التعيين>"</string>
+ <string name="default_preference_summary_password" msgid="3695366978153175549">"<لم يتم التعيين>"</string>
+ <string name="default_preference_summary_domain_address" msgid="443247296785732364">"<لم يتم التعيين>"</string>
<string name="display_name_summary" msgid="6749135030093260358">"<مثل اسم المستخدم>"</string>
<string name="optional_summary" msgid="620379377865437488">"<اختياري>"</string>
<string name="advanced_settings_show" msgid="2318728080037568529">"▷ المس لإظهار الكل"</string>
diff --git a/sip/res/values-b+sr+Latn/strings.xml b/sip/res/values-b+sr+Latn/strings.xml
index adb537b..dadfa20 100644
--- a/sip/res/values-b+sr+Latn/strings.xml
+++ b/sip/res/values-b+sr+Latn/strings.xml
@@ -22,7 +22,7 @@
<string name="sip_receive_calls" msgid="3403644006618369349">"Primaj dolazne pozive"</string>
<string name="sip_receive_calls_summary" msgid="5306603671778761443">"Više troši bateriju"</string>
<string name="sip_call_options_title" msgid="5027066677561068192">"Koristite SIP pozivanje"</string>
- <string name="sip_call_options_wifi_only_title" msgid="6663105297927456484">"Koristite SIP pozivanje (samo za WiFi)"</string>
+ <string name="sip_call_options_wifi_only_title" msgid="6663105297927456484">"Koristite SIP pozivanje (samo za Wi-Fi)"</string>
<string name="sip_call_options_entry_1" msgid="4722647332760934261">"Za sve pozive kada je mreža za prenos podataka dostupna"</string>
<string name="sip_call_options_entry_2" msgid="7338504256051655013">"Samo za SIP pozive"</string>
<string name="sip_call_options_wifi_only_entry_1" msgid="922329055414010991">"Za sve pozive"</string>
@@ -41,7 +41,7 @@
<string name="registration_status_still_trying" msgid="7178623685868766282">"I dalje pokušava..."</string>
<string name="registration_status_not_receiving" msgid="3873074208531938401">"Ne primamo pozive."</string>
<string name="registration_status_no_data" msgid="2987064560116584121">"Registracija naloga je zaustavljena zato što ne postoji internet veza."</string>
- <string name="registration_status_no_wifi_data" msgid="685470618241482948">"Registracija naloga je zaustavljena zato što ne postoji WiFi veza."</string>
+ <string name="registration_status_no_wifi_data" msgid="685470618241482948">"Registracija naloga je zaustavljena zato što ne postoji Wi-Fi veza."</string>
<string name="registration_status_not_running" msgid="6236403137652262659">"Registracija naloga nije uspela."</string>
<string name="registration_status_done" msgid="6787397199273357721">"Primamo pozive."</string>
<string name="registration_status_failed_try_later" msgid="7855389184910312091">"Registracija naloga nije uspela: <xliff:g id="REGISTRATION_ERROR_MESSAGE">%s</xliff:g>; pokušaćemo kasnije"</string>
@@ -72,7 +72,7 @@
<string name="empty_alert" msgid="3693655518612836718">"Polje <xliff:g id="INPUT_FIELD_NAME">%s</xliff:g> je obavezno i ne može da bude prazno."</string>
<string name="not_a_valid_port" msgid="3664668836663491376">"Broj porta bi trebalo da bude između 1000 i 65.534."</string>
<string name="no_internet_available" msgid="161720645084325479">"Da biste uputili SIP poziv, prvo proverite internet vezu."</string>
- <string name="no_wifi_available" msgid="1179092018692306312">"Treba da budete povezani sa WiFi mrežom da biste upućivali SIP pozive (koristite Podešavanja bežičnih veza i mreža)."</string>
+ <string name="no_wifi_available" msgid="1179092018692306312">"Treba da budete povezani sa Wi-Fi mrežom da biste upućivali SIP pozive (koristite Podešavanja bežičnih veza i mreža)."</string>
<string name="no_voip" msgid="3366395789297981738">"SIP pozivanje nije podržano"</string>
<string name="sip_system_decide" msgid="197230378376326430">"Automatski"</string>
<string name="sip_always_send_keepalive" msgid="4986533673960084769">"Uvek šalji"</string>
diff --git a/sip/res/values-bs/strings.xml b/sip/res/values-bs/strings.xml
index cc0f175..69aa5a8 100644
--- a/sip/res/values-bs/strings.xml
+++ b/sip/res/values-bs/strings.xml
@@ -72,7 +72,7 @@
<string name="empty_alert" msgid="3693655518612836718">"<xliff:g id="INPUT_FIELD_NAME">%s</xliff:g> je obavezno polje i ne može biti prazno."</string>
<string name="not_a_valid_port" msgid="3664668836663491376">"Broj priključka treba biti između 1000 i 65534."</string>
<string name="no_internet_available" msgid="161720645084325479">"Da uputite SIP poziv, prvo provjerite internet vezu."</string>
- <string name="no_wifi_available" msgid="1179092018692306312">"Trebate biti povezani s WiFi mrežom za SIP pozive (koristite postavke Bežično povezivanje i mreža)."</string>
+ <string name="no_wifi_available" msgid="1179092018692306312">"Trebate biti povezani na Wi-FI mrežu za SIP pozive (koristite postavke Bežično povezivanje i mreža)."</string>
<string name="no_voip" msgid="3366395789297981738">"SIP pozivanje nije podržano"</string>
<string name="sip_system_decide" msgid="197230378376326430">"Automatski"</string>
<string name="sip_always_send_keepalive" msgid="4986533673960084769">"Uvijek pošalji"</string>
diff --git a/sip/res/values-eu/strings.xml b/sip/res/values-eu/strings.xml
index 5ad7d9c..5c7b0ce 100644
--- a/sip/res/values-eu/strings.xml
+++ b/sip/res/values-eu/strings.xml
@@ -26,7 +26,7 @@
<string name="sip_call_options_entry_1" msgid="4722647332760934261">"Dei guztietan datu-sarea erabilgarri dagoenean"</string>
<string name="sip_call_options_entry_2" msgid="7338504256051655013">"SIP deietan soilik"</string>
<string name="sip_call_options_wifi_only_entry_1" msgid="922329055414010991">"Dei guztietan"</string>
- <string name="add_sip_account" msgid="5754758646745144384">"Gehitu kontu bat"</string>
+ <string name="add_sip_account" msgid="5754758646745144384">"Gehitu kontua"</string>
<string name="remove_sip_account" msgid="8272617403399636513">"Kendu kontua"</string>
<string name="sip_account_list" msgid="2596262496233721769">"SIP kontuak"</string>
<string name="saving_account" msgid="3390358043846687266">"Kontua gordetzen…"</string>
@@ -41,7 +41,7 @@
<string name="registration_status_still_trying" msgid="7178623685868766282">"Oraindik saiatzen…"</string>
<string name="registration_status_not_receiving" msgid="3873074208531938401">"Ez da deirik jasotzen ari."</string>
<string name="registration_status_no_data" msgid="2987064560116584121">"Kontua erregistratzeko prozesua gelditu da ez zaudelako konektatuta Internetera."</string>
- <string name="registration_status_no_wifi_data" msgid="685470618241482948">"Kontua erregistratzeko prozesua gelditu da ez dagoelako wifi-konexiorik."</string>
+ <string name="registration_status_no_wifi_data" msgid="685470618241482948">"Kontua erregistratzeko prozesua gelditu da ez dagoelako wifi bidezko konexiorik."</string>
<string name="registration_status_not_running" msgid="6236403137652262659">"Ez da kontua erregistratu."</string>
<string name="registration_status_done" msgid="6787397199273357721">"Deiak jasotzen."</string>
<string name="registration_status_failed_try_later" msgid="7855389184910312091">"Ez da kontua erregistratu: (<xliff:g id="REGISTRATION_ERROR_MESSAGE">%s</xliff:g>). Geroago saiatuko gara berriro."</string>
@@ -72,7 +72,7 @@
<string name="empty_alert" msgid="3693655518612836718">"<xliff:g id="INPUT_FIELD_NAME">%s</xliff:g> behar da eta ezin da hutsik utzi."</string>
<string name="not_a_valid_port" msgid="3664668836663491376">"Ataka-zenbakiak 1000 eta 65534 artean egon behar luke."</string>
<string name="no_internet_available" msgid="161720645084325479">"SIP deiak egiteko, egiaztatu Internetera konektatuta zaudela."</string>
- <string name="no_wifi_available" msgid="1179092018692306312">"Wifi-sare batera konektatuta egon behar zara SIP deiak egiteko (erabili hari gabekoen eta sareen ezarpenak)."</string>
+ <string name="no_wifi_available" msgid="1179092018692306312">"Wi-Fi sare batera konektatuta egon behar zara SIP deiak egiteko (erabili hari gabekoen eta sareen ezarpenak)."</string>
<string name="no_voip" msgid="3366395789297981738">"SIP deiak ez dira onartzen"</string>
<string name="sip_system_decide" msgid="197230378376326430">"Automatikoa"</string>
<string name="sip_always_send_keepalive" msgid="4986533673960084769">"Bidali beti"</string>
diff --git a/sip/res/values-fa/strings.xml b/sip/res/values-fa/strings.xml
index e2843bc..6a0f0bf 100644
--- a/sip/res/values-fa/strings.xml
+++ b/sip/res/values-fa/strings.xml
@@ -59,8 +59,8 @@
<string name="transport_title" msgid="1661659138226029178">"نوع حملونقل"</string>
<string name="send_keepalive_title" msgid="5319788151608946049">"ارسال حفظ اتصال"</string>
<string name="advanced_settings" msgid="2704644977548662872">"تنظیمات اختیاری"</string>
- <string name="auth_username_title" msgid="9002505242616662698">"نام کاربری برای اصالتسنجی"</string>
- <string name="auth_username_summary" msgid="6346313945275377230">"نام کاربری مورد استفاده برای اصالتسنجی"</string>
+ <string name="auth_username_title" msgid="9002505242616662698">"نام کاربری برای احراز هویت"</string>
+ <string name="auth_username_summary" msgid="6346313945275377230">"نام کاربری مورد استفاده برای احراز هویت"</string>
<string name="default_preference_summary_username" msgid="8788114717555599222">"<تنظیم نشده>"</string>
<string name="default_preference_summary_password" msgid="3695366978153175549">"<تنظیم نشده>"</string>
<string name="default_preference_summary_domain_address" msgid="443247296785732364">"<تنظیم نشده>"</string>
diff --git a/sip/res/values-gl/strings.xml b/sip/res/values-gl/strings.xml
index a6c15d5..1a40bf3 100644
--- a/sip/res/values-gl/strings.xml
+++ b/sip/res/values-gl/strings.xml
@@ -57,7 +57,7 @@
<string name="proxy_address_title" msgid="4120361943254795287">"Enderezo proxy saínte"</string>
<string name="port_title" msgid="1703586046264385110">"Número de porto"</string>
<string name="transport_title" msgid="1661659138226029178">"Tipo de transporte"</string>
- <string name="send_keepalive_title" msgid="5319788151608946049">"Enviar sinal de conexión permanente"</string>
+ <string name="send_keepalive_title" msgid="5319788151608946049">"Enviar sinal conexión"</string>
<string name="advanced_settings" msgid="2704644977548662872">"Configuración opcional"</string>
<string name="auth_username_title" msgid="9002505242616662698">"Nome de usuario da autenticación"</string>
<string name="auth_username_summary" msgid="6346313945275377230">"Nome de usuario utilizado para a autenticación"</string>
diff --git a/sip/res/values-gu/strings.xml b/sip/res/values-gu/strings.xml
index 9eb810b..aa32a85 100644
--- a/sip/res/values-gu/strings.xml
+++ b/sip/res/values-gu/strings.xml
@@ -18,7 +18,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="sip_settings" msgid="7452587325305604702">"SIP સેટિંગ્સ"</string>
<string name="sip_accounts" msgid="7297896885665783239">"SIP એકાઉન્ટ્સ"</string>
- <string name="sip_accounts_title" msgid="3061686404598143943">"એકાઉન્ટ"</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_call_options_title" msgid="5027066677561068192">"SIP કૉલિંગનો ઉપયોગ કરો"</string>
diff --git a/sip/res/values-in/strings.xml b/sip/res/values-in/strings.xml
index 709205c..535e5ac 100644
--- a/sip/res/values-in/strings.xml
+++ b/sip/res/values-in/strings.xml
@@ -40,7 +40,7 @@
<string name="registration_status_registering" msgid="7986331597809521791">"Mendaftarkan..."</string>
<string name="registration_status_still_trying" msgid="7178623685868766282">"Masih mencoba..."</string>
<string name="registration_status_not_receiving" msgid="3873074208531938401">"Tidak menerima panggilan."</string>
- <string name="registration_status_no_data" msgid="2987064560116584121">"Pendaftaran akun terhenti karena tidak ada koneksi internet."</string>
+ <string name="registration_status_no_data" msgid="2987064560116584121">"Pendaftaran akun terhenti karena tidak ada sambungan internet."</string>
<string name="registration_status_no_wifi_data" msgid="685470618241482948">"Pendaftaran akun terhenti karena tidak ada sambungan Wi-Fi."</string>
<string name="registration_status_not_running" msgid="6236403137652262659">"Pendaftaran akun gagal."</string>
<string name="registration_status_done" msgid="6787397199273357721">"Menerima panggilan."</string>
@@ -71,7 +71,7 @@
<string name="all_empty_alert" msgid="6085603517610199098">"Masukkan detail akun SIP baru."</string>
<string name="empty_alert" msgid="3693655518612836718">"<xliff:g id="INPUT_FIELD_NAME">%s</xliff:g> diwajibkan dan tidak boleh kosong."</string>
<string name="not_a_valid_port" msgid="3664668836663491376">"Nomor port harus dalam rentang 1000 dan 65534."</string>
- <string name="no_internet_available" msgid="161720645084325479">"Untuk melakukan panggilan SIP, periksa koneksi internet terlebih dahulu."</string>
+ <string name="no_internet_available" msgid="161720645084325479">"Untuk melakukan panggilan SIP, periksa sambungan internet terlebih dahulu."</string>
<string name="no_wifi_available" msgid="1179092018692306312">"Anda harus tersambung ke jaringan Wi-Fi untuk melakukan panggilan SIP (gunakan setelan Nirkabel & Jaringan)."</string>
<string name="no_voip" msgid="3366395789297981738">"Panggilan SIP tidak didukung"</string>
<string name="sip_system_decide" msgid="197230378376326430">"Otomatis"</string>
diff --git a/sip/res/values-ky/strings.xml b/sip/res/values-ky/strings.xml
index ab37d84..23d5561 100644
--- a/sip/res/values-ky/strings.xml
+++ b/sip/res/values-ky/strings.xml
@@ -29,8 +29,8 @@
<string name="add_sip_account" msgid="5754758646745144384">"Аккаунт кошуу"</string>
<string name="remove_sip_account" msgid="8272617403399636513">"Аккаунтту өчүрүү"</string>
<string name="sip_account_list" msgid="2596262496233721769">"SIP аккаунттары"</string>
- <string name="saving_account" msgid="3390358043846687266">"Аккаунт сакталууда…"</string>
- <string name="removing_account" msgid="1544132880414780408">"Аккаунт жок кылынууда…"</string>
+ <string name="saving_account" msgid="3390358043846687266">"Каттоо эсеби сакталууда…"</string>
+ <string name="removing_account" msgid="1544132880414780408">"Каттоо эсеби жок кылынууда…"</string>
<string name="sip_menu_save" msgid="4377112554203123060">"Сактоо"</string>
<string name="sip_menu_discard" msgid="1883166691772895243">"Жокко чыгаруу"</string>
<string name="alert_dialog_close" msgid="1734746505531110706">"Профилди жабуу"</string>
@@ -40,7 +40,7 @@
<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>
- <string name="registration_status_no_data" msgid="2987064560116584121">"Интернет жок болгондуктан, эсепти каттоо аракети токтотулду."</string>
+ <string name="registration_status_no_data" msgid="2987064560116584121">"Интернет байланышы жок болгондуктан, эсепти каттоо аракети токтотулду."</string>
<string name="registration_status_no_wifi_data" msgid="685470618241482948">"Wi-Fi байланышы жок болгондуктан, эсепти каттоо аракети токтотулду."</string>
<string name="registration_status_not_running" msgid="6236403137652262659">"Эсеп катталбай калды."</string>
<string name="registration_status_done" msgid="6787397199273357721">"Чалуулар кабыл алынат."</string>
diff --git a/sip/res/values-mk/strings.xml b/sip/res/values-mk/strings.xml
index f6b568a..b810af8 100644
--- a/sip/res/values-mk/strings.xml
+++ b/sip/res/values-mk/strings.xml
@@ -26,7 +26,7 @@
<string name="sip_call_options_entry_1" msgid="4722647332760934261">"За сите повици кога е достапна мрежа на податоци"</string>
<string name="sip_call_options_entry_2" msgid="7338504256051655013">"Само за повици со SIP"</string>
<string name="sip_call_options_wifi_only_entry_1" msgid="922329055414010991">"За сите повици"</string>
- <string name="add_sip_account" msgid="5754758646745144384">"Додајте сметка"</string>
+ <string name="add_sip_account" msgid="5754758646745144384">"Додај сметка"</string>
<string name="remove_sip_account" msgid="8272617403399636513">"Отстрани сметка"</string>
<string name="sip_account_list" msgid="2596262496233721769">"Сметки за SIP"</string>
<string name="saving_account" msgid="3390358043846687266">"Зачувување на сметката..."</string>
diff --git a/sip/res/values-mr/strings.xml b/sip/res/values-mr/strings.xml
index 60d9c88..252b3bf 100644
--- a/sip/res/values-mr/strings.xml
+++ b/sip/res/values-mr/strings.xml
@@ -45,13 +45,13 @@
<string name="registration_status_not_running" msgid="6236403137652262659">"खाते नोंदणी अयशस्वी."</string>
<string name="registration_status_done" msgid="6787397199273357721">"कॉल प्राप्त करत आहे."</string>
<string name="registration_status_failed_try_later" msgid="7855389184910312091">"खाते नोंदणी अयशस्वी: (<xliff:g id="REGISTRATION_ERROR_MESSAGE">%s</xliff:g>); नंतर प्रयत्न करू"</string>
- <string name="registration_status_invalid_credentials" msgid="8896714049938660777">"खाते नोंदणी अयशस्वी: चुकीचे वापरकर्ता नाव किंवा संकेतशब्द."</string>
+ <string name="registration_status_invalid_credentials" msgid="8896714049938660777">"खाते नोंदणी अयशस्वी: चुकीचे वापरकर्तानाव किंवा संकेतशब्द."</string>
<string name="registration_status_server_unreachable" msgid="3832339558868965604">"खाते नोंदणी अयशस्वी: सर्व्हर नाव तपासा."</string>
<string name="third_party_account_summary" msgid="5918779106950859167">"हे खाते सध्या <xliff:g id="ACCOUNT_OWNER">%s</xliff:g> अॅपद्वारे वापरात आहे."</string>
<string name="sip_edit_title" msgid="7438891546610820307">"SIP खाते तपशील"</string>
<string name="sip_edit_new_title" msgid="8394790068979636381">"SIP खाते तपशील"</string>
<string name="domain_address_title" msgid="8238078615181248579">"सर्व्हर"</string>
- <string name="username_title" msgid="298416796886107970">"वापरकर्ता नाव"</string>
+ <string name="username_title" msgid="298416796886107970">"वापरकर्तानाव"</string>
<string name="password_title" msgid="8035579335591959021">"पासवर्ड"</string>
<string name="display_name_title" msgid="3730105783656830160">"प्रदर्शन नाव"</string>
<string name="proxy_address_title" msgid="4120361943254795287">"आउटबाउंड प्रॉक्सी पत्ता"</string>
@@ -60,7 +60,7 @@
<string name="send_keepalive_title" msgid="5319788151608946049">"चैतन्यमय-ठेवा पाठवा"</string>
<string name="advanced_settings" msgid="2704644977548662872">"पर्यायी सेटिंग्ज"</string>
<string name="auth_username_title" msgid="9002505242616662698">"ऑथेंटिकेशन वापरकर्ता नाव"</string>
- <string name="auth_username_summary" msgid="6346313945275377230">"प्रमाणीकरणासाठी वापरकर्ता नाव वापरले"</string>
+ <string name="auth_username_summary" msgid="6346313945275377230">"प्रमाणीकरणासाठी वापरकर्तानाव वापरले"</string>
<string name="default_preference_summary_username" msgid="8788114717555599222">"<सेट नाही>"</string>
<string name="default_preference_summary_password" msgid="3695366978153175549">"<सेट नाही>"</string>
<string name="default_preference_summary_domain_address" msgid="443247296785732364">"<सेट नाही>"</string>
diff --git a/sip/res/values-ne/strings.xml b/sip/res/values-ne/strings.xml
index e8938aa..3b33681 100644
--- a/sip/res/values-ne/strings.xml
+++ b/sip/res/values-ne/strings.xml
@@ -31,7 +31,7 @@
<string name="sip_account_list" msgid="2596262496233721769">"SIP खाताहरू"</string>
<string name="saving_account" msgid="3390358043846687266">"खाता बचत गर्दै..."</string>
<string name="removing_account" msgid="1544132880414780408">"खाता हटाउँदै..."</string>
- <string name="sip_menu_save" msgid="4377112554203123060">"सेभ गर्नुहोस्"</string>
+ <string name="sip_menu_save" msgid="4377112554203123060">"सुरक्षित गर्नुहोस्"</string>
<string name="sip_menu_discard" msgid="1883166691772895243">"त्याग्नुहोस्"</string>
<string name="alert_dialog_close" msgid="1734746505531110706">"प्रोफाइल बन्द गर्नुहोस्"</string>
<string name="alert_dialog_ok" msgid="7806760618798687406">"ठिक छ"</string>
@@ -45,13 +45,13 @@
<string name="registration_status_not_running" msgid="6236403137652262659">"खाता दर्ता गर्न विफल"</string>
<string name="registration_status_done" msgid="6787397199273357721">"कलहरू प्राप्त गर्दै।"</string>
<string name="registration_status_failed_try_later" msgid="7855389184910312091">"खाता दर्ता गर्न विफल: (<xliff:g id="REGISTRATION_ERROR_MESSAGE">%s</xliff:g>); पछि प्रयास गर्नुहोस्"</string>
- <string name="registration_status_invalid_credentials" msgid="8896714049938660777">"खाता दर्ता प्रक्रिया असफल: गलत युजरनेम वा पासवर्ड।"</string>
+ <string name="registration_status_invalid_credentials" msgid="8896714049938660777">"खाता दर्ता प्रक्रिया असफल: गलत प्रयोगकर्ताको नाम वा पासवर्ड।"</string>
<string name="registration_status_server_unreachable" msgid="3832339558868965604">"खाता दर्ता गर्न असफल: सर्भरको नाम जाँच गर्नुहोस्।"</string>
<string name="third_party_account_summary" msgid="5918779106950859167">"<xliff:g id="ACCOUNT_OWNER">%s</xliff:g> अनुप्रयोगद्वारा यो खाता हाल प्रयोगमा छ।"</string>
<string name="sip_edit_title" msgid="7438891546610820307">"SIP खाता विवरणहरू"</string>
<string name="sip_edit_new_title" msgid="8394790068979636381">"SIP खाता विवरणहरू"</string>
<string name="domain_address_title" msgid="8238078615181248579">"सर्भर"</string>
- <string name="username_title" msgid="298416796886107970">"एक-पटके टेक्स्ट म्यासेज"</string>
+ <string name="username_title" msgid="298416796886107970">"एक-पटके पाठ सन्देश"</string>
<string name="password_title" msgid="8035579335591959021">"पासवर्ड"</string>
<string name="display_name_title" msgid="3730105783656830160">"नाम प्रदर्शन गर्नुहोस्"</string>
<string name="proxy_address_title" msgid="4120361943254795287">"बाहिरका प्रोक्सी ठेगाना"</string>
@@ -59,12 +59,12 @@
<string name="transport_title" msgid="1661659138226029178">"यातायात प्रकार"</string>
<string name="send_keepalive_title" msgid="5319788151608946049">"चालु राख्नको लागि पठाउनुहोस्"</string>
<string name="advanced_settings" msgid="2704644977548662872">"वैकल्पिक सेटिङहरू"</string>
- <string name="auth_username_title" msgid="9002505242616662698">"प्रमाणीकरणको एक-पटके टेक्स्ट म्यासेज"</string>
- <string name="auth_username_summary" msgid="6346313945275377230">"प्रमाणीकरणको लागि एक-पटके टेक्स्ट म्यासेज प्रयोग भएको"</string>
+ <string name="auth_username_title" msgid="9002505242616662698">"प्रमाणीकरणको एक-पटके पाठ सन्देश"</string>
+ <string name="auth_username_summary" msgid="6346313945275377230">"प्रमाणीकरणको लागि एक-पटके पाठ सन्देश प्रयोग भएको"</string>
<string name="default_preference_summary_username" msgid="8788114717555599222">"<सेट गरिएको छैन>"</string>
<string name="default_preference_summary_password" msgid="3695366978153175549">"<सेट गरिएको छैन>"</string>
<string name="default_preference_summary_domain_address" msgid="443247296785732364">"<सेट गरिएको छैन>"</string>
- <string name="display_name_summary" msgid="6749135030093260358">"<एक-पटके टेक्स्ट म्यासेज जस्तै>"</string>
+ <string name="display_name_summary" msgid="6749135030093260358">"<एक-पटके पाठ सन्देश जस्तै>"</string>
<string name="optional_summary" msgid="620379377865437488">"<वैकल्पिकgt;"</string>
<string name="advanced_settings_show" msgid="2318728080037568529">"▷ छोएर सबै देखाउनुहोस्"</string>
<string name="advanced_settings_hide" msgid="6200816937370652083">"▽ छोएर सबै लुकाउनुहोस्"</string>
diff --git a/sip/res/values-pt-rPT/strings.xml b/sip/res/values-pt-rPT/strings.xml
index 35aca3e..ae1b2ed 100644
--- a/sip/res/values-pt-rPT/strings.xml
+++ b/sip/res/values-pt-rPT/strings.xml
@@ -47,7 +47,7 @@
<string name="registration_status_failed_try_later" msgid="7855389184910312091">"O registo da conta não teve êxito: (<xliff:g id="REGISTRATION_ERROR_MESSAGE">%s</xliff:g>); tentaremos mais tarde"</string>
<string name="registration_status_invalid_credentials" msgid="8896714049938660777">"O registo da conta não teve êxito: palavra-passe ou nome de utilizador incorreto"</string>
<string name="registration_status_server_unreachable" msgid="3832339558868965604">"O registo da conta não teve êxito: verifique o nome do servidor."</string>
- <string name="third_party_account_summary" msgid="5918779106950859167">"Esta conta está atualmente a ser utilizada pela app <xliff:g id="ACCOUNT_OWNER">%s</xliff:g>."</string>
+ <string name="third_party_account_summary" msgid="5918779106950859167">"Esta conta está atualmente a ser utilizada pela aplicação <xliff:g id="ACCOUNT_OWNER">%s</xliff:g>."</string>
<string name="sip_edit_title" msgid="7438891546610820307">"Detalhes da conta SIP"</string>
<string name="sip_edit_new_title" msgid="8394790068979636381">"Detalhes da conta SIP"</string>
<string name="domain_address_title" msgid="8238078615181248579">"Servidor"</string>
diff --git a/sip/res/values-sq/strings.xml b/sip/res/values-sq/strings.xml
index e2e2ff1..1b83380 100644
--- a/sip/res/values-sq/strings.xml
+++ b/sip/res/values-sq/strings.xml
@@ -32,7 +32,7 @@
<string name="saving_account" msgid="3390358043846687266">"Po ruan llogarinë…"</string>
<string name="removing_account" msgid="1544132880414780408">"Po heq llogarinë…"</string>
<string name="sip_menu_save" msgid="4377112554203123060">"Ruaj"</string>
- <string name="sip_menu_discard" msgid="1883166691772895243">"Hiq"</string>
+ <string name="sip_menu_discard" msgid="1883166691772895243">"Injoro"</string>
<string name="alert_dialog_close" msgid="1734746505531110706">"Mbyll profilin"</string>
<string name="alert_dialog_ok" msgid="7806760618798687406">"Në rregull"</string>
<string name="close_profile" msgid="3756064641769751774">"Mbyll"</string>
@@ -54,7 +54,7 @@
<string name="username_title" msgid="298416796886107970">"Emri i përdoruesit"</string>
<string name="password_title" msgid="8035579335591959021">"Fjalëkalimi"</string>
<string name="display_name_title" msgid="3730105783656830160">"Shfaq emrin"</string>
- <string name="proxy_address_title" msgid="4120361943254795287">"Adresa e proxy-it dalës"</string>
+ <string name="proxy_address_title" msgid="4120361943254795287">"Adresa e përfaqësuesit dalës"</string>
<string name="port_title" msgid="1703586046264385110">"Numri i portës"</string>
<string name="transport_title" msgid="1661659138226029178">"Lloji i transportit"</string>
<string name="send_keepalive_title" msgid="5319788151608946049">"Kontrolli i veprimtarisë"</string>
diff --git a/sip/res/values-sr/strings.xml b/sip/res/values-sr/strings.xml
index 4c15528..f877e1b 100644
--- a/sip/res/values-sr/strings.xml
+++ b/sip/res/values-sr/strings.xml
@@ -22,7 +22,7 @@
<string name="sip_receive_calls" msgid="3403644006618369349">"Примај долазне позиве"</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 позивање (само за WiFi)"</string>
+ <string name="sip_call_options_wifi_only_title" msgid="6663105297927456484">"Користите SIP позивање (само за Wi-Fi)"</string>
<string name="sip_call_options_entry_1" msgid="4722647332760934261">"За све позиве када је мрежа за пренос података доступна"</string>
<string name="sip_call_options_entry_2" msgid="7338504256051655013">"Само за SIP позиве"</string>
<string name="sip_call_options_wifi_only_entry_1" msgid="922329055414010991">"За све позиве"</string>
@@ -41,7 +41,7 @@
<string name="registration_status_still_trying" msgid="7178623685868766282">"И даље покушава..."</string>
<string name="registration_status_not_receiving" msgid="3873074208531938401">"Не примамо позиве."</string>
<string name="registration_status_no_data" msgid="2987064560116584121">"Регистрација налога је заустављена зато што не постоји интернет веза."</string>
- <string name="registration_status_no_wifi_data" msgid="685470618241482948">"Регистрација налога је заустављена зато што не постоји WiFi веза."</string>
+ <string name="registration_status_no_wifi_data" msgid="685470618241482948">"Регистрација налога је заустављена зато што не постоји Wi-Fi веза."</string>
<string name="registration_status_not_running" msgid="6236403137652262659">"Регистрација налога није успела."</string>
<string name="registration_status_done" msgid="6787397199273357721">"Примамо позиве."</string>
<string name="registration_status_failed_try_later" msgid="7855389184910312091">"Регистрација налога није успела: <xliff:g id="REGISTRATION_ERROR_MESSAGE">%s</xliff:g>; покушаћемо касније"</string>
@@ -72,7 +72,7 @@
<string name="empty_alert" msgid="3693655518612836718">"Поље <xliff:g id="INPUT_FIELD_NAME">%s</xliff:g> је обавезно и не може да буде празно."</string>
<string name="not_a_valid_port" msgid="3664668836663491376">"Број порта би требало да буде између 1000 и 65.534."</string>
<string name="no_internet_available" msgid="161720645084325479">"Да бисте упутили SIP позив, прво проверите интернет везу."</string>
- <string name="no_wifi_available" msgid="1179092018692306312">"Треба да будете повезани са WiFi мрежом да бисте упућивали SIP позиве (користите Подешавања бежичних веза и мрежа)."</string>
+ <string name="no_wifi_available" msgid="1179092018692306312">"Треба да будете повезани са Wi-Fi мрежом да бисте упућивали SIP позиве (користите Подешавања бежичних веза и мрежа)."</string>
<string name="no_voip" msgid="3366395789297981738">"SIP позивање није подржано"</string>
<string name="sip_system_decide" msgid="197230378376326430">"Аутоматски"</string>
<string name="sip_always_send_keepalive" msgid="4986533673960084769">"Увек шаљи"</string>
diff --git a/sip/res/values-ta/strings.xml b/sip/res/values-ta/strings.xml
index 657d296..0cb2b75 100644
--- a/sip/res/values-ta/strings.xml
+++ b/sip/res/values-ta/strings.xml
@@ -41,7 +41,7 @@
<string name="registration_status_still_trying" msgid="7178623685868766282">"இன்னும் முயற்சிக்கிறது…"</string>
<string name="registration_status_not_receiving" msgid="3873074208531938401">"அழைப்புகளைப் பெறவில்லை."</string>
<string name="registration_status_no_data" msgid="2987064560116584121">"இணைய இணைப்பு இல்லாததால், கணக்குப் பதிவு நிறுத்தப்பட்டது."</string>
- <string name="registration_status_no_wifi_data" msgid="685470618241482948">"வைஃபை இணைப்பு இல்லாததால், கணக்குப் பதிவு நிறுத்தப்பட்டது."</string>
+ <string name="registration_status_no_wifi_data" msgid="685470618241482948">"Wi-Fi இணைப்பு இல்லாததால், கணக்குப் பதிவு நிறுத்தப்பட்டது."</string>
<string name="registration_status_not_running" msgid="6236403137652262659">"கணக்குப் பதிவு தோல்வி."</string>
<string name="registration_status_done" msgid="6787397199273357721">"அழைப்புகளைப் பெறுதல்."</string>
<string name="registration_status_failed_try_later" msgid="7855389184910312091">"கணக்குப் பதிவு தோல்வி: (<xliff:g id="REGISTRATION_ERROR_MESSAGE">%s</xliff:g>); பிறகு முயலவும்"</string>
diff --git a/sip/res/values-uz/strings.xml b/sip/res/values-uz/strings.xml
index f951de2..e6d00e7 100644
--- a/sip/res/values-uz/strings.xml
+++ b/sip/res/values-uz/strings.xml
@@ -26,7 +26,7 @@
<string name="sip_call_options_entry_1" msgid="4722647332760934261">"Tarmoq trafigi mavjud bo‘lganda barcha qo‘ng‘iroqlar uchun"</string>
<string name="sip_call_options_entry_2" msgid="7338504256051655013">"Faqat SIP chaqiruvlar uchun"</string>
<string name="sip_call_options_wifi_only_entry_1" msgid="922329055414010991">"Barcha chaqiruvlar uchun"</string>
- <string name="add_sip_account" msgid="5754758646745144384">"Hisob kiritish"</string>
+ <string name="add_sip_account" msgid="5754758646745144384">"Hisob qo‘shish"</string>
<string name="remove_sip_account" msgid="8272617403399636513">"Hisobni olib tashlash"</string>
<string name="sip_account_list" msgid="2596262496233721769">"SIP hisoblari"</string>
<string name="saving_account" msgid="3390358043846687266">"Hisob saqlanmoqda…"</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..2845dac 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,54 @@
* 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, 0);
+
+ 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/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index 54acaad..ae6f072 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,11 +284,16 @@
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) {
if (DBG) log("PhoneStateListener onCallStateChanged: state is " + state);
@@ -397,14 +407,10 @@
} else {
if (phoneType == PhoneConstants.PHONE_TYPE_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);
- }
+ addPreferencesFromResource(R.xml.cdma_call_privacy);
+ CdmaVoicePrivacySwitchPreference buttonVoicePrivacy =
+ (CdmaVoicePrivacySwitchPreference) findPreference(BUTTON_VP_KEY);
+ buttonVoicePrivacy.setPhone(mPhone);
} else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
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..1a867b6 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;
}
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..d5b697a 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: "
@@ -464,15 +490,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 +510,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 +673,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);
@@ -678,7 +710,7 @@
TelephonyFrameworkInitializer
.getTelephonyServiceManager().getCarrierConfigServiceRegisterer().register(this);
logd("CarrierConfigLoader has started");
- mSubscriptionInfoUpdater = PhoneFactory.getSubscriptionInfoUpdater();
+ mSubscriptionInfoUpdater = subscriptionInfoUpdater;
mHandler.sendEmptyMessage(EVENT_CHECK_SYSTEM_UPDATE);
}
@@ -687,11 +719,11 @@
*
* 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());
} else {
Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance);
}
@@ -699,7 +731,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 +854,8 @@
}
}
- private CarrierIdentifier getCarrierIdentifierForPhoneId(int phoneId) {
+ @VisibleForTesting
+ /* package */ CarrierIdentifier getCarrierIdentifierForPhoneId(int phoneId) {
String mcc = "";
String mnc = "";
String imsi = "";
@@ -848,6 +882,7 @@
}
/** Returns the package name of a priveleged carrier app, or null if there is none. */
+ @Nullable
private String getCarrierPackageForPhoneId(int phoneId) {
List<String> carrierPackageNames = TelephonyManager.from(mContext)
.getCarrierPackageNamesForIntentAndPhone(
@@ -976,12 +1011,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 +1167,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 +1179,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 +1236,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 +1276,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 +1304,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 +1330,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 +1372,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 +1654,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..a8d2e93
--- /dev/null
+++ b/src/com/android/phone/CdmaCallForwardOptions.java
@@ -0,0 +1,254 @@
+/*
+ * 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.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+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 {
+ 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..2e310aa 100644
--- a/src/com/android/phone/CdmaCallOptions.java
+++ b/src/com/android/phone/CdmaCallOptions.java
@@ -19,18 +19,19 @@
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.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 static final String CALL_FORWARDING_KEY = "call_forwarding_key";
+ private static final String CALL_WAITING_KEY = "call_waiting_key";
private CdmaVoicePrivacySwitchPreference mButtonVoicePrivacy;
@Override
@@ -55,8 +56,15 @@
if (subInfoHelper.getPhone().getPhoneType() != PhoneConstants.PHONE_TYPE_CDMA
|| carrierConfig.getBoolean(CarrierConfigManager.KEY_VOICE_PRIVACY_DISABLE_UI_BOOL)) {
// disable the entire screen
- getPreferenceScreen().setEnabled(false);
+ mButtonVoicePrivacy.setEnabled(false);
}
+
+ Preference callForwardingPref = getPreferenceScreen().findPreference(CALL_FORWARDING_KEY);
+ callForwardingPref.setIntent(subInfoHelper.getIntent(CdmaCallForwardOptions.class));
+
+ CdmaCallWaitingPreference callWaitingPref = (CdmaCallWaitingPreference)getPreferenceScreen()
+ .findPreference(CALL_WAITING_KEY);
+ callWaitingPref.init(this, subInfoHelper.getPhone());
}
@Override
@@ -76,5 +84,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..4cda7ba
--- /dev/null
+++ b/src/com/android/phone/CdmaCallWaitingPreference.java
@@ -0,0 +1,194 @@
+/*
+ * 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 com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Phone;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.util.AttributeSet;
+import android.util.Log;
+
+public class CdmaCallWaitingPreference extends Preference {
+ private static final String LOG_TAG = "CdmaCallWaitingPreference";
+ private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+ private int mButtonClicked;
+ private Context mContext;
+ private Phone mPhone;
+ private SubscriptionInfoHelper mSubscriptionInfoHelper;
+ private TimeConsumingPreferenceListener mTcpListener;
+ private MyHandler mHandler = new MyHandler();
+
+ 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);
+ }
+ }
+
+ @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();
+ }
+
+ 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/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java
index dcae24b..bd6ba6b 100644
--- a/src/com/android/phone/ImsRcsController.java
+++ b/src/com/android/phone/ImsRcsController.java
@@ -16,32 +16,48 @@
package com.android.phone;
+import android.Manifest;
+import android.app.ActivityManager;
+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.ims.ImsResolver;
import com.android.internal.telephony.imsphone.ImsPhone;
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 +70,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 +95,7 @@
mApp = app;
TelephonyFrameworkInitializer
.getTelephonyServiceManager().getTelephonyImsServiceRegisterer().register(this);
+ mImsResolver = mApp.getImsResolver();
}
/**
@@ -84,7 +104,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 +122,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 +139,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 +161,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 -> {
@@ -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,177 @@
@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");
+ if (!isCallingProcessInForeground(Binder.getCallingUid())) {
+ throw new SecurityException("The caller is not in the foreground.");
}
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");
+ if (!isCallingProcessInForeground(Binder.getCallingUid())) {
+ throw new SecurityException("The caller is not in the foreground.");
+ }
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.
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ return null;
+ }
+ return uceCtrlManager.addUceRegistrationOverride(featureTags);
+ }
+
+ /**
+ * 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.
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ return null;
+ }
+ return uceCtrlManager.removeUceRegistrationOverride(featureTags);
+ }
+
+ /**
+ * 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 {
+ // Permission check happening in PhoneInterfaceManager.
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ return null;
+ }
+ return uceCtrlManager.clearUceRegistrationOverride();
+ }
+
+ /**
+ * @return current RcsContactUceCapability instance that will be used for PUBLISH.
+ */
+ // Used for SHELL command only right now.
+ public RcsContactUceCapability getLatestRcsContactUceCapabilityShell(int subId)
+ throws ImsException {
+ // Permission check happening in PhoneInterfaceManager.
+ UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
+ UceControllerManager.class);
+ if (uceCtrlManager == null) {
+ return null;
+ }
+ return uceCtrlManager.getLatestRcsContactUceCapability();
+ }
+
+ /**
+ * @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 {
+ // 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;
+ }
+
+ @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);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -313,6 +479,146 @@
}
}
+ @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) {
+ enforceImsSingleRegistrationPermission("destroySipDelegate");
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ SipTransportController transport = getRcsFeatureController(subId).getFeature(
+ SipTransportController.class);
+ if (transport == null) {
+ return;
+ }
+ transport.destroySipDelegate(subId, connection, reason);
+ } 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);
+ } 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 +630,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,6 +648,39 @@
}
/**
+ * Make sure the caller has the ACCESS_RCS_USER_CAPABILITY_EXCHANGE permission.
+ *
+ * @throws SecurityException if the caller does not have the required permission.
+ */
+ private void enforceAccessUserCapabilityExchangePermission(String message) {
+ mApp.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, message);
+ }
+
+ /**
+ * Make sure the caller has the READ_CONTACTS permission.
+ *
+ * @throws SecurityException if the caller does not have the required permission.
+ */
+ private void enforceReadContactsPermission(String message) {
+ mApp.enforceCallingOrSelfPermission(
+ android.Manifest.permission.READ_CONTACTS, message);
+ }
+
+ /**
+ * Check if the calling process is in the foreground.
+ *
+ * @return true if the caller is in the foreground.
+ */
+ private boolean isCallingProcessInForeground(int uid) {
+ ActivityManager am = mApp.getSystemService(ActivityManager.class);
+ boolean isCallingProcessForeground = am != null
+ && am.getUidImportance(uid)
+ == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+ return isCallingProcessForeground;
+ }
+
+ /**
* Retrieve ImsPhone instance.
*
* @param subId the subscription ID
@@ -387,7 +735,21 @@
return c;
}
+ 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..0825cfb 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.getRoamingState();
+ } catch (NullPointerException | ClassCastException e) {
+ return false;
+ }
+ }
}
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index 1be7273..4fb61f0 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);
}
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 0971754..e341427 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -55,6 +55,7 @@
import android.util.Log;
import android.widget.Toast;
+import com.android.ims.ImsFeatureBinderRepository;
import com.android.internal.telephony.CallManager;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.MmiCode;
@@ -68,6 +69,8 @@
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.util.IndentingPrintWriter;
import com.android.phone.settings.SettingsConstants;
import com.android.phone.vvm.CarrierVvmPackageInstalledReceiver;
@@ -267,7 +270,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 +286,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;
@@ -354,8 +359,28 @@
String defaultImsRcsPackage = getResources().getString(
R.string.config_ims_rcs_package);
mImsResolver = new ImsResolver(this, defaultImsMmtelPackage,
- defaultImsRcsPackage, PhoneFactory.getPhones().length);
+ defaultImsRcsPackage, PhoneFactory.getPhones().length,
+ new ImsFeatureBinderRepository());
mImsResolver.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.
@@ -915,6 +940,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
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index d740c8f..34f5d61 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -23,6 +23,7 @@
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;
@@ -33,7 +34,6 @@
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 +54,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 +67,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 +78,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,28 +92,34 @@
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.UiccCardInfo;
import android.telephony.UiccSlotInfo;
import android.telephony.UssdResponse;
import android.telephony.VisualVoicemailSmsFilterSettings;
import android.telephony.data.ApnSetting;
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;
@@ -123,6 +133,7 @@
import com.android.ims.ImsManager;
import com.android.ims.internal.IImsServiceFeatureCallback;
+import com.android.ims.rcs.uce.eab.EabUtil;
import com.android.internal.telephony.CallForwardInfo;
import com.android.internal.telephony.CallManager;
import com.android.internal.telephony.CallStateException;
@@ -132,9 +143,11 @@
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;
@@ -150,6 +163,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;
@@ -175,12 +189,15 @@
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.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.FileDescriptor;
@@ -193,7 +210,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;
@@ -222,10 +241,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;
@@ -292,6 +309,25 @@
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;
// Parameters of select command.
private static final int SELECT_COMMAND = 0xA4;
@@ -301,6 +337,7 @@
/** The singleton instance. */
private static PhoneInterfaceManager sInstance;
+ private static List<String> sThermalMitigationAllowlistedPackages = new ArrayList<>();
private PhoneGlobals mApp;
private CallManager mCM;
@@ -311,11 +348,14 @@
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_";
@@ -332,12 +372,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.
*/
@@ -752,40 +797,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:
@@ -830,38 +971,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");
@@ -869,56 +1015,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;
@@ -926,12 +1096,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) {
@@ -949,29 +1119,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;
@@ -1249,11 +1433,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;
@@ -1303,19 +1503,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();
}
@@ -1324,6 +1525,7 @@
request.notifyAll();
}
break;
+ }
case CMD_MODEM_REBOOT:
request = (MainThreadRequest) msg.obj;
onCompleted = obtainMessage(EVENT_RESET_MODEM_CONFIG_DONE, request);
@@ -1340,7 +1542,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);
@@ -1355,6 +1557,7 @@
}
notifyRequester(request);
break;
+ }
case CMD_GET_MODEM_STATUS:
request = (MainThreadRequest) msg.obj;
onCompleted = obtainMessage(EVENT_GET_MODEM_STATUS_DONE, request);
@@ -1399,6 +1602,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 IllegalArgumentException(
+ "Failed to retrieve system selection 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;
@@ -1464,27 +1696,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;
@@ -1495,6 +1744,165 @@
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_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;
@@ -1530,8 +1938,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*/);
}
/**
@@ -1541,7 +1949,7 @@
*/
private Object sendRequest(int command, Object argument, WorkSource workSource) {
return sendRequest(command, argument, SubscriptionManager.INVALID_SUBSCRIPTION_ID,
- null, workSource);
+ null, workSource, -1 /*timeoutInMs*/);
}
/**
@@ -1550,7 +1958,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);
}
/**
@@ -1559,7 +1978,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*/);
}
/**
@@ -1568,17 +1987,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.");
}
@@ -1595,16 +2015,36 @@
Message msg = mMainThreadHandler.obtainMessage(command, request);
msg.sendToTarget();
- // Wait for the request to complete
+
synchronized (request) {
- while (request.result == null) {
- try {
- request.wait();
- } catch (InterruptedException e) {
- // Do nothing, go back and wait until the request is complete
+ if (timeoutInMs >= 0) {
+ // Wait for at least timeoutInMs before returning null request result
+ long now = SystemClock.elapsedRealtime();
+ long deadline = now + timeoutInMs;
+ while (request == null && now < deadline) {
+ try {
+ request.wait(deadline - now);
+ } catch (InterruptedException e) {
+ // Do nothing, go back and check if request is completed or timeout
+ } finally {
+ now = SystemClock.elapsedRealtime();
+ }
+ }
+ } else {
+ // Wait for the request to complete
+ while (request.result == null) {
+ try {
+ request.wait();
+ } catch (InterruptedException e) {
+ // Do nothing, go back and wait until the request is complete
+ }
}
}
}
+ if (request.result == null) {
+ Log.wtf(LOG_TAG,
+ "sendRequest: Blocking command timed out. Something has gone terribly wrong.");
+ }
return request.result;
}
@@ -1665,6 +2105,7 @@
PreferenceManager.getDefaultSharedPreferences(mApp);
mNetworkScanRequestTracker = new NetworkScanRequestTracker();
mPhoneConfigurationManager = PhoneConfigurationManager.getInstance();
+ mRadioInterfaceCapabilities = RadioInterfaceCapabilityController.getInstance();
mNotifyUserActivity = new AtomicBoolean(false);
publish();
@@ -1823,7 +2264,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 {
@@ -1836,7 +2278,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 {
@@ -1851,6 +2294,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;
@@ -1862,7 +2306,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;
}
@@ -1884,6 +2329,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;
}
@@ -1939,24 +2389,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);
@@ -2138,7 +2625,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;
@@ -2158,7 +2646,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;
@@ -2324,80 +2813,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.");
@@ -2456,7 +2908,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();
}
@@ -2509,13 +2961,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());
}
@@ -2712,7 +3166,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");
}
@@ -2736,6 +3190,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;
@@ -2925,7 +3383,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) {
@@ -3221,7 +3681,7 @@
@Override
public int getNetworkSelectionMode(int subId) {
TelephonyPermissions
- .enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ .enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getNetworkSelectionMode");
final long identity = Binder.clearCallingIdentity();
try {
@@ -3258,7 +3718,7 @@
@Override
public void registerImsRegistrationCallback(int subId, IImsRegistrationCallback c)
throws RemoteException {
- TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "registerImsRegistrationCallback");
if (!ImsManager.isImsSupportedOnDevice(mApp)) {
@@ -3284,7 +3744,7 @@
*/
@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);
@@ -3341,7 +3801,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,
@@ -3381,7 +3841,7 @@
@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,
@@ -3406,7 +3866,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);
@@ -3506,7 +3966,7 @@
*/
@Override
public boolean isAdvancedCallingSettingEnabled(int subId) {
- TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isAdvancedCallingSettingEnabled");
// TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
@@ -3543,7 +4003,7 @@
*/
@Override
public boolean isVtSettingEnabled(int subId) {
- TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isVtSettingEnabled");
final long identity = Binder.clearCallingIdentity();
try {
@@ -3577,7 +4037,7 @@
*/
@Override
public boolean isVoWiFiSettingEnabled(int subId) {
- TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isVoWiFiSettingEnabled");
final long identity = Binder.clearCallingIdentity();
try {
@@ -3612,7 +4072,7 @@
*/
@Override
public boolean isVoWiFiRoamingSettingEnabled(int subId) {
- TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isVoWiFiRoamingSettingEnabled");
final long identity = Binder.clearCallingIdentity();
try {
@@ -3664,7 +4124,7 @@
*/
@Override
public int getVoWiFiModeSetting(int subId) {
- TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getVoWiFiModeSetting");
final long identity = Binder.clearCallingIdentity();
try {
@@ -3746,7 +4206,7 @@
*/
@Override
public boolean isTtyOverVolteEnabled(int subId) {
- TelephonyPermissions.enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isTtyOverVolteEnabled");
final long identity = Binder.clearCallingIdentity();
try {
@@ -4240,7 +4700,7 @@
@Override
public int getNetworkTypeForSubscriber(int subId, String callingPackage,
String callingFeatureId) {
- final int targetSdk = getTargetSdk(callingPackage);
+ 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
@@ -4268,8 +4728,8 @@
*/
@Override
public int getDataNetworkType(String callingPackage, String callingFeatureId) {
- return getDataNetworkTypeForSubscriber(getDefaultSubscription(), callingPackage,
- callingFeatureId);
+ return getDataNetworkTypeForSubscriber(mSubscriptionController.getDefaultDataSubId(),
+ callingPackage, callingFeatureId);
}
/**
@@ -4379,7 +4839,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);
@@ -4986,58 +5447,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);
}
@@ -5118,6 +5556,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.
@@ -5130,7 +5598,7 @@
@ImsFeature.FeatureType int featureType) {
int[] subIds = SubscriptionManager.getSubId(slotId);
TelephonyPermissions
- .enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+ .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, (subIds != null ? subIds[0] : SubscriptionManager.INVALID_SUBSCRIPTION_ID),
"getBoundImsServicePackage");
@@ -5182,12 +5650,20 @@
}
}
- 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);
}
@@ -5208,13 +5684,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.
@@ -5258,7 +5735,7 @@
@Override
public String getManualNetworkSelectionPlmn(int subId) {
TelephonyPermissions
- .enforeceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ .enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getManualNetworkSelectionPlmn");
final long identity = Binder.clearCallingIdentity();
@@ -5318,7 +5795,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 {
@@ -5326,8 +5804,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);
}
@@ -5338,7 +5847,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 {
@@ -5346,38 +5856,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);
}
@@ -5477,123 +6079,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.
@@ -5603,9 +6111,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);
@@ -5615,21 +6122,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);
}
@@ -5646,16 +6193,32 @@
*/
@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)) {
+ Rlog.e(LOG_TAG, "Invalid allowed network type reason: " + reason);
+ 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);
}
@@ -5685,33 +6248,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.
@@ -5801,7 +6337,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 {
@@ -5821,6 +6368,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) {
@@ -5839,7 +6433,13 @@
final long identity = Binder.clearCallingIdentity();
try {
- SubscriptionInfo subInfo = subController.getSubscriptionInfo(phone.getSubId());
+ 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);
SubscriptionManager subManager = (SubscriptionManager)
phone.getContext().getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
for (String pkg : packages) {
@@ -5862,7 +6462,13 @@
final long identity = Binder.clearCallingIdentity();
try {
- SubscriptionInfo subInfo = subController.getSubscriptionInfo(phone.getSubId());
+ 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);
SubscriptionManager subManager = (SubscriptionManager)
phone.getContext().getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
return subManager.canManageSubscription(subInfo, pkgName)
@@ -6014,18 +6620,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,
@@ -6322,20 +6969,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");
@@ -6348,7 +6986,7 @@
final long identity = Binder.clearCallingIdentity();
try {
TelephonyPermissions
- .enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+ .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, phone.getSubId(), "getRadioAccessFamily");
raf = ProxyController.getInstance().getRadioAccessFamily(phoneId);
} finally {
@@ -6568,6 +7206,8 @@
@Override
public @Nullable PhoneAccountHandle getPhoneAccountHandleForSubscriptionId(int subscriptionId) {
+ enforceReadPrivilegedPermission("getPhoneAccountHandleForSubscriptionId, "
+ + "subscriptionId: " + subscriptionId);
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subscriptionId);
@@ -6644,9 +7284,17 @@
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);
+ if (phone != null) {
+ SubscriptionManager.setSubscriptionProperty(subId,
+ SubscriptionManager.ALLOWED_NETWORK_TYPES,
+ "user=" + RadioAccessFamily.getRafFromNetworkType(
+ RILConstants.PREFERRED_NETWORK_MODE));
+ phone.loadAllowedNetworksFromSubscriptionDatabase();
+ }
setDataRoamingEnabled(subId, getDefaultDataRoamingEnabled(subId));
CarrierInfoManager.deleteAllCarrierKeysForImsiEncryption(mApp);
}
@@ -7171,31 +7819,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.
@@ -7296,20 +7919,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);
@@ -7367,7 +8006,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);
@@ -7472,17 +8141,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);
}
@@ -7519,7 +8190,7 @@
@Override
public boolean isManualNetworkSelectionAllowed(int subId) {
TelephonyPermissions
- .enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+ .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isManualNetworkSelectionAllowed");
boolean isAllowed = true;
@@ -7758,6 +8429,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);
}
@@ -7800,7 +8476,7 @@
@Override
public int getCdmaRoamingMode(int subId) {
TelephonyPermissions
- .enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+ .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getCdmaRoamingMode");
final long identity = Binder.clearCallingIdentity();
@@ -7825,6 +8501,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");
@@ -7867,18 +8557,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;
@@ -8284,7 +8972,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.
*
@@ -8358,6 +9046,24 @@
}
@Override
+ public List<RadioAccessSpecifier> getSystemSelectionChannels(int subId) {
+ TelephonyPermissions
+ .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+ mApp, subId, "getSystemSelectionChannels");
+ WorkSource workSource = getWorkSource(Binder.getCallingUid());
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ List<RadioAccessSpecifier> specifiers =
+ (List<RadioAccessSpecifier>) sendRequest(CMD_GET_SYSTEM_SELECTION_CHANNELS,
+ null, subId, workSource);
+ 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(
@@ -8396,6 +9102,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 {
@@ -8408,6 +9119,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 {
@@ -8416,55 +9132,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 setMobileDataPolicyEnabledStatus(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.
*/
@@ -8503,15 +9218,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);
}
}
@@ -8641,4 +9360,900 @@
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(
+ @NonNull @TelephonyManager.RadioInterfaceCapability String capability) {
+ Set<String> radioInterfaceCapabilities =
+ mRadioInterfaceCapabilities.getCapabilities();
+ if (radioInterfaceCapabilities == null) {
+ throw new RuntimeException("radio interface capabilities are not available");
+ } else {
+ 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 thermal reason. This does not guarantee that the
+ * requested radio power state will actually be set. See {@link
+ * PhoneInternalInterface#setRadioPowerForReason} for more details.
+ *
+ * @param subId the subscription ID of the phone requesting to set the radio power state.
+ * @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(int subId, boolean enable) {
+ Phone phone = getPhone(subId);
+ if (phone != null) {
+ phone.setRadioPowerForReason(enable, Phone.RADIO_POWER_REASON_THERMAL);
+ return true;
+ }
+ return false;
+ }
+
+ 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(subId, 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) {
+ throw new IllegalArgumentException("modem does not support data throttling");
+ }
+ 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;
+ }
+
+ /**
+ * 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(subId, 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) {
+ TelephonyConnectionService service =
+ registry.getTelephonyConnectionService();
+ Phone phone = getPhone(subId);
+ if (phone == null) {
+ thermalMitigationResult =
+ TelephonyManager.THERMAL_MITIGATION_RESULT_MODEM_NOT_AVAILABLE;
+ break;
+ }
+
+ if (PhoneConstantConversions.convertCallState(phone.getState())
+ != TelephonyManager.CALL_STATE_IDLE
+ || phone.isInEmergencySmsMode() || phone.isInEcm()
+ || (service != null && service.isEmergencyCallPending())) {
+ String errorMessage = "Phone state is not valid. call state = "
+ + PhoneConstantConversions.convertCallState(phone.getState())
+ + " isInEmergencySmsMode = " + phone.isInEmergencySmsMode()
+ + " isInEmergencyCallbackMode = " + phone.isInEcm();
+ errorMessage += service == null
+ ? " TelephonyConnectionService is null"
+ : " isEmergencyCallPending = "
+ + service.isEmergencyCallPending();
+ Log.e(LOG_TAG, errorMessage);
+ 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(subId, 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) {
+ return rpm.isRcsVolteSingleRegistrationEnabled(subId);
+ }
+ return false;
+ } 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_UNSUPPORTED_OPERATION,
+ "Service not available for the subscription.");
+ }
+ } 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");
+ } else {
+ configBinder.setRcsClientConfiguration(rcc);
+ }
+ } catch (RemoteException e) {
+ Rlog.e(LOG_TAG, "fail to setRcsClientConfiguration " + e.getMessage());
+ } 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(),
+ "setCarrierSingleRegistrationEnabledOverride");
+ 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);
+ }
+ }
+
+
+ /**
+ * 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);
+ }
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+
+ @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);
+ }
+ }
+ }
+
+ /**
+ * Prepare TelephonyManager for an unattended reboot. The reboot is
+ * required to be done shortly after the API is invoked.
+ */
+ @Override
+ @TelephonyManager.PrepareUnattendedRebootResult
+ public int prepareForUnattendedReboot() {
+ enforceRebootPermission();
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return (int) sendRequest(CMD_PREPARE_UNATTENDED_REBOOT, null);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
diff --git a/src/com/android/phone/RcsProvisioningMonitor.java b/src/com/android/phone/RcsProvisioningMonitor.java
new file mode 100644
index 0000000..18c8c0b
--- /dev/null
+++ b/src/com/android/phone/RcsProvisioningMonitor.java
@@ -0,0 +1,913 @@
+/*
+ * 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.");
+ String packageName = getDmaPackageName();
+ 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)) {
+ if (mRcsProvisioningInfos.get(subId).getSingleRegistrationCapability()
+ == ProvisioningManager.STATUS_CAPABLE) {
+ try {
+ RcsConfig rcsConfig = new RcsConfig(getConfig(subId));
+ return rcsConfig.isRcsVolteSingleRegistrationSupported();
+ } catch (IllegalArgumentException e) {
+ logd("fail to get rcs config for sub:" + subId);
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 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) -> {
+ 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);
+ // 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 IImsConfig getIImsConfig(int subId, int feature) {
+ return mPhone.getImsResolver().getImsConfig(
+ SubscriptionManager.getSlotIndex(subId), feature);
+ }
+
+ 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..32562fa 100644
--- a/src/com/android/phone/ServiceStateProvider.java
+++ b/src/com/android/phone/ServiceStateProvider.java
@@ -18,6 +18,9 @@
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;
@@ -61,18 +64,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()}.
@@ -257,6 +248,8 @@
IS_USING_CARRIER_AGGREGATION,
OPERATOR_ALPHA_LONG_RAW,
OPERATOR_ALPHA_SHORT_RAW,
+ DATA_NETWORK_TYPE,
+ DUPLEX_MODE,
};
@Override
@@ -392,6 +385,8 @@
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[] {
voice_reg_state,
@@ -418,6 +413,8 @@
is_using_carrier_aggregation,
operator_alpha_long_raw,
operator_alpha_short_raw,
+ data_network_type,
+ duplex_mode,
});
}
}
@@ -480,6 +477,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 +499,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 +522,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);
}
}
diff --git a/src/com/android/phone/SimPhonebookProvider.java b/src/com/android/phone/SimPhonebookProvider.java
new file mode 100644
index 0000000..4a15950
--- /dev/null
+++ b/src/com/android/phone/SimPhonebookProvider.java
@@ -0,0 +1,911 @@
+/*
+ * 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.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 {
+ return mIccPhoneBookSupplier.get().updateAdnRecordsInEfByIndexForSubscriber(
+ args.subscriptionId, existingRecord.getEfid(), newName, newPhone,
+ 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..be6cb1f 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -16,30 +16,47 @@
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.content.Context;
-import android.os.BasicShellCommandHandler;
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.CarrierConfigManager;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
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 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 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;
/**
@@ -60,13 +77,16 @@
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 IMS_SET_CARRIER_SERVICE = "set-ims-service";
- private static final String IMS_GET_CARRIER_SERVICE = "get-ims-service";
+ 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";
private static final String IMS_ENABLE = "enable";
private static final String IMS_DISABLE = "disable";
// Used to disable or enable processing of conference event package data from the network.
@@ -81,6 +101,40 @@
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 RCS_UCE_COMMAND = "uce";
+ private static final String UCE_GET_EAB_CONTACT = "get-eab-contact";
+ 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";
+
+ // 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 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";
+
// Take advantage of existing methods that already contain permissions checks when possible.
private final ITelephony mInterface;
@@ -131,6 +185,48 @@
}
};
+ /**
+ * 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 =
@@ -150,6 +246,8 @@
case IMS_SUBCOMMAND: {
return handleImsCommand();
}
+ case RCS_UCE_COMMAND:
+ return handleRcsUceCommand();
case NUMBER_VERIFICATION_SUBCOMMAND:
return handleNumberVerificationCommand();
case EMERGENCY_CALLBACK_MODE:
@@ -163,6 +261,20 @@
return handleDataTestModeCommand();
case END_BLOCK_SUPPRESSION:
return handleEndBlockSuppressionCommand();
+ case GBA_SUBCOMMAND:
+ return handleGbaCommand();
+ case D2D_SUBCOMMAND:
+ return handleD2dCommand();
+ case SINGLE_REGISTATION_CONFIG:
+ return handleSingleRegistrationConfigCommand();
+ case RESTART_MODEM:
+ return handleRestartModemCommand();
+ case UNATTENDED_REBOOT:
+ return handleUnattendedReboot();
+ case HAS_CARRIER_PRIVILEGES_COMMAND:
+ return handleHasCarrierPrivilegesCommand();
+ case THERMAL_MITIGATION_COMMAND:
+ return handleThermalMitigationCommand();
default: {
return handleDefaultCommands(cmd);
}
@@ -177,6 +289,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 +299,41 @@
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.");
onHelpIms();
+ onHelpUce();
onHelpEmergencyNumber();
onHelpEndBlockSupperssion();
onHelpDataTestMode();
onHelpCc();
+ onHelpGba();
+ onHelpSrc();
+ onHelpD2D();
+ }
+
+ 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));
}
private void onHelpIms() {
@@ -210,9 +354,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 +372,44 @@
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");
+ }
+
private void onHelpNumberVerification() {
PrintWriter pw = getOutPrintWriter();
pw.println("Number verification commands");
@@ -234,6 +421,16 @@
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 onHelpDataTestMode() {
PrintWriter pw = getOutPrintWriter();
pw.println("Mobile Data Test Mode Commands:");
@@ -290,6 +487,69 @@
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 int handleImsCommand() {
String arg = getNextArg();
if (arg == null) {
@@ -298,12 +558,15 @@
}
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_CLEAR_SERVICE_OVERRIDE: {
+ return handleImsClearCarrierServiceCommand();
+ }
case IMS_ENABLE: {
return handleEnableIms();
}
@@ -478,6 +741,94 @@
return -1;
}
+ 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();
+ }
+ }
+
+ return -1;
+ }
+
+ private int handleD2dSendCommand() {
+ PrintWriter errPw = getErrPrintWriter();
+ String opt;
+ 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;
+ }
+
// ims set-ims-service
private int handleImsSetServiceCommand() {
PrintWriter errPw = getErrPrintWriter();
@@ -563,6 +914,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 +1604,589 @@
}
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_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();
+ }
+ 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 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 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 int handleHasCarrierPrivilegesCommand() {
+ String packageName = getNextArgRequired();
+
+ boolean hasCarrierPrivileges;
+ 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;
+ }
+
+ getOutPrintWriter().println(hasCarrierPrivileges);
+ return 0;
+ }
}
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/settings/AccessibilitySettingsFragment.java b/src/com/android/phone/settings/AccessibilitySettingsFragment.java
index ad3f133..8355fa6 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,13 +59,10 @@
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) {
if (DBG) Log.d(LOG_TAG, "PhoneStateListener.onCallStateChanged: state=" + state);
@@ -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..3f4ae58 100644
--- a/src/com/android/phone/settings/RadioInfo.java
+++ b/src/com/android/phone/settings/RadioInfo.java
@@ -17,7 +17,6 @@
package com.android.phone.settings;
import static android.net.ConnectivityManager.NetworkCallback;
-import static android.provider.Settings.Global.PREFERRED_NETWORK_MODE;
import android.content.ComponentName;
import android.content.Context;
@@ -37,9 +36,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 +51,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.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;
@@ -202,9 +200,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;
@@ -231,7 +226,6 @@
private TextView mDBm;
private TextView mMwi;
private TextView mCfi;
- private TextView mLocation;
private TextView mCellInfo;
private TextView mSent;
private TextView mReceived;
@@ -283,7 +277,6 @@
private boolean mCfiValue = false;
private List<CellInfo> mCellInfoResult = null;
- private CellLocation mCellLocationResult = null;
private int mPreferredNetworkTypeResult;
private int mCellInfoRefreshRateIndex;
@@ -303,10 +296,20 @@
};
// 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,
+ TelephonyCallback.PreciseCallStateListener {
+
@Override
- public void onDataConnectionStateChanged(int state) {
+ public void onDataConnectionStateChanged(int state, int networkType) {
updateDataState();
updateNetworkType();
}
@@ -328,11 +331,6 @@
}
@Override
- public void onCellLocationChanged(CellLocation location) {
- updateLocation(location);
- }
-
- @Override
public void onMessageWaitingIndicatorChanged(boolean mwi) {
mMwiValue = mwi;
updateMessageWaiting();
@@ -385,8 +383,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 +411,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 +456,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 +480,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);
@@ -616,9 +597,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 +637,6 @@
updateNetworkType();
updateNrStats(null);
- updateLocation(mCellLocationResult);
updateCellInfo(mCellInfoResult);
updateSubscriptionIds();
@@ -702,7 +684,7 @@
log("onPause: unregister phone & data intents");
- mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+ mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback);
mTelephonyManager.setCellInfoListRate(sCellInfoListRateDisabled);
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
@@ -803,7 +785,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 +796,6 @@
mSent.setText("");
mReceived.setText("");
mCallState.setText("");
- mLocation.setText("");
mMwiValue = false;
mMwi.setText("");
mCfiValue = false;
@@ -826,21 +807,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 +843,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) : "";
}
@@ -1286,11 +1218,9 @@
private void updateAllCellInfo() {
mCellInfo.setText("");
- mLocation.setText("");
final Runnable updateAllCellInfoResults = new Runnable() {
public void run() {
- updateLocation(mCellLocationResult);
updateCellInfo(mCellInfoResult);
}
};
@@ -1299,7 +1229,6 @@
@Override
public void run() {
mCellInfoResult = mTelephonyManager.getAllCellInfo();
- mCellLocationResult = mTelephonyManager.getCellLocation();
mHandler.post(updateAllCellInfoResults);
}
@@ -1520,9 +1449,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 +1464,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 +1479,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 +1523,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 +1549,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() {
@@ -1710,21 +1642,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();
}
}
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 66b1af9..2bd5306 100644
--- a/src/com/android/phone/settings/VoicemailSettingsActivity.java
+++ b/src/com/android/phone/settings/VoicemailSettingsActivity.java
@@ -238,6 +238,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);
@@ -250,12 +254,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 140cc74..468d38f 100644
--- a/src/com/android/phone/settings/fdn/EditFdnContactScreen.java
+++ b/src/com/android/phone/settings/fdn/EditFdnContactScreen.java
@@ -16,21 +16,18 @@
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 static android.app.Activity.RESULT_OK;
+
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.provider.ContactsContract.CommonDataKinds;
+import android.telephony.CarrierConfigManager;
import android.telephony.PhoneNumberUtils;
import android.text.Editable;
import android.text.Selection;
@@ -42,50 +39,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
*/
@@ -108,13 +86,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());
@@ -145,11 +120,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.");
@@ -231,20 +202,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.
*/
@@ -374,36 +340,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 ?
@@ -426,13 +374,7 @@
}
}
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- finish();
- }
- }, 2000);
-
+ mHandler.postDelayed(() -> finish(), 2000);
}
private final View.OnClickListener mClicked = new View.OnClickListener() {
@@ -486,35 +428,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/CallQualityManager.java b/src/com/android/services/telephony/CallQualityManager.java
new file mode 100644
index 0000000..262ce88
--- /dev/null
+++ b/src/com/android/services/telephony/CallQualityManager.java
@@ -0,0 +1,139 @@
+/*
+ * 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 android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.telecom.BluetoothCallQualityReport;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.SlidingWindowEventCounter;
+import com.android.phone.R;
+
+/**
+ * class to handle call quality events that are received by telecom and telephony
+ */
+public class CallQualityManager {
+ private static final String TAG = CallQualityManager.class.getCanonicalName();
+
+ /** notification ids */
+ public static final int BLUETOOTH_CHOPPY_VOICE_NOTIFICATION_ID = 700;
+ public static final String CALL_QUALITY_CHANNEL_ID = "CallQualityNotificationChannel";
+ public static final long NOTIFICATION_BACKOFF_TIME_MILLIS = 5L * 60 * 1000;
+ public static final int NUM_OCCURRENCES_THRESHOLD = 5;
+ public static final long TIME_WINDOW_MILLIS = 5 * 1000;
+
+ private final Context mContext;
+ private final NotificationChannel mNotificationChannel;
+ private final NotificationManager mNotificationManager;
+ private final SlidingWindowEventCounter mSlidingWindowEventCounter;
+
+ private long mNotificationLastTime;
+
+ public CallQualityManager(Context context) {
+ mContext = context;
+ mNotificationChannel = new NotificationChannel(CALL_QUALITY_CHANNEL_ID,
+ mContext.getString(R.string.call_quality_notification_name),
+ NotificationManager.IMPORTANCE_HIGH);
+ mNotificationManager = (NotificationManager)
+ mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ mNotificationManager.createNotificationChannel(mNotificationChannel);
+ //making sure at the start we qualify to show notifications
+ mNotificationLastTime =
+ SystemClock.elapsedRealtime() - NOTIFICATION_BACKOFF_TIME_MILLIS - 1;
+ mSlidingWindowEventCounter =
+ new SlidingWindowEventCounter(TIME_WINDOW_MILLIS, NUM_OCCURRENCES_THRESHOLD);
+ }
+
+ /**
+ * method that is called whenever a
+ * {@code BluetoothCallQualityReport.EVENT_SEND_BLUETOOTH_CALL_QUALITY_REPORT} is received
+ * @param extras Bundle that includes serialized {@code BluetoothCallQualityReport} parcelable
+ */
+ @VisibleForTesting
+ public void onBluetoothCallQualityReported(Bundle extras) {
+ if (extras == null) {
+ Log.d(TAG, "onBluetoothCallQualityReported: no extras provided");
+ }
+
+ BluetoothCallQualityReport callQualityReport = extras.getParcelable(
+ BluetoothCallQualityReport.EXTRA_BLUETOOTH_CALL_QUALITY_REPORT);
+
+ if (callQualityReport.isChoppyVoice()) {
+ onChoppyVoice();
+ }
+ // TODO: once other signals are also sent, we will add more actions here
+ }
+
+ /**
+ * method to post a notification to user suggesting ways to improve call quality in case of
+ * bluetooth choppy voice
+ */
+ @VisibleForTesting
+ public void onChoppyVoice() {
+ String title = "Call Quality Improvement";
+ Log.d(TAG, "Bluetooth choppy voice signal received.");
+ if (mSlidingWindowEventCounter.addOccurrence(SystemClock.elapsedRealtime())) {
+ timedNotify(title,
+ mContext.getText(R.string.call_quality_notification_bluetooth_details));
+ }
+ }
+
+ // notify user only if you haven't in the last NOTIFICATION_BACKOFF_TIME_MILLIS milliseconds
+ private void timedNotify(String title, CharSequence details) {
+ if (!mContext.getResources().getBoolean(
+ R.bool.enable_bluetooth_call_quality_notification)) {
+ Log.d(TAG, "Bluetooth call quality notifications not enabled.");
+ return;
+ }
+ long now = SystemClock.elapsedRealtime();
+ if (now - mNotificationLastTime > NOTIFICATION_BACKOFF_TIME_MILLIS) {
+ int iconId = android.R.drawable.stat_notify_error;
+
+ Notification notification = new Notification.Builder(mContext)
+ .setSmallIcon(iconId)
+ .setWhen(System.currentTimeMillis())
+ .setAutoCancel(true)
+ .setContentTitle(title)
+ .setContentText(details)
+ .setStyle(new Notification.BigTextStyle().bigText(details))
+ .setChannelId(CALL_QUALITY_CHANNEL_ID)
+ .setOnlyAlertOnce(true)
+ .build();
+
+ mNotificationManager.notify(TAG, BLUETOOTH_CHOPPY_VOICE_NOTIFICATION_ID, notification);
+ mNotificationLastTime = now;
+ Log.d(TAG, "Call quality signal received, showing notification");
+ } else {
+ Log.d(TAG, "Call quality signal received, but not showing notification, "
+ + "as recently notified in the last "
+ + NOTIFICATION_BACKOFF_TIME_MILLIS / 1000 + " seconds");
+ }
+ }
+
+ /**
+ * close the notifications that have been emitted during the call
+ */
+ public void clearNotifications() {
+ mNotificationManager.cancel(TAG, BLUETOOTH_CHOPPY_VOICE_NOTIFICATION_ID);
+ }
+}
diff --git a/src/com/android/services/telephony/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/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 82a393d..c9f762b 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)
@@ -1383,9 +1393,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..ee4baae 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;
@@ -65,6 +67,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 +224,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 +253,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 +283,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 +293,22 @@
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) {
+ 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/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 7d6e65a..9226cae 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();
@@ -574,7 +589,9 @@
PersistableBundle b =
PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
boolean carrierConfigEnabled = b != null
- && b.getBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL);
+ && (b.getBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL)
+ || b.getBoolean(
+ CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL));
return carrierConfigEnabled && isUserContactDiscoverySettingEnabled();
}
@@ -776,7 +793,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 +812,7 @@
Log.i(this, "updateAdhocConfCapability - changed, new value: "
+ isAdhocConfCapable);
mIsAdhocConfCapable = isAdhocConfCapable;
- mAccount = registerPstnPhoneAccount(mIsEmergency, mIsDummy);
+ mAccount = registerPstnPhoneAccount(mIsEmergency, mIsTestAccount);
}
}
}
@@ -814,7 +831,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 +840,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 +860,7 @@
activeDataSubId);
if (isEmergencyPreferred != mIsEmergencyPreferred) {
Log.i(this, "updateDefaultDataSubId - changed, new value: " + isEmergencyPreferred);
- mAccount = registerPstnPhoneAccount(mIsEmergency, mIsDummy);
+ mAccount = registerPstnPhoneAccount(mIsEmergency, mIsTestAccount);
}
}
@@ -1053,7 +1081,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();
@@ -1119,6 +1151,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 +1174,7 @@
this.mTelephonyConnectionService = telephonyConnectionService;
}
- TelephonyConnectionService getTelephonyConnectionService() {
+ public TelephonyConnectionService getTelephonyConnectionService() {
return mTelephonyConnectionService;
}
@@ -1346,8 +1379,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 +1400,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 +1416,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 +1450,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 +1518,7 @@
}
mAccounts.add(new AccountEntry(phone, false /* emergency */,
- false /* isDummy */));
+ false /* isTest */));
}
}
} finally {
@@ -1487,14 +1529,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..d745aab 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;
@@ -28,6 +29,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
+import android.telecom.BluetoothCallQualityReport;
import android.telecom.CallAudioState;
import android.telecom.Conference;
import android.telecom.Connection;
@@ -46,6 +48,8 @@
import android.telephony.TelephonyManager;
import android.telephony.ims.ImsCallProfile;
import android.telephony.ims.ImsStreamMediaProfile;
+import android.telephony.ims.RtpHeaderExtension;
+import android.telephony.ims.RtpHeaderExtensionType;
import android.text.TextUtils;
import android.util.Pair;
@@ -61,6 +65,12 @@
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.RtpAdapter;
+import com.android.internal.telephony.d2d.RtpTransport;
+import com.android.internal.telephony.d2d.Timeouts;
import com.android.internal.telephony.gsm.SuppServiceNotification;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneCall;
@@ -81,11 +91,12 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
/**
* 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 +107,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}.
@@ -117,6 +131,9 @@
private static final int MSG_REDIAL_CONNECTION_CHANGED = 20;
private static final int MSG_REJECT = 21;
+ 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;
@@ -149,15 +166,18 @@
mOriginalConnection.getAddress() != null &&
mOriginalConnection.getAddress().equals(connection.getAddress())) ||
connection.getState() == mOriginalConnection.getStateBeforeHandover())) {
- Log.d(TelephonyConnection.this,
- "SettingOriginalConnection " + mOriginalConnection.toString()
- + " with " + connection.toString());
+ Log.d(TelephonyConnection.this, "Setting original connection after"
+ + " handover or redial, current original connection="
+ + mOriginalConnection.toString()
+ + ", new original connection="
+ + connection.toString());
setOriginalConnection(connection);
mWasImsConnection = false;
}
} else {
Log.w(TelephonyConnection.this,
- what + ": mOriginalConnection==null - invalid state (not cleaned up)");
+ what + ": mOriginalConnection==null --"
+ + " invalid state (not cleaned up)");
}
break;
case MSG_RINGBACK_TONE:
@@ -478,6 +498,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, state);
+ }
+ }
+
private final PostDialListener mPostDialListener = new PostDialListener() {
@Override
public void onPostDialWait() {
@@ -692,6 +725,20 @@
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);
+ }
};
private TelephonyConnectionService mTelephonyConnectionService;
@@ -796,11 +843,27 @@
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(
new ConcurrentHashMap<TelephonyConnectionListener, Boolean>(8, 0.9f, 1));
+ private CallQualityManager mCallQualityManager;
+
protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection,
String callId, @android.telecom.Call.Details.CallDirection int callDirection) {
setCallDirection(callDirection);
@@ -810,6 +873,20 @@
}
}
+ @Override
+ public void onCallEvent(String event, Bundle extras) {
+ switch (event) {
+ case BluetoothCallQualityReport.EVENT_BLUETOOTH_CALL_QUALITY_REPORT:
+ if (mCallQualityManager == null) {
+ mCallQualityManager = new CallQualityManager(getPhone().getContext());
+ }
+ mCallQualityManager.onBluetoothCallQualityReported(extras);
+ break;
+ default:
+ break;
+ }
+
+ }
/**
* Creates a clone of the current {@link TelephonyConnection}.
*
@@ -1295,6 +1372,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 +1396,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;
}
@@ -1379,7 +1461,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,6 +1472,9 @@
if (isImsConnection()) {
mWasImsConnection = true;
}
+ if (originalConnection instanceof ImsPhoneConnection) {
+ maybeConfigureDeviceToDeviceCommunication();
+ }
mIsMultiParty = mOriginalConnection.isMultiparty();
Bundle extrasToPut = new Bundle();
@@ -1403,6 +1490,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 +1579,8 @@
}
}
- private void refreshCodecType() {
+ private void refreshCodec() {
+ boolean changed = false;
Bundle newExtras = getExtras();
if (newExtras == null) {
newExtras = new Bundle();
@@ -1499,6 +1596,40 @@
Connection.AUDIO_CODEC_NONE);
if (newCodecType != oldCodecType) {
newExtras.putInt(Connection.EXTRA_AUDIO_CODEC, newCodecType);
+ Log.i(this, "put audio codec:" + 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, "put audio bitrate:" + 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, "put audio bandwidth:" + 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, "Audio attribute, 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 +1708,7 @@
}
}
- isVowifiEnabled = ImsUtil.isWfcEnabled(phone.getContext(), phone.getPhoneId());
+ isVowifiEnabled = isWfcEnabled(phone);
}
if (isCurrentVideoCall) {
@@ -2167,6 +2298,10 @@
case DISCONNECTING:
break;
}
+
+ if (mCommunicator != null) {
+ mCommunicator.onStateChanged(this, getState());
+ }
}
}
@@ -2182,7 +2317,7 @@
updateAddress();
updateMultiparty();
refreshDisableAddCall();
- refreshCodecType();
+ refreshCodec();
}
/**
@@ -2713,7 +2848,7 @@
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);
@@ -2763,6 +2898,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
@@ -2921,6 +3062,9 @@
setDisconnected(disconnectCause);
notifyDisconnected(disconnectCause);
notifyStateChanged(getState());
+ if (mCallQualityManager != null) {
+ mCallQualityManager.clearNotifications();
+ }
}
/**
@@ -3055,6 +3199,89 @@
}
/**
+ * 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.d(this, "maybeConfigureDeviceToDeviceCommunication: not using D2D.");
+ return;
+ }
+ if (!isImsConnection()) {
+ Log.d(this, "maybeConfigureDeviceToDeviceCommunication: not an IMS connection.");
+ return;
+ }
+ // Implement abstracted out RTP functionality the RTP transport depends on.
+ RtpAdapter rtpAdapter = new RtpAdapter() {
+ @Override
+ public Set<RtpHeaderExtensionType> getAcceptedRtpHeaderExtensions() {
+ if (!isImsConnection()) {
+ return Collections.EMPTY_SET;
+ }
+ ImsPhoneConnection originalConnection =
+ (ImsPhoneConnection) mOriginalConnection;
+ return originalConnection.getAcceptedRtpHeaderExtensions();
+ }
+
+ @Override
+ public void sendRtpHeaderExtensions(
+ @NonNull Set<RtpHeaderExtension> rtpHeaderExtensions) {
+ if (!isImsConnection()) {
+ Log.w(TelephonyConnection.this, "sendRtpHeaderExtensions: not an ims conn.");
+ }
+ Log.d(TelephonyConnection.this, "sendRtpHeaderExtensions: sending %d messages",
+ rtpHeaderExtensions.size());
+ ImsPhoneConnection originalConnection =
+ (ImsPhoneConnection) mOriginalConnection;
+ originalConnection.sendRtpHeaderExtensions(rtpHeaderExtensions);
+ }
+ };
+ mRtpTransport = new RtpTransport(rtpAdapter, null /* TODO: not needed yet */, mHandler);
+
+ DtmfAdapter dtmfAdapter = digit -> {
+ if (!isImsConnection()) {
+ Log.w(TelephonyConnection.this, "sendDtmf: not an ims conn.");
+ }
+ Log.d(TelephonyConnection.this, "sendDtmf: send digit %c", digit);
+ ImsPhoneConnection originalConnection =
+ (ImsPhoneConnection) mOriginalConnection;
+ originalConnection.getImsCall().sendDtmf(digit, null /* result msg not needed */);
+ };
+ ContentResolver cr = getPhone().getContext().getContentResolver();
+ mDtmfTransport = new DtmfTransport(dtmfAdapter, new Timeouts.Adapter(cr),
+ Executors.newSingleThreadScheduledExecutor());
+ mCommunicator = new Communicator(List.of(mRtpTransport, mDtmfTransport), this);
+ mD2DCallStateAdapter = new D2DCallStateAdapter(mCommunicator);
+ addTelephonyConnectionListener(mD2DCallStateAdapter);
+ }
+
+ /**
+ * @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) {
+ Bundle extras = new Bundle();
+ extras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE, msg.getType());
+ extras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE, msg.getValue());
+ sendConnectionEvent(Connection.EVENT_DEVICE_TO_DEVICE_MESSAGE, extras);
+ }
+ }
+
+ /**
* Called by a {@link ConnectionService} to notify Telecom that a {@link Conference#onMerge()}
* operation has started.
*/
@@ -3201,4 +3428,29 @@
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));
+ }
}
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 290b4e1..a1bea2a 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -17,10 +17,13 @@
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;
@@ -46,6 +49,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 +63,14 @@
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.settings.SuppServicesUiUtil;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -72,6 +78,7 @@
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;
@@ -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
@@ -795,6 +805,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) {
@@ -907,6 +921,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 +953,7 @@
for (Phone curPhone : mPhoneFactoryProxy.getPhones()) {
curPhone.setRadioPower(true, false, false, true);
}
+ mIsEmergencyCallPending = false;
}
return;
}
@@ -946,6 +969,7 @@
Log.i(this, "handleOnComplete - delayDialForDdsSwitch result = " + result);
adjustAndPlaceOutgoingConnection(phone, originalConnection, request,
numberToDial, handle, originalPhoneType, true);
+ mIsEmergencyCallPending = false;
}
});
}
@@ -956,6 +980,7 @@
mDisconnectCauseFactory.toTelecomDisconnectCause(
android.telephony.DisconnectCause.POWER_OFF,
"Failed to turn on radio."));
+ mIsEmergencyCallPending = false;
}
}
@@ -1055,7 +1080,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 +1165,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(
@@ -1678,13 +1704,18 @@
? connection.getAddress().getSchemeSpecificPart()
: "";
+ if (showDataDialog(phone, number)) {
+ connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
+ android.telephony.DisconnectCause.DIALED_MMI, "UT is not available"));
+ return;
+ }
+
com.android.internal.telephony.Connection originalConnection = null;
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
@@ -1736,7 +1767,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);
@@ -2381,6 +2413,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 +2494,45 @@
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;
+ 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));
+ }});
+
+ });
+ }
+
+ 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;
+ }
}
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/MessageTransportStateTracker.java b/src/com/android/services/telephony/rcs/MessageTransportStateTracker.java
new file mode 100644
index 0000000..b2dcfe8
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/MessageTransportStateTracker.java
@@ -0,0 +1,485 @@
+/*
+ * 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 java.io.PrintWriter;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Tracks the SIP message path both from the IMS application to the SipDelegate and from the
+ * SipDelegate back to the IMS Application.
+ * <p>
+ * Responsibilities include:
+ * 1) Queue incoming and outgoing SIP messages and deliver to IMS application and SipDelegate in
+ * order. If there is an error delivering the message, notify the caller.
+ * 2) TODO Perform basic validation of outgoing messages.
+ * 3) TODO Record the status of ongoing SIP Dialogs and trigger the completion of pending
+ * consumers when they are finished or call closeDialog to clean up the SIP
+ * dialogs that did not complete within the allotted timeout time.
+ * <p>
+ * Note: This handles incoming binder calls, so all calls from other processes should be handled on
+ * the provided Executor.
+ */
+public class MessageTransportStateTracker implements DelegateBinderStateManager.StateCallback {
+ private static final String TAG = "MessageST";
+
+ /**
+ * Communicates the result of verifying whether a SIP message should be sent based on the
+ * contents of the SIP message as well as if the transport is in an available state for the
+ * intended recipient of the message.
+ */
+ private static class VerificationResult {
+ public static final VerificationResult SUCCESS = new VerificationResult();
+
+ /**
+ * If {@code true}, the requested SIP message has been verified to be sent to the remote. If
+ * {@code false}, the SIP message has failed verification and should not be sent to the
+ * result. The {@link #restrictedReason} field will contain the reason for the verification
+ * failure.
+ */
+ public final boolean isVerified;
+
+ /**
+ * The reason associated with why the SIP message was not verified and generated a
+ * {@code false} result for {@link #isVerified}.
+ */
+ public final int restrictedReason;
+
+ /**
+ * Communicates a verified result of success. Use {@link #SUCCESS} instead.
+ */
+ private VerificationResult() {
+ isVerified = true;
+ restrictedReason = SipDelegateManager.MESSAGE_FAILURE_REASON_UNKNOWN;
+ }
+
+ /**
+ * The result of verifying that the SIP Message should be sent.
+ * @param reason The reason associated with why the SIP message was not verified and
+ * generated a {@code false} result for {@link #isVerified}.
+ */
+ VerificationResult(@SipDelegateManager.MessageFailureReason int reason) {
+ isVerified = false;
+ restrictedReason = reason;
+ }
+ }
+
+ // 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 {
+ // TODO track the SIP Dialogs created/destroyed on the associated
+ // SipDelegate.
+ 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 {
+ // TODO track the SIP Dialogs created/destroyed on the associated
+ // SipDelegate.
+ 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(() -> {
+ VerificationResult result = verifyOutgoingMessage(sipMessage);
+ if (!result.isVerified) {
+ notifyDelegateSendError("Outgoing messages restricted", sipMessage,
+ result.restrictedReason);
+ return;
+ }
+ try {
+ // TODO track the SIP Dialogs created/destroyed on the associated
+ // SipDelegate.
+ mSipDelegate.sendMessage(sipMessage, configVersion);
+ logi("sendMessage: message sent - " + sipMessage + ", configVersion: "
+ + 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(() -> {
+ if (mSipDelegate == null) {
+ logw("closeDialog called when SipDelegate is not associated, callId: "
+ + callId);
+ return;
+ }
+ try {
+ // TODO track the SIP Dialogs created/destroyed on the associated
+ // SipDelegate.
+ mSipDelegate.cleanupSession(callId);
+ } catch (RemoteException e) {
+ logw("SipDelegate not available when closeDialog was called "
+ + "for call id: " + 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(() -> {
+ VerificationResult result = verifyIncomingMessage(message);
+ if (!result.isVerified) {
+ notifyAppReceiveError("Incoming messages restricted", message,
+ result.restrictedReason);
+ return;
+ }
+ try {
+ // TODO track the SIP Dialogs created/destroyed on the associated
+ // SipDelegate.
+ mAppCallback.onMessageReceived(message);
+ logi("onMessageReceived: received " + 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, int)} 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 {
+ 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, int)} 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 {
+ mAppCallback.onMessageSendFailure(viaTransactionId, reason);
+ } catch (RemoteException e) {
+ logw("Error sending onMessageSendFailure to SipDelegateConnection, remote"
+ + " not available for transaction ID: " + viaTransactionId);
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
+
+ private final ISipDelegateMessageCallback mAppCallback;
+ private final Executor mExecutor;
+ private final int mSubId;
+ private final LocalLog mLocalLog = new LocalLog(SipTransportController.LOG_SIZE);
+
+ private ISipDelegate mSipDelegate;
+ private Consumer<Boolean> mPendingClosedConsumer;
+ private int mDelegateClosingReason = -1;
+ private int mDelegateClosedReason = -1;
+
+ public MessageTransportStateTracker(int subId, Executor executor,
+ ISipDelegateMessageCallback appMessageCallback) {
+ mSubId = subId;
+ mAppCallback = appMessageCallback;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onRegistrationStateChanged(DelegateRegistrationState registrationState) {
+ // TODO: integrate registration changes to SipMessage verification checks.
+ }
+
+ @Override
+ public void onImsConfigurationChanged(SipDelegateImsConfiguration config) {
+ // Not needed for this Tracker
+ }
+
+ @Override
+ public void onConfigurationChanged(SipDelegateConfiguration config) {
+ // Not needed for this Tracker
+ }
+
+ /**
+ * 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 deniedFeatureTags Feature tags that have been denied. Outgoing SIP messages relating
+ * to these tags will be denied.
+ */
+ public void openTransport(ISipDelegate delegate, Set<FeatureTagState> deniedFeatureTags) {
+ mSipDelegate = delegate;
+ mDelegateClosingReason = -1;
+ mDelegateClosedReason = -1;
+ // TODO: integrate denied tags to SipMessage verification checks.
+ }
+
+ /** Dump state about this tracker that should be included in the dumpsys */
+ public void dump(PrintWriter printWriter) {
+ printWriter.println("Most recent logs:");
+ mLocalLog.dump(printWriter);
+ }
+
+ /**
+ * @return SipDelegate implementation to be sent to IMS application.
+ */
+ public ISipDelegate getDelegateConnection() {
+ return mSipDelegateConnection;
+ }
+
+ /**
+ * @return MessageCallback implementation to be sent to the ImsService.
+ */
+ public ISipDelegateMessageCallback getMessageCallback() {
+ return mDelegateConnectionMessageCallback;
+ }
+
+ /**
+ * Gradually close all SIP Dialogs by:
+ * 1) denying all new outgoing SIP Dialog requests with the reason specified and
+ * 2) only allowing existing SIP Dialogs to continue.
+ * <p>
+ * This will allow traffic to continue on existing SIP Dialogs until a BYE is sent and the
+ * SIP Dialogs are closed or a timeout is hit and {@link SipDelegate#closeDialog(String)} is
+ * forcefully called on all open SIP Dialogs.
+ * <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) {
+ mDelegateClosingReason = delegateClosingReason;
+ mPendingClosedConsumer = resultConsumer;
+ mExecutor.execute(() -> {
+ // TODO: Track SIP Dialogs and complete when there are no SIP dialogs open anymore or
+ // the timeout occurs.
+ mPendingClosedConsumer.accept(true);
+ mPendingClosedConsumer = null;
+ closeTransport(closedReason);
+ });
+ }
+
+ /**
+ * Close all ongoing SIP Dialogs 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) {
+ closeTransport(closedReason);
+ }
+
+ // Clean up all state related to the existing SipDelegate immediately.
+ private void closeTransport(int closedReason) {
+ // TODO: add logic to forcefully close open SIP dialogs once they are being tracked.
+ mSipDelegate = null;
+ if (mPendingClosedConsumer != null) {
+ mExecutor.execute(() -> {
+ logw("closeTransport: transport close forced with pending consumer.");
+ mPendingClosedConsumer.accept(false /*closedGracefully*/);
+ mPendingClosedConsumer = null;
+ });
+ }
+ mDelegateClosingReason = -1;
+ mDelegateClosedReason = closedReason;
+ }
+
+ private VerificationResult verifyOutgoingMessage(SipMessage message) {
+ if (mDelegateClosingReason > -1) {
+ return new VerificationResult(mDelegateClosingReason);
+ }
+ if (mDelegateClosedReason > -1) {
+ return new VerificationResult(mDelegateClosedReason);
+ }
+ if (mSipDelegate == null) {
+ logw("sendMessage called when SipDelegate is not associated." + message);
+ return new VerificationResult(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD);
+ }
+ return VerificationResult.SUCCESS;
+ }
+
+ private VerificationResult verifyIncomingMessage(SipMessage message) {
+ // Do not restrict incoming based on closing reason.
+ if (mDelegateClosedReason > -1) {
+ return new VerificationResult(mDelegateClosedReason);
+ }
+ return VerificationResult.SUCCESS;
+ }
+
+ private void notifyDelegateSendError(String logReason, SipMessage message, int reasonCode) {
+ // TODO parse SipMessage header for viaTransactionId.
+ logw("Error sending SipMessage[id: " + null + ", code: " + reasonCode + "] -> SipDelegate "
+ + "for reason: " + logReason);
+ try {
+ mAppCallback.onMessageSendFailure(null, reasonCode);
+ } catch (RemoteException e) {
+ logw("notifyDelegateSendError, SipDelegate is not available: " + e);
+ }
+ }
+
+ private void notifyAppReceiveError(String logReason, SipMessage message, int reasonCode) {
+ // TODO parse SipMessage header for viaTransactionId.
+ logw("Error sending SipMessage[id: " + null + ", code: " + reasonCode + "] -> "
+ + "SipDelegateConnection for reason: " + logReason);
+ try {
+ mSipDelegate.notifyMessageReceiveError(null, reasonCode);
+ } catch (RemoteException e) {
+ logw("notifyAppReceiveError, SipDelegate is not available: " + e);
+ }
+ }
+
+ private void logi(String log) {
+ Log.w(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..3eefdb0 100644
--- a/src/com/android/services/telephony/rcs/RcsFeatureController.java
+++ b/src/com/android/services/telephony/rcs/RcsFeatureController.java
@@ -30,7 +30,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 +65,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 +112,8 @@
ImsRegistrationCallbackHelper.ImsRegistrationUpdate cb, Executor executor);
}
- private FeatureConnectorFactory<RcsFeatureManager> mFeatureFactory = FeatureConnector::new;
+ private FeatureConnectorFactory<RcsFeatureManager> mFeatureFactory =
+ RcsFeatureManager::getConnector;
private RegistrationHelperFactory mRegistrationHelperFactory =
ImsRegistrationCallbackHelper::new;
@@ -113,15 +124,11 @@
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) {
@@ -131,17 +138,21 @@
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");
+ }
// 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,10 +399,21 @@
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 {
// Open persistent listener connection, sends RcsFeature#onFeatureReady.
manager.openConnection();
- manager.updateCapabilities();
+ manager.updateCapabilities(mAssociatedSubId);
manager.registerImsRegistrationCallback(mImsRcsRegistrationHelper.getCallbackBinder());
}
@@ -427,6 +461,14 @@
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();
}
}
@@ -434,6 +476,10 @@
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..5eb0558
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/SipDelegateBinderConnection.java
@@ -0,0 +1,268 @@
+/*
+ * 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");
+ 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..2d6d4f0
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/SipDelegateController.java
@@ -0,0 +1,408 @@
+/*
+ * 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.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;
+
+/**
+ * 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 MessageTransportStateTracker mMessageTransportStateTracker;
+ 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);
+
+ mMessageTransportStateTracker = new MessageTransportStateTracker(mSubId, executorService,
+ messageCallback);
+ mDelegateStateTracker = new DelegateStateTracker(mSubId, stateCallback,
+ mMessageTransportStateTracker.getDelegateConnection());
+ }
+
+ /**
+ * Inject dependencies for testing only.
+ */
+ @VisibleForTesting
+ public SipDelegateController(int subId, DelegateRequest initialRequest, String packageName,
+ ScheduledExecutorService executorService,
+ MessageTransportStateTracker messageTransportStateTracker,
+ DelegateStateTracker delegateStateTracker,
+ DelegateBinderStateManager.Factory connectionFactory) {
+ mSubId = subId;
+ mInitialRequest = initialRequest;
+ mPackageName = packageName;
+ mExecutorService = executorService;
+ mMessageTransportStateTracker = messageTransportStateTracker;
+ 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;
+ }
+
+ public ISipDelegate getSipDelegateInterface() {
+ return mMessageTransportStateTracker.getDelegateConnection();
+ }
+
+ /**
+ * 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);
+ mMessageTransportStateTracker.openTransport(resultPair.first, 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");
+ mMessageTransportStateTracker.close(messageDestroyedReason);
+ pendingTransportClosed.complete(true);
+ } else {
+ mMessageTransportStateTracker.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(mMessageTransportStateTracker.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(mMessageTransportStateTracker);
+
+ 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();
+ mMessageTransportStateTracker.dump(pw);
+ pw.decreaseIndent();
+
+ pw.decreaseIndent();
+ }
+
+ private void logi(String log) {
+ Log.w(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/SipTransportController.java b/src/com/android/services/telephony/rcs/SipTransportController.java
new file mode 100644
index 0000000..cecc8a2
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/SipTransportController.java
@@ -0,0 +1,1039 @@
+/*
+ * 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.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 {
+ static final int LOG_SIZE = 50;
+ static final String LOG_TAG = "SipTransportC";
+
+ /**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);
+ }
+ }
+
+ /**
+ * 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;
+ return reason == that.reason
+ && controller.equals(that.controller);
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(controller, reason);
+ }
+
+ @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<>();
+
+ // 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
+ 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;
+ }
+
+ // 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);
+ }
+ 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
+ .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) {
+ 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.w(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/UceControllerManager.java b/src/com/android/services/telephony/rcs/UceControllerManager.java
new file mode 100644
index 0000000..3051253
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/UceControllerManager.java
@@ -0,0 +1,388 @@
+/*
+ * 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.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.Log;
+
+import com.android.ims.RcsFeatureManager;
+import com.android.ims.rcs.uce.UceController;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+import java.util.List;
+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 int mSubId;
+ private volatile UceController mUceController;
+ private volatile RcsFeatureManager mRcsFeatureManager;
+
+ public UceControllerManager(Context context, int slotId, int subId) {
+ Log.d(LOG_TAG, "create: slotId=" + slotId + ", subId=" + subId);
+
+ mSlotId = slotId;
+ mSubId = subId;
+ mContext = context;
+ mExecutorService = Executors.newSingleThreadExecutor();
+ mUceController = new UceController(mContext, subId);
+ }
+
+ /**
+ * Constructor to inject dependencies for testing.
+ */
+ @VisibleForTesting
+ public UceControllerManager(Context context, int slotId, int subId, ExecutorService executor) {
+ mSlotId = slotId;
+ mSubId = subId;
+ mContext = context;
+ mExecutorService = executor;
+ mUceController = new UceController(mContext, subId);
+ }
+
+ @Override
+ public void onRcsConnected(RcsFeatureManager manager) {
+ mExecutorService.submit(() -> {
+ mRcsFeatureManager = manager;
+ mUceController.onRcsConnected(manager);
+ });
+ }
+
+ @Override
+ public void onRcsDisconnected() {
+ mExecutorService.submit(() -> {
+ mRcsFeatureManager = null;
+ mUceController.onRcsDisconnected();
+ });
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(LOG_TAG, "onDestroy");
+ mExecutorService.submit(() -> 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 subId) {
+ mExecutorService.submit(() -> {
+ Log.i(LOG_TAG, "onAssociatedSubscriptionUpdated: slotId=" + mSlotId
+ + ", subId=" + mSubId + ", newSubId=" + subId);
+ if (mSubId == subId) {
+ Log.w(LOG_TAG, "onAssociatedSubscriptionUpdated called with the same subId");
+ return;
+ }
+ mSubId = subId;
+ // Destroy existing UceController and create a new one.
+ mUceController.onDestroy();
+ mUceController = new UceController(mContext, subId);
+
+ // The RCS should be connected when the mRcsFeatureManager is not null. Set it to the
+ // new UceController instance.
+ if (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: subId=" + mSubId);
+ mUceController.onCarrierConfigChanged();
+ });
+ }
+
+ @VisibleForTesting
+ public void setUceController(UceController uceController) {
+ mUceController = uceController;
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ private boolean checkUceControllerState() throws ImsException {
+ if (mUceController == null || mUceController.isUnavailable()) {
+ throw new ImsException("UCE controller is unavailable",
+ ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+ }
+ return true;
+ }
+
+
+ @Override
+ public void dump(PrintWriter printWriter) {
+ IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
+ pw.println("UceControllerManager" + "[" + mSlotId + "]:");
+ pw.increaseIndent();
+ mUceController.dump(pw);
+ 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/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/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/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..d889936 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
+</LinearLayout>
diff --git a/testapps/SmsManagerTestApp/res/values/donottranslate_strings.xml b/testapps/SmsManagerTestApp/res/values/donottranslate_strings.xml
index d6497a3..7383a4a 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>
-</resources>
\ No newline at end of file
+ <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>
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..7538df7
--- /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="13"
+ android:versionName="1.0.12">
+
+ <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/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..1c461fe
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSession.java
@@ -0,0 +1,258 @@
+/*
+ * 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.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 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;
+
+ /** 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;
+
+ listenForBearer();
+ }
+
+ private final QosCallback qosCallback = new QosCallback() {
+ @Override
+ public void onError(@NonNull QosCallbackException exception) {
+ Toast.makeText(context, DEDICATED_BEARER_ERROR, Toast.LENGTH_SHORT).show();
+ Log.e(LOG_TAG, "onError: " + exception.toString());
+ super.onError(exception);
+ }
+
+ @Override
+ public void onQosSessionAvailable(@NonNull QosSession session,
+ @NonNull QosSessionAttributes sessionAttributes) {
+ Toast.makeText(context, DEDICATED_BEARER_SUCCESS, Toast.LENGTH_SHORT).show();
+ Log.d(LOG_TAG, "onQosSessionAvailable: " + session.toString() + ", "
+ + sessionAttributes.toString());
+ super.onQosSessionAvailable(session, sessionAttributes);
+ }
+
+ @Override
+ public void onQosSessionLost(@NonNull QosSession session) {
+ Toast.makeText(context, DEDICATED_BEARER_ERROR, Toast.LENGTH_SHORT).show();
+ Log.e(LOG_TAG, "onQosSessionLost: " + session.toString());
+ super.onQosSessionLost(session);
+ }
+ };
+
+ 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..fc5ee4c 100644
--- a/tests/src/com/android/TestContext.java
+++ b/tests/src/com/android/TestContext.java
@@ -17,37 +17,60 @@
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.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 +117,11 @@
}
@Override
+ public ContentResolver getContentResolver() {
+ return null;
+ }
+
+ @Override
public Object getSystemService(String name) {
switch (name) {
case (Context.CARRIER_CONFIG_SERVICE) : {
@@ -108,6 +136,9 @@
case (Context.TELEPHONY_SUBSCRIPTION_SERVICE) : {
return mMockSubscriptionManager;
}
+ case(Context.TELEPHONY_IMS_SERVICE) : {
+ return mMockImsManager;
+ }
}
return null;
}
@@ -129,7 +160,78 @@
return null;
}
- public PersistableBundle getCarrierConfig() {
- return mCarrierConfig;
+ /**
+ * @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..fec502a
--- /dev/null
+++ b/tests/src/com/android/TestExecutorService.java
@@ -0,0 +1,193 @@
+/*
+ * 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.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+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;
+
+ 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 false;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public boolean isDone() {
+ return true;
+ }
+
+ @Override
+ public T get() throws InterruptedException, ExecutionException {
+ try {
+ return mTask.call();
+ } catch (Exception e) {
+ throw new ExecutionException(e);
+ }
+ }
+
+ @Override
+ public T get(long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ try {
+ return mTask.call();
+ } catch (Exception e) {
+ throw new ExecutionException(e);
+ }
+ }
+
+ @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;
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ }
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ return null;
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return false;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return false;
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit) {
+ return false;
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> task) {
+ return new CompletedFuture<>(task);
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable task, T result) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public Future<?> submit(Runnable task) {
+ task.run();
+ return new CompletedFuture<>(() -> null);
+ }
+
+ @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) {
+ // No need to worry about delays yet
+ command.run();
+ return new CompletedFuture<>(() -> null, delay);
+ }
+
+ @Override
+ public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+ return new CompletedFuture<>(callable, delay);
+ }
+
+ @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) {
+ command.run();
+ }
+}
diff --git a/tests/src/com/android/phone/CarrierConfigLoaderTest.java b/tests/src/com/android/phone/CarrierConfigLoaderTest.java
new file mode 100644
index 0000000..f58e6cc
--- /dev/null
+++ b/tests/src/com/android/phone/CarrierConfigLoaderTest.java
@@ -0,0 +1,400 @@
+/*
+ * 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();
+ 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/RcsProvisioningMonitorTest.java b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
new file mode 100644
index 0000000..c7d0c8f
--- /dev/null
+++ b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
@@ -0,0 +1,760 @@
+/*
+ * 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 = "<RCSConfig>\n"
+ + "\t<rcsVolteSingleRegistration>1</rcsVolteSingleRegistration>\n"
+ + "\t<SERVICES>\n"
+ + "\t\t<SupportedRCSProfileVersions>UP_2.0</SupportedRCSProfileVersions>\n"
+ + "\t\t<ChatAuth>1</ChatAuth>\n"
+ + "\t\t<GroupChatAuth>1</GroupChatAuth>\n"
+ + "\t\t<ftAuth>1</ftAuth>\n"
+ + "\t\t<standaloneMsgAuth>1</standaloneMsgAuth>\n"
+ + "\t\t<geolocPushAuth>1</geolocPushAuth>\n"
+ + "\t\t<Ext>\n"
+ + "\t\t\t<DataOff>\n"
+ + "\t\t\t\t<rcsMessagingDataOff>1</rcsMessagingDataOff>\n"
+ + "\t\t\t\t<fileTransferDataOff>1</fileTransferDataOff>\n"
+ + "\t\t\t\t<mmsDataOff>1</mmsDataOff>\n"
+ + "\t\t\t\t<syncDataOff>1</syncDataOff>\n"
+ + "\t\t\t</DataOff>\n"
+ + "\t\t</Ext>\n"
+ + "\t</SERVICES>\n"
+ + "</RCSConfig>";
+ private static final String CONFIG_SINGLE_REGISTRATION_DISABLED = "<RCSConfig>\n"
+ + "\t<rcsVolteSingleRegistration>0</rcsVolteSingleRegistration>\n"
+ + "</RCSConfig>";
+ 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();
+ assertFalse(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();
+ assertFalse(mRcsProvisioningMonitor.isRcsVolteSingleRegistrationEnabled(FAKE_SUB_ID_BASE));
+ }
+
+ @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 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..d85976a 100644
--- a/tests/src/com/android/phone/ServiceStateProviderTest.java
+++ b/tests/src/com/android/phone/ServiceStateProviderTest.java
@@ -18,6 +18,7 @@
import static android.provider.Telephony.ServiceStateTable;
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;
@@ -31,8 +32,11 @@
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
+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;
@@ -60,7 +64,7 @@
private final String[] mTestProjection =
{
ServiceStateTable.VOICE_REG_STATE,
- ServiceStateProvider.DATA_REG_STATE,
+ ServiceStateTable.DATA_REG_STATE,
ServiceStateProvider.VOICE_OPERATOR_ALPHA_LONG,
ServiceStateProvider.VOICE_OPERATOR_ALPHA_SHORT,
ServiceStateTable.VOICE_OPERATOR_NUMERIC,
@@ -81,15 +85,24 @@
ServiceStateProvider.IS_USING_CARRIER_AGGREGATION,
ServiceStateProvider.OPERATOR_ALPHA_LONG_RAW,
ServiceStateProvider.OPERATOR_ALPHA_SHORT_RAW,
+ ServiceStateTable.DATA_NETWORK_TYPE,
+ ServiceStateTable.DUPLEX_MODE,
};
+ // 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);
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();
@@ -99,6 +112,15 @@
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
@@ -183,6 +205,8 @@
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));
@@ -206,6 +230,8 @@
assertEquals(isUsingCarrierAggregation, cursor.getInt(19));
assertEquals(operatorAlphaLongRaw, cursor.getString(20));
assertEquals(operatorAlphaShortRaw, cursor.getString(21));
+ assertEquals(dataNetworkType, cursor.getInt(22));
+ assertEquals(duplexMode, cursor.getInt(23));
}
/**
@@ -226,21 +252,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 +268,106 @@
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;
}
}
diff --git a/tests/src/com/android/phone/SimPhonebookProviderTest.java b/tests/src/com/android/phone/SimPhonebookProviderTest.java
new file mode 100644
index 0000000..4ab92a7
--- /dev/null
+++ b/tests/src/com/android/phone/SimPhonebookProviderTest.java
@@ -0,0 +1,1485 @@
+/*
+ * 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.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 updateAdnRecordsInEfBySearch(int efid, String oldTag, String oldPhoneNumber,
+ String newTag, String newPhoneNumber, String pin2) {
+ return updateAdnRecordsInEfBySearchForSubscriber(
+ mDefaultSubscriptionId, efid,
+ oldTag, oldPhoneNumber, newTag, newPhoneNumber, pin2);
+ }
+
+ @Override
+ public 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 updateAdnRecordsInEfByIndex(int efid, String newTag, String newPhoneNumber,
+ int index, String pin2) {
+ return updateAdnRecordsInEfByIndexForSubscriber(mDefaultSubscriptionId,
+ efid, newTag, newPhoneNumber, index, pin2);
+ }
+
+ @Override
+ public 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};
+ }
+ }
+
+ /**
+ * 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/services/telephony/TelephonyConnectionServiceTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
index 2060e6f..07fe6a8 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
@@ -936,14 +936,14 @@
// 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();
@@ -1021,14 +1021,14 @@
// 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();
@@ -1047,14 +1047,14 @@
// 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();
@@ -1073,14 +1073,14 @@
// 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();
@@ -1107,13 +1107,13 @@
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();
@@ -1140,13 +1140,13 @@
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();
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..67e0329 100644
--- a/tests/src/com/android/services/telephony/TestTelephonyConnection.java
+++ b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
@@ -21,6 +21,7 @@
import android.os.Bundle;
import android.os.PersistableBundle;
import android.telecom.PhoneAccountHandle;
+import android.telephony.TelephonyManager;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -61,6 +62,9 @@
Resources mMockResources;
@Mock
+ TelephonyManager mMockTelephonyManager;
+
+ @Mock
EmergencyNumberTracker mEmergencyNumberTracker;
private Phone mMockPhone;
@@ -84,6 +88,7 @@
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);
@@ -101,6 +106,8 @@
when(mMockPhone.getContext()).thenReturn(mMockContext);
when(mMockPhone.getCurrentSubscriberUris()).thenReturn(null);
when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockContext.getSystemService(Context.TELEPHONY_SERVICE))
+ .thenReturn(mMockTelephonyManager);
when(mMockResources.getBoolean(anyInt())).thenReturn(false);
when(mMockPhone.getDefaultPhone()).thenReturn(mMockPhone);
when(mMockPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_IMS);
@@ -169,6 +176,12 @@
// Requires ImsManager dependencies, do not implement during testing.
}
+ @Override
+ boolean isWfcEnabled(Phone phone) {
+ // Requires ImsManager dependencies, mock for test.
+ return true;
+ }
+
public int getNotifyPhoneAccountChangedCount() {
return mNotifyPhoneAccountChangedCount;
}
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/MessageTransportStateTrackerTest.java b/tests/src/com/android/services/telephony/rcs/MessageTransportStateTrackerTest.java
new file mode 100644
index 0000000..f69b9a8
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/MessageTransportStateTrackerTest.java
@@ -0,0 +1,243 @@
+/*
+ * 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.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+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.os.RemoteException;
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.SipMessage;
+import android.telephony.ims.aidl.ISipDelegate;
+import android.telephony.ims.aidl.ISipDelegateMessageCallback;
+
+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.Mock;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+@RunWith(AndroidJUnit4.class)
+public class MessageTransportStateTrackerTest 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]);
+
+ // Use for finer-grained control of when the Executor executes.
+ private static class PendingExecutor implements Executor {
+ private final ArrayList<Runnable> mPendingRunnables = new ArrayList<>();
+
+ @Override
+ public void execute(Runnable command) {
+ mPendingRunnables.add(command);
+ }
+
+ public void executePending() {
+ for (Runnable r : mPendingRunnables) {
+ r.run();
+ }
+ mPendingRunnables.clear();
+ }
+ }
+
+ @Mock private ISipDelegateMessageCallback mDelegateMessageCallback;
+ @Mock private ISipDelegate mISipDelegate;
+ @Mock private Consumer<Boolean> mMockCloseConsumer;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @SmallTest
+ @Test
+ public void testDelegateConnectionSendOutgoingMessage() throws Exception {
+ MessageTransportStateTracker tracker = new MessageTransportStateTracker(TEST_SUB_ID,
+ Runnable::run, mDelegateMessageCallback);
+
+ tracker.openTransport(mISipDelegate, Collections.emptySet());
+ 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));
+
+ tracker.close(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED);
+ tracker.getDelegateConnection().sendMessage(TEST_MESSAGE, 1 /*version*/);
+ verify(mDelegateMessageCallback).onMessageSendFailure(any(),
+ eq(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED));
+ }
+
+ @SmallTest
+ @Test
+ public void testDelegateConnectionCloseGracefully() throws Exception {
+ PendingExecutor executor = new PendingExecutor();
+ MessageTransportStateTracker tracker = new MessageTransportStateTracker(TEST_SUB_ID,
+ executor, mDelegateMessageCallback);
+
+ tracker.openTransport(mISipDelegate, Collections.emptySet());
+ tracker.getDelegateConnection().sendMessage(TEST_MESSAGE, 1 /*version*/);
+ executor.executePending();
+ verify(mISipDelegate).sendMessage(TEST_MESSAGE, 1 /*version*/);
+ verify(mDelegateMessageCallback, never()).onMessageSendFailure(any(), anyInt());
+
+ // Use PendingExecutor a little weird here, we need to queue sendMessage first, even though
+ // closeGracefully will complete partly synchronously to test that the pending message will
+ // return MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION before the scheduled
+ // graceful close operation completes.
+ tracker.getDelegateConnection().sendMessage(TEST_MESSAGE, 1 /*version*/);
+ tracker.closeGracefully(
+ SipDelegateManager.MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION,
+ SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED,
+ mMockCloseConsumer);
+ verify(mMockCloseConsumer, never()).accept(any());
+ // resolve pending close operation
+ executor.executePending();
+ verify(mDelegateMessageCallback).onMessageSendFailure(any(),
+ eq(SipDelegateManager.MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION));
+ // Still should only report one call of sendMessage from before
+ verify(mISipDelegate).sendMessage(TEST_MESSAGE, 1 /*version*/);
+ verify(mMockCloseConsumer).accept(true);
+
+ // ensure that after close operation completes, we get the correct
+ // MESSAGE_FAILURE_REASON_DELEGATE_CLOSED message.
+ tracker.getDelegateConnection().sendMessage(TEST_MESSAGE, 1 /*version*/);
+ executor.executePending();
+ verify(mDelegateMessageCallback).onMessageSendFailure(any(),
+ eq(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED));
+ // Still should only report one call of sendMessage from before
+ verify(mISipDelegate).sendMessage(TEST_MESSAGE, 1 /*version*/);
+ }
+
+ @SmallTest
+ @Test
+ public void testDelegateConnectionNotifyMessageReceived() throws Exception {
+ MessageTransportStateTracker tracker = new MessageTransportStateTracker(TEST_SUB_ID,
+ Runnable::run, mDelegateMessageCallback);
+ tracker.openTransport(mISipDelegate, Collections.emptySet());
+ tracker.getDelegateConnection().notifyMessageReceived("z9hG4bK776asdhds");
+ verify(mISipDelegate).notifyMessageReceived("z9hG4bK776asdhds");
+ }
+
+ @SmallTest
+ @Test
+ public void testDelegateConnectionNotifyMessageReceiveError() throws Exception {
+ MessageTransportStateTracker tracker = new MessageTransportStateTracker(TEST_SUB_ID,
+ Runnable::run, mDelegateMessageCallback);
+ tracker.openTransport(mISipDelegate, Collections.emptySet());
+ tracker.getDelegateConnection().notifyMessageReceiveError("z9hG4bK776asdhds",
+ SipDelegateManager.MESSAGE_FAILURE_REASON_NETWORK_NOT_AVAILABLE);
+ verify(mISipDelegate).notifyMessageReceiveError("z9hG4bK776asdhds",
+ SipDelegateManager.MESSAGE_FAILURE_REASON_NETWORK_NOT_AVAILABLE);
+ }
+
+ @SmallTest
+ @Test
+ public void testDelegateConnectionCloseSession() throws Exception {
+ MessageTransportStateTracker tracker = new MessageTransportStateTracker(TEST_SUB_ID,
+ Runnable::run, mDelegateMessageCallback);
+ tracker.openTransport(mISipDelegate, Collections.emptySet());
+ tracker.getDelegateConnection().cleanupSession("testCallId");
+ verify(mISipDelegate).cleanupSession("testCallId");
+ }
+
+ @SmallTest
+ @Test
+ public void testDelegateOnMessageReceived() throws Exception {
+ MessageTransportStateTracker tracker = new MessageTransportStateTracker(TEST_SUB_ID,
+ Runnable::run, mDelegateMessageCallback);
+ tracker.openTransport(mISipDelegate, Collections.emptySet());
+
+ 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(any(),
+ eq(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD));
+ }
+
+ @SmallTest
+ @Test
+ public void testDelegateOnMessageReceivedClosedGracefully() throws Exception {
+ PendingExecutor executor = new PendingExecutor();
+ MessageTransportStateTracker tracker = new MessageTransportStateTracker(TEST_SUB_ID,
+ executor, mDelegateMessageCallback);
+ tracker.openTransport(mISipDelegate, Collections.emptySet());
+
+ tracker.getMessageCallback().onMessageReceived(TEST_MESSAGE);
+ executor.executePending();
+ verify(mDelegateMessageCallback).onMessageReceived(TEST_MESSAGE);
+
+ tracker.getMessageCallback().onMessageReceived(TEST_MESSAGE);
+ tracker.closeGracefully(
+ SipDelegateManager.MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION,
+ SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED,
+ mMockCloseConsumer);
+ executor.executePending();
+ // Incoming SIP message should not be blocked by closeGracefully
+ verify(mDelegateMessageCallback, times(2)).onMessageReceived(TEST_MESSAGE);
+ }
+
+ @SmallTest
+ @Test
+ public void testDelegateOnMessageSent() throws Exception {
+ MessageTransportStateTracker tracker = new MessageTransportStateTracker(TEST_SUB_ID,
+ Runnable::run, mDelegateMessageCallback);
+ tracker.openTransport(mISipDelegate, Collections.emptySet());
+ tracker.getMessageCallback().onMessageSent("z9hG4bK776asdhds");
+ verify(mDelegateMessageCallback).onMessageSent("z9hG4bK776asdhds");
+ }
+
+ @SmallTest
+ @Test
+ public void testDelegateonMessageSendFailure() throws Exception {
+ MessageTransportStateTracker tracker = new MessageTransportStateTracker(TEST_SUB_ID,
+ Runnable::run, mDelegateMessageCallback);
+ tracker.openTransport(mISipDelegate, Collections.emptySet());
+ tracker.getMessageCallback().onMessageSendFailure("z9hG4bK776asdhds",
+ SipDelegateManager.MESSAGE_FAILURE_REASON_NETWORK_NOT_AVAILABLE);
+ verify(mDelegateMessageCallback).onMessageSendFailure("z9hG4bK776asdhds",
+ SipDelegateManager.MESSAGE_FAILURE_REASON_NETWORK_NOT_AVAILABLE);
+ }
+}
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..27f896b
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/SipDelegateControllerTest.java
@@ -0,0 +1,268 @@
+/*
+ * 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;
+
+@RunWith(AndroidJUnit4.class)
+public class SipDelegateControllerTest extends TelephonyTestBase {
+ private static final int TEST_SUB_ID = 1;
+
+ @Mock private ISipDelegate mMockSipDelegate;
+ @Mock private MessageTransportStateTracker 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, Collections.emptySet());
+ verify(mMockDelegateStateTracker).sipDelegateConnected(Collections.emptySet());
+ }
+
+ @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, times(2)).openTransport(mMockSipDelegate,
+ 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 DelegateRequest getBaseDelegateRequest() {
+ return new DelegateRequest(getBaseFTSet());
+ }
+
+ 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/SipTransportControllerTest.java b/tests/src/com/android/services/telephony/rcs/SipTransportControllerTest.java
new file mode 100644
index 0000000..d364fe4
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/SipTransportControllerTest.java
@@ -0,0 +1,990 @@
+/*
+ * 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.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;
+ public final ISipDelegate mMockDelegate;
+ public final IBinder mMockDelegateBinder;
+
+ SipDelegateControllerContainer(int id, String name, DelegateRequest request) {
+ delegateController = mock(SipDelegateController.class);
+ mMockDelegate = mock(ISipDelegate.class);
+ mMockDelegateBinder = mock(IBinder.class);
+ doReturn(mMockDelegateBinder).when(mMockDelegate).asBinder();
+ doReturn(name).when(delegateController).getPackageName();
+ doReturn(request).when(delegateController).getInitialRequest();
+ doReturn(mMockDelegate).when(delegateController).getSipDelegateInterface();
+ subId = id;
+ packageName = name;
+ delegateRequest = request;
+ }
+ }
+
+ @Mock private RcsFeatureManager mRcsManager;
+ @Mock private ISipTransport mSipTransport;
+ @Mock private IImsRegistration mImsRegistration;
+ @Mock private ISipDelegateConnectionStateCallback mMockStateCallback;
+ @Mock private ISipDelegateMessageCallback mMockMessageCallback;
+ @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,
+ mMockStateCallback, mMockMessageCallback);
+ 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,
+ mMockStateCallback, mMockMessageCallback);
+ 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,
+ mMockStateCallback, mMockMessageCallback);
+ 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();
+
+ SipDelegateController c = injectMockDelegateController(TEST_PACKAGE_NAME, r);
+ createDelegateAndVerify(controller, c, r, r.getFeatureTags(), Collections.emptySet(),
+ TEST_PACKAGE_NAME);
+ verifyDelegateRegistrationChangedEvent(1 /*times*/, 0 /*waitMs*/);
+ triggerFullNetworkRegistrationAndVerify(controller, c);
+ }
+
+ @SmallTest
+ @Test
+ public void basicCreateDestroy() throws Exception {
+ SipTransportController controller = setupLiveTransportController();
+
+ DelegateRequest r = getBaseDelegateRequest();
+ SipDelegateController c = injectMockDelegateController(TEST_PACKAGE_NAME, r);
+ createDelegateAndVerify(controller, c, r, r.getFeatureTags(), Collections.emptySet(),
+ TEST_PACKAGE_NAME);
+ verifyDelegateRegistrationChangedEvent(1, 0 /*throttle*/);
+
+ destroyDelegateAndVerify(controller, c, false,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ verifyDelegateRegistrationChangedEvent(2 /*times*/, 0 /*waitMs*/);
+ 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.
+ SipDelegateController c = injectMockDelegateController(TEST_PACKAGE_NAME_2, r);
+ createDelegateAndVerify(controller, c, r, Collections.emptySet(), getDeniedTags,
+ TEST_PACKAGE_NAME_2);
+ }
+
+ @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);
+ SipDelegateController c1 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ firstDelegateRequest);
+ createDelegateAndVerify(controller, c1, firstDelegateRequest, firstDelegate,
+ Collections.emptySet(), TEST_PACKAGE_NAME);
+ // 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);
+ SipDelegateController c2 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ secondDelegateRequest);
+ createDelegateAndVerify(controller, c2, secondDelegateRequest, grantedAndDenied.first,
+ grantedAndDenied.second, TEST_PACKAGE_NAME, 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);
+ SipDelegateController c1 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ firstDelegateRequest);
+ createDelegateAndVerify(controller, c1, firstDelegateRequest,
+ firstDelegateRequest.getFeatureTags(), Collections.emptySet(), TEST_PACKAGE_NAME);
+ 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.
+ SipDelegateController c2 = injectMockDelegateController(TEST_PACKAGE_NAME_2,
+ secondDelegateRequest);
+ createDelegateAndVerify(controller, c2, secondDelegateRequest, Collections.emptySet(),
+ secondDeniedTags, TEST_PACKAGE_NAME_2, 1);
+
+ // now swap the SMS role.
+ CompletableFuture<Boolean> pendingC1Change = setChangeSupportedFeatureTagsFuture(c1,
+ Collections.emptySet(), firstDeniedTags);
+ CompletableFuture<Boolean> pendingC2Change = setChangeSupportedFeatureTagsFuture(c2,
+ 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).changeSupportedFeatureTags(Collections.emptySet(), firstDeniedTags);
+ // we should not get a change for c2 until pendingC1Change completes.
+ verify(c2, 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).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);
+ SipDelegateController c1 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ firstDelegateRequest);
+ createDelegateAndVerify(controller, c1, firstDelegateRequest, firstDelegate,
+ Collections.emptySet(), TEST_PACKAGE_NAME);
+ 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);
+ SipDelegateController c2 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ secondDelegateRequest);
+ createDelegateAndVerify(controller, c2, secondDelegateRequest, grantedAndDenied.first,
+ grantedAndDenied.second, TEST_PACKAGE_NAME, 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,
+ 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).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);
+ SipDelegateController c1 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ firstDelegateRequest);
+ CompletableFuture<Boolean> pendingC1Change = createDelegate(controller, c1,
+ firstDelegateRequest, firstDelegate, Collections.emptySet(), TEST_PACKAGE_NAME);
+
+ // 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);
+ SipDelegateController c2 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ secondDelegateRequest);
+ CompletableFuture<Boolean> pendingC2Change = createDelegate(controller, c2,
+ secondDelegateRequest, grantedAndDeniedC2.first, grantedAndDeniedC2.second,
+ TEST_PACKAGE_NAME);
+
+ // 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);
+ SipDelegateController c3 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ thirdDelegateRequest);
+ CompletableFuture<Boolean> pendingC3Change = createDelegate(controller, c3,
+ thirdDelegateRequest, grantedAndDeniedC3.first, grantedAndDeniedC3.second,
+ TEST_PACKAGE_NAME);
+
+ verifyNoDelegateRegistrationChangedEvent();
+ assertTrue(scheduleDelayedWait(2 * THROTTLE_MS));
+ verifyDelegateChanged(c1, pendingC1Change, firstDelegate, Collections.emptySet(), 0);
+ verifyDelegateChanged(c2, pendingC2Change, grantedAndDeniedC2.first,
+ grantedAndDeniedC2.second, 0);
+ verifyDelegateChanged(c3, 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, thirdDelegate, Collections.emptySet());
+ CompletableFuture<Integer> pendingDestroyC1 = destroyDelegate(controller, c1,
+ false /*force*/,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ CompletableFuture<Integer> pendingDestroyC2 = destroyDelegate(controller, c2,
+ false /*force*/,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ assertTrue(scheduleDelayedWait(2 * THROTTLE_MS));
+ verifyDestroyDelegate(controller, c1, pendingDestroyC1, false /*force*/,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ verifyDestroyDelegate(controller, c2, pendingDestroyC2, false /*force*/,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+
+ // All requested features should now be granted
+ completePendingChange(pendingChangeC3, true);
+ verify(c3).changeSupportedFeatureTags(thirdDelegate, Collections.emptySet());
+ // In total reeval should have only been called twice.
+ verify(c3, 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);
+ SipDelegateController c1 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ firstDelegateRequest);
+ createDelegateAndVerify(controller, c1, firstDelegateRequest, firstDelegate,
+ Collections.emptySet(), TEST_PACKAGE_NAME);
+ verifyDelegateRegistrationChangedEvent(1 /*times*/, 0 /*waitMs*/);
+
+ CompletableFuture<Integer> pendingDestroy = setDestroyFuture(c1, true,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SUBSCRIPTION_TORN_DOWN);
+ controller.onAssociatedSubscriptionUpdated(TEST_SUB_ID + 1);
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ verifyDestroyDelegate(controller, c1, 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);
+ SipDelegateController c1 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ firstDelegateRequest);
+ createDelegateAndVerify(controller, c1, firstDelegateRequest, firstDelegate,
+ Collections.emptySet(), TEST_PACKAGE_NAME);
+
+ CompletableFuture<Integer> pendingDestroy = setDestroyFuture(c1, true,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD);
+ controller.onRcsDisconnected();
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ verifyDestroyDelegate(controller, c1, 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);
+ SipDelegateController c1 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ firstDelegateRequest);
+ createDelegateAndVerify(controller, c1, firstDelegateRequest, firstDelegate,
+ Collections.emptySet(), TEST_PACKAGE_NAME);
+
+ CompletableFuture<Integer> pendingDestroy = setDestroyFuture(c1, true,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SUBSCRIPTION_TORN_DOWN);
+ controller.onDestroy();
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ verifyDelegateDeregistrationEvent();
+ // verify change was called.
+ verify(c1).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);
+ SipDelegateController c1 = injectMockDelegateController(TEST_PACKAGE_NAME,
+ firstDelegateRequest);
+ CompletableFuture<Boolean> pendingC1Change = createDelegate(controller, c1,
+ firstDelegateRequest, firstDelegate, Collections.emptySet(), TEST_PACKAGE_NAME);
+ assertTrue(scheduleDelayedWait(2 * THROTTLE_MS));
+ verifyDelegateChanged(c1, pendingC1Change, firstDelegate, Collections.emptySet(), 0);
+
+
+ CompletableFuture<Integer> pendingDestroy = setDestroyFuture(c1, 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);
+ SipDelegateController c2 = injectMockDelegateController(TEST_SUB_ID + 1,
+ TEST_PACKAGE_NAME, secondDelegateRequest);
+ CompletableFuture<Boolean> pendingC2Change = createDelegate(controller, c2,
+ TEST_SUB_ID + 1, secondDelegateRequest, secondDelegate,
+ Collections.emptySet(), TEST_PACKAGE_NAME);
+ assertTrue(scheduleDelayedWait(THROTTLE_MS));
+
+ //trigger destroyed event
+ verifyDestroyDelegate(controller, c1, pendingDestroy, true /*force*/,
+ SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SUBSCRIPTION_TORN_DOWN);
+ assertTrue(scheduleDelayedWait(2 * THROTTLE_MS));
+ verifyDelegateChanged(c2, 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);
+ SipDelegateController sc = injectMockDelegateController(TEST_PACKAGE_NAME,
+ delegateRequest);
+ CompletableFuture<Boolean> pendingScChange = createDelegate(controller, sc,
+ delegateRequest, requestTags, Collections.emptySet(), TEST_PACKAGE_NAME);
+ allowedTags.remove(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG);
+ deniedTags.add(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG);
+
+ assertTrue(scheduleDelayedWait(2 * THROTTLE_MS));
+ verifyDelegateChanged(sc, 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);
+ SipDelegateController sc = injectMockDelegateController(TEST_PACKAGE_NAME,
+ delegateRequest);
+ CompletableFuture<Boolean> pendingScChange = createDelegate(controller, sc,
+ delegateRequest, requestTags, Collections.emptySet(), TEST_PACKAGE_NAME);
+
+ assertTrue(scheduleDelayedWait(2 * THROTTLE_MS));
+ verifyDelegateChanged(sc, 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);
+ SipDelegateController sc = injectMockDelegateController(TEST_PACKAGE_NAME,
+ delegateRequest);
+ CompletableFuture<Boolean> pendingScChange = createDelegate(controller, sc,
+ delegateRequest, requestTags, Collections.emptySet(), TEST_PACKAGE_NAME);
+
+ assertTrue(scheduleDelayedWait(2 * THROTTLE_MS));
+ verifyDelegateChanged(sc, 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,
+ SipDelegateController delegateController, DelegateRequest r, Set<String> allowedTags,
+ Set<FeatureTagState> deniedTags, String packageName,
+ int numPreviousChanges) throws ImsException {
+
+ CompletableFuture<Boolean> pendingChange = createDelegate(controller, delegateController, r,
+ allowedTags, deniedTags, packageName);
+ verifyDelegateChanged(delegateController, pendingChange, allowedTags, deniedTags,
+ numPreviousChanges);
+ }
+
+ private void createDelegateAndVerify(SipTransportController controller,
+ SipDelegateController delegateController, DelegateRequest r, Set<String> allowedTags,
+ Set<FeatureTagState> deniedTags, String packageName) throws ImsException {
+ createDelegateAndVerify(controller, delegateController, r, allowedTags, deniedTags,
+ packageName, 0);
+ }
+
+ private CompletableFuture<Boolean> createDelegate(SipTransportController controller,
+ SipDelegateController delegateController, int subId, DelegateRequest r,
+ Set<String> allowedTags, Set<FeatureTagState> deniedTags, String packageName) {
+ CompletableFuture<Boolean> pendingChange = setChangeSupportedFeatureTagsFuture(
+ delegateController, allowedTags, deniedTags);
+ try {
+ controller.createSipDelegate(subId, r, packageName, mMockStateCallback,
+ mMockMessageCallback);
+ } catch (ImsException e) {
+ fail("ImsException thrown:" + e);
+ }
+ // move to internal & schedule eval
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ // reeval
+ waitForExecutorAction(mExecutorService, TIMEOUT_MS);
+ return pendingChange;
+ }
+
+ private CompletableFuture<Boolean> createDelegate(SipTransportController controller,
+ SipDelegateController delegateController, DelegateRequest r, Set<String> allowedTags,
+ Set<FeatureTagState> deniedTags, String packageName) throws ImsException {
+ return createDelegate(controller, delegateController, TEST_SUB_ID, r, allowedTags,
+ deniedTags, packageName);
+ }
+
+ 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,
+ SipDelegateController delegateController, boolean force, int reason) {
+ CompletableFuture<Integer> pendingDestroy = destroyDelegate(controller, delegateController,
+ force, reason);
+ verifyDestroyDelegate(controller, 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(SipTransportController controller,
+ 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,
+ SipDelegateController 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,
+ SipDelegateController 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> getBaseDeniedSet() {
+ Set<FeatureTagState> deniedTags = new ArraySet<>();
+ deniedTags.add(new FeatureTagState(ImsSignallingUtils.MMTEL_TAG,
+ SipDelegateManager.DENIED_REASON_IN_USE_BY_ANOTHER_DELEGATE));
+ return deniedTags;
+ }
+
+ private Set<FeatureTagState> getDeniedTagsForReason(Set<String> deniedTags, int reason) {
+ return deniedTags.stream().map(t -> new FeatureTagState(t, reason))
+ .collect(Collectors.toSet());
+ }
+
+ private SipDelegateController injectMockDelegateController(String packageName,
+ DelegateRequest r) {
+ return injectMockDelegateController(TEST_SUB_ID, packageName, r);
+ }
+
+ private SipDelegateController injectMockDelegateController(int subId, String packageName,
+ DelegateRequest r) {
+ SipDelegateControllerContainer c = new SipDelegateControllerContainer(subId,
+ packageName, r);
+ mMockControllers.add(c);
+ return c.delegateController;
+ }
+
+ 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/UceControllerManagerTest.java b/tests/src/com/android/services/telephony/rcs/UceControllerManagerTest.java
new file mode 100644
index 0000000..82687f8
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/UceControllerManagerTest.java
@@ -0,0 +1,250 @@
+/*
+ * 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.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 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, mSubId,
+ mExecutorService);
+ manager.setUceController(mUceController);
+ return manager;
+ }
+}