[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: af44325b22 -s ours
am skip reason: contains skip directive
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Telephony/+/26667396
Change-Id: I5cde6d826fa7172c66fa64f6b4faeba8519b1d78
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index dc35c5d..3bbafc0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -41,9 +41,15 @@
"com.android.phone.common-lib",
"guava",
"PlatformProperties",
+ "modules-utils-fastxmlserializer",
"modules-utils-os",
"nist-sip",
- "service-entitlement"
+ "service-entitlement",
+ "telephony_flags_core_java_lib",
+ "android.permission.flags-aconfig-java",
+ "satellite-s2storage-ro",
+ "s2-geometry-library-java",
+ "dropbox_flags_lib",
],
srcs: [
@@ -78,6 +84,8 @@
proto: {
type: "lite",
},
+
+ generate_product_characteristics_rro: true,
}
// Allow other applications to use public constants from SlicePurchaseController
@@ -87,7 +95,7 @@
libs: [
"telephony-common",
"service-entitlement"
- ],
+ ],
}
platform_compat_config {
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8d03ed7..1190d38 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -71,6 +71,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.LOCATION_BYPASS" />
<uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
<uses-permission android:name="android.permission.BROADCAST_SMS"/>
<uses-permission android:name="android.permission.BROADCAST_WAP_PUSH"/>
@@ -137,6 +138,7 @@
<uses-permission android:name="android.permission.BIND_TELEPHONY_DATA_SERVICE" />
<uses-permission android:name="android.permission.BIND_SATELLITE_GATEWAY_SERVICE" />
<uses-permission android:name="android.permission.BIND_SATELLITE_SERVICE" />
+ <uses-permission android:name="android.permission.SATELLITE_COMMUNICATION" />
<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" />
@@ -167,9 +169,17 @@
<!-- Needed to register for UWB state changes for satellite communication -->
<uses-permission android:name="android.permission.UWB_PRIVILEGED"/>
- <permission android:name="com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"
- android:label="Access last known cell identity."
- android:protectionLevel="signature"/>
+ <!-- Needed to initiate configuration update -->
+ <uses-permission android:name="android.permission.UPDATE_CONFIG"/>
+
+ <!-- Needed to bind the domain selection service. -->
+ <uses-permission android:name="android.permission.BIND_DOMAIN_SELECTION_SERVICE" />
+
+ <!-- Needed to send safety center updates for cellular transparency features -->
+ <uses-permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE"/>
+
+ <!-- Needed because the DISPLAY_EMERGENCY_MESSAGE ConnectionEvent contains a PendingIntent to activate the satellite feature. -->
+ <uses-permission android:name="com.google.android.apps.stargate.permission.SEND_EMERGENCY_INTENTS"/>
<application android:name="PhoneApp"
android:persistent="true"
@@ -279,7 +289,7 @@
<activity android:name="GsmUmtsCallForwardOptions"
android:label="@string/labelCF"
android:configChanges="orientation|screenSize|keyboardHidden"
- android:exported="true"
+ android:exported="false"
android:theme="@style/CallSettingsWithoutDividerTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -289,7 +299,7 @@
<activity android:name="CdmaCallForwardOptions"
android:label="@string/labelCF"
android:configChanges="orientation|screenSize|keyboardHidden"
- android:exported="true"
+ android:exported="false"
android:theme="@style/CallSettingsWithoutDividerTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -299,7 +309,7 @@
<activity android:name="GsmUmtsCallBarringOptions"
android:label="@string/labelCallBarring"
android:configChanges="orientation|screenSize|keyboardHidden"
- android:exported="true"
+ android:exported="false"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -309,7 +319,7 @@
<activity android:name="GsmUmtsAdditionalCallOptions"
android:label="@string/labelGSMMore"
android:configChanges="orientation|screenSize|keyboardHidden"
- android:exported="true"
+ android:exported="false"
android:theme="@style/CallSettingsWithoutDividerTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -570,10 +580,29 @@
</intent-filter>
</receiver>
+ <!-- Update configuration data file -->
+ <receiver android:name="com.android.internal.telephony.configupdate.TelephonyConfigUpdateInstallReceiver"
+ android:exported="true"
+ android:permission="android.permission.UPDATE_CONFIG">
+ <intent-filter>
+ <action android:name="android.os.action.UPDATE_CONFIG" />
+ <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+ </intent-filter>
+ </receiver>
+
<receiver
android:name="com.android.internal.telephony.uicc.ShowInstallAppNotificationReceiver"
android:exported="false"/>
+ <receiver
+ android:name=".security.SafetySourceReceiver"
+ android:exported="false"
+ androidprv:systemUserOnly="true">
+ <intent-filter>
+ <action android:name="android.safetycenter.action.REFRESH_SAFETY_SOURCES"/>
+ </intent-filter>
+ </receiver>
+
<activity
android:name="com.android.phone.settings.PickSmsSubscriptionActivity"
android:exported="false"
@@ -619,17 +648,6 @@
</intent-filter>
</activity>
- <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" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.VOICE_LAUNCH" />
- </intent-filter>
- </activity>
-
<provider
android:name="ServiceStateProvider"
android:authorities="service-state"
@@ -637,5 +655,13 @@
android:multiprocess="false"
android:singleUser="true"
android:writePermission="android.permission.MODIFY_PHONE_STATE"/>
+
+ <service android:name="com.android.services.telephony.domainselection.TelephonyDomainSelectionService"
+ android:exported="true"
+ android:permission="android.permission.BIND_DOMAIN_SELECTION_SERVICE">
+ <intent-filter>
+ <action android:name="android.telephony.DomainSelectionService"/>
+ </intent-filter>
+ </service>
</application>
</manifest>
diff --git a/OWNERS b/OWNERS
index 0fed2f0..96033ab 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,17 +1,5 @@
-breadley@google.com
-fionaxu@google.com
-jackyu@google.com
-rgreenwalt@google.com
-tgunn@google.com
-sarahchin@google.com
-xiaotonj@google.com
-huiwang@google.com
-jayachandranc@google.com
-chinmayd@google.com
-amruthr@google.com
-sasindran@google.com
-tjstuart@google.com
-pmadapurmath@google.com
-grantmenke@google.com
+include platform/frameworks/opt/telephony:/OWNERS
per-file *SimPhonebookProvider* = file:platform/packages/apps/Contacts:/OWNERS
+
+per-file config.xml=hwangoo@google.com,forestchoi@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,radhikaagrawal@google.com,jdyou@google.com
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 3831b6b..04a8efb 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -9,7 +9,8 @@
]
},
{
- "name": "CarrierAppIntegrationTestCases"
+ "name": "CarrierAppIntegrationTestCases",
+ "keywords": ["internal"]
},
{
"name": "CtsSimRestrictedApisTestCases",
diff --git a/assets/CarrierRestrictionOperatorDetails.json b/assets/CarrierRestrictionOperatorDetails.json
index f3da100..596ab75 100644
--- a/assets/CarrierRestrictionOperatorDetails.json
+++ b/assets/CarrierRestrictionOperatorDetails.json
@@ -1,5 +1,10 @@
{
"_comment": "Operator should register with its application package name, carrierId and all the corresponding SHAIDs",
"_comment": "Example format :: << \"packageName\" : {\"carrierId\":<int>, \"callerSHA1Id\":[<SHAID1>, <SHAID2>]} >>",
- "com.vzw.hss.myverizon":{"carrierId":1839,"callerSHA1Id":["C58EE7871896786F8BF70EBDB137DE10074043E9","AE23A03436DF07B0CD70FE881CDA2EC1D21215D7B7B0CC68E67B67F5DF89526A"]}
+ "com.vzw.hss.myverizon":{"carrierIds":[1839],"callerSHA1Id":["C58EE7871896786F8BF70EBDB137DE10074043E9","AE23A03436DF07B0CD70FE881CDA2EC1D21215D7B7B0CC68E67B67F5DF89526A"]},
+ "com.google.android.apps.tycho":{"carrierIds":[1989],"callerSHA1Id":["B9CFCE1C47A6AC713442718F15EF55B00B3A6D1A6D48CB46249FA8EB51465350","4C36AF4A5BDAD97C1F3D8B283416D244496C2AC5EAFE8226079EF6F676FD1859"]},
+ "com.comcast.mobile.mxs":{"carrierIds": [2032, 2532, 2556],"callerSHA1Id":["CB16D6A0BEA2F4ED808107408104B2DB3258DF52","914C26403B57D2D482359FC235CC825AD00D52B0121C18EF2B2B9D4DDA4B8996"]},
+ "com.xfinity.digitalhome": {"carrierIds": [2032],"callerSHA1Id":["be268ab5b6126ce1abd6c3131b33b6d02e44d717","31b4c17315c2269040d535f7b6a79cf4d11517c664d9de8f1ddf4f8a785aad47"]},
+ "com.xfinity.digitalhome.debug":{"carrierIds": [2032],"callerSHA1Id":["611f61225138a566428f34e927ad5f2a02fd151e","c9133e8168f97573c8c567f46777dff74ade0c015ecf2c5e91be3e4e76ddcae2"]},
+ "com.xfinity.dh.xm.app": {"carrierIds": [2032],"callerSHA1Id":["611f61225138a566428f34e927ad5f2a02fd151e","c9133e8168f97573c8c567f46777dff74ade0c015ecf2c5e91be3e4e76ddcae2"]}
}
\ No newline at end of file
diff --git a/assets/google_us_san_sat_s2.dat b/assets/google_us_san_sat_s2.dat
new file mode 100644
index 0000000..60b00df
--- /dev/null
+++ b/assets/google_us_san_sat_s2.dat
Binary files differ
diff --git a/ecc/input/OWNERS b/ecc/input/OWNERS
index 5685875..2ed2f3f 100644
--- a/ecc/input/OWNERS
+++ b/ecc/input/OWNERS
@@ -1,4 +1,5 @@
set noparent
tgunn@google.com
-chinmayd@google.com
\ No newline at end of file
+breadley@google.com
+rgreenwalt@google.com
\ No newline at end of file
diff --git a/ecc/input/eccdata.txt b/ecc/input/eccdata.txt
index 7252480..c4edc9e 100644
--- a/ecc/input/eccdata.txt
+++ b/ecc/input/eccdata.txt
@@ -906,6 +906,16 @@
types: TYPE_UNSPECIFIED
routing: NORMAL
}
+ eccs {
+ phone_number: "028"
+ types: TYPE_UNSPECIFIED
+ routing: NORMAL
+ }
+ eccs {
+ phone_number: "116000"
+ types: TYPE_UNSPECIFIED
+ routing: NORMAL
+ }
ecc_fallback: "112"
}
countries {
@@ -1372,6 +1382,13 @@
types: AMBULANCE
types: FIRE
}
+ eccs {
+ phone_number: "100"
+ types: POLICE
+ types: AMBULANCE
+ types: FIRE
+ routing: NORMAL
+ }
ecc_fallback: "112"
}
countries {
@@ -2342,77 +2359,6 @@
types: POLICE
types: AMBULANCE
types: FIRE
- routing: EMERGENCY
- }
- eccs {
- phone_number: "984"
- types: TYPE_UNSPECIFIED
- routing: EMERGENCY
- }
- eccs {
- phone_number: "985"
- types: MOUNTAIN_RESCUE
- routing: EMERGENCY
- }
- eccs {
- phone_number: "986"
- types: POLICE
- routing: EMERGENCY
- }
- eccs {
- phone_number: "997"
- types: POLICE
- routing: EMERGENCY
- }
- eccs {
- phone_number: "998"
- types: FIRE
- routing: EMERGENCY
- }
- eccs {
- phone_number: "999"
- types: AMBULANCE
- routing: EMERGENCY
- }
- eccs {
- phone_number: "991"
- types: TYPE_UNSPECIFIED
- routing: EMERGENCY
- }
- eccs {
- phone_number: "992"
- types: TYPE_UNSPECIFIED
- routing: EMERGENCY
- }
- eccs {
- phone_number: "993"
- types: TYPE_UNSPECIFIED
- routing: EMERGENCY
- }
- eccs {
- phone_number: "994"
- types: TYPE_UNSPECIFIED
- routing: EMERGENCY
- }
- eccs {
- phone_number: "995"
- types: TYPE_UNSPECIFIED
- routing: EMERGENCY
- }
- eccs {
- phone_number: "996"
- types: TYPE_UNSPECIFIED
- routing: EMERGENCY
- }
- eccs {
- phone_number: "987"
- types: TYPE_UNSPECIFIED
- routing: EMERGENCY
- }
- eccs {
- phone_number: "989"
- types: TYPE_UNSPECIFIED
- routing: EMERGENCY
}
ecc_fallback: "112"
}
diff --git a/ecc/output/OWNERS b/ecc/output/OWNERS
index 5685875..2ed2f3f 100644
--- a/ecc/output/OWNERS
+++ b/ecc/output/OWNERS
@@ -1,4 +1,5 @@
set noparent
tgunn@google.com
-chinmayd@google.com
\ No newline at end of file
+breadley@google.com
+rgreenwalt@google.com
\ No newline at end of file
diff --git a/ecc/output/eccdata b/ecc/output/eccdata
index fc86961..482ed79 100644
--- a/ecc/output/eccdata
+++ b/ecc/output/eccdata
Binary files differ
diff --git a/res/layout/radio_info.xml b/res/layout/radio_info.xml
index ffe63de..1f7b53d 100644
--- a/res/layout/radio_info.xml
+++ b/res/layout/radio_info.xml
@@ -185,6 +185,12 @@
<TextView android:id="@+id/network_slicing_config" style="@style/info_value" />
</LinearLayout>
+ <!-- eUICC info -->
+ <LinearLayout style="@style/RadioInfo_entry_layout" android:orientation="horizontal">
+ <TextView android:id="@+id/euicc_info_label" android:text="@string/radio_info_euicc_info" style="@style/info_label" />
+ <TextView android:id="@+id/euicc_info" style="@style/info_value" />
+ </LinearLayout>
+
<!-- Horizontal Rule -->
<View
android:layout_width="fill_parent"
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index c451e65..f9e0ed3 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Verlaat noodterugbelmodus om \'n nienoodoproep te maak."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Nie geregistreer op netwerk nie."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobiele netwerk nie beskikbaar nie."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Selnetwerk is nie beskikbaar nie.\n\nVerbind met ’n draadlose netwerk om ’n oproep te maak.\n\n2G is gedeaktiveer op hierdie toestel, wat dalk jou konnektiwiteit kan beïnvloed. Gaan na Instellings en aktiveer Laat 2G toe om voort te gaan."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobiele netwerk is nie beskikbaar nie. Koppel aan \'n draadlose netwerk om \'n oproep te maak."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Selnetwerk is nie beskikbaar nie.\n\nVerbind met ’n draadlose netwerk om ’n oproep te maak.\n\n2G is gedeaktiveer op hierdie toestel, wat jou konnektiwiteit kan beïnvloed. Gaan na Instellings en aktiveer Laat 2G toe om voort te gaan."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Voer \'n geldige nommer in om \'n oproep te maak."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Oproep het misluk."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Oproep kan nie op die oomblik bygevoeg word nie. Jy kan probeer in verbinding tree deur \'n boodskap te stuur."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Sub-ID van verstekdata-SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Aflaaibandwydte (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Oplaaibandwydte (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Fisieke LTE-kanaalopstelling:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Fisieke kanaalopstellings:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Herlaaikoers van selinligting:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Alle selmetingsinligting:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Datadiens:"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 112441b..8566882 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"የተንቀሳቃሽ ስልክ አውታረ መረብ አይገኝም።\n\nጥሪ ለማድረግ ከሽቦ አልባ አውታር ጋር ያገናኙ።\n\n2G በዚህ መሣሪያ ላይ ተሰናክሏል፣ ይህም ግንኙነትዎ ላይ ተጽዕኖ እያሳደረ ይሆናል። ወደ ቅንብሮች ይሂዱ እና ለመቀጠል 2G ፍቀድ የሚለውን ያንቁ።"</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"የተንቀሳቃሽ ስልክ አውታረ መረብ አይገኝም። ጥሪ ለማድረግ ከሽቦ አልባ አውታረ መረብ ጋር ያገናኙ።"</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"የተንቀሳቃሽ ስልክ አውታረ መረብ አይገኝም።\n\nጥሪ ለማድረግ ከሽቦ አልባ አውታር ጋር ያገናኙ።\n\n2G እዚህ መሣሪያ ላይ ተሰናክሏል፣ ይህም ግንኙነትዎ ላይ ተጽዕኖ እያደረገ ይሆናል። ወደ ቅንብሮች ይሂዱ እና ለመቀጠል 2G ፍቀድ የሚለውን ያንቁ።"</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"የነባሪ ውሂብ ሲም SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL መተላለፊያ ይዘት (ኪቢ/ሴ)፡"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"የUL መተላለፊያ ይዘት (ኪቢ/ሴ)፡"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"የLTE አካላዊ ሰርጥ ውቅረት:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"የአካላዊ ሰርጥ ውቅረቶች፦"</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>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 986e6b6..ab24fd2 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"شبكة الجوّال غير متوفّرة.\n\nيُرجى الاتصال بشبكة لاسلكية لإجراء مكالمة.\n\nتم إيقاف شبكة الجيل الثاني على هذا الجهاز، ما قد يؤثر في إمكانية الاتصال بالإنترنت. انتقِل إلى \"الإعدادات\" وفعِّل خيار \"السماح بشبكة الجيل الثاني\" للمتابعة."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"شبكة الجوّال ليست متوفرة. اتصل بشبكة لاسلكية لإجراء مكالمة."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"شبكة الجوّال غير متوفّرة.\n\nيُرجى الاتصال بشبكة لاسلكية لإجراء مكالمة.\n\nتم إيقاف شبكة الجيل الثاني على هذا الجهاز، ما قد يؤثر في إمكانية الاتصال بالإنترنت. انتقِل إلى \"الإعدادات\" وفعِّل خيار \"السماح بشبكة الجيل الثاني\" للمتابعة."</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"المعرّف الفرعي لشريحة SIM التلقائية للبيانات:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"معدّل نقل بيانات DL (كيلوبت في الثانية):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"معدّل نقل بيانات UL (كيلوبت في الثانية):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"ضبط قناة LTE Physical:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"إعدادات القنوات:"</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>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 87ccace..d1254d5 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"ম’বাইল নেটৱৰ্ক উপলব্ধ নহয়।\n\nকল কৰিবলৈ এটা ৱায়াৰলেছ নেটৱৰ্কৰ সৈতে সংযোগ কৰক।\n\nএই ডিভাইচটোত 2G অক্ষম কৰা হৈছে, যিটোৱে আপোনাৰ সংযোগত প্ৰভাৱ পেলাব পাৰে। অব্যাহত ৰাখিবলৈ ছেটিঙলৈ গৈ 2Gক অনুমতি দিয়ক সক্ষম কৰক।"</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"ম’বাইল নেটৱৰ্ক উপলব্ধ নহয়। কল কৰিবৰ কাৰণে কোনো বেতাঁৰ নেটৱৰ্কৰ সৈতে সংযোগ কৰক।"</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"ম’বাইল নেটৱৰ্ক উপলব্ধ নহয়।\n\nকল কৰিবলৈ এটা বেতাঁৰ নেটৱৰ্কৰ সৈতে সংযোগ কৰক।\n\nএই ডিভাইচটোত 2G অক্ষম কৰা হৈছে, যিটোৱে আপোনাৰ সংযোগত প্ৰভাৱ পেলাব পাৰে। অব্যাহত ৰাখিবলৈ ছেটিঙলৈ গৈ 2Gক অনুমতি দিয়ক সক্ষম কৰক।"</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ডিফ’ল্ট ডেটা ছিমৰ ছাবআইডি:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL বেণ্ডৱিথ (কেবিপিএছ):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL বেণ্ডৱিথ (কেবিপিএছ):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ফিজিকেল চেনেলৰ কনফিগাৰেশ্বন:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ফিজিকেল চেনেল কনফিগাৰেশ্বন:"</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>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index b31c670..c8d7f42 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Qeyri-fövqəladə zəng etmək üçün fövqəladə zəng rejimindən çıxın."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Şəbəkədə qeydə alınmayıb."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobil şəbəkə əlçatımlı deyil."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobil şəbəkə əlçatan deyil.\n\nZəng etmək üçün simsiz şəbəkəyə qoşulun.\n\nBu cihazda 2G deaktiv edilib, bu da bağlantınıza təsir edə bilər. Davam etmək üçün Ayarlara keçib \"2G-yə icazə verin\" seçimini aktiv edin."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobil şəbəkə əlçatmazdır. Zəng etmək üçün Wi-Fi şəbəkəsinə qoşulun."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobil şəbəkə əlçatan deyil.\n\nZəng etmək üçün simsiz şəbəkəyə qoşulun.\n\nBu cihazda 2G deaktiv edilib, bu da bağlantınıza təsir edə bilər. Davam etmək üçün Ayarlara keçib \"2G-yə icazə verin\" seçimini aktiv edin."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Zəngi yerləşdirmək üçün düzgün nömrə daxil edin."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Zəng alınmadı."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Hazırda zəngi əlavə etmək mümkün deyil. Mesaj göndərərək təkrar əlaqə saxlaya bilərsiniz."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Defolt data SIM üçün alt Id:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL Buraxılışı (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL Buraxılışı (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE Fiziki Kanal Konfiqurasiyası:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Fiziki Kanal Konfiqurasiyaları:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Mobil məlumatın yenilənmə göstəricisi:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Operatorun bütün mobil ölçü məlumatı:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Data Xidməti:"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 3474c39..9a19c63 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Izađite iz režima hitnog povratnog poziva da biste uputili poziv koji nije hitan."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Nije registrovano na mreži."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobilna mreža nije dostupna."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobilna mreža nije dostupna.\n\nPovežite se na bežičnu mrežu da biste uputili poziv.\n\n2G je onemogućen na ovom uređaju, što može da utiče na povezivanje. Idite u Podešavanja i omogućite opciju Dozvoli 2G da biste nastavili."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobilna mreža nije dostupna. Povežite se na bežičnu da biste uputili poziv."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobilna mreža nije dostupna.\n\nPovežite se na bežičnu mrežu da biste uputili poziv.\n\n2G je onemogućen na ovom uređaju, što može da utiče na povezivanje. Idite u Podešavanja i omogućite opciju Dozvoli 2G da biste nastavili."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Da biste uputili poziv, unesite važeći broj."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Poziv nije uspeo."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Dodavanje poziva trenutno nije moguće. Možete da pokušate da ostvarite kontakt pomoću poruke."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubID podrazumevanog SIM-a za podatke:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL propusni opseg (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL propusni opseg (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfiguracija LTE fizičkog kanala:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfiguracije fizičkog kanala:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Učestalost osvežavanja informacija o predajniku:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Sve informacije o merenju za predajnik:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Usluga prenosa podataka:"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 33e26a1..031059c 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"Мабільная сетка недаступная.\n\nКаб зрабіць выклік, падключыцеся да бесправадной сеткі.\n\nНа гэтай прыладзе адключана перадача даных у рэжыме 2G, што можа ўплываць на магчымасць падключэння. Каб працягнуць, перайдзіце ў налады і ўключыце параметр \"Дазволіць 2G\"."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Мабільная сетка недаступная. Падлучыцеся да бесправадной сеткі, каб зрабіць выклік."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Мабільная сетка недаступная.\n\nКаб зрабіць выклік, падключыцеся да бесправадной сеткі.\n\nНа гэтай прыладзе адключана перадача даных у рэжыме 2G, што можа ўплываць на магчымасць падключэння. Каб працягнуць, перайдзіце ў налады і ўключыце параметр \"Дазволіць 2G\"."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Каб зрабіць выклік, увядзіце сапраўдны нумар."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Збой выклiку."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Немагчыма зараз дадаць выклік. Можна выйсці на сувязь, адправіўшы паведамленне."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubID стандартнай SIM-карты для перадачы даных:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Прапускная здольнасць канала спампоўвання (кбіт/с):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Прапускная здольнасць канала запампоўвання (кбіт/с):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Канфігурацыя фізічнага канала LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Канфігурацыі фізічнага канала:"</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>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index cf06d50..c7d383f 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"Не е налице мобилна мрежа.\n\nСвържете се с безжична мрежа, за да извършите обаждане.\n\nУслугата 2G е деактивирана на това устройство, което може да влияе на свързаността ви. За да продължите, отворете настройките и активирайте „Разрешаване на 2G“."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Няма мобилна мрежа. Свържете се с безжична, за да се обадите."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Не е налице мобилна мрежа.\n\nСвържете се с безжична мрежа, за да извършите обаждане.\n\nУслугата 2G е деактивирана на това устройство, което може да влияе на свързаността ви. За да продължите, отворете настройките и активирайте „Разрешаване на 2G“."</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Идентификационен подномер на SIM картата за данни по подразбиране:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Пропускателна способност при изтегляне (кб/сек):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Пропускателна способност при качване (кб/сек):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Конфигурация на физическия канал на LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Конфигурации на физическия канал:"</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>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index b519003..52548a1 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"মোবাইল নেটওয়ার্ক উপলভ্য নেই।\n\nকল করতে হলে ওয়্যারলেস নেটওয়ার্কের সাথে কানেক্ট করুন।\n\nএই ডিভাইসে 2G পরিষেবা বন্ধ করে দেওয়া হয়েছে, যার ফলে হয়ত আপনার কানেক্টিভিটি প্রভাবিত হচ্ছে। সেটিংসে যান এবং চালিয়ে যেতে \'2G পরিষেবার অনুমতি দিন\' বিকল্পটি চালু করুন।"</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"মোবাইল নেটওয়ার্ক উপলব্ধ নেই৷ একটি কল করতে কোনো ওয়্যারলেস নেটওয়ার্কে সংযোগ করুন৷"</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"মোবাইল নেটওয়ার্ক উপলভ্য নেই।\n\nকল করতে হলে ওয়্যারলেস নেটওয়ার্কের সাথে কানেক্ট করুন।\n\nএই ডিভাইসে 2G পরিষেবা বন্ধ করে দেওয়া হয়েছে, যার ফলে হয়ত আপনার কানেক্টিভিটি প্রভাবিত হচ্ছে। সেটিংসে যান এবং চালিয়ে যেতে \'2G পরিষেবার অনুমতি দিন\' বিকল্পটি চালু করুন।"</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ডিফল্ট ডেটা সিমের SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"ডিএল ব্যান্ডউইথ (কেবিপিএস):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"ইউএল ব্যান্ডউইথ (কেবিপিএস):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ফিজিক্যাল চ্যানেল কনফিগারেশন:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ফিজিক্যাল চ্যানেল কনফিগারেশন:"</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>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 5da6a69..243e162 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"Mobilna mreža nije dostupna.\n\nPovežite se s bežičnom mrežom da uputite poziv.\n\n2G je onemogućen na uređaju, što može uticati na povezivost. Da nastavite, idite u Postavke i omogućite Dozvoli 2G."</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_2g_user" msgid="8218768986365299663">"Mobilna mreža nije dostupna.\n\nPovežite se s bežičnom mrežom da uputite poziv.\n\n2G je onemogućen na uređaju, što može uticati na povezivost. Da nastavite, idite u Postavke i omogućite Dozvoli 2G."</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Pomoćni ID za zadani SIM za prijenos podataka:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL propusnost (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL propusnost (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfiguracija LTE fizičkog kanala:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfiguracije fizičkih kanala:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Interval osvježavanja informacija o ćeliji:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Sve informacije o mjerenju ćelije:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Prijenos podataka:"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index cf1c3a8..d5373f2 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Surt del mode de devolució de trucada d\'emergència per fer un altre tipus de trucada."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"No registrat a la xarxa."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"La xarxa mòbil no està disponible."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"La xarxa mòbil no està disponible.\n\nConnecta\'t a una xarxa sense fil per fer una trucada.\n\nEl 2G està desactivat en aquest dispositiu, cosa que pot afectar la connectivitat. Ves a Configuració i activa Permet 2G per continuar."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"La xarxa mòbil no està disponible. Per fer una trucada, connecta\'t a una xarxa sense fil."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"La xarxa mòbil no està disponible.\n\nConnecta\'t a una xarxa sense fil per fer una trucada.\n\nEl 2G està desactivat en aquest dispositiu, cosa que pot afectar la connectivitat. Ves a Configuració i activa Permet 2G per continuar."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Per realitzar una trucada, introdueix un número vàlid."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"No s\'ha pogut fer la trucada."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"En aquest moment no es pot afegir la trucada. Prova d\'enviar un missatge."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Subidentificador de la SIM de dades predeterminada:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Amplada de banda de baixada (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Amplada de banda de pujada (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuració del canal físic de LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configuracions del canal físic:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Freqüència d\'actualització de la informació del mòbil:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Tota la informació de mesures del mòbil:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Servei de dades:"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 520c0de..d4013c8 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Chcete-li uskutečnit běžný hovor, opusťte režim tísňového volání."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Přihlášení k síti nebylo úspěšné."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobilní síť je nedostupná."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobilní síť není k dispozici.\n\nAbyste mohli uskutečnit hovor, připojte se k bezdrátové síti.\n\nV tomto zařízení není aktivní připojení 2G, což může ovlivňovat možnosti připojení. Přejděte do nastavení a zapněte volbu Povolit 2G."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobilní síť není k dispozici. Pokud chcete provést hovor, připojte se k bezdrátové síti."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobilní síť není k dispozici.\n\nAbyste mohli uskutečnit hovor, připojte se k bezdrátové síti.\n\nV tomto zařízení není aktivní připojení 2G, což může ovlivňovat možnosti připojení. Přejděte do nastavení a zapněte volbu Povolit 2G."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Chcete-li uskutečnit hovor, zadejte platné telefonní číslo."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Volání se nezdařilo."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Hovor momentálně není možné přidat. Můžete místo toho zkusit poslat zprávu."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId výchozí datové SIM karty:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Rychlost stahování (kB/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Rychlost nahrávání (kB/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfigurace fyzického kanálu LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfigurace fyzického kanálu:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Interval obnovení informací o mobilní síti:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Všechny údaje o měření mobilní sítě:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Datová služba:"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 56d09f4..7699384 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Afslut nødtilbagekaldstilstand for at foretage et almindeligt opkald."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Ikke registreret på netværk."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobilnetværket er ikke tilgængeligt."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobilnetværk er ikke tilgængeligt.\n\nOpret forbindelse til et trådløst netværk for at foretage et opkald.\n\n2G er deaktiveret på denne enhed, hvilket kan påvirke dine forbindelsesmuligheder. Gå til Indstillinger, og aktivér Tillad 2G for at fortsætte."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobilnetværk er ikke tilgængeligt. Opret forbindelse til et trådløst netværk for at foretage et opkald."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobilnetværk er ikke tilgængeligt.\n\nOpret forbindelse til et trådløst netværk for at foretage et opkald.\n\n2G er deaktiveret på denne enhed, hvilket kan påvirke dine forbindelsesmuligheder. Gå til Indstillinger, og aktivér Tillad 2G for at fortsætte."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Angiv et gyldigt nummer for at foretage et opkald."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Opkald mislykkedes."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Opkaldet kan ikke tilføjes lige nu. Du kan prøve at sende en besked i stedet."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Under-id for standard-SIM-kort til data:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Båndbredde til download (kB/sek.):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Båndbredde til upload (kB/sek.):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Fysisk kanalkonfiguration for LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Fysiske kanalkonfigurationer:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Opdateringsfrekvens for celleoplysninger:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Alle oplysninger om cellemåling:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Datatjeneste:"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index db3d447..b465b8e 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Beende den Notfallrückrufmodus, um einen Anruf zu tätigen, bei dem es sich nicht um einen Notfall handelt."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Nicht in Netzwerk registriert."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobilfunknetz ist nicht verfügbar."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobilfunknetz nicht verfügbar.\n\nStelle eine Verbindung zu einem drahtlosen Netzwerk her, um zu telefonieren.\n\n2G ist auf diesem Gerät deaktiviert. Dies kann deine Verbindung beeinträchtigen. Gehe zu den Einstellungen und aktiviere „2G zulassen“, um fortzufahren."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Es ist kein Mobilfunknetz verfügbar. Stelle zum Telefonieren eine WLAN-Verbindung her."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobilfunknetz nicht verfügbar.\n\nStelle eine Verbindung zu einem drahtlosen Netzwerk her, um zu telefonieren.\n\n2G ist auf diesem Gerät deaktiviert. Dies kann deine Verbindung beeinträchtigen. Gehe zu den Einstellungen und aktiviere „2G zulassen“, um fortzufahren."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Gib eine gültige Nummer ein."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Fehler beim Anruf."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Der Anruf kann momentan nicht hinzugefügt werden. Versuche stattdessen, eine Nachricht zu senden."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Unter-ID der standardmäßigen Daten-SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL-Bandbreite (kbit/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL-Bandbreite (kbit/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfiguration des physischen LTE-Kanals:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfiguration des physischen Kanals:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Aktualisierungsrate der Mobiltelefoninformationen:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Alle Informationen zu Zellenmesswerten:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Datendienst:"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index e2c6854..54b2ad3 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"Το δίκτυο κινητής τηλεφωνίας δεν είναι διαθέσιμο.\n\nΣυνδεθείτε σε ένα ασύρματο δίκτυο για να πραγματοποιήσετε μια κλήση.\n\nΤο 2G είναι απενεργοποιημένο σε αυτή τη συσκευή και αυτό ενδέχεται να επηρεάζει τη σύνδεσή σας. Μεταβείτε στις Ρυθμίσεις και ενεργοποιήστε το στοιχείο Να επιτρέπεται το 2G για να συνεχίσετε."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Το δίκτυο κινητής τηλεφωνίας δεν είναι διαθέσιμο. Συνδεθείτε σε ένα ασύρματο δίκτυο για να πραγματοποιήσετε μια κλήση."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Το δίκτυο κινητής τηλεφωνίας δεν είναι διαθέσιμο.\n\nΣυνδεθείτε σε ένα ασύρματο δίκτυο για να πραγματοποιήσετε μια κλήση.\n\nΤο δίκτυο 2G είναι απενεργοποιημένο σε αυτή τη συσκευή και αυτό ενδέχεται να επηρεάζει τη σύνδεσή σας. Μεταβείτε στις Ρυθμίσεις και ενεργοποιήστε το στοιχείο Να επιτρέπεται το 2G για να συνεχίσετε."</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId προεπιλεγμένης SIM δεδομένων:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Εύρος ζώνης DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Εύρος ζώνης UL (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Διαμόρφωση φυσικού καναλιού LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Διαμορφώσεις φυσικού καναλιού:"</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>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 2d81593..8c8cdae 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -542,7 +542,9 @@
<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>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobile network not available.\n\nConnect to a wireless network to make a call.\n\n2G is disabled on this device, which may be impacting your connectivity. Go to Settings and enable Allow 2G to continue."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobile network isn\'t available. Connect to a wireless network to make a call."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobile network is not available.\n\nConnect to a wireless network to make a call.\n\n2 G is disabled on this device, which may be impacting your connectivity. Go to Settings and enable Allow 2 G to continue."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"To place a call, enter a valid number."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Call failed."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Call cannot be added at this time. You can try to get in touch by sending a message."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId of default data SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL bandwidth (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL bandwidth (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE physical channel configuration:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Physical channel configurations:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Mobile info refresh rate:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"All mobile measurement info:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Data service:"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index e7a6f1b..9eb9192 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -542,7 +542,9 @@
<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>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobile network not available.\n\nConnect to a wireless network to make a call.\n\n2G is disabled on this device, which may be impacting your connectivity. Go to Settings and enable Allow 2G to continue."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobile network is not available. Connect to a wireless network to make a call."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobile network is not available.\n\nConnect to a wireless network to make a call.\n\n2G is disabled on this device, which may be impacting your connectivity. Go to Settings and enable Allow 2G to continue."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"To place a call, enter a valid number."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Call failed."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Call cannot be added at this time. You can try to reach out by sending a message."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId of default data SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL Bandwidth (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL Bandwidth (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE Physical Channel Configuration:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Physical Channel Configurations:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Cell Info Refresh Rate:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"All Cell Measurement Info:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Data Service:"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 2d81593..8c8cdae 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -542,7 +542,9 @@
<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>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobile network not available.\n\nConnect to a wireless network to make a call.\n\n2G is disabled on this device, which may be impacting your connectivity. Go to Settings and enable Allow 2G to continue."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobile network isn\'t available. Connect to a wireless network to make a call."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobile network is not available.\n\nConnect to a wireless network to make a call.\n\n2 G is disabled on this device, which may be impacting your connectivity. Go to Settings and enable Allow 2 G to continue."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"To place a call, enter a valid number."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Call failed."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Call cannot be added at this time. You can try to get in touch by sending a message."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId of default data SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL bandwidth (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL bandwidth (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE physical channel configuration:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Physical channel configurations:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Mobile info refresh rate:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"All mobile measurement info:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Data service:"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 2d81593..8c8cdae 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -542,7 +542,9 @@
<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>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobile network not available.\n\nConnect to a wireless network to make a call.\n\n2G is disabled on this device, which may be impacting your connectivity. Go to Settings and enable Allow 2G to continue."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobile network isn\'t available. Connect to a wireless network to make a call."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobile network is not available.\n\nConnect to a wireless network to make a call.\n\n2 G is disabled on this device, which may be impacting your connectivity. Go to Settings and enable Allow 2 G to continue."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"To place a call, enter a valid number."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Call failed."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Call cannot be added at this time. You can try to get in touch by sending a message."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId of default data SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL bandwidth (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL bandwidth (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE physical channel configuration:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Physical channel configurations:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Mobile info refresh rate:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"All mobile measurement info:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Data service:"</string>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 319986a..ba58d76 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -542,7 +542,9 @@
<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>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobile network not available.\n\nConnect to a wireless network to make a call.\n\n2G is disabled on this device, which may be impacting your connectivity. Go to Settings and enable Allow 2G to continue."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobile network is not available. Connect to a wireless network to make a call."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobile network is not available.\n\nConnect to a wireless network to make a call.\n\n2G is disabled on this device, which may be impacting your connectivity. Go to Settings and enable Allow 2G to continue."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"To place a call, enter a valid number."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Call failed."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Call cannot be added at this time. You can try to reach out by sending a message."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId of default data SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL Bandwidth (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL Bandwidth (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE Physical Channel Configuration:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Physical Channel Configurations:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Cell Info Refresh Rate:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"All Cell Measurement Info:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Data Service:"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index aa4aab1..a689a5f 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Para realizar una llamada que no sea de emergencia, sal del modo de devolución de llamada de emergencia."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"No registrado en la red."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"La red móvil no está disponible."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"La red móvil no está disponible.\n\nConéctate a una red inalámbrica para realizar una llamada.\n\nLa conexión 2G está inhabilitada en este dispositivo, lo que podría afectar la conectividad. Ve a Configuración y habilita Permitir 2G para continuar."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"La red móvil no está disponible. Conéctate a una red inalámbrica para realizar una llamada."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"La red móvil no está disponible.\n\nConéctate a una red inalámbrica para realizar una llamada.\n\nLa conexión 2G está inhabilitada en este dispositivo, lo que podría afectar la conectividad. Ve a Configuración y habilita Permitir 2G para continuar."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Para realizar una llamada, ingresa un número válido."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Error en la llamada"</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"No se puede agregar la llamada en este momento. Para comunicarte, puedes enviar un mensaje."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ID secundario de SIM de datos predeterminada:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Ancho de banda de descarga (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Ancho de banda de carga (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuración del canal físico de LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Parámetros de configuración de canales físicos:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Frecuencia de actualización de datos del celular:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Información sobre las dimensiones del celular:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Servicio de datos:"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index ee17505..700c004 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Sal del modo de devolución de llamada de emergencia para hacer otro tipo de llamada."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"No se ha podido conectar a la red"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"La red móvil no está disponible."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"La red móvil no está disponible.\n\nConéctate a una red inalámbrica para hacer una llamada.\n\nEl 2G está inhabilitado en este dispositivo, lo que puede afectar a tu conectividad. Ve a Ajustes y habilita Permitir 2G para continuar."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"La red móvil no está disponible. Conéctate a una para llamar."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"La red móvil no está disponible.\n\nConéctate a una red inalámbrica para hacer una llamada.\n\nEl 2G está inhabilitado en este dispositivo, lo que puede afectar a tu conectividad. Ve a Ajustes y habilita Permitir 2G para continuar."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Para realizar una llamada, introduce un número válido."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"No se ha podido llamar."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"No se puede realizar la llamada en estos momentos. Intenta ponerte en contacto mediante un mensaje."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ID secundario de la SIM de datos predeterminada:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Ancho de banda de bajada (Kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Ancho de banda de subida (Kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuración del canal físico de LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configuraciones del canal físico:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Frecuencia de actualización de la información del teléfono:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Información sobre las dimensiones de los teléfonos:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Servicio de datos:"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index d306e21..5ceb68c 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Muude kui hädaabikõne tegemiseks väljuge hädaabikõnede režiimist."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Ei ole võrku registreeritud."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobiilsidevõrk pole saadaval."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobiilsidevõrk pole saadaval.\n\nHelistamiseks looge ühendus juhtmeta võrguga.\n\n2G on selles seadmes keelatud, mis võib teie ühenduvust mõjutada. Jätkamiseks avage Seaded ja lubage lüliti Luba 2G."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobiilsidevõrk pole saadaval. Helistamiseks looge ühendus traadita võrguga."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobiilsidevõrk pole saadaval.\n\nHelistamiseks looge ühendus juhtmeta võrguga.\n\n2G on selles seadmes keelatud, mis võib teie ühenduvust mõjutada. Jätkamiseks avage Seaded ja lubage lüliti Luba 2G."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Helistamiseks sisestage kehtiv number."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Kõne ebaõnnestus."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Kõnet ei saa praegu lisada. Proovige helistamise asemel sõnum saata."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Andmete vaike-SIM-kaardi alam-ID:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL-i ribalaius (kbit/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL-i ribalaius (kbit/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE füüsilise kanali konfiguratsioon:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Füüsilise kanali seadistused:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Kärje teabe värskendamissagedus:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Kõik kärje mõõteandmed:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Andmesideteenus:"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 837d33b..ac70f2a 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Larrialdikoak ez diren deiak egiteko, irten larrialdi-zerbitzuen deiak jasotzeko modutik."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Ez dago sarean erregistratuta."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Sare mugikorra ez dago erabilgarri."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Sare mugikorra ez dago erabilgarri.\n\nDeia egiteko, konektatu hari gabeko sare batera.\n\n2G desgaituta dago gailuan, eta baliteke horrek konexioan eragina izatea. Aurrera egiteko, joan ezarpenetara eta eman 2G erabiltzeko baimena."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Sare mugikorra ez dago erabilgarri. Deia egiteko, konektatu haririk gabeko sare batera."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Sare mugikorra ez dago erabilgarri.\n\nDeia egiteko, konektatu hari gabeko sare batera.\n\n2G desgaituta dago gailuan, eta baliteke horrek konexioan eragina izatea. Aurrera egiteko, joan ezarpenetara eta eman 2G erabiltzeko baimena."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Deitzeko, idatzi balio duen zenbaki bat."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Ezin izan da deitu."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Une honetan, ezin da egin deia. Deitu ordez, mezu bat bidaltzen saia zaitezke."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Datu-konexioetarako SIM lehenetsiaren azpiIDa:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Deskargatzeko banda-zabalera (Kb/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Kargen banda-zabalera (Kb/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE kanal fisikoaren konfigurazioa:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Kanal fisikoen konfigurazioa:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Sare mugikorraren informazioa eguneratzeko maiztasuna:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Sare mugikorraren neurketa guztien informazioa:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Datu-zerbitzua:"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index f3ca5bf..850078e 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"شبکه تلفن همراه دردسترس نیست.\n\nبرای تماس گرفتن به شبکه بیسیم متصل شوید.\n\nشبکه نسل دوم در این دستگاه غیرفعال است که ممکن است بر قابلیت اتصالتان تأثیر بگذارد. برای ادامه دادن به «تنظیمات» بروید و گزینه «اجازه دادن به شبکه نسل دوم» را فعال کنید."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"شبکه تلفن همراه دردسترس نیست. برای برقراری تماس به شبکه بیسیم متصل شوید."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"شبکه تلفن همراه دردسترس نیست.\n\nبرای تماس گرفتن به شبکه بیسیم متصل شوید.\n\nشبکه نسل دوم در این دستگاه غیرفعال است که ممکن است بر قابلیت اتصالتان تأثیر بگذارد. برای ادامه دادن به «تنظیمات» بروید و گزینه «اجازه دادن به شبکه نسل دوم» را فعال کنید."</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId مربوط به سیمکارت داده پیشفرض:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"پهنای باند DL (کیلوبیت بر ثانیه):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"پهنای باند UL (کیلوبیت بر ثانیه):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"پیکربندی کانال فیزیکی LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"پیکربندیهای کانال فیزیکی:"</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>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index da008a6..0c17693 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Poistu hätäpuhelujen takaisinsoittotilasta soittaaksesi muun kuin hätäpuhelun."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Ei rekisteröity verkkoon."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobiiliverkko ei käytettävissä."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobiiliverkko ei ole käytettävissä.\n\nYhdistä langattomaan verkkoon, jos haluat soittaa puhelun.\n\n2G ei ole käytössä tällä laitteella, mikä saattaa vaikuttaa yhteyksiin. Jatka siirtymällä asetuksiin ja ottamalla \"Salli 2G\" käyttöön."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobiiliverkko ei ole käytettävissä. Yhdistä langattomaan verkkoon, jos haluat soittaa puhelun."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobiiliverkko ei ole käytettävissä.\n\nYhdistä langattomaan verkkoon, jos haluat soittaa puhelun.\n\n2G ei ole käytössä tällä laitteella, mikä saattaa vaikuttaa yhteyksiin. Jatka siirtymällä asetuksiin ja ottamalla \"Salli 2G\" käyttöön."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Soita antamalla kelvollinen numero."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Puhelu epäonnistui."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Puhelua ei voi lisätä juuri nyt. Voit sen sijaan yrittää lähettää viestin."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Oletusarvoisen data-SIM-kortin alitunnus:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL-kaistanleveys (kt/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL-kaistanleveys (kt/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Fyysisen LTE-kanavan kokoonpano:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Fyysisen kanavan määritykset:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Matkapuhelintietojen päivitysaikaväli:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Matkapuhelimen kaikki mittaustiedot:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Datapalvelu:"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 05e4cfc..56fbfed 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Quittez le mode de rappel d\'urgence pour effectuer un appel non urgent."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Non enregistré sur le réseau"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Réseau pour mobile non disponible"</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Réseau mobile non disponible.\n\nConnectez-vous à un réseau sans fil pour passer un appel.\n\nLe réseau 2G est désactivé sur cet appareil, ce qui peut affecter votre connectivité. Accédez aux paramètres et activez Autoriser les réseaux 2G pour continuer."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Le réseau mobile n\'est pas accessible. Connectez-vous à un réseau sans fil pour effectuer un appel."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Le réseau mobile n\'est pas disponible.\n\nConnectez-vous à un réseau sans fil pour passer un appel.\n\nLe réseau 2G est désactivé sur cet appareil, ce qui peut affecter votre connectivité. Accédez aux paramètres et activez Autoriser les réseaux 2G pour continuer."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Pour faire un appel, entrez un numéro valide."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Échec de l\'appel."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Impossible d\'ajouter l\'appel pour le moment. Vous pouvez essayer de joindre la personne en lui envoyant un message."</string>
@@ -876,7 +878,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_phy_chan_config" msgid="1277949603275436081">"Configuration du canal physique LTE :"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configurations des canaux physiques :"</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>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Service de données :"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 95a882d..57b28b7 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Veuillez quitter le mode de rappel d\'urgence pour passer un appel standard."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Non enregistré sur le réseau."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Réseau mobile non disponible"</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Réseau mobile non disponible.\n\nConnectez-vous à un réseau sans fil pour passer un appel.\n\nLa 2G est désactivée sur cet appareil, ce qui peut affecter votre connectivité. Accédez aux paramètres et activez \"Autoriser la 2G\" pour continuer."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Le réseau mobile n\'est pas disponible. Connectez-vous à un réseau sans fil pour passer un appel."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Réseau mobile non disponible.\n\nConnectez-vous à un réseau sans fil pour passer un appel.\n\nLa 2G est désactivée sur cet appareil, ce qui peut affecter votre connectivité. Accédez aux paramètres et activez \"Autoriser la 2G\" pour continuer."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Pour émettre un appel, veuillez saisir un numéro valide."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Échec de l\'appel."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Impossible d\'ajouter un appel pour le moment. Essayez plutôt d\'envoyer un message."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Sous-identifiant SIM par défaut pour les données :"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Bande passante de téléchargement (kbit/s) :"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Bande passante d\'importation (kbit/s) :"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuration de la chaîne physique LTE :"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configurations des canaux physiques :"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Fréquence d\'actualisation des informations mobiles :"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Toutes les informations mobiles liées aux mesures :"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Service de données :"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 8ce562c..3a54591 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Sae do modo de devolución de chamada de emerxencia para facer unha chamada que non sexa de emerxencia."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Sen rexistro na rede"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"A rede móbil non está dispoñible."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"A rede de telefonía móbil non está dispoñible.\n\nConéctate a unha rede sen fíos para facer chamadas.\n\nA rede 2G está desactivada neste dispositivo, o que pode impedir que te conectes. Para continuar, vai a Configuración e activa a opción Permitir uso de 2G."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"A rede móbil non está dispoñible. Conéctate a unha rede sen fíos para facer unha chamada."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"A rede de telefonía móbil non está dispoñible.\n\nConéctate a unha rede sen fíos para facer chamadas.\n\nA rede 2G está desactivada neste dispositivo, o que pode impedir que te conectes. Para continuar, vai a Configuración e activa a opción Permitir uso de 2G."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Para realizar unha chamada, introduce un número válido."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Produciuse un erro na chamada."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Non se pode realizar a chamada neste momento. Podes tentar poñerte en contacto mediante unha mensaxe."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Código de identificación secundario da SIM de datos predeterminada:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Largura de banda de descarga (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Largura de banda de carga (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuración de canle física de LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configuración da canle física:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Taxa de actualización da información para móbiles:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Toda a información de medición para móbiles:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Servizo de datos:"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 6b10ddc..c8982a1 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"મોબાઇલ નેટવર્ક ઉપલબ્ધ નથી.\n\nકોઈ કૉલ કરવા માટે, વાયરલેસ નેટવર્કથી કનેક્ટ કરો.\n\nઆ ડિવાઇસ પર 2G સેવા બંધ છે, જે તમારી કનેક્ટિવિટીને અસર કરી શકે છે. સેટિંગમાં જાઓ અને આ સેવાને ચાલુ રાખવા માટે \'2Gને મંજૂરી આપો\' ચાલુ કરો."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"મોબાઇલ નેટવર્ક ઉપલબ્ધ નથી. કૉલ કરવા માટે વાયરલેસ નેટવર્ક સાથે કનેક્ટ કરો."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"મોબાઇલ નેટવર્ક ઉપલબ્ધ નથી.\n\nકોઈ કૉલ કરવા માટે, વાયરલેસ નેટવર્કથી કનેક્ટ કરો.\n\nઆ ડિવાઇસ પર 2G સેવા બંધ છે, જે તમારી કનેક્ટિવિટીને અસર કરી શકે છે. સેટિંગમાં જાઓ અને આ સેવાને ચાલુ રાખવા માટે \'2Gને મંજૂરી આપો\' ચાલુ કરો."</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ડિફૉલ્ટ ડેટા સિમનું SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL બૅન્ડવિડ્થ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL બૅન્ડવિડ્થ (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ભૌતિક ચૅનલની ગોઠવણી:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ચૅનલનું ભૌતિક કન્ફિગ્યુરેશન:"</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>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 565ece4..9f6ba51 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"मोबाइल नेटवर्क उपलब्ध नहीं है.\n\nकॉल करने के लिए किसी वायरलेस नेटवर्क से कनेक्ट करें.\n\nइस डिवाइस पर 2G नेटवर्क इस्तेमाल करने की सुविधा बंद है. ऐसा हो सकता है कि इसका असर कनेक्टिविटी पर पड़ा हो. जारी रखने के लिए, Settings में जाकर ‘2G के इस्तेमाल की मंज़ूरी दें’ को चालू करें."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"मोबाइल नेटवर्क उपलब्ध नहीं है. कॉल करने के लिए किसी वायरलेस नेटवर्क से कनेक्ट करें."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"मोबाइल नेटवर्क उपलब्ध नहीं है.\n\nकॉल करने के लिए किसी वायरलेस नेटवर्क से कनेक्ट करें.\n\nइस डिवाइस पर 2G नेटवर्क इस्तेमाल करने की सुविधा बंद है, जिससे कनेक्टिविटी पर असर पड़ सकता है. जारी रखने के लिए, Settings में जाएं और ‘2G के इस्तेमाल की मंज़ूरी दें’ को चालू करें."</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"डिफ़ॉल्ट डेटा सिम का सब-आईडी:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"डीएल बैंडविड्थ (केबीपीएस):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"यूएल बैंडविड्थ (केबीपीएस):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"एलटीई की फ़िज़िकल चैनल कॉन्फ़िगरेशन:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"फ़िज़िकल चैनल के कॉन्फ़िगरेशन:"</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>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 692c18d..19a1a13 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Isključite način hitnih poziva da biste uputili poziv koji nije hitan."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Nije registrirano na mreži."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobilna mreža nije dostupna."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobilna mreža nije dostupna.\n\nPovežite se s bežičnom mrežom da biste uputili poziv.\n\n2G je onemogućen na ovom uređaju, što možda utječe na povezivost. Za nastavak otvorite postavke i omogućite opciju Dopusti 2G."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobilna mreža nije dostupna. Povežite se s bežičnom mrežom da biste uputili poziv."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobilna mreža nije dostupna.\n\nPovežite se s bežičnom mrežom da biste uputili poziv.\n\n2G je onemogućen na ovom uređaju, što možda utječe na povezivost. Za nastavak otvorite postavke i omogućite opciju Dopusti 2G."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Unesite važeći broj da biste uspostavili poziv."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Poziv nije uspio."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Poziv se trenutačno ne može dodati. Pokušajte poslati poruku."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SUBID zadanog SIM-a za podatkovni promet:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL propusnost (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL propusnost (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfiguracija LTE fizičkog kanala:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfiguracije fizičkog kanala:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Stopa osvježavanja informacija u ćeliji:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Sve informacije ćelija o mjerenju:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Podatkovna usluga:"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index a0226d1..a575853 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Lépjen ki a Segélykérő visszahívása módból nem vészjellegű hívás kezdeményezéséhez."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Nincs regisztrálva a hálózaton."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"A mobilhálózat nem érhető el."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Nem áll rendelkezésre mobilhálózat.\n\nHívás indításához csatlakozzon vezeték nélküli hálózathoz.\n\nA 2G le van tiltva ezen az eszközön, ami hatással lehet a kapcsolódásra. A folytatáshoz nyissa meg a beállításokat, és kapcsolja be a 2G engedélyezése lehetőséget."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"A mobilhálózat nem érhető el. Hívás indításához csatlakozzon egy vezeték nélküli hálózathoz."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Nem áll rendelkezésre mobilhálózat.\n\nHívás indításához csatlakozzon vezeték nélküli hálózathoz.\n\nA 2G le van tiltva ezen az eszközön, ami hatással lehet a kapcsolódásra. A folytatáshoz nyissa meg a beállításokat, és kapcsolja be a 2G engedélyezése lehetőséget."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Hívásindításhoz adjon meg egy érvényes számot."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Sikertelen hívás."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Jelenleg nem indítható hívás. Üzenet küldésével érheti el a másik felet."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Alapértelmezett adatok SIM-alazonosítója:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Letöltési sávszélesség (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Feltöltési sávszélesség (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE fizikai csatorna konfigurációja:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Fizikai csatorna konfigurációi:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Cellainformáció frissítési gyakorisága:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Minden cellamérési információ:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Adatszolgáltatás:"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 8de0ae2..621bbba 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"Բջջային ցանցն անհասանելի է։\n\nԶանգելու համար միացեք անլար ցանցին։\n\n2G-ն անջատված է այս սարքում, ինչը կարող է ազդել կապի վրա։ Շարունակելու համար անցեք Կարգավորումներ և միացրեք «Թույլատրել 2G-ն»։"</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Բջջային ցանցն անհասանելի է: Զանգելու համար միացեք Wi-Fi ցանցին:"</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Բջջային ցանցն անհասանելի է։\n\nԶանգելու համար միացեք անլար ցանցին։\n\n2G-ն անջատված է այս սարքում, ինչը կարող է ազդել կապի վրա։ Շարունակելու համար անցեք Կարգավորումներ և միացրեք «Թույլատրել 2G-ն»։"</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SIM քարտի հավելյալ ID կանխադրված բջջային ինտերնետի համար՝"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL թողունակությունը (կբ/վ)՝"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL թողունակությունը (կբ/վ)՝"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ֆիզիկական ալիքի կարգավորում՝"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Ֆիզիկական ալիքի կազմաձևեր՝"</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>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 12110ed..f8c8761 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Keluar dari mode telepon balik darurat untuk melakukan panggilan non-darurat."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Tidak terdaftar pada jaringan."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Jaringan seluler tidak tersedia."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Jaringan seluler tidak tersedia.\n\nHubungkan ke jaringan nirkabel untuk melakukan panggilan.\n\n2G dinonaktifkan di perangkat ini, yang mungkin memengaruhi konektivitas Anda. Buka Setelan dan aktifkan Izinkan 2G untuk melanjutkan."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Jaringan seluler tidak tersedia. Sambungkan ke jaringan nirkabel untuk melakukan panggilan."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Jaringan seluler tidak tersedia.\n\nSambungkan ke jaringan nirkabel untuk melakukan panggilan.\n\n2G dinonaktifkan di perangkat ini, yang mungkin memengaruhi konektivitas Anda. Buka Setelan dan aktifkan Izinkan 2G untuk melanjutkan."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Untuk melakukan panggilan telepon, masukkan nomor yang valid."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Telepon gagal."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Panggilan tidak bisa ditambahkan saat ini. Anda bisa mencoba menghubungi dengan mengirim pesan."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId SIM data default:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Bandwidth DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Bandwidth UL (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfigurasi Saluran Fisik LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfigurasi Saluran Fisik:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Rasio Pembaruan Info Sel"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Semua Info Pengukuran Sel:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Layanan Data:"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 589503f..78aed19 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Hætta í stillingu fyrir svarhringingu neyðarsímtala til að hringja símtal sem ekki er neyðarsímtal."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Ekki skráð á símkerfi."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Farsímakerfi ekki tiltækt."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Farsímakerfið er ekki tiltækt.\n\nTengstu þráðlausu neti til að hringja.\n\nSlökkt er á 2G í þessu tæki, sem kann að hafa áhrif á tenginguna. Opnaðu stillingar og virkjaðu Leyfa 2G til að halda áfram."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Farsímakerfi er ekki tiltækt. Tengstu þráðlausu neti til að hringja."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Farsímakerfið er ekki tiltækt.\n\nTengstu þráðlausu neti til að hringja.\n\nSlökkt er á 2G í þessu tæki, sem kann að hafa áhrif á tenginguna. Opnaðu stillingar og virkjaðu Leyfa 2G til að halda áfram."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Sláðu inn gilt númer til að hringja símtal."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Tókst ekki að hringja."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Ekki er hægt að bæta símtali við að svo stöddu. Þú getur reynt að hafa samband með því að senda skilaboð."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Undirauðkenni sjálfgefins SIM-korts fyrir gögn:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Niðurhalsbandvídd (kb/sek.):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Upphleðslubandvídd (kb/sek.):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE-rásarstilling:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Áþreifanleg stilling stöðva:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Endurnýjunartíðni loftnetaupplýsinga:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Allar mælingarupplýsingar loftneta:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Gagnaþjónusta:"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 68a5335..b4bdf3e 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Per effettuare chiamate non di emergenza, esci dalla modalità di richiamata di emergenza."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Non registrato sulla rete."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Rete cellulare non disponibile."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Rete mobile non disponibile.\n\nConnettiti a una rete wireless per effettuare una chiamata.\n\nIl 2G è disattivato su questo dispositivo, e questo potrebbe influire sulla tua connettività. Vai alle Impostazioni e attiva Consenti 2G per continuare."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"La rete cellulare non è disponibile. Connettiti a una rete wireless per effettuare una chiamata."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"La rete mobile non è disponibile.\n\nConnettiti a una rete wireless per effettuare una chiamata.\n\nIl 2G è disattivato su questo dispositivo, e questo potrebbe influire sulla tua connettività. Vai alle Impostazioni e attiva Consenti 2G per continuare."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Per effettuare una chiamata, inserisci un numero valido."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Chiamata non riuscita."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Impossibile aggiungere la chiamata al momento. Prova a inviare un messaggio."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ID secondario della SIM dati predefinita:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Larghezza di banda DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Larghezza di banda UL (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configurazione canale fisico LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configurazioni dei canali fisici:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Frequenza di aggiornamento delle informazioni sulle celle:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Informazioni sulla misurazione di tutte le celle:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Servizio dati:"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 110ae8e..d9cac84 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"הרשת הסלולרית לא זמינה.\n\nצריך להתחבר לרשת אלחוטית כדי להתקשר.\n\nהחיבור ל-2G מושבת במכשיר, ויכול להיות שזה משפיע על הקישוריות. כדי להמשיך, צריך לפתוח את ההגדרות ולאפשר שימוש ב-2G."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"הרשת הסלולרית לא זמינה. עליך להתחבר לרשת אלחוטית כדי להתקשר."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"הרשת הסלולרית לא זמינה.\n\nצריך להתחבר לרשת אלחוטית כדי להתקשר.\n\nהחיבור ל-2G מושבת במכשיר, ויכול להיות שזה משפיע על הקישוריות. כדי להמשיך, צריך לפתוח את ההגדרות ולאפשר שימוש ב-2G."</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>
@@ -877,7 +879,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"תת-מזהה של כרטיס ה-SIM עם חבילת גלישה המוגדר כברירת מחדל:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"רוחב פס DL (ב-kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"רוחב פס UL (ב-kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"תצורת ערוץ פיזי של LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ההגדרות האישיות של הערוץ הפיזי"</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>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 13ed54d..819d39b 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"モバイル ネットワークを利用できません。\n\n電話をかけるにはワイヤレス ネットワークに接続してください。\n\nこのデバイスでは 2G が無効になっており、接続性に影響している可能性があります。続行するには、[設定] に移動し、[2G の許可] を有効にしてください。"</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"モバイル ネットワークを利用できません。電話をかけるにはワイヤレス ネットワークに接続してください。"</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"モバイル ネットワークを利用できません。\n\n電話をかけるにはワイヤレス ネットワークに接続してください。\n\nこのデバイスでは 2G が無効になっており、接続性に影響している可能性があります。続行するには、[設定] に移動し、[2G の許可] を有効にしてください。"</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>
@@ -876,7 +878,7 @@
<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_phy_chan_config" msgid="1277949603275436081">"LTE の物理チャネル設定:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"物理チャネルの構成:"</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>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 586858d..f1b922b 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"მობილური ქსელი მიუწვდომელია.\n\nდაუკავშირდით უსადენო ქსელს ზარის განსახორციელებლად.\n\n2G გამორთულია ამ მოწყობილობაზე, რამაც შეიძლება გავლენა მოახდინოს თქვენს კავშირზე. გასაგრძელებლად გადადით პარამეტრებზე და ჩართეთ „2G-ის დაშვება“."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"მობილური ქსელი მიუწვდომელია. ზარის განსახორციელებლად დაუკავშირდით უსადენო ქსელს."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"მობილური ქსელი მიუწვდომელია.\n\nდაუკავშირდით უსადენო ქსელს ზარის განსახორციელებლად.\n\n2G გამორთულია ამ მოწყობილობაზე, რამაც შეიძლება გავლენა მოახდინოს თქვენს კავშირზე. გასაგრძელებლად გადადით პარამეტრებზე და ჩართეთ „2G-ის დაშვება“."</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"მონაცემების ნაგულისხმევი SIM-ის subId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL-არხის გამტარუნარიანობა (კბიტ/წმ):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL-არხის გამტარუნარიანობა (კბიტ/წმ):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ფიზიკური არხის კონფიგურაცია:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"არხის ფიზიკური კონფიგურაცია:"</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>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index a10c8f2..f9296e4 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"Мобильдік желі қолжетімді емес.\n\nҚоңырау шалу үшін сымсыз желіге қосылыңыз.\n\nОсы құрылғыдағы 2G функциясы өшірілген. Бұл байланыс жұмысына әсер етуі мүмкін. Жалғастыру үшін параметрлерге өтіп, \"2G қолдануға рұқсат беру\" опциясын қосыңыз."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Мобильдік желі қолжетімді емес. Қоңырау шалу үшін сымсыз желіге қосылыңыз."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Мобильдік желі қолжетімді емес.\n\nҚоңырау шалу үшін сымсыз желіге қосылыңыз.\n\nОсы құрылғыдағы 2G функциясы өшірілген. Бұл қосылу мүмкіндігіне әсер етуі мүмкін. Жалғастыру үшін \"Параметрлерге\" өтіп, \"2G қолдануға рұқсат беру\" опциясын қосыңыз."</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Әдепкі деректер SIM картасының қосалқы идентификаторы:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL өткізу мүмкіндігі (кбит/сек):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL өткізу мүмкіндігі (кбит/сек):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE физикалық арна конфигурациясы:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Физикалық арна конфигурациялары:"</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>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index cc8ac7d..2ffd05a 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"មិនអាចប្រើបណ្ដាញទូរសព្ទចល័តបានទេ។\n\nសូមភ្ជាប់ទៅបណ្តាញឥតខ្សែ ដើម្បីហៅទូរសព្ទ។\n\n2G ត្រូវបានបិទនៅលើឧបករណ៍នេះ ដែលអាចប៉ះពាល់ដល់ការតភ្ជាប់របស់អ្នក។ សូមចូលទៅកាន់ការកំណត់ ហើយបើក \"អនុញ្ញាត 2G\" ដើម្បីបន្ត។"</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"មិនមានបណ្តាញទូរសព្ទទេ។ ភ្ជាប់ទៅបណ្តាញឥតខ្សែ ដើម្បីអាចហៅទូរសព្ទបាន។"</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"មិនអាចប្រើបណ្ដាញទូរសព្ទចល័តបានទេ។\n\nសូមភ្ជាប់ទៅបណ្តាញឥតខ្សែ ដើម្បីហៅទូរសព្ទ។\n\n2G ត្រូវបានបិទនៅលើឧបករណ៍នេះ ដែលអាចប៉ះពាល់ដល់ការតភ្ជាប់របស់អ្នក។ សូមចូលទៅកាន់ការកំណត់ ហើយបើក \"អនុញ្ញាត 2G\" ដើម្បីបន្ត។"</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"លេខសម្គាល់រងរបស់ស៊ីមទិន្នន័យលំនាំដើម៖"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"កម្រិតបញ្ជូន DL (kbps) ៖"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"កម្រិតបញ្ជូន UL (kbps) ៖"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"ការកំណត់រចនាសម្ព័ន្ធបណ្ដាញរូបវ័ន្ត LTE ៖"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ការកំណត់រចនាសម្ព័ន្ធបណ្ដាញរូបវន្ត៖"</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>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 3be99a8..8bb8d39 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"ಮೊಬೈಲ್ ನೆಟ್ವರ್ಕ್ ಲಭ್ಯವಿಲ್ಲ.\n\nಕರೆ ಮಾಡಲು ವೈರ್ಲೆಸ್ ನೆಟ್ವರ್ಕ್ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಿ.\n\nಈ ಸಾಧನದಲ್ಲಿ 2G ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ, ಇದು ನಿಮ್ಮ ಕನೆಕ್ಟಿವಿಟಿ ಮೇಲೆ ಪರಿಣಾಮ ಬೀರಬಹುದು. ಸೆಟ್ಟಿಂಗ್ಗಳಿಗೆ ಹೋಗಿ ಮತ್ತು ಮುಂದುವರಿಸಲು 2G ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"ಮೊಬೈಲ್ ನೆಟ್ವರ್ಕ್ ಲಭ್ಯವಿಲ್ಲ. ಕರೆ ಮಾಡಲು ವೈರ್ಲೆಸ್ ನೆಟ್ವರ್ಕ್ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಿ."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"ಮೊಬೈಲ್ ನೆಟ್ವರ್ಕ್ ಲಭ್ಯವಿಲ್ಲ.\n\nಕರೆ ಮಾಡಲು ವೈರ್ಲೆಸ್ ನೆಟ್ವರ್ಕ್ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಿ.\n\nಈ ಸಾಧನದಲ್ಲಿ 2G ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ, ಇದು ನಿಮ್ಮ ಕನೆಕ್ಟಿವಿಟಿ ಮೇಲೆ ಪರಿಣಾಮ ಬೀರಬಹುದು. ಸೆಟ್ಟಿಂಗ್ಗಳಿಗೆ ಹೋಗಿ ಮತ್ತು ಮುಂದುವರಿಸಲು 2G ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ."</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ಡೀಫಾಲ್ಟ್ ಡೇಟಾ ಸಿಮ್ನ ವಿಷಯಐಡಿ:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL ಬ್ಯಾಂಡ್ವಿಡ್ತ್ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL ಬ್ಯಾಂಡ್ವಿಡ್ತ್ (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ಭೌತಿಕ ಚಾನೆಲ್ ಕಾನ್ಫಿಗರೇಶನ್:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ಭೌತಿಕ ಚಾನಲ್ ಕಾನ್ಫಿಗರೇಶನ್ಗಳು:"</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>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index affc995..ef3aaf4 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"모바일 네트워크를 사용할 수 없습니다.\n\n전화를 걸려면 무선 네트워크에 연결하세요.\n\n기기에서 2G가 사용 중지되어 있어 연결 상태에 영향을 줄 수 있습니다. 계속하려면 \'설정\'으로 이동하여 \'2G 허용\'을 사용 설정하세요."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"모바일 네트워크를 사용할 수 없습니다. 전화를 걸려면 무선 네트워크에 연결하세요."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"모바일 네트워크를 사용할 수 없습니다.\n\n전화를 걸려면 무선 네트워크에 연결하세요.\n\n이 기기에서 2G가 사용 중지되어 있으므로 연결에 영향을 미칠 수 있습니다. 계속하려면 \'설정\'으로 이동하여 \'2G 허용\'을 사용 설정하세요."</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>
@@ -876,7 +878,7 @@
<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_phy_chan_config" msgid="1277949603275436081">"LTE 물리적 채널 구성:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"물리적 채널 구성:"</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>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index fd21eac..dbaf180 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"Мобилдик тармак жеткиликсиз.\n\nЧалуу үчүн зымсыз тармакка туташыңыз.\n\nБул түзмөктө 2G өчүрүлгөндүктөн, байланышка таасирин тийгизиши мүмкүн. Улантуу үчүн параметрлерге өтүп, \"2G тармагына уруксат берүү\" параметрин иштетиңиз."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Мобилдик тармак жеткиликтүү эмес. Чалуу үчүн зымсыз тармакка туташыңыз."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Мобилдик тармак жеткиликсиз.\n\nЧалуу үчүн зымсыз тармакка туташыңыз.\n\nБул түзмөктө 2G өчүрүлгөндүктөн, байланышка таасирин тийгизиши мүмкүн. Улантуу үчүн параметрлерге өтүп, \"2G тармагына уруксат берүү\" параметрин иштетиңиз."</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Демейки оператордун SIM картасынын көз салуу идентификатору:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL өткөрүү жөндөмдүүлүгү (кб/сек.):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL өткөрүү жөндөмдүүлүгү (кб/сек.):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE физикалык каналынын конфигурациясы:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Физикалык каналдын конфигурациялары:"</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>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 378b6a4..87ad624 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"ເຄືອຂ່າຍມືຖືບໍ່ມີໃຫ້ໃຊ້.\n\nເຊື່ອມຕໍ່ຫາເຄືອຂ່າຍໄຮ້ສາຍເພື່ອໂທອອກ.\n\n2G ປິດໄວ້ຢູ່ອຸປະກອນນີ້, ເຊິ່ງອາດສົ່ງຜົນຕໍ່ການເຊື່ອມຕໍ່ຂອງທ່ານ. ໄປຫາການຕັ້ງຄ່າ ແລະ ເປີດການນຳໃຊ້ອະນຸຍາດ 2G ເພື່ອສືບຕໍ່."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"ບໍ່ສາມາດໃຊ້ອິນເຕີເນັດມືຖືໄດ້. ກະລຸນາເຊື່ອມຕໍ່ຫາ Wi-Fi ເພື່ອໂທ."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"ເຄືອຂ່າຍມືຖືບໍ່ມີໃຫ້ໃຊ້.\n\nເຊື່ອມຕໍ່ຫາເຄືອຂ່າຍໄຮ້ສາຍເພື່ອໂທອອກ.\n\n2G ປິດໄວ້ຢູ່ອຸປະກອນນີ້, ເຊິ່ງອາດສົ່ງຜົນຕໍ່ການເຊື່ອມຕໍ່ຂອງທ່ານ. ໄປຫາການຕັ້ງຄ່າ ແລະ ເປີດການນຳໃຊ້ອະນຸຍາດ 2G ເພື່ອສືບຕໍ່."</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId ຂອງຊິມອິນເຕີເນັດເລີ່ມຕົ້ນ:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"ແບນວິດ DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"ແບນວິດ UL (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"ການຕັ້ງຄ່າຊ່ອງ LTE ກາຍະພາບ:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ການຕັ້ງຄ່າຊ່ອງທາງກາຍະພາບ:"</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>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index b765788..c8dee14 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Jei norite skambinti ne pagalbos numeriu, išjunkite atgalinio skambinimo pagalbos numeriu režimą."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Neregistruota tinkle."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobilusis tinklas negalimas."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobiliojo ryšio tinklas nepasiekiamas.\n\nPrisijunkite prie belaidžio ryšio tinklo, kad galėtumėte skambinti.\n\n2G išjungtas šiame įrenginyje, o tai gali paveikti jūsų ryšį. Eikite į skiltį „Nustatymai“ ir įgalinkite funkciją „Leisti 2G“, kad galėtumėte tęsti."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobiliojo ryšio tinklas nepasiekiamas. Prisijunkite prie belaidžio ryšio tinklo, kad galėtumėte skambinti."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobiliojo ryšio tinklas nepasiekiamas.\n\nPrisijunkite prie belaidžio ryšio tinklo, kad galėtumėte skambinti.\n\n2G išjungtas šiame įrenginyje, o tai gali paveikti jūsų ryšį. Eikite į skiltį „Nustatymai“ ir įgalinkite funkciją „Leisti 2G“, kad galėtumėte tęsti."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Kad galėtumėte paskambinti, įveskite tinkamą numerį."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Paskambinti nepavyko."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Šiuo metu skambučio pridėti negalima. Galite pabandyti susisiekti išsiųsdami pranešimą."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Numatytųjų duomenų SIM kortelės papildomas ID:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL pralaidumas (Kb/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL pralaidumas (Kb/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE fizinio kanalo konfigūracija:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Fizinių kanalų konfigūracijos:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Mobiliojo ryšio informacijos atnaujinimo dažnis:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Visų mobiliųjų ryšių įvertinimo informacija:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Duomenų paslauga:"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 5ea7760..ceb1a75 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Lai veiktu parastu zvanu, izejiet no ārkārtas atzvana režīma."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Tīklā nav reģistrēts."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobilais tīkls nav pieejams."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobilais tīkls nav pieejams.\n\nLai veiktu zvanu, izveidojiet savienojumu ar bezvadu tīklu.\n\nŠajā ierīcē ir atspējots 2G, un tas var ietekmēt jūsu savienojamību. Lai turpinātu, pārejiet uz iestatījumiem un iespējojiet opciju “Atļaut 2G”."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobilais tīkls nav pieejams. Lai veiktu zvanu, izveidojiet savienojumu ar bezvadu tīklu."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobilais tīkls nav pieejams.\n\nLai veiktu zvanu, izveidojiet savienojumu ar bezvadu tīklu.\n\nŠajā ierīcē ir atspējots 2G, un tas var ietekmēt jūsu savienojamību. Lai turpinātu, pārejiet uz iestatījumiem un iespējojiet opciju “Atļaut 2G”."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Lai veiktu zvanu, ievadiet derīgu numuru."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Zvans neizdevās."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Pašlaik nevar pievienot zvanu. Varat mēģināt sūtīt īsziņu."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Noklusējuma datu SIM kartes papildu ID:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL joslas platums (kb/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL joslas platums (kb/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE fiziskā kanāla konfigurācija:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Fiziskā kanāla konfigurācijas:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Mobilā tīkla informācijas atsvaidzināšanas biežums:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Visa mobilā tīkla mērījumu informācija:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Datu pakalpojums:"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index b90b9d2..bd230ce 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"Мобилната мрежа не е достапна.\n\nПоврзете се на безжична мрежа за да остварите повик.\n\n2G е оневозможено на уредов, а тоа може да влијае врз поврзливоста. Одете во „Поставки“ и овозможете ја опцијата „Дозволи 2G“ за да продолжите."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Не е достапна мобилна мрежа. Поврзете се на безжична мрежа за да повикате."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Мобилната мрежа не е достапна.\n\nПоврзете се на безжична мрежа за да остварите повик.\n\n2G е оневозможено на уредов, а тоа може да влијае врз поврзливоста. Одете во „Поставки“ и овозможете ја опцијата „Дозволи 2G“ за да продолжите."</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>
@@ -876,7 +878,7 @@
<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_phy_chan_config" msgid="1277949603275436081">"Конфигурација на физички канал на LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Конфигурации на физички канали:"</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>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 1820338..bab4ea2 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"മൊബൈൽ നെറ്റ്വർക്ക് ലഭ്യമല്ല.\n\nകോൾ ചെയ്യാൻ ഒരു വയർലെസ് നെറ്റ്വർക്കിലേക്ക് കണക്റ്റ് ചെയ്യുക.\n\nഈ ഉപകരണത്തിൽ 2G പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു, അത് നിങ്ങളുടെ കണക്റ്റിവിറ്റിയെ ബാധിച്ചേക്കാം. തുടരാൻ, ക്രമീകരണത്തിലേക്ക് പോയി 2G അനുവദിക്കുക പ്രവർത്തനക്ഷമമാക്കുക."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"മൊബൈൽ നെറ്റ്വർക്ക് ലഭ്യമല്ല. കോൾ വിളിക്കാൻ വയർലെസ്സ് നെറ്റ്വർക്കിലേക്ക് കണക്റ്റുചെയ്യുക."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"മൊബൈൽ നെറ്റ്വർക്ക് ലഭ്യമല്ല.\n\nകോൾ ചെയ്യാൻ ഒരു വയർലെസ് നെറ്റ്വർക്കിലേക്ക് കണക്റ്റ് ചെയ്യുക.\n\nഈ ഉപകരണത്തിൽ 2G പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു, അത് നിങ്ങളുടെ കണക്റ്റിവിറ്റിയെ ബാധിച്ചേക്കാം. തുടരാൻ, ക്രമീകരണത്തിലേക്ക് പോയി 2G അനുവദിക്കുക പ്രവർത്തനക്ഷമമാക്കുക."</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ഡിഫോൾട്ട് ഡാറ്റാ സിമ്മിന്റെ ഉപഐഡി:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL ബാൻഡ്വിഡ്ത് (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL ബാൻഡ്വിഡ്ത് (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ഫിസിക്കൽ ചാനൽ കോൺഫിഗറേഷൻ:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ഫിസിക്കൽ ചാനൽ കോൺഫിഗറേഷനുകൾ:"</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>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 08e60d1..9bf1b28 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Яаралтай түргэн тусламжийн бус дуудлага хийхийн тулд яаралтай түргэн тусламжийн callback горимоос гарна уу."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Сүлжээнд бүртгэгдээгүй."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Мобайль сүлжээ байхгүй."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Мобайл сүлжээ боломжгүй байна.\n\nДуудлага хийхийн тулд утасгүй сүлжээнд холбогдоно уу.\n\n2G-г энэ төхөөрөмж дээр идэвхгүй болгосон бөгөөд энэ нь таны холболтод нөлөөлж байж магадгүй. Үргэлжлүүлэхийн тулд Тохиргоо руу очоод, 2G-г зөвшөөрөхийг идэвхжүүлнэ үү."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Мобайл сүлжээнд холбогдох боломжгүй байна. Дуудлага хийхийн тулд утасгүй интернетэд холбогдоно уу."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Мобайл сүлжээ боломжгүй байна.\n\nДуудлага хийхийн тулд утасгүй сүлжээнд холбогдоно уу.\n\n2G-г энэ төхөөрөмж дээр идэвхгүй болгосон бөгөөд энэ нь таны холболтод нөлөөлж байж магадгүй. Үргэлжлүүлэхийн тулд Тохиргоо руу очоод, 2G-г зөвшөөрөхийг идэвхжүүлнэ үү."</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>
@@ -876,7 +878,7 @@
<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_phy_chan_config" msgid="1277949603275436081">"LTE Сувгийн бодит тохиргоо:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Биет сувгийн тохируулга:"</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>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 4f266e4..372ecf3 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"मोबाइल नेटवर्क उपलब्ध नाही. \n\nकॉल करण्यासाठी वायरलेस नेटवर्कशी कनेक्ट करा.\n\nया डिव्हाइसवर 2G बंद केले आहे, ज्यामुळे तुमच्या कनेक्टिव्हिटीवर परिणाम होत असेल. सेटिंग्ज वर जा आणि पुढे सुरू ठेवण्यासाठी 2G सुरू करा."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"मोबाइल नेटवर्क उपलब्ध नाही. कॉल करण्यासाठी वायरलेस नेटवर्कशी कनेक्ट करा."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"मोबाइल नेटवर्क उपलब्ध नाही. \n\nकॉल करण्यासाठी वायरलेस नेटवर्कशी कनेक्ट करा.\n\nया डिव्हाइसवर 2G बंद केले आहे, ज्यामुळे तुमच्या कनेक्टिव्हिटीवर परिणाम होत असेल. सेटिंग्ज वर जा आणि पुढे सुरू ठेवण्यासाठी 2G सुरू करा."</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"डीफॉल्ट डेटा सिम SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL बँडविड्थ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL बँडविड्थ (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE फिजिकल चॅनलचे कॉन्फिगरेशन:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"प्रत्यक्ष चॅनलची कॉन्फिगरेशन:"</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>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 979ad44..436ef38 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Keluar daripada mod panggil balik kecemasan untuk membuat panggilan bukan kecemasan."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Tidak didaftarkan pada rangkaian."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Rangkaian mudah alih tidak tersedia."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Rangkaian mudah alih tidak tersedia.\n\nBuat sambungan kepada rangkaian wayarles untuk membuat panggilan.\n\n2G dilumpuhkan pada peranti ini, yang mungkin menjejaskan kesambungan anda. Akses Tetapan dan dayakan Benarkan 2G untuk meneruskan kesambungan."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Rangkaian selular tidak tersedia. Sambung ke rangkaian wayarles untuk membuat panggilan."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Rangkaian mudah alih tidak tersedia.\n\nSambungkan kepada rangkaian wayarles untuk membuat panggilan.\n\n2G dilumpuhkan pada peranti ini, yang mungkin menjejaskan kesambungan anda. Akses Tetapan dan dayakan Benarkan 2G untuk meneruskan kesambungan."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Untuk membuat panggilan, masukkan nombor yang sah."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Panggilan gagal."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Panggilan tidak dapat ditambahkan pada masa ini. Anda boleh cuba menghantar mesej untuk berhubung."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId SIM data lalai:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Lebar Jalur DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Lebar Jalur UL (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfigurasi Saluran Fizikal LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfigurasi Saluran Fizikal:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Kadar Muat Semula Maklumat Selular:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Semua Maklumat Ukuran Selular:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Perkhidmatan Data:"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index c782d93..0728e53 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"မိုဘိုင်းကွန်ရက် မရနိုင်ပါ။\n\nဖုန်းခေါ်ရန် ကြိုးမဲ့ကွန်ရက်သို့ ချိတ်ဆက်ပါ။\n\nဤစက်တွင် 2G ကိုပိတ်ထားပြီး ၎င်းက သင့်ချိတ်ဆက်နိုင်မှုအပေါ် သက်ရောက်နိုင်သည်။ ဆက်တင်များသို့သွားပြီး ရှေ့ဆက်ရန် 2G ကိုခွင့်ပြုပါ။"</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"မိုဘိုင်းကွန်ရက် မရနိုင်ပါ။ ခေါ်ဆိုမှုပြုလုပ်ရန် ကြိုးမဲ့ကွန်ရက်သို့ ချိတ်ဆက်လိုက်ပါ။"</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"မိုဘိုင်းကွန်ရက် မရနိုင်ပါ။\n\nဖုန်းခေါ်ရန် ကြိုးမဲ့ကွန်ရက်သို့ ချိတ်ဆက်ပါ။\n\nဤစက်တွင် 2G ကိုပိတ်ထားပြီး ၎င်းက သင့်ချိတ်ဆက်နိုင်မှုအပေါ် သက်ရောက်နိုင်သည်။ ဆက်တင်များသို့သွားပြီး ရှေ့ဆက်ရန် 2G ကိုခွင့်ပြုပါ။"</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"မူရင်း ဒေတာဆင်းမ်ကဒ်အတွက် Id အခွဲ −"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL မြန်နှုန်း (kbps)−"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL မြန်နှုန်း (kbps)−"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ရုပ်ပိုင်းဆိုင်ရာ ချန်နယ်စီစဉ်သတ်မှတ်မှု−"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ရုပ်ပိုင်းဆိုင်ရာ ချန်နယ်စီစဉ်သတ်မှတ်ချက်များ-"</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>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 14b25d9..03e345a 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Avslutt modusen for nødanrop for å gjøre et vanlig anrop."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Ikke registrert på nettverket."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobilnettverket er ikke tilgjengelig."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobilnettverk er ikke tilgjengelig.\n\nKoble til et trådløst nettverk for å ringe.\n\n2G er deaktivert på denne enheten – dette kan påvirke tilkoblingen. Gå til Innstillinger og aktiver «Tillat 2G» for å fortsette."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobilnettverk er ikke tilgjengelig. Koble til et trådløst nettverk for å ringe."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobilnettverk er ikke tilgjengelig.\n\nKoble til et trådløst nettverk for å ringe.\n\n2G er deaktivert på denne enheten – dette kan påvirke tilkoblingen. Gå til Innstillinger og aktiver «Tillat 2G» for å fortsette."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Skriv inn et gyldig nummer for å plassere en samtale."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Anropet mislyktes."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Kan ikke legge til anropet akkurat nå. Du kan prøve å ta kontakt ved å sende en melding."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Under-ID for standard-SIM-kort for data:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Båndbredde for nedlasting (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Båndbredde for opplasting (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfigurering av fysisk LTE-kanal:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfigurasjoner for fysiske kanaler:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Oppdateringsfrekvens for celleinformasjon:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"All informasjon for cellemåling:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Datatjeneste:"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index e5ccc17..43c912b 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"मोबाइल नेटवर्क उपलब्ध छैन।\n\nकल गर्न वायरलेस नेटवर्कमा कनेक्ट गर्नुहोस्।\n\nयो डिभाइसमा 2G नेटवर्क अफ गरिएको छ, यसै कारणले तपाईंको कनेक्टिभिटी प्रभावित भएको हुन सक्छ। सेटिङमा जानुहोस् र जारी राख्नका निम्ति 2G प्रयोग गर्ने अनुमति दिनुहोस्।"</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"मोबाइल नेटवर्क उपलब्ध छैन। कल गर्न तारविनाको नेटवर्कमा कनेक्ट गर्नुहोस्।"</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"मोबाइल नेटवर्क उपलब्ध छैन।\n\nकल गर्न वायरलेस नेटवर्कमा कनेक्ट गर्नुहोस्।\n\nयो डिभाइसमा 2G अफ गरिएको छ, यसै कारणले तपाईंको कनेक्टिभिटी प्रभावित भएको हुन सक्छ। सेटिङमा जानुहोस् र जारी राख्नका निम्ति 2G प्रयोग गर्ने अनुमति दिनुहोस्।"</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>
@@ -876,7 +878,7 @@
<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_phy_chan_config" msgid="1277949603275436081">"LTE को भौतिक च्यानलको कन्फिगरेसन:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"भौतिक च्यानलका कन्फिगुरेसनहरू:"</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>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 96c99c7..ed3e472 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Sluit de modus voor noodoproepen af om een niet-noodoproep te plaatsen."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Niet geregistreerd op netwerk."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobiel netwerk niet beschikbaar."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobiel netwerk niet beschikbaar.\n\nMaak verbinding met een draadloos netwerk om te bellen.\n\n2G is uitgezet op dit apparaat. Dit kan gevolgen hebben voor je connectiviteit. Ga naar Instellingen en zet 2G toestaan aan om door te gaan."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobiel netwerk is niet beschikbaar. Maak verbinding met een draadloos netwerk om te bellen."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobiel netwerk is niet beschikbaar.\n\nMaak verbinding met een draadloos netwerk om te bellen.\n\n2G staat uit op dit apparaat. Dit kan gevolgen hebben voor je connectiviteit. Ga naar Instellingen en zet 2G toestaan aan om door te gaan."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Als je wilt bellen, moet je een geldig nummer invoeren."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Gesprek mislukt."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Gesprek kan op dit moment niet worden toegevoegd. Je kunt contact opnemen door een bericht te sturen."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId van standaard simkaart voor data:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL-bandbreedte (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL-bandbreedte (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Fysieke LTE-kanaalconfiguratie:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configuraties voor fysieke kanalen:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Vernieuwingsfrequentie van mobiele data:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Alle mobiele meetgegevens:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Gegevensservice:"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index f0f8cfa..400d1df 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"ମୋବାଇଲ ନେଟୱାର୍କ ଉପଲବ୍ଧ ନାହିଁ।\n\nଏକ କଲ କରିବାକୁ ଗୋଟିଏ ୱେୟାରଲେସ ନେଟୱାର୍କ ସହ କନେକ୍ଟ କରନ୍ତୁ।\n\nଏହି ଡିଭାଇସରେ 2Gକୁ ଅକ୍ଷମ କରାଯାଇଛି, ଯାହା ଆପଣଙ୍କ କନେକ୍ଟିଭିଟିକୁ ପ୍ରଭାବିତ କରୁଥାଇପାରେ। ଜାରି ରଖିବା ପାଇଁ ସେଟିଂସକୁ ଯାଇ \'2Gକୁ ଅନୁମତି ଦିଅନ୍ତୁ\'କୁ ସକ୍ଷମ କରନ୍ତୁ।"</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"ମୋବାଇଲ୍ ନେଟ୍ୱର୍କ ଉପଲବ୍ଧ ନାହିଁ। କଲ୍ କରିବା ପାଇଁ ଗୋଟିଏ ତାରବିହୀନ ନେଟ୍ୱର୍କରେ କନେକ୍ଟ କରନ୍ତୁ"</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"ମୋବାଇଲ ନେଟୱାର୍କ ଉପଲବ୍ଧ ନାହିଁ।\n\nଏକ କଲ କରିବାକୁ ଗୋଟିଏ ୱେୟାରଲେସ ନେଟୱାର୍କ ସହ କନେକ୍ଟ କରନ୍ତୁ।\n\nଏହି ଡିଭାଇସରେ 2Gକୁ ଅକ୍ଷମ କରାଯାଇଛି, ଯାହା ଆପଣଙ୍କ କନେକ୍ଟିଭିଟିକୁ ପ୍ରଭାବିତ କରୁଥାଇପାରେ। ଜାରି ରଖିବା ପାଇଁ ସେଟିଂସକୁ ଯାଇ \'2Gକୁ ଅନୁମତି ଦିଅନ୍ତୁ\'କୁ ସକ୍ଷମ କରନ୍ତୁ।"</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>
@@ -876,7 +878,7 @@
<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_phy_chan_config" msgid="1277949603275436081">"LTE ଫିଜିକାଲ୍ ଚ୍ୟାନେଲ୍ କନ୍ଫିଗରେସନ୍:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ଫିଜିକାଲ ଚେନେଲ କନଫିଗରେସନଗୁଡ଼ିକ:"</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>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 33ed1bd..07639ee 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"ਮੋਬਾਈਲ ਨੈੱਟਵਰਕ ਉਪਲਬਧ ਨਹੀਂ ਹੈ।\n\nਕਾਲ ਕਰਨ ਲਈ ਵਾਇਰਲੈੱਸ ਨੈੱਟਵਰਕ ਨਾਲ ਕਨੈਕਟ ਕਰੋ।\n\nਇਸ ਡੀਵਾਈਸ \'ਤੇ 2G ਬੰਦ ਹੈ, ਜੋ ਤੁਹਾਡੀ ਕਨੈਕਟੀਵਿਟੀ ਨੂੰ ਪ੍ਰਭਾਵਿਤ ਕਰ ਸਕਦਾ ਹੈ। ਜਾਰੀ ਰੱਖਣ ਲਈ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ \'2G ਨੂੰ ਆਗਿਆ ਦਿਓ\' ਨੂੰ ਚਾਲੂ ਕਰੋ।"</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"ਮੋਬਾਈਲ ਨੈੱਟਵਰਕ ਉਪਲਬਧ ਨਹੀਂ ਹੈ। ਕਾਲ ਕਰਨ ਲਈ ਕਿਸੇ ਵਾਇਰਲੈੱਸ ਨੈੱਟਵਰਕ ਨਾਲ ਕਨੈਕਟ ਕਰੋ।"</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"ਮੋਬਾਈਲ ਨੈੱਟਵਰਕ ਉਪਲਬਧ ਨਹੀਂ ਹੈ।\n\nਕਾਲ ਕਰਨ ਲਈ ਵਾਇਰਲੈੱਸ ਨੈੱਟਵਰਕ ਨਾਲ ਕਨੈਕਟ ਕਰੋ।\n\nਇਸ ਡੀਵਾਈਸ \'ਤੇ 2G ਬੰਦ ਹੈ, ਜੋ ਤੁਹਾਡੀ ਕਨੈਕਟੀਵਿਟੀ ਨੂੰ ਪ੍ਰਭਾਵਿਤ ਕਰ ਸਕਦਾ ਹੈ। ਜਾਰੀ ਰੱਖਣ ਲਈ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ \'2G ਨੂੰ ਆਗਿਆ ਦਿਓ\' ਨੂੰ ਚਾਲੂ ਕਰੋ।"</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ਪੂਰਵ-ਨਿਰਧਾਰਤ ਡਾਟਾ ਸਿਮ ਦਾ SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL ਬੈਂਡਵਿਡਥ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL ਬੈਂਡਵਿਡਥ (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ਭੌਤਿਕ ਚੈਨਲ ਸੰਰੂਪਣ:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ਭੌਤਿਕ ਚੈਨਲ ਸੰਰੂਪਣ:"</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>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 9e4927e..9cca02c 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Aby zadzwonić normalnie, wyjdź z trybu alarmowego połączenia zwrotnego."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Nie zarejestrowano w sieci"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Sieć komórkowa jest niedostępna."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Sieć komórkowa jest niedostępna.\n\nAby zadzwonić, połącz się z siecią bezprzewodową.\n\nSieć 2G na tym urządzeniu jest wyłączona, co może mieć wpływ na łączność. Aby kontynuować, przejdź do Ustawień i włącz opcję „Zezwól na 2G”."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Sieć komórkowa jest niedostępna. Połącz się z siecią bezprzewodową, by zadzwonić."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Sieć komórkowa jest niedostępna.\n\nAby zadzwonić, połącz się z siecią bezprzewodową.\n\nSieć 2G na tym urządzeniu jest wyłączona, co może mieć wpływ na łączność. Aby kontynuować, przejdź do Ustawień i włącz opcję „Zezwól na 2G”."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Aby zadzwonić, wybierz prawidłowy numer."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Nie udało się połączyć."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"W tej chwili nie możesz zadzwonić. Zamiast tego możesz wysłać wiadomość."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Identyfikator domyślnej karty SIM do transmisji danych:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Przepustowość kanału DL (kb/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Przepustowość kanału UL (kb/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfiguracja kanału fizycznego LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfiguracje kanału fizycznego:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Częstotliwość odświeżania informacji o sieci komórkowej:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Wszystkie informacje pomiarowe z sieci komórkowej:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Usługa transmisji danych:"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index cb028c9..c024458 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Sair do modo de chamada de retorno de emergência para efetuar uma chamada que não é de emergência."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Sem registo na rede."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Rede móvel não disponível."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"A rede móvel não está disponível.\n\nLigue-se a uma rede sem fios para fazer uma chamada.\n\nO 2G está desativado neste dispositivo, o que pode estar a afetar a sua conetividade. Aceda às Definições e ative a opção Permitir 2G para continuar."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"A rede móvel não está disponível. Ligue-se a uma rede sem fios para efetuar uma chamada."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"A rede móvel não está disponível.\n\nLigue-se a uma rede sem fios para fazer uma chamada.\n\nO 2G está desativado neste dispositivo, o que pode estar a afetar a sua conetividade. Aceda às Definições e ative a opção Permitir 2G para continuar."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Para telefonar, introduza um número válido."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"A chamada falhou."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Não é possível adicionar a chamada neste momento. Pode tentar entrar em contacto ao enviar uma mensagem."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubID do SIM de dados predefinido:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Largura de banda de transferência (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Largura de banda de carregamento (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuração do canal físico LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configurações do canal físico:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Taxa de atualização das informações da célula:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Todas as informações de medição de células:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Serviço de dados:"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 78cda63..fed9998 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Saia do modo de retorno de chamada de emergência para fazer uma chamada que não seja de emergência."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Não registrado na rede."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Rede móvel não disponível."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Rede móvel indisponível.\n\nConecte-se a uma rede sem fio para fazer uma ligação.\n\nO 2G está desativado neste dispositivo e isso pode afetar sua conectividade. Acesse \"Configurações\" e ative a opção \"Permitir 2G\" para continuar."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"A rede móvel não está disponível. Conecte-se a uma rede sem fio para fazer uma chamada."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"A rede móvel está indisponível.\n\nConecte-se a uma rede sem fio para fazer uma ligação.\n\nO 2G está desativado neste dispositivo e pode estar afetando sua conectividade. Acesse \"Configurações\" e ative a opção \"Permitir 2G\" para continuar."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Para realizar uma chamada, digite um número válido."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Falha na chamada."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Não é possível ligar no momento. Entre em contato enviando uma mensagem."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Subcódigo do chip de dados padrão:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Largura de banda DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Largura de banda UL (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuração do canal físico de LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configurações do canal físico:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Taxa de atualização das informações do celular:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Todas as informações de medição do celular:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Serviço de dados:"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index e48fcaa..eacf588 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Ieși din modul de apelare inversă de urgență pentru a efectua un apel care nu este de urgență."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Neînregistrat în rețea."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Rețeaua mobilă nu este disponibilă."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Rețeaua mobilă nu este disponibilă.\n\nConectează-te la o rețea wireless pentru a iniția un apel.\n\n2G este dezactivat pe acest dispozitiv, ceea ce poate afecta conectivitatea. Accesează Setări și activează setarea Permite 2G pentru a continua."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Rețeaua mobilă nu este disponibilă. Pentru a apela, conectează-te la o rețea wireless."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Rețeaua mobilă nu este disponibilă.\n\nConectează-te la o rețea wireless pentru a iniția un apel.\n\n2G este dezactivat pe acest dispozitiv, ceea ce poate afecta conectivitatea. Accesează Setări și activează setarea Permite 2G pentru a continua."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Pentru a apela, introdu un număr valid."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Apelul nu a fost inițiat."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Apelul nu poate fi inițiat în acest moment. Poți lua legătura cu persoana respectivă trimițându-i un mesaj."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId pentru SIM-ul de date prestabilit:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Lățime de bandă de descărcare (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Lățime de bandă de încărcare (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configurarea canalului fizic LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configurațiile canalului fizic:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Rata de actualizare a informațiilor despre celulă:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Toate informațiile de măsurare despre celulă:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Serviciu de date:"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index dbf6842..b20530d 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"Мобильная сеть недоступна.\n\nЧтобы позвонить, подключитесь к беспроводной сети.\n\nНа этом устройстве отключена передача данных по сетям 2G, что может влиять на функции связи. Чтобы продолжить, перейдите в настройки и включите параметр \"Разрешить 2G\"."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Мобильная сеть недоступна. Чтобы позвонить, подключитесь к Wi-Fi."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Мобильная сеть недоступна.\n\nЧтобы позвонить, подключитесь к беспроводной сети.\n\nНа этом устройстве отключена передача данных по сетям 2G, что может влиять на функции связи. Чтобы продолжить, перейдите в настройки и включите параметр \"Разрешить 2G\"."</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Доп. идентификатор SIM-карты для мобильного Интернета по умолчанию:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Пропускная способность DL-канала (кбит/c):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Пропускная способность UL-канала (кбит/с):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Конфигурация физического канала LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Конфигурации физического канала:"</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>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 4206329..5c165e9 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"ජංගම ජාලය නොලැබේ.\n\nඇමතුමක් කිරීමට රැහැන් රහිත ජාලයකට සම්බන්ධ කරන්න.\n\nමෙම උපාංගය මත 2G අබල කර ඇත, එය ඔබේ සම්බන්ධතාවට බලපාමින් තිබේවි. ඉදිරියට යාමට සැකසීම් වෙත ගොස් 2G හට ඉඩ දෙන්න සබල කරන්න."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"ජංගම ජාලය ලබා ගැනීමට නොහැකිය. ඇමතුමක් කිරීමට රැහැන් රහිත ජාලයකට සම්බන්ධ කරන්න."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"ජංගම ජාලය නොලැබේ.\n\nඇමතුමක් කිරීමට රැහැන් රහිත ජාලයකට සම්බන්ධ කරන්න.\n\nමෙම උපාංගය මත 2G අබල කර ඇත, එය ඔබේ සම්බන්ධතාවට බලපාමින් තිබේවි. ඉදිරියට යාමට සැකසීම් වෙත ගොස් 2G හට ඉඩ දෙන්න සබල කරන්න."</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"පෙරනිමි දත්ත SIM පතේ උප හැඳුනුම:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL කලාප පළල (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL කලාප පළල (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE භෞතික නාලිකා වින්යාසය:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"භෞතික නාලිකා වින්යාස කිරීම්:"</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>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 26e66ca..aeaf29b 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Ak chcete volať štandardným spôsobom, ukončite režim tiesňového spätného volania."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Prihlásenie do siete nebolo úspešné."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobilná sieť nie je k dispozícii."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobilná sieť nie je k dispozícii.\n\nAk chcete volať, pripojte sa k bezdrôtovej sieti.\n\nV tomto zariadení nie je zapnuté pripojenie 2G, čo môže mať vplyv na možnosti pripojenia. Ak chcete pokračovať, prejdite do nastavení a zapnite možnosť Povoliť 2G."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobilná sieť nie je k dispozícii. Ak chcete volať, pripojte sa k bezdrôtovej sieti."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobilná sieť nie je k dispozícii.\n\nAk chcete volať, pripojte sa k bezdrôtovej sieti.\n\nV tomto zariadení je prístup k 2G vypnutý, čo môže mať vplyv na pripojenie. Ak chcete pokračovať, prejdite do nastavení a zapnite možnosť Povoliť 2G."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Ak chcete volať, zadajte platné číslo"</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Hovor zlyhal."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Hovor sa momentálne nedá pridať. Môžete namiesto toho skúsiť poslať správu."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Podradený identifikátor predvolenej dátovej SIM karty:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Rýchlosť pripojenia DL (kB/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Rýchlosť pripojenia UL (kB/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfigurácia fyzického kanála LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfigurácie fyzického kanála:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Frekvencia obnovenia informácií o mobilnej sieti:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Všetky informácie o meraní mobilnej siete:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Dátová služba:"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index c92a975..43ae7bf 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Če ne gre za klic v sili, zaprite način za povratni klici v sili."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Ni registrirano v omrežju."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Omrežje prenosnega telefona ni na voljo."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobilno omrežje ni na voljo.\n\nČe želite opraviti klic, vzpostavite povezavo z brezžičnim omrežjem.\n\nOmrežje 2G je v tej napravi onemogočeno, kar lahko vpliva na povezljivost. Odprite nastavitve in vklopite možnost »Omogoči 2G«, če želite nadaljevati."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobilno omrežje ni na voljo. Če želite opraviti klic, vzpostavite povezavo z brezžičnim omrežjem."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobilno omrežje ni na voljo.\n\nČe želite opraviti klic, vzpostavite povezavo z brezžičnim omrežjem.\n\nOmrežje 2G je v tej napravi onemogočeno, kar lahko vpliva na povezljivost. Odprite nastavitve in vklopite možnost »Omogoči 2G«, če želite nadaljevati."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Če želite opraviti klic, vnesite veljavno številko."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Klic ni uspel."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Trenutno ni mogoče dodati klica. Poskusite poslati sporočilo."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ID naročnine privzete kartice SIM za prenos podatkov:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Pasovna širina za prenos (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Pasovna širina za nalaganje (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfiguracija fizičnega kanala LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Fizične konfiguracije kanalov:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Frekvenca osveževanja podatkov o celici:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Vsi podatki o meritvah celice:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Podatkovna storitev:"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 3a9307a..136f687 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Dil nga modaliteti i kthimit të telefonatës së urgjencës për të bërë një telefonatë jo urgjente."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"I paregjistruar në rrjet."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Rrjeti celular nuk mundësohet."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Rrjeti celular nuk ofrohet.\n\nLidhu me një rrjet wireless për të bërë një telefonatë.\n\n2G është çaktivizuar në këtë pajisje, e cila mund të ketë ndikim te lidhshmëria. Shko te \"Cilësimet\" dhe aktivizo \"Lejo 2G\" për të vazhduar."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Rrjeti celular nuk ofrohet. Lidhu me një rrjet wireless për të bërë një telefonatë."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Rrjeti celular nuk ofrohet.\n\nLidhu me një rrjet wireless për të bërë një telefonatë.\n\n2G është çaktivizuar në këtë pajisje, e cila mund të ketë ndikim te lidhshmëria. Shko te \"Cilësimet\" dhe aktivizo \"Lejo 2G\" për të vazhduar."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Për të kryer një telefonatë, fut një numër të vlefshëm."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Thirrja dështoi."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Telefonata nuk mund të shtohet në këtë moment. Mund të provosh të kontaktosh duke dërguar një mesazh."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ID-ja dytësore e kartës SIM të parazgjedhur të të dhënave:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Gjerësia e bandës DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Gjerësia e bandës UL (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfigurimi i kanalit fizik LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfigurimet e kanalit fizik:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Shpejtësia e rifreskimit të informacioneve të rrjetit celular"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Të gjitha informacionet e matjes së rrjetit celular:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Shërbimi i të dhënave:"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 81073b0..c47624a 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"Мобилна мрежа није доступна.\n\nПовежите се на бежичну мрежу да бисте упутили позив.\n\n2G је онемогућен на овом уређају, што може да утиче на повезивање. Идите у Подешавања и омогућите опцију Дозволи 2G да бисте наставили."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Мобилна мрежа није доступна. Повежите се на бежичну да бисте упутили позив."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Мобилна мрежа није доступна.\n\nПовежите се на бежичну мрежу да бисте упутили позив.\n\n2G је онемогућен на овом уређају, што може да утиче на повезивање. Идите у Подешавања и омогућите опцију Дозволи 2G да бисте наставили."</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubID подразумеваног SIM-а за податке:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL пропусни опсег (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL пропусни опсег (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Конфигурација LTE физичког канала:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Конфигурације физичког канала:"</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>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 2b25ed6..be95e53 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Avsluta läget för återuppringning vid nödsamtal om du vill ringa ett vanligt samtal."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Inte registrerat på nätverk."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Inget mobilt nätverk är tillgängligt."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Det finns inga tillgängliga mobilnätverk.\n\nAnslut till ett trådlöst nätverk om du vill ringa.\n\n2G har inaktiverats på den här enheten, vilket kan påverka anslutningen. Öppna inställningarna och aktivera Tillåt 2G om du vill fortsätta."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Det finns inga tillgängliga mobilnätverk. Anslut till ett trådlöst nätverk om du vill ringa."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Det finns inget mobilnätverk.\n\nAnslut till ett trådlöst nätverk om du vill ringa ett samtal.\n\n2G har inaktiverats på den här enheten, vilket kan påverka anslutningen. Öppna inställningarna och aktivera Tillåt 2G om du vill fortsätta."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Ange ett giltigt nummer om du vill ringa ett samtal."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Det gick inte att koppla samtalet."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Det går inte att lägga till samtalet just nu. Ta istället kontakt genom att skicka ett meddelande."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId för standarddata på SIM-kortet:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Bandbredd för nedladdning (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Bandbredd för uppladdning (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Fysisk kanalkonfiguration för LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfigurationer för fysiska kanaler:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Frekvens för uppdatering av mastinformation:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Alla information om mastmätning:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Datatjänst:"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index e1b02a0..10b603b 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Ondoka kwenye hali ya kupiga simu za dharura ili upige simu zisizokuwa za dharura."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Haijasajiliwa kwa mitandao"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mtandao wa simu haupatikani."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mtandao wa simu haupatikani.\n\nUnganisha kwenye mtandao pasiwaya ili upige simu.\n\n2G imezimwa kwenye kifaa hiki, hali ambayo huenda inaathiri muunganisho wako. Nenda kwenye Mipangilio kisha uwashe \'Ruhusu 2G\' ili uendelee."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mtandao wa simu za mkononi haupatikani. Unganisha kwenye mtandao pasiwaya ili upige simu."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mtandao wa simu haupatikani.\n\nUnganisha kwenye mtandao pasiwaya ili upige simu.\n\n2G imezimwa kwenye kifaa hiki, hali ambayo huenda inaathiri muunganisho wako. Nenda kwenye Mipangilio kisha uwashe \'Ruhusu 2G\' ili uendelee."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Ili upige simu, weka namba sahihi."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Imeshindwa kupiga simu."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Simu haiwezi kuongezwa kwa sasa. Unaweza kujaribu kuwasiliana kwa kutuma ujumbe."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId ya SIM chaguomsingi ya data:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Kipimo Data cha DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Kipimo Data cha UL (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Mipangilio ya Kituo Halisi cha LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Mipangilio ya Kituo Halisi:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Kasi ya Kuonyesha Upya Maelezo ya Simu:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Maelezo Yote ya Vipimo vya Simu:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Huduma ya Data:"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index c2cf5f4..d679d64 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"மொபைல் நெட்வொர்க் கிடைக்கவில்லை.\n\nஅழைப்பைச் செய்ய வயர்லெஸ் நெட்வொர்க்குடன் இணைக்கவும்.\n\nஇந்தச் சாதனத்தில் 2G முடக்கப்பட்டுள்ளதால் இணைப்பு பாதிக்கப்பட்டிருக்கலாம். தொடர, அமைப்புகளுக்குச் சென்று \'2G சேவையை அனுமதி\' என்பதை இயக்கவும்."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"மொபைல் நெட்வொர்க் கிடைக்கவில்லை. அழைக்க, வயர்லெஸ் நெட்வொர்க்குடன் இணைக்கவும்."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"மொபைல் நெட்வொர்க் கிடைக்கவில்லை.\n\nஅழைப்பைச் செய்ய வயர்லெஸ் நெட்வொர்க்குடன் இணைக்கவும்.\n\nஇந்தச் சாதனத்தில் 2G முடக்கப்பட்டுள்ளதால் இணைப்பு பாதிக்கப்பட்டிருக்கலாம். தொடர, அமைப்புகளுக்குச் சென்று \'2G சேவையை அனுமதி\' என்பதை இயக்கவும்."</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"இயல்பான டேட்டா சிம்மின் துணை ஐடி:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL இணைய வேகம் (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL இணைய வேகம் (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ஃபிசிக்கல் சேனல் உள்ளமைவு:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"சேனல் உள்ளமைவுகள்:"</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>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index f0f50bb..59122f0 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"మొబైల్ నెట్వర్క్ అందుబాటులో లేదు.\n\nకాల్ చేయడానికి వైర్లెస్ నెట్వర్క్కు కనెక్ట్ చేయండి.\n\nఈ పరికరంలో 2G డిజేబుల్ చేయబడింది, ఇది మీ కనెక్టివిటీని ప్రభావితం చేస్తూ ఉండవచ్చు. సెట్టింగ్లకు వెళ్లి, \'కొనసాగించడానికి 2Gని అనుమతించండి\'ని ఎనేబుల్ చేయండి."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"మొబైల్ నెట్వర్క్ అందుబాటులో లేదు. కాల్ చేయడానికి వైర్లెస్ నెట్వర్క్కు కనెక్ట్ చేయండి."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"మొబైల్ నెట్వర్క్ అందుబాటులో లేదు.\n\nకాల్ చేయడానికి వైర్లెస్ నెట్వర్క్కు కనెక్ట్ చేయండి.\n\nఈ పరికరంలో 2G డిజేబుల్ చేయబడింది, ఇది మీ కనెక్టివిటీని ప్రభావితం చేస్తూ ఉండవచ్చు. సెట్టింగ్లకు వెళ్లి, \'కొనసాగించడానికి 2Gని అనుమతించండి\'ని ఎనేబుల్ చేయండి."</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>
@@ -876,7 +878,7 @@
<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_phy_chan_config" msgid="1277949603275436081">"LTE భౌతిక ఛానెల్ కాన్ఫిగరేషన్:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ఫిజికల్ ఛానెల్ కాన్ఫిగరేషన్లు:"</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>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index c9dd1c3..13e9401 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"เครือข่ายมือถือไม่พร้อมใช้งาน\n\nเชื่อมต่อเครือข่ายไร้สายเพื่อโทรออก\n\nอุปกรณ์นี้ปิดใช้งาน 2G อยู่ซึ่งอาจส่งผลต่อการเชื่อมต่อ โปรดไปที่การตั้งค่าและเปิด \"อนุญาตให้ใช้ 2G\" เพื่อดำเนินการต่อ"</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"เครือข่ายมือถือไม่พร้อมใช้งาน โปรดเชื่อมต่อเครือข่ายไร้สายเพื่อโทรออก"</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"เครือข่ายมือถือไม่พร้อมใช้งาน\n\nเชื่อมต่อเครือข่ายไร้สายเพื่อโทรออก\n\nอุปกรณ์นี้ปิดใช้งาน 2G อยู่ซึ่งอาจส่งผลต่อการเชื่อมต่อ โปรดไปที่การตั้งค่าและเปิด \"อนุญาตให้ใช้ 2G\" เพื่อดำเนินการต่อ"</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId ของซิมอินเทอร์เน็ตเริ่มต้น:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"แบนด์วิดท์ดาวน์โหลด (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"แบนด์วิดท์อัปโหลด (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"การกำหนดค่าแชเนลทางกายภาพของ LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"การกำหนดค่าแชแนลทางกายภาพ:"</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>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 94dec21..2481f80 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Lumabas sa emergency callback mode upang makapagsagawa ng hindi pang-emergency na pagtawag."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Hindi nakarehistro sa network."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Hindi available ang mobile network."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Hindi available ang mobile network.\n\nKumonekta sa wireless network para tumawag.\n\nNaka-disable ang 2G sa device na ito, na posibleng nakakaapekto sa iyong pagkakonekta. Pumunta sa Mga Setting at i-enable ang Payagan ang 2G para magpatuloy."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Hindi available ang mobile network. Kumonekta sa isang wireless network upang tumawag."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Hindi available ang mobile network.\n\nKumonekta sa wireless network para tumawag.\n\nNaka-disable ang 2G sa device na ito, na posibleng nakakaapekto sa iyong pagkakonekta. Pumunta sa Mga Setting at i-enable ang Payagan ang 2G para magpatuloy."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Upang tumawag, maglagay ng wastong numero."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Nabigo ang tawag."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Hindi maidaragdag ang tawag na ito sa ngayon. Maaari mong subukang makipag-ugnayan sa pamamagitan ng pagpapadala ng isang mensahe."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId ng default na data SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL Bandwidth (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL Bandwidth (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuration ng LTE Physical Channel:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Mga Configuration ng Physical Channel:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Rate ng Pag-refresh ng Impormasyon ng Cell:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Impormasyon ng Pagsukat sa Lahat ng Cell:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Serbisyo ng Data:"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 4c176ce..05136ac 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Acil durum çağrısı dışında bir çağrı yapmak için acil durumda geri aranma modundan çıkın."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Ağda kayıtlı değil."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mobil ağ kullanılamıyor."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobil ağ kullanılamıyor.\n\nArama yapmak için kablosuz ağa bağlanın.\n\nBu cihazda 2G devre dışı olduğundan bağlantınız etkilenebilir. Devam etmek için Ayarlar\'a gidip \"2G\'ye izin ver\" ayarını etkinleştirin."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobil ağ kullanılamıyor. Telefon etmek için kablosuz ağa bağlanın."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobil ağ kullanılamıyor.\n\nArama yapmak için kablosuz ağa bağlanın.\n\nBu cihazda 2G devre dışı olduğundan bağlantınız etkilenebilir. Devam etmek için Ayarlar\'a gidip \"2G\'ye izin ver\" ayarını etkinleştirin."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Arama yapmak için geçerli bir numara girin."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Sesli arama başarısız oldu."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Çağrı şu anda eklenemiyor. Mesaj göndererek ulaşmayı deneyebilirsiniz."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Varsayılan veri SIM\'inin alt kimliği:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"İndirme Bant Genişliği (kb/sn.):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Yükleme Bant Genişliği (kb/sn.):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE Fiziksel Kanal Yapılandırması:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Fiziksel Kanal Yapılandırmaları:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Hücre Bilgilerini Yenileme Hızı:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Tüm Hücre Ölçümü Bilgileri:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Veri Hizmeti:"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index d9e8d8c..d2f093d 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"Мобільна мережа недоступна.\n\nЩоб зателефонувати, підключіться до бездротової мережі.\n\nНа цьому пристрої вимкнено 2G, що може вплинути на з’єднання. Щоб продовжити, перейдіть у налаштування й увімкніть \"Дозволити 2G\"."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Мобільна мережа недоступна. Щоб зателефонувати, під’єднайтеся до бездротової мережі."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Мобільна мережа недоступна.\n\nЩоб зателефонувати, підключіться до бездротової мережі.\n\nНа цьому пристрої вимкнено 2G, що може вплинути на з’єднання. Щоб продовжити, перейдіть у налаштування й увімкніть \"Дозволити 2G\"."</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Субідентифікатор SIM-карти для даних за умовчанням:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Пропускна спроможність DL (кбіт/с):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Пропускна спроможність UL (кбіт/с):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Конфігурація фізичного каналу LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Конфігурації фізичного каналу:"</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>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 71a2373..4a747ef 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"موبائل نیٹ ورک دستیاب نہیں ہے۔\n\nکال کرنے کے لیے وائرلیس نیٹ ورک سے منسلک ہوں۔\n\n2G اس آلے پر غیر فعال ہے، جو آپ کی کنیکٹیوٹی کو متاثر کر سکتا ہے۔ ترتیبات پر جائیں اور \'2G کو جاری رکھنے کی اجازت دیں\' کو فعال کریں۔"</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"موبائل نیٹ ورک دستیاب نہیں ہے۔ کال کرنے کیلئے کسی وائرلیس نیٹ ورک سے منسلک ہوں۔"</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"موبائل نیٹ ورک دستیاب نہیں ہے۔\n\nکال کرنے کے لیے وائرلیس نیٹ ورک سے منسلک ہوں۔\n\n2G اس آلے پر غیر فعال ہے، جو آپ کی کنیکٹیوٹی کو متاثر کر سکتا ہے۔ ترتیبات پر جائیں اور \'2G کو جاری رکھنے کی اجازت دیں\' کو فعال کریں۔"</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>
@@ -876,7 +878,7 @@
<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_phy_chan_config" msgid="1277949603275436081">"LTE فزيکل چینل کنفیگریشن:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"چینل کی طبعی کنفیگریشنز:"</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>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 2adc7ba..1f1df52 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Odatiy qo‘ng‘iroq qilish uchun favqulodda qayta qo‘ng‘iroq rejimidan chiqing."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Tarmoqda ro‘yxatdan o‘tmagan."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Uyali tarmoq mavjud emas."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Mobil tarmoq ishlamayapti.\n\nTelefon qilish uchun simsiz tarmoqqa ulaning.\n\nBu qurilmada 2G yoqilmagan va aloqaga taʼsir qilishi mumkin. Davom etish uchun Sozlamalar orqali 2G ulanishga ruxsat bering."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Mobil tarmoqdan foydalanib bo‘lmaydi. Qo‘ng‘iroq qilish uchun Wi-Fi tarmog‘iga ulaning."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Mobil tarmoq ishlamayapti.\n\nTelefon qilish uchun simsiz tarmoqqa ulaning.\n\nBu qurilmada 2G yoqilmagan va aloqaga taʼsir qilishi mumkin. Davom etish uchun Sozlamalar orqali 2G ulanishga ruxsat bering."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Qo‘ng‘iroq qilish uchun raqamni to‘g‘ri kiriting."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Chaqiruv amalga oshmadi."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Qo‘ng‘iroq qilib bo‘lmayapti. Xabar yuborib ko‘ring."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Mobil internet uchun birlamchi SIM kartaning qoʻshimcha identifikatori:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Axborot uzatish tezligi (kbit/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL kanalining axborot uzatish tezligi (kbit/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE jismoniy kanal konfiguratsiyasi:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Tashqi kanal konfiguratsiyalari:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Tarmoq haqidagi axborotning yangilanish darajasi:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Tarmoq statistikasi:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Mobil internet xizmati:"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index a4a0e35..f9da305 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Thoát khỏi chế độ gọi lại khẩn cấp để thực hiện cuộc gọi không khẩn cấp."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Chưa được đăng ký trên mạng."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Mạng di động không khả dụng."</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Không có mạng di động.\n\nHãy kết nối với mạng không dây để gọi điện.\n\n2G đã bị tắt trên thiết bị này. Điều này có thể ảnh hưởng đến khả năng kết nối của bạn. Chuyển đến phần Cài đặt rồi bật cài đặt Cho phép 2G để tiếp tục."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Hiện không có mạng di động. Hãy kết nối với mạng không dây để thực hiện cuộc gọi."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Không có mạng di động.\n\nHãy kết nối với mạng không dây để gọi điện.\n\n2G đã bị tắt trên thiết bị này. Điều này có thể ảnh hưởng đến khả năng kết nối của bạn. Chuyển đến phần Cài đặt rồi bật cài đặt Cho phép 2G để tiếp tục."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Để thực hiện cuộc gọi, hãy nhập một số hợp lệ."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Cuộc gọi không thành công."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Không thể thêm cuộc gọi tại thời điểm này. Bạn có thể cố gắng liên hệ bằng cách gửi tin nhắn."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Mã phụ của SIM dữ liệu mặc định:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Băng thông DL (kb/giây):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Băng thông UL (kb/giây):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Cấu hình kênh LTE thực:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Cấu hình của kênh thực tế:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Tốc độ làm mới thông tin mạng di động:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Toàn bộ thông tin về số đo mạng di động:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Dịch vụ dữ liệu:"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 4aebfe8..a708248 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"移动网络不可用。\n\n需连接至无线网络才能拨打电话。\n\n此设备已停用 2G,这可能会影响您的连接。如需继续操作,请前往“设置”并启用“允许启用 2G”。"</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"移动网络不可用。需连接至无线网络才能拨打电话。"</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"移动网络不可用。\n\n需连接至无线网络才能拨打电话。\n\n此设备已停用 2G,这可能会影响您的连接。如需继续操作,请前往“设置”并启用“允许启用 2G”。"</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>
@@ -876,7 +878,7 @@
<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_phy_chan_config" msgid="1277949603275436081">"LTE 物理信道配置:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"实体频道配置:"</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>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index c5eef28..8e87d8c 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -542,7 +542,9 @@
<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_2g" msgid="904434080740846116">"無法使用流動網絡。\n\n必須連接無線網絡才可打電話。\n\n此裝置已停用 2G,可能影響到裝置的連接性。請先前往「設定」開啟「允許 2G」再試。"</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"無法使用流動網絡。請連接無線網絡,以撥打電話。"</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"無法使用流動網絡。\n\n請連接無線網絡,以撥打電話。\n\n2G 已在此裝置停用,這可能會影響裝置的連接性。請前往「設定」並開啟「允許 2G」以繼續。"</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"預設數據 SIM 卡的子 ID:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"下載頻寬 (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"上載頻寬 (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE 實體渠道設定:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"實體頻道設定:"</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>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 52a8814..9316d57 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"結束緊急回撥模式,以便撥打非緊急電話。"</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"尚未註冊網路。"</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"無法使用 Google 行動服務網路。"</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"無法使用行動網路。\n\n如要撥打電話,請連線至無線網路。\n\n這部裝置已停用 2G,這可能會影響連線能力。請前往「設定」並啟用「允許啟用 2G」,再繼續操作。"</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"無法使用行動網路。連上 Wi-Fi 網路即可撥打電話。"</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"無法使用行動網路。\n\n如要撥打電話,請連線至無線網路。\n\n這部裝置已停用 2G,這可能會影響連線能力。請前往「設定」並啟用「允許啟用 2G」,再繼續操作。"</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>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"預設資料 SIM 卡的子 ID:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"下行頻寬 (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"上行頻寬 (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE 實體通道設定:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"實體頻道設定:"</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>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 51a33ac..93bc84b 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -542,7 +542,9 @@
<string name="incall_error_ecm_emergency_only" msgid="5622379058883722080">"Phuma kwimodi yokushayela emuva yesiko esiphuthumayo ukuze wenze ikholi yemodi engaphuthumi."</string>
<string name="incall_error_emergency_only" msgid="8786127461027964653">"Ayibhalisiwe kwinethiwekhi."</string>
<string name="incall_error_out_of_service" msgid="1927265196942672791">"Inethiwekhi yefoni ayitholakali"</string>
+ <string name="incall_error_out_of_service_2g" msgid="904434080740846116">"Inethiwekhi yeselula ayitholakali.\n\nXhuma kunethiwekhi e-wireless ukuze ufone.\n\nU-2G ukhutshaziwe kule divayisi, okungenzeka kunomthelela ekuxhumaneni kwakho. Iya Kumasethingi bese uvula u-Vumela u-2G ukuze uqhubeke."</string>
<string name="incall_error_out_of_service_wfc" msgid="4497663185857190885">"Inethiwekhi yeselula ayitholakali. Xhumeka kunethiwekhi engenantambo ukuze wenze ikholi."</string>
+ <string name="incall_error_out_of_service_wfc_2g_user" msgid="8218768986365299663">"Inethiwekhi yeselula ayitholakali.\n\nXhuma kunethiwekhi e-wireless ukuze ufone.\n\nU-2G ukhutshaziwe kule divayisi, okungenzeka kunomthelela ekuxhumaneni kwakho. Iya Kumasethingi bese uvula u-Vumela u-2G ukuze uqhubeke."</string>
<string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Ukuze wenze ikholi, faka inombolo evumelekile."</string>
<string name="incall_error_call_failed" msgid="393508653582682539">"Ikholi ihlulekile."</string>
<string name="incall_error_cannot_add_call" msgid="5425764862628655443">"Ikholi ayikwazi ukungezwa ngalesi sikhathi. Ungazama ukufinyelela ngokuthumela umlayezo."</string>
@@ -876,7 +878,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"I-SubId ye-SIM yedatha yokuzenzakalela:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Umkhawulokudonsa we-DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Umkhawulokudonsa we-UL (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Ukulungiselelwa okuphathekayo kwesiteshi se-LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Ukucushwa Kwesiteshi Esiphathekayo:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Isilinganiso sokuqalisa kabusha solwazi lweseli:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Ulwazi lwesilinganiso seseli:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Isevisi yedatha:"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index ba65302..2ad2af3 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -318,7 +318,29 @@
<string-array name="thermal_mitigation_allowlisted_packages" translatable="false">
</string-array>
- <!-- Flag specifying whether the AOSP domain selection is enabled or
- the device should fallback to the modem based domain selection architecture. -->
- <bool name="config_enable_aosp_domain_selection">false</bool>
+ <!-- Array of countries that active SIM is needed for emergency calls. Values should be
+ ISO3166 country codes in lowercase. -->
+ <string-array name="config_countries_require_sim_for_emergency" translatable="false">
+ <!-- b/177967010 -->
+ <item>jp</item>
+ <!-- b/230443699 -->
+ <item>in</item>
+ <item>sg</item>
+ <!-- b/198393826 -->
+ <item>de</item>
+ </string-array>
+
+ <!-- Array of countries that a normal service capable subscription is preferred
+ for emergency calls. Values should be ISO3166 country codes in lowercase. -->
+ <string-array name="config_countries_prefer_normal_service_capable_subscription"
+ translatable="false">
+ <!-- b/317945295 -->
+ <item>in</item>
+ <item>sg</item>
+ </string-array>
+
+ <!-- The component name(a flattened ComponentName string) for the telephony domain selection
+ service. The device should fallback to the modem based domain selection architecture
+ if this is not configured. -->
+ <string name="config_domain_selection_service_component_name" translatable="false"></string>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b32b030..476ab89 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1206,8 +1206,13 @@
<string name="incall_error_emergency_only">Not registered on network.</string>
<!-- In-call screen: call failure message displayed in an error dialog -->
<string name="incall_error_out_of_service">Mobile network not available.</string>
+ <!-- In-call screen: call failure message displayed in an error dialog if 2G is disabled -->
+ <string name="incall_error_out_of_service_2g">Mobile network not available.\n\nConnect to a wireless network to make a call.\n\n2G is disabled on this device, which may be impacting your connectivity. Go to Settings and enable "Allow 2G" to continue.</string>
<!-- In-call screen: call failure message displayed in an error dialog -->
<string name="incall_error_out_of_service_wfc">Mobile network is not available. Connect to a wireless network to make a call.</string>
+ <!-- In-call screen: call failure message displayed in an error dialog if the user disabled 2G -->
+ <string name="incall_error_out_of_service_wfc_2g_user">Mobile network is not available.\n\nConnect to a wireless network to make a call.\n\n2G is disabled on this device, which may be impacting your connectivity. Go to Settings and enable "Allow 2G" to continue.</string>
+
<!-- In-call screen: call failure message displayed in an error dialog -->
<string name="incall_error_no_phone_number_supplied">To place a call, enter a valid number.</string>
<!-- In-call screen: call failure message displayed in an error dialog -->
@@ -2101,7 +2106,7 @@
<!-- 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_phy_chan_config">LTE Physical Channel Configuration:</string>
+ <string name="radio_info_phy_chan_config">Physical Channel Configurations:</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>
<!-- Radio Info screen. Label for a status item. Used for diagnostic info screens, precise translation isn't needed -->
@@ -2180,6 +2185,8 @@
<string name="radio_info_nr_frequency">NR Frequency:</string>
<!-- Radio Info screen. Label for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="radio_info_network_slicing_config" translatable="false">Network Slicing Config:</string>
+ <!-- Radio Info screen. Label for a status item. Used for diagnostic info screens, precise translation isn't needed -->
+ <string name="radio_info_euicc_info" translatable="false">eUICC info:</string>
<!-- Band Mode Selection -->
<!-- Band mode screen. Title of activity. -->
diff --git a/src/com/android/phone/CallForwardEditPreference.java b/src/com/android/phone/CallForwardEditPreference.java
index d1c8303..b877112 100644
--- a/src/com/android/phone/CallForwardEditPreference.java
+++ b/src/com/android/phone/CallForwardEditPreference.java
@@ -26,6 +26,7 @@
import com.android.internal.telephony.CommandException;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.Flags;
import java.util.HashMap;
import java.util.Locale;
@@ -34,6 +35,9 @@
private static final String LOG_TAG = "CallForwardEditPreference";
private static final String SRC_TAGS[] = {"{0}"};
+
+ private static final int DEFAULT_NO_REPLY_TIMER_FOR_CFNRY = 20;
+
private CharSequence mSummaryOnTemplate;
/**
* Remembers which button was clicked by a user. If no button is clicked yet, this should have
@@ -154,7 +158,14 @@
.getCarrierConfigForSubId(mPhone.getSubId());
if (carrierConfig.getBoolean(
CarrierConfigManager.KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL, true)) {
- time = 20;
+ if (Flags.setNoReplyTimerForCfnry()) {
+ // Get timer value from carrier config
+ time = carrierConfig.getInt(
+ CarrierConfigManager.KEY_NO_REPLY_TIMER_FOR_CFNRY_SEC_INT,
+ DEFAULT_NO_REPLY_TIMER_FOR_CFNRY);
+ } else {
+ time = DEFAULT_NO_REPLY_TIMER_FOR_CFNRY;
+ }
}
}
final String number = getPhoneNumber();
diff --git a/src/com/android/phone/CallNotifier.java b/src/com/android/phone/CallNotifier.java
index 73b61b6..daf3aa2 100644
--- a/src/com/android/phone/CallNotifier.java
+++ b/src/com/android/phone/CallNotifier.java
@@ -489,7 +489,7 @@
public void updatePhoneStateListeners(boolean isRefresh, int updateType, int subIdToUpdate) {
List<SubscriptionInfo> subInfos = SubscriptionManagerService.getInstance()
.getActiveSubscriptionInfoList(mApplication.getOpPackageName(),
- mApplication.getAttributionTag());
+ mApplication.getAttributionTag(), true/*isForAllProfile*/);
// Sort sub id list based on slot id, so that CFI/MWI notifications will be updated for
// slot 0 first then slot 1. This is needed to ensure that when CFI or MWI is enabled for
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index fa85f27..9b71919 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -16,12 +16,15 @@
package com.android.phone;
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION;
import static android.service.carrier.CarrierService.ICarrierServiceWrapper.KEY_CONFIG_BUNDLE;
import static android.service.carrier.CarrierService.ICarrierServiceWrapper.RESULT_ERROR;
+import static android.telephony.TelephonyManager.ENABLE_FEATURE_MAPPING;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
+import android.app.compat.CompatChanges;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -39,6 +42,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.PermissionEnforcer;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
@@ -48,6 +52,7 @@
import android.service.carrier.CarrierIdentifier;
import android.service.carrier.CarrierService;
import android.service.carrier.ICarrierService;
+import android.telephony.AnomalyReporter;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyFrameworkInitializer;
@@ -65,10 +70,11 @@
import com.android.internal.telephony.PhoneConfigurationManager;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.TelephonyPermissions;
+import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.subscription.SubscriptionManagerService;
import com.android.internal.telephony.util.ArrayUtils;
+import com.android.internal.telephony.util.TelephonyUtils;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.telephony.Rlog;
import java.io.File;
import java.io.FileDescriptor;
@@ -78,12 +84,20 @@
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import java.util.Objects;
import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* CarrierConfigLoader binds to privileged carrier apps to fetch carrier config overlays.
@@ -91,6 +105,9 @@
public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
private static final String LOG_TAG = "CarrierConfigLoader";
+ private static final SimpleDateFormat TIME_FORMAT =
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
+
// Package name for platform carrier config app, bundled with system image.
@NonNull private final String mPlatformCarrierConfigPackage;
@@ -129,7 +146,7 @@
// Broadcast receiver for system events
@NonNull
private final BroadcastReceiver mSystemBroadcastReceiver = new ConfigLoaderBroadcastReceiver();
- @NonNull private final LocalLog mCarrierConfigLoadingLog = new LocalLog(100);
+ @NonNull private final LocalLog mCarrierConfigLoadingLog = new LocalLog(256);
// Number of phone instances (active modem count)
private int mNumPhones;
@@ -203,6 +220,10 @@
CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL
};
+ // UUID to report anomaly when config changed reported with subId that map to invalid phone
+ private static final String UUID_NOTIFY_CONFIG_CHANGED_WITH_INVALID_PHONE =
+ "d81cef11-c2f1-4d76-955d-7f50e8590c48";
+
// Handler to process various events.
//
// For each phoneId, the event sequence should be:
@@ -228,7 +249,7 @@
@Override
public void handleMessage(@NonNull Message msg) {
final int phoneId = msg.arg1;
- logdWithLocalLog("mHandler: " + eventToString(msg.what) + " phoneId: " + phoneId);
+ logd(eventToString(msg.what) + " phoneId: " + phoneId);
if (!SubscriptionManager.isValidPhoneId(phoneId)
&& msg.what != EVENT_MULTI_SIM_CONFIG_CHANGED) {
return;
@@ -276,19 +297,11 @@
PersistableBundle config = restoreConfigFromXml(
mPlatformCarrierConfigPackage, OVERRIDE_PACKAGE_ADDITION, phoneId);
if (config != null) {
- logd("Loaded persistent override config from XML. package="
- + mPlatformCarrierConfigPackage
- + " phoneId=" + phoneId);
mPersistentOverrideConfigs[phoneId] = config;
}
config = restoreConfigFromXml(mPlatformCarrierConfigPackage, "", phoneId);
if (config != null) {
- logd(
- "Loaded config from XML. package="
- + mPlatformCarrierConfigPackage
- + " phoneId="
- + phoneId);
mConfigFromDefaultApp[phoneId] = config;
Message newMsg = obtainMessage(EVENT_FETCH_DEFAULT_DONE, phoneId, -1);
newMsg.getData().putBoolean("loaded_from_xml", true);
@@ -363,10 +376,10 @@
carrierService.getCarrierConfig(phoneId, carrierId, resultReceiver);
logdWithLocalLog("Fetch config for default app: "
+ mPlatformCarrierConfigPackage
- + " carrierid: " + carrierId.toString());
+ + ", carrierId=" + carrierId.getSpecificCarrierId());
} catch (RemoteException e) {
loge("Failed to get carrier config from default app: " +
- mPlatformCarrierConfigPackage + " err: " + e.toString());
+ mPlatformCarrierConfigPackage + " err: " + e);
unbindIfBound(mContext, conn, phoneId);
break; // So we don't set a timeout.
}
@@ -418,11 +431,6 @@
final PersistableBundle config =
restoreConfigFromXml(carrierPackageName, "", phoneId);
if (config != null) {
- logd(
- "Loaded config from XML. package="
- + carrierPackageName
- + " phoneId="
- + phoneId);
mConfigFromCarrierApp[phoneId] = config;
Message newMsg = obtainMessage(EVENT_FETCH_CARRIER_DONE, phoneId, -1);
newMsg.getData().putBoolean("loaded_from_xml", true);
@@ -503,9 +511,9 @@
carrierService.getCarrierConfig(phoneId, carrierId, resultReceiver);
logdWithLocalLog("Fetch config for carrier app: "
+ getCarrierPackageForPhoneId(phoneId)
- + " carrierid: " + carrierId.toString());
+ + ", carrierId=" + carrierId.getSpecificCarrierId());
} catch (RemoteException e) {
- loge("Failed to get carrier config: " + e.toString());
+ loge("Failed to get carrier config: " + e);
unbindIfBound(mContext, conn, phoneId);
break; // So we don't set a timeout.
}
@@ -577,8 +585,6 @@
restoreNoSimConfigFromXml(mPlatformCarrierConfigPackage);
if (config != null) {
- logd("Loaded no SIM config from XML. package="
- + mPlatformCarrierConfigPackage);
mNoSimConfig = config;
sendMessage(
obtainMessage(
@@ -673,7 +679,7 @@
+ mPlatformCarrierConfigPackage);
} catch (RemoteException e) {
loge("Failed to get no sim carrier config from default app: " +
- mPlatformCarrierConfigPackage + " err: " + e.toString());
+ mPlatformCarrierConfigPackage + " err: " + e);
unbindIfBoundForNoSimConfig(mContext, conn, phoneId);
break; // So we don't set a timeout.
}
@@ -689,12 +695,18 @@
@NonNull private final Handler mHandler;
+ @NonNull private final FeatureFlags mFeatureFlags;
+
+ @NonNull private final PackageManager mPackageManager;
+
/**
* Constructs a CarrierConfigLoader, registers it as a service, and registers a broadcast
* receiver for relevant events.
*/
@VisibleForTesting
- /* package */ CarrierConfigLoader(@NonNull Context context, @NonNull Looper looper) {
+ /* package */ CarrierConfigLoader(@NonNull Context context, @NonNull Looper looper,
+ @NonNull FeatureFlags featureFlags) {
+ super(PermissionEnforcer.fromContext(context));
mContext = context;
mPlatformCarrierConfigPackage =
mContext.getString(R.string.platform_carrier_config_package);
@@ -722,6 +734,8 @@
TelephonyManager.from(context).registerCarrierPrivilegesCallback(phoneId,
new HandlerExecutor(mHandler), mCarrierServiceChangeCallbacks[phoneId]);
}
+ mFeatureFlags = featureFlags;
+ mPackageManager = context.getPackageManager();
logd("CarrierConfigLoader has started");
PhoneConfigurationManager.registerForMultiSimConfigChange(
@@ -736,10 +750,11 @@
* This is only done once, at startup, from {@link com.android.phone.PhoneApp#onCreate}.
*/
@NonNull
- /* package */ static CarrierConfigLoader init(@NonNull Context context) {
+ /* package */ static CarrierConfigLoader init(@NonNull Context context,
+ @NonNull FeatureFlags featureFlags) {
synchronized (CarrierConfigLoader.class) {
if (sInstance == null) {
- sInstance = new CarrierConfigLoader(context, Looper.myLooper());
+ sInstance = new CarrierConfigLoader(context, Looper.myLooper(), featureFlags);
// Make this service available through ServiceManager.
TelephonyFrameworkInitializer.getTelephonyServiceManager()
.getCarrierConfigServiceRegisterer().register(sInstance);
@@ -1003,7 +1018,7 @@
int phoneId, @Nullable CarrierIdentifier carrierId, @NonNull PersistableBundle config,
boolean isNoSimConfig) {
if (packageName == null) {
- loge("Cannot save config with null packageName");
+ loge("Cannot save config with null packageName. phoneId=" + phoneId);
return;
}
@@ -1013,7 +1028,7 @@
} else {
if (TelephonyManager.getSimStateForSlotIndex(phoneId)
!= TelephonyManager.SIM_STATE_LOADED) {
- loge("Skip save config because SIM records are not loaded.");
+ loge("Skip saving config because SIM records are not loaded. phoneId=" + phoneId);
return;
}
@@ -1021,7 +1036,7 @@
final int cid = carrierId != null ? carrierId.getSpecificCarrierId()
: TelephonyManager.UNKNOWN_CARRIER_ID;
if (iccid == null) {
- loge("Cannot save config with null iccid.");
+ loge("Cannot save config with null iccid. phoneId=" + phoneId);
return;
}
fileName = getFilenameForConfig(packageName, extraString, iccid, cid);
@@ -1038,12 +1053,12 @@
final String version = getPackageVersion(packageName);
if (version == null) {
- loge("Failed to get package version for: " + packageName);
+ loge("Failed to get package version for: " + packageName + ", phoneId=" + phoneId);
return;
}
- logdWithLocalLog(
- "Save config to xml, packagename: " + packageName + " phoneId: " + phoneId);
+ logdWithLocalLog("Save carrier config to cache. phoneId=" + phoneId
+ + ", xml=" + getFilePathForLogging(fileName) + ", version=" + version);
FileOutputStream outFile = null;
try {
@@ -1104,52 +1119,52 @@
} else {
if (TelephonyManager.getSimStateForSlotIndex(phoneId)
!= TelephonyManager.SIM_STATE_LOADED) {
- loge("Skip restore config because SIM records are not loaded.");
+ loge("Skip restore config because SIM records are not loaded. phoneId=" + phoneId);
return null;
}
iccid = getIccIdForPhoneId(phoneId);
final int cid = getSpecificCarrierIdForPhoneId(phoneId);
if (iccid == null) {
- loge("Cannot restore config with null iccid.");
+ loge("Cannot restore config with null iccid. phoneId=" + phoneId);
return null;
}
fileName = getFilenameForConfig(packageName, extraString, iccid, cid);
}
PersistableBundle restoredBundle = null;
- File file = null;
- FileInputStream inFile = null;
- try {
- file = new File(mContext.getFilesDir(),fileName);
- inFile = new FileInputStream(file);
+ File file = new File(mContext.getFilesDir(), fileName);
+ String filePath = file.getPath();
+ String savedVersion = null;
+ try (FileInputStream inFile = new FileInputStream(file)) {
restoredBundle = PersistableBundle.readFromStream(inFile);
- String savedVersion = restoredBundle.getString(KEY_VERSION);
+ savedVersion = restoredBundle.getString(KEY_VERSION);
restoredBundle.remove(KEY_VERSION);
if (!version.equals(savedVersion)) {
- loge("Saved version mismatch: " + version + " vs " + savedVersion);
+ loge("Saved version mismatch: " + version + " vs " + savedVersion
+ + ", phoneId=" + phoneId);
restoredBundle = null;
}
-
- inFile.close();
} catch (FileNotFoundException e) {
// Missing file is normal occurrence that might occur with a new sim or when restoring
// an override file during boot and should not be treated as an error.
- if (file != null) {
- if (isNoSimConfig) {
- logd("File not found: " + file.getPath());
- } else {
- String filePath = file.getPath();
- filePath = getFilePathForLogging(filePath, iccid);
- logd("File not found : " + filePath);
- }
+ if (isNoSimConfig) {
+ logd("File not found: " + file.getPath() + ", phoneId=" + phoneId);
+ } else {
+ logd("File not found : " + getFilePathForLogging(filePath, iccid)
+ + ", phoneId=" + phoneId);
}
} catch (IOException e) {
loge(e.toString());
}
+ if (restoredBundle != null) {
+ logdWithLocalLog("Restored carrier config from cache. phoneId=" + phoneId + ", xml="
+ + getFilePathForLogging(fileName) + ", version=" + savedVersion
+ + ", modified time=" + getFileTime(filePath));
+ }
return restoredBundle;
}
@@ -1159,7 +1174,7 @@
@NonNull
private String getFilePathForLogging(@Nullable String filePath, @Nullable String iccid) {
// If loggable then return with actual file path
- if (Rlog.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ if (TelephonyUtils.IS_DEBUGGABLE) {
return filePath;
}
String path = filePath;
@@ -1203,7 +1218,7 @@
});
if (packageFiles == null || packageFiles.length < 1) return false;
for (File f : packageFiles) {
- logd("Deleting " + getFilePathForLogging(f.getName()));
+ logdWithLocalLog("Deleting " + getFilePathForLogging(f.getName()));
f.delete();
}
return true;
@@ -1326,6 +1341,8 @@
return new PersistableBundle();
}
+ enforceTelephonyFeatureWithException(callingPackage, "getConfigForSubIdWithFeature");
+
int phoneId = SubscriptionManager.getPhoneId(subscriptionId);
PersistableBundle retConfig = CarrierConfigManager.getDefaultConfig();
if (SubscriptionManager.isValidPhoneId(phoneId)) {
@@ -1370,6 +1387,9 @@
Objects.requireNonNull(keys, "Config keys must be non-null");
enforceCallerIsSystemOrRequestingPackage(callingPackage);
+ enforceTelephonyFeatureWithException(callingPackage,
+ "getConfigSubsetForSubIdWithFeature");
+
// Permission check is performed inside and an empty bundle will return on failure.
// No SecurityException thrown here since most clients expect to retrieve the overridden
// value if present or use default one if not
@@ -1408,17 +1428,20 @@
return configSubset;
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@Override
public void overrideConfig(int subscriptionId, @Nullable PersistableBundle overrides,
boolean persistent) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_PHONE_STATE, null);
+ overrideConfig_enforcePermission();
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);
}
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(), "overrideConfig");
+
// Post to run on handler thread on which all states should be confined.
mHandler.post(() -> {
overrideConfig(mOverrideConfigs, phoneId, overrides);
@@ -1440,6 +1463,8 @@
fileToDelete.delete();
}
}
+ logdWithLocalLog("overrideConfig: subId=" + subscriptionId + ", persistent="
+ + persistent + ", overrides=" + overrides);
updateSubscriptionDatabase(phoneId);
});
}
@@ -1464,11 +1489,22 @@
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);
+ final String msg =
+ "Ignore invalid phoneId: " + phoneId + " for subId: " + subscriptionId;
+ if (mFeatureFlags.addAnomalyWhenNotifyConfigChangedWithInvalidPhone()) {
+ AnomalyReporter.reportAnomaly(
+ UUID.fromString(UUID_NOTIFY_CONFIG_CHANGED_WITH_INVALID_PHONE), msg);
+ }
+ logd(msg);
+ throw new IllegalArgumentException(msg);
}
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ "notifyConfigChangedForSubId");
+
+ logdWithLocalLog("Notified carrier config changed. phoneId=" + phoneId
+ + ", 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
// deleted, this can return and we will eventually bind to the carrier app.
@@ -1478,14 +1514,17 @@
updateConfigForPhoneId(phoneId);
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@Override
public void updateConfigForPhoneId(int phoneId, @NonNull String simState) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_PHONE_STATE, null);
- logdWithLocalLog("Update config for phoneId: " + phoneId + " simState: " + simState);
+ updateConfigForPhoneId_enforcePermission();
+ logdWithLocalLog("Update config for phoneId=" + phoneId + " simState=" + simState);
if (!SubscriptionManager.isValidPhoneId(phoneId)) {
throw new IllegalArgumentException("Invalid phoneId: " + phoneId);
}
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(), "updateConfigForPhoneId");
+
// requires Java 7 for switch on string.
switch (simState) {
case IccCardConstants.INTENT_VALUE_ICC_ABSENT:
@@ -1502,12 +1541,15 @@
}
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@Override
@NonNull
public String getDefaultCarrierServicePackageName() {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ getDefaultCarrierServicePackageName_enforcePermission();
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
"getDefaultCarrierServicePackageName");
+
return mPlatformCarrierConfigPackage;
}
@@ -1573,6 +1615,27 @@
}
/**
+ * Get the file time in readable format.
+ *
+ * @param filePath The full file path.
+ *
+ * @return The time in string format.
+ */
+ @Nullable
+ private String getFileTime(@NonNull String filePath) {
+ String formattedModifiedTime = null;
+ try {
+ // Convert the modified time to a readable format
+ formattedModifiedTime = TIME_FORMAT.format(Files.readAttributes(Paths.get(filePath),
+ BasicFileAttributes.class).lastModifiedTime().toMillis());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return formattedModifiedTime;
+ }
+
+ /**
* 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
@@ -1613,8 +1676,20 @@
}
printConfig(mNoSimConfig, indentPW, "mNoSimConfig");
- indentPW.println("CarrierConfigLoadingLog=");
+ indentPW.println("mNumPhones=" + mNumPhones);
+ indentPW.println("mPlatformCarrierConfigPackage=" + mPlatformCarrierConfigPackage);
+ indentPW.println("mServiceConnection=[" + Stream.of(mServiceConnection)
+ .map(c -> c != null ? c.pkgName : null)
+ .collect(Collectors.joining(", ")) + "]");
+ indentPW.println("mServiceBoundForNoSimConfig="
+ + Arrays.toString(mServiceBoundForNoSimConfig));
+ indentPW.println("mHasSentConfigChange=" + Arrays.toString(mHasSentConfigChange));
+ indentPW.println("mFromSystemUnlocked=" + Arrays.toString(mFromSystemUnlocked));
+ indentPW.println();
+ indentPW.println("CarrierConfigLoader local log=");
+ indentPW.increaseIndent();
mCarrierConfigLoadingLog.dump(fd, indentPW, args);
+ indentPW.decreaseIndent();
if (requestingPackage != null) {
logd("Including default and requesting package " + requestingPackage
@@ -1626,6 +1701,16 @@
dumpCarrierServiceIfBound(fd, indentPW, "Requesting package", requestingPackage,
true /* considerCarrierPrivileges */);
}
+
+ indentPW.println();
+ indentPW.println("Cached config files:");
+ indentPW.increaseIndent();
+ for (File f : mContext.getFilesDir().listFiles((FilenameFilter) (d, filename)
+ -> filename.startsWith("carrierconfig-"))) {
+ indentPW.println(getFilePathForLogging(f.getName()) + ", modified time="
+ + getFileTime(f.getAbsolutePath()));
+ }
+ indentPW.decreaseIndent();
}
private void printConfig(@NonNull PersistableBundle configApp,
@@ -1775,6 +1860,40 @@
== TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
}
+ /**
+ * Get the current calling package name.
+ * @return the current calling package name
+ */
+ @Nullable
+ private String getCurrentPackageName() {
+ if (mPackageManager == null) return null;
+ String[] callingUids = mPackageManager.getPackagesForUid(Binder.getCallingUid());
+ return (callingUids == null) ? null : callingUids[0];
+ }
+
+ /**
+ * Make sure the device has required telephony feature
+ *
+ * @throws UnsupportedOperationException if the device does not have required telephony feature
+ */
+ private void enforceTelephonyFeatureWithException(@Nullable String callingPackage,
+ @NonNull String methodName) {
+ if (callingPackage == null || mPackageManager == null) {
+ return;
+ }
+
+ if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
+ || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
+ Binder.getCallingUserHandle())) {
+ return;
+ }
+
+ if (!mPackageManager.hasSystemFeature(FEATURE_TELEPHONY_SUBSCRIPTION)) {
+ throw new UnsupportedOperationException(
+ methodName + " is unsupported without " + FEATURE_TELEPHONY_SUBSCRIPTION);
+ }
+ }
+
private class CarrierServiceConnection implements ServiceConnection {
final int phoneId;
@NonNull final String pkgName;
diff --git a/src/com/android/phone/CdmaCallOptions.java b/src/com/android/phone/CdmaCallOptions.java
index 6145870..4f94b58 100644
--- a/src/com/android/phone/CdmaCallOptions.java
+++ b/src/com/android/phone/CdmaCallOptions.java
@@ -16,8 +16,10 @@
package com.android.phone;
+import android.content.Context;
import android.os.Bundle;
import android.os.PersistableBundle;
+import android.os.UserManager;
import android.preference.Preference;
import android.preference.PreferenceScreen;
import android.telephony.CarrierConfigManager;
@@ -30,6 +32,7 @@
import android.view.MenuItem;
import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.flags.Flags;
public class CdmaCallOptions extends TimeConsumingPreferenceActivity {
private static final String LOG_TAG = "CdmaCallOptions";
@@ -78,9 +81,21 @@
buttonVoicePrivacy.setEnabled(false);
}
+ // If mobile network configs are restricted, then hide the mCallForwardingPref and
+ // mCallWaitingPref.
+ UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
+ boolean mobileNetworkConfigsRestricted =
+ userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
+ if (Flags.ensureAccessToCallSettingsIsRestricted() && mobileNetworkConfigsRestricted) {
+ Log.i(LOG_TAG, "Mobile network configs are restricted, hiding CDMA call forwarding "
+ + "and CDMA call waiting options.");
+ }
+
mCallForwardingPref = getPreferenceScreen().findPreference(CALL_FORWARDING_KEY);
if (carrierConfig != null && carrierConfig.getBoolean(
- CarrierConfigManager.KEY_CALL_FORWARDING_VISIBILITY_BOOL)) {
+ CarrierConfigManager.KEY_CALL_FORWARDING_VISIBILITY_BOOL) &&
+ (!mobileNetworkConfigsRestricted ||
+ !Flags.ensureAccessToCallSettingsIsRestricted())) {
mCallForwardingPref.setIntent(
subInfoHelper.getIntent(CdmaCallForwardOptions.class));
} else {
@@ -91,7 +106,9 @@
mCallWaitingPref = (CdmaCallWaitingPreference) getPreferenceScreen()
.findPreference(CALL_WAITING_KEY);
if (carrierConfig == null || !carrierConfig.getBoolean(
- CarrierConfigManager.KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL)) {
+ CarrierConfigManager.KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL) ||
+ (Flags.ensureAccessToCallSettingsIsRestricted() &&
+ mobileNetworkConfigsRestricted)) {
getPreferenceScreen().removePreference(mCallWaitingPref);
mCallWaitingPref = null;
}
diff --git a/src/com/android/phone/ChangeIccPinScreen.java b/src/com/android/phone/ChangeIccPinScreen.java
index 5369aa3..0784495 100644
--- a/src/com/android/phone/ChangeIccPinScreen.java
+++ b/src/com/android/phone/ChangeIccPinScreen.java
@@ -18,12 +18,14 @@
import android.app.Activity;
import android.app.AlertDialog;
+import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.AsyncResult;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.os.UserManager;
import android.text.method.DigitsKeyListener;
import android.util.Log;
import android.view.View;
@@ -46,12 +48,12 @@
private static final boolean DBG = false;
private static final int EVENT_PIN_CHANGED = 100;
-
+
private enum EntryState {
ES_PIN,
ES_PUK
}
-
+
private EntryState mState;
private static final int NO_ERROR = 0;
@@ -61,6 +63,8 @@
private static final int MIN_PIN_LENGTH = 4;
private static final int MAX_PIN_LENGTH = 8;
+ private UserManager mUserManager;
+ private boolean mDisallowedConfig;
private Phone mPhone;
private boolean mChangePin2;
private TextView mBadPinError;
@@ -91,49 +95,74 @@
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ mUserManager = this.getSystemService(UserManager.class);
+ if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
+ mDisallowedConfig = true;
+ }
+
mPhone = PhoneGlobals.getPhone();
resolveIntent();
setContentView(R.layout.change_sim_pin_screen);
+ setupView();
+ mState = EntryState.ES_PIN;
+ }
+
+ private void setupView() {
mOldPin = (EditText) findViewById(R.id.old_pin);
- mOldPin.setKeyListener(DigitsKeyListener.getInstance());
- mOldPin.setMovementMethod(null);
- mOldPin.setOnClickListener(mClicked);
-
mNewPin1 = (EditText) findViewById(R.id.new_pin1);
- mNewPin1.setKeyListener(DigitsKeyListener.getInstance());
- mNewPin1.setMovementMethod(null);
- mNewPin1.setOnClickListener(mClicked);
-
mNewPin2 = (EditText) findViewById(R.id.new_pin2);
- mNewPin2.setKeyListener(DigitsKeyListener.getInstance());
- mNewPin2.setMovementMethod(null);
- mNewPin2.setOnClickListener(mClicked);
-
mBadPinError = (TextView) findViewById(R.id.bad_pin);
mMismatchError = (TextView) findViewById(R.id.mismatch);
-
mButton = (Button) findViewById(R.id.button);
- mButton.setOnClickListener(mClicked);
-
mScrollView = (ScrollView) findViewById(R.id.scroll);
-
mPUKCode = (EditText) findViewById(R.id.puk_code);
- mPUKCode.setKeyListener(DigitsKeyListener.getInstance());
- mPUKCode.setMovementMethod(null);
- mPUKCode.setOnClickListener(mClicked);
-
mPUKSubmit = (Button) findViewById(R.id.puk_submit);
- mPUKSubmit.setOnClickListener(mClicked);
-
mIccPUKPanel = (LinearLayout) findViewById(R.id.puk_panel);
-
int id = mChangePin2 ? R.string.change_pin2 : R.string.change_pin;
setTitle(getResources().getText(id));
-
- mState = EntryState.ES_PIN;
+
+ if (mDisallowedConfig) {
+ mOldPin.setEnabled(false);
+ mOldPin.setAlpha(.5f);
+
+ mNewPin1.setEnabled(false);
+ mNewPin1.setAlpha(.5f);
+
+ mNewPin2.setEnabled(false);
+ mNewPin2.setAlpha(.5f);
+
+ mButton.setEnabled(false);
+ mButton.setAlpha(.5f);
+
+ mPUKCode.setEnabled(false);
+ mPUKCode.setAlpha(.5f);
+
+ mPUKSubmit.setEnabled(false);
+ mPUKSubmit.setAlpha(.5f);
+ } else {
+ mOldPin.setKeyListener(DigitsKeyListener.getInstance());
+ mOldPin.setMovementMethod(null);
+ mOldPin.setOnClickListener(mClicked);
+
+ mNewPin1.setKeyListener(DigitsKeyListener.getInstance());
+ mNewPin1.setMovementMethod(null);
+ mNewPin1.setOnClickListener(mClicked);
+
+ mNewPin2.setKeyListener(DigitsKeyListener.getInstance());
+ mNewPin2.setMovementMethod(null);
+ mNewPin2.setOnClickListener(mClicked);
+
+ mButton.setOnClickListener(mClicked);
+
+ mPUKCode.setKeyListener(DigitsKeyListener.getInstance());
+ mPUKCode.setMovementMethod(null);
+ mPUKCode.setOnClickListener(mClicked);
+
+ mPUKSubmit.setOnClickListener(mClicked);
+ }
}
private void resolveIntent() {
diff --git a/src/com/android/phone/DiagnosticDataCollector.java b/src/com/android/phone/DiagnosticDataCollector.java
index e997270..e0b1dac 100644
--- a/src/com/android/phone/DiagnosticDataCollector.java
+++ b/src/com/android/phone/DiagnosticDataCollector.java
@@ -16,7 +16,6 @@
package com.android.phone;
-import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.WorkerThread;
import android.os.DropBoxManager;
@@ -25,7 +24,6 @@
import android.telephony.AnomalyReporter;
import android.telephony.TelephonyManager;
import android.util.Log;
-import java.util.UUID;
import java.io.BufferedReader;
import java.io.IOException;
@@ -34,6 +32,7 @@
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
+import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -75,16 +74,16 @@
}
public void persistEmergencyDianosticData(@NonNull DataCollectorConfig.Adapter dc,
- @NonNull TelephonyManager.EmergencyCallDiagnosticParams edp, @NonNull String tag) {
+ @NonNull TelephonyManager.EmergencyCallDiagnosticData ecdData, @NonNull String tag) {
- if (edp.isTelephonyDumpSysCollectionEnabled()) {
+ if (ecdData.isTelephonyDumpsysCollectionEnabled()) {
persistTelephonyState(dc, tag);
}
- if (edp.isTelecomDumpSysCollectionEnabled()) {
+ if (ecdData.isTelecomDumpsysCollectionEnabled()) {
persistTelecomState(dc, tag);
}
- if (edp.isLogcatCollectionEnabled()) {
- persistLogcat(dc, tag, edp.getLogcatStartTime());
+ if (ecdData.isLogcatCollectionEnabled()) {
+ persistLogcat(dc, tag, ecdData.getLogcatCollectionStartTimeMillis());
}
}
diff --git a/src/com/android/phone/EnableIccPinScreen.java b/src/com/android/phone/EnableIccPinScreen.java
index 160978f..092fa64 100644
--- a/src/com/android/phone/EnableIccPinScreen.java
+++ b/src/com/android/phone/EnableIccPinScreen.java
@@ -17,10 +17,12 @@
package com.android.phone;
import android.app.Activity;
+import android.content.Context;
import android.os.AsyncResult;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.os.UserManager;
import android.text.TextUtils;
import android.text.method.DigitsKeyListener;
import android.util.Log;
@@ -41,11 +43,13 @@
private static final int ENABLE_ICC_PIN_COMPLETE = 100;
private static final boolean DBG = false;
+ private UserManager mUserManager;
private LinearLayout mPinFieldContainer;
private EditText mPinField;
private TextView mStatusField;
private boolean mEnable;
private Phone mPhone;
+ private boolean mDisallowedConfig = false;
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
@@ -64,6 +68,11 @@
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ mUserManager = this.getSystemService(UserManager.class);
+ if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
+ mDisallowedConfig = true;
+ }
+
setContentView(R.layout.enable_sim_pin_screen);
setupView();
@@ -76,12 +85,20 @@
private void setupView() {
mPinField = (EditText) findViewById(R.id.pin);
- mPinField.setKeyListener(DigitsKeyListener.getInstance());
- mPinField.setMovementMethod(null);
- mPinField.setOnClickListener(mClicked);
-
mPinFieldContainer = (LinearLayout) findViewById(R.id.pinc);
mStatusField = (TextView) findViewById(R.id.status);
+
+ if (mDisallowedConfig) {
+ mPinField.setEnabled(false);
+ mPinField.setAlpha(.5f);
+
+ mPinFieldContainer.setEnabled(false);
+ mPinFieldContainer.setAlpha(.5f);
+ } else {
+ mPinField.setKeyListener(DigitsKeyListener.getInstance());
+ mPinField.setMovementMethod(null);
+ mPinField.setOnClickListener(mClicked);
+ }
}
private void showStatus(CharSequence statusMsg) {
diff --git a/src/com/android/phone/GsmUmtsCallOptions.java b/src/com/android/phone/GsmUmtsCallOptions.java
index 51d1b66..be5295d 100644
--- a/src/com/android/phone/GsmUmtsCallOptions.java
+++ b/src/com/android/phone/GsmUmtsCallOptions.java
@@ -16,16 +16,20 @@
package com.android.phone;
+import android.content.Context;
import android.os.Bundle;
import android.os.PersistableBundle;
+import android.os.UserManager;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
+import android.util.Log;
import android.view.MenuItem;
import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.flags.Flags;
public class GsmUmtsCallOptions extends PreferenceActivity {
private static final String LOG_TAG = "GsmUmtsCallOptions";
@@ -79,10 +83,23 @@
isAirplaneModeOff = PhoneGlobals.AIRPLANE_ON != airplaneMode;
}
+ // If mobile network configs are restricted, then hide the GsmUmtsCallForwardOptions,
+ // GsmUmtsAdditionalCallOptions, and GsmUmtsCallBarringOptions.
+ UserManager userManager = (UserManager) subInfoHelper.getPhone().getContext()
+ .getSystemService(Context.USER_SERVICE);
+ boolean mobileNetworkConfigsRestricted =
+ userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
+ if (Flags.ensureAccessToCallSettingsIsRestricted() && mobileNetworkConfigsRestricted) {
+ Log.i(LOG_TAG, "Mobile network configs are restricted, hiding GSM call "
+ + "forwarding, additional call settings, and call options.");
+ }
+
Preference callForwardingPref = prefScreen.findPreference(CALL_FORWARDING_KEY);
if (callForwardingPref != null) {
if (b != null && b.getBoolean(
- CarrierConfigManager.KEY_CALL_FORWARDING_VISIBILITY_BOOL)) {
+ CarrierConfigManager.KEY_CALL_FORWARDING_VISIBILITY_BOOL) &&
+ (!Flags.ensureAccessToCallSettingsIsRestricted() ||
+ !mobileNetworkConfigsRestricted)) {
callForwardingPref.setIntent(
subInfoHelper.getIntent(GsmUmtsCallForwardOptions.class));
callForwardingPref.setEnabled(isAirplaneModeOff);
@@ -97,7 +114,9 @@
if (b != null && (b.getBoolean(
CarrierConfigManager.KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL)
|| b.getBoolean(
- CarrierConfigManager.KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL))) {
+ CarrierConfigManager.KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL)) &&
+ (!Flags.ensureAccessToCallSettingsIsRestricted() ||
+ !mobileNetworkConfigsRestricted)) {
additionalGsmSettingsPref.setIntent(
subInfoHelper.getIntent(GsmUmtsAdditionalCallOptions.class));
additionalGsmSettingsPref.setEnabled(isAirplaneModeOff);
@@ -108,7 +127,9 @@
Preference callBarringPref = prefScreen.findPreference(CALL_BARRING_KEY);
if (callBarringPref != null) {
- if (b != null && b.getBoolean(CarrierConfigManager.KEY_CALL_BARRING_VISIBILITY_BOOL)) {
+ if (b != null && b.getBoolean(CarrierConfigManager.KEY_CALL_BARRING_VISIBILITY_BOOL) &&
+ (!Flags.ensureAccessToCallSettingsIsRestricted() ||
+ !mobileNetworkConfigsRestricted)) {
callBarringPref.setIntent(subInfoHelper.getIntent(GsmUmtsCallBarringOptions.class));
callBarringPref.setEnabled(isAirplaneModeOff);
} else {
diff --git a/src/com/android/phone/IccNetworkDepersonalizationPanel.java b/src/com/android/phone/IccNetworkDepersonalizationPanel.java
index a4ec8a4..7099476 100644
--- a/src/com/android/phone/IccNetworkDepersonalizationPanel.java
+++ b/src/com/android/phone/IccNetworkDepersonalizationPanel.java
@@ -112,7 +112,7 @@
}
public static void dialogDismiss(int phoneId) {
- if (phoneId >= sShowingDialog.length) {
+ if (phoneId >= sShowingDialog.length || !SubscriptionManager.isValidPhoneId(phoneId)) {
Log.e(TAG, "[IccNetworkDepersonalizationPanel] - dismiss; invalid phoneId " + phoneId);
return;
}
diff --git a/src/com/android/phone/ImsProvisioningController.java b/src/com/android/phone/ImsProvisioningController.java
index a62980e..b2e34ae 100644
--- a/src/com/android/phone/ImsProvisioningController.java
+++ b/src/com/android/phone/ImsProvisioningController.java
@@ -839,7 +839,7 @@
mSubscriptionManager = mApp.getSystemService(SubscriptionManager.class);
mTelephonyRegistryManager = mApp.getSystemService(TelephonyRegistryManager.class);
mTelephonyRegistryManager.addOnSubscriptionsChangedListener(
- mSubChangedListener, mSubChangedListener.getHandlerExecutor());
+ mSubChangedListener, mHandler::post);
mImsProvisioningLoader = imsProvisioningLoader;
PhoneConfigurationManager.registerForMultiSimConfigChange(mHandler,
diff --git a/src/com/android/phone/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java
index 3f35454..a778f6a 100644
--- a/src/com/android/phone/ImsRcsController.java
+++ b/src/com/android/phone/ImsRcsController.java
@@ -16,7 +16,13 @@
package com.android.phone;
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY_IMS;
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION;
+import static android.telephony.TelephonyManager.ENABLE_FEATURE_MAPPING;
+
import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
@@ -53,6 +59,7 @@
import com.android.internal.telephony.ISipDialogStateCallback;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.TelephonyPermissions;
+import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.ims.ImsResolver;
import com.android.services.telephony.rcs.RcsFeatureController;
import com.android.services.telephony.rcs.SipTransportController;
@@ -74,6 +81,8 @@
private PhoneGlobals mApp;
private TelephonyRcsService mRcsService;
private ImsResolver mImsResolver;
+ private FeatureFlags mFeatureFlags;
+ private PackageManager mPackageManager;
// set by shell cmd phone src set-device-enabled true/false
private Boolean mSingleRegistrationOverride;
@@ -90,10 +99,10 @@
* Initialize the singleton ImsRcsController instance.
* This is only done once, at startup, from PhoneApp.onCreate().
*/
- static ImsRcsController init(PhoneGlobals app) {
+ static ImsRcsController init(PhoneGlobals app, FeatureFlags featureFlags) {
synchronized (ImsRcsController.class) {
if (sInstance == null) {
- sInstance = new ImsRcsController(app);
+ sInstance = new ImsRcsController(app, featureFlags);
} else {
Log.wtf(TAG, "init() called multiple times! sInstance = " + sInstance);
}
@@ -102,9 +111,11 @@
}
/** Private constructor; @see init() */
- private ImsRcsController(PhoneGlobals app) {
+ private ImsRcsController(PhoneGlobals app, FeatureFlags featureFlags) {
Log.i(TAG, "ImsRcsController");
mApp = app;
+ mFeatureFlags = featureFlags;
+ mPackageManager = mApp.getPackageManager();
TelephonyFrameworkInitializer
.getTelephonyServiceManager().getTelephonyImsServiceRegisterer().register(this);
mImsResolver = ImsResolver.getInstance();
@@ -118,6 +129,10 @@
public void registerImsRegistrationCallback(int subId, IImsRegistrationCallback callback) {
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "registerImsRegistrationCallback");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "registerImsRegistrationCallback");
+
final long token = Binder.clearCallingIdentity();
try {
getRcsFeatureController(subId).registerImsRegistrationCallback(subId, callback);
@@ -136,6 +151,10 @@
public void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback callback) {
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "unregisterImsRegistrationCallback");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "unregisterImsRegistrationCallback");
+
final long token = Binder.clearCallingIdentity();
try {
getRcsFeatureController(subId).unregisterImsRegistrationCallback(subId, callback);
@@ -153,6 +172,10 @@
public void getImsRcsRegistrationState(int subId, IIntegerConsumer consumer) {
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getImsRcsRegistrationState");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "getImsRcsRegistrationState");
+
final long token = Binder.clearCallingIdentity();
try {
getRcsFeatureController(subId).getRegistrationState(regState -> {
@@ -175,6 +198,10 @@
public void getImsRcsRegistrationTransportType(int subId, IIntegerConsumer consumer) {
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getImsRcsRegistrationTransportType");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "getImsRcsRegistrationTransportType");
+
final long token = Binder.clearCallingIdentity();
try {
getRcsFeatureController(subId).getRegistrationTech(regTech -> {
@@ -204,6 +231,10 @@
@Override
public void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback) {
enforceReadPrivilegedPermission("registerRcsAvailabilityCallback");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "registerRcsAvailabilityCallback");
+
final long token = Binder.clearCallingIdentity();
try {
getRcsFeatureController(subId).registerRcsAvailabilityCallback(subId, callback);
@@ -224,6 +255,10 @@
@Override
public void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback) {
enforceReadPrivilegedPermission("unregisterRcsAvailabilityCallback");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "unregisterRcsAvailabilityCallback");
+
final long token = Binder.clearCallingIdentity();
try {
getRcsFeatureController(subId).unregisterRcsAvailabilityCallback(subId, callback);
@@ -247,6 +282,10 @@
@RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
@ImsRegistrationImplBase.ImsRegistrationTech int radioTech) {
enforceReadPrivilegedPermission("isCapable");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isCapable");
+
final long token = Binder.clearCallingIdentity();
try {
return getRcsFeatureController(subId).isCapable(capability, radioTech);
@@ -273,6 +312,10 @@
@RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
@ImsRegistrationImplBase.ImsRegistrationTech int radioTech) {
enforceReadPrivilegedPermission("isAvailable");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isAvailable");
+
final long token = Binder.clearCallingIdentity();
try {
return getRcsFeatureController(subId).isAvailable(capability, radioTech);
@@ -290,6 +333,10 @@
List<Uri> contactNumbers, IRcsUceControllerCallback c) {
enforceAccessUserCapabilityExchangePermission("requestCapabilities");
enforceReadContactsPermission("requestCapabilities");
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ FEATURE_TELEPHONY_IMS, "requestCapabilities");
+
final long token = Binder.clearCallingIdentity();
try {
UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
@@ -311,6 +358,10 @@
String callingFeatureId, Uri contactNumber, IRcsUceControllerCallback c) {
enforceAccessUserCapabilityExchangePermission("requestAvailability");
enforceReadContactsPermission("requestAvailability");
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ FEATURE_TELEPHONY_IMS, "requestAvailability");
+
final long token = Binder.clearCallingIdentity();
try {
UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
@@ -330,6 +381,10 @@
@Override
public @PublishState int getUcePublishState(int subId) {
enforceReadPrivilegedPermission("getUcePublishState");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "getUcePublishState");
+
final int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
boolean isSupportPublishingState = false;
@@ -485,6 +540,10 @@
@Override
public void registerUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c) {
enforceReadPrivilegedPermission("registerUcePublishStateCallback");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "registerUcePublishStateCallback");
+
final int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
boolean isSupportPublishingState = false;
@@ -510,6 +569,10 @@
@Override
public void unregisterUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c) {
enforceReadPrivilegedPermission("unregisterUcePublishStateCallback");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "unregisterUcePublishStateCallback");
+
final long token = Binder.clearCallingIdentity();
try {
UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
@@ -534,6 +597,10 @@
+ "isUceSettingEnabled");
return false;
}
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ FEATURE_TELEPHONY_IMS, "isUceSettingEnabled");
+
final long token = Binder.clearCallingIdentity();
try {
return SubscriptionManager.getBooleanSubscriptionProperty(subId,
@@ -546,6 +613,10 @@
@Override
public void setUceSettingEnabled(int subId, boolean isEnabled) {
enforceModifyPermission();
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "setUceSettingEnabled");
+
final long token = Binder.clearCallingIdentity();
try {
SubscriptionManager.setSubscriptionProperty(subId,
@@ -680,6 +751,10 @@
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
}
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION, "registerSipDialogStateCallback");
+
try {
SipTransportController transport = getRcsFeatureController(subId).getFeature(
SipTransportController.class);
@@ -707,6 +782,10 @@
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
}
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION, "unregisterSipDialogStateCallback");
+
try {
SipTransportController transport = getRcsFeatureController(subId).getFeature(
SipTransportController.class);
@@ -897,6 +976,40 @@
PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION);
}
+ /**
+ * Get the current calling package name.
+ * @return the current calling package name
+ */
+ @Nullable
+ private String getCurrentPackageName() {
+ if (mPackageManager == null) return null;
+ String[] callingUids = mPackageManager.getPackagesForUid(Binder.getCallingUid());
+ return (callingUids == null) ? null : callingUids[0];
+ }
+
+ /**
+ * Make sure the device has required telephony feature
+ *
+ * @throws UnsupportedOperationException if the device does not have required telephony feature
+ */
+ private void enforceTelephonyFeatureWithException(@Nullable String callingPackage,
+ @NonNull String telephonyFeature, @NonNull String methodName) {
+ if (callingPackage == null || mPackageManager == null) {
+ return;
+ }
+
+ if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
+ || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
+ Binder.getCallingUserHandle())) {
+ return;
+ }
+
+ if (!mPackageManager.hasSystemFeature(telephonyFeature)) {
+ throw new UnsupportedOperationException(
+ methodName + " is unsupported without " + telephonyFeature);
+ }
+ }
+
void setRcsService(TelephonyRcsService rcsService) {
mRcsService = rcsService;
}
diff --git a/src/com/android/phone/ImsStateCallbackController.java b/src/com/android/phone/ImsStateCallbackController.java
index edad754..019c1ca 100644
--- a/src/com/android/phone/ImsStateCallbackController.java
+++ b/src/com/android/phone/ImsStateCallbackController.java
@@ -739,7 +739,7 @@
updateFeatureControllerSize(numSlots);
mTelephonyRegistryManager.addOnSubscriptionsChangedListener(
- mSubChangedListener, mSubChangedListener.getHandlerExecutor());
+ mSubChangedListener, mHandler::post);
PhoneConfigurationManager.registerForMultiSimConfigChange(mHandler,
EVENT_MSIM_CONFIGURATION_CHANGE, null);
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index b28bd5c..3cd9a8b 100644
--- a/src/com/android/phone/NotificationMgr.java
+++ b/src/com/android/phone/NotificationMgr.java
@@ -64,6 +64,8 @@
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.RILConstants;
import com.android.internal.telephony.TelephonyCapabilities;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.FeatureFlagsImpl;
import com.android.internal.telephony.util.NotificationChannelController;
import com.android.phone.settings.VoicemailSettingsActivity;
@@ -146,6 +148,9 @@
// maps each subId to selected network operator name.
private SparseArray<String> mSelectedNetworkOperatorName = new SparseArray<>();
+ // feature flags
+ private final FeatureFlags mFeatureFlags;
+
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
@@ -177,6 +182,7 @@
mSubscriptionManager = SubscriptionManager.from(mContext);
mTelecomManager = app.getSystemService(TelecomManager.class);
mTelephonyManager = (TelephonyManager) app.getSystemService(Context.TELEPHONY_SERVICE);
+ mFeatureFlags = new FeatureFlagsImpl();
}
/**
@@ -409,7 +415,22 @@
}
}
} else {
- cancelAsUser(Integer.toString(subId) /* tag */, VOICEMAIL_NOTIFICATION, UserHandle.ALL);
+ UserHandle subAssociatedUserHandle =
+ mSubscriptionManager.getSubscriptionUserHandle(subId);
+ List<UserHandle> users = getUsersExcludeDying();
+ for (UserHandle userHandle : users) {
+ boolean isManagedUser = mUserManager.isManagedProfile(userHandle.getIdentifier());
+ if (!hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, userHandle)
+ && (userHandle.equals(subAssociatedUserHandle)
+ || (subAssociatedUserHandle == null && !isManagedUser))
+ && !maybeSendVoicemailNotificationUsingDefaultDialer(phone, 0, null, null,
+ false, userHandle, isRefresh)) {
+ cancelAsUser(
+ Integer.toString(subId) /* tag */,
+ VOICEMAIL_NOTIFICATION,
+ userHandle);
+ }
+ }
}
}
@@ -813,10 +834,73 @@
* @param subId The subscription ID
*/
void updateNetworkSelection(int serviceState, int subId) {
+ if (!mFeatureFlags.dismissNetworkSelectionNotificationOnSimDisable()) {
+ updateNetworkSelectionForFeatureDisabled(serviceState, subId);
+ return;
+ }
+
+ // for dismissNetworkSelectionNotificationOnSimDisable feature enabled.
int phoneId = SubscriptionManager.getPhoneId(subId);
Phone phone = SubscriptionManager.isValidPhoneId(phoneId) ?
PhoneFactory.getPhone(phoneId) : PhoneFactory.getDefaultPhone();
if (TelephonyCapabilities.supportsNetworkSelection(phone)) {
+ if (SubscriptionManager.isValidSubscriptionId(subId)
+ && mSubscriptionManager.isActiveSubId(subId)) {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+ String selectedNetworkOperatorName =
+ sp.getString(Phone.NETWORK_SELECTION_NAME_KEY + subId, "");
+ // get the shared preference of network_selection.
+ // empty is auto mode, otherwise it is the operator alpha name
+ // in case there is no operator name, check the operator numeric
+ if (TextUtils.isEmpty(selectedNetworkOperatorName)) {
+ selectedNetworkOperatorName =
+ sp.getString(Phone.NETWORK_SELECTION_KEY + subId, "");
+ }
+ boolean isManualSelection;
+ // if restoring manual selection is controlled by framework, then get network
+ // selection from shared preference, otherwise get from real network indicators.
+ boolean restoreSelection = !mContext.getResources().getBoolean(
+ com.android.internal.R.bool.skip_restoring_network_selection);
+ if (restoreSelection) {
+ isManualSelection = !TextUtils.isEmpty(selectedNetworkOperatorName);
+ } else {
+ isManualSelection = phone.getServiceStateTracker().mSS.getIsManualSelection();
+ }
+
+ if (DBG) {
+ log("updateNetworkSelection()..." + "state = " + serviceState + " new network "
+ + (isManualSelection ? selectedNetworkOperatorName : ""));
+ }
+
+ if (isManualSelection) {
+ mSelectedNetworkOperatorName.put(subId, selectedNetworkOperatorName);
+ shouldShowNotification(serviceState, subId);
+ } else {
+ dismissNetworkSelectionNotification(subId);
+ clearUpNetworkSelectionNotificationParam(subId);
+ }
+ } else {
+ if (DBG) {
+ log("updateNetworkSelection()... state = " + serviceState
+ + " not updating network due to invalid subId " + subId);
+ }
+ dismissNetworkSelectionNotificationForInactiveSubId();
+ }
+ }
+ }
+
+ /**
+ * Update notification about no service of user selected operator.
+ * For dismissNetworkSelectionNotificationOnSimDisable feature disabled.
+ *
+ * @param serviceState Phone service state
+ * @param subId The subscription ID
+ */
+ private void updateNetworkSelectionForFeatureDisabled(int serviceState, int subId) {
+ int phoneId = SubscriptionManager.getPhoneId(subId);
+ Phone phone = SubscriptionManager.isValidPhoneId(phoneId)
+ ? PhoneFactory.getPhone(phoneId) : PhoneFactory.getDefaultPhone();
+ if (TelephonyCapabilities.supportsNetworkSelection(phone)) {
if (SubscriptionManager.isValidSubscriptionId(subId)) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
String selectedNetworkOperatorName =
@@ -866,7 +950,10 @@
}
}
- private void dismissNetworkSelectionNotificationForInactiveSubId() {
+ /**
+ * Dismiss the network selection "no service" notification for all inactive subscriptions.
+ */
+ public void dismissNetworkSelectionNotificationForInactiveSubId() {
for (int i = 0; i < mSelectedUnavailableNotify.size(); i++) {
int subId = mSelectedUnavailableNotify.keyAt(i);
if (!mSubscriptionManager.isActiveSubId(subId)) {
@@ -876,15 +963,6 @@
}
}
- /* package */ void postTransientNotification(int notifyId, CharSequence msg) {
- if (mToast != null) {
- mToast.cancel();
- }
-
- mToast = Toast.makeText(mContext, msg, Toast.LENGTH_LONG);
- mToast.show();
- }
-
private void log(String msg) {
Log.d(LOG_TAG, msg);
}
diff --git a/src/com/android/phone/PhoneApp.java b/src/com/android/phone/PhoneApp.java
index df151bf..bb663dc 100644
--- a/src/com/android/phone/PhoneApp.java
+++ b/src/com/android/phone/PhoneApp.java
@@ -38,7 +38,11 @@
mPhoneGlobals = new PhoneGlobals(this);
mPhoneGlobals.onCreate();
- TelecomAccountRegistry.getInstance(this).setupOnBoot();
+ TelecomAccountRegistry telecomAccountRegistry =
+ TelecomAccountRegistry.getInstance(this);
+ if (telecomAccountRegistry != null) {
+ telecomAccountRegistry.setupOnBoot();
+ }
}
}
}
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 7a61dd1..c3d7305 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -50,6 +50,7 @@
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyLocalConnection;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.LocalLog;
import android.util.Log;
@@ -71,16 +72,18 @@
import com.android.internal.telephony.data.DataEvaluation.DataDisallowedReason;
import com.android.internal.telephony.domainselection.DomainSelectionResolver;
import com.android.internal.telephony.emergency.EmergencyStateTracker;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.FeatureFlagsImpl;
import com.android.internal.telephony.ims.ImsResolver;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
import com.android.internal.telephony.satellite.SatelliteController;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
import com.android.internal.telephony.uicc.UiccPort;
import com.android.internal.telephony.uicc.UiccProfile;
import com.android.internal.util.IndentingPrintWriter;
import com.android.phone.settings.SettingsConstants;
import com.android.phone.vvm.CarrierVvmPackageInstalledReceiver;
-import com.android.services.telephony.domainselection.TelephonyDomainSelectionService;
import com.android.services.telephony.rcs.TelephonyRcsService;
import java.io.FileDescriptor;
@@ -163,7 +166,6 @@
public ImsStateCallbackController mImsStateCallbackController;
public ImsProvisioningController mImsProvisioningController;
CarrierConfigLoader configLoader;
- TelephonyDomainSelectionService mDomainSelectionService;
private Phone phoneInEcm;
@@ -181,6 +183,22 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"ROAMING_NOTIFICATION_REASON_"},
+ value = {
+ ROAMING_NOTIFICATION_REASON_DATA_SETTING_CHANGED,
+ ROAMING_NOTIFICATION_REASON_DATA_ROAMING_SETTING_CHANGED,
+ ROAMING_NOTIFICATION_REASON_CARRIER_CONFIG_CHANGED,
+ ROAMING_NOTIFICATION_REASON_SERVICE_STATE_CHANGED,
+ ROAMING_NOTIFICATION_REASON_DEFAULT_DATA_SUBS_CHANGED})
+ public @interface RoamingNotificationReason {}
+ private static final int ROAMING_NOTIFICATION_REASON_DATA_SETTING_CHANGED = 0;
+ private static final int ROAMING_NOTIFICATION_REASON_DATA_ROAMING_SETTING_CHANGED = 1;
+ private static final int ROAMING_NOTIFICATION_REASON_CARRIER_CONFIG_CHANGED = 2;
+ private static final int ROAMING_NOTIFICATION_REASON_SERVICE_STATE_CHANGED = 3;
+ private static final int ROAMING_NOTIFICATION_REASON_DEFAULT_DATA_SUBS_CHANGED = 4;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"ROAMING_NOTIFICATION_"},
value = {
ROAMING_NOTIFICATION_NO_NOTIFICATION,
@@ -193,8 +211,18 @@
private static final int ROAMING_NOTIFICATION_DISCONNECTED = 2;
@RoamingNotification
+ private int mCurrentRoamingNotification = ROAMING_NOTIFICATION_NO_NOTIFICATION;
+
+ /**
+ * Reasons that have already shown notification to prevent duplicate shows for the same reason.
+ */
+ private ArraySet<String> mShownNotificationReasons = new ArraySet<>();
+
+ // For reorganize_roaming_notification feature disabled.
+ @RoamingNotification
private int mPrevRoamingNotification = ROAMING_NOTIFICATION_NO_NOTIFICATION;
+ // For reorganize_roaming_notification feature disabled.
/** Operator numerics for which we've shown is-roaming notifications. **/
private ArraySet<String> mPrevRoamingOperatorNumerics = new ArraySet<>();
@@ -214,13 +242,15 @@
private final CarrierVvmPackageInstalledReceiver mCarrierVvmPackageInstalledReceiver =
new CarrierVvmPackageInstalledReceiver();
- private final SettingsObserver mSettingsObserver;
+ private SettingsObserver mSettingsObserver;
private BinderCallsStats.SettingsObserver mBinderCallsSettingsObserver;
// Mapping of phone ID to the associated TelephonyCallback. These should be registered without
// fine or coarse location since we only use ServiceState for
private PhoneAppCallback[] mTelephonyCallbacks;
+ private FeatureFlags mFeatureFlags = new FeatureFlagsImpl();
+
private class PhoneAppCallback extends TelephonyCallback implements
TelephonyCallback.ServiceStateListener {
private final int mSubId;
@@ -313,11 +343,23 @@
break;
case EVENT_DATA_ROAMING_DISCONNECTED:
- notificationMgr.showDataRoamingNotification(msg.arg1, false);
+ if (SubscriptionManagerService.getInstance()
+ .isEsimBootStrapProvisioningActiveForSubId(msg.arg1)) {
+ Log.i(LOG_TAG,
+ "skip notification/warnings during esim bootstrap activation");
+ } else {
+ notificationMgr.showDataRoamingNotification(msg.arg1, false);
+ }
break;
case EVENT_DATA_ROAMING_CONNECTED:
- notificationMgr.showDataRoamingNotification(msg.arg1, true);
+ if (SubscriptionManagerService.getInstance()
+ .isEsimBootStrapProvisioningActiveForSubId(msg.arg1)) {
+ Log.i(LOG_TAG,
+ "skip notification/warnings during esim bootstrap activation");
+ } else {
+ notificationMgr.showDataRoamingNotification(msg.arg1, true);
+ }
break;
case EVENT_DATA_ROAMING_OK:
@@ -333,17 +375,20 @@
break;
case EVENT_SIM_STATE_CHANGED:
- // Marks the event where the SIM goes into ready state.
- // Right now, this is only used for the PUK-unlocking
- // process.
EventSimStateChangedBag bag = (EventSimStateChangedBag)msg.obj;
+ // Dismiss the "No services" notification if the SIM is removed.
+ if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(bag.mIccStatus)) {
+ notificationMgr.dismissNetworkSelectionNotificationForInactiveSubId();
+ }
+
+ // Marks the event where the SIM goes into ready state.
+ // Right now, this is only used for the PUK-unlocking process.
if (IccCardConstants.INTENT_VALUE_ICC_READY.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.
+ // When the right event is triggered and there are UI objects in the
+ // foreground, we close them to display the lock panel.
if (mPUKEntryActivity != null) {
Log.i(LOG_TAG, "Dismiss puk entry activity");
mPUKEntryActivity.finish();
@@ -363,8 +408,19 @@
//TODO: handle message here;
break;
case EVENT_DATA_ROAMING_SETTINGS_CHANGED:
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ updateDataRoamingStatus(
+ ROAMING_NOTIFICATION_REASON_DATA_ROAMING_SETTING_CHANGED);
+ } else {
+ updateDataRoamingStatusForFeatureDisabled(null);
+ }
+ break;
case EVENT_MOBILE_DATA_SETTINGS_CHANGED:
- updateDataRoamingStatus();
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ updateDataRoamingStatus(ROAMING_NOTIFICATION_REASON_DATA_SETTING_CHANGED);
+ } else {
+ updateDataRoamingStatusForFeatureDisabled(null);
+ }
break;
case EVENT_CARRIER_CONFIG_CHANGED:
int subId = (Integer) msg.obj;
@@ -419,7 +475,13 @@
public PhoneGlobals(Context context) {
super(context);
sMe = this;
- mSettingsObserver = new SettingsObserver(context, mHandler);
+ if (mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()) {
+ if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ mSettingsObserver = new SettingsObserver(context, mHandler);
+ }
+ } else {
+ mSettingsObserver = new SettingsObserver(context, mHandler);
+ }
}
public void onCreate() {
@@ -427,6 +489,13 @@
ContentResolver resolver = getContentResolver();
+ if (mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()) {
+ if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ Log.v(LOG_TAG, "onCreate()... but not defined FEATURE_TELEPHONY");
+ return;
+ }
+ }
+
// Initialize the shim from frameworks/opt/telephony into packages/services/Telephony.
TelephonyLocalConnection.setInstance(new LocalConnectionImpl(this));
@@ -451,17 +520,17 @@
// Create DomainSelectionResolver always, but it MUST be initialized only when
// the device supports AOSP domain selection architecture and
// has new IRadio that supports its related HAL APIs.
- DomainSelectionResolver.make(this,
- getResources().getBoolean(R.bool.config_enable_aosp_domain_selection));
+ String dssComponentName = getResources().getString(
+ R.string.config_domain_selection_service_component_name);
+ DomainSelectionResolver.make(this, dssComponentName);
// Initialize the telephony framework
- PhoneFactory.makeDefaultPhones(this);
+ PhoneFactory.makeDefaultPhones(this, mFeatureFlags);
// Initialize the DomainSelectionResolver after creating the Phone instance
// to check the Radio HAL version.
if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
- mDomainSelectionService = new TelephonyDomainSelectionService(this);
- DomainSelectionResolver.getInstance().initialize(mDomainSelectionService);
+ DomainSelectionResolver.getInstance().initialize();
// Initialize EmergencyStateTracker if domain selection is supported
boolean isSuplDdsSwitchRequiredForEmergencyCall = getResources()
.getBoolean(R.bool.config_gnss_supl_requires_default_data_for_emergency);
@@ -513,7 +582,7 @@
// Create the SatelliteController singleton, which acts as a backend service for
// {@link android.telephony.satellite.SatelliteManager}.
- SatelliteController.make(this);
+ SatelliteController.make(this, mFeatureFlags);
// Create an instance of CdmaPhoneCallState and initialize it to IDLE
cdmaPhoneCallState = new CdmaPhoneCallState();
@@ -528,17 +597,17 @@
mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
- phoneMgr = PhoneInterfaceManager.init(this);
+ phoneMgr = PhoneInterfaceManager.init(this, mFeatureFlags);
- imsRcsController = ImsRcsController.init(this);
+ imsRcsController = ImsRcsController.init(this, mFeatureFlags);
- configLoader = CarrierConfigLoader.init(this);
+ configLoader = CarrierConfigLoader.init(this, mFeatureFlags);
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS)) {
mImsStateCallbackController =
ImsStateCallbackController.make(this, PhoneFactory.getPhones().length);
mTelephonyRcsService = new TelephonyRcsService(this,
- PhoneFactory.getPhones().length);
+ PhoneFactory.getPhones().length, mFeatureFlags);
mTelephonyRcsService.initialize();
imsRcsController.setRcsService(mTelephonyRcsService);
mImsProvisioningController =
@@ -573,6 +642,15 @@
intentFilter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
registerReceiver(mReceiver, intentFilter);
+ int defaultDataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
+ if (SubscriptionManager.isValidSubscriptionId(defaultDataSubId)) {
+ if (VDBG) {
+ Log.v(LOG_TAG, "Loaded initial default data sub: " + defaultDataSubId);
+ }
+ mDefaultDataSubId = defaultDataSubId;
+ registerSettingsObserver();
+ updateDataRoamingStatus(ROAMING_NOTIFICATION_REASON_DEFAULT_DATA_SUBS_CHANGED);
+ }
PhoneConfigurationManager.registerForMultiSimConfigChange(
mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED, null);
@@ -728,13 +806,14 @@
}
private void handleAirplaneModeChange(boolean isAirplaneNewlyOn) {
+ Log.i(LOG_TAG, "handleAirplaneModeChange: isAirplaneNewlyOn=" + isAirplaneNewlyOn);
int cellState =
Settings.Global.getInt(
getContentResolver(), Settings.Global.CELL_ON, PhoneConstants.CELL_ON_FLAG);
switch (cellState) {
case PhoneConstants.CELL_OFF_FLAG:
- // Airplane mode does not affect the cell radio if user
- // has turned it off.
+ // Airplane mode does not affect the cell radio if user has turned it off.
+ Log.i(LOG_TAG, "Ignore airplane mode change due to cell off.");
break;
case PhoneConstants.CELL_ON_FLAG:
maybeTurnCellOff(isAirplaneNewlyOn);
@@ -774,7 +853,11 @@
/** Clear fields on power off radio **/
private void clearCacheOnRadioOff() {
// Re-show is-roaming notifications after APM mode
- mPrevRoamingOperatorNumerics.clear();
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ mShownNotificationReasons.clear();
+ } else {
+ mPrevRoamingOperatorNumerics.clear();
+ }
}
private void setRadioPowerOn() {
@@ -805,12 +888,16 @@
} else {
Log.i(LOG_TAG, "Ignoring airplane mode: settings prevent cell radio power off");
}
+ } else {
+ Log.i(LOG_TAG, "Ignoring airplane mode: not newly on");
}
}
private void maybeTurnCellOn(boolean isAirplaneNewlyOn) {
if (!isAirplaneNewlyOn) {
setRadioPowerOn();
+ } else {
+ Log.i(LOG_TAG, "Ignoring airplane mode off: radio is already on.");
}
}
@@ -867,7 +954,11 @@
} else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
// Roaming status could be overridden by carrier config, so we need to update it.
if (VDBG) Log.v(LOG_TAG, "carrier config changed.");
- updateDataRoamingStatus();
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ updateDataRoamingStatus(ROAMING_NOTIFICATION_REASON_CARRIER_CONFIG_CHANGED);
+ } else {
+ updateDataRoamingStatusForFeatureDisabled(null);
+ }
updateLimitedSimFunctionForDualSim();
int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
@@ -882,7 +973,12 @@
registerSettingsObserver();
Phone phone = getPhone(mDefaultDataSubId);
if (phone != null) {
- updateDataRoamingStatus();
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ updateDataRoamingStatus(
+ ROAMING_NOTIFICATION_REASON_DEFAULT_DATA_SUBS_CHANGED);
+ } else {
+ updateDataRoamingStatusForFeatureDisabled(null);
+ }
}
}
}
@@ -898,7 +994,11 @@
+ mDefaultDataSubId + ", ss roaming=" + serviceState.getDataRoaming());
}
if (subId == mDefaultDataSubId) {
- updateDataRoamingStatus(serviceState.getOperatorNumeric());
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ updateDataRoamingStatus(ROAMING_NOTIFICATION_REASON_SERVICE_STATE_CHANGED);
+ } else {
+ updateDataRoamingStatusForFeatureDisabled(serviceState.getOperatorNumeric());
+ }
}
}
@@ -906,11 +1006,149 @@
* When roaming, if mobile data cannot be established due to data roaming not enabled, we need
* to notify the user so they can enable it through settings. Vise versa if the condition
* changes, we need to dismiss the notification.
+ * @param reason to inform which event is called for notification update.
*/
- private void updateDataRoamingStatus() {
- updateDataRoamingStatus(null /*roamingOperatorNumeric*/);
+ private void updateDataRoamingStatus(@RoamingNotificationReason int reason) {
+ if (VDBG) Log.v(LOG_TAG, "updateDataRoamingStatus");
+ Phone phone = getPhone(mDefaultDataSubId);
+ if (phone == null) {
+ Log.w(LOG_TAG, "Can't get phone with sub id = " + mDefaultDataSubId);
+ return;
+ }
+
+ ServiceState serviceState = phone.getServiceState();
+ if (serviceState == null) {
+ Log.e(LOG_TAG, "updateDataRoamingStatus: serviceState is null");
+ return;
+ }
+
+ String roamingNumeric = serviceState.getOperatorNumeric();
+ String roamingNumericReason = "RoamingNumeric=" + roamingNumeric;
+ String callingReason = "CallingReason=" + reason;
+ boolean dataIsNowRoaming = serviceState.getDataRoaming();
+ boolean dataAllowed;
+ boolean notAllowedDueToRoamingOff;
+ List<DataDisallowedReason> reasons = phone.getDataNetworkController()
+ .getInternetDataDisallowedReasons();
+ dataAllowed = reasons.isEmpty();
+ notAllowedDueToRoamingOff = (reasons.size() == 1
+ && reasons.contains(DataDisallowedReason.ROAMING_DISABLED));
+ StringBuilder sb = new StringBuilder("updateDataRoamingStatus");
+ sb.append(" dataAllowed=").append(dataAllowed);
+ sb.append(", reasons=").append(reasons);
+ sb.append(", dataIsNowRoaming=").append(dataIsNowRoaming);
+ sb.append(", ").append(roamingNumericReason);
+ sb.append(", ").append(callingReason);
+ mDataRoamingNotifLog.log(sb.toString());
+ if (VDBG) {
+ Log.v(LOG_TAG, sb.toString());
+ }
+
+ // Determine if a given roaming numeric has never been shown.
+ boolean shownInThisNumeric = false;
+ if (reason == ROAMING_NOTIFICATION_REASON_CARRIER_CONFIG_CHANGED
+ || reason == ROAMING_NOTIFICATION_REASON_SERVICE_STATE_CHANGED) {
+ shownInThisNumeric = mShownNotificationReasons.contains(roamingNumericReason);
+ }
+ // Determine if a notification has never been shown by given calling reason.
+ boolean shownForThisReason = mShownNotificationReasons.contains(callingReason);
+
+ if (!dataAllowed && notAllowedDueToRoamingOff) {
+ if (!shownInThisNumeric && roamingNumeric != null) {
+ mShownNotificationReasons.add(roamingNumericReason);
+ }
+ if (!shownForThisReason
+ && reason == ROAMING_NOTIFICATION_REASON_CARRIER_CONFIG_CHANGED) {
+ mShownNotificationReasons.add(callingReason);
+ }
+ // No need to show it again if we never cancelled it explicitly.
+ if (getCurrentRoamingNotification() == ROAMING_NOTIFICATION_DISCONNECTED) {
+ return;
+ }
+
+ // If the only reason of no data is data roaming disabled, then we notify the user
+ // so the user can turn on data roaming.
+ if (!shownInThisNumeric && !shownForThisReason) {
+ updateDataRoamingNotification(ROAMING_NOTIFICATION_DISCONNECTED);
+ } else {
+ // Don't show roaming notification if we've already shown for this MccMnc
+ Log.d(LOG_TAG, "Skip roaming disconnected notification since already"
+ + " shownInThisNumeric=" + shownInThisNumeric
+ + " shownForThisReason=" + shownForThisReason);
+ // Dismiss notification if the other notification is shown.
+ if (getCurrentRoamingNotification() != ROAMING_NOTIFICATION_NO_NOTIFICATION) {
+ updateDataRoamingNotification(ROAMING_NOTIFICATION_NO_NOTIFICATION);
+ }
+ }
+ } else if (dataAllowed && dataIsNowRoaming) {
+ if (!shownInThisNumeric && roamingNumeric != null) {
+ mShownNotificationReasons.add(roamingNumericReason);
+ }
+ if (!shownForThisReason
+ && reason == ROAMING_NOTIFICATION_REASON_CARRIER_CONFIG_CHANGED) {
+ mShownNotificationReasons.add(callingReason);
+ }
+ boolean shouldShowRoamingNotification = shouldShowRoamingNotification(roamingNumeric);
+ // No need to show it again if we never cancelled it explicitly.
+ if (getCurrentRoamingNotification() == ROAMING_NOTIFICATION_CONNECTED) {
+ return;
+ }
+
+ // Inform users that roaming charges may apply.
+ if (!shownInThisNumeric && !shownForThisReason && shouldShowRoamingNotification) {
+ updateDataRoamingNotification(ROAMING_NOTIFICATION_CONNECTED);
+ } else {
+ // Don't show roaming notification if we've already shown for this MccMnc or
+ // disabled from carrier config.
+ Log.d(LOG_TAG, "Skip roaming connected notification since already"
+ + " shownInThisNumeric:" + shownInThisNumeric
+ + " shownForThisReason:" + shownForThisReason
+ + " shouldShowRoamingNotification:" + shouldShowRoamingNotification);
+ // Dismiss notification if the other notification is shown.
+ if (getCurrentRoamingNotification() != ROAMING_NOTIFICATION_NO_NOTIFICATION) {
+ updateDataRoamingNotification(ROAMING_NOTIFICATION_NO_NOTIFICATION);
+ }
+ }
+ } else if (getCurrentRoamingNotification() != ROAMING_NOTIFICATION_NO_NOTIFICATION) {
+ // Otherwise we either 1) we are not roaming or 2) roaming is off but ROAMING_DISABLED
+ // is not the only data disable reason. In this case we dismiss the notification we
+ // showed earlier.
+ updateDataRoamingNotification(ROAMING_NOTIFICATION_NO_NOTIFICATION);
+ }
}
+ private void updateDataRoamingNotification(@RoamingNotification int roamingNotification) {
+ int event;
+ switch (roamingNotification) {
+ case ROAMING_NOTIFICATION_NO_NOTIFICATION:
+ Log.d(LOG_TAG, "Dismiss roaming notification");
+ mDataRoamingNotifLog.log("Hide roaming.");
+ event = EVENT_DATA_ROAMING_OK;
+ break;
+ case ROAMING_NOTIFICATION_CONNECTED:
+ Log.d(LOG_TAG, "Show roaming connected notification");
+ mDataRoamingNotifLog.log("Show roaming on.");
+ event = EVENT_DATA_ROAMING_CONNECTED;
+ break;
+ case ROAMING_NOTIFICATION_DISCONNECTED:
+ Log.d(LOG_TAG, "Show roaming disconnected notification");
+ mDataRoamingNotifLog.log("Show roaming off.");
+ event = EVENT_DATA_ROAMING_DISCONNECTED;
+ break;
+ default:
+ Log.d(LOG_TAG, "Should never reach here.");
+ mDataRoamingNotifLog.log("Should never reach here.");
+ return;
+ }
+ mCurrentRoamingNotification = roamingNotification;
+ mHandler.obtainMessage(event, mDefaultDataSubId, 0).sendToTarget();
+ }
+
+ private @RoamingNotification int getCurrentRoamingNotification() {
+ return mCurrentRoamingNotification;
+ }
+
+ // For reorganize_roaming_notification feature disabled.
/**
* When roaming, if mobile data cannot be established due to data roaming not enabled, we need
* to notify the user so they can enable it through settings. Vise versa if the condition
@@ -918,8 +1156,9 @@
* @param roamingOperatorNumeric The operator numeric for the current roaming. {@code null} if
* the current roaming operator numeric didn't change.
*/
- private void updateDataRoamingStatus(@Nullable String roamingOperatorNumeric) {
- if (VDBG) Log.v(LOG_TAG, "updateDataRoamingStatus");
+ private void updateDataRoamingStatusForFeatureDisabled(
+ @Nullable String roamingOperatorNumeric) {
+ if (VDBG) Log.v(LOG_TAG, "updateDataRoamingStatusForFeatureDisabled");
Phone phone = getPhone(mDefaultDataSubId);
if (phone == null) {
Log.w(LOG_TAG, "Can't get phone with sub id = " + mDefaultDataSubId);
@@ -959,10 +1198,11 @@
msg.arg1 = mDefaultDataSubId;
msg.sendToTarget();
} else if (dataAllowed && dataIsNowRoaming(mDefaultDataSubId)) {
- boolean isShowRoamingNotificationEnabled = getCarrierConfigForSubId(mDefaultDataSubId)
- .getBoolean(CarrierConfigManager
- .KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL);
- if (!isShowRoamingNotificationEnabled) return;
+ if (!shouldShowRoamingNotification(roamingOperatorNumeric != null
+ ? roamingOperatorNumeric : phone.getServiceState().getOperatorNumeric())) {
+ Log.d(LOG_TAG, "Skip showing roaming connected notification.");
+ return;
+ }
// Don't show roaming notification if we've already shown for this MccMnc
if (roamingOperatorNumeric != null
&& !mPrevRoamingOperatorNumerics.add(roamingOperatorNumeric)) {
@@ -999,6 +1239,46 @@
return getPhone(subId).getServiceState().getDataRoaming();
}
+ private boolean shouldShowRoamingNotification(String roamingNumeric) {
+ PersistableBundle config = getCarrierConfigForSubId(mDefaultDataSubId);
+ boolean showRoamingNotification = config.getBoolean(
+ CarrierConfigManager.KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL);
+
+ if (TextUtils.isEmpty(roamingNumeric) || !mFeatureFlags.hideRoamingIcon()) {
+ Log.d(LOG_TAG, "shouldShowRoamingNotification: roamingNumeric=" + roamingNumeric
+ + ", hideRoaming=" + mFeatureFlags.hideRoamingIcon());
+ return showRoamingNotification;
+ }
+
+ String[] includedMccMncs = config.getStringArray(CarrierConfigManager
+ .KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_INCLUDED_MCC_MNCS_STRING_ARRAY);
+ if (includedMccMncs != null) {
+ for (String mccMnc : includedMccMncs) {
+ if (roamingNumeric.equals(mccMnc)) {
+ Log.d(LOG_TAG, "shouldShowRoamingNotification: show for MCC/MNC " + mccMnc);
+ return showRoamingNotification;
+ }
+ }
+ }
+
+ String[] excludedMccs = config.getStringArray(CarrierConfigManager
+ .KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY);
+ String roamingMcc = roamingNumeric.length() < 3 ? "" : roamingNumeric.substring(0, 3);
+ if (excludedMccs != null && !TextUtils.isEmpty(roamingMcc)) {
+ for (String mcc : excludedMccs) {
+ if (roamingMcc.equals(mcc)) {
+ Log.d(LOG_TAG, "shouldShowRoamingNotification: ignore for MCC " + mcc);
+ return false;
+ }
+ }
+ }
+
+ if (showRoamingNotification) {
+ Log.d(LOG_TAG, "shouldShowRoamingNotification: show for numeric " + roamingNumeric);
+ }
+ return showRoamingNotification;
+ }
+
private void updateLimitedSimFunctionForDualSim() {
if (DBG) Log.d(LOG_TAG, "updateLimitedSimFunctionForDualSim");
// check conditions to display limited SIM function notification under dual SIM
@@ -1086,10 +1366,21 @@
IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
pw.println("------- PhoneGlobals -------");
pw.increaseIndent();
- pw.println("mPrevRoamingNotification=" + mPrevRoamingNotification);
+ pw.println("FeatureFlags:");
+ pw.increaseIndent();
+ pw.println("reorganizeRoamingNotification="
+ + mFeatureFlags.reorganizeRoamingNotification());
+ pw.println("dismissNetworkSelectionNotificationOnSimDisable="
+ + mFeatureFlags.dismissNetworkSelectionNotificationOnSimDisable());
+ pw.decreaseIndent();
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ pw.println("mCurrentRoamingNotification=" + mCurrentRoamingNotification);
+ } else {
+ pw.println("mPrevRoamingNotification=" + mPrevRoamingNotification);
+ }
pw.println("mDefaultDataSubId=" + mDefaultDataSubId);
- pw.println("mDataRoamingNotifLog:");
pw.println("isSmsCapable=" + TelephonyManager.from(this).isSmsCapable());
+ pw.println("mDataRoamingNotifLog:");
pw.increaseIndent();
mDataRoamingNotifLog.dump(fd, pw, args);
pw.decreaseIndent();
@@ -1123,11 +1414,12 @@
e.printStackTrace();
}
pw.decreaseIndent();
- if (mDomainSelectionService != null) {
- mDomainSelectionService.dump(fd, pw, args);
- }
pw.decreaseIndent();
- pw.println("mPrevRoamingOperatorNumerics:" + mPrevRoamingOperatorNumerics);
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ pw.println("mShownNotificationReasons=" + mShownNotificationReasons);
+ } else {
+ pw.println("mPrevRoamingOperatorNumerics:" + mPrevRoamingOperatorNumerics);
+ }
pw.println("------- End PhoneGlobals -------");
}
}
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 0107b1c..6118640 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -16,9 +16,17 @@
package com.android.phone;
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY_IMS;
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.permission.flags.Flags.opEnableMobileDataByUser;
+import static android.telephony.TelephonyManager.ENABLE_FEATURE_MAPPING;
import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK;
import static android.telephony.TelephonyManager.HAL_SERVICE_RADIO;
+import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_ACCESS_BARRED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_CDMA;
import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_GSM;
@@ -146,10 +154,14 @@
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.stub.ImsConfigImplBase;
import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.telephony.satellite.INtnSignalStrengthCallback;
+import android.telephony.satellite.ISatelliteCapabilitiesCallback;
import android.telephony.satellite.ISatelliteDatagramCallback;
+import android.telephony.satellite.ISatelliteModemStateCallback;
import android.telephony.satellite.ISatelliteProvisionStateCallback;
-import android.telephony.satellite.ISatelliteStateCallback;
import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
+import android.telephony.satellite.NtnSignalStrength;
+import android.telephony.satellite.NtnSignalStrengthCallback;
import android.telephony.satellite.SatelliteCapabilities;
import android.telephony.satellite.SatelliteDatagram;
import android.telephony.satellite.SatelliteDatagramCallback;
@@ -202,12 +214,14 @@
import com.android.internal.telephony.SmsApplication;
import com.android.internal.telephony.SmsController;
import com.android.internal.telephony.SmsPermissions;
+import com.android.internal.telephony.TelephonyCountryDetector;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.telephony.data.DataUtils;
import com.android.internal.telephony.domainselection.DomainSelectionResolver;
import com.android.internal.telephony.emergency.EmergencyNumberTracker;
import com.android.internal.telephony.euicc.EuiccConnector;
+import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.ims.ImsResolver;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
@@ -234,6 +248,8 @@
import com.android.phone.callcomposer.CallComposerPictureManager;
import com.android.phone.callcomposer.CallComposerPictureTransfer;
import com.android.phone.callcomposer.ImageData;
+import com.android.phone.satellite.accesscontrol.SatelliteAccessController;
+import com.android.phone.satellite.entitlement.SatelliteEntitlementController;
import com.android.phone.settings.PickSmsSubscriptionActivity;
import com.android.phone.slice.SlicePurchaseController;
import com.android.phone.utils.CarrierAllowListInfo;
@@ -241,6 +257,7 @@
import com.android.phone.vvm.RemoteVvmTaskManager;
import com.android.phone.vvm.VisualVoicemailSettingsUtil;
import com.android.phone.vvm.VisualVoicemailSmsFilterConfig;
+import com.android.server.feature.flags.Flags;
import com.android.services.telephony.TelecomAccountRegistry;
import com.android.services.telephony.TelephonyConnectionService;
import com.android.telephony.Rlog;
@@ -296,8 +313,6 @@
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;
- private static final int EVENT_INVOKE_OEM_RIL_REQUEST_RAW_DONE = 28;
private static final int CMD_TRANSMIT_APDU_BASIC_CHANNEL = 29;
private static final int EVENT_TRANSMIT_APDU_BASIC_CHANNEL_DONE = 30;
private static final int CMD_EXCHANGE_SIM_IO = 31;
@@ -397,27 +412,34 @@
// Toggling null cipher and integrity support was added in IRadioNetwork 2.1
private static final int MIN_NULL_CIPHER_AND_INTEGRITY_VERSION = 201;
+ // Cellular identifier disclosure transparency was added in IRadioNetwork 2.2
+ private static final int MIN_IDENTIFIER_DISCLOSURE_VERSION = 202;
+ // Null cipher notification support was added in IRadioNetwork 2.2
+ private static final int MIN_NULL_CIPHER_NOTIFICATION_VERSION = 202;
/** The singleton instance. */
private static PhoneInterfaceManager sInstance;
private static List<String> sThermalMitigationAllowlistedPackages = new ArrayList<>();
private final PhoneGlobals mApp;
+ private FeatureFlags mFeatureFlags;
+ private com.android.server.telecom.flags.FeatureFlags mTelecomFeatureFlags;
private final CallManager mCM;
private final ImsResolver mImsResolver;
private final SatelliteController mSatelliteController;
+ private final SatelliteAccessController mSatelliteAccessController;
private final UserManager mUserManager;
- private final AppOpsManager mAppOps;
private final MainThreadHandler mMainThreadHandler;
private final SharedPreferences mTelephonySharedPreferences;
private final PhoneConfigurationManager mPhoneConfigurationManager;
private final RadioInterfaceCapabilityController mRadioInterfaceCapabilities;
+ private AppOpsManager mAppOps;
+ private PackageManager mPackageManager;
/** User Activity */
private final AtomicBoolean mNotifyUserActivity;
private static final int USER_ACTIVITY_NOTIFICATION_DELAY = 200;
-
private final Set<Integer> mCarrierPrivilegeTestOverrideSubIds = new ArraySet<>();
private static final String PREF_CARRIERS_ALPHATAG_PREFIX = "carrier_alphtag_";
@@ -1154,19 +1176,6 @@
handleNullReturnEvent(msg, "setAllowedNetworkTypesForReason");
break;
- case CMD_INVOKE_OEM_RIL_REQUEST_RAW:
- request = (MainThreadRequest)msg.obj;
- onCompleted = obtainMessage(EVENT_INVOKE_OEM_RIL_REQUEST_RAW_DONE, request);
- defaultPhone.invokeOemRilRequestRaw((byte[]) request.argument, onCompleted);
- break;
-
- case EVENT_INVOKE_OEM_RIL_REQUEST_RAW_DONE:
- ar = (AsyncResult)msg.obj;
- request = (MainThreadRequest)ar.userObj;
- request.result = ar;
- notifyRequester(request);
- break;
-
case CMD_SET_VOICEMAIL_NUMBER:
request = (MainThreadRequest) msg.obj;
onCompleted = obtainMessage(EVENT_SET_VOICEMAIL_NUMBER_DONE, request);
@@ -1579,7 +1588,7 @@
// This is for the implementation of carrierRestrictionStatus.
CallerCallbackInfo callbackInfo = (CallerCallbackInfo) request.argument;
Consumer<Integer> callback = callbackInfo.getConsumer();
- int callerCarrierId = callbackInfo.getCarrierId();
+ Set<Integer> callerCarrierIds = callbackInfo.getCarrierIds();
int lockStatus = TelephonyManager.CARRIER_RESTRICTION_STATUS_UNKNOWN;
if (ar.exception == null && ar.result instanceof CarrierRestrictionRules) {
CarrierRestrictionRules carrierRestrictionRules =
@@ -1594,8 +1603,10 @@
Rlog.e(LOG_TAG, "CarrierIdentifier exception = " + ex);
}
lockStatus = carrierRestrictionRules.getCarrierRestrictionStatus();
- if (carrierId != -1 && callerCarrierId == carrierId && lockStatus
- == TelephonyManager.CARRIER_RESTRICTION_STATUS_RESTRICTED) {
+ int restrictedStatus =
+ TelephonyManager.CARRIER_RESTRICTION_STATUS_RESTRICTED;
+ if (carrierId != -1 && callerCarrierIds.contains(carrierId) &&
+ lockStatus == restrictedStatus) {
lockStatus = TelephonyManager
.CARRIER_RESTRICTION_STATUS_RESTRICTED_TO_CALLER;
}
@@ -2226,8 +2237,8 @@
onCompleted = obtainMessage(EVENT_PURCHASE_PREMIUM_CAPABILITY_DONE, request);
PurchasePremiumCapabilityArgument arg =
(PurchasePremiumCapabilityArgument) request.argument;
- SlicePurchaseController.getInstance(request.phone).purchasePremiumCapability(
- arg.capability, onCompleted);
+ SlicePurchaseController.getInstance(request.phone, mFeatureFlags)
+ .purchasePremiumCapability(arg.capability, onCompleted);
break;
}
@@ -2440,10 +2451,10 @@
* Initialize the singleton PhoneInterfaceManager instance.
* This is only done once, at startup, from PhoneApp.onCreate().
*/
- /* package */ static PhoneInterfaceManager init(PhoneGlobals app) {
+ /* package */ static PhoneInterfaceManager init(PhoneGlobals app, FeatureFlags featureFlags) {
synchronized (PhoneInterfaceManager.class) {
if (sInstance == null) {
- sInstance = new PhoneInterfaceManager(app);
+ sInstance = new PhoneInterfaceManager(app, featureFlags);
} else {
Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance);
}
@@ -2452,8 +2463,10 @@
}
/** Private constructor; @see init() */
- private PhoneInterfaceManager(PhoneGlobals app) {
+ private PhoneInterfaceManager(PhoneGlobals app, FeatureFlags featureFlags) {
mApp = app;
+ mFeatureFlags = featureFlags;
+ mTelecomFeatureFlags = new com.android.server.telecom.flags.FeatureFlagsImpl();
mCM = PhoneGlobals.getInstance().mCM;
mImsResolver = ImsResolver.getInstance();
mSatelliteController = SatelliteController.getInstance();
@@ -2465,9 +2478,16 @@
mPhoneConfigurationManager = PhoneConfigurationManager.getInstance();
mRadioInterfaceCapabilities = RadioInterfaceCapabilityController.getInstance();
mNotifyUserActivity = new AtomicBoolean(false);
+ mPackageManager = app.getPackageManager();
+ mSatelliteAccessController = SatelliteAccessController.getOrCreateInstance(
+ getDefaultPhone().getContext(), featureFlags);
PropertyInvalidatedCache.invalidateCache(TelephonyManager.CACHE_KEY_PHONE_ACCOUNT_TO_SUBID);
publish();
CarrierAllowListInfo.loadInstance(mApp);
+
+ // Create the SatelliteEntitlementController singleton, for using the get the
+ // entitlementStatus for satellite service.
+ SatelliteEntitlementController.make(mApp, mFeatureFlags);
}
@VisibleForTesting
@@ -2559,6 +2579,9 @@
}
public void dial(String number) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "dial");
+
dialForSubscriber(getPreferredVoiceSubscription(), number);
}
@@ -2604,6 +2627,9 @@
return;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "call");
+
final long identity = Binder.clearCallingIdentity();
try {
String url = createTelUrl(number);
@@ -2647,6 +2673,10 @@
public int[] supplyPinReportResultForSubscriber(int subId, String pin) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+ "supplyPinReportResultForSubscriber");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subId);
@@ -2661,6 +2691,9 @@
public int[] supplyPukReportResultForSubscriber(int subId, String puk, String pin) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "supplyPukForSubscriber");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subId);
@@ -2745,6 +2778,9 @@
* If PUK is null, unlock SIM card with PIN
*
* If PUK is not null, unlock SIM card with PUK and set PIN code
+ *
+ * Besides, since it is reused in class level, the thread's looper will be stopped to avoid
+ * its thread leak.
*/
synchronized int[] unlockSim(String puk, String pin) {
@@ -2780,6 +2816,8 @@
if (mResult == PhoneConstants.PIN_RESULT_SUCCESS && pin.length() > 0) {
UiccController.getInstance().getPinStorage().storePin(pin, mPhoneId);
}
+ // This instance is no longer reused, so quit its thread's looper.
+ mHandler.getLooper().quitSafely();
return resultArray;
}
@@ -2861,6 +2899,9 @@
return false;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "isRadioOnWithFeature");
+
final long identity = Binder.clearCallingIdentity();
try {
return isRadioOnForSubscriber(subId);
@@ -2890,6 +2931,9 @@
public void toggleRadioOnOffForSubscriber(int subId) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "toggleRadioOnOffForSubscriber");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -2925,6 +2969,10 @@
public boolean needMobileRadioShutdown() {
enforceReadPrivilegedPermission("needMobileRadioShutdown");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "needMobileRadioShutdown");
+
/*
* If any of the Radios are available, it will need to be
* shutdown. So return true if any Radio is available.
@@ -2946,6 +2994,9 @@
public void shutdownMobileRadios() {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "shutdownMobileRadios");
+
final long identity = Binder.clearCallingIdentity();
try {
for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
@@ -3027,18 +3078,22 @@
@TelephonyManager.RadioPowerReason int reason) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "requestRadioPowerOffForReason");
+
log("requestRadioPowerOffForReason: subId=" + subId
+ ",reason=" + reason + ",callingPackage=" + getCurrentPackageName());
final long identity = Binder.clearCallingIdentity();
try {
- final Phone phone = getPhoneFromSubIdOrDefault(subId);
- if (phone != null) {
+ boolean result = false;
+ for (Phone phone : PhoneFactory.getPhones()) {
+ result = true;
phone.setRadioPowerForReason(false, reason);
- return true;
- } else {
- loge("requestRadioPowerOffForReason: phone is null");
- return false;
}
+ if (!result) {
+ loge("requestRadioPowerOffForReason: no phone exists");
+ }
+ return result;
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -3056,16 +3111,20 @@
@TelephonyManager.RadioPowerReason int reason) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "clearRadioPowerOffForReason");
+
final long identity = Binder.clearCallingIdentity();
try {
- final Phone phone = getPhoneFromSubIdOrDefault(subId);
- if (phone != null) {
+ boolean result = false;
+ for (Phone phone : PhoneFactory.getPhones()) {
+ result = true;
phone.setRadioPowerForReason(true, reason);
- return true;
- } else {
- loge("clearRadioPowerOffForReason: phone is null");
- return false;
}
+ if (!result) {
+ loge("clearRadioPowerOffForReason: no phone exists");
+ }
+ return result;
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -3082,6 +3141,9 @@
public List getRadioPowerOffReasons(int subId, String callingPackage, String callingFeatureId) {
enforceReadPrivilegedPermission("getRadioPowerOffReasons");
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getRadioPowerOffReasons");
+
final long identity = Binder.clearCallingIdentity();
List result = new ArrayList();
try {
@@ -3107,6 +3169,9 @@
public boolean enableDataConnectivity(String callingPackage) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_DATA, "enableDataConnectivity");
+
final long identity = Binder.clearCallingIdentity();
try {
int subId = SubscriptionManager.getDefaultDataSubscriptionId();
@@ -3128,6 +3193,9 @@
public boolean disableDataConnectivity(String callingPackage) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_DATA, "disableDataConnectivity");
+
final long identity = Binder.clearCallingIdentity();
try {
int subId = SubscriptionManager.getDefaultDataSubscriptionId();
@@ -3146,6 +3214,9 @@
@Override
public boolean isDataConnectivityPossible(int subId) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "isDataConnectivityPossible");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -3166,6 +3237,9 @@
public void handleUssdRequest(int subId, String ussdRequest, ResultReceiver wrappedCallback) {
enforceCallPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "handleUssdRequest");
+
final long identity = Binder.clearCallingIdentity();
try {
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
@@ -3181,6 +3255,9 @@
public boolean handlePinMmiForSubscriber(int subId, String dialString) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "handlePinMmiForSubscriber");
+
final long identity = Binder.clearCallingIdentity();
try {
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
@@ -3228,6 +3305,10 @@
+ "targeting API level 31+.");
}
}
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getCallStateForSubscription");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subId);
@@ -3245,6 +3326,9 @@
@Override
public int getDataStateForSubId(int subId) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "getDataStateForSubId");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -3266,6 +3350,9 @@
@Override
public @DataActivityType int getDataActivityForSubId(int subId) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "getDataActivityForSubId");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -3303,6 +3390,9 @@
? new CellIdentityCdma() : new CellIdentityGsm();
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getCellLocation");
+
WorkSource workSource = getWorkSource(Binder.getCallingUid());
final long identity = Binder.clearCallingIdentity();
try {
@@ -3316,6 +3406,9 @@
@Override
public String getNetworkCountryIsoForPhone(int phoneId) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getNetworkCountryIsoForPhone");
+
// Reporting the correct network country is ambiguous when IWLAN could conflict with
// registered cell info, so return a NULL country instead.
final long identity = Binder.clearCallingIdentity();
@@ -3386,6 +3479,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getNeighboringCellInfo");
+
if (DBG_LOC) log("getNeighboringCellInfo: is active user");
List<CellInfo> info = getAllCellInfo(callingPackage, callingFeatureId);
@@ -3439,6 +3535,9 @@
return getCachedCellInfo();
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getAllCellInfo");
+
if (DBG_LOC) log("getAllCellInfo: is active user");
WorkSource workSource = getWorkSource(Binder.getCallingUid());
final long identity = Binder.clearCallingIdentity();
@@ -3507,6 +3606,8 @@
return;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "requestCellInfoUpdateInternal");
final Phone phone = getPhoneFromSubId(subId);
if (phone == null) throw new IllegalArgumentException("Invalid Subscription Id: " + subId);
@@ -3545,6 +3646,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_GSM, "getImeiForSlot");
+
final long identity = Binder.clearCallingIdentity();
try {
return phone.getImei();
@@ -3560,6 +3664,10 @@
callingFeatureId, "getPrimaryImei")) {
throw new SecurityException("Caller does not have permission");
}
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_GSM, "getPrimaryImei");
+
final long identity = Binder.clearCallingIdentity();
try {
for (Phone phone : PhoneFactory.getPhones()) {
@@ -3575,6 +3683,9 @@
@Override
public String getTypeAllocationCodeForSlot(int slotIndex) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_GSM, "getTypeAllocationCodeForSlot");
+
Phone phone = PhoneFactory.getPhone(slotIndex);
String tac = null;
if (phone != null) {
@@ -3609,6 +3720,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CDMA, "getMeidForSlot");
+
final long identity = Binder.clearCallingIdentity();
try {
return phone.getMeid();
@@ -3619,6 +3733,9 @@
@Override
public String getManufacturerCodeForSlot(int slotIndex) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CDMA, "getManufacturerCodeForSlot");
+
Phone phone = PhoneFactory.getPhone(slotIndex);
String manufacturerCode = null;
if (phone != null) {
@@ -3648,6 +3765,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY, "getDeviceSoftwareVersionForSlot");
+
final long identity = Binder.clearCallingIdentity();
try {
return phone.getDeviceSvn();
@@ -3658,6 +3778,9 @@
@Override
public int getSubscriptionCarrierId(int subId) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getSubscriptionCarrierId");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -3669,6 +3792,9 @@
@Override
public String getSubscriptionCarrierName(int subId) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getSubscriptionCarrierName");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -3680,6 +3806,9 @@
@Override
public int getSubscriptionSpecificCarrierId(int subId) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getSubscriptionSpecificCarrierId");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -3692,6 +3821,10 @@
@Override
public String getSubscriptionSpecificCarrierName(int subId) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+ "getSubscriptionSpecificCarrierName");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -3710,6 +3843,10 @@
if (phone == null) {
return TelephonyManager.UNKNOWN_CARRIER_ID;
}
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getCarrierIdFromMccMnc");
+
final long identity = Binder.clearCallingIdentity();
try {
return CarrierResolver.getCarrierIdFromMccMnc(phone.getContext(), mccmnc);
@@ -3830,6 +3967,9 @@
@Override
public int getActivePhoneTypeForSlot(int slotIndex) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY, "getActivePhoneTypeForSlot");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = PhoneFactory.getPhone(slotIndex);
@@ -3861,6 +4001,10 @@
return -1;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CDMA,
+ "getCdmaEriIconIndexForSubscriber");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -3946,6 +4090,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "getCdmaMdn");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CDMA, "getCdmaMdn");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -3968,6 +4115,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "getCdmaMin");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CDMA, "getCdmaMin");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -3997,6 +4147,9 @@
+ ", configured package: " + authorizedPackage);
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "requestNumberVerification");
+
if (range == null) {
throw new NullPointerException("Range must be non-null");
}
@@ -4011,6 +4164,9 @@
* Returns true if CDMA provisioning needs to run.
*/
public boolean needsOtaServiceProvisioning() {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "needsOtaServiceProvisioning");
+
final long identity = Binder.clearCallingIdentity();
try {
return getDefaultPhone().needsOtaServiceProvisioning();
@@ -4027,6 +4183,9 @@
TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(
mApp, subId, "setVoiceMailNumber");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "setVoiceMailNumber");
+
final long identity = Binder.clearCallingIdentity();
try {
Boolean success = (Boolean) sendRequest(CMD_SET_VOICEMAIL_NUMBER,
@@ -4046,6 +4205,9 @@
throw new SecurityException("caller must be system dialer");
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getVisualVoicemailSettings");
+
final long identity = Binder.clearCallingIdentity();
try {
PhoneAccountHandle phoneAccountHandle = PhoneAccountHandleConverter.fromSubId(subId);
@@ -4068,6 +4230,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getVisualVoicemailPackageName");
+
final long identity = Binder.clearCallingIdentity();
try {
return RemoteVvmTaskManager.getRemotePackage(mApp, subId).getPackageName();
@@ -4081,6 +4246,9 @@
VisualVoicemailSmsFilterSettings settings) {
mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "enableVisualVoicemailSmsFilter");
+
final long identity = Binder.clearCallingIdentity();
try {
VisualVoicemailSmsFilterConfig.enableVisualVoicemailSmsFilter(
@@ -4094,6 +4262,9 @@
public void disableVisualVoicemailSmsFilter(String callingPackage, int subId) {
mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "disableVisualVoicemailSmsFilter");
+
final long identity = Binder.clearCallingIdentity();
try {
VisualVoicemailSmsFilterConfig.disableVisualVoicemailSmsFilter(
@@ -4137,6 +4308,10 @@
mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
enforceVisualVoicemailPackage(callingPackage, subId);
enforceSendSmsPermission();
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "sendVisualVoicemailSmsForSubscriber");
+
SmsController smsController = PhoneFactory.getSmsController();
smsController.sendVisualVoicemailSmsForSubscriber(callingPackage, callingAttributionTag,
subId, number, port, text, sentIntent);
@@ -4150,6 +4325,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "setVoiceActivationState");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "setVoiceActivationState");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -4171,6 +4349,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "setDataActivationState");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "setDataActivationState");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -4191,6 +4372,9 @@
public int getVoiceActivationState(int subId, String callingPackage) {
enforceReadPrivilegedPermission("getVoiceActivationState");
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getVoiceActivationState");
+
final Phone phone = getPhone(subId);
final long identity = Binder.clearCallingIdentity();
try {
@@ -4211,6 +4395,9 @@
public int getDataActivationState(int subId, String callingPackage) {
enforceReadPrivilegedPermission("getDataActivationState");
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_DATA, "getDataActivationState");
+
final Phone phone = getPhone(subId);
final long identity = Binder.clearCallingIdentity();
try {
@@ -4254,6 +4441,9 @@
*/
@Override
public boolean isConcurrentVoiceAndDataAllowed(int subId) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "isConcurrentVoiceAndDataAllowed");
+
final long identity = Binder.clearCallingIdentity();
try {
return getPhoneFromSubIdOrDefault(subId).isConcurrentVoiceAndDataAllowed();
@@ -4278,6 +4468,9 @@
getDefaultSubscription(), "sendDialerSpecialCode");
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "sendDialerSpecialCode");
+
final long identity = Binder.clearCallingIdentity();
try {
defaultPhone.sendDialerSpecialCode(inputCode);
@@ -4291,6 +4484,10 @@
TelephonyPermissions
.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getNetworkSelectionMode");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getNetworkSelectionMode");
+
final long identity = Binder.clearCallingIdentity();
try {
if (!isActiveSubscription(subId)) {
@@ -4305,6 +4502,10 @@
@Override
public boolean isInEmergencySmsMode() {
enforceReadPrivilegedPermission("isInEmergencySmsMode");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_MESSAGING, "isInEmergencySmsMode");
+
final long identity = Binder.clearCallingIdentity();
try {
for (Phone phone : PhoneFactory.getPhones()) {
@@ -4389,6 +4590,76 @@
}
/**
+ * Requires carrier privileges or READ_PRECISE_PHONE_STATE permission.
+ * @param subId The subscription to use to check the configuration.
+ * @param c The callback that will be used to send the result.
+ */
+ @Override
+ public void registerImsEmergencyRegistrationCallback(int subId, IImsRegistrationCallback c)
+ throws RemoteException {
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ mApp, subId, "registerImsEmergencyRegistrationCallback");
+
+ if (!ImsManager.isImsSupportedOnDevice(mApp)) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "IMS not available on device.");
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ int slotId = getSlotIndexOrException(subId);
+ verifyImsMmTelConfiguredOrThrow(slotId);
+
+ ImsStateCallbackController controller = ImsStateCallbackController.getInstance();
+ if (controller != null) {
+ ImsManager imsManager = controller.getImsManager(subId);
+ if (imsManager != null) {
+ imsManager.addEmergencyRegistrationCallbackForSubscription(c, subId);
+ } else {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+ }
+ } else {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
+ }
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Requires carrier privileges or READ_PRECISE_PHONE_STATE permission.
+ * @param subId The subscription to use to check the configuration.
+ * @param c The callback that will be used to send the result.
+ */
+ @Override
+ public void unregisterImsEmergencyRegistrationCallback(int subId, IImsRegistrationCallback c) {
+ TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ mApp, subId, "unregisterImsEmergencyRegistrationCallback");
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
+ }
+ final long token = Binder.clearCallingIdentity();
+
+ try {
+ ImsStateCallbackController controller = ImsStateCallbackController.getInstance();
+ if (controller != null) {
+ ImsManager imsManager = controller.getImsManager(subId);
+ if (imsManager != null) {
+ imsManager.removeEmergencyRegistrationCallbackForSubscription(c, subId);
+ } else {
+ Log.i(LOG_TAG, "unregisterImsEmergencyRegistrationCallback: " + subId
+ + "is inactive, ignoring unregister.");
+ // If the ImsManager is not valid, just return, since the callback
+ // will already have been removed internally.
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
* Get the IMS service registration state for the MmTelFeature associated with this sub id.
*/
@Override
@@ -4529,6 +4800,10 @@
@Override
public boolean isCapable(int subId, int capability, int regTech) {
enforceReadPrivilegedPermission("isCapable");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isCapable");
+
final long token = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -4548,6 +4823,10 @@
@Override
public boolean isAvailable(int subId, int capability, int regTech) {
enforceReadPrivilegedPermission("isAvailable");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isAvailable");
+
final long token = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subId);
@@ -4611,6 +4890,9 @@
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isAdvancedCallingSettingEnabled");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isAdvancedCallingSettingEnabled");
+
final long token = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -4627,6 +4909,10 @@
public void setAdvancedCallingSettingEnabled(int subId, boolean isEnabled) {
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp, subId,
"setAdvancedCallingSettingEnabled");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "setAdvancedCallingSettingEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -4648,6 +4934,10 @@
public boolean isVtSettingEnabled(int subId) {
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isVtSettingEnabled");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isVtSettingEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -4664,6 +4954,10 @@
public void setVtSettingEnabled(int subId, boolean isEnabled) {
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp, subId,
"setVtSettingEnabled");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "setVtSettingEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -4685,6 +4979,10 @@
public boolean isVoWiFiSettingEnabled(int subId) {
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isVoWiFiSettingEnabled");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isVoWiFiSettingEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -4701,6 +4999,10 @@
public void setVoWiFiSettingEnabled(int subId, boolean isEnabled) {
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp, subId,
"setVoWiFiSettingEnabled");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "setVoWiFiSettingEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -4723,6 +5025,10 @@
public boolean isCrossSimCallingEnabledByUser(int subId) {
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isCrossSimCallingEnabledByUser");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isCrossSimCallingEnabledByUser");
+
final long identity = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -4746,6 +5052,10 @@
public void setCrossSimCallingEnabled(int subId, boolean isEnabled) {
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp, subId,
"setCrossSimCallingEnabled");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "setCrossSimCallingEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -4768,6 +5078,10 @@
public boolean isVoWiFiRoamingSettingEnabled(int subId) {
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isVoWiFiRoamingSettingEnabled");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isVoWiFiRoamingSettingEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -4784,6 +5098,10 @@
public void setVoWiFiRoamingSettingEnabled(int subId, boolean isEnabled) {
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp, subId,
"setVoWiFiRoamingSettingEnabled");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "setVoWiFiRoamingSettingEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -4801,6 +5119,10 @@
public void setVoWiFiNonPersistent(int subId, boolean isCapable, int mode) {
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp, subId,
"setVoWiFiNonPersistent");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "setVoWiFiNonPersistent");
+
final long identity = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -4821,6 +5143,10 @@
public int getVoWiFiModeSetting(int subId) {
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getVoWiFiModeSetting");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "getVoWiFiModeSetting");
+
final long identity = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -4837,6 +5163,10 @@
public void setVoWiFiModeSetting(int subId, int mode) {
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp, subId,
"setVoWiFiModeSetting");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "setVoWiFiModeSetting");
+
final long identity = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -4853,6 +5183,10 @@
@Override
public int getVoWiFiRoamingModeSetting(int subId) {
enforceReadPrivilegedPermission("getVoWiFiRoamingModeSetting");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "getVoWiFiRoamingModeSetting");
+
final long identity = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -4869,6 +5203,10 @@
public void setVoWiFiRoamingModeSetting(int subId, int mode) {
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp, subId,
"setVoWiFiRoamingModeSetting");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "setVoWiFiRoamingModeSetting");
+
final long identity = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -4886,6 +5224,10 @@
public void setRttCapabilitySetting(int subId, boolean isEnabled) {
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp, subId,
"setRttCapabilityEnabled");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "setRttCapabilitySetting");
+
final long identity = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -4907,6 +5249,10 @@
public boolean isTtyOverVolteEnabled(int subId) {
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isTtyOverVolteEnabled");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isTtyOverVolteEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -5032,6 +5378,9 @@
boolean isProvisioned) {
checkModifyPhoneStatePermission(subId, "setRcsProvisioningStatusForCapability");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "setRcsProvisioningStatusForCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsProvisioningController controller = ImsProvisioningController.getInstance();
@@ -5052,6 +5401,9 @@
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getRcsProvisioningStatusForCapability");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "getRcsProvisioningStatusForCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsProvisioningController controller = ImsProvisioningController.getInstance();
@@ -5072,6 +5424,9 @@
boolean isProvisioned) {
checkModifyPhoneStatePermission(subId, "setImsProvisioningStatusForCapability");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "setImsProvisioningStatusForCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsProvisioningController controller = ImsProvisioningController.getInstance();
@@ -5091,6 +5446,9 @@
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getProvisioningStatusForCapability");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "getImsProvisioningStatusForCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsProvisioningController controller = ImsProvisioningController.getInstance();
@@ -5111,6 +5469,9 @@
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isProvisioningRequiredForCapability");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isProvisioningRequiredForCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsProvisioningController controller = ImsProvisioningController.getInstance();
@@ -5131,6 +5492,9 @@
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isProvisioningRequiredForCapability");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isRcsProvisioningRequiredForCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsProvisioningController controller = ImsProvisioningController.getInstance();
@@ -5154,6 +5518,9 @@
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getImsProvisioningInt");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "getImsProvisioningInt");
+
final long identity = Binder.clearCallingIdentity();
try {
// TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
@@ -5194,6 +5561,9 @@
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getImsProvisioningString");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "getImsProvisioningString");
+
final long identity = Binder.clearCallingIdentity();
try {
// TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
@@ -5221,6 +5591,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp, subId,
"setImsProvisioningInt");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "setImsProvisioningInt");
+
final long identity = Binder.clearCallingIdentity();
try {
// TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
@@ -5260,6 +5633,10 @@
}
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp, subId,
"setImsProvisioningString");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "setImsProvisioningString");
+
final long identity = Binder.clearCallingIdentity();
try {
// TODO: Refactor to remove ImsManager dependence and query through ImsPhone directly.
@@ -5333,6 +5710,9 @@
return TelephonyManager.NETWORK_TYPE_UNKNOWN;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getNetworkTypeForSubscriber");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -5370,6 +5750,9 @@
}
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getDataNetworkTypeForSubscriber");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -5398,6 +5781,9 @@
}
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getVoiceNetworkTypeForSubscriber");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -5425,6 +5811,9 @@
*/
@Override
public boolean hasIccCardUsingSlotIndex(int slotIndex) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "hasIccCardUsingSlotIndex");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = PhoneFactory.getPhone(slotIndex);
@@ -5462,6 +5851,9 @@
return PhoneConstants.LTE_ON_CDMA_UNKNOWN;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getLteOnCdmaModeForSubscriber");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -5543,6 +5935,7 @@
@Override
public IccOpenLogicalChannelResponse iccOpenLogicalChannel(
@NonNull IccLogicalChannelRequest request) {
+
Phone phone = getPhoneFromValidIccLogicalChannelRequest(request,
/*message=*/ "iccOpenLogicalChannel");
@@ -5550,6 +5943,9 @@
// Verify that the callingPackage in the request belongs to the calling UID
mAppOps.checkPackage(Binder.getCallingUid(), request.callingPackage);
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "iccOpenLogicalChannel");
+
return iccOpenLogicalChannelWithPermission(phone, request);
}
@@ -5596,6 +5992,9 @@
@Override
public boolean iccCloseLogicalChannel(@NonNull IccLogicalChannelRequest request) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "iccCloseLogicalChannel");
+
Phone phone = getPhoneFromValidIccLogicalChannelRequest(request,
/*message=*/"iccCloseLogicalChannel");
@@ -5643,6 +6042,10 @@
int command, int p1, int p2, int p3, String data) {
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "iccTransmitApduLogicalChannel");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "iccTransmitApduLogicalChannel");
+
if (DBG) {
log("iccTransmitApduLogicalChannel: subId=" + subId + " chnl=" + channel
+ " cla=" + cla + " cmd=" + command + " p1=" + p1 + " p2=" + p2 + " p3="
@@ -5656,6 +6059,11 @@
public String iccTransmitApduLogicalChannelByPort(int slotIndex, int portIndex, int channel,
int cla, int command, int p1, int p2, int p3, String data) {
enforceModifyPermission();
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+ "iccTransmitApduLogicalChannelBySlot");
+
if (DBG) {
log("iccTransmitApduLogicalChannelByPort: slotIndex=" + slotIndex + " portIndex="
+ portIndex + " chnl=" + channel + " cla=" + cla + " cmd=" + command + " p1="
@@ -5697,6 +6105,10 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "iccTransmitApduBasicChannel");
mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "iccTransmitApduBasicChannel");
+
if (DBG) {
log("iccTransmitApduBasicChannel: subId=" + subId + " cla=" + cla + " cmd="
+ command + " p1=" + p1 + " p2=" + p2 + " p3=" + p3 + " data=" + data);
@@ -5710,6 +6122,10 @@
String callingPackage, int cla, int command, int p1, int p2, int p3, String data) {
enforceModifyPermission();
mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "iccTransmitApduBasicChannelBySlot");
+
if (DBG) {
log("iccTransmitApduBasicChannelByPort: slotIndex=" + slotIndex + " portIndex="
+ portIndex + " cla=" + cla + " cmd=" + command + " p1=" + p1 + " p2="
@@ -5762,6 +6178,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "iccExchangeSimIO");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "iccExchangeSimIO");
+
final long identity = Binder.clearCallingIdentity();
try {
if (DBG) {
@@ -5807,6 +6226,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getForbiddenPlmns");
+
final long identity = Binder.clearCallingIdentity();
try {
if (appType != TelephonyManager.APPTYPE_USIM
@@ -5843,6 +6265,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "setForbiddenPlmns");
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "setForbiddenPlmns");
+
if (appType != TelephonyManager.APPTYPE_USIM && appType != TelephonyManager.APPTYPE_SIM) {
loge("setForbiddenPlmnList(): App Type must be USIM or SIM");
throw new IllegalArgumentException("Invalid appType: App Type must be USIM or SIM");
@@ -5872,6 +6297,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "sendEnvelopeWithStatus");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "sendEnvelopeWithStatus");
+
final long identity = Binder.clearCallingIdentity();
try {
IccIoResult response = (IccIoResult) sendRequest(CMD_SEND_ENVELOPE, content, subId);
@@ -5977,6 +6405,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, phone.getSubId(), "resetModemConfig");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "resetModemConfig");
+
final long identity = Binder.clearCallingIdentity();
try {
Boolean success = (Boolean) sendRequest(CMD_RESET_MODEM_CONFIG, null);
@@ -6003,6 +6434,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, phone.getSubId(), "rebootModem");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "rebootModem");
+
final long identity = Binder.clearCallingIdentity();
try {
Boolean success = (Boolean) sendRequest(CMD_MODEM_REBOOT, null);
@@ -6023,6 +6457,9 @@
public void resetIms(int slotIndex) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_IMS, "resetIms");
+
final long identity = Binder.clearCallingIdentity();
try {
if (mImsResolver == null) {
@@ -6302,6 +6739,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "setNetworkSelectionModeAutomatic");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "setNetworkSelectionModeAutomatic");
+
final long identity = Binder.clearCallingIdentity();
try {
if (!isActiveSubscription(subId)) {
@@ -6332,6 +6772,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "setNetworkSelectionModeManual");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "setNetworkSelectionModeManual");
+
final long identity = Binder.clearCallingIdentity();
if (!isActiveSubscription(subId)) {
return false;
@@ -6362,6 +6805,9 @@
.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getManualNetworkSelectionPlmn");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getManualNetworkSelectionPlmn");
+
final long identity = Binder.clearCallingIdentity();
try {
if (!isActiveSubscription(subId)) {
@@ -6424,6 +6870,10 @@
public void getCallForwarding(int subId, int callForwardingReason,
ICallForwardingInfoCallback callback) {
enforceReadPrivilegedPermission("getCallForwarding");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getCallForwarding");
+
long identity = Binder.clearCallingIdentity();
try {
if (DBG) {
@@ -6476,6 +6926,10 @@
public void setCallForwarding(int subId, CallForwardingInfo callForwardingInfo,
IIntegerConsumer callback) {
enforceModifyPermission();
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "setCallForwarding");
+
long identity = Binder.clearCallingIdentity();
try {
if (DBG) {
@@ -6509,6 +6963,10 @@
@Override
public void getCallWaitingStatus(int subId, IIntegerConsumer callback) {
enforceReadPrivilegedPermission("getCallWaitingStatus");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getCallWaitingStatus");
+
long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subId);
@@ -6560,6 +7018,10 @@
@Override
public void setCallWaitingStatus(int subId, boolean enable, IIntegerConsumer callback) {
enforceModifyPermission();
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "setCallWaitingStatus");
+
long identity = Binder.clearCallingIdentity();
try {
if (DBG) log("setCallWaitingStatus: subId " + subId + " enable: " + enable);
@@ -6656,6 +7118,10 @@
}
}
}
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "requestNetworkScan");
+
int callingUid = Binder.getCallingUid();
int callingPid = Binder.getCallingPid();
final long identity = Binder.clearCallingIdentity();
@@ -6731,6 +7197,9 @@
.enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getAllowedNetworkTypesBitmask");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getAllowedNetworkTypesBitmask");
+
final long identity = Binder.clearCallingIdentity();
try {
if (DBG) log("getAllowedNetworkTypesBitmask");
@@ -6755,6 +7224,10 @@
@TelephonyManager.AllowedNetworkTypesReason int reason) {
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getAllowedNetworkTypesForReason");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getAllowedNetworkTypesForReason");
+
final long identity = Binder.clearCallingIdentity();
try {
return getPhoneFromSubIdOrDefault(subId).getAllowedNetworkTypes(reason);
@@ -6848,6 +7321,10 @@
"setAllowedNetworkTypesForReason cannot be called with carrier privileges for"
+ " reason " + reason);
}
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "setAllowedNetworkTypesForReason");
+
if (!TelephonyManager.isValidAllowedNetworkTypesReason(reason)) {
loge("setAllowedNetworkTypesForReason: Invalid allowed network type reason: " + reason);
return false;
@@ -6893,6 +7370,10 @@
@Override
public boolean isTetheringApnRequiredForSubscriber(int subId) {
enforceModifyPermission();
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "isTetheringApnRequiredForSubscriber");
+
final long identity = Binder.clearCallingIdentity();
final Phone phone = getPhone(subId);
try {
@@ -6995,6 +7476,9 @@
enforceReadPrivilegedPermission(functionName);
}
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "isDataEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
int phoneId = SubscriptionManager.getPhoneId(subId);
@@ -7041,6 +7525,8 @@
}
}
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "isDataEnabledForReason");
final long identity = Binder.clearCallingIdentity();
try {
@@ -7069,6 +7555,9 @@
@Override
public int getCarrierPrivilegeStatus(int subId) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getCarrierPrivilegeStatus");
+
// No permission needed; this only lets the caller inspect their own status.
return getCarrierPrivilegeStatusForUidWithPermission(subId, Binder.getCallingUid());
}
@@ -7076,6 +7565,10 @@
@Override
public int getCarrierPrivilegeStatusForUid(int subId, int uid) {
enforceReadPrivilegedPermission("getCarrierPrivilegeStatusForUid");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getCarrierPrivilegeStatusForUid");
+
return getCarrierPrivilegeStatusForUidWithPermission(subId, uid);
}
@@ -7096,6 +7589,10 @@
@Override
public int checkCarrierPrivilegesForPackage(int subId, String pkgName) {
enforceReadPrivilegedPermission("checkCarrierPrivilegesForPackage");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "checkCarrierPrivilegesForPackage");
+
if (TextUtils.isEmpty(pkgName)) {
return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
}
@@ -7115,6 +7612,11 @@
@Override
public int checkCarrierPrivilegesForPackageAnyPhone(String pkgName) {
enforceReadPrivilegedPermission("checkCarrierPrivilegesForPackageAnyPhone");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+ "checkCarrierPrivilegesForPackageAnyPhone");
+
return checkCarrierPrivilegesForPackageAnyPhoneWithPermission(pkgName);
}
@@ -7143,6 +7645,11 @@
@Override
public List<String> getCarrierPackageNamesForIntentAndPhone(Intent intent, int phoneId) {
enforceReadPrivilegedPermission("getCarrierPackageNamesForIntentAndPhone");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+ "getCarrierPackageNamesForIntentAndPhone");
+
Phone phone = PhoneFactory.getPhone(phoneId);
if (phone == null) {
return Collections.emptyList();
@@ -7171,6 +7678,11 @@
@Override
public List<String> getPackagesWithCarrierPrivilegesForAllPhones() {
enforceReadPrivilegedPermission("getPackagesWithCarrierPrivilegesForAllPhones");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+ "getPackagesWithCarrierPrivilegesForAllPhones");
+
Set<String> privilegedPackages = new ArraySet<>();
final long identity = Binder.clearCallingIdentity();
try {
@@ -7187,6 +7699,10 @@
public @Nullable String getCarrierServicePackageNameForLogicalSlot(int logicalSlotIndex) {
enforceReadPrivilegedPermission("getCarrierServicePackageNameForLogicalSlot");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+ "getCarrierServicePackageNameForLogicalSlot");
+
final Phone phone = PhoneFactory.getPhone(logicalSlotIndex);
if (phone == null) {
return null;
@@ -7215,6 +7731,9 @@
public void setCallComposerStatus(int subId, int status) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "setCallComposerStatus");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subId);
@@ -7238,6 +7757,9 @@
public int getCallComposerStatus(int subId) {
enforceReadPrivilegedPermission("getCallComposerStatus");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getCallComposerStatus");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subId);
@@ -7260,6 +7782,10 @@
TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(mApp,
subId, "setLine1NumberForDisplayForSubscriber");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+ "setLine1NumberForDisplayForSubscriber");
+
final long identity = Binder.clearCallingIdentity();
try {
final String iccId = getIccId(subId);
@@ -7316,6 +7842,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getLine1NumberForDisplay");
+
final long identity = Binder.clearCallingIdentity();
try {
String iccId = getIccId(subId);
@@ -7443,6 +7972,9 @@
public String[] getMergedImsisFromGroup(int subId, String callingPackage) {
enforceReadPrivilegedPermission("getMergedImsisFromGroup");
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getMergedImsisFromGroup");
+
final long identity = Binder.clearCallingIdentity();
try {
final TelephonyManager telephonyManager = mApp.getSystemService(
@@ -7488,6 +8020,9 @@
TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(mApp,
subId, "setOperatorBrandOverride");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "setOperatorBrandOverride");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -7518,39 +8053,6 @@
}
@Override
- @Deprecated
- public int invokeOemRilRequestRaw(byte[] oemReq, byte[] oemResp) {
- enforceModifyPermission();
-
- int returnValue = 0;
- try {
- AsyncResult result = (AsyncResult) sendRequest(CMD_INVOKE_OEM_RIL_REQUEST_RAW, oemReq);
- if(result.exception == null) {
- if (result.result != null) {
- byte[] responseData = (byte[])(result.result);
- if(responseData.length > oemResp.length) {
- Log.w(LOG_TAG, "Buffer to copy response too small: Response length is " +
- responseData.length + "bytes. Buffer Size is " +
- oemResp.length + "bytes.");
- }
- System.arraycopy(responseData, 0, oemResp, 0, responseData.length);
- returnValue = responseData.length;
- }
- } else {
- CommandException ex = (CommandException) result.exception;
- returnValue = ex.getCommandError().ordinal();
- if(returnValue > 0) returnValue *= -1;
- }
- } catch (RuntimeException e) {
- Log.w(LOG_TAG, "sendOemRilRequestRaw: Runtime Exception");
- returnValue = (CommandException.Error.GENERIC_FAILURE.ordinal());
- if(returnValue > 0) returnValue *= -1;
- }
-
- return returnValue;
- }
-
- @Override
public int getRadioAccessFamily(int phoneId, String callingPackage) {
int raf = RadioAccessFamily.RAF_UNKNOWN;
Phone phone = PhoneFactory.getPhone(phoneId);
@@ -7567,6 +8069,9 @@
throw e;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getRadioAccessFamily");
+
final long identity = Binder.clearCallingIdentity();
try {
raf = ProxyController.getInstance().getRadioAccessFamily(phoneId);
@@ -7587,6 +8092,10 @@
} catch (PackageManager.NameNotFoundException e) {
throw new SecurityException("Invalid package:" + callingPackage);
}
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "uploadCallComposerPicture");
+
RoleManager rm = mApp.getSystemService(RoleManager.class);
List<String> dialerRoleHolders = rm.getRoleHolders(RoleManager.ROLE_DIALER);
if (!dialerRoleHolders.contains(callingPackage)) {
@@ -7673,6 +8182,9 @@
final Phone defaultPhone = getDefaultPhone();
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_IMS, "enableVideoCalling");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsManager.getInstance(defaultPhone.getContext(),
@@ -7690,6 +8202,9 @@
return false;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_IMS, "isVideoCallingEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
// Check the user preference and the system-level IMS setting. Even if the user has
@@ -7715,6 +8230,9 @@
return false;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "canChangeDtmfToneLength");
+
final long identity = Binder.clearCallingIdentity();
try {
CarrierConfigManager configManager =
@@ -7733,6 +8251,9 @@
return false;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY, "isWorldPhone");
+
final long identity = Binder.clearCallingIdentity();
try {
CarrierConfigManager configManager =
@@ -7752,6 +8273,9 @@
@Override
public boolean isHearingAidCompatibilitySupported() {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "isHearingAidCompatibilitySupported");
+
final long identity = Binder.clearCallingIdentity();
try {
return mApp.getResources().getBoolean(R.bool.hac_enabled);
@@ -7768,6 +8292,9 @@
*/
@Override
public boolean isRttSupported(int subscriptionId) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_IMS, "isRttSupported");
+
final long identity = Binder.clearCallingIdentity();
final Phone phone = getPhone(subscriptionId);
if (phone == null) {
@@ -7777,8 +8304,9 @@
try {
boolean isCarrierSupported = mApp.getCarrierConfigForSubId(subscriptionId).getBoolean(
CarrierConfigManager.KEY_RTT_SUPPORTED_BOOL);
- boolean isDeviceSupported =
- phone.getContext().getResources().getBoolean(R.bool.config_support_rtt);
+ boolean isDeviceSupported = (phone.getContext().getResources() != null)
+ ? phone.getContext().getResources().getBoolean(R.bool.config_support_rtt)
+ : false;
return isCarrierSupported && isDeviceSupported;
} finally {
Binder.restoreCallingIdentity(identity);
@@ -7793,6 +8321,12 @@
public boolean isRttEnabled(int subscriptionId) {
final long identity = Binder.clearCallingIdentity();
try {
+ if (mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()) {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS)) {
+ return false;
+ }
+ }
+
boolean isRttSupported = isRttSupported(subscriptionId);
boolean isUserRttSettingOn = Settings.Secure.getInt(
mApp.getContentResolver(), Settings.Secure.RTT_CALLING_MODE, 0) != 0;
@@ -7882,6 +8416,10 @@
subscriptionId,
"getPhoneAccountHandleForSubscriptionId, " + "subscriptionId: "
+ subscriptionId);
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getPhoneAccountHandleForSubscriptionId");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subscriptionId);
@@ -7949,6 +8487,10 @@
@Override
public void factoryReset(int subId, String callingPackage) {
enforceSettingsPermission();
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY, "factoryReset");
+
if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)) {
return;
}
@@ -7967,7 +8509,8 @@
setNetworkSelectionModeAutomatic(subId);
Phone phone = getPhone(subId);
cleanUpAllowedNetworkTypes(phone, subId);
- setDataRoamingEnabled(subId, getDefaultDataRoamingEnabled(subId));
+ setDataRoamingEnabled(subId, phone == null ? false
+ : phone.getDataSettingsManager().isDefaultDataRoamingEnabled());
getPhone(subId).resetCarrierKeysForImsiEncryption();
}
// There has been issues when Sms raw table somehow stores orphan
@@ -8021,6 +8564,10 @@
@Override
public String getSimLocaleForSubscriber(int subId) {
enforceReadPrivilegedPermission("getSimLocaleForSubscriber, subId: " + subId);
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getSimLocaleForSubscriber");
+
final Phone phone = getPhone(subId);
if (phone == null) {
log("getSimLocaleForSubscriber, invalid subId");
@@ -8086,7 +8633,7 @@
*/
private List<SubscriptionInfo> getActiveSubscriptionInfoListPrivileged() {
return getSubscriptionManagerService().getActiveSubscriptionInfoList(
- mApp.getOpPackageName(), mApp.getAttributionTag());
+ mApp.getOpPackageName(), mApp.getAttributionTag(), true/*isForAllProfile*/);
}
private ActivityStatsTechSpecificInfo[] mLastModemActivitySpecificInfo = null;
@@ -8103,6 +8650,10 @@
@Override
public void requestModemActivityInfo(ResultReceiver result) {
enforceModifyPermission();
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY, "requestModemActivityInfo");
+
WorkSource workSource = getWorkSource(Binder.getCallingUid());
final long identity = Binder.clearCallingIdentity();
@@ -8247,6 +8798,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getServiceStateForSubscriber");
+
boolean hasFinePermission = false;
boolean hasCoarsePermission = false;
if (!renounceFineLocationAccess) {
@@ -8326,6 +8880,9 @@
*/
@Override
public Uri getVoicemailRingtoneUri(PhoneAccountHandle accountHandle) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getVoicemailRingtoneUri");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = PhoneUtils.getPhoneForPhoneAccountHandle(accountHandle);
@@ -8362,6 +8919,9 @@
"setVoicemailRingtoneUri");
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "setVoicemailRingtoneUri");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = PhoneUtils.getPhoneForPhoneAccountHandle(phoneAccountHandle);
@@ -8383,6 +8943,9 @@
*/
@Override
public boolean isVoicemailVibrationEnabled(PhoneAccountHandle accountHandle) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "isVoicemailVibrationEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = PhoneUtils.getPhoneForPhoneAccountHandle(accountHandle);
@@ -8419,6 +8982,9 @@
"setVoicemailVibrationEnabled");
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "setVoicemailVibrationEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = PhoneUtils.getPhoneForPhoneAccountHandle(phoneAccountHandle);
@@ -8495,6 +9061,10 @@
@Override
public String getAidForAppType(int subId, int appType) {
enforceReadPrivilegedPermission("getAidForAppType");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getAidForAppType");
+
Phone phone = getPhone(subId);
final long identity = Binder.clearCallingIdentity();
@@ -8552,6 +9122,10 @@
@Override
public String getCdmaPrlVersion(int subId) {
enforceReadPrivilegedPermission("getCdmaPrlVersion");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CDMA, "getCdmaPrlVersion");
+
Phone phone = getPhone(subId);
final long identity = Binder.clearCallingIdentity();
@@ -8601,6 +9175,10 @@
@TelephonyManager.SetCarrierRestrictionResult
public int setAllowedCarriers(CarrierRestrictionRules carrierRestrictionRules) {
enforceModifyPermission();
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CARRIERLOCK, "setAllowedCarriers");
+
WorkSource workSource = getWorkSource(Binder.getCallingUid());
if (carrierRestrictionRules == null) {
@@ -8627,6 +9205,10 @@
@Override
public CarrierRestrictionRules getAllowedCarriers() {
enforceReadPrivilegedPermission("getAllowedCarriers");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CARRIERLOCK, "getAllowedCarriers");
+
WorkSource workSource = getWorkSource(Binder.getCallingUid());
final long identity = Binder.clearCallingIdentity();
@@ -8657,15 +9239,19 @@
@Override
public void getCarrierRestrictionStatus(IIntegerConsumer callback, String packageName) {
enforceReadPermission("getCarrierRestrictionStatus");
- int carrierId = validateCallerAndGetCarrierId(packageName);
- if (carrierId == CarrierAllowListInfo.INVALID_CARRIER_ID) {
+
+ enforceTelephonyFeatureWithException(packageName,
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getCarrierRestrictionStatus");
+
+ Set<Integer> carrierIds = validateCallerAndGetCarrierIds(packageName);
+ if (carrierIds.contains(CarrierAllowListInfo.INVALID_CARRIER_ID)) {
Rlog.e(LOG_TAG, "getCarrierRestrictionStatus: caller is not registered");
throw new SecurityException("Not an authorized caller");
}
final long identity = Binder.clearCallingIdentity();
try {
Consumer<Integer> consumer = FunctionalUtils.ignoreRemoteException(callback::accept);
- CallerCallbackInfo callbackInfo = new CallerCallbackInfo(consumer, carrierId);
+ CallerCallbackInfo callbackInfo = new CallerCallbackInfo(consumer, carrierIds);
sendRequestAsync(CMD_GET_ALLOWED_CARRIERS, callbackInfo);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -8680,9 +9266,9 @@
}
@VisibleForTesting
- public int validateCallerAndGetCarrierId(String packageName) {
+ public Set<Integer> validateCallerAndGetCarrierIds(String packageName) {
CarrierAllowListInfo allowListInfo = CarrierAllowListInfo.loadInstance(mApp);
- return allowListInfo.validateCallerAndGetCarrierId(packageName);
+ return allowListInfo.validateCallerAndGetCarrierIds(packageName);
}
/**
@@ -8779,6 +9365,11 @@
@Override
public void carrierActionReportDefaultNetworkStatus(int subId, boolean report) {
enforceModifyPermission();
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS,
+ "carrierActionReportDefaultNetworkStatus");
+
final Phone phone = getPhone(subId);
final long identity = Binder.clearCallingIdentity();
@@ -8803,6 +9394,10 @@
@Override
public void carrierActionResetAll(int subId) {
enforceModifyPermission();
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "carrierActionResetAll");
+
final Phone phone = getPhone(subId);
if (phone == null) {
loge("carrierAction: ResetAll fails with invalid sibId: " + subId);
@@ -8865,8 +9460,17 @@
enforceModifyPermission();
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_DATA, "setDataEnabledForReason");
+
+ int callingUid = Binder.getCallingUid();
final long identity = Binder.clearCallingIdentity();
try {
+ if (reason == TelephonyManager.DATA_ENABLED_REASON_USER && enabled
+ && null != callingPackage && opEnableMobileDataByUser()) {
+ mAppOps.noteOpNoThrow(AppOpsManager.OPSTR_ENABLE_MOBILE_DATA_BY_USER,
+ callingUid, callingPackage, null, null);
+ }
Phone phone = getPhone(subId);
if (phone != null) {
if (reason == TelephonyManager.DATA_ENABLED_REASON_CARRIER) {
@@ -8935,6 +9539,10 @@
@Override
public void setSimPowerStateForSlot(int slotIndex, int state) {
enforceModifyPermission();
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "setSimPowerStateForSlot");
+
Phone phone = PhoneFactory.getPhone(slotIndex);
WorkSource workSource = getWorkSource(Binder.getCallingUid());
@@ -8964,6 +9572,11 @@
public void setSimPowerStateForSlotWithCallback(int slotIndex, int state,
IIntegerConsumer callback) {
enforceModifyPermission();
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+ "setSimPowerStateForSlotWithCallback");
+
Phone phone = PhoneFactory.getPhone(slotIndex);
WorkSource workSource = getWorkSource(Binder.getCallingUid());
@@ -9001,6 +9614,10 @@
@Override
public boolean getEmergencyCallbackMode(int subId) {
enforceReadPrivilegedPermission("getEmergencyCallbackMode");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getEmergencyCallbackMode");
+
final long identity = Binder.clearCallingIdentity();
try {
return getPhoneFromSubIdOrDefault(subId).isInEcm();
@@ -9018,6 +9635,9 @@
*/
@Override
public SignalStrength getSignalStrength(int subId) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getSignalStrength");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone p = getPhone(subId);
@@ -9047,6 +9667,9 @@
return TelephonyManager.RADIO_POWER_UNAVAILABLE;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getRadioPowerState");
+
final long identity = Binder.clearCallingIdentity();
try {
return phone.getRadioPowerState();
@@ -9087,6 +9710,9 @@
mApp, subId, functionName);
}
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "isDataRoamingEnabled");
+
boolean isEnabled = false;
final long identity = Binder.clearCallingIdentity();
try {
@@ -9114,6 +9740,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "setDataRoamingEnabled");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "setDataRoamingEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subId);
@@ -9131,6 +9760,9 @@
.enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isManualNetworkSelectionAllowed");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "isManualNetworkSelectionAllowed");
+
boolean isAllowed = true;
final long identity = Binder.clearCallingIdentity();
try {
@@ -9177,6 +9809,10 @@
throw new SecurityException("Caller does not have permission.");
}
}
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getUiccCardsInfo");
+
// checking compatibility, if calling app's target SDK is T and beyond.
if (CompatChanges.isChangeEnabled(GET_API_SIGNATURES_FROM_UICC_PORT_INFO,
Binder.getCallingUid())) {
@@ -9287,6 +9923,9 @@
// we are reading iccId which is PII data.
enforceReadPrivilegedPermission("getUiccSlotsInfo");
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getUiccSlotsInfo");
+
// checking compatibility, if calling app's target SDK is T and beyond.
if (CompatChanges.isChangeEnabled(GET_API_SIGNATURES_FROM_UICC_PORT_INFO,
Binder.getCallingUid())) {
@@ -9389,6 +10028,9 @@
public boolean switchSlots(int[] physicalSlots) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "switchSlots");
+
final long identity = Binder.clearCallingIdentity();
try {
List<UiccSlotMapping> slotMappings = new ArrayList<>();
@@ -9408,6 +10050,9 @@
public boolean setSimSlotMapping(@NonNull List<UiccSlotMapping> slotMapping) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "setSimSlotMapping");
+
final long identity = Binder.clearCallingIdentity();
try {
return (Boolean) sendRequest(CMD_SWITCH_SLOTS, slotMapping);
@@ -9418,6 +10063,9 @@
@Override
public int getCardIdForDefaultEuicc(int subId, String callingPackage) {
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_EUICC, "getCardIdForDefaultEuicc");
+
final long identity = Binder.clearCallingIdentity();
try {
return UiccController.getInstance().getCardIdForDefaultEuicc();
@@ -9465,20 +10113,6 @@
}
/**
- * Returns true if the data roaming is enabled by default, i.e the system property
- * of {@link #DEFAULT_DATA_ROAMING_PROPERTY_NAME} is true or the config of
- * {@link CarrierConfigManager#KEY_CARRIER_DEFAULT_DATA_ROAMING_ENABLED_BOOL} is true.
- */
- private boolean getDefaultDataRoamingEnabled(int subId) {
- final CarrierConfigManager configMgr = (CarrierConfigManager)
- mApp.getSystemService(Context.CARRIER_CONFIG_SERVICE);
- boolean isDataRoamingEnabled = TelephonyProperties.data_roaming().orElse(false);
- isDataRoamingEnabled |= configMgr.getConfigForSubId(subId).getBoolean(
- CarrierConfigManager.KEY_CARRIER_DEFAULT_DATA_ROAMING_ENABLED_BOOL);
- return isDataRoamingEnabled;
- }
-
- /**
* Returns the default network type for the given {@code subId}, if the default network type is
* not set, return {@link Phone#PREFERRED_NT_MODE}.
*/
@@ -9526,10 +10160,6 @@
TelephonyPermissions.enforceShellOnly(
Binder.getCallingUid(), "setCarrierServicePackageOverride");
- // Verify that the callingPackage belongs to the calling UID
- mApp.getSystemService(AppOpsManager.class)
- .checkPackage(Binder.getCallingUid(), callingPackage);
-
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -9588,6 +10218,9 @@
.enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getCdmaRoamingMode");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CDMA, "getCdmaRoamingMode");
+
final long identity = Binder.clearCallingIdentity();
try {
return (int) sendRequest(CMD_GET_CDMA_ROAMING_MODE, null /* argument */, subId);
@@ -9601,6 +10234,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "setCdmaRoamingMode");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CDMA, "setCdmaRoamingMode");
+
final long identity = Binder.clearCallingIdentity();
try {
return (boolean) sendRequest(CMD_SET_CDMA_ROAMING_MODE, mode, subId);
@@ -9615,6 +10251,9 @@
.enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getCdmaSubscriptionMode");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CDMA, "getCdmaSubscriptionMode");
+
final long identity = Binder.clearCallingIdentity();
try {
return (int) sendRequest(CMD_GET_CDMA_SUBSCRIPTION_MODE, null /* argument */, subId);
@@ -9628,6 +10267,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "setCdmaSubscriptionMode");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CDMA, "setCdmaSubscriptionMode");
+
final long identity = Binder.clearCallingIdentity();
try {
return (boolean) sendRequest(CMD_SET_CDMA_SUBSCRIPTION_MODE, mode, subId);
@@ -9644,6 +10286,10 @@
"getEmergencyNumberList")) {
throw new SecurityException("Requires READ_PHONE_STATE permission.");
}
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getEmergencyNumberList");
+
final long identity = Binder.clearCallingIdentity();
try {
Map<Integer, List<EmergencyNumber>> emergencyNumberListInternal = new HashMap<>();
@@ -9669,6 +10315,10 @@
.enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, defaultPhone.getSubId(), "isEmergencyNumber(Potential)");
}
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "isEmergencyNumber");
+
final long identity = Binder.clearCallingIdentity();
try {
for (Phone phone: PhoneFactory.getPhones()) {
@@ -9761,6 +10411,9 @@
public int getEmergencyNumberDbVersion(int subId) {
enforceReadPrivilegedPermission("getEmergencyNumberDbVersion");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getEmergencyNumberDbVersion");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -9778,6 +10431,9 @@
public void notifyOtaEmergencyNumberDbInstalled() {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "notifyOtaEmergencyNumberDbInstalled");
+
final long identity = Binder.clearCallingIdentity();
try {
for (Phone phone: PhoneFactory.getPhones()) {
@@ -9795,6 +10451,9 @@
public void updateOtaEmergencyNumberDbFilePath(ParcelFileDescriptor otaParcelFileDescriptor) {
enforceActiveEmergencySessionPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "updateOtaEmergencyNumberDbFilePath");
+
final long identity = Binder.clearCallingIdentity();
try {
for (Phone phone: PhoneFactory.getPhones()) {
@@ -9812,6 +10471,9 @@
public void resetOtaEmergencyNumberDbFilePath() {
enforceActiveEmergencySessionPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "resetOtaEmergencyNumberDbFilePath");
+
final long identity = Binder.clearCallingIdentity();
try {
for (Phone phone: PhoneFactory.getPhones()) {
@@ -9852,6 +10514,9 @@
public boolean enableModemForSlot(int slotIndex, boolean enable) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY, "enableModemForSlot");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = PhoneFactory.getPhone(slotIndex);
@@ -9880,6 +10545,9 @@
throw new SecurityException("Requires READ_PHONE_STATE permission.");
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY, "isModemEnabledForSlot");
+
final long identity = Binder.clearCallingIdentity();
try {
try {
@@ -9896,6 +10564,9 @@
public void setMultiSimCarrierRestriction(boolean isMultiSimCarrierRestricted) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CARRIERLOCK, "setMultiSimCarrierRestriction");
+
final long identity = Binder.clearCallingIdentity();
try {
mTelephonySharedPreferences.edit()
@@ -9915,6 +10586,9 @@
return TelephonyManager.MULTISIM_NOT_SUPPORTED_BY_HARDWARE;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "isMultiSimSupported");
+
final long identity = Binder.clearCallingIdentity();
try {
return isMultiSimSupportedInternal();
@@ -9966,6 +10640,10 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, "switchMultiSimConfig");
}
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "switchMultiSimConfig");
+
final long identity = Binder.clearCallingIdentity();
try {
@@ -9983,6 +10661,10 @@
@Override
public boolean isApplicationOnUicc(int subId, int appType) {
enforceReadPrivilegedPermission("isApplicationOnUicc");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "isApplicationOnUicc");
+
Phone phone = getPhone(subId);
if (phone == null) {
return false;
@@ -10019,6 +10701,11 @@
"doesSwitchMultiSimConfigTriggerReboot")) {
return false;
}
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+ "doesSwitchMultiSimConfigTriggerReboot");
+
final long identity = Binder.clearCallingIdentity();
try {
return mPhoneConfigurationManager.isRebootRequiredForModemConfigChange();
@@ -10039,6 +10726,10 @@
// Verify that the callingPackage belongs to the calling UID
mApp.getSystemService(AppOpsManager.class)
.checkPackage(Binder.getCallingUid(), callingPackage);
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getSlotsMapping");
+
final long identity = Binder.clearCallingIdentity();
List<UiccSlotMapping> slotMap = new ArrayList<>();
try {
@@ -10083,11 +10774,14 @@
/**
* Get the current calling package name.
- * @return the current calling package name
+ *
+ * @return the current calling package name, or null if there is no known package.
*/
@Override
- public String getCurrentPackageName() {
- return mApp.getPackageManager().getPackagesForUid(Binder.getCallingUid())[0];
+ public @Nullable String getCurrentPackageName() {
+ PackageManager pm = mApp.getPackageManager();
+ String[] packageNames = pm == null ? null : pm.getPackagesForUid(Binder.getCallingUid());
+ return packageNames == null ? null : packageNames[0];
}
/**
@@ -10109,6 +10803,9 @@
enforceReadPrivilegedPermission("Needs READ_PRIVILEGED_PHONE_STATE for "
+ "isDataEnabledForApn");
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_DATA, "isDataEnabledForApn");
+
// Now that all security checks passes, perform the operation as ourselves.
final long identity = Binder.clearCallingIdentity();
try {
@@ -10131,6 +10828,9 @@
public boolean isApnMetered(@ApnType int apnType, int subId) {
enforceReadPrivilegedPermission("isApnMetered");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "isApnMetered");
+
// Now that all security checks passes, perform the operation as ourselves.
final long identity = Binder.clearCallingIdentity();
try {
@@ -10148,6 +10848,10 @@
public void setSystemSelectionChannels(List<RadioAccessSpecifier> specifiers,
int subscriptionId, IBooleanConsumer resultCallback) {
enforceModifyPermission();
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "setSystemSelectionChannels");
+
long token = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subscriptionId);
@@ -10182,6 +10886,10 @@
TelephonyPermissions
.enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getSystemSelectionChannels");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getSystemSelectionChannels");
+
WorkSource workSource = getWorkSource(Binder.getCallingUid());
final long identity = Binder.clearCallingIdentity();
try {
@@ -10200,6 +10908,10 @@
@Override
public boolean isMvnoMatched(int slotIndex, int mvnoType, @NonNull String mvnoMatchData) {
enforceReadPrivilegedPermission("isMvnoMatched");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "isMvnoMatched");
+
return UiccController.getInstance().mvnoMatches(slotIndex, mvnoType, mvnoMatchData);
}
@@ -10247,6 +10959,9 @@
@Override
public String getMmsUAProfUrl(int subId) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_MESSAGING, "getMmsUAProfUrl");
+
//TODO investigate if this API should require proper permission check in R b/133791609
final long identity = Binder.clearCallingIdentity();
try {
@@ -10264,6 +10979,9 @@
@Override
public String getMmsUserAgent(int subId) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_MESSAGING, "getMmsUserAgent");
+
//TODO investigate if this API should require proper permission check in R b/133791609
final long identity = Binder.clearCallingIdentity();
try {
@@ -10283,6 +11001,9 @@
public boolean isMobileDataPolicyEnabled(int subscriptionId, int policy) {
enforceReadPrivilegedPermission("isMobileDataPolicyEnabled");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "isMobileDataPolicyEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subscriptionId);
@@ -10299,6 +11020,9 @@
boolean enabled) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "setMobileDataPolicyEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subscriptionId);
@@ -10353,10 +11077,19 @@
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
}
- if (!isImsAvailableOnDevice()) {
- // ProvisioningManager can not handle ServiceSpecificException.
- // Throw the IllegalStateException and annotate ProvisioningManager.
- throw new IllegalStateException("IMS not available on device.");
+
+ if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
+ || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, getCurrentPackageName(),
+ Binder.getCallingUserHandle())) {
+ if (!isImsAvailableOnDevice()) {
+ // ProvisioningManager can not handle ServiceSpecificException.
+ // Throw the IllegalStateException and annotate ProvisioningManager.
+ throw new IllegalStateException("IMS not available on device.");
+ }
+ } else {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION,
+ "notifyRcsAutoConfigurationReceived");
}
final long identity = Binder.clearCallingIdentity();
@@ -10371,6 +11104,9 @@
public boolean isIccLockEnabled(int subId) {
enforceReadPrivilegedPermission("isIccLockEnabled");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "isIccLockEnabled");
+
// Now that all security checks passes, perform the operation as ourselves.
final long identity = Binder.clearCallingIdentity();
try {
@@ -10400,6 +11136,9 @@
public int setIccLockEnabled(int subId, boolean enabled, String password) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "setIccLockEnabled");
+
Phone phone = getPhone(subId);
if (phone == null) {
return 0;
@@ -10432,6 +11171,9 @@
public int changeIccLockPassword(int subId, String oldPassword, String newPassword) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "changeIccLockPassword");
+
Phone phone = getPhone(subId);
if (phone == null) {
return 0;
@@ -10502,6 +11244,9 @@
throw new SecurityException("Requires READ_PHONE_STATE permission.");
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getEquivalentHomePlmns");
+
Phone phone = getPhone(subId);
if (phone == null) {
throw new RuntimeException("phone is not available");
@@ -10518,6 +11263,10 @@
@Override
public boolean isRadioInterfaceCapabilitySupported(
final @NonNull @TelephonyManager.RadioInterfaceCapability String capability) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS,
+ "isRadioInterfaceCapabilitySupported");
+
Set<String> radioInterfaceCapabilities =
mRadioInterfaceCapabilities.getCapabilities();
if (radioInterfaceCapabilities == null) {
@@ -10534,6 +11283,10 @@
Binder.getCallingUid(), "bootstrapAuthenticationRequest",
Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION,
Manifest.permission.MODIFY_PHONE_STATE);
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "bootstrapAuthenticationRequest");
+
if (DBG) {
log("bootstrapAuthenticationRequest, subId:" + subId + ", appType:"
+ appType + ", NAF:" + nafUrl + ", sp:" + securityProtocol
@@ -10693,6 +11446,10 @@
enforceModifyPermission();
mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "sendThermalMitigationRequest");
+
if (!getThermalMitigationAllowlist(getDefaultPhone().getContext())
.contains(callingPackage)) {
throw new SecurityException("Calling package must be configured in the device config. "
@@ -10923,9 +11680,16 @@
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.");
+ if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
+ || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, getCurrentPackageName(),
+ Binder.getCallingUserHandle())) {
+ if (!isImsAvailableOnDevice()) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "IMS not available on device.");
+ }
+ } else {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION, "registerRcsProvisioningCallback");
}
final long identity = Binder.clearCallingIdentity();
@@ -10954,10 +11718,19 @@
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
}
- if (!isImsAvailableOnDevice()) {
- // operation failed silently
- Rlog.w(LOG_TAG, "IMS not available on device.");
- return;
+
+ if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
+ || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, getCurrentPackageName(),
+ Binder.getCallingUserHandle())) {
+ if (!isImsAvailableOnDevice()) {
+ // operation failed silently
+ Rlog.w(LOG_TAG, "IMS not available on device.");
+ return;
+ }
+ } else {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION,
+ "unregisterRcsProvisioningCallback");
}
final long identity = Binder.clearCallingIdentity();
@@ -10980,10 +11753,17 @@
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
}
- if (!isImsAvailableOnDevice()) {
- // ProvisioningManager can not handle ServiceSpecificException.
- // Throw the IllegalStateException and annotate ProvisioningManager.
- throw new IllegalStateException("IMS not available on device.");
+ if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
+ || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, getCurrentPackageName(),
+ Binder.getCallingUserHandle())) {
+ if (!isImsAvailableOnDevice()) {
+ // ProvisioningManager can not handle ServiceSpecificException.
+ // Throw the IllegalStateException and annotate ProvisioningManager.
+ throw new IllegalStateException("IMS not available on device.");
+ }
+ } else {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION, "triggerRcsReconfiguration");
}
final long identity = Binder.clearCallingIdentity();
@@ -11005,9 +11785,16 @@
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.");
+ if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
+ || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, getCurrentPackageName(),
+ Binder.getCallingUserHandle())) {
+ if (!isImsAvailableOnDevice()) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "IMS not available on device.");
+ }
+ } else {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION, "setRcsClientConfiguration");
}
final long identity = Binder.clearCallingIdentity();
@@ -11421,6 +12208,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "setSignalStrengthUpdateRequest");
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "setSignalStrengthUpdateRequest");
+
final int callingUid = Binder.getCallingUid();
// Verify that tha callingPackage belongs to the calling UID
mApp.getSystemService(AppOpsManager.class)
@@ -11447,6 +12237,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "clearSignalStrengthUpdateRequest");
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "clearSignalStrengthUpdateRequest");
+
final int callingUid = Binder.getCallingUid();
// Verify that tha callingPackage belongs to the calling UID
mApp.getSystemService(AppOpsManager.class)
@@ -11513,6 +12306,10 @@
@Override
public PhoneCapability getPhoneCapability() {
enforceReadPrivilegedPermission("getPhoneCapability");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY, "getPhoneCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
return mPhoneConfigurationManager.getCurrentPhoneCapability();
@@ -11531,6 +12328,9 @@
WorkSource workSource = getWorkSource(Binder.getCallingUid());
enforceRebootPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "prepareForUnattendedReboot");
+
final long identity = Binder.clearCallingIdentity();
try {
return (int) sendRequest(CMD_PREPARE_UNATTENDED_REBOOT, null, workSource);
@@ -11551,6 +12351,10 @@
.enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, SubscriptionManager.INVALID_SUBSCRIPTION_ID, "getSlicingConfig");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS,
+ "getSlicingConfig");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getDefaultPhone();
@@ -11579,6 +12383,9 @@
+ "permission READ_BASIC_PHONE_STATE.");
}
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "isPremiumCapabilityAvailableForPurchase");
+
Phone phone = getPhone(subId);
if (phone == null) {
loge("isPremiumCapabilityAvailableForPurchase: phone is null, subId=" + subId);
@@ -11586,7 +12393,7 @@
}
final long identity = Binder.clearCallingIdentity();
try {
- return SlicePurchaseController.getInstance(phone)
+ return SlicePurchaseController.getInstance(phone, mFeatureFlags)
.isPremiumCapabilityAvailableForPurchase(capability);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -11621,6 +12428,9 @@
throw new SecurityException("purchasePremiumCapability requires permission INTERNET.");
}
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "purchasePremiumCapability");
+
Phone phone = getPhone(subId);
if (phone == null) {
try {
@@ -11756,8 +12566,9 @@
* @return {@CellIdentity} last known cell identity {@CellIdentity}.
*
* Require {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and
- * com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID, otherwise throws
+ * {@link android.Manifest.permission#ACCESS_LAST_KNOWN_CELL_ID}, otherwise throws
* SecurityException.
+ *
* If there is current registered network this value will be same as the registered cell
* identity. If the device goes out of service the previous cell identity is cached and
* will be returned. If the cache age of the Cell identity is more than 24 hours
@@ -11833,6 +12644,27 @@
return result;
}
+ /**
+ * Get the aggregated satellite plmn list. This API collects plmn data from multiple sources,
+ * including carrier config, entitlement server, and config update.
+ *
+ * @param subId subId The subscription ID of the carrier.
+ *
+ * @return List of plmns for carrier satellite service. If no plmn is available, empty list will
+ * be returned.
+ *
+ * @throws SecurityException if the caller doesn't have the required permission.
+ */
+ @NonNull public List<String> getSatellitePlmnsForCarrier(int subId) {
+ enforceSatelliteCommunicationPermission("getSatellitePlmnsForCarrier");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mSatelliteController.getSatellitePlmnsForCarrier(subId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
@Override
public void setVoiceServiceStateOverride(int subId, boolean hasService, String callingPackage) {
// Only telecom (and shell, for CTS purposes) is allowed to call this method.
@@ -11901,6 +12733,10 @@
boolean updateIfNeeded) {
enforceInteractAcrossUsersPermission("getDefaultRespondViaMessageApplication");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_MESSAGING,
+ "getDefaultRespondViaMessageApplication");
+
Context context = getPhoneFromSubIdOrDefault(subId).getContext();
UserHandle userHandle = null;
@@ -11969,6 +12805,29 @@
}
}
+ private void checkForIdentifierDisclosureNotificationSupport() {
+ if (getHalVersion(HAL_SERVICE_NETWORK) < MIN_IDENTIFIER_DISCLOSURE_VERSION) {
+ throw new UnsupportedOperationException(
+ "Cellular identifier disclosure transparency operations require HAL 2.2 or "
+ + "above");
+ }
+ if (!getDefaultPhone().isIdentifierDisclosureTransparencySupported()) {
+ throw new UnsupportedOperationException(
+ "Cellular identifier disclosure transparency operations unsupported by modem");
+ }
+ }
+
+ private void checkForNullCipherNotificationSupport() {
+ if (getHalVersion(HAL_SERVICE_NETWORK) < MIN_NULL_CIPHER_NOTIFICATION_VERSION) {
+ throw new UnsupportedOperationException(
+ "Null cipher notification operations require HAL 2.2 or above");
+ }
+ if (!getDefaultPhone().isNullCipherNotificationSupported()) {
+ throw new UnsupportedOperationException(
+ "Null cipher notification operations unsupported by modem");
+ }
+ }
+
/**
* Get the SIM state for the slot index.
* For Remote-SIMs, this method returns {@link IccCardConstants.State#UNKNOWN}
@@ -11978,6 +12837,9 @@
@Override
@SimState
public int getSimStateForSlotIndex(int slotIndex) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getSimStateForSlotIndex");
+
IccCardConstants.State simState;
if (slotIndex < 0) {
simState = IccCardConstants.State.UNKNOWN;
@@ -12007,22 +12869,26 @@
long logcatStartTimestampMillis, boolean enableTelecomDump,
boolean enableTelephonyDump) {
DropBoxManager db = mApp.getSystemService(DropBoxManager.class);
- TelephonyManager.EmergencyCallDiagnosticParams edp =
- new TelephonyManager.EmergencyCallDiagnosticParams();
- edp.setLogcatCollection(enableLogcat, logcatStartTimestampMillis);
- edp.setTelephonyDumpSysCollection(enableTelephonyDump);
- edp.setTelecomDumpSysCollection(enableTelecomDump);
- Log.d(LOG_TAG, "persisting with Params " + edp.toString());
+ TelephonyManager.EmergencyCallDiagnosticData.Builder ecdDataBuilder =
+ new TelephonyManager.EmergencyCallDiagnosticData.Builder();
+ ecdDataBuilder
+ .setTelecomDumpsysCollectionEnabled(enableTelecomDump)
+ .setTelephonyDumpsysCollectionEnabled(enableTelephonyDump);
+ if (enableLogcat) {
+ ecdDataBuilder.setLogcatCollectionStartTimeMillis(logcatStartTimestampMillis);
+ }
+ TelephonyManager.EmergencyCallDiagnosticData ecdData = ecdDataBuilder.build();
+ Log.d(LOG_TAG, "persisting with Params " + ecdData.toString());
DiagnosticDataCollector ddc = new DiagnosticDataCollector(Runtime.getRuntime(),
Executors.newCachedThreadPool(), db,
mApp.getSystemService(ActivityManager.class).isLowRamDevice());
- ddc.persistEmergencyDianosticData(new DataCollectorConfig.Adapter(), edp, dropboxTag);
+ ddc.persistEmergencyDianosticData(new DataCollectorConfig.Adapter(), ecdData, dropboxTag);
}
/**
* Request telephony to persist state for debugging emergency call failures.
*
- * @param dropBoxTag Tag to use when persisting data to dropbox service.
+ * @param dropboxTag Tag to use when persisting data to dropbox service.
* @param enableLogcat whether to collect logcat output
* @param logcatStartTimestampMillis timestamp from when logcat buffers would be persisted
* @param enableTelecomDump whether to collect telecom dumpsys
@@ -12033,8 +12899,16 @@
public void persistEmergencyCallDiagnosticData(@NonNull String dropboxTag, boolean enableLogcat,
long logcatStartTimestampMillis, boolean enableTelecomDump,
boolean enableTelephonyDump) {
- mApp.enforceCallingPermission(android.Manifest.permission.DUMP,
- "persistEmergencyCallDiagnosticData");
+ // Verify that the caller has READ_DROPBOX_DATA permission.
+ if (mTelecomFeatureFlags.telecomResolveHiddenDependencies()
+ && Flags.enableReadDropboxPermission()) {
+ mApp.enforceCallingPermission(permission.READ_DROPBOX_DATA,
+ "persistEmergencyCallDiagnosticData");
+ } else {
+ // Otherwise, enforce legacy permission.
+ mApp.enforceCallingPermission(android.Manifest.permission.DUMP,
+ "persistEmergencyCallDiagnosticData");
+ }
final long identity = Binder.clearCallingIdentity();
try {
persistEmergencyCallDiagnosticDataInternal(dropboxTag, enableLogcat,
@@ -12053,6 +12927,10 @@
public List<CellBroadcastIdRange> getCellBroadcastIdRanges(int subId) {
mApp.enforceCallingPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS,
"getCellBroadcastIdRanges");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_MESSAGING, "getCellBroadcastIdRanges");
+
final long identity = Binder.clearCallingIdentity();
try {
return getPhone(subId).getCellBroadcastIdRanges();
@@ -12072,6 +12950,10 @@
@Nullable IIntegerConsumer callback) {
mApp.enforceCallingPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS,
"setCellBroadcastIdRanges");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_MESSAGING, "setCellBroadcastIdRanges");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhoneFromSubId(subId);
@@ -12119,16 +13001,50 @@
* @param enableSatellite {@code true} to enable the satellite modem and
* {@code false} to disable.
* @param enableDemoMode {@code true} to enable demo mode and {@code false} to disable.
+ * @param isEmergency {@code true} to enable emergency mode, {@code false} otherwise.
* @param callback The callback to get the result of the request.
*
* @throws SecurityException if the caller doesn't have the required permission.
*/
@Override
public void requestSatelliteEnabled(int subId, boolean enableSatellite, boolean enableDemoMode,
- @NonNull IIntegerConsumer callback) {
+ boolean isEmergency, @NonNull IIntegerConsumer callback) {
enforceSatelliteCommunicationPermission("requestSatelliteEnabled");
- mSatelliteController.requestSatelliteEnabled(subId, enableSatellite, enableDemoMode,
- callback);
+ if (enableSatellite) {
+ ResultReceiver resultReceiver = new ResultReceiver(mMainThreadHandler) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ Log.d(LOG_TAG, "Satellite access restriction resultCode=" + resultCode
+ + ", resultData=" + resultData);
+ boolean isAllowed = false;
+ Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(
+ callback::accept);
+ if (resultCode == SATELLITE_RESULT_SUCCESS) {
+ if (resultData != null
+ && resultData.containsKey(KEY_SATELLITE_COMMUNICATION_ALLOWED)) {
+ isAllowed = resultData.getBoolean(KEY_SATELLITE_COMMUNICATION_ALLOWED);
+ } else {
+ loge("KEY_SATELLITE_COMMUNICATION_ALLOWED does not exist.");
+ }
+ } else {
+ result.accept(resultCode);
+ return;
+ }
+ if (isAllowed) {
+ mSatelliteController.requestSatelliteEnabled(
+ subId, enableSatellite, enableDemoMode, callback);
+ } else {
+ result.accept(SATELLITE_RESULT_ACCESS_BARRED);
+ }
+ }
+ };
+ mSatelliteAccessController.requestIsCommunicationAllowedForCurrentLocation(
+ subId, resultReceiver);
+ } else {
+ // No need to check if satellite is allowed at current location when disabling satellite
+ mSatelliteController.requestSatelliteEnabled(
+ subId, enableSatellite, enableDemoMode, callback);
+ }
}
/**
@@ -12163,6 +13079,22 @@
}
/**
+ * Request to get whether the satellite service is enabled with emergency mode.
+ *
+ * @param subId The subId of the subscription to check whether the satellite demo mode
+ * is enabled for.
+ * @param result The result receiver that returns whether the satellite emergency mode is
+ * enabled if the request is successful or an error code if the request failed.
+ *
+ * @throws SecurityException if the caller doesn't have the required permission.
+ */
+ @Override
+ public void requestIsEmergencyModeEnabled(int subId, @NonNull ResultReceiver result) {
+ enforceSatelliteCommunicationPermission("requestIsEmergencyModeEnabled");
+ result.send(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, null);
+ }
+
+ /**
* Request to get whether the satellite service is supported on the device.
*
* @param subId The subId of the subscription to check satellite service support for.
@@ -12276,13 +13208,13 @@
* @param subId The subId of the subscription to register for provision state changed.
* @param callback The callback to handle the satellite provision state changed event.
*
- * @return The {@link SatelliteManager.SatelliteError} result of the operation.
+ * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have the required permission.
*/
@Override
- @SatelliteManager.SatelliteError public int registerForSatelliteProvisionStateChanged(int subId,
- @NonNull ISatelliteProvisionStateCallback callback) {
+ @SatelliteManager.SatelliteResult public int registerForSatelliteProvisionStateChanged(
+ int subId, @NonNull ISatelliteProvisionStateCallback callback) {
enforceSatelliteCommunicationPermission("registerForSatelliteProvisionStateChanged");
return mSatelliteController.registerForSatelliteProvisionStateChanged(subId, callback);
}
@@ -12326,13 +13258,13 @@
* @param subId The subId of the subscription to register for satellite modem state changed.
* @param callback The callback to handle the satellite modem state changed event.
*
- * @return The {@link SatelliteManager.SatelliteError} result of the operation.
+ * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have the required permission.
*/
@Override
- @SatelliteManager.SatelliteError public int registerForSatelliteModemStateChanged(int subId,
- @NonNull ISatelliteStateCallback callback) {
+ @SatelliteManager.SatelliteResult public int registerForSatelliteModemStateChanged(int subId,
+ @NonNull ISatelliteModemStateCallback callback) {
enforceSatelliteCommunicationPermission("registerForSatelliteModemStateChanged");
return mSatelliteController.registerForSatelliteModemStateChanged(subId, callback);
}
@@ -12343,15 +13275,15 @@
*
* @param subId The subId of the subscription to unregister for satellite modem state changed.
* @param callback The callback that was passed to
- * {@link #registerForSatelliteModemStateChanged(int, ISatelliteStateCallback)}.
+ * {@link #registerForModemStateChanged(int, ISatelliteModemStateCallback)}.
*
* @throws SecurityException if the caller doesn't have the required permission.
*/
@Override
- public void unregisterForSatelliteModemStateChanged(int subId,
- @NonNull ISatelliteStateCallback callback) {
- enforceSatelliteCommunicationPermission("unregisterForSatelliteModemStateChanged");
- mSatelliteController.unregisterForSatelliteModemStateChanged(subId, callback);
+ public void unregisterForModemStateChanged(int subId,
+ @NonNull ISatelliteModemStateCallback callback) {
+ enforceSatelliteCommunicationPermission("unregisterForModemStateChanged");
+ mSatelliteController.unregisterForModemStateChanged(subId, callback);
}
/**
@@ -12360,15 +13292,15 @@
* @param subId The subId of the subscription to register for incoming satellite datagrams.
* @param callback The callback to handle incoming datagrams over satellite.
*
- * @return The {@link SatelliteManager.SatelliteError} result of the operation.
+ * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have the required permission.
*/
@Override
- @SatelliteManager.SatelliteError public int registerForSatelliteDatagram(int subId,
+ @SatelliteManager.SatelliteResult public int registerForIncomingDatagram(int subId,
@NonNull ISatelliteDatagramCallback callback) {
- enforceSatelliteCommunicationPermission("registerForSatelliteDatagram");
- return mSatelliteController.registerForSatelliteDatagram(subId, callback);
+ enforceSatelliteCommunicationPermission("registerForIncomingDatagram");
+ return mSatelliteController.registerForIncomingDatagram(subId, callback);
}
/**
@@ -12377,15 +13309,15 @@
*
* @param subId The subId of the subscription to unregister for incoming satellite datagrams.
* @param callback The callback that was passed to
- * {@link #registerForSatelliteDatagram(int, ISatelliteDatagramCallback)}.
+ * {@link #registerForIncomingDatagram(int, ISatelliteDatagramCallback)}.
*
* @throws SecurityException if the caller doesn't have the required permission.
*/
@Override
- public void unregisterForSatelliteDatagram(int subId,
+ public void unregisterForIncomingDatagram(int subId,
@NonNull ISatelliteDatagramCallback callback) {
- enforceSatelliteCommunicationPermission("unregisterForSatelliteDatagram");
- mSatelliteController.unregisterForSatelliteDatagram(subId, callback);
+ enforceSatelliteCommunicationPermission("unregisterForIncomingDatagram");
+ mSatelliteController.unregisterForIncomingDatagram(subId, callback);
}
/**
@@ -12396,14 +13328,13 @@
* {@link SatelliteDatagramCallback#onSatelliteDatagramReceived(long, SatelliteDatagram, int, Consumer)})}
*
* @param subId The subId of the subscription used for receiving datagrams.
- * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request.
+ * @param callback The callback to get {@link SatelliteManager.SatelliteResult} of the request.
*
* @throws SecurityException if the caller doesn't have required permission.
*/
- @Override
- public void pollPendingSatelliteDatagrams(int subId, IIntegerConsumer callback) {
- enforceSatelliteCommunicationPermission("pollPendingSatelliteDatagrams");
- mSatelliteController.pollPendingSatelliteDatagrams(subId, callback);
+ public void pollPendingDatagrams(int subId, IIntegerConsumer callback) {
+ enforceSatelliteCommunicationPermission("pollPendingDatagrams");
+ mSatelliteController.pollPendingDatagrams(subId, callback);
}
/**
@@ -12420,17 +13351,17 @@
* Datagram will be passed down to modem without any encoding or encryption.
* @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
* full screen mode.
- * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request.
+ * @param callback The callback to get {@link SatelliteManager.SatelliteResult} of the request.
*
* @throws SecurityException if the caller doesn't have required permission.
*/
@Override
- public void sendSatelliteDatagram(int subId, @SatelliteManager.DatagramType int datagramType,
+ public void sendDatagram(int subId, @SatelliteManager.DatagramType int datagramType,
@NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
@NonNull IIntegerConsumer callback) {
- enforceSatelliteCommunicationPermission("sendSatelliteDatagram");
- mSatelliteController.sendSatelliteDatagram(subId, datagramType, datagram,
- needFullScreenPointingUI, callback);
+ enforceSatelliteCommunicationPermission("sendDatagram");
+ mSatelliteController.sendDatagram(subId, datagramType, datagram, needFullScreenPointingUI,
+ callback);
}
/**
@@ -12445,11 +13376,10 @@
* @throws SecurityException if the caller doesn't have the required permission.
*/
@Override
- public void requestIsSatelliteCommunicationAllowedForCurrentLocation(int subId,
+ public void requestIsCommunicationAllowedForCurrentLocation(int subId,
@NonNull ResultReceiver result) {
- enforceSatelliteCommunicationPermission(
- "requestIsSatelliteCommunicationAllowedForCurrentLocation");
- mSatelliteController.requestIsSatelliteCommunicationAllowedForCurrentLocation(subId,
+ enforceSatelliteCommunicationPermission("requestIsCommunicationAllowedForCurrentLocation");
+ mSatelliteAccessController.requestIsCommunicationAllowedForCurrentLocation(subId,
result);
}
@@ -12479,9 +13409,192 @@
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void onDeviceAlignedWithSatellite(int subId, @NonNull boolean isAligned) {
+ public void setDeviceAlignedWithSatellite(int subId, @NonNull boolean isAligned) {
enforceSatelliteCommunicationPermission("informDeviceAlignedToSatellite");
- mSatelliteController.onDeviceAlignedWithSatellite(subId, isAligned);
+ mSatelliteController.setDeviceAlignedWithSatellite(subId, isAligned);
+ }
+
+ /**
+ * Add a restriction reason for disallowing carrier supported satellite plmn scan and attach
+ * by modem.
+ *
+ * @param subId The subId of the subscription to request for.
+ * @param reason Reason for disallowing satellite communication for carrier.
+ * @param callback Listener for the {@link SatelliteManager.SatelliteError} result of the
+ * operation.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ */
+ public void addAttachRestrictionForCarrier(int subId,
+ @SatelliteManager.SatelliteCommunicationRestrictionReason int reason,
+ @NonNull IIntegerConsumer callback) {
+ enforceSatelliteCommunicationPermission("addAttachRestrictionForCarrier");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mSatelliteController.addAttachRestrictionForCarrier(subId, reason, callback);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Remove a restriction reason for disallowing carrier supported satellite plmn scan and attach
+ * by modem.
+ *
+ * @param subId The subId of the subscription to request for.
+ * @param reason Reason for disallowing satellite communication.
+ * @param callback Listener for the {@link SatelliteManager.SatelliteError} result of the
+ * operation.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ */
+ public void removeAttachRestrictionForCarrier(int subId,
+ @SatelliteManager.SatelliteCommunicationRestrictionReason int reason,
+ @NonNull IIntegerConsumer callback) {
+ enforceSatelliteCommunicationPermission("removeAttachRestrictionForCarrier");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mSatelliteController.removeAttachRestrictionForCarrier(subId, reason, callback);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Get reasons for disallowing satellite communication, as requested by
+ * {@link #addSatelliteAttachRestrictionForCarrier(int, int, IIntegerConsumer)}.
+ *
+ * @param subId The subId of the subscription to request for.
+ *
+ * @return Integer array of reasons for disallowing satellite communication.
+ *
+ * @throws SecurityException if the caller doesn't have the required permission.
+ */
+ public @NonNull int[] getAttachRestrictionReasonsForCarrier(
+ int subId) {
+ enforceSatelliteCommunicationPermission("getAttachRestrictionReasonsForCarrier");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Set<Integer> reasonSet =
+ mSatelliteController.getAttachRestrictionReasonsForCarrier(subId);
+ return reasonSet.stream().mapToInt(i->i).toArray();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Request to get the signal strength of the satellite connection.
+ *
+ * @param subId The subId of the subscription to request for.
+ * @param result Result receiver to get the error code of the request and the current signal
+ * strength of the satellite connection.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ */
+ @Override
+ public void requestNtnSignalStrength(int subId, @NonNull ResultReceiver result) {
+ enforceSatelliteCommunicationPermission("requestNtnSignalStrength");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mSatelliteController.requestNtnSignalStrength(subId, result);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Registers for NTN signal strength changed from satellite modem. If the registration operation
+ * is not successful, a {@link ServiceSpecificException} that contains
+ * {@link SatelliteManager.SatelliteResult} will be thrown.
+ *
+ * @param subId The subId of the subscription to request for.
+ * @param callback The callback to handle the NTN signal strength changed event. If the
+ * operation is successful, {@link NtnSignalStrengthCallback#onNtnSignalStrengthChanged(
+ * NtnSignalStrength)} will return an instance of {@link NtnSignalStrength} with a value of
+ * {@link NtnSignalStrength.NtnSignalStrengthLevel} when the signal strength of non-terrestrial
+ * network has changed.
+ *
+ * @throws SecurityException If the caller doesn't have the required permission.
+ * @throws ServiceSpecificException If the callback registration operation fails.
+ */
+ @Override
+ public void registerForNtnSignalStrengthChanged(int subId,
+ @NonNull INtnSignalStrengthCallback callback) throws RemoteException {
+ enforceSatelliteCommunicationPermission("registerForNtnSignalStrengthChanged");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mSatelliteController.registerForNtnSignalStrengthChanged(subId, callback);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Unregisters for NTN signal strength changed from satellite modem.
+ * If callback was not registered before, the request will be ignored.
+ *
+ * @param subId The subId of the subscription to unregister for listening NTN signal strength
+ * changed event.
+ * @param callback The callback that was passed to
+ * {@link #registerForNtnSignalStrengthChanged(int, INtnSignalStrengthCallback)}
+ *
+ * @throws SecurityException if the caller doesn't have the required permission.
+ */
+ @Override
+ public void unregisterForNtnSignalStrengthChanged(
+ int subId, @NonNull INtnSignalStrengthCallback callback) {
+ enforceSatelliteCommunicationPermission("unregisterForNtnSignalStrengthChanged");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mSatelliteController.unregisterForNtnSignalStrengthChanged(subId, callback);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Registers for satellite capabilities change event from the satellite service.
+ *
+ * @param subId The subId of the subscription to request for.
+ * @param callback The callback to handle the satellite capabilities changed event.
+ *
+ * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ */
+ @Override
+ @SatelliteManager.SatelliteResult public int registerForCapabilitiesChanged(
+ int subId, @NonNull ISatelliteCapabilitiesCallback callback) {
+ enforceSatelliteCommunicationPermission("registerForCapabilitiesChanged");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mSatelliteController.registerForCapabilitiesChanged(subId, callback);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Unregisters for satellite capabilities change event from the satellite service.
+ * If callback was not registered before, the request will be ignored.
+ *
+ * @param subId The subId of the subscription to unregister for satellite capabilities change.
+ * @param callback The callback that was passed to.
+ * {@link #registerForCapabilitiesChanged(int, ISatelliteCapabilitiesCallback)}.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ */
+ @Override
+ public void unregisterForCapabilitiesChanged(int subId,
+ @NonNull ISatelliteCapabilitiesCallback callback) {
+ enforceSatelliteCommunicationPermission("unregisterForCapabilitiesChanged");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mSatelliteController.unregisterForCapabilitiesChanged(subId, callback);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
/**
@@ -12557,20 +13670,300 @@
}
/**
- * This API can be used by only CTS to update the timeout duration in milliseconds whether
- * the device is aligned with the satellite for demo mode
+ * This API can be used by only CTS to override the timeout durations used by the
+ * DatagramController module.
*
* @param timeoutMillis The timeout duration in millisecond.
* @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
*/
- public boolean setSatelliteDeviceAlignedTimeoutDuration(long timeoutMillis) {
- Log.d(LOG_TAG, "setDeviceAlignedTimeoutDuration - " + timeoutMillis);
+ public boolean setDatagramControllerTimeoutDuration(
+ boolean reset, int timeoutType, long timeoutMillis) {
+ Log.d(LOG_TAG, "setDatagramControllerTimeoutDuration - " + timeoutMillis + ", reset="
+ + reset + ", timeoutMillis=" + timeoutMillis);
TelephonyPermissions.enforceShellOnly(
- Binder.getCallingUid(), "setDeviceAlignedTimeoutDuration");
+ Binder.getCallingUid(), "setDatagramControllerTimeoutDuration");
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp,
SubscriptionManager.INVALID_SUBSCRIPTION_ID,
- "setDeviceAlignedTimeoutDuration");
- return mSatelliteController.setSatelliteDeviceAlignedTimeoutDuration(timeoutMillis);
+ "setDatagramControllerTimeoutDuration");
+ return mSatelliteController.setDatagramControllerTimeoutDuration(
+ reset, timeoutType, timeoutMillis);
+ }
+
+ /**
+ * This API can be used by only CTS to override the timeout durations used by the
+ * SatelliteController module.
+ *
+ * @param timeoutMillis The timeout duration in millisecond.
+ * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
+ */
+ public boolean setSatelliteControllerTimeoutDuration(
+ boolean reset, int timeoutType, long timeoutMillis) {
+ Log.d(LOG_TAG, "setSatelliteControllerTimeoutDuration - " + timeoutMillis + ", reset="
+ + reset + ", timeoutMillis=" + timeoutMillis);
+ TelephonyPermissions.enforceShellOnly(
+ Binder.getCallingUid(), "setSatelliteControllerTimeoutDuration");
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ "setSatelliteControllerTimeoutDuration");
+ return mSatelliteController.setSatelliteControllerTimeoutDuration(
+ reset, timeoutType, timeoutMillis);
+ }
+
+ /**
+ * This API can be used in only testing to override connectivity status in monitoring emergency
+ * calls and sending EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer.
+ *
+ * @param handoverType The type of handover from emergency call to satellite messaging. Use one
+ * of the following values to enable the override:
+ * 0 - EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS
+ * 1 - EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911
+ * To disable the override, use -1 for handoverType.
+ * @param delaySeconds The event EVENT_DISPLAY_EMERGENCY_MESSAGE will be sent to Dialer
+ * delaySeconds after the emergency call starts.
+ * @return {@code true} if the handover type is set successfully, {@code false} otherwise.
+ */
+ public boolean setEmergencyCallToSatelliteHandoverType(int handoverType, int delaySeconds) {
+ Log.d(LOG_TAG, "setEmergencyCallToSatelliteHandoverType - " + handoverType);
+ TelephonyPermissions.enforceShellOnly(
+ Binder.getCallingUid(), "setEmergencyCallToSatelliteHandoverType");
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ "setEmergencyCallToSatelliteHandoverType");
+ return mSatelliteController.setEmergencyCallToSatelliteHandoverType(
+ handoverType, delaySeconds);
+ }
+
+ /**
+ * This API can be used in only testing to override oem-enabled satellite provision status.
+ *
+ * @param reset {@code true} mean the overriding status should not be used, {@code false}
+ * otherwise.
+ * @param isProvisioned The overriding provision status.
+ * @return {@code true} if the provision status is set successfully, {@code false} otherwise.
+ */
+ public boolean setOemEnabledSatelliteProvisionStatus(boolean reset, boolean isProvisioned) {
+ Log.d(LOG_TAG, "setOemEnabledSatelliteProvisionStatus - reset=" + reset
+ + ", isProvisioned=" + isProvisioned);
+ TelephonyPermissions.enforceShellOnly(
+ Binder.getCallingUid(), "setOemEnabledSatelliteProvisionStatus");
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ "setOemEnabledSatelliteProvisionStatus");
+ return mSatelliteController.setOemEnabledSatelliteProvisionStatus(reset, isProvisioned);
+ }
+
+ /**
+ * This API should be used by only CTS tests to forcefully set telephony country codes.
+ *
+ * @return {@code true} if the country code is set successfully, {@code false} otherwise.
+ */
+ public boolean setCountryCodes(boolean reset, List<String> currentNetworkCountryCodes,
+ Map cachedNetworkCountryCodes, String locationCountryCode,
+ long locationCountryCodeTimestampNanos) {
+ Log.d(LOG_TAG, "setCountryCodes: currentNetworkCountryCodes="
+ + String.join(", ", currentNetworkCountryCodes)
+ + ", locationCountryCode=" + locationCountryCode
+ + ", locationCountryCodeTimestampNanos" + locationCountryCodeTimestampNanos
+ + ", reset=" + reset + ", cachedNetworkCountryCodes="
+ + String.join(", ", cachedNetworkCountryCodes.keySet()));
+ TelephonyPermissions.enforceShellOnly(
+ Binder.getCallingUid(), "setCachedLocationCountryCode");
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ "setCachedLocationCountryCode");
+ return TelephonyCountryDetector.getInstance(getDefaultPhone().getContext()).setCountryCodes(
+ reset, currentNetworkCountryCodes, cachedNetworkCountryCodes, locationCountryCode,
+ locationCountryCodeTimestampNanos);
+ }
+
+ /**
+ * This API should be used by only CTS tests to override the overlay configs of satellite
+ * access controller.
+ *
+ * @param reset {@code true} mean the overridden configs should not be used, {@code false}
+ * otherwise.
+ * @return {@code true} if the overlay configs are set successfully, {@code false} otherwise.
+ */
+ public boolean setSatelliteAccessControlOverlayConfigs(boolean reset, boolean isAllowed,
+ String s2CellFile, long locationFreshDurationNanos,
+ List<String> satelliteCountryCodes) {
+ Log.d(LOG_TAG, "setSatelliteAccessControlOverlayConfigs: reset=" + reset
+ + ", isAllowed" + isAllowed + ", s2CellFile=" + s2CellFile
+ + ", locationFreshDurationNanos=" + locationFreshDurationNanos
+ + ", satelliteCountryCodes=" + ((satelliteCountryCodes != null)
+ ? String.join(", ", satelliteCountryCodes) : null));
+ TelephonyPermissions.enforceShellOnly(
+ Binder.getCallingUid(), "setSatelliteAccessControlOverlayConfigs");
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ "setSatelliteAccessControlOverlayConfigs");
+ return mSatelliteAccessController.setSatelliteAccessControlOverlayConfigs(reset, isAllowed,
+ s2CellFile, locationFreshDurationNanos, satelliteCountryCodes);
+ }
+
+ /**
+ * This API can be used by only CTS to override the cached value for the device overlay config
+ * value : config_send_satellite_datagram_to_modem_in_demo_mode, which determines whether
+ * outgoing satellite datagrams should be sent to modem in demo mode.
+ *
+ * @param shouldSendToModemInDemoMode Whether send datagram in demo mode should be sent to
+ * satellite modem or not.
+ *
+ * @return {@code true} if the operation is successful, {@code false} otherwise.
+ */
+ public boolean setShouldSendDatagramToModemInDemoMode(boolean shouldSendToModemInDemoMode) {
+ if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+ Log.d(LOG_TAG, "shouldSendDatagramToModemInDemoMode: oemEnabledSatelliteFlag is "
+ + "disabled");
+ return false;
+ }
+ Log.d(LOG_TAG, "setShouldSendDatagramToModemInDemoMode");
+ TelephonyPermissions.enforceShellOnly(
+ Binder.getCallingUid(), "setShouldSendDatagramToModemInDemoMode");
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ "setShouldSendDatagramToModemInDemoMode");
+ return mSatelliteController.setShouldSendDatagramToModemInDemoMode(
+ shouldSendToModemInDemoMode);
+ }
+
+ /**
+ * Sets the service defined in ComponentName to be bound.
+ *
+ * This should only be used for testing.
+ * @return {@code true} if the DomainSelectionService to bind to was set,
+ * {@code false} otherwise.
+ */
+ @Override
+ public boolean setDomainSelectionServiceOverride(ComponentName componentName) {
+ Log.i(LOG_TAG, "setDomainSelectionServiceOverride component=" + componentName);
+
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "setDomainSelectionServiceOverride");
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp,
+ getDefaultSubscription(), "setDomainSelectionServiceOverride");
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+ return DomainSelectionResolver.getInstance()
+ .setDomainSelectionServiceOverride(componentName);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return false;
+ }
+
+ /**
+ * Clears the DomainSelectionService override.
+ *
+ * This should only be used for testing.
+ * @return {@code true} if the DomainSelectionService override was cleared,
+ * {@code false} otherwise.
+ */
+ @Override
+ public boolean clearDomainSelectionServiceOverride() {
+ Log.i(LOG_TAG, "clearDomainSelectionServiceOverride");
+
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "clearDomainSelectionServiceOverride");
+ TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp,
+ getDefaultSubscription(), "clearDomainSelectionServiceOverride");
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+ return DomainSelectionResolver.getInstance()
+ .clearDomainSelectionServiceOverride();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return false;
+ }
+
+ /**
+ * Enable or disable notifications sent for cellular identifier disclosure events.
+ *
+ * Disclosure events are defined as instances where a device has sent a cellular identifier
+ * on the Non-access stratum (NAS) before a security context is established. As a result the
+ * identifier is sent in the clear, which has privacy implications for the user.
+ *
+ * @param enable if notifications about disclosure events should be enabled
+ * @throws SecurityException if the caller does not have the required privileges
+ * @throws UnsupportedOperationException if the modem does not support this feature.
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ public void setEnableCellularIdentifierDisclosureNotifications(boolean enable) {
+ enforceModifyPermission();
+ checkForIdentifierDisclosureNotificationSupport();
+
+ SharedPreferences.Editor editor = mTelephonySharedPreferences.edit();
+ editor.putBoolean(Phone.PREF_IDENTIFIER_DISCLOSURE_NOTIFICATIONS_ENABLED, enable);
+ editor.apply();
+
+ // Each phone instance is responsible for updating its respective modem immediately
+ // after we've made a preference change.
+ for (Phone phone : PhoneFactory.getPhones()) {
+ phone.handleIdentifierDisclosureNotificationPreferenceChange();
+ }
+ }
+
+ /**
+ * Get whether or not cellular identifier disclosure notifications are enabled.
+ *
+ * @throws SecurityException if the caller does not have the required privileges
+ * @throws UnsupportedOperationException if the modem does not support this feature.
+ */
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public boolean isCellularIdentifierDisclosureNotificationsEnabled() {
+ enforceReadPrivilegedPermission("isCellularIdentifierDisclosureNotificationEnabled");
+ checkForIdentifierDisclosureNotificationSupport();
+ return getDefaultPhone().getIdentifierDisclosureNotificationsPreferenceEnabled();
+ }
+
+ /**
+ * Enables or disables notifications sent when cellular null cipher or integrity algorithms
+ * are in use by the cellular modem.
+ *
+ * @throws IllegalStateException if the Telephony process is not currently available
+ * @throws SecurityException if the caller does not have the required privileges
+ * @throws UnsupportedOperationException if the modem does not support reporting on ciphering
+ * and integrity algorithms in use
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ public void setNullCipherNotificationsEnabled(boolean enable) {
+ enforceModifyPermission();
+ checkForNullCipherNotificationSupport();
+
+ SharedPreferences.Editor editor = mTelephonySharedPreferences.edit();
+ editor.putBoolean(Phone.PREF_NULL_CIPHER_NOTIFICATIONS_ENABLED, enable);
+ editor.apply();
+
+ // Each phone instance is responsible for updating its respective modem immediately
+ // after a preference change.
+ for (Phone phone : PhoneFactory.getPhones()) {
+ phone.handleNullCipherNotificationPreferenceChanged();
+ }
+ }
+
+ /**
+ * Get whether notifications are enabled for null cipher or integrity algorithms in use by the
+ * cellular modem.
+ *
+ * @throws IllegalStateException if the Telephony process is not currently available
+ * @throws SecurityException if the caller does not have the required privileges
+ * @throws UnsupportedOperationException if the modem does not support reporting on ciphering
+ * and integrity algorithms in use
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public boolean isNullCipherNotificationsEnabled() {
+ enforceReadPrivilegedPermission("isNullCipherNotificationsEnabled");
+ checkForNullCipherNotificationSupport();
+ return getDefaultPhone().getNullCipherNotificationsPreferenceEnabled();
}
/**
@@ -12609,19 +14002,72 @@
*/
private static class CallerCallbackInfo {
private final Consumer<Integer> mConsumer;
- private final int mCarrierId;
+ private final Set<Integer> mCarrierIds;
- public CallerCallbackInfo(Consumer<Integer> consumer, int carrierId) {
+ public CallerCallbackInfo(Consumer<Integer> consumer, Set<Integer> carrierIds) {
mConsumer = consumer;
- mCarrierId = carrierId;
+ mCarrierIds = carrierIds;
}
public Consumer<Integer> getConsumer() {
return mConsumer;
}
- public int getCarrierId() {
- return mCarrierId;
+ public Set<Integer> getCarrierIds() {
+ return mCarrierIds;
+ }
+ }
+
+ /*
+ * PhoneInterfaceManager is a singleton. Unit test calls the init() with context.
+ * But the context that is passed in is unused if the phone app is already alive.
+ * In this case PackageManager object is different in PhoneInterfaceManager and Unit test.
+ */
+ @VisibleForTesting
+ public void setPackageManager(PackageManager packageManager) {
+ mPackageManager = packageManager;
+ }
+
+ /*
+ * PhoneInterfaceManager is a singleton. Unit test calls the init() with context.
+ * But the context that is passed in is unused if the phone app is already alive.
+ * In this case PackageManager object is different in PhoneInterfaceManager and Unit test.
+ */
+ @VisibleForTesting
+ public void setAppOpsManager(AppOpsManager appOps) {
+ mAppOps = appOps;
+ }
+
+ /*
+ * PhoneInterfaceManager is a singleton. Unit test calls the init() with FeatureFlags.
+ * But the FeatureFlags that is passed in is unused if the phone app is already alive.
+ * In this case FeatureFlags object is different in PhoneInterfaceManager and Unit test.
+ */
+ @VisibleForTesting
+ public void setFeatureFlags(FeatureFlags featureFlags) {
+ mFeatureFlags = featureFlags;
+ }
+
+ /**
+ * Make sure the device has required telephony feature
+ *
+ * @throws UnsupportedOperationException if the device does not have required telephony feature
+ */
+ private void enforceTelephonyFeatureWithException(@Nullable String callingPackage,
+ @NonNull String telephonyFeature, @NonNull String methodName) {
+ if (callingPackage == null || mPackageManager == null) {
+ return;
+ }
+
+ if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
+ || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
+ Binder.getCallingUserHandle())) {
+ return;
+ }
+
+ if (!mPackageManager.hasSystemFeature(telephonyFeature)) {
+ throw new UnsupportedOperationException(
+ methodName + " is unsupported without " + telephonyFeature);
}
}
}
diff --git a/src/com/android/phone/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java
index 4826d2b..0c8a9c7 100644
--- a/src/com/android/phone/PhoneUtils.java
+++ b/src/com/android/phone/PhoneUtils.java
@@ -703,8 +703,12 @@
}
public static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) {
- return makePstnPhoneAccountHandleWithPrefix(phone, "",
- false, phone.getUserHandle());
+ if (phone == null) {
+ return null;
+ } else {
+ return makePstnPhoneAccountHandleWithPrefix(phone, "",
+ false, phone.getUserHandle());
+ }
}
public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(
diff --git a/src/com/android/phone/RcsProvisioningMonitor.java b/src/com/android/phone/RcsProvisioningMonitor.java
index a948d08..87a2869 100644
--- a/src/com/android/phone/RcsProvisioningMonitor.java
+++ b/src/com/android/phone/RcsProvisioningMonitor.java
@@ -524,7 +524,7 @@
filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
mPhone.registerReceiver(mReceiver, filter);
mTelephonyRegistryManager.addOnSubscriptionsChangedListener(
- mSubChangedListener, mSubChangedListener.getHandlerExecutor());
+ mSubChangedListener, mHandler::post);
mDmaChangedListener.register();
//initialize configs for all active sub
onSubChanged();
diff --git a/src/com/android/phone/ServiceStateProvider.java b/src/com/android/phone/ServiceStateProvider.java
index 3fa1e58..894d1c7 100644
--- a/src/com/android/phone/ServiceStateProvider.java
+++ b/src/com/android/phone/ServiceStateProvider.java
@@ -41,6 +41,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.Parcel;
+import android.os.UserHandle;
import android.telephony.LocationAccessPolicy;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
@@ -577,27 +578,31 @@
ServiceState newSS, int subId) {
final boolean firstUpdate = (oldSS == null) ? true : false;
- // for every field, if the field has changed values, notify via the provider
+ // For every field, if the field has changed values, notify via the provider to all users
if (firstUpdate || voiceRegStateChanged(oldSS, newSS)) {
context.getContentResolver().notifyChange(
getUriForSubscriptionIdAndField(subId, VOICE_REG_STATE),
- /* observer= */ null, /* syncToNetwork= */ false);
+ /* observer= */ null, /* syncToNetwork= */ false, UserHandle.USER_ALL);
}
if (firstUpdate || dataRegStateChanged(oldSS, newSS)) {
context.getContentResolver().notifyChange(
- getUriForSubscriptionIdAndField(subId, DATA_REG_STATE), null, false);
+ getUriForSubscriptionIdAndField(subId, DATA_REG_STATE),
+ /* observer= */ null, /* syncToNetwork= */ false, UserHandle.USER_ALL);
}
if (firstUpdate || voiceRoamingTypeChanged(oldSS, newSS)) {
context.getContentResolver().notifyChange(
- getUriForSubscriptionIdAndField(subId, VOICE_ROAMING_TYPE), null, false);
+ getUriForSubscriptionIdAndField(subId, VOICE_ROAMING_TYPE),
+ /* observer= */ null, /* syncToNetwork= */ false, UserHandle.USER_ALL);
}
if (firstUpdate || dataRoamingTypeChanged(oldSS, newSS)) {
context.getContentResolver().notifyChange(
- getUriForSubscriptionIdAndField(subId, DATA_ROAMING_TYPE), null, false);
+ getUriForSubscriptionIdAndField(subId, DATA_ROAMING_TYPE),
+ /* observer= */ null, /* syncToNetwork= */ false, UserHandle.USER_ALL);
}
if (firstUpdate || dataNetworkTypeChanged(oldSS, newSS)) {
context.getContentResolver().notifyChange(
- getUriForSubscriptionIdAndField(subId, DATA_NETWORK_TYPE), null, false);
+ getUriForSubscriptionIdAndField(subId, DATA_NETWORK_TYPE),
+ /* observer= */ null, /* syncToNetwork= */ false, UserHandle.USER_ALL);
}
}
@@ -635,14 +640,15 @@
@VisibleForTesting
public static void notifyChangeForSubId(Context context, ServiceState oldSS, ServiceState newSS,
int subId) {
- // if the voice or data registration or roaming state field has changed values, notify via
- // the provider.
+ // If the voice or data registration or roaming state field has changed values, notify via
+ // the provider to all users.
// If oldSS is null and newSS is not (e.g. first update of service state) this will also
- // notify
+ // notify to all users.
if (oldSS == null || voiceRegStateChanged(oldSS, newSS) || dataRegStateChanged(oldSS, newSS)
|| voiceRoamingTypeChanged(oldSS, newSS) || dataRoamingTypeChanged(oldSS, newSS)
|| dataNetworkTypeChanged(oldSS, newSS)) {
- context.getContentResolver().notifyChange(getUriForSubscriptionId(subId), null, false);
+ context.getContentResolver().notifyChange(getUriForSubscriptionId(subId),
+ /* observer= */ null, /* syncToNetwork= */ false, UserHandle.USER_ALL);
}
}
diff --git a/src/com/android/phone/SimPhonebookProvider.java b/src/com/android/phone/SimPhonebookProvider.java
index 8952865..3917d83 100644
--- a/src/com/android/phone/SimPhonebookProvider.java
+++ b/src/com/android/phone/SimPhonebookProvider.java
@@ -50,6 +50,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IIccPhoneBook;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.uicc.AdnRecord;
import com.android.internal.telephony.uicc.IccConstants;
@@ -173,17 +174,21 @@
@Override
public boolean onCreate() {
ContentResolver resolver = getContext().getContentResolver();
- return onCreate(getContext().getSystemService(SubscriptionManager.class),
+
+ SubscriptionManager sm = getContext().getSystemService(SubscriptionManager.class);
+ if (sm == null) {
+ return false;
+ } else if (Flags.workProfileApiSplit()) {
+ sm = sm.createForAllUserProfiles();
+ }
+ return onCreate(sm,
SimPhonebookProvider::getIccPhoneBook,
uri -> resolver.notifyChange(uri, null));
}
@TestApi
- boolean onCreate(SubscriptionManager subscriptionManager,
+ boolean onCreate(@NonNull SubscriptionManager subscriptionManager,
Supplier<IIccPhoneBook> iccPhoneBookSupplier, ContentNotifier notifier) {
- if (subscriptionManager == null) {
- return false;
- }
mSubscriptionManager = subscriptionManager;
mIccPhoneBookSupplier = iccPhoneBookSupplier;
mContentNotifier = notifier;
diff --git a/src/com/android/phone/SpecialCharSequenceMgr.java b/src/com/android/phone/SpecialCharSequenceMgr.java
index 3bf0e1a..8fe084b 100644
--- a/src/com/android/phone/SpecialCharSequenceMgr.java
+++ b/src/com/android/phone/SpecialCharSequenceMgr.java
@@ -33,6 +33,7 @@
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.TelephonyCapabilities;
+import com.android.internal.telephony.flags.Flags;
import java.util.ArrayList;
import java.util.List;
@@ -208,6 +209,9 @@
private static int getNextSubIdForState(IccCardConstants.State state, Context context) {
SubscriptionManager subscriptionManager = SubscriptionManager.from(context);
+ if (Flags.workProfileApiSplit()) {
+ subscriptionManager = subscriptionManager.createForAllUserProfiles();
+ }
List<SubscriptionInfo> list = subscriptionManager.getActiveSubscriptionInfoList();
if (list == null) {
// getActiveSubscriptionInfoList was null callers expect an empty list.
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index 498e1ea..429b7cc 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -24,6 +24,9 @@
import static java.util.Map.entry;
import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
import android.os.Binder;
@@ -65,6 +68,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -187,8 +191,24 @@
"set-satellite-listening-timeout-duration";
private static final String SET_SATELLITE_POINTING_UI_CLASS_NAME =
"set-satellite-pointing-ui-class-name";
- private static final String SET_SATELLITE_DEVICE_ALIGNED_TIMEOUT_DURATION =
- "set-satellite-device-aligned-timeout-duration";
+ private static final String SET_DATAGRAM_CONTROLLER_TIMEOUT_DURATION =
+ "set-datagram-controller-timeout-duration";
+
+ private static final String SET_SATELLITE_CONTROLLER_TIMEOUT_DURATION =
+ "set-satellite-controller-timeout-duration";
+ private static final String SET_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE =
+ "set-emergency-call-to-satellite-handover-type";
+ private static final String SET_COUNTRY_CODES = "set-country-codes";
+ private static final String SET_SATELLITE_ACCESS_CONTROL_OVERLAY_CONFIGS =
+ "set-satellite-access-control-overlay-configs";
+ private static final String SET_OEM_ENABLED_SATELLITE_PROVISION_STATUS =
+ "set-oem-enabled-satellite-provision-status";
+ private static final String SET_SHOULD_SEND_DATAGRAM_TO_MODEM_IN_DEMO_MODE =
+ "set-should-send-datagram-to-modem-in-demo-mode";
+
+ private static final String DOMAIN_SELECTION_SUBCOMMAND = "domainselection";
+ private static final String DOMAIN_SELECTION_SET_SERVICE_OVERRIDE = "set-dss-override";
+ private static final String DOMAIN_SELECTION_CLEAR_SERVICE_OVERRIDE = "clear-dss-override";
private static final String INVALID_ENTRY_ERROR = "An emergency number (only allow '0'-'9', "
+ "'*', '#' or '+') needs to be specified after -a in the command ";
@@ -370,6 +390,8 @@
return setCarrierServicePackageOverride();
case CLEAR_CARRIER_SERVICE_PACKAGE_OVERRIDE:
return clearCarrierServicePackageOverride();
+ case DOMAIN_SELECTION_SUBCOMMAND:
+ return handleDomainSelectionCommand();
case SET_SATELLITE_SERVICE_PACKAGE_NAME:
return handleSetSatelliteServicePackageNameCommand();
case SET_SATELLITE_GATEWAY_SERVICE_PACKAGE_NAME:
@@ -378,8 +400,20 @@
return handleSetSatelliteListeningTimeoutDuration();
case SET_SATELLITE_POINTING_UI_CLASS_NAME:
return handleSetSatellitePointingUiClassNameCommand();
- case SET_SATELLITE_DEVICE_ALIGNED_TIMEOUT_DURATION:
- return handleSettSatelliteDeviceAlignedTimeoutDuration();
+ case SET_DATAGRAM_CONTROLLER_TIMEOUT_DURATION:
+ return handleSetDatagramControllerTimeoutDuration();
+ case SET_SATELLITE_CONTROLLER_TIMEOUT_DURATION:
+ return handleSetSatelliteControllerTimeoutDuration();
+ case SET_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE:
+ return handleSetEmergencyCallToSatelliteHandoverType();
+ case SET_SHOULD_SEND_DATAGRAM_TO_MODEM_IN_DEMO_MODE:
+ return handleSetShouldSendDatagramToModemInDemoMode();
+ case SET_SATELLITE_ACCESS_CONTROL_OVERLAY_CONFIGS:
+ return handleSetSatelliteAccessControlOverlayConfigs();
+ case SET_COUNTRY_CODES:
+ return handleSetCountryCodes();
+ case SET_OEM_ENABLED_SATELLITE_PROVISION_STATUS:
+ return handleSetOemEnabledSatelliteProvisionStatus();
default: {
return handleDefaultCommands(cmd);
}
@@ -434,6 +468,7 @@
onHelpRadio();
onHelpImei();
onHelpSatellite();
+ onHelpDomainSelection();
}
private void onHelpD2D() {
@@ -779,6 +814,37 @@
pw.println(" launch. If no option is specified, it will launch the default.");
pw.println(" -c: the satellite pointing UI app class name that Telephony will");
pw.println(" launch.");
+ pw.println(" set-emergency-call-to-satellite-handover-type [-t HANDOVER_TYPE ");
+ pw.println(" -d DELAY_SECONDS] Override connectivity status in monitoring emergency ");
+ pw.println(" call and sending EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer.");
+ pw.println(" Options are:");
+ pw.println(" -t: the emergency call to satellite handover type.");
+ pw.println(" If no option is specified, override is disabled.");
+ pw.println(" -d: the delay in seconds in sending EVENT_DISPLAY_EMERGENCY_MESSAGE.");
+ pw.println(" If no option is specified, there is no delay in sending the event.");
+ pw.println(" set-satellite-access-control-overlay-configs [-r -a -f SATELLITE_S2_FILE ");
+ pw.println(" -d LOCATION_FRESH_DURATION_NANOS -c COUNTRY_CODES] Override the overlay");
+ pw.println(" configs of satellite access controller.");
+ pw.println(" Options are:");
+ pw.println(" -r: clear the overriding. Absent means enable overriding.");
+ pw.println(" -a: the country codes is an allowed list. Absent means disallowed.");
+ pw.println(" -f: the satellite s2 file.");
+ pw.println(" -d: the location fresh duration nanos.");
+ pw.println(" -c: the list of satellite country codes separated by comma.");
+ pw.println(" set-country-codes [-r -n CURRENT_NETWORK_COUNTRY_CODES -c");
+ pw.println(" CACHED_NETWORK_COUNTRY_CODES -l LOCATION_COUNTRY_CODE -t");
+ pw.println(" LOCATION_COUNTRY_CODE_TIMESTAMP] ");
+ pw.println(" Override the cached location country code and its update timestamp. ");
+ pw.println(" Options are:");
+ pw.println(" -r: clear the overriding. Absent means enable overriding.");
+ pw.println(" -n: the current network country code ISOs.");
+ pw.println(" -c: the cached network country code ISOs.");
+ pw.println(" -l: the location country code ISO.");
+ pw.println(" -t: the update timestamp nanos of the location country code.");
+ pw.println(" set-oem-enabled-satellite-provision-status [-p true/false]");
+ pw.println(" Sets the OEM-enabled satellite provision status. Options are:");
+ pw.println(" -p: the overriding satellite provision status. If no option is ");
+ pw.println(" specified, reset the overridden provision status.");
}
private void onHelpImei() {
@@ -790,6 +856,15 @@
pw.println(" is specified, it will choose the default voice SIM slot.");
}
+ private void onHelpDomainSelection() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Domain Selection Commands:");
+ pw.println(" domainselection set-dss-override COMPONENT_NAME");
+ pw.println(" Sets the service defined in COMPONENT_NAME to be bound");
+ pw.println(" domainselection clear-dss-override");
+ pw.println(" Clears DomainSelectionService override.");
+ }
+
private int handleImsCommand() {
String arg = getNextArg();
if (arg == null) {
@@ -3217,6 +3292,55 @@
return 0;
}
+ private int handleSetEmergencyCallToSatelliteHandoverType() {
+ PrintWriter errPw = getErrPrintWriter();
+ int handoverType = -1;
+ int delaySeconds = 0;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-t": {
+ try {
+ handoverType = Integer.parseInt(getNextArgRequired());
+ } catch (NumberFormatException e) {
+ errPw.println("SetEmergencyCallToSatelliteHandoverType: require an integer"
+ + " for handoverType");
+ return -1;
+ }
+ break;
+ }
+ case "-d": {
+ try {
+ delaySeconds = Integer.parseInt(getNextArgRequired());
+ } catch (NumberFormatException e) {
+ errPw.println("SetEmergencyCallToSatelliteHandoverType: require an integer"
+ + " for delaySeconds");
+ return -1;
+ }
+ break;
+ }
+ }
+ }
+ Log.d(LOG_TAG, "handleSetEmergencyCallToSatelliteHandoverType: handoverType="
+ + handoverType + ", delaySeconds=" + delaySeconds);
+
+ try {
+ boolean result =
+ mInterface.setEmergencyCallToSatelliteHandoverType(handoverType, delaySeconds);
+ if (VDBG) {
+ Log.v(LOG_TAG, "setEmergencyCallToSatelliteHandoverType result =" + result);
+ }
+ getOutPrintWriter().println(result);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "setEmergencyCallToSatelliteHandoverType: " + handoverType
+ + ", error = " + e.getMessage());
+ errPw.println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
private int handleSetSatelliteListeningTimeoutDuration() {
PrintWriter errPw = getErrPrintWriter();
long timeoutMillis = 0;
@@ -3249,31 +3373,42 @@
return 0;
}
- private int handleSettSatelliteDeviceAlignedTimeoutDuration() {
+ private int handleSetDatagramControllerTimeoutDuration() {
PrintWriter errPw = getErrPrintWriter();
+ boolean reset = false;
+ int timeoutType = 0;
long timeoutMillis = 0;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
- case "-t": {
+ case "-d": {
timeoutMillis = Long.parseLong(getNextArgRequired());
break;
}
+ case "-r": {
+ reset = true;
+ break;
+ }
+ case "-t": {
+ timeoutType = Integer.parseInt(getNextArgRequired());
+ break;
+ }
}
}
- Log.d(LOG_TAG, "handleSettSatelliteDeviceAlignedTimeoutDuration: timeoutMillis="
- + timeoutMillis);
+ Log.d(LOG_TAG, "setDatagramControllerTimeoutDuration: timeoutMillis="
+ + timeoutMillis + ", reset=" + reset + ", timeoutType=" + timeoutType);
try {
- boolean result = mInterface.setSatelliteDeviceAlignedTimeoutDuration(timeoutMillis);
+ boolean result = mInterface.setDatagramControllerTimeoutDuration(
+ reset, timeoutType, timeoutMillis);
if (VDBG) {
- Log.v(LOG_TAG, "setSatelliteDeviceAlignedTimeoutDuration " + timeoutMillis
+ Log.v(LOG_TAG, "setDatagramControllerTimeoutDuration " + timeoutMillis
+ ", result = " + result);
}
getOutPrintWriter().println(result);
} catch (RemoteException e) {
- Log.w(LOG_TAG, "setSatelliteDeviceAlignedTimeoutDuration: " + timeoutMillis
+ Log.w(LOG_TAG, "setDatagramControllerTimeoutDuration: " + timeoutMillis
+ ", error = " + e.getMessage());
errPw.println("Exception: " + e.getMessage());
return -1;
@@ -3281,6 +3416,277 @@
return 0;
}
+ private int handleSetSatelliteControllerTimeoutDuration() {
+ PrintWriter errPw = getErrPrintWriter();
+ boolean reset = false;
+ int timeoutType = 0;
+ long timeoutMillis = 0;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-d": {
+ timeoutMillis = Long.parseLong(getNextArgRequired());
+ break;
+ }
+ case "-r": {
+ reset = true;
+ break;
+ }
+ case "-t": {
+ timeoutType = Integer.parseInt(getNextArgRequired());
+ break;
+ }
+ }
+ }
+ Log.d(LOG_TAG, "setSatelliteControllerTimeoutDuration: timeoutMillis="
+ + timeoutMillis + ", reset=" + reset + ", timeoutType=" + timeoutType);
+
+ try {
+ boolean result = mInterface.setSatelliteControllerTimeoutDuration(
+ reset, timeoutType, timeoutMillis);
+ if (VDBG) {
+ Log.v(LOG_TAG, "setSatelliteControllerTimeoutDuration " + timeoutMillis
+ + ", result = " + result);
+ }
+ getOutPrintWriter().println(result);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "setSatelliteControllerTimeoutDuration: " + timeoutMillis
+ + ", error = " + e.getMessage());
+ errPw.println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
+ private int handleSetShouldSendDatagramToModemInDemoMode() {
+ PrintWriter errPw = getErrPrintWriter();
+ String opt;
+ boolean shouldSendToDemoMode;
+
+ if ((opt = getNextArg()) == null) {
+ errPw.println(
+ "adb shell cmd phone set-should-send-datagram-to-modem-in-demo-mode :"
+ + " Invalid Argument");
+ return -1;
+ } else {
+ switch (opt) {
+ case "true": {
+ shouldSendToDemoMode = true;
+ break;
+ }
+ case "false": {
+ shouldSendToDemoMode = false;
+ break;
+ }
+ default:
+ errPw.println(
+ "adb shell cmd phone set-should-send-datagram-to-modem-in-demo-mode :"
+ + " Invalid Argument");
+ return -1;
+ }
+ }
+
+ Log.d(LOG_TAG,
+ "handleSetShouldSendDatagramToModemInDemoMode(" + shouldSendToDemoMode + ")");
+
+ try {
+ boolean result = mInterface.setShouldSendDatagramToModemInDemoMode(
+ shouldSendToDemoMode);
+ if (VDBG) {
+ Log.v(LOG_TAG, "handleSetShouldSendDatagramToModemInDemoMode returns: "
+ + result);
+ }
+ getOutPrintWriter().println(false);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "setShouldSendDatagramToModemInDemoMode(" + shouldSendToDemoMode
+ + "), error = " + e.getMessage());
+ errPw.println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
+ private int handleSetSatelliteAccessControlOverlayConfigs() {
+ PrintWriter errPw = getErrPrintWriter();
+ boolean reset = false;
+ boolean isAllowed = false;
+ String s2CellFile = null;
+ long locationFreshDurationNanos = 0;
+ List<String> satelliteCountryCodes = null;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-r": {
+ reset = true;
+ break;
+ }
+ case "-a": {
+ isAllowed = true;
+ break;
+ }
+ case "-f": {
+ s2CellFile = getNextArgRequired();
+ break;
+ }
+ case "-d": {
+ locationFreshDurationNanos = Long.parseLong(getNextArgRequired());
+ break;
+ }
+ case "-c": {
+ String countryCodeStr = getNextArgRequired();
+ satelliteCountryCodes = Arrays.asList(countryCodeStr.split(","));
+ break;
+ }
+ }
+ }
+ Log.d(LOG_TAG, "handleSetSatelliteAccessControlOverlayConfigs: reset=" + reset
+ + ", isAllowed=" + isAllowed + ", s2CellFile=" + s2CellFile
+ + ", locationFreshDurationNanos=" + locationFreshDurationNanos
+ + ", satelliteCountryCodes=" + satelliteCountryCodes);
+
+ try {
+ boolean result = mInterface.setSatelliteAccessControlOverlayConfigs(reset, isAllowed,
+ s2CellFile, locationFreshDurationNanos, satelliteCountryCodes);
+ if (VDBG) {
+ Log.v(LOG_TAG, "setSatelliteAccessControlOverlayConfigs result =" + result);
+ }
+ getOutPrintWriter().println(result);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "setSatelliteAccessControlOverlayConfigs: ex=" + e.getMessage());
+ errPw.println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
+ private int handleSetCountryCodes() {
+ PrintWriter errPw = getErrPrintWriter();
+ List<String> currentNetworkCountryCodes = new ArrayList<>();
+ String locationCountryCode = null;
+ long locationCountryCodeTimestampNanos = 0;
+ Map<String, Long> cachedNetworkCountryCodes = new HashMap<>();
+ boolean reset = false;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-r": {
+ reset = true;
+ break;
+ }
+ case "-n": {
+ String countryCodeStr = getNextArgRequired();
+ currentNetworkCountryCodes = Arrays.asList(countryCodeStr.split(","));
+ break;
+ }
+ case "-c": {
+ String cachedNetworkCountryCodeStr = getNextArgRequired();
+ cachedNetworkCountryCodes = parseStringLongMap(cachedNetworkCountryCodeStr);
+ break;
+ }
+ case "-l": {
+ locationCountryCode = getNextArgRequired();
+ break;
+ }
+ case "-t": {
+ locationCountryCodeTimestampNanos = Long.parseLong(getNextArgRequired());
+ break;
+ }
+ }
+ }
+ Log.d(LOG_TAG, "setCountryCodes: locationCountryCode="
+ + locationCountryCode + ", locationCountryCodeTimestampNanos="
+ + locationCountryCodeTimestampNanos + ", currentNetworkCountryCodes="
+ + currentNetworkCountryCodes);
+
+ try {
+ boolean result = mInterface.setCountryCodes(reset, currentNetworkCountryCodes,
+ cachedNetworkCountryCodes, locationCountryCode,
+ locationCountryCodeTimestampNanos);
+ if (VDBG) {
+ Log.v(LOG_TAG, "setCountryCodes result =" + result);
+ }
+ getOutPrintWriter().println(result);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "setCountryCodes: ex=" + e.getMessage());
+ errPw.println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
+ private int handleSetOemEnabledSatelliteProvisionStatus() {
+ PrintWriter errPw = getErrPrintWriter();
+ boolean isProvisioned = false;
+ boolean reset = true;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-p": {
+ try {
+ isProvisioned = Boolean.parseBoolean(getNextArgRequired());
+ reset = false;
+ } catch (Exception e) {
+ errPw.println("setOemEnabledSatelliteProvisionStatus requires a boolean "
+ + "after -p indicating provision status");
+ return -1;
+ }
+ }
+ }
+ }
+ Log.d(LOG_TAG, "setOemEnabledSatelliteProvisionStatus: reset=" + reset
+ + ", isProvisioned=" + isProvisioned);
+
+ try {
+ boolean result = mInterface.setOemEnabledSatelliteProvisionStatus(reset, isProvisioned);
+ if (VDBG) {
+ Log.v(LOG_TAG, "setOemEnabledSatelliteProvisionStatus result = " + result);
+ }
+ getOutPrintWriter().println(result);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "setOemEnabledSatelliteProvisionStatus: error = " + e.getMessage());
+ errPw.println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
+ /**
+ * Sample inputStr = "US,UK,CA;2,1,3"
+ * Sample output: {[US,2], [UK,1], [CA,3]}
+ */
+ @NonNull private Map<String, Long> parseStringLongMap(@Nullable String inputStr) {
+ Map<String, Long> result = new HashMap<>();
+ if (!TextUtils.isEmpty(inputStr)) {
+ String[] stringLongArr = inputStr.split(";");
+ if (stringLongArr.length != 2) {
+ Log.e(LOG_TAG, "parseStringLongMap: invalid inputStr=" + inputStr);
+ return result;
+ }
+
+ String[] stringArr = stringLongArr[0].split(",");
+ String[] longArr = stringLongArr[1].split(",");
+ if (stringArr.length != longArr.length) {
+ Log.e(LOG_TAG, "parseStringLongMap: invalid inputStr=" + inputStr);
+ return result;
+ }
+
+ for (int i = 0; i < stringArr.length; i++) {
+ try {
+ result.put(stringArr[i], Long.parseLong(longArr[i]));
+ } catch (Exception ex) {
+ Log.e(LOG_TAG, "parseStringLongMap: invalid inputStr=" + inputStr
+ + ", ex=" + ex);
+ return result;
+ }
+ }
+ }
+ return result;
+ }
+
private int handleCarrierRestrictionStatusCommand() {
try {
String MOCK_MODEM_SERVICE_NAME = "android.telephony.mockmodem.MockModemService";
@@ -3377,7 +3783,7 @@
// clear-carrier-service-package-override
private int clearCarrierServicePackageOverride() {
PrintWriter errPw = getErrPrintWriter();
- int subId = getDefaultSlot();
+ int subId = SubscriptionManager.getDefaultSubscriptionId();
String opt;
while ((opt = getNextOption()) != null) {
@@ -3414,10 +3820,70 @@
return 0;
}
+ private int handleDomainSelectionCommand() {
+ String arg = getNextArg();
+ if (arg == null) {
+ onHelpDomainSelection();
+ return 0;
+ }
+
+ switch (arg) {
+ case DOMAIN_SELECTION_SET_SERVICE_OVERRIDE: {
+ return handleDomainSelectionSetServiceOverrideCommand();
+ }
+ case DOMAIN_SELECTION_CLEAR_SERVICE_OVERRIDE: {
+ return handleDomainSelectionClearServiceOverrideCommand();
+ }
+ }
+
+ return -1;
+ }
+
+ // domainselection set-dss-override
+ private int handleDomainSelectionSetServiceOverrideCommand() {
+ PrintWriter errPw = getErrPrintWriter();
+
+ String componentName = getNextArg();
+
+ try {
+ boolean result = mInterface.setDomainSelectionServiceOverride(
+ ComponentName.unflattenFromString(componentName));
+ if (VDBG) {
+ Log.v(LOG_TAG, "domainselection set-dss-override "
+ + componentName + ", result=" + result);
+ }
+ getOutPrintWriter().println(result);
+ } catch (Exception e) {
+ Log.w(LOG_TAG, "domainselection set-dss-override "
+ + componentName + ", error=" + e.getMessage());
+ errPw.println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
+ // domainselection clear-dss-override
+ private int handleDomainSelectionClearServiceOverrideCommand() {
+ PrintWriter errPw = getErrPrintWriter();
+
+ try {
+ boolean result = mInterface.clearDomainSelectionServiceOverride();
+ if (VDBG) {
+ Log.v(LOG_TAG, "domainselection clear-dss-override result=" + result);
+ }
+ getOutPrintWriter().println(result);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "domainselection clear-dss-override error=" + e.getMessage());
+ errPw.println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
/**
* Building the string that can be used to build the JsonObject which supports to stub the data
* in CarrierAllowListInfo for CTS testing. sample format is like
- * {"com.android.example":{"carrierId":"10000","callerSHA1Id":["XXXXXXXXXXXXXX"]}}
+ * {"com.android.example":{"carrierIds":[10000],"callerSHA1Id":["XXXXXXXXXXXXXX"]}}
*/
private String convertToJsonString(int index, String param) {
@@ -3429,7 +3895,7 @@
break;
case 1:
jSonString =
- "{" + QUOTES + token[0] + QUOTES + ":" + QUOTES + token[1] + QUOTES + ",";
+ "{" + QUOTES + token[0] + QUOTES + ":" + "[" + token[1] + "],";
break;
case 2:
jSonString =
diff --git a/src/com/android/phone/euicc/EuiccUiDispatcherActivity.java b/src/com/android/phone/euicc/EuiccUiDispatcherActivity.java
index 5da52d6..3e44062 100644
--- a/src/com/android/phone/euicc/EuiccUiDispatcherActivity.java
+++ b/src/com/android/phone/euicc/EuiccUiDispatcherActivity.java
@@ -113,7 +113,7 @@
@Nullable
Intent resolveEuiccUiIntent() {
EuiccManager euiccManager = (EuiccManager) getSystemService(Context.EUICC_SERVICE);
- if (!euiccManager.isEnabled()) {
+ if (euiccManager == null || !euiccManager.isEnabled()) {
Log.w(TAG, "eUICC not enabled");
return null;
}
diff --git a/src/com/android/phone/satellite/accesscontrol/S2RangeSatelliteOnDeviceAccessController.java b/src/com/android/phone/satellite/accesscontrol/S2RangeSatelliteOnDeviceAccessController.java
new file mode 100644
index 0000000..4490460
--- /dev/null
+++ b/src/com/android/phone/satellite/accesscontrol/S2RangeSatelliteOnDeviceAccessController.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellite.accesscontrol;
+
+import android.annotation.NonNull;
+import android.telephony.Rlog;
+
+import com.android.storage.s2.S2LevelRange;
+import com.android.telephony.sats2range.read.SatS2RangeFileReader;
+
+import com.google.common.geometry.S2CellId;
+import com.google.common.geometry.S2LatLng;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * An implementation of {@link SatelliteOnDeviceAccessController} that uses
+ * {@link SatS2RangeFileReader}.
+ */
+final class S2RangeSatelliteOnDeviceAccessController extends SatelliteOnDeviceAccessController {
+ private static final String TAG = "S2RangeSatelliteOnDeviceAccessController";
+ private static final boolean DBG = false;
+
+ @NonNull private final SatS2RangeFileReader mSatS2RangeFileReader;
+
+ private final int mS2Level;
+
+ private S2RangeSatelliteOnDeviceAccessController(
+ @NonNull SatS2RangeFileReader satS2RangeFileReader, int s2Level) {
+ mSatS2RangeFileReader = Objects.requireNonNull(satS2RangeFileReader);
+ mS2Level = s2Level;
+ }
+
+ /**
+ * Returns a new {@link S2RangeSatelliteOnDeviceAccessController} using the specified data file.
+ *
+ * @param file The input file that contains the S2-range-based access restriction information.
+ * @throws IOException in the event of a problem while reading the underlying file.
+ * @throws IllegalArgumentException if either the S2 level defined by
+ * {@code config_oem_enabled_satellite_s2cell_level} or the satellite access allow defined by
+ * {@code config_oem_enabled_satellite_access_allow} does not match the values included in the
+ * header of the input file.
+ */
+ public static S2RangeSatelliteOnDeviceAccessController create(
+ @NonNull File file) throws IOException, IllegalArgumentException {
+ SatS2RangeFileReader reader = SatS2RangeFileReader.open(file);
+ int s2Level = reader.getS2Level();
+ return new S2RangeSatelliteOnDeviceAccessController(reader, s2Level);
+ }
+
+ public static LocationToken createLocationTokenForLatLng(
+ double latDegrees, double lngDegrees, int s2Level) {
+ return new LocationTokenImpl(getS2CellId(latDegrees, lngDegrees, s2Level).id());
+ }
+
+ @Override
+ public boolean isSatCommunicationAllowedAtLocation(LocationToken locationToken)
+ throws IOException {
+ if (!(locationToken instanceof LocationTokenImpl)) {
+ throw new IllegalArgumentException("Unknown locationToken=" + locationToken);
+ }
+ LocationTokenImpl locationTokenImpl = (LocationTokenImpl) locationToken;
+ return isSatCommunicationAllowedAtLocation(locationTokenImpl.getS2CellId());
+ }
+
+ @Override
+ public int getS2Level() {
+ return mS2Level;
+ }
+
+ private boolean isSatCommunicationAllowedAtLocation(long s2CellId) throws IOException {
+ S2LevelRange entry = mSatS2RangeFileReader.findEntryByCellId(s2CellId);
+ if (mSatS2RangeFileReader.isAllowedList()) {
+ // The file contains an allowed list of S2 cells. Thus, satellite is allowed if an
+ // entry is found
+ return (entry != null);
+ } else {
+ // The file contains a disallowed list of S2 cells. Thus, satellite is allowed if an
+ // entry is not found
+ return (entry == null);
+ }
+ }
+
+ private static S2CellId getS2CellId(double latDegrees, double lngDegrees, int s2Level) {
+ // Create the leaf S2 cell containing the given S2LatLng
+ S2CellId cellId = S2CellId.fromLatLng(S2LatLng.fromDegrees(latDegrees, lngDegrees));
+
+ // Return the S2 cell at the expected S2 level
+ return cellId.parent(s2Level);
+ }
+
+ @Override
+ public void close() throws IOException {
+ mSatS2RangeFileReader.close();
+ }
+
+ private static void logd(@NonNull String log) {
+ Rlog.d(TAG, log);
+ }
+
+ private static void loge(@NonNull String log) {
+ Rlog.e(TAG, log);
+ }
+
+ private static class LocationTokenImpl extends LocationToken {
+
+ private final long mS2CellId;
+
+ private LocationTokenImpl(long s2CellId) {
+ this.mS2CellId = s2CellId;
+ }
+
+ long getS2CellId() {
+ return mS2CellId;
+ }
+
+ @Override
+ public String toString() {
+ return DBG ? toPiiString() : "LocationToken{<redacted>}";
+ }
+
+ @Override
+ public String toPiiString() {
+ return "LocationToken{"
+ + "mS2CellId=" + mS2CellId
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof LocationTokenImpl)) {
+ return false;
+ }
+ LocationTokenImpl that = (LocationTokenImpl) o;
+ return mS2CellId == that.mS2CellId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mS2CellId);
+ }
+ }
+}
diff --git a/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java b/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java
new file mode 100644
index 0000000..8489015
--- /dev/null
+++ b/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java
@@ -0,0 +1,991 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellite.accesscontrol;
+
+import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED;
+import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_SUPPORTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
+
+import android.annotation.ArrayRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.location.Location;
+import android.location.LocationManager;
+import android.location.LocationRequest;
+import android.os.AsyncResult;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ResultReceiver;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.provider.DeviceConfig;
+import android.telecom.TelecomManager;
+import android.telephony.AnomalyReporter;
+import android.telephony.Rlog;
+import android.telephony.satellite.SatelliteManager;
+import android.text.TextUtils;
+import android.util.Pair;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyCountryDetector;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.satellite.SatelliteConfig;
+import com.android.internal.telephony.satellite.SatelliteController;
+import com.android.phone.PhoneGlobals;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * This module is responsible for making sure that satellite communication can be used by devices
+ * in only regions allowed by OEMs.
+ */
+public class SatelliteAccessController extends Handler {
+ private static final String TAG = "SatelliteAccessController";
+ /**
+ * UUID to report an anomaly when getting an exception in looking up on-device data for the
+ * current location.
+ */
+ private static final String UUID_ON_DEVICE_LOOKUP_EXCEPTION =
+ "dbea1641-630e-4780-9f25-8337ba6c3563";
+ /**
+ * UUID to report an anomaly when getting an exception in creating the on-device access
+ * controller.
+ */
+ private static final String UUID_CREATE_ON_DEVICE_ACCESS_CONTROLLER_EXCEPTION =
+ "3ac767d8-2867-4d60-97c2-ae9d378a5521";
+ protected static final long WAIT_FOR_CURRENT_LOCATION_TIMEOUT_MILLIS =
+ TimeUnit.SECONDS.toMillis(180);
+ protected static final long KEEP_ON_DEVICE_ACCESS_CONTROLLER_RESOURCES_TIMEOUT_MILLIS =
+ TimeUnit.MINUTES.toMillis(30);
+ protected static final int DEFAULT_S2_LEVEL = 12;
+ private static final int DEFAULT_LOCATION_FRESH_DURATION_SECONDS = 600;
+ private static final boolean DEFAULT_SATELLITE_ACCESS_ALLOW = true;
+ private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
+ private static final String BOOT_ALLOW_MOCK_MODEM_PROPERTY = "ro.boot.radio.allow_mock_modem";
+ private static final boolean DEBUG = !"user".equals(Build.TYPE);
+ private static final int MAX_CACHE_SIZE = 50;
+
+ private static final int CMD_IS_SATELLITE_COMMUNICATION_ALLOWED = 1;
+ protected static final int EVENT_WAIT_FOR_CURRENT_LOCATION_TIMEOUT = 2;
+ protected static final int EVENT_KEEP_ON_DEVICE_ACCESS_CONTROLLER_RESOURCES_TIMEOUT = 3;
+ protected static final int EVENT_CONFIG_DATA_UPDATED = 4;
+
+ private static SatelliteAccessController sInstance;
+
+ /** Feature flags to control behavior and errors. */
+ @NonNull private final FeatureFlags mFeatureFlags;
+ @GuardedBy("mLock")
+ @Nullable protected SatelliteOnDeviceAccessController mSatelliteOnDeviceAccessController;
+ @NonNull private final LocationManager mLocationManager;
+ @NonNull private final TelecomManager mTelecomManager;
+ @NonNull private final TelephonyCountryDetector mCountryDetector;
+ @NonNull private final SatelliteController mSatelliteController;
+ @NonNull private final ResultReceiver mInternalSatelliteSupportedResultReceiver;
+ @NonNull protected final Object mLock = new Object();
+ @GuardedBy("mLock")
+ @NonNull
+ private final Set<ResultReceiver> mSatelliteAllowResultReceivers = new HashSet<>();
+ @NonNull private List<String> mSatelliteCountryCodes;
+ private boolean mIsSatelliteAllowAccessControl;
+ @Nullable private File mSatelliteS2CellFile;
+ private long mLocationFreshDurationNanos;
+ @GuardedBy("mLock")
+ private boolean mIsOverlayConfigOverridden = false;
+ @NonNull private List<String> mOverriddenSatelliteCountryCodes;
+ private boolean mOverriddenIsSatelliteAllowAccessControl;
+ @Nullable private File mOverriddenSatelliteS2CellFile;
+ private long mOverriddenLocationFreshDurationNanos;
+ @GuardedBy("mLock")
+ @NonNull
+ private final Map<SatelliteOnDeviceAccessController.LocationToken, Boolean>
+ mCachedAccessRestrictionMap = new LinkedHashMap<>() {
+ @Override
+ protected boolean removeEldestEntry(
+ Entry<SatelliteOnDeviceAccessController.LocationToken, Boolean> eldest) {
+ return size() > MAX_CACHE_SIZE;
+ }
+ };
+ @GuardedBy("mLock")
+ @Nullable
+ CancellationSignal mLocationRequestCancellationSignal = null;
+ private int mS2Level = DEFAULT_S2_LEVEL;
+ @GuardedBy("mLock")
+ @Nullable private Location mFreshLastKnownLocation = null;
+
+ /** These are used for CTS test */
+ private Path mCtsSatS2FilePath = null;
+ private static final String GOOGLE_US_SAN_SAT_S2_FILE_NAME = "google_us_san_sat_s2.dat";
+
+ /**
+ * Create a SatelliteAccessController instance.
+ *
+ * @param context The context associated with the {@link SatelliteAccessController} instance.
+ * @param featureFlags The FeatureFlags that are supported.
+ * @param locationManager The LocationManager for querying current location of the device.
+ * @param looper The Looper to run the SatelliteAccessController on.
+ * @param satelliteOnDeviceAccessController The on-device satellite access controller instance.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ protected SatelliteAccessController(@NonNull Context context,
+ @NonNull FeatureFlags featureFlags, @NonNull Looper looper,
+ @NonNull LocationManager locationManager, @NonNull TelecomManager telecomManager,
+ @Nullable SatelliteOnDeviceAccessController satelliteOnDeviceAccessController,
+ @Nullable File s2CellFile) {
+ super(looper);
+ mFeatureFlags = featureFlags;
+ mLocationManager = locationManager;
+ mTelecomManager = telecomManager;
+ mSatelliteOnDeviceAccessController = satelliteOnDeviceAccessController;
+ mCountryDetector = TelephonyCountryDetector.getInstance(context);
+ mSatelliteController = SatelliteController.getInstance();
+ loadOverlayConfigs(context);
+ mSatelliteController.registerForConfigUpdateChanged(this, EVENT_CONFIG_DATA_UPDATED,
+ context);
+ if (s2CellFile != null) {
+ mSatelliteS2CellFile = s2CellFile;
+ }
+ mInternalSatelliteSupportedResultReceiver = new ResultReceiver(this) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ handleIsSatelliteSupportedResult(resultCode, resultData);
+ }
+ };
+ // Init the SatelliteOnDeviceAccessController so that the S2 level can be cached
+ initSatelliteOnDeviceAccessController();
+ }
+
+ /** @return the singleton instance of {@link SatelliteAccessController} */
+ public static synchronized SatelliteAccessController getOrCreateInstance(
+ @NonNull Context context, @NonNull FeatureFlags featureFlags) {
+ if (sInstance == null) {
+ HandlerThread handlerThread = new HandlerThread("SatelliteAccessController");
+ handlerThread.start();
+ sInstance = new SatelliteAccessController(context, featureFlags,
+ handlerThread.getLooper(), context.getSystemService(LocationManager.class),
+ context.getSystemService(TelecomManager.class), null, null);
+ }
+ return sInstance;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_IS_SATELLITE_COMMUNICATION_ALLOWED:
+ handleCmdIsSatelliteAllowedForCurrentLocation(
+ (Pair<Integer, ResultReceiver>) msg.obj);
+ break;
+ case EVENT_WAIT_FOR_CURRENT_LOCATION_TIMEOUT:
+ handleWaitForCurrentLocationTimedOutEvent();
+ break;
+ case EVENT_KEEP_ON_DEVICE_ACCESS_CONTROLLER_RESOURCES_TIMEOUT:
+ cleanupOnDeviceAccessControllerResources();
+ break;
+ case EVENT_CONFIG_DATA_UPDATED:
+ AsyncResult ar = (AsyncResult) msg.obj;
+ updateSatelliteConfigData((Context) ar.userObj);
+ break;
+ default:
+ logw("SatelliteAccessControllerHandler: unexpected message code: " + msg.what);
+ break;
+ }
+ }
+
+ /**
+ * Request to get whether satellite communication is allowed for the current location.
+ *
+ * @param subId The subId of the subscription to check whether satellite communication is
+ * allowed for the current location for.
+ * @param result The result receiver that returns whether satellite communication is allowed
+ * for the current location if the request is successful or an error code
+ * if the request failed.
+ */
+ public void requestIsCommunicationAllowedForCurrentLocation(int subId,
+ @NonNull ResultReceiver result) {
+ if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+ logd("oemEnabledSatelliteFlag is disabled");
+ result.send(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, null);
+ return;
+ }
+ sendRequestAsync(CMD_IS_SATELLITE_COMMUNICATION_ALLOWED, new Pair<>(subId, result));
+ }
+
+ /**
+ * This API should be used by only CTS tests to override the overlay configs of satellite
+ * access controller.
+ */
+ public boolean setSatelliteAccessControlOverlayConfigs(boolean reset, boolean isAllowed,
+ @Nullable String s2CellFile, long locationFreshDurationNanos,
+ @Nullable List<String> satelliteCountryCodes) {
+ if (!isMockModemAllowed()) {
+ logd("setSatelliteAccessControllerOverlayConfigs: mock modem is not allowed");
+ return false;
+ }
+ logd("setSatelliteAccessControlOverlayConfigs: reset=" + reset
+ + ", isAllowed" + isAllowed + ", s2CellFile=" + s2CellFile
+ + ", locationFreshDurationNanos=" + locationFreshDurationNanos
+ + ", satelliteCountryCodes=" + ((satelliteCountryCodes != null)
+ ? String.join(", ", satelliteCountryCodes) : null));
+ synchronized (mLock) {
+ if (reset) {
+ mIsOverlayConfigOverridden = false;
+ cleanUpCtsResources();
+ } else {
+ mIsOverlayConfigOverridden = true;
+ mOverriddenIsSatelliteAllowAccessControl = isAllowed;
+ if (!TextUtils.isEmpty(s2CellFile)) {
+ mOverriddenSatelliteS2CellFile = getTestSatelliteS2File(s2CellFile);
+ if (!mOverriddenSatelliteS2CellFile.exists()) {
+ logd("The overriding file "
+ + mOverriddenSatelliteS2CellFile.getAbsolutePath()
+ + " does not exist");
+ mOverriddenSatelliteS2CellFile = null;
+ }
+ } else {
+ mOverriddenSatelliteS2CellFile = null;
+ }
+ mOverriddenLocationFreshDurationNanos = locationFreshDurationNanos;
+ if (satelliteCountryCodes != null) {
+ mOverriddenSatelliteCountryCodes = satelliteCountryCodes;
+ } else {
+ mOverriddenSatelliteCountryCodes = new ArrayList<>();
+ }
+ }
+ cleanupOnDeviceAccessControllerResources();
+ initSatelliteOnDeviceAccessController();
+ }
+ return true;
+ }
+
+ private File getTestSatelliteS2File(String fileName) {
+ logd("getTestSatelliteS2File: fileName=" + fileName);
+ if (TextUtils.equals(fileName, GOOGLE_US_SAN_SAT_S2_FILE_NAME)) {
+ mCtsSatS2FilePath = copyTestSatS2FileToPhoneDirectory(GOOGLE_US_SAN_SAT_S2_FILE_NAME);
+ if (mCtsSatS2FilePath != null) {
+ return mCtsSatS2FilePath.toFile();
+ } else {
+ loge("getTestSatelliteS2File: mCtsSatS2FilePath is null");
+ }
+ }
+ return new File(fileName);
+ }
+
+ @Nullable private static Path copyTestSatS2FileToPhoneDirectory(String sourceFileName) {
+ PhoneGlobals phoneGlobals = PhoneGlobals.getInstance();
+ File ctsFile = phoneGlobals.getDir("cts", Context.MODE_PRIVATE);
+ if (!ctsFile.exists()) {
+ ctsFile.mkdirs();
+ }
+
+ Path targetDir = ctsFile.toPath();
+ Path targetSatS2FilePath = targetDir.resolve(sourceFileName);
+ try {
+ InputStream inputStream = phoneGlobals.getAssets().open(sourceFileName);
+ if (inputStream == null) {
+ loge("copyTestSatS2FileToPhoneDirectory: Resource=" + sourceFileName
+ + " not found");
+ } else {
+ Files.copy(inputStream, targetSatS2FilePath, StandardCopyOption.REPLACE_EXISTING);
+ }
+ } catch (IOException ex) {
+ loge("copyTestSatS2FileToPhoneDirectory: ex=" + ex);
+ }
+ return targetSatS2FilePath;
+ }
+
+ private void cleanUpCtsResources() {
+ if (mCtsSatS2FilePath != null) {
+ try {
+ Files.delete(mCtsSatS2FilePath);
+ } catch (IOException ex) {
+ loge("cleanUpCtsResources: ex=" + ex);
+ }
+ }
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ protected long getElapsedRealtimeNanos() {
+ return SystemClock.elapsedRealtimeNanos();
+ }
+
+ /**
+ * Update country codes, S2CellFile and satellite region allowed by ConfigUpdater
+ * or CarrierConfig
+ */
+ private void updateSatelliteConfigData(Context context) {
+ logd("updateSatelliteConfigData");
+
+ SatelliteConfig satelliteConfig = mSatelliteController.getSatelliteConfig();
+ if (satelliteConfig != null && satelliteConfig.getSatelliteS2CellFile(context) != null) {
+ logd("Check mSatelliteS2CellFile from ConfigUpdater");
+ Path pathSatelliteS2CellFile = satelliteConfig.getSatelliteS2CellFile(context);
+ mSatelliteS2CellFile = pathSatelliteS2CellFile.toFile();
+ if (mSatelliteS2CellFile != null && !mSatelliteS2CellFile.exists()) {
+ loge("The satellite S2 cell file " + mSatelliteS2CellFile.getAbsolutePath()
+ + " does not exist");
+ mSatelliteS2CellFile = null;
+ } else {
+ logd("S2 cell file from ConfigUpdater:" + mSatelliteS2CellFile.getAbsolutePath());
+ }
+ }
+
+ if (mSatelliteS2CellFile == null) {
+ logd("Check mSatelliteS2CellFile from device overlay config");
+ String satelliteS2CellFileName = getSatelliteS2CellFileFromOverlayConfig(context);
+ mSatelliteS2CellFile = TextUtils.isEmpty(satelliteS2CellFileName)
+ ? null : new File(satelliteS2CellFileName);
+ if (mSatelliteS2CellFile != null && !mSatelliteS2CellFile.exists()) {
+ loge("The satellite S2 cell file " + mSatelliteS2CellFile.getAbsolutePath()
+ + " does not exist");
+ mSatelliteS2CellFile = null;
+ }
+ }
+
+ if (mSatelliteS2CellFile == null) {
+ logd("Since mSatelliteS2CellFile is null, don't need to refer other configurations");
+ return;
+ }
+
+ if (satelliteConfig != null
+ && !satelliteConfig.getDeviceSatelliteCountryCodes().isEmpty()) {
+ mSatelliteCountryCodes = satelliteConfig.getDeviceSatelliteCountryCodes();
+ logd("update mSatelliteCountryCodes by ConfigUpdater: "
+ + String.join(",", mSatelliteCountryCodes));
+ } else {
+ mSatelliteCountryCodes = getSatelliteCountryCodesFromOverlayConfig(context);
+ }
+
+ if (satelliteConfig != null && satelliteConfig.isSatelliteDataForAllowedRegion() != null) {
+ mIsSatelliteAllowAccessControl = satelliteConfig.isSatelliteDataForAllowedRegion();
+ logd("update mIsSatelliteAllowAccessControl by ConfigUpdater: "
+ + mIsSatelliteAllowAccessControl);
+ } else {
+ mIsSatelliteAllowAccessControl = getSatelliteAccessAllowFromOverlayConfig(context);
+ }
+
+ // Clean up resources so that the new config data will be used when receiving new requests
+ cleanupOnDeviceAccessControllerResources();
+ }
+
+ private void loadOverlayConfigs(@NonNull Context context) {
+ mSatelliteCountryCodes = getSatelliteCountryCodesFromOverlayConfig(context);
+ mIsSatelliteAllowAccessControl = getSatelliteAccessAllowFromOverlayConfig(context);
+ String satelliteS2CellFileName = getSatelliteS2CellFileFromOverlayConfig(context);
+ mSatelliteS2CellFile = TextUtils.isEmpty(satelliteS2CellFileName)
+ ? null : new File(satelliteS2CellFileName);
+ if (mSatelliteS2CellFile != null && !mSatelliteS2CellFile.exists()) {
+ loge("The satellite S2 cell file " + satelliteS2CellFileName + " does not exist");
+ mSatelliteS2CellFile = null;
+ }
+ mLocationFreshDurationNanos = getSatelliteLocationFreshDurationFromOverlayConfig(context);
+ }
+
+ private long getLocationFreshDurationNanos() {
+ synchronized (mLock) {
+ if (mIsOverlayConfigOverridden) {
+ return mOverriddenLocationFreshDurationNanos;
+ }
+ return mLocationFreshDurationNanos;
+ }
+ }
+
+ @NonNull private List<String> getSatelliteCountryCodes() {
+ synchronized (mLock) {
+ if (mIsOverlayConfigOverridden) {
+ return mOverriddenSatelliteCountryCodes;
+ }
+ return mSatelliteCountryCodes;
+ }
+ }
+
+ @Nullable private File getSatelliteS2CellFile() {
+ synchronized (mLock) {
+ if (mIsOverlayConfigOverridden) {
+ return mOverriddenSatelliteS2CellFile;
+ }
+ return mSatelliteS2CellFile;
+ }
+ }
+
+ private boolean isSatelliteAllowAccessControl() {
+ synchronized (mLock) {
+ if (mIsOverlayConfigOverridden) {
+ return mOverriddenIsSatelliteAllowAccessControl;
+ }
+ return mIsSatelliteAllowAccessControl;
+ }
+ }
+
+ private void handleCmdIsSatelliteAllowedForCurrentLocation(
+ @NonNull Pair<Integer, ResultReceiver> requestArguments) {
+ synchronized (mLock) {
+ mSatelliteAllowResultReceivers.add(requestArguments.second);
+ if (mSatelliteAllowResultReceivers.size() > 1) {
+ logd("requestIsCommunicationAllowedForCurrentLocation is already being "
+ + "processed");
+ return;
+ }
+ mSatelliteController.requestIsSatelliteSupported(
+ requestArguments.first, mInternalSatelliteSupportedResultReceiver);
+ }
+ }
+
+ private void handleWaitForCurrentLocationTimedOutEvent() {
+ logd("Timed out to wait for current location");
+ synchronized (mLock) {
+ if (mLocationRequestCancellationSignal != null) {
+ mLocationRequestCancellationSignal.cancel();
+ mLocationRequestCancellationSignal = null;
+ onCurrentLocationAvailable(null);
+ } else {
+ loge("handleWaitForCurrentLocationTimedOutEvent: "
+ + "mLocationRequestCancellationSignal is null");
+ }
+ }
+ }
+
+ private void handleIsSatelliteSupportedResult(int resultCode, Bundle resultData) {
+ logd("handleIsSatelliteSupportedResult: resultCode=" + resultCode);
+ synchronized (mLock) {
+ if (resultCode == SATELLITE_RESULT_SUCCESS) {
+ if (resultData.containsKey(KEY_SATELLITE_SUPPORTED)) {
+ boolean isSatelliteSupported = resultData.getBoolean(KEY_SATELLITE_SUPPORTED);
+ if (!isSatelliteSupported) {
+ logd("Satellite is not supported");
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED,
+ false);
+ sendSatelliteAllowResultToReceivers(resultCode, bundle);
+ } else {
+ checkSatelliteAccessRestrictionForCurrentLocation();
+ }
+ } else {
+ loge("KEY_SATELLITE_SUPPORTED does not exist.");
+ sendSatelliteAllowResultToReceivers(resultCode, resultData);
+ }
+ } else {
+ sendSatelliteAllowResultToReceivers(resultCode, resultData);
+ }
+ }
+ }
+
+ private void sendSatelliteAllowResultToReceivers(int resultCode, Bundle resultData) {
+ synchronized (mLock) {
+ for (ResultReceiver resultReceiver : mSatelliteAllowResultReceivers) {
+ resultReceiver.send(resultCode, resultData);
+ }
+ mSatelliteAllowResultReceivers.clear();
+ }
+ }
+
+ /**
+ * Telephony-internal logic to verify if satellite access is restricted at the current location.
+ */
+ private void checkSatelliteAccessRestrictionForCurrentLocation() {
+ synchronized (mLock) {
+ List<String> networkCountryIsoList = mCountryDetector.getCurrentNetworkCountryIso();
+ if (!networkCountryIsoList.isEmpty()) {
+ logd("Use current network country codes=" + String.join(", ",
+ networkCountryIsoList));
+
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(KEY_SATELLITE_COMMUNICATION_ALLOWED,
+ isSatelliteAccessAllowedForLocation(networkCountryIsoList));
+ sendSatelliteAllowResultToReceivers(SATELLITE_RESULT_SUCCESS, bundle);
+ } else {
+ if (shouldUseOnDeviceAccessController()) {
+ // This will be an asynchronous check when it needs to wait for the current
+ // location from location service
+ checkSatelliteAccessRestrictionUsingOnDeviceData();
+ } else {
+ // This is always a synchronous check
+ checkSatelliteAccessRestrictionUsingCachedCountryCodes();
+ }
+ }
+ }
+ }
+
+ /**
+ * This function synchronously checks if satellite is allowed at current location using cached
+ * country codes.
+ */
+ private void checkSatelliteAccessRestrictionUsingCachedCountryCodes() {
+ Pair<String, Long> locationCountryCodeInfo =
+ mCountryDetector.getCachedLocationCountryIsoInfo();
+ Map<String, Long> networkCountryCodeInfoMap =
+ mCountryDetector.getCachedNetworkCountryIsoInfo();
+ List<String> countryCodeList;
+
+ // Check if the cached location country code's timestamp is newer than all cached network
+ // country codes
+ if (!TextUtils.isEmpty(locationCountryCodeInfo.first) && isGreaterThanAll(
+ locationCountryCodeInfo.second, networkCountryCodeInfoMap.values())) {
+ // Use cached location country code
+ countryCodeList = Arrays.asList(locationCountryCodeInfo.first);
+ } else {
+ // Use cached network country codes
+ countryCodeList = networkCountryCodeInfoMap.keySet().stream().toList();
+ }
+ logd("Use cached country codes=" + String.join(", ", countryCodeList));
+
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(KEY_SATELLITE_COMMUNICATION_ALLOWED,
+ isSatelliteAccessAllowedForLocation(countryCodeList));
+ sendSatelliteAllowResultToReceivers(SATELLITE_RESULT_SUCCESS, bundle);
+ }
+
+ /**
+ * This function asynchronously checks if satellite is allowed at the current location using
+ * on-device data. Asynchronous check happens when it needs to wait for the current location
+ * from location service.
+ */
+ private void checkSatelliteAccessRestrictionUsingOnDeviceData() {
+ synchronized (mLock) {
+ logd("Use on-device data");
+ if (mFreshLastKnownLocation != null) {
+ checkSatelliteAccessRestrictionForLocation(mFreshLastKnownLocation);
+ mFreshLastKnownLocation = null;
+ } else {
+ Location freshLastKnownLocation = getFreshLastKnownLocation();
+ if (freshLastKnownLocation != null) {
+ checkSatelliteAccessRestrictionForLocation(freshLastKnownLocation);
+ } else {
+ queryCurrentLocation();
+ }
+ }
+ }
+ }
+
+ private void queryCurrentLocation() {
+ synchronized (mLock) {
+ if (mLocationRequestCancellationSignal != null) {
+ logd("Request for current location was already sent to LocationManager");
+ return;
+ }
+ mLocationRequestCancellationSignal = new CancellationSignal();
+ mLocationManager.getCurrentLocation(LocationManager.GPS_PROVIDER,
+ new LocationRequest.Builder(0)
+ .setQuality(LocationRequest.QUALITY_HIGH_ACCURACY)
+ .setLocationSettingsIgnored(true)
+ .build(),
+ mLocationRequestCancellationSignal, this::post,
+ this::onCurrentLocationAvailable);
+ startWaitForCurrentLocationTimer();
+ }
+ }
+
+ private void onCurrentLocationAvailable(@Nullable Location location) {
+ logd("onCurrentLocationAvailable " + (location != null));
+ synchronized (mLock) {
+ stopWaitForCurrentLocationTimer();
+ mLocationRequestCancellationSignal = null;
+ if (location != null) {
+ checkSatelliteAccessRestrictionForLocation(location);
+ } else {
+ checkSatelliteAccessRestrictionUsingCachedCountryCodes();
+ }
+ }
+ }
+
+ private void checkSatelliteAccessRestrictionForLocation(@NonNull Location location) {
+ synchronized (mLock) {
+ try {
+ SatelliteOnDeviceAccessController.LocationToken locationToken =
+ SatelliteOnDeviceAccessController.createLocationTokenForLatLng(
+ location.getLatitude(),
+ location.getLongitude(), mS2Level);
+ boolean satelliteAllowed;
+ if (mCachedAccessRestrictionMap.containsKey(locationToken)) {
+ satelliteAllowed = mCachedAccessRestrictionMap.get(locationToken);
+ } else {
+ if (!initSatelliteOnDeviceAccessController()) {
+ loge("Failed to init SatelliteOnDeviceAccessController");
+ checkSatelliteAccessRestrictionUsingCachedCountryCodes();
+ return;
+ }
+ satelliteAllowed = mSatelliteOnDeviceAccessController
+ .isSatCommunicationAllowedAtLocation(locationToken);
+ updateCachedAccessRestrictionMap(locationToken, satelliteAllowed);
+ }
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(KEY_SATELLITE_COMMUNICATION_ALLOWED, satelliteAllowed);
+ sendSatelliteAllowResultToReceivers(SATELLITE_RESULT_SUCCESS, bundle);
+ } catch (Exception ex) {
+ loge("checkSatelliteAccessRestrictionForLocation: ex=" + ex);
+ reportAnomaly(UUID_ON_DEVICE_LOOKUP_EXCEPTION,
+ "On-device satellite lookup exception");
+ checkSatelliteAccessRestrictionUsingCachedCountryCodes();
+ }
+ }
+ }
+
+ private void updateCachedAccessRestrictionMap(@NonNull
+ SatelliteOnDeviceAccessController.LocationToken locationToken,
+ boolean satelliteAllowed) {
+ synchronized (mLock) {
+ mCachedAccessRestrictionMap.put(locationToken, satelliteAllowed);
+ }
+ }
+
+ private boolean isGreaterThanAll(
+ long comparedItem, @NonNull Collection<Long> itemCollection) {
+ for (long item : itemCollection) {
+ if (comparedItem <= item) return false;
+ }
+ return true;
+ }
+
+ private boolean isSatelliteAccessAllowedForLocation(
+ @NonNull List<String> networkCountryIsoList) {
+ if (isSatelliteAllowAccessControl()) {
+ // The current country is unidentified, we're uncertain and thus returning false
+ if (networkCountryIsoList.isEmpty()) {
+ return false;
+ }
+
+ // In case of allowed list, satellite is allowed if all country codes are be in the
+ // allowed list
+ return getSatelliteCountryCodes().containsAll(networkCountryIsoList);
+ } else {
+ // No country is barred, thus returning true
+ if (getSatelliteCountryCodes().isEmpty()) {
+ return true;
+ }
+
+ // The current country is unidentified, we're uncertain and thus returning false
+ if (networkCountryIsoList.isEmpty()) {
+ return false;
+ }
+
+ // In case of disallowed list, if any country code is in the list, satellite will be
+ // disallowed
+ for (String countryCode : networkCountryIsoList) {
+ if (getSatelliteCountryCodes().contains(countryCode)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ private boolean shouldUseOnDeviceAccessController() {
+ if (getSatelliteS2CellFile() == null) {
+ return false;
+ }
+
+ if (isInEmergency() || mLocationManager.isLocationEnabled()) {
+ return true;
+ }
+
+ Location freshLastKnownLocation = getFreshLastKnownLocation();
+ if (freshLastKnownLocation != null) {
+ synchronized (mLock) {
+ mFreshLastKnownLocation = freshLastKnownLocation;
+ }
+ return true;
+ } else {
+ synchronized (mLock) {
+ mFreshLastKnownLocation = null;
+ }
+ }
+ return false;
+ }
+
+ @Nullable private Location getFreshLastKnownLocation() {
+ Location lastKnownLocation = getLastKnownLocation();
+ if (lastKnownLocation != null) {
+ long lastKnownLocationAge =
+ getElapsedRealtimeNanos() - lastKnownLocation.getElapsedRealtimeNanos();
+ if (lastKnownLocationAge <= getLocationFreshDurationNanos()) {
+ return lastKnownLocation;
+ }
+ }
+ return null;
+ }
+
+ private boolean isInEmergency() {
+ // Check if emergency call is ongoing
+ if (mTelecomManager.isInEmergencyCall()) {
+ return true;
+ }
+ // Check if the device is in emergency callback mode
+ for (Phone phone : PhoneFactory.getPhones()) {
+ if (phone.isInEcm()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Nullable
+ private Location getLastKnownLocation() {
+ Location result = null;
+ for (String provider : mLocationManager.getProviders(true)) {
+ Location location = mLocationManager.getLastKnownLocation(provider);
+ if (location != null && (result == null
+ || result.getElapsedRealtimeNanos() < location.getElapsedRealtimeNanos())) {
+ result = location;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @return {@code true} if successfully initialize the {@link SatelliteOnDeviceAccessController}
+ * instance, {@code false} otherwise.
+ * @throws IllegalStateException in case of getting any exception in creating the
+ * {@link SatelliteOnDeviceAccessController} instance and the device is using a user build.
+ */
+ private boolean initSatelliteOnDeviceAccessController() throws IllegalStateException {
+ synchronized (mLock) {
+ if (getSatelliteS2CellFile() == null) return false;
+
+ // mSatelliteOnDeviceAccessController was already initialized successfully
+ if (mSatelliteOnDeviceAccessController != null) {
+ restartKeepOnDeviceAccessControllerResourcesTimer();
+ return true;
+ }
+
+ try {
+ mSatelliteOnDeviceAccessController =
+ SatelliteOnDeviceAccessController.create(getSatelliteS2CellFile());
+ restartKeepOnDeviceAccessControllerResourcesTimer();
+ mS2Level = mSatelliteOnDeviceAccessController.getS2Level();
+ logd("mS2Level=" + mS2Level);
+ } catch (Exception ex) {
+ loge("Got exception in creating an instance of SatelliteOnDeviceAccessController,"
+ + " ex=" + ex + ", sat s2 file="
+ + getSatelliteS2CellFile().getAbsolutePath());
+ reportAnomaly(UUID_CREATE_ON_DEVICE_ACCESS_CONTROLLER_EXCEPTION,
+ "Exception in creating on-device satellite access controller");
+ mSatelliteOnDeviceAccessController = null;
+ if (!mIsOverlayConfigOverridden) {
+ mSatelliteS2CellFile = null;
+ }
+ return false;
+ }
+ return true;
+ }
+ }
+
+ private void cleanupOnDeviceAccessControllerResources() {
+ synchronized (mLock) {
+ logd("cleanupOnDeviceAccessControllerResources="
+ + (mSatelliteOnDeviceAccessController != null));
+ if (mSatelliteOnDeviceAccessController != null) {
+ try {
+ mSatelliteOnDeviceAccessController.close();
+ } catch (Exception ex) {
+ loge("cleanupOnDeviceAccessControllerResources: ex=" + ex);
+ }
+ mSatelliteOnDeviceAccessController = null;
+ stopKeepOnDeviceAccessControllerResourcesTimer();
+ }
+ }
+ }
+
+ private static boolean getSatelliteAccessAllowFromOverlayConfig(@NonNull Context context) {
+ Boolean accessAllowed = null;
+ try {
+ accessAllowed = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_oem_enabled_satellite_access_allow);
+ } catch (Resources.NotFoundException ex) {
+ loge("getSatelliteAccessAllowFromOverlayConfig: got ex=" + ex);
+ }
+ if (accessAllowed == null && isMockModemAllowed()) {
+ logd("getSatelliteAccessAllowFromOverlayConfig: Read "
+ + "config_oem_enabled_satellite_access_allow from device config");
+ accessAllowed = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TELEPHONY,
+ "config_oem_enabled_satellite_access_allow", DEFAULT_SATELLITE_ACCESS_ALLOW);
+ }
+ if (accessAllowed == null) {
+ logd("Use default satellite access allow=true control");
+ accessAllowed = true;
+ }
+ return accessAllowed;
+ }
+
+ @Nullable
+ private static String getSatelliteS2CellFileFromOverlayConfig(@NonNull Context context) {
+ String s2CellFile = null;
+ try {
+ s2CellFile = context.getResources().getString(
+ com.android.internal.R.string.config_oem_enabled_satellite_s2cell_file);
+ } catch (Resources.NotFoundException ex) {
+ loge("getSatelliteS2CellFileFromOverlayConfig: got ex=" + ex);
+ }
+ if (TextUtils.isEmpty(s2CellFile) && isMockModemAllowed()) {
+ logd("getSatelliteS2CellFileFromOverlayConfig: Read "
+ + "config_oem_enabled_satellite_s2cell_file from device config");
+ s2CellFile = DeviceConfig.getString(DeviceConfig.NAMESPACE_TELEPHONY,
+ "config_oem_enabled_satellite_s2cell_file", null);
+ }
+ logd("s2CellFile=" + s2CellFile);
+ return s2CellFile;
+ }
+
+ @NonNull
+ private static List<String> getSatelliteCountryCodesFromOverlayConfig(
+ @NonNull Context context) {
+ String[] countryCodes = readStringArrayFromOverlayConfig(context,
+ com.android.internal.R.array.config_oem_enabled_satellite_country_codes);
+ if (countryCodes.length == 0 && isMockModemAllowed()) {
+ logd("getSatelliteCountryCodesFromOverlayConfig: Read "
+ + "config_oem_enabled_satellite_country_codes from device config");
+ String countryCodesStr = DeviceConfig.getString(DeviceConfig.NAMESPACE_TELEPHONY,
+ "config_oem_enabled_satellite_country_codes", "");
+ countryCodes = countryCodesStr.split(",");
+ }
+ return Arrays.stream(countryCodes)
+ .map(x -> x.toUpperCase(Locale.US))
+ .collect(Collectors.toList());
+ }
+
+ @NonNull
+ private static String[] readStringArrayFromOverlayConfig(
+ @NonNull Context context, @ArrayRes int id) {
+ String[] strArray = null;
+ try {
+ strArray = context.getResources().getStringArray(id);
+ } catch (Resources.NotFoundException ex) {
+ loge("readStringArrayFromOverlayConfig: id= " + id + ", ex=" + ex);
+ }
+ if (strArray == null) {
+ strArray = new String[0];
+ }
+ return strArray;
+ }
+
+ private static long getSatelliteLocationFreshDurationFromOverlayConfig(
+ @NonNull Context context) {
+ Integer freshDuration = null;
+ try {
+ freshDuration = context.getResources().getInteger(com.android.internal.R.integer
+ .config_oem_enabled_satellite_location_fresh_duration);
+ } catch (Resources.NotFoundException ex) {
+ loge("getSatelliteLocationFreshDurationFromOverlayConfig: got ex=" + ex);
+ }
+ if (freshDuration == null && isMockModemAllowed()) {
+ logd("getSatelliteLocationFreshDurationFromOverlayConfig: Read "
+ + "config_oem_enabled_satellite_location_fresh_duration from device config");
+ freshDuration = DeviceConfig.getInt(DeviceConfig.NAMESPACE_TELEPHONY,
+ "config_oem_enabled_satellite_location_fresh_duration",
+ DEFAULT_LOCATION_FRESH_DURATION_SECONDS);
+ }
+ if (freshDuration == null) {
+ logd("Use default satellite location fresh duration="
+ + DEFAULT_LOCATION_FRESH_DURATION_SECONDS);
+ freshDuration = DEFAULT_LOCATION_FRESH_DURATION_SECONDS;
+ }
+ return TimeUnit.SECONDS.toNanos(freshDuration);
+ }
+
+ private void startWaitForCurrentLocationTimer() {
+ synchronized (mLock) {
+ if (hasMessages(EVENT_WAIT_FOR_CURRENT_LOCATION_TIMEOUT)) {
+ logw("WaitForCurrentLocationTimer is already started");
+ removeMessages(EVENT_WAIT_FOR_CURRENT_LOCATION_TIMEOUT);
+ }
+ sendEmptyMessageDelayed(EVENT_WAIT_FOR_CURRENT_LOCATION_TIMEOUT,
+ WAIT_FOR_CURRENT_LOCATION_TIMEOUT_MILLIS);
+ }
+ }
+
+ private void stopWaitForCurrentLocationTimer() {
+ synchronized (mLock) {
+ removeMessages(EVENT_WAIT_FOR_CURRENT_LOCATION_TIMEOUT);
+ }
+ }
+
+ private void restartKeepOnDeviceAccessControllerResourcesTimer() {
+ synchronized (mLock) {
+ if (hasMessages(EVENT_KEEP_ON_DEVICE_ACCESS_CONTROLLER_RESOURCES_TIMEOUT)) {
+ logd("KeepOnDeviceAccessControllerResourcesTimer is already started. "
+ + "Restarting it...");
+ removeMessages(EVENT_KEEP_ON_DEVICE_ACCESS_CONTROLLER_RESOURCES_TIMEOUT);
+ }
+ sendEmptyMessageDelayed(EVENT_KEEP_ON_DEVICE_ACCESS_CONTROLLER_RESOURCES_TIMEOUT,
+ KEEP_ON_DEVICE_ACCESS_CONTROLLER_RESOURCES_TIMEOUT_MILLIS);
+ }
+ }
+
+ private void stopKeepOnDeviceAccessControllerResourcesTimer() {
+ synchronized (mLock) {
+ removeMessages(EVENT_KEEP_ON_DEVICE_ACCESS_CONTROLLER_RESOURCES_TIMEOUT);
+ }
+ }
+
+ private void reportAnomaly(@NonNull String uuid, @NonNull String log) {
+ loge(log);
+ AnomalyReporter.reportAnomaly(UUID.fromString(uuid), log);
+ }
+
+ private static boolean isMockModemAllowed() {
+ return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false)
+ || SystemProperties.getBoolean(BOOT_ALLOW_MOCK_MODEM_PROPERTY, false));
+ }
+
+ /**
+ * Posts the specified command to be executed on the main thread and returns immediately.
+ *
+ * @param command command to be executed on the main thread
+ * @param argument additional parameters required to perform of the operation
+ */
+ private void sendRequestAsync(int command, @NonNull Object argument) {
+ Message msg = this.obtainMessage(command, argument);
+ msg.sendToTarget();
+ }
+
+ private static void logd(@NonNull String log) {
+ Rlog.d(TAG, log);
+ }
+
+ private static void logw(@NonNull String log) {
+ Rlog.w(TAG, log);
+ }
+
+ private static void loge(@NonNull String log) {
+ Rlog.e(TAG, log);
+ }
+}
diff --git a/src/com/android/phone/satellite/accesscontrol/SatelliteOnDeviceAccessController.java b/src/com/android/phone/satellite/accesscontrol/SatelliteOnDeviceAccessController.java
new file mode 100644
index 0000000..520699f
--- /dev/null
+++ b/src/com/android/phone/satellite/accesscontrol/SatelliteOnDeviceAccessController.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellite.accesscontrol;
+
+import android.annotation.NonNull;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A class that performs location-based access control for satellite communication synchronously
+ * without exposing implementation details.
+ */
+public abstract class SatelliteOnDeviceAccessController implements Closeable {
+
+ /**
+ * Returns the default {@link SatelliteOnDeviceAccessController}. This method will open the
+ * underlying storage and may carry some CPU and I/O expense; callers may want to hold the
+ * {@link SatelliteOnDeviceAccessController} object for multiple lookups to amortize that cost
+ * but at the cost of some memory, or close it immediately after a single use.
+ *
+ * @param file The input file that contains the location-based access restriction information.
+ * @throws IOException in the unlikely event of errors when reading underlying file(s)
+ * @throws IllegalArgumentException if the input file format does not match the format defined
+ * by the device overlay configs.
+ */
+ public static SatelliteOnDeviceAccessController create(
+ @NonNull File file) throws IOException, IllegalArgumentException {
+ return S2RangeSatelliteOnDeviceAccessController.create(file);
+ }
+
+ /**
+ * Returns a token for a given location. See {@link LocationToken} for details.
+ */
+ public static LocationToken createLocationTokenForLatLng(double latDegrees, double lngDegrees,
+ int s2Level) {
+ return S2RangeSatelliteOnDeviceAccessController
+ .createLocationTokenForLatLng(latDegrees, lngDegrees, s2Level);
+ }
+
+ /**
+ * Returns {@code true} if the satellite communication is allowed at the provided location,
+ * {@code false} otherwise.
+ *
+ * @throws IOException in the unlikely event of errors when reading the underlying file
+ */
+ public abstract boolean isSatCommunicationAllowedAtLocation(LocationToken locationToken)
+ throws IOException;
+
+ /**
+ * Returns the S2 level of the file.
+ */
+ public abstract int getS2Level();
+
+ /**
+ * A class that represents an area with the same value. Two locations with tokens that
+ * {@link #equals(Object) equal each other} will definitely return the same value.
+ *
+ * <p>Depending on the implementation, it may be cheaper to obtain a {@link LocationToken} than
+ * doing a full lookup.
+ */
+ public abstract static class LocationToken {
+ @Override
+ public abstract boolean equals(Object other);
+
+ @Override
+ public abstract int hashCode();
+
+ /** This will print out the location information */
+ public abstract String toPiiString();
+ }
+}
diff --git a/src/com/android/phone/satellite/entitlement/SatelliteEntitlementApi.java b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementApi.java
new file mode 100644
index 0000000..c856eb5
--- /dev/null
+++ b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementApi.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellite.entitlement;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+
+import com.android.libraries.entitlement.CarrierConfig;
+import com.android.libraries.entitlement.ServiceEntitlement;
+import com.android.libraries.entitlement.ServiceEntitlementException;
+import com.android.libraries.entitlement.ServiceEntitlementRequest;
+
+/**
+ * Class that sends an HTTP request to the entitlement server and processes the response to check
+ * whether satellite service can be activated.
+ * @hide
+ */
+public class SatelliteEntitlementApi {
+ @NonNull
+ private final ServiceEntitlement mServiceEntitlement;
+ private final Context mContext;
+
+ public SatelliteEntitlementApi(@NonNull Context context,
+ @NonNull PersistableBundle carrierConfig, @NonNull int subId) {
+ mContext = context;
+ mServiceEntitlement = new ServiceEntitlement(mContext,
+ getCarrierConfigFromEntitlementServerUrl(carrierConfig), subId);
+ }
+
+ /**
+ * Returns satellite entitlement result from the entitlement server.
+ * @return The SatelliteEntitlementResult
+ */
+ public SatelliteEntitlementResult checkEntitlementStatus() throws ServiceEntitlementException {
+ ServiceEntitlementRequest.Builder requestBuilder = ServiceEntitlementRequest.builder();
+ requestBuilder.setAcceptContentType(ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON);
+ ServiceEntitlementRequest request = requestBuilder.build();
+
+ String response = mServiceEntitlement.queryEntitlementStatus(
+ ServiceEntitlement.APP_SATELLITE_ENTITLEMENT, request);
+ SatelliteEntitlementResponse satelliteEntitlementResponse =
+ new SatelliteEntitlementResponse(response);
+ return new SatelliteEntitlementResult(satelliteEntitlementResponse.getEntitlementStatus(),
+ satelliteEntitlementResponse.getPlmnAllowed());
+ }
+
+ @NonNull
+ private CarrierConfig getCarrierConfigFromEntitlementServerUrl(
+ @NonNull PersistableBundle carrierConfig) {
+ String entitlementServiceUrl = carrierConfig.getString(
+ CarrierConfigManager.ImsServiceEntitlement.KEY_ENTITLEMENT_SERVER_URL_STRING,
+ "");
+ return CarrierConfig.builder().setServerUrl(entitlementServiceUrl).build();
+ }
+}
diff --git a/src/com/android/phone/satellite/entitlement/SatelliteEntitlementController.java b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementController.java
new file mode 100644
index 0000000..94362a0
--- /dev/null
+++ b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementController.java
@@ -0,0 +1,542 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellite.entitlement;
+
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_ENABLED;
+
+import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;
+import static java.time.temporal.ChronoUnit.SECONDS;
+
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.ExponentialBackoff;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.satellite.SatelliteController;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
+import com.android.libraries.entitlement.ServiceEntitlementException;
+
+import java.time.Instant;
+import java.time.format.DateTimeParseException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class query the entitlement server to receive values for satellite services and passes the
+ * response to the {@link com.android.internal.telephony.satellite.SatelliteController}.
+ * @hide
+ */
+public class SatelliteEntitlementController extends Handler {
+ private static final String TAG = "SatelliteEntitlementController";
+ @NonNull private static SatelliteEntitlementController sInstance;
+ /** Message code used in handleMessage() */
+ private static final int CMD_START_QUERY_ENTITLEMENT = 1;
+ private static final int CMD_RETRY_QUERY_ENTITLEMENT = 2;
+ private static final int CMD_STOP_RETRY_QUERY_ENTITLEMENT = 3;
+
+ /** Retry on next trigger event. */
+ private static final int HTTP_RESPONSE_500 = 500;
+ /** Retry after the time specified in the “Retry-After” header. After retry count doesn't exceed
+ * MAX_RETRY_COUNT. */
+ private static final int HTTP_RESPONSE_503 = 503;
+ /** Default query refresh time is 1 month. */
+
+ private static final int DEFAULT_QUERY_REFRESH_DAYS = 30;
+ private static final long INITIAL_DELAY_MILLIS = TimeUnit.MINUTES.toMillis(10); // 10 min
+ private static final long MAX_DELAY_MILLIS = TimeUnit.DAYS.toMillis(5); // 5 days
+ private static final int MULTIPLIER = 2;
+ private static final int MAX_RETRY_COUNT = 5;
+ @NonNull private final SubscriptionManagerService mSubscriptionManagerService;
+ @NonNull private final CarrierConfigManager mCarrierConfigManager;
+ @NonNull private final CarrierConfigManager.CarrierConfigChangeListener
+ mCarrierConfigChangeListener;
+ @NonNull private final ConnectivityManager mConnectivityManager;
+ @NonNull private final ConnectivityManager.NetworkCallback mNetworkCallback;
+ @NonNull private final BroadcastReceiver mReceiver;
+ @NonNull private final Context mContext;
+ private final Object mLock = new Object();
+ /** Map key : subId, value : ExponentialBackoff. */
+ private Map<Integer, ExponentialBackoff> mExponentialBackoffPerSub = new HashMap<>();
+ /** Map key : subId, value : SatelliteEntitlementResult. */
+ private Map<Integer, SatelliteEntitlementResult> mSatelliteEntitlementResultPerSub =
+ new HashMap<>();
+ /** Map key : subId, value : the last query time to millis. */
+ private Map<Integer, Long> mLastQueryTimePerSub = new HashMap<>();
+ /** Map key : subId, value : Count the number of retries caused by the 'ExponentialBackoff' and
+ * '503 error case with the Retry-After header'. */
+ private Map<Integer, Integer> mRetryCountPerSub = new HashMap<>();
+
+ /**
+ * Create the SatelliteEntitlementController singleton instance.
+ * @param context The Context to use to create the SatelliteEntitlementController.
+ * @param featureFlags The feature flag.
+ */
+ public static void make(@NonNull Context context, @NonNull FeatureFlags featureFlags) {
+ if (!featureFlags.carrierEnabledSatelliteFlag()) {
+ logd("carrierEnabledSatelliteFlag is disabled. don't created this.");
+ return;
+ }
+ if (sInstance == null) {
+ HandlerThread handlerThread = new HandlerThread(TAG);
+ handlerThread.start();
+ sInstance =
+ new SatelliteEntitlementController(context, handlerThread.getLooper());
+ }
+ }
+
+ /**
+ * Create a SatelliteEntitlementController to request query to the entitlement server for
+ * satellite services and receive responses.
+ *
+ * @param context The Context for the SatelliteEntitlementController.
+ * @param looper The looper for the handler. It does not run on main thread.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public SatelliteEntitlementController(@NonNull Context context, @NonNull Looper looper) {
+ super(looper);
+ mContext = context;
+ mSubscriptionManagerService = SubscriptionManagerService.getInstance();
+ mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
+ mCarrierConfigChangeListener = (slotIndex, subId, carrierId, specificCarrierId) ->
+ handleCarrierConfigChanged(slotIndex, subId, carrierId, specificCarrierId);
+ mCarrierConfigManager.registerCarrierConfigChangeListener(this::post,
+ mCarrierConfigChangeListener);
+ mConnectivityManager = context.getSystemService(ConnectivityManager.class);
+ mNetworkCallback = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onAvailable(Network network) {
+ handleInternetConnected();
+ }
+
+ @Override
+ public void onLost(Network network) {
+ handleInternetDisconnected();
+ }
+ };
+ NetworkRequest networkrequest = new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build();
+ mConnectivityManager.registerNetworkCallback(networkrequest, mNetworkCallback, this);
+ mReceiver = new SatelliteEntitlementControllerReceiver();
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ context.registerReceiver(mReceiver, intentFilter);
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ switch (msg.what) {
+ case CMD_START_QUERY_ENTITLEMENT:
+ handleCmdStartQueryEntitlement();
+ break;
+ case CMD_RETRY_QUERY_ENTITLEMENT:
+ handleCmdRetryQueryEntitlement(msg.arg1);
+ break;
+ case CMD_STOP_RETRY_QUERY_ENTITLEMENT:
+ stopExponentialBackoff(msg.arg1);
+ break;
+ default:
+ logd("do not used this message");
+ }
+ }
+
+ private void handleCarrierConfigChanged(int slotIndex, int subId, int carrierId,
+ int specificCarrierId) {
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ return;
+ }
+ logd("handleCarrierConfigChanged(): slotIndex(" + slotIndex + "), subId("
+ + subId + "), carrierId(" + carrierId + "), specificCarrierId("
+ + specificCarrierId + ")");
+
+ sendEmptyMessage(CMD_START_QUERY_ENTITLEMENT);
+ }
+
+ private class SatelliteEntitlementControllerReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
+ boolean airplaneMode = intent.getBooleanExtra("state", false);
+ handleAirplaneModeChange(airplaneMode);
+ }
+ }
+ }
+
+ private void handleAirplaneModeChange(boolean airplaneMode) {
+ if (!airplaneMode) {
+ resetEntitlementQueryCounts(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ }
+ }
+
+ private boolean isInternetConnected() {
+ Network activeNetwork = mConnectivityManager.getActiveNetwork();
+ NetworkCapabilities networkCapabilities =
+ mConnectivityManager.getNetworkCapabilities(activeNetwork);
+ // TODO b/319780796 Add checking if it is not a satellite.
+ return networkCapabilities != null
+ && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ }
+
+ private void handleInternetConnected() {
+ sendEmptyMessage(CMD_START_QUERY_ENTITLEMENT);
+ }
+
+ private void handleInternetDisconnected() {
+ mExponentialBackoffPerSub.forEach((key, value) -> {
+ Message message = obtainMessage();
+ message.what = CMD_STOP_RETRY_QUERY_ENTITLEMENT;
+ message.arg1 = key;
+ sendMessage(message);
+ });
+ }
+
+ /**
+ * Check if the device can request to entitlement server (if there is an internet connection and
+ * if the throttle time has passed since the last request), and then pass the response to
+ * SatelliteController if the response is received.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public void handleCmdStartQueryEntitlement() {
+ if (!isInternetConnected()) {
+ logd("Internet disconnected");
+ return;
+ }
+
+ for (int subId : mSubscriptionManagerService.getActiveSubIdList(true)) {
+ if (!shouldQueryEntitlement(subId)) {
+ return;
+ }
+
+ // Check the satellite service query result from the entitlement server for the
+ // satellite service.
+ try {
+ mSatelliteEntitlementResultPerSub.remove(subId);
+ mSatelliteEntitlementResultPerSub.put(subId, getSatelliteEntitlementApi(
+ subId).checkEntitlementStatus());
+ } catch (ServiceEntitlementException e) {
+ loge(e.toString());
+ if (!isInternetConnected()) {
+ logd("handleCmdStartQueryEntitlement: disconnected. " + e);
+ return;
+ }
+ if (shouldHandleErrorResponse(e, subId)) {
+ logd("handleCmdStartQueryEntitlement: handle response.");
+ return;
+ }
+ startExponentialBackoff(subId);
+ return;
+ }
+ queryCompleted(subId);
+ }
+ }
+
+ /** When airplane mode changes from on to off, reset the values required to start the first
+ * query. */
+ private void resetEntitlementQueryCounts(String event) {
+ logd("resetEntitlementQueryCounts: " + event);
+ mLastQueryTimePerSub = new HashMap<>();
+ mExponentialBackoffPerSub = new HashMap<>();
+ mRetryCountPerSub = new HashMap<>();
+ }
+
+ /**
+ * If the HTTP response does not receive a body containing the 200 ok with sat mode
+ * configuration,
+ *
+ * 1. If the 500 response received, then no more retry until next event occurred.
+ * 2. If the 503 response with Retry-After header received, then the query is retried until
+ * MAX_RETRY_COUNT.
+ * 3. If other response or exception is occurred, then the query is retried until
+ * MAX_RETRY_COUNT is reached using the ExponentialBackoff.
+ */
+ private void handleCmdRetryQueryEntitlement(int subId) {
+ logd("handleCmdRetryQueryEntitlement: " + subId);
+ try {
+ synchronized (mLock) {
+ mSatelliteEntitlementResultPerSub.put(subId, getSatelliteEntitlementApi(
+ subId).checkEntitlementStatus());
+ }
+ } catch (ServiceEntitlementException e) {
+ if (!isInternetConnected()) {
+ logd("retryQuery: Internet disconnected. reset the retry and after the "
+ + "internet is connected then the first query is triggered." + e);
+ stopExponentialBackoff(subId);
+ return;
+ }
+ if (shouldHandleErrorResponse(e, subId)) {
+ logd("retryQuery: handle response.");
+ stopExponentialBackoff(subId);
+ return;
+ }
+ mExponentialBackoffPerSub.get(subId).notifyFailed();
+ mRetryCountPerSub.put(subId,
+ mRetryCountPerSub.getOrDefault(subId, 0) + 1);
+ logd("handleCmdRetryQueryEntitlement:" + e + "[" + subId + "] cnt="
+ + mRetryCountPerSub.getOrDefault(subId, 0) + "] Retrying in "
+ + mExponentialBackoffPerSub.get(subId).getCurrentDelay() + " ms.");
+ }
+ }
+
+ /** Only handle '500' and '503 with retry-after header' error responses received.
+ * If the 500 response is received, no retry until the next trigger event occurs.
+ * If the 503 response with Retry-After header, retry is attempted according to the value in the
+ * Retry-After header up to MAX_RETRY_COUNT.
+ * In other cases, it performs an exponential backoff process. */
+ private boolean shouldHandleErrorResponse(ServiceEntitlementException e, int subId) {
+ int responseCode = e.getHttpStatus();
+ logd("shouldHandleErrorResponse: received the " + responseCode);
+ if (responseCode == HTTP_RESPONSE_503 && e.getRetryAfter() != null
+ && !e.getRetryAfter().isEmpty()) {
+ if (mRetryCountPerSub.getOrDefault(subId, 0) >= MAX_RETRY_COUNT) {
+ logd("The 503 retry after reaching the " + MAX_RETRY_COUNT
+ + "The retry will not be attempted until the next trigger event.");
+ queryCompleted(subId);
+ return true;
+ }
+ long retryAfterSeconds = parseSecondsFromRetryAfter(e.getRetryAfter());
+ if (retryAfterSeconds == -1) {
+ logd("Unable parsing the retry-after. try to exponential backoff.");
+ return false;
+ }
+ mRetryCountPerSub.put(subId, mRetryCountPerSub.getOrDefault(subId, 0) + 1);
+ logd("[" + subId + "] cnt=" + mRetryCountPerSub.getOrDefault(subId, 0)
+ + " Retrying in " + TimeUnit.SECONDS.toMillis(retryAfterSeconds) + " sec");
+ Message message = obtainMessage();
+ message.what = CMD_RETRY_QUERY_ENTITLEMENT;
+ message.arg1 = subId;
+ sendMessageDelayed(message, TimeUnit.SECONDS.toMillis(retryAfterSeconds));
+ return true;
+ } else if (responseCode == HTTP_RESPONSE_500) {
+ logd("The retry on the next trigger event.");
+ queryCompleted(subId);
+ return true;
+ }
+ return false;
+ }
+
+ /** Parse the HTTP-date or a number of seconds in the retry-after value. */
+ private long parseSecondsFromRetryAfter(String retryAfter) {
+ try {
+ return Long.parseLong(retryAfter);
+ } catch (NumberFormatException numberFormatException) {
+ }
+
+ try {
+ return SECONDS.between(
+ Instant.now(), RFC_1123_DATE_TIME.parse(retryAfter, Instant::from));
+ } catch (DateTimeParseException dateTimeParseException) {
+ }
+
+ return -1;
+ }
+
+ private void startExponentialBackoff(int subId) {
+ stopExponentialBackoff(subId);
+ mExponentialBackoffPerSub.put(subId,
+ new ExponentialBackoff(INITIAL_DELAY_MILLIS, MAX_DELAY_MILLIS,
+ MULTIPLIER, this.getLooper(), () -> {
+ synchronized (mLock) {
+ if (mSatelliteEntitlementResultPerSub.containsKey(subId)) {
+ logd("handleCmdStartQueryEntitlement: get the response "
+ + "successfully.");
+ mExponentialBackoffPerSub.get(subId).stop();
+ queryCompleted(subId);
+ return;
+ }
+
+ if (mRetryCountPerSub.getOrDefault(subId, 0) >= MAX_RETRY_COUNT) {
+ logd("The ExponentialBackoff is stopped after reaching the "
+ + MAX_RETRY_COUNT + ". The retry don't attempted until the"
+ + " refresh time expires.");
+ mExponentialBackoffPerSub.get(subId).stop();
+ queryCompleted(subId);
+ return;
+ }
+ if (!mSatelliteEntitlementResultPerSub.containsKey(subId)) {
+ handleCmdRetryQueryEntitlement(subId);
+ }
+ }
+ }));
+ mExponentialBackoffPerSub.get(subId).start();
+ mRetryCountPerSub.put(subId, mRetryCountPerSub.getOrDefault(subId, 0) + 1);
+ logd("start ExponentialBackoff [" + mRetryCountPerSub.getOrDefault(subId, 0)
+ + "] Retrying in " + mExponentialBackoffPerSub.get(subId).getCurrentDelay()
+ + " ms.");
+ }
+
+ /** If the Internet connection is lost during the ExponentialBackoff, stop the
+ * ExponentialBackoff and reset it. */
+ private void stopExponentialBackoff(int subId) {
+ if (isExponentialBackoffInProgress(subId)) {
+ logd("stopExponentialBackoff: reset ExponentialBackoff");
+ mExponentialBackoffPerSub.get(subId).stop();
+ mExponentialBackoffPerSub.remove(subId);
+ }
+ }
+
+ /**
+ * No more query retry, update the result. If there is no response from the server, then used
+ * the default value - 'satellite disabled' and empty 'PLMN allowed list'.
+ * And then it send a delayed message to trigger the query again after A refresh day has passed.
+ */
+ private void queryCompleted(int subId) {
+ if (!mSatelliteEntitlementResultPerSub.containsKey(subId)) {
+ logd("queryCompleted: create default SatelliteEntitlementResult");
+ mSatelliteEntitlementResultPerSub.put(subId,
+ SatelliteEntitlementResult.getDefaultResult());
+ }
+
+ saveLastQueryTime(subId);
+ Message message = obtainMessage();
+ message.what = CMD_START_QUERY_ENTITLEMENT;
+ message.arg1 = subId;
+ sendMessageDelayed(message, TimeUnit.DAYS.toMillis(
+ getSatelliteEntitlementStatusRefreshDays(subId)));
+ logd("queryCompleted: updateSatelliteEntitlementStatus");
+ updateSatelliteEntitlementStatus(subId,
+ mSatelliteEntitlementResultPerSub.get(subId).getEntitlementStatus()
+ == SATELLITE_ENTITLEMENT_STATUS_ENABLED,
+ mSatelliteEntitlementResultPerSub.get(subId).getAllowedPLMNList());
+ stopExponentialBackoff(subId);
+ mRetryCountPerSub.remove(subId);
+ }
+
+ /** Check whether there is a saved subId. Returns true if there is a saved subId,
+ * otherwise return false.*/
+ private boolean isExponentialBackoffInProgress(int subId) {
+ return mExponentialBackoffPerSub.containsKey(subId);
+ }
+
+ /**
+ * Check if the subId can query the entitlement server to get the satellite configuration.
+ */
+ private boolean shouldQueryEntitlement(int subId) {
+ if (!isSatelliteEntitlementSupported(subId)) {
+ logd("Doesn't support entitlement query for satellite.");
+ return false;
+ }
+
+ if (isExponentialBackoffInProgress(subId)) {
+ logd("In progress ExponentialBackoff.");
+ return false;
+ }
+
+ return shouldRefreshEntitlementStatus(subId);
+ }
+
+ /**
+ * Compare the last query time to the refresh time from the CarrierConfig to see if the device
+ * can query the entitlement server.
+ */
+ private boolean shouldRefreshEntitlementStatus(int subId) {
+ long lastQueryTimeMillis = getLastQueryTime(subId);
+ long refreshTimeMillis = TimeUnit.DAYS.toMillis(
+ getSatelliteEntitlementStatusRefreshDays(subId));
+ boolean isAvailable =
+ (System.currentTimeMillis() - lastQueryTimeMillis) > refreshTimeMillis;
+ if (!isAvailable) {
+ logd("query is already done. can query after " + Instant.ofEpochMilli(
+ refreshTimeMillis + lastQueryTimeMillis));
+ }
+ return isAvailable;
+ }
+
+ /**
+ * Get the SatelliteEntitlementApi.
+ *
+ * @param subId The subId of the subscription for creating SatelliteEntitlementApi
+ * @return A new SatelliteEntitlementApi object.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public SatelliteEntitlementApi getSatelliteEntitlementApi(int subId) {
+ return new SatelliteEntitlementApi(mContext, getConfigForSubId(subId), subId);
+ }
+
+ /** If there is a value stored in the cache, it is used. If there is no value stored in the
+ * cache, it is considered the first query. */
+ private long getLastQueryTime(int subId) {
+ return mLastQueryTimePerSub.getOrDefault(subId, 0L);
+ }
+
+ /** Return the satellite entitlement status refresh days from carrier config. */
+ private int getSatelliteEntitlementStatusRefreshDays(int subId) {
+ return getConfigForSubId(subId).getInt(
+ CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT,
+ DEFAULT_QUERY_REFRESH_DAYS);
+ }
+
+ /** Return the satellite entitlement supported bool from carrier config. */
+ private boolean isSatelliteEntitlementSupported(int subId) {
+ return getConfigForSubId(subId).getBoolean(
+ CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL);
+ }
+
+ @NonNull
+ private PersistableBundle getConfigForSubId(int subId) {
+ PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId,
+ CarrierConfigManager.ImsServiceEntitlement.KEY_ENTITLEMENT_SERVER_URL_STRING,
+ CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT,
+ CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL);
+ if (config == null || config.isEmpty()) {
+ config = CarrierConfigManager.getDefaultConfig();
+ }
+ return config;
+ }
+
+ private void saveLastQueryTime(int subId) {
+ long lastQueryTimeMillis = System.currentTimeMillis();
+ mLastQueryTimePerSub.put(subId, lastQueryTimeMillis);
+ }
+
+ /**
+ * Send to satelliteController for update the satellite service enabled or not and plmn Allowed
+ * list.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public void updateSatelliteEntitlementStatus(int subId, boolean enabled,
+ List<String> plmnAllowedList) {
+ SatelliteController.getInstance().onSatelliteEntitlementStatusUpdated(subId, enabled,
+ plmnAllowedList, null);
+ }
+
+ private static void logd(String log) {
+ Rlog.d(TAG, log);
+ }
+
+ private static void loge(String log) {
+ Rlog.e(TAG, log);
+ }
+}
diff --git a/src/com/android/phone/satellite/entitlement/SatelliteEntitlementResponse.java b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementResponse.java
new file mode 100644
index 0000000..1fe0ecf
--- /dev/null
+++ b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementResponse.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellite.entitlement;
+
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_DISABLED;
+
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.satellite.SatelliteNetworkInfo;
+import com.android.libraries.entitlement.ServiceEntitlement;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * This class parses whether the satellite service configuration.
+ * @hide
+ */
+public class SatelliteEntitlementResponse {
+ private static final String TAG = "SatelliteEntitlementResponse";
+
+ /** Overall status of the SatMode entitlement, stating if the satellite service can be offered
+ * on the device, and if it can be activated or not by the user. */
+ private static final String ENTITLEMENT_STATUS_KEY = "EntitlementStatus";
+ /** List of allowed PLMNs where the service can be used. */
+ private static final String PLMN_ALLOWED_KEY = "PLMNAllowed";
+ /** List of barred PLMNs where the service can’t be used. */
+ private static final String PLMN_BARRED_KEY = "PLMNBarred";
+ /** allowed PLMN-ID where the service can be used or is barred. */
+ private static final String PLMN_KEY = "PLMN";
+ /** The data plan is of the metered or un-metered type. This value is optional. */
+ private static final String DATA_PLAN_TYPE_KEY = "DataPlanType";
+
+ @SatelliteEntitlementResult.SatelliteEntitlementStatus private int mEntitlementStatus;
+
+ /**
+ * <p> Available options are :
+ * "PLMNAllowed":[{ "PLMN": "XXXXXX", “DataPlanType”: "unmetered"},
+ * {"PLMN": "XXXXXX", “DataPlanType”: "metered"},
+ * {"PLMN": "XXXXXX"}]
+ */
+ private List<SatelliteNetworkInfo> mPlmnAllowedList;
+ /**
+ * <p> Available option is :
+ * "PLMNBarred":[{"PLMN": "XXXXXX"}, {"PLMN”:"XXXXXX"}]
+ */
+ private List<String> mPlmnBarredList;
+
+ public SatelliteEntitlementResponse(String response) {
+ mEntitlementStatus = SATELLITE_ENTITLEMENT_STATUS_DISABLED;
+ mPlmnAllowedList = new ArrayList<>();
+ mPlmnBarredList = new ArrayList<>();
+ parsingResponse(response);
+ }
+
+ /**
+ * Get the entitlement status for the satellite service
+ * @return The satellite entitlement status
+ */
+ public int getEntitlementStatus() {
+ return mEntitlementStatus;
+ }
+
+ /**
+ * Get the PLMNAllowed from the response
+ * @return The PLMNs Allowed list. PLMN and Data Plan Type(optional).
+ */
+ public List<SatelliteNetworkInfo> getPlmnAllowed() {
+ return mPlmnAllowedList.stream().map((info) -> new SatelliteNetworkInfo(info.mPlmn,
+ info.mDataPlanType)).collect(Collectors.toList());
+ }
+
+ /**
+ * Get the PLMNBarredList from the response
+ * @return The PLMNs Barred List
+ */
+ @VisibleForTesting
+ public List<String> getPlmnBarredList() {
+ return mPlmnBarredList.stream().map(String::new).collect(Collectors.toList());
+ }
+
+ private void parsingResponse(String response) {
+ JSONObject jsonAuthResponse = null;
+ try {
+ jsonAuthResponse = new JSONObject(response);
+ if (!jsonAuthResponse.has(ServiceEntitlement.APP_SATELLITE_ENTITLEMENT)) {
+ loge("parsingResponse failed with no app");
+ return;
+ }
+ JSONObject jsonToken = jsonAuthResponse.getJSONObject(
+ ServiceEntitlement.APP_SATELLITE_ENTITLEMENT);
+ if (jsonToken.has(ENTITLEMENT_STATUS_KEY)) {
+ String entitlementStatus = jsonToken.getString(ENTITLEMENT_STATUS_KEY);
+ if (entitlementStatus == null) {
+ loge("parsingResponse EntitlementStatus is null");
+ return;
+ }
+ mEntitlementStatus = Integer.valueOf(entitlementStatus);
+ }
+ if (jsonToken.has(PLMN_ALLOWED_KEY)) {
+ JSONArray jsonArray = jsonToken.getJSONArray(PLMN_ALLOWED_KEY);
+ mPlmnAllowedList = new ArrayList<>();
+ for (int i = 0; i < jsonArray.length(); i++) {
+ String dataPlanType = jsonArray.getJSONObject(i).has(DATA_PLAN_TYPE_KEY)
+ ? jsonArray.getJSONObject(i).getString(DATA_PLAN_TYPE_KEY) : "";
+ mPlmnAllowedList.add(new SatelliteNetworkInfo(
+ jsonArray.getJSONObject(i).getString(PLMN_KEY), dataPlanType));
+ }
+ }
+ if (jsonToken.has(PLMN_BARRED_KEY)) {
+ mPlmnBarredList = new ArrayList<>();
+ JSONArray jsonArray = jsonToken.getJSONArray(PLMN_BARRED_KEY);
+ for (int i = 0; i < jsonArray.length(); i++) {
+ mPlmnBarredList.add(jsonArray.getJSONObject(i).getString(PLMN_KEY));
+ }
+ }
+ } catch (JSONException e) {
+ loge("parsingResponse: failed JSONException", e);
+ } catch (NumberFormatException e) {
+ loge("parsingResponse: failed NumberFormatException", e);
+ }
+ }
+
+ private static void loge(String log) {
+ Log.e(TAG, log);
+ }
+
+ private static void loge(String log, Exception e) {
+ Log.e(TAG, log, e);
+ }
+}
diff --git a/src/com/android/phone/satellite/entitlement/SatelliteEntitlementResult.java b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementResult.java
new file mode 100644
index 0000000..3289232
--- /dev/null
+++ b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementResult.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellite.entitlement;
+
+import android.annotation.IntDef;
+
+import com.android.internal.telephony.satellite.SatelliteNetworkInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * This class stores the result of the satellite entitlement query and passes them to
+ * SatelliteEntitlementController.
+ */
+public class SatelliteEntitlementResult {
+ /** SatMode allowed, but not yet provisioned and activated on the network. */
+ public static final int SATELLITE_ENTITLEMENT_STATUS_DISABLED = 0;
+ /** SatMode service allowed, provisioned and activated on the network. User can access the
+ * satellite service. */
+ public static final int SATELLITE_ENTITLEMENT_STATUS_ENABLED = 1;
+ /** SatMode cannot be offered for network or device. */
+ public static final int SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE = 2;
+ /** SatMode is being provisioned on the network. Not yet activated. */
+ public static final int SATELLITE_ENTITLEMENT_STATUS_PROVISIONING = 3;
+
+ @IntDef(prefix = {"SATELLITE_ENTITLEMENT_STATUS_"}, value = {
+ SATELLITE_ENTITLEMENT_STATUS_DISABLED,
+ SATELLITE_ENTITLEMENT_STATUS_ENABLED,
+ SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE,
+ SATELLITE_ENTITLEMENT_STATUS_PROVISIONING
+ })
+ public @interface SatelliteEntitlementStatus {}
+
+ private @SatelliteEntitlementStatus int mEntitlementStatus;
+ /**
+ * An SatelliteNetworkInfo list consisting of the PLMN and the DataPlanType in the PLMNAlowed
+ * item of the satellite configuration received from the entitlement server.
+ */
+ private List<SatelliteNetworkInfo> mAllowedSatelliteNetworkInfoList;
+
+ /**
+ * Store the result of the satellite entitlement response.
+ *
+ * @param entitlementStatus The entitlement status.
+ * @param allowedSatelliteNetworkInfoList The allowedSatelliteNetworkInfoList
+ */
+ public SatelliteEntitlementResult(@SatelliteEntitlementStatus int entitlementStatus,
+ List<SatelliteNetworkInfo> allowedSatelliteNetworkInfoList) {
+ mEntitlementStatus = entitlementStatus;
+ mAllowedSatelliteNetworkInfoList = allowedSatelliteNetworkInfoList;
+ }
+
+ /**
+ * Get the entitlement status.
+ *
+ * @return The entitlement status.
+ */
+ public @SatelliteEntitlementStatus int getEntitlementStatus() {
+ return mEntitlementStatus;
+ }
+
+ /**
+ * Get the plmn allowed list
+ *
+ * @return The plmn allowed list.
+ */
+ public List<String> getAllowedPLMNList() {
+ return mAllowedSatelliteNetworkInfoList.stream().map(info -> info.mPlmn).collect(
+ Collectors.toList());
+ }
+
+ /**
+ * Get the default SatelliteEntitlementResult. EntitlementStatus set to
+ * `SATELLITE_ENTITLEMENT_STATUS_DISABLED` and SatelliteNetworkInfo list set to empty.
+ *
+ * @return If there is no response, return default SatelliteEntitlementResult
+ */
+ public static SatelliteEntitlementResult getDefaultResult() {
+ return new SatelliteEntitlementResult(SATELLITE_ENTITLEMENT_STATUS_DISABLED,
+ new ArrayList<>());
+ }
+}
diff --git a/src/com/android/phone/security/SafetySourceReceiver.java b/src/com/android/phone/security/SafetySourceReceiver.java
new file mode 100644
index 0000000..76f8e72
--- /dev/null
+++ b/src/com/android/phone/security/SafetySourceReceiver.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.security;
+
+import static android.safetycenter.SafetyCenterManager.ACTION_REFRESH_SAFETY_SOURCES;
+import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.Flags;
+import com.android.phone.PhoneGlobals;
+import com.android.telephony.Rlog;
+
+public class SafetySourceReceiver extends BroadcastReceiver {
+ private static final String TAG = "TelephonySafetySourceReceiver";
+ @Override
+ public void onReceive(Context context, Intent intent) {
+
+ // If none of the features that depend on this receiver are enabled, there's no reason
+ // to progress.
+ if (!Flags.enableIdentifierDisclosureTransparencyUnsolEvents()
+ || !Flags.enableModemCipherTransparencyUnsolEvents()) {
+ return;
+ }
+
+ String action = intent.getAction();
+ if (!ACTION_REFRESH_SAFETY_SOURCES.equals(action)) {
+ return;
+ }
+
+ String refreshBroadcastId =
+ intent.getStringExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID);
+ if (refreshBroadcastId == null) {
+ return;
+ }
+
+ if (Flags.enforceTelephonyFeatureMappingForPublicApis()) {
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ refreshSafetySources(refreshBroadcastId);
+ }
+ } else {
+ refreshSafetySources(refreshBroadcastId);
+ }
+ }
+
+ private void refreshSafetySources(String refreshBroadcastId) {
+ Phone phone = getDefaultPhone();
+ // It's possible that phones have not been created yet. Safety center may send a refresh
+ // broadcast very early on.
+ if (phone != null) {
+ phone.refreshSafetySources(refreshBroadcastId);
+ }
+
+ }
+
+ @VisibleForTesting
+ public Phone getDefaultPhone() {
+ try {
+ return PhoneGlobals.getPhone();
+ } catch (IllegalStateException e) {
+ Rlog.i(TAG, "Unable to get phone. Skipping safety source refresh: " + e.getMessage());
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
index 7cc9235..976afd4 100644
--- a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
+++ b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
@@ -24,6 +24,7 @@
import android.util.Log;
import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.Flags;
import com.android.phone.PhoneUtils;
import com.android.phone.R;
import com.android.phone.SubscriptionInfoHelper;
@@ -94,6 +95,9 @@
mTelecomManager = getActivity().getSystemService(TelecomManager.class);
mTelephonyManager = TelephonyManager.from(getActivity());
mSubscriptionManager = SubscriptionManager.from(getActivity());
+ if (Flags.workProfileApiSplit()) {
+ mSubscriptionManager = mSubscriptionManager.createForAllUserProfiles();
+ }
}
@Override
diff --git a/src/com/android/phone/settings/RadioInfo.java b/src/com/android/phone/settings/RadioInfo.java
index 124badf..7b6c493 100644
--- a/src/com/android/phone/settings/RadioInfo.java
+++ b/src/com/android/phone/settings/RadioInfo.java
@@ -44,6 +44,7 @@
import android.os.Message;
import android.os.PersistableBundle;
import android.os.SystemProperties;
+import android.os.UserManager;
import android.telephony.AccessNetworkConstants;
import android.telephony.CarrierConfigManager;
import android.telephony.CellIdentityCdma;
@@ -73,6 +74,7 @@
import android.telephony.TelephonyDisplayInfo;
import android.telephony.TelephonyManager;
import android.telephony.data.NetworkSlicingConfig;
+import android.telephony.euicc.EuiccManager;
import android.telephony.ims.ImsException;
import android.telephony.ims.ImsManager;
import android.telephony.ims.ImsMmTelManager;
@@ -207,6 +209,7 @@
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;
+ private static final int EVENT_UPDATE_NR_STATS = 1008;
private static final int MENU_ITEM_VIEW_ADN = 1;
private static final int MENU_ITEM_VIEW_FDN = 2;
@@ -257,6 +260,7 @@
private TextView mNrState;
private TextView mNrFrequency;
private TextView mNetworkSlicingConfig;
+ private TextView mEuiccInfo;
private EditText mSmsc;
private Switch mRadioPowerOnSwitch;
private Switch mSimulateOutOfServiceSwitch;
@@ -287,6 +291,7 @@
private ImsManager mImsManager = null;
private Phone mPhone = null;
private ProvisioningManager mProvisioningManager = null;
+ private EuiccManager mEuiccManager;
private String mPingHostnameResultV4;
private String mPingHostnameResultV6;
@@ -296,6 +301,7 @@
private List<CellInfo> mCellInfoResult = null;
private final boolean[] mSimulateOos = new boolean[2];
+ private String mEuiccInfoResult = "";
private int mPreferredNetworkTypeResult;
private int mCellInfoRefreshRateIndex;
@@ -379,7 +385,14 @@
updateNetworkType();
updateRawRegistrationState(serviceState);
updateImsProvisionedState();
- updateNrStats(serviceState);
+
+ // Since update NR stats includes a ril message to get slicing information, it runs
+ // as blocking during the timeout period of 1 second. if ServiceStateChanged event
+ // fires consecutively, RadioInfo can run for more than 10 seconds. This can cause ANR.
+ // Therefore, send event only when there is no same event being processed.
+ if (!mHandler.hasMessages(EVENT_UPDATE_NR_STATS)) {
+ mHandler.obtainMessage(EVENT_UPDATE_NR_STATS).sendToTarget();
+ }
}
@Override
@@ -465,6 +478,10 @@
}
updatePhysicalChannelConfiguration((List<PhysicalChannelConfig>) ar.result);
break;
+ case EVENT_UPDATE_NR_STATS:
+ log("got EVENT_UPDATE_NR_STATS");
+ updateNrStats();
+ break;
default:
super.handleMessage(msg);
break;
@@ -482,6 +499,15 @@
return;
}
+ UserManager userManager =
+ (UserManager) getApplicationContext().getSystemService(Context.USER_SERVICE);
+ if (userManager != null
+ && userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
+ Log.w(TAG, "User is restricted from configuring mobile networks.");
+ finish();
+ return;
+ }
+
setContentView(R.layout.radio_info);
log("Started onCreate");
@@ -492,6 +518,7 @@
mPhone = getPhone(SubscriptionManager.getDefaultSubscriptionId());
mTelephonyManager = ((TelephonyManager) getSystemService(TELEPHONY_SERVICE))
.createForSubscriptionId(mPhone.getSubId());
+ mEuiccManager = getSystemService(EuiccManager.class);
mImsManager = new ImsManager(mPhone.getContext());
try {
@@ -539,6 +566,7 @@
mNrFrequency = (TextView) findViewById(R.id.nr_frequency);
mPhyChanConfig = (TextView) findViewById(R.id.phy_chan_config);
mNetworkSlicingConfig = (TextView) findViewById(R.id.network_slicing_config);
+ mEuiccInfo = (TextView) findViewById(R.id.euicc_info);
// hide 5G stats on devices that don't support 5G
if ((mTelephonyManager.getSupportedRadioAccessFamily()
@@ -690,7 +718,8 @@
updateProperties();
updateDnsCheckState();
updateNetworkType();
- updateNrStats(null);
+ updateNrStats();
+ updateEuiccInfo();
updateCellInfo(mCellInfoResult);
updateSubscriptionIds();
@@ -835,7 +864,9 @@
@Override
protected void onDestroy() {
super.onDestroy();
- mQueuedWork.shutdown();
+ if (mQueuedWork != null) {
+ mQueuedWork.shutdown();
+ }
}
// returns array of string labels for each phone index. The array index is equal to the phone
@@ -1235,15 +1266,12 @@
AccessNetworkConstants.TRANSPORT_TYPE_WLAN));
}
- private void updateNrStats(ServiceState serviceState) {
+ private void updateNrStats() {
if ((mTelephonyManager.getSupportedRadioAccessFamily()
& TelephonyManager.NETWORK_TYPE_BITMASK_NR) == 0) {
return;
}
- ServiceState ss = serviceState;
- if (ss == null && mPhone != null) {
- ss = mPhone.getServiceState();
- }
+ ServiceState ss = (mPhone == null) ? null : mPhone.getServiceState();
if (ss != null) {
NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
@@ -1257,6 +1285,13 @@
}
mNrState.setText(NetworkRegistrationInfo.nrStateToString(ss.getNrState()));
mNrFrequency.setText(ServiceState.frequencyRangeToString(ss.getNrFrequencyRange()));
+ } else {
+ Log.e(TAG, "Clear Nr stats by null service state");
+ mEndcAvailable.setText("");
+ mDcnrRestricted.setText("");
+ mNrAvailable.setText("");
+ mNrState.setText("");
+ mNrFrequency.setText("");
}
CompletableFuture<NetworkSlicingConfig> resultFuture = new CompletableFuture<>();
@@ -1317,6 +1352,31 @@
mReceived.setText(rxPackets + " " + packets + ", " + rxBytes + " " + bytes);
}
+ private void updateEuiccInfo() {
+ final Runnable setEuiccInfo = new Runnable() {
+ public void run() {
+ mEuiccInfo.setText(mEuiccInfoResult);
+ }
+ };
+
+ mQueuedWork.execute(new Runnable() {
+ @Override
+ public void run() {
+ if (!mEuiccManager.isEnabled()) {
+ mEuiccInfoResult = "Not enabled";
+ }
+ try {
+ mEuiccInfoResult = " { Available memory in bytes:"
+ + mEuiccManager.getAvailableMemoryInBytes()
+ + " }";
+ } catch (Exception e) {
+ mEuiccInfoResult = e.getMessage();
+ }
+ mHandler.post(setEuiccInfo);
+ }
+ });
+ }
+
/**
* Ping a host name
*/
@@ -1539,8 +1599,7 @@
};
private boolean isRadioOn() {
- //FIXME: Replace with a TelephonyManager call
- return mPhone.getServiceState().getState() != ServiceState.STATE_POWER_OFF;
+ return mTelephonyManager.getRadioPowerState() == TelephonyManager.RADIO_POWER_ON;
}
private void updateRadioPowerState() {
diff --git a/src/com/android/phone/settings/fdn/FdnSetting.java b/src/com/android/phone/settings/fdn/FdnSetting.java
index 8f46c85..e347dec 100644
--- a/src/com/android/phone/settings/fdn/FdnSetting.java
+++ b/src/com/android/phone/settings/fdn/FdnSetting.java
@@ -19,10 +19,12 @@
import android.app.ActionBar;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
+import android.content.Context;
import android.os.AsyncResult;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.os.UserManager;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
import android.util.Log;
diff --git a/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java b/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java
index d5ef816..3bfe1a4 100644
--- a/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java
+++ b/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java
@@ -46,6 +46,7 @@
private static final String PROVISION_STATUS_KEY = "ProvStatus";
private static final String SERVICE_FLOW_URL_KEY = "ServiceFlow_URL";
private static final String SERVICE_FLOW_USERDATA_KEY = "ServiceFlow_UserData";
+ private static final String SERVICE_FLOW_CONTENTS_TYPE_KEY = "ServiceFlow_ContentsType";
private static final String DEFAULT_EAP_AKA_RESPONSE = "Default EAP AKA response";
/**
* UUID to report an anomaly if an unexpected error is received during entitlement check.
@@ -120,13 +121,11 @@
}
try {
JSONObject jsonAuthResponse = new JSONObject(response);
- String entitlementStatus = null;
- String provisionStatus = null;
if (jsonAuthResponse.has(ServiceEntitlement.APP_DATA_PLAN_BOOST)) {
JSONObject jsonToken = jsonAuthResponse.getJSONObject(
ServiceEntitlement.APP_DATA_PLAN_BOOST);
if (jsonToken.has(ENTITLEMENT_STATUS_KEY)) {
- entitlementStatus = jsonToken.getString(ENTITLEMENT_STATUS_KEY);
+ String entitlementStatus = jsonToken.getString(ENTITLEMENT_STATUS_KEY);
if (entitlementStatus == null) {
return null;
}
@@ -134,7 +133,7 @@
Integer.parseInt(entitlementStatus);
}
if (jsonToken.has(PROVISION_STATUS_KEY)) {
- provisionStatus = jsonToken.getString(PROVISION_STATUS_KEY);
+ String provisionStatus = jsonToken.getString(PROVISION_STATUS_KEY);
if (provisionStatus != null) {
premiumNetworkEntitlementResponse.mProvisionStatus =
Integer.parseInt(provisionStatus);
@@ -148,6 +147,10 @@
premiumNetworkEntitlementResponse.mServiceFlowUserData =
jsonToken.getString(SERVICE_FLOW_USERDATA_KEY);
}
+ if (jsonToken.has(SERVICE_FLOW_CONTENTS_TYPE_KEY)) {
+ premiumNetworkEntitlementResponse.mServiceFlowContentsType =
+ jsonToken.getString(SERVICE_FLOW_CONTENTS_TYPE_KEY);
+ }
} else {
Log.e(TAG, "queryEntitlementStatus failed with no app");
}
diff --git a/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java b/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java
index ba44581..9126244 100644
--- a/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java
+++ b/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java
@@ -25,19 +25,19 @@
*
* The relationship between entitlement status (left column) and provision status (top row)
* is defined in the table below:
- * +--------------+-----------------+-------------------+-------------------+---------------+
- * | | Not Provisioned | Provisioned | Not Available | In Progress |
- * +--------------+-----------------+-------------------+-------------------+---------------+
- * | Disabled | Check failed | Check failed | Check failed | Check failed |
- * +--------------+-----------------+-------------------+-------------------+---------------+
- * | Enabled | Carrier error | Display webview | Display webview | Carrier error |
- * +--------------+-----------------+-------------------+-------------------+---------------+
- * | Incompatible | Check failed | Check failed | Check failed | Check failed |
- * +--------------+-----------------+-------------------+-------------------+---------------+
- * | Provisioning | Carrier error | Carrier error | In Progress | In Progress |
- * +--------------+-----------------+-------------------+-------------------+---------------+
- * | Included | Carrier error | Already purchased | Already purchased | Carrier error |
- * +--------------+-----------------+-------------------+-------------------+---------------+
+ * +--------------+-----------------+-------------------+-------------------+-----------------+
+ * | | Not Provisioned | Provisioned | Not Available | In Progress |
+ * +--------------+-----------------+-------------------+-------------------+-----------------+
+ * | Disabled | Check failed | Check failed | Check failed | Check failed |
+ * +--------------+-----------------+-------------------+-------------------+-----------------+
+ * | Enabled | Display webview | Already purchased | Already purchased | In progress |
+ * +--------------+-----------------+-------------------+-------------------+-----------------+
+ * | Incompatible | Check failed | Check failed | Check failed | Check failed |
+ * +--------------+-----------------+-------------------+-------------------+-----------------+
+ * | Provisioning | Carrier error | Carrier error | In progress | In progress |
+ * +--------------+-----------------+-------------------+-------------------+-----------------+
+ * | Included | Carrier error | Already purchased | Already purchased | Carrier error |
+ * +--------------+-----------------+-------------------+-------------------+-----------------+
*/
public class PremiumNetworkEntitlementResponse {
public static final int PREMIUM_NETWORK_ENTITLEMENT_STATUS_DISABLED = 0;
@@ -72,13 +72,19 @@
@PremiumNetworkProvisionStatus public int mProvisionStatus;
@NonNull public String mServiceFlowURL;
@NonNull public String mServiceFlowUserData;
+ @NonNull public String mServiceFlowContentsType;
/**
- * @return {@code true} if the premium network is provisioned and {@code false} otherwise.
+ * @return {@code true} if the premium network is already purchased and {@code false} otherwise.
*/
- public boolean isProvisioned() {
- return !isInvalidResponse()
- && mEntitlementStatus == PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCLUDED;
+ public boolean isAlreadyPurchased() {
+ switch (mEntitlementStatus) {
+ case PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED:
+ case PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCLUDED:
+ return mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_PROVISIONED
+ || mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_NOT_AVAILABLE;
+ }
+ return false;
}
/**
@@ -86,8 +92,14 @@
* {@code false} otherwise.
*/
public boolean isProvisioningInProgress() {
- return !isInvalidResponse()
- && mEntitlementStatus == PREMIUM_NETWORK_ENTITLEMENT_STATUS_PROVISIONING;
+ switch (mEntitlementStatus) {
+ case PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED:
+ return mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_IN_PROGRESS;
+ case PREMIUM_NETWORK_ENTITLEMENT_STATUS_PROVISIONING:
+ return mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_IN_PROGRESS
+ || mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_NOT_AVAILABLE;
+ }
+ return false;
}
/**
@@ -108,7 +120,6 @@
*/
public boolean isInvalidResponse() {
switch (mEntitlementStatus) {
- case PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED:
case PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCLUDED:
return mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_NOT_PROVISIONED
|| mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_IN_PROGRESS;
@@ -123,6 +134,7 @@
@NonNull public String toString() {
return "PremiumNetworkEntitlementResponse{mEntitlementStatus=" + mEntitlementStatus
+ ", mProvisionStatus=" + mProvisionStatus + ", mServiceFlowURL=" + mServiceFlowURL
- + ", mServiceFlowUserData" + mServiceFlowUserData + "}";
+ + ", mServiceFlowUserData=" + mServiceFlowUserData + ", mServiceFlowContentsType="
+ + mServiceFlowContentsType + "}";
}
}
diff --git a/src/com/android/phone/slice/SlicePurchaseController.java b/src/com/android/phone/slice/SlicePurchaseController.java
index b1abe56..9a42e16 100644
--- a/src/com/android/phone/slice/SlicePurchaseController.java
+++ b/src/com/android/phone/slice/SlicePurchaseController.java
@@ -48,6 +48,9 @@
import android.telephony.TelephonyManager;
import android.telephony.data.NetworkSliceInfo;
import android.telephony.data.NetworkSlicingConfig;
+import android.telephony.data.RouteSelectionDescriptor;
+import android.telephony.data.TrafficDescriptor;
+import android.telephony.data.UrspRule;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.URLUtil;
@@ -55,6 +58,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.FeatureFlags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -92,12 +96,15 @@
public static final int FAILURE_CODE_UNKNOWN = 0;
/** Performance boost purchase failed because the carrier URL is unavailable. */
public static final int FAILURE_CODE_CARRIER_URL_UNAVAILABLE = 1;
- /** Performance boost purchase failed because the server is unreachable. */
- public static final int FAILURE_CODE_SERVER_UNREACHABLE = 2;
/** Performance boost purchase failed because user authentication failed. */
- public static final int FAILURE_CODE_AUTHENTICATION_FAILED = 3;
+ public static final int FAILURE_CODE_AUTHENTICATION_FAILED = 2;
/** Performance boost purchase failed because the payment failed. */
- public static final int FAILURE_CODE_PAYMENT_FAILED = 4;
+ public static final int FAILURE_CODE_PAYMENT_FAILED = 3;
+ /**
+ * Performance boost purchase failed because the content type was specified but
+ * user data does not exist.
+ */
+ public static final int FAILURE_CODE_NO_USER_DATA = 4;
/**
* Failure codes that the carrier website can return when a premium capability purchase fails.
@@ -106,9 +113,9 @@
@IntDef(prefix = { "FAILURE_CODE_" }, value = {
FAILURE_CODE_UNKNOWN,
FAILURE_CODE_CARRIER_URL_UNAVAILABLE,
- FAILURE_CODE_SERVER_UNREACHABLE,
FAILURE_CODE_AUTHENTICATION_FAILED,
- FAILURE_CODE_PAYMENT_FAILED})
+ FAILURE_CODE_PAYMENT_FAILED,
+ FAILURE_CODE_NO_USER_DATA})
public @interface FailureCode {}
/** Value for an invalid premium capability. */
@@ -177,6 +184,12 @@
private static final String ACTION_SLICE_PURCHASE_APP_RESPONSE_NOT_DEFAULT_DATA_SUBSCRIPTION =
"com.android.phone.slice.action."
+ "SLICE_PURCHASE_APP_RESPONSE_NOT_DEFAULT_DATA_SUBSCRIPTION";
+ /**
+ * Action indicating the performance boost notification was not shown because the user
+ * disabled notifications for the application or channel.
+ */
+ private static final String ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED =
+ "com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED";
/** Action indicating the purchase request was successful. */
private static final String ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS =
"com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_SUCCESS";
@@ -209,6 +222,8 @@
public static final String EXTRA_CARRIER = "com.android.phone.slice.extra.CARRIER";
/** Extra for the user data received from the entitlement service to send to the webapp. */
public static final String EXTRA_USER_DATA = "com.android.phone.slice.extra.USER_DATA";
+ /** Extra for the contents type received from the entitlement service to send to the webapp. */
+ public static final String EXTRA_CONTENTS_TYPE = "com.android.phone.slice.extra.CONTENTS_TYPE";
/**
* Extra for the canceled PendingIntent that the slice purchase application can send as a
* response if the performance boost notification or WebView was canceled by the user.
@@ -242,6 +257,14 @@
public static final String EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION =
"com.android.phone.slice.extra.INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION";
/**
+ * Extra for the notifications disabled PendingIntent that the slice purchase application can
+ * send as a response if the premium capability purchase request failed because the user
+ * disabled notifications for the application or channel.
+ * Sends {@link #ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED}.
+ */
+ public static final String EXTRA_INTENT_NOTIFICATIONS_DISABLED =
+ "com.android.phone.slice.extra.INTENT_NOTIFICATIONS_DISABLED";
+ /**
* Extra for the success PendingIntent that the slice purchase application can send as a
* response if the premium capability purchase request was successful.
* Sends {@link #ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS}.
@@ -287,6 +310,8 @@
/** The Phone instance used to create the SlicePurchaseController. */
@NonNull private final Phone mPhone;
+ /** Feature flags to control behavior and errors. */
+ @NonNull private final FeatureFlags mFeatureFlags;
/** The set of capabilities that are pending network setup. */
@NonNull private final Set<Integer> mPendingSetupCapabilities = new HashSet<>();
/** The set of throttled capabilities. */
@@ -320,7 +345,7 @@
/**
* Create a SlicePurchaseControllerBroadcastReceiver for the given capability
*
- * @param capability The requested capability to listen to response for.
+ * @param capability The requested premium capability to listen to response for.
*/
SlicePurchaseControllerBroadcastReceiver(
@TelephonyManager.PremiumCapability int capability) {
@@ -391,6 +416,17 @@
false);
break;
}
+ case ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED: {
+ logd("Slice purchase application unable to show notification for capability: "
+ + TelephonyManager.convertPremiumCapabilityToString(capability)
+ + " because the user has disabled notifications.");
+ int error = mFeatureFlags.slicingAdditionalErrorCodes()
+ ? TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED
+ : TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED;
+ SlicePurchaseController.getInstance(phoneId)
+ .handlePurchaseResult(capability, error, true);
+ break;
+ }
case ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS: {
long duration = intent.getLongExtra(EXTRA_PURCHASE_DURATION, 0);
SlicePurchaseController.getInstance(phoneId).onCarrierSuccess(
@@ -417,14 +453,16 @@
* @param phone The Phone to get the SlicePurchaseController for.
* @return The static SlicePurchaseController instance.
*/
- @NonNull public static synchronized SlicePurchaseController getInstance(@NonNull Phone phone) {
+ @NonNull public static synchronized SlicePurchaseController getInstance(@NonNull Phone phone,
+ @NonNull FeatureFlags featureFlags) {
// TODO: Add listeners for multi sim setting changed (maybe carrier config changed too)
// that dismiss notifications and update SlicePurchaseController instance
int phoneId = phone.getPhoneId();
if (sInstances.get(phoneId) == null) {
HandlerThread handlerThread = new HandlerThread("SlicePurchaseController");
handlerThread.start();
- sInstances.put(phoneId, new SlicePurchaseController(phone, handlerThread.getLooper()));
+ sInstances.put(phoneId,
+ new SlicePurchaseController(phone, featureFlags, handlerThread.getLooper()));
}
return sInstances.get(phoneId);
}
@@ -444,18 +482,21 @@
* Create a SlicePurchaseController for the given phone on the given looper.
*
* @param phone The Phone to create the SlicePurchaseController for.
+ * @param featureFlags The FeatureFlags that are supported.
* @param looper The Looper to run the SlicePurchaseController on.
*/
@VisibleForTesting
- public SlicePurchaseController(@NonNull Phone phone, @NonNull Looper looper) {
+ public SlicePurchaseController(@NonNull Phone phone, @NonNull FeatureFlags featureFlags,
+ @NonNull Looper looper) {
super(looper);
mPhone = phone;
+ mFeatureFlags = featureFlags;
// TODO: Create a cached value for slicing config in DataIndication and initialize here
mPhone.mCi.registerForSlicingConfigChanged(this, EVENT_SLICING_CONFIG_CHANGED, null);
mIsSlicingUpsellEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_TELEPHONY, KEY_ENABLE_SLICING_UPSELL, false);
DeviceConfig.addOnPropertiesChangedListener(
- DeviceConfig.NAMESPACE_TELEPHONY, this::post,
+ DeviceConfig.NAMESPACE_TELEPHONY, Runnable::run,
properties -> {
if (TextUtils.equals(DeviceConfig.NAMESPACE_TELEPHONY,
properties.getNamespace())) {
@@ -475,6 +516,17 @@
mLocalDate = localDate;
}
+ /**
+ * Set the NetworkSlicingConfig to use for determining whether the premium capability was
+ * successfully set up on the carrier network.
+ *
+ * @param slicingConfig The LocalDate instance to use.
+ */
+ @VisibleForTesting
+ public void setSlicingConfig(@NonNull NetworkSlicingConfig slicingConfig) {
+ mSlicingConfig = slicingConfig;
+ }
+
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
@@ -488,7 +540,8 @@
case EVENT_SLICING_CONFIG_CHANGED: {
AsyncResult ar = (AsyncResult) msg.obj;
NetworkSlicingConfig config = (NetworkSlicingConfig) ar.result;
- logd("EVENT_SLICING_CONFIG_CHANGED: from " + mSlicingConfig + " to " + config);
+ logd("EVENT_SLICING_CONFIG_CHANGED: previous= " + mSlicingConfig);
+ logd("EVENT_SLICING_CONFIG_CHANGED: current= " + config);
mSlicingConfig = config;
onSlicingConfigChanged();
break;
@@ -690,6 +743,17 @@
private void onStartSlicePurchaseApplication(
@TelephonyManager.PremiumCapability int capability) {
+ updateNotificationCounts();
+ if (mMonthlyCount >= getCarrierConfigs().getInt(
+ CarrierConfigManager.KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT)
+ || mDailyCount >= getCarrierConfigs().getInt(
+ CarrierConfigManager.KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT)) {
+ logd("Reached maximum number of performance boost notifications.");
+ handlePurchaseResult(capability,
+ TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED, false);
+ return;
+ }
+
final PremiumNetworkEntitlementApi premiumNetworkEntitlementApi =
getPremiumNetworkEntitlementApi();
PremiumNetworkEntitlementResponse premiumNetworkEntitlementResponse =
@@ -711,8 +775,8 @@
return;
}
- if (premiumNetworkEntitlementResponse.isProvisioned()) {
- logd("Entitlement Check: Already provisioned.");
+ if (premiumNetworkEntitlementResponse.isAlreadyPurchased()) {
+ logd("Entitlement Check: Already purchased/provisioned.");
handlePurchaseResult(capability,
PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED, true);
return;
@@ -725,7 +789,6 @@
return;
}
- String userData = premiumNetworkEntitlementResponse.mServiceFlowUserData;
String purchaseUrl = getPurchaseUrl(premiumNetworkEntitlementResponse);
String carrier = getSimOperator();
if (TextUtils.isEmpty(purchaseUrl) || TextUtils.isEmpty(carrier)) {
@@ -734,17 +797,6 @@
return;
}
- updateNotificationCounts();
- if (mMonthlyCount >= getCarrierConfigs().getInt(
- CarrierConfigManager.KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT)
- || mDailyCount >= getCarrierConfigs().getInt(
- CarrierConfigManager.KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT)) {
- logd("Reached maximum number of performance boost notifications.");
- handlePurchaseResult(capability,
- TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED, false);
- return;
- }
-
// Start timeout for purchase completion.
long timeout = getCarrierConfigs().getLong(CarrierConfigManager
.KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG);
@@ -761,9 +813,9 @@
intent.putExtra(EXTRA_PREMIUM_CAPABILITY, capability);
intent.putExtra(EXTRA_PURCHASE_URL, purchaseUrl);
intent.putExtra(EXTRA_CARRIER, carrier);
- if (!TextUtils.isEmpty(userData)) {
- intent.putExtra(EXTRA_USER_DATA, userData);
- }
+ intent.putExtra(EXTRA_USER_DATA, premiumNetworkEntitlementResponse.mServiceFlowUserData);
+ intent.putExtra(EXTRA_CONTENTS_TYPE,
+ premiumNetworkEntitlementResponse.mServiceFlowContentsType);
intent.putExtra(EXTRA_INTENT_CANCELED, createPendingIntent(
ACTION_SLICE_PURCHASE_APP_RESPONSE_CANCELED, capability, false));
intent.putExtra(EXTRA_INTENT_CARRIER_ERROR, createPendingIntent(
@@ -773,6 +825,8 @@
intent.putExtra(EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION, createPendingIntent(
ACTION_SLICE_PURCHASE_APP_RESPONSE_NOT_DEFAULT_DATA_SUBSCRIPTION, capability,
false));
+ intent.putExtra(EXTRA_INTENT_NOTIFICATIONS_DISABLED, createPendingIntent(
+ ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED, capability, false));
intent.putExtra(EXTRA_INTENT_SUCCESS, createPendingIntent(
ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS, capability, true));
intent.putExtra(EXTRA_INTENT_NOTIFICATION_SHOWN, createPendingIntent(
@@ -788,6 +842,7 @@
filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_CARRIER_ERROR);
filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_REQUEST_FAILED);
filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_NOT_DEFAULT_DATA_SUBSCRIPTION);
+ filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED);
filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS);
filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN);
mPhone.getContext().registerReceiver(
@@ -977,7 +1032,8 @@
private long getThrottleDuration(@TelephonyManager.PurchasePremiumCapabilityResult int result) {
if (result == TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
- || result == TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT) {
+ || result == TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
+ || result == TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED) {
return getCarrierConfigs().getLong(CarrierConfigManager
.KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG);
}
@@ -1037,26 +1093,71 @@
return mPhone.getSubId() == SubscriptionManager.getDefaultDataSubscriptionId();
}
- private boolean isSlicingConfigActive(@TelephonyManager.PremiumCapability int capability) {
+ /**
+ * Check whether the current network slicing configuration indicates that the given premium
+ * capability is active and set up on the carrier network.
+ * @param capability The premium capability to check for.
+ * @return {@code true} if the slicing config indicates the capability is active and
+ * {@code false} otherwise.
+ */
+ @VisibleForTesting
+ public boolean isSlicingConfigActive(@TelephonyManager.PremiumCapability int capability) {
if (mSlicingConfig == null) {
return false;
}
- int capabilityServiceType = getSliceServiceType(capability);
- for (NetworkSliceInfo sliceInfo : mSlicingConfig.getSliceInfo()) {
- if (sliceInfo.getSliceServiceType() == capabilityServiceType
- && sliceInfo.getStatus() == NetworkSliceInfo.SLICE_STATUS_ALLOWED) {
- return true;
+ for (UrspRule urspRule : mSlicingConfig.getUrspRules()) {
+ for (TrafficDescriptor trafficDescriptor : urspRule.getTrafficDescriptors()) {
+ TrafficDescriptor.OsAppId osAppId =
+ new TrafficDescriptor.OsAppId(trafficDescriptor.getOsAppId());
+ if (osAppId.getAppId().equals(getAppId(capability))) {
+ for (RouteSelectionDescriptor rsd : urspRule.getRouteSelectionDescriptor()) {
+ for (NetworkSliceInfo sliceInfo : rsd.getSliceInfo()) {
+ if (sliceInfo.getStatus() == NetworkSliceInfo.SLICE_STATUS_ALLOWED
+ && getSliceServiceTypes(capability).contains(
+ sliceInfo.getSliceServiceType())) {
+ return true;
+ }
+ }
+ }
+ }
}
}
return false;
}
- @NetworkSliceInfo.SliceServiceType private int getSliceServiceType(
- @TelephonyManager.PremiumCapability int capability) {
+ /**
+ * Get the application ID associated with the given premium capability.
+ * The app ID is a field in {@link TrafficDescriptor} that helps match URSP rules to determine
+ * whether the premium capability was successfully set up on the carrier network.
+ * @param capability The premium capability to get the app ID for.
+ * @return The application ID associated with the premium capability.
+ */
+ @VisibleForTesting
+ @NonNull public static String getAppId(@TelephonyManager.PremiumCapability int capability) {
if (capability == TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY) {
- return NetworkSliceInfo.SLICE_SERVICE_TYPE_URLLC;
+ return "PRIORITIZE_LATENCY";
}
- return NetworkSliceInfo.SLICE_SERVICE_TYPE_NONE;
+ return "";
+ }
+
+ /**
+ * Get the slice service types associated with the given premium capability.
+ * The slice service type is a field in {@link NetworkSliceInfo} that helps to match determine
+ * whether the premium capability was successfully set up on the carrier network.
+ * @param capability The premium capability to get the associated slice service types for.
+ * @return A set of slice service types associated with the premium capability.
+ */
+ @VisibleForTesting
+ @NonNull @NetworkSliceInfo.SliceServiceType public static Set<Integer> getSliceServiceTypes(
+ @TelephonyManager.PremiumCapability int capability) {
+ Set<Integer> sliceServiceTypes = new HashSet<>();
+ if (capability == TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY) {
+ sliceServiceTypes.add(NetworkSliceInfo.SLICE_SERVICE_TYPE_EMBB);
+ sliceServiceTypes.add(NetworkSliceInfo.SLICE_SERVICE_TYPE_URLLC);
+ } else {
+ sliceServiceTypes.add(NetworkSliceInfo.SLICE_SERVICE_TYPE_NONE);
+ }
+ return sliceServiceTypes;
}
private boolean isNetworkAvailable() {
@@ -1094,9 +1195,9 @@
switch (failureCode) {
case FAILURE_CODE_UNKNOWN: return "UNKNOWN";
case FAILURE_CODE_CARRIER_URL_UNAVAILABLE: return "CARRIER_URL_UNAVAILABLE";
- case FAILURE_CODE_SERVER_UNREACHABLE: return "SERVER_UNREACHABLE";
case FAILURE_CODE_AUTHENTICATION_FAILED: return "AUTHENTICATION_FAILED";
case FAILURE_CODE_PAYMENT_FAILED: return "PAYMENT_FAILED";
+ case FAILURE_CODE_NO_USER_DATA: return "NO_USER_DATA";
default:
return "UNKNOWN(" + failureCode + ")";
}
diff --git a/src/com/android/phone/utils/CarrierAllowListInfo.java b/src/com/android/phone/utils/CarrierAllowListInfo.java
index 8e22cb9..62b71ff 100644
--- a/src/com/android/phone/utils/CarrierAllowListInfo.java
+++ b/src/com/android/phone/utils/CarrierAllowListInfo.java
@@ -37,6 +37,7 @@
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -47,7 +48,7 @@
private static final String JSON_CHARSET = "UTF-8";
private static final String MESSAGE_DIGEST_ALGORITHM = "SHA1";
private static final String CALLER_SHA_1_ID = "callerSHA1Id";
- private static final String CALLER_CARRIER_ID = "carrierId";
+ private static final String CALLER_CARRIER_ID = "carrierIds";
public static final int INVALID_CARRIER_ID = -1;
private static final String CARRIER_RESTRICTION_OPERATOR_REGISTERED_FILE =
@@ -68,11 +69,12 @@
return mInstance;
}
- public int validateCallerAndGetCarrierId(String packageName) {
+ public Set<Integer> validateCallerAndGetCarrierIds(String packageName) {
CarrierInfo carrierInfo = parseJsonForCallerInfo(packageName);
boolean isValid = (carrierInfo != null) && validateCallerSignature(mContext, packageName,
carrierInfo.getSHAIdList());
- return (isValid) ? carrierInfo.getCallerCarrierId() : INVALID_CARRIER_ID;
+ return (isValid) ? carrierInfo.getCallerCarrierIdList() : Collections.singleton(
+ INVALID_CARRIER_ID);
}
private void loadJsonFile(Context context) {
@@ -95,12 +97,18 @@
if (mDataJSON != null && callerPackage != null) {
JSONObject callerJSON = mDataJSON.getJSONObject(callerPackage.trim());
JSONArray callerJSONArray = callerJSON.getJSONArray(CALLER_SHA_1_ID);
- int carrierId = callerJSON.getInt(CALLER_CARRIER_ID);
+ JSONArray carrierIdArray = callerJSON.getJSONArray(CALLER_CARRIER_ID);
+
+ Set<Integer> carrierIds = new HashSet<>();
+ for (int index = 0; index < carrierIdArray.length(); index++) {
+ carrierIds.add(carrierIdArray.getInt(index));
+ }
+
List<String> appSignatures = new ArrayList<>();
for (int index = 0; index < callerJSONArray.length(); index++) {
appSignatures.add((String) callerJSONArray.get(index));
}
- return new CarrierInfo(carrierId, appSignatures);
+ return new CarrierInfo(carrierIds, appSignatures);
}
} catch (JSONException ex) {
Rlog.e(LOG_TAG, "getCallerSignatureInfo: JSONException = " + ex);
@@ -183,16 +191,16 @@
}
private static class CarrierInfo {
- final private int mCallerCarrierId;
+ final private Set<Integer> mCallerCarrierIdList;
final private List<String> mSHAIdList;
- public CarrierInfo(int carrierId, List<String> SHAIds) {
- mCallerCarrierId = carrierId;
+ public CarrierInfo(Set<Integer> carrierIds, List<String> SHAIds) {
+ mCallerCarrierIdList = carrierIds;
mSHAIdList = SHAIds;
}
- public int getCallerCarrierId() {
- return mCallerCarrierId;
+ public Set<Integer> getCallerCarrierIdList() {
+ return mCallerCarrierIdList;
}
public List<String> getSHAIdList() {
@@ -203,7 +211,7 @@
@TestApi
public List<String> getShaIdList(String srcPkg, int carrierId) {
CarrierInfo carrierInfo = parseJsonForCallerInfo(srcPkg);
- if (carrierInfo != null && carrierInfo.getCallerCarrierId() == carrierId) {
+ if (carrierInfo != null && carrierInfo.getCallerCarrierIdList().contains(carrierId)) {
return carrierInfo.getSHAIdList();
}
Rlog.e(LOG_TAG, "getShaIdList carrierId or shaIdList is empty");
diff --git a/src/com/android/services/telephony/DisconnectCauseUtil.java b/src/com/android/services/telephony/DisconnectCauseUtil.java
index d36f8be..d1961fd 100644
--- a/src/com/android/services/telephony/DisconnectCauseUtil.java
+++ b/src/com/android/services/telephony/DisconnectCauseUtil.java
@@ -23,15 +23,18 @@
import android.telecom.DisconnectCause;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import android.telephony.ims.ImsReasonInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.CallFailCause;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.FeatureFlagsImpl;
import com.android.phone.ImsUtil;
import com.android.phone.PhoneGlobals;
-import com.android.phone.common.R;
+import com.android.phone.R;
public class DisconnectCauseUtil {
@@ -72,7 +75,7 @@
public static DisconnectCause toTelecomDisconnectCause(
int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason) {
return toTelecomDisconnectCause(telephonyDisconnectCause, telephonyPreciseDisconnectCause,
- reason, SubscriptionManager.getDefaultVoicePhoneId(), null);
+ reason, SubscriptionManager.getDefaultVoicePhoneId(), null, new FlagsAdapterImpl());
}
/**
@@ -86,7 +89,7 @@
public static DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause,
String reason, int phoneId) {
return toTelecomDisconnectCause(telephonyDisconnectCause, CallFailCause.NOT_VALID,
- reason, phoneId, null);
+ reason, phoneId, null, new FlagsAdapterImpl(), false);
}
/**
@@ -101,9 +104,29 @@
*/
public static DisconnectCause toTelecomDisconnectCause(
int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason,
- int phoneId, ImsReasonInfo imsReasonInfo) {
+ int phoneId, ImsReasonInfo imsReasonInfo, FlagsAdapter featureFlags) {
return toTelecomDisconnectCause(telephonyDisconnectCause, telephonyPreciseDisconnectCause,
- reason, phoneId, imsReasonInfo, getCarrierConfigBundle(phoneId));
+ reason, phoneId, imsReasonInfo, getCarrierConfigBundle(phoneId), featureFlags,
+ false);
+ }
+
+ /**
+ * Converts from a disconnect code in {@link android.telephony.DisconnectCause} into a more
+ * generic {@link android.telecom.DisconnectCause}.object, possibly populated with a localized
+ * message and tone for Slot.
+ * @param telephonyDisconnectCause The code for the reason for the disconnect.
+ * @param telephonyPreciseDisconnectCause The code for the precise reason for the disconnect.
+ * @param reason Description of the reason for the disconnect, not intended for the user to see.
+ * @param phoneId To support localized message based on phoneId
+ * @param imsReasonInfo
+ */
+ public static DisconnectCause toTelecomDisconnectCause(
+ int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason,
+ int phoneId, ImsReasonInfo imsReasonInfo, FlagsAdapter featureFlags,
+ boolean shouldTreatAsEmergency) {
+ return toTelecomDisconnectCause(telephonyDisconnectCause, telephonyPreciseDisconnectCause,
+ reason, phoneId, imsReasonInfo, getCarrierConfigBundle(phoneId), featureFlags,
+ shouldTreatAsEmergency);
}
/**
@@ -115,19 +138,24 @@
@VisibleForTesting
static DisconnectCause toTelecomDisconnectCause(
int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason,
- int phoneId, ImsReasonInfo imsReasonInfo, PersistableBundle carrierConfig) {
+ int phoneId, ImsReasonInfo imsReasonInfo, PersistableBundle carrierConfig,
+ FlagsAdapter featureFlags, boolean shouldTreatAsEmergency) {
Context context = PhoneGlobals.getInstance();
- return new DisconnectCause(
- toTelecomDisconnectCauseCode(telephonyDisconnectCause, carrierConfig),
- toTelecomDisconnectCauseLabel(context, telephonyDisconnectCause,
- telephonyPreciseDisconnectCause, carrierConfig),
- toTelecomDisconnectCauseDescription(context, telephonyDisconnectCause, phoneId),
- toTelecomDisconnectReason(context, telephonyDisconnectCause, reason, phoneId),
- toTelecomDisconnectCauseTone(telephonyDisconnectCause, carrierConfig),
- telephonyDisconnectCause,
- telephonyPreciseDisconnectCause,
- imsReasonInfo);
+ return new DisconnectCause.Builder(
+ toTelecomDisconnectCauseCode(telephonyDisconnectCause, carrierConfig))
+ .setLabel(toTelecomDisconnectCauseLabel(context, telephonyDisconnectCause,
+ telephonyPreciseDisconnectCause, carrierConfig, featureFlags))
+ .setDescription(toTelecomDisconnectCauseDescription(
+ context, telephonyDisconnectCause, phoneId, shouldTreatAsEmergency))
+ .setReason(toTelecomDisconnectReason(
+ context, telephonyDisconnectCause, reason, phoneId))
+ .setTone(toTelecomDisconnectCauseTone(
+ telephonyDisconnectCause, carrierConfig, featureFlags))
+ .setTelephonyDisconnectCause(telephonyDisconnectCause)
+ .setTelephonyPreciseDisconnectCause(telephonyPreciseDisconnectCause)
+ .setImsReasonInfo(imsReasonInfo)
+ .build();
}
/**
@@ -135,8 +163,8 @@
* {@link android.telecom.DisconnectCause} disconnect code.
* @return The disconnect code as defined in {@link android.telecom.DisconnectCause}.
*/
- private static int toTelecomDisconnectCauseCode(int telephonyDisconnectCause,
- PersistableBundle carrierConfig) {
+ private static @DisconnectCause.DisconnectCauseCode int toTelecomDisconnectCauseCode(
+ int telephonyDisconnectCause, PersistableBundle carrierConfig) {
// special case: some carriers determine what disconnect causes play the BUSY tone.
// hence, must adjust the disconnectCause CODE to match the tone.
@@ -264,20 +292,29 @@
*/
private static CharSequence toTelecomDisconnectCauseLabel(
Context context, int telephonyDisconnectCause, int telephonyPreciseDisconnectCause,
- PersistableBundle carrierConfig) {
+ PersistableBundle carrierConfig, FlagsAdapter featureFlags) {
CharSequence label;
-
- // special case: some carriers determine what disconnect causes play the BUSY tone.
- // hence, must adjust the disconnectCause LABEL to match the tone.
- if (doesCarrierClassifyDisconnectCauseAsBusyCause(telephonyDisconnectCause,
- carrierConfig)) {
- return context.getResources().getString(R.string.callFailed_userBusy);
+ if (!featureFlags.doNotOverridePreciseLabel()) {
+ // special case: some carriers determine what disconnect causes play the BUSY tone.
+ // hence, must adjust the disconnectCause LABEL to match the tone.
+ if (doesCarrierClassifyDisconnectCauseAsBusyCause(telephonyDisconnectCause,
+ carrierConfig)) {
+ return context.getResources().getString(R.string.callFailed_userBusy);
+ }
}
if (telephonyPreciseDisconnectCause != CallFailCause.NOT_VALID) {
label = getLabelFromPreciseDisconnectCause(context, telephonyPreciseDisconnectCause,
telephonyDisconnectCause);
} else {
+ if (featureFlags.doNotOverridePreciseLabel()) {
+ // special case: some carriers determine what disconnect causes play the BUSY tone.
+ // hence, must adjust the disconnectCause LABEL to match the tone.
+ if (doesCarrierClassifyDisconnectCauseAsBusyCause(telephonyDisconnectCause,
+ carrierConfig)) {
+ return context.getResources().getString(R.string.callFailed_userBusy);
+ }
+ }
label = getLabelFromDisconnectCause(context, telephonyDisconnectCause);
}
return label;
@@ -619,7 +656,8 @@
* Returns a description of the disconnect cause to be shown to the user.
*/
private static CharSequence toTelecomDisconnectCauseDescription(
- Context context, int telephonyDisconnectCause, int phoneId) {
+ Context context, int telephonyDisconnectCause, int phoneId,
+ boolean shouldTreatAsEmergency) {
if (context == null ) {
return "";
}
@@ -742,14 +780,31 @@
case android.telephony.DisconnectCause.OUT_OF_SERVICE:
// No network connection.
+ FeatureFlags mFeatureFlags = new FeatureFlagsImpl();
if (ImsUtil.shouldPromoteWfc(context, phoneId)) {
resourceId = R.string.incall_error_promote_wfc;
} else if (ImsUtil.isWfcModeWifiOnly(context, phoneId)) {
resourceId = R.string.incall_error_wfc_only_no_wireless_network;
} else if (ImsUtil.isWfcEnabled(context, phoneId)) {
- resourceId = R.string.incall_error_out_of_service_wfc;
+ if (!mFeatureFlags.showCallFailNotificationFor2gToggle()) {
+ resourceId = R.string.incall_error_out_of_service_wfc;
+ break;
+ }
+ if (is2gDisabled(phoneId) && !shouldTreatAsEmergency) {
+ resourceId = R.string.incall_error_out_of_service_wfc_2g_user;
+ } else {
+ resourceId = R.string.incall_error_out_of_service_wfc;
+ }
} else {
- resourceId = R.string.incall_error_out_of_service;
+ if (!mFeatureFlags.showCallFailNotificationFor2gToggle()) {
+ resourceId = R.string.incall_error_out_of_service;
+ break;
+ }
+ if (is2gDisabled(phoneId) && !shouldTreatAsEmergency) {
+ resourceId = R.string.incall_error_out_of_service_2g;
+ } else {
+ resourceId = R.string.incall_error_out_of_service;
+ }
}
break;
@@ -889,7 +944,7 @@
* Returns the tone to play for the disconnect cause, or UNKNOWN if none should be played.
*/
private static int toTelecomDisconnectCauseTone(int telephonyDisconnectCause,
- PersistableBundle carrierConfig) {
+ PersistableBundle carrierConfig, FlagsAdapter featureFlags) {
// special case: some carriers determine what disconnect causes play the BUSY tone.
if (doesCarrierClassifyDisconnectCauseAsBusyCause(telephonyDisconnectCause,
@@ -898,6 +953,10 @@
}
switch (telephonyDisconnectCause) {
+ case android.telephony.DisconnectCause.BUSY:
+ if (featureFlags.doNotOverridePreciseLabel()) {
+ return ToneGenerator.TONE_SUP_BUSY;
+ }
case android.telephony.DisconnectCause.CONGESTION:
return ToneGenerator.TONE_SUP_CONGESTION;
@@ -932,14 +991,21 @@
/**
* Helper method that examines the carrierConfig KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY
* containing the DisconnectCauses that are classified as DisconnectCause.BUSY
- * @param telephonyDisconnectCause
+ *
* @param carrierConfig object that holds all the carrier specific settings
* @return whether the cause is in the carrier config busy tone array
*/
- private static boolean doesCarrierClassifyDisconnectCauseAsBusyCause(
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static boolean doesCarrierClassifyDisconnectCauseAsBusyCause(
int telephonyDisconnectCause, PersistableBundle carrierConfig) {
+ if (carrierConfig == null) {
+ return false;
+ }
int[] busyToneArray = carrierConfig.getIntArray(
CarrierConfigManager.KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY);
+ if (busyToneArray == null) {
+ return false;
+ }
for (int busyTone : busyToneArray) {
if (busyTone == telephonyDisconnectCause) {
return true;
@@ -961,4 +1027,18 @@
return config;
}
+ /**
+ * Returns true if 2G is disabled.
+ */
+ protected static boolean is2gDisabled(int phoneId) {
+ Phone phone = PhoneFactory.getPhone(phoneId);
+ if (phone == null) {
+ return false;
+ }
+ long bitmask2g = TelephonyManager.NETWORK_CLASS_BITMASK_2G;
+ long currentlyAllowedNetworkTypes = phone.getAllowedNetworkTypes(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
+ boolean is2gEnabled = (currentlyAllowedNetworkTypes & bitmask2g) != 0;
+ return !is2gEnabled;
+ }
}
diff --git a/src/com/android/services/telephony/FlagsAdapter.java b/src/com/android/services/telephony/FlagsAdapter.java
new file mode 100644
index 0000000..fdf00a5
--- /dev/null
+++ b/src/com/android/services/telephony/FlagsAdapter.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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;
+
+/**
+ * FlagsAdapter can assist in testing flags that are "Fixed Read Only Flags"
+ * (is_fixed_read_only: true)
+ */
+public interface FlagsAdapter {
+ boolean doNotOverridePreciseLabel();
+}
diff --git a/src/com/android/services/telephony/FlagsAdapterImpl.java b/src/com/android/services/telephony/FlagsAdapterImpl.java
new file mode 100644
index 0000000..c935c59
--- /dev/null
+++ b/src/com/android/services/telephony/FlagsAdapterImpl.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 com.android.internal.telephony.flags.Flags;
+
+/**
+ * FlagsAdapterImpl should always be used in production when Telephony is checking a flag status.
+ * To help with testing, it may be necessary to have a different implementation
+ * (e.g. flag is read only).
+ */
+public class FlagsAdapterImpl implements FlagsAdapter {
+ public boolean doNotOverridePreciseLabel() {
+ return Flags.doNotOverridePreciseLabel();
+ }
+}
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 755c85f..7f0c800 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -38,6 +38,7 @@
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.flags.Flags;
import com.android.phone.PhoneUtils;
import com.android.phone.R;
import com.android.telephony.Rlog;
@@ -700,7 +701,11 @@
if (mConferenceHost == null) {
return;
}
- mConferenceHost.performHold();
+ if (Flags.conferenceHoldUnholdChangedToSendMessage()) {
+ mConferenceHost.onHold();
+ } else {
+ mConferenceHost.performHold();
+ }
}
/**
@@ -711,7 +716,11 @@
if (mConferenceHost == null) {
return;
}
- mConferenceHost.performUnhold();
+ if (Flags.conferenceHoldUnholdChangedToSendMessage()) {
+ mConferenceHost.onUnhold();
+ } else {
+ mConferenceHost.performUnhold();
+ }
}
/**
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 2b69b82..064c69b 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -16,6 +16,7 @@
package com.android.services.telephony;
+import android.app.ActivityManager;
import android.app.PropertyInvalidatedCache;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -63,6 +64,8 @@
import com.android.internal.telephony.ExponentialBackoff;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.SimultaneousCallingTracker;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.subscription.SubscriptionManagerService;
import com.android.phone.PhoneGlobals;
import com.android.phone.PhoneUtils;
@@ -70,11 +73,16 @@
import com.android.telephony.Rlog;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
/**
* Owns all data we have registered with Telecom including handling dynamic addition and
@@ -121,6 +129,7 @@
final class AccountEntry implements PstnPhoneCapabilitiesNotifier.Listener {
private final Phone mPhone;
private PhoneAccount mAccount;
+ private SimultaneousCallingTracker mSCT;
private final PstnIncomingCallNotifier mIncomingCallNotifier;
private final PstnPhoneCapabilitiesNotifier mPhoneCapabilitiesNotifier;
private boolean mIsEmergency;
@@ -131,6 +140,7 @@
private MmTelFeature.MmTelCapabilities mMmTelCapabilities;
private ImsMmTelManager.CapabilityCallback mMmtelCapabilityCallback;
private RegistrationManager.RegistrationCallback mImsRegistrationCallback;
+ private SimultaneousCallingTracker.Listener mSimultaneousCallingTrackerListener;
private ImsMmTelManager mMmTelManager;
private final boolean mIsTestAccount;
private boolean mIsVideoCapable;
@@ -143,12 +153,18 @@
private boolean mIsManageImsConferenceCallSupported;
private boolean mIsUsingSimCallManager;
private boolean mIsShowPreciseFailedCause;
+ private Set<Integer> mSimultaneousCallSupportedSubIds;
AccountEntry(Phone phone, boolean isEmergency, boolean isTest) {
mPhone = phone;
mIsEmergency = isEmergency;
mIsTestAccount = isTest;
mIsAdhocConfCapable = mPhone.isImsRegistered();
+ if (Flags.simultaneousCallingIndications()) {
+ mSCT = SimultaneousCallingTracker.getInstance();
+ mSimultaneousCallSupportedSubIds =
+ mSCT.getSubIdsSupportingSimultaneousCalling(mPhone.getSubId());
+ }
mAccount = registerPstnPhoneAccount(isEmergency, isTest);
Log.i(this, "Registered phoneAccount: %s with handle: %s",
mAccount, mAccount.getAccountHandle());
@@ -201,6 +217,21 @@
}
};
registerImsRegistrationCallback();
+
+ if (Flags.simultaneousCallingIndications()) {
+ //Register SimultaneousCallingTracker listener:
+ mSimultaneousCallingTrackerListener = new SimultaneousCallingTracker.Listener() {
+ @Override
+ public void onSimultaneousCallingSupportChanged(Map<Integer,
+ Set<Integer>> simultaneousCallSubSupportMap) {
+ updateSimultaneousCallSubSupportMap(simultaneousCallSubSupportMap);
+ }
+ };
+ SimultaneousCallingTracker.getInstance()
+ .addListener(mSimultaneousCallingTrackerListener);
+ Log.d(LOG_TAG, "Finished registering mSimultaneousCallingTrackerListener for "
+ + "phoneId = " + mPhone.getPhoneId() + "; subId = " + mPhone.getSubId());
+ }
}
void teardown() {
@@ -215,6 +246,10 @@
mMmTelManager.unregisterImsRegistrationCallback(mImsRegistrationCallback);
}
}
+ if (Flags.simultaneousCallingIndications()) {
+ SimultaneousCallingTracker.getInstance()
+ .removeListener(mSimultaneousCallingTrackerListener);
+ }
}
private void registerMmTelCapabilityCallback() {
@@ -464,6 +499,15 @@
mIsUsingSimCallManager = isCarrierUsingSimCallManager();
mIsShowPreciseFailedCause = isCarrierShowPreciseFailedCause();
+ // Set CAPABILITY_EMERGENCY_CALLS_ONLY flag if either
+ // - Carrier config overrides subscription is not voice capable, or
+ // - Resource config overrides it be emergency_calls_only
+ // TODO(b/316183370:): merge the two cases when clearing up flag
+ if (Flags.dataOnlyServiceAllowEmergencyCallOnly()) {
+ if (!isSubscriptionVoiceCapableByCarrierConfig()) {
+ capabilities |= PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY;
+ }
+ }
if (isEmergency && mContext.getResources().getBoolean(
R.bool.config_emergency_account_emergency_calls_only)) {
capabilities |= PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY;
@@ -503,7 +547,7 @@
Log.i(this, "Adding Merged Account with group: " + Rlog.pii(LOG_TAG, groupId));
}
- PhoneAccount account = PhoneAccount.builder(phoneAccountHandle, label)
+ PhoneAccount.Builder accountBuilder = PhoneAccount.builder(phoneAccountHandle, label)
.setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, line1Number, null))
.setSubscriptionAddress(
Uri.fromParts(PhoneAccount.SCHEME_TEL, subNumber, null))
@@ -514,10 +558,19 @@
.setSupportedUriSchemes(Arrays.asList(
PhoneAccount.SCHEME_TEL, PhoneAccount.SCHEME_VOICEMAIL))
.setExtras(extras)
- .setGroupId(groupId)
- .build();
+ .setGroupId(groupId);
- return account;
+ if (Flags.simultaneousCallingIndications()) {
+ Set <PhoneAccountHandle> simultaneousCallingHandles =
+ mSimultaneousCallSupportedSubIds.stream()
+ .map(subscriptionId -> PhoneUtils.makePstnPhoneAccountHandleWithId(
+ String.valueOf(subscriptionId), userToRegister))
+ .collect(Collectors.toSet());
+ accountBuilder.setSimultaneousCallingRestriction(simultaneousCallingHandles);
+ }
+
+
+ return accountBuilder.build();
}
public PhoneAccountHandle getPhoneAccountHandle() {
@@ -803,6 +856,21 @@
}
/**
+ * @return true if the subscription is voice capable by the carrier config.
+ */
+ private boolean isSubscriptionVoiceCapableByCarrierConfig() {
+ PersistableBundle b =
+ PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
+ if (b == null) {
+ return true; // For any abnormal case, we assume subscription is voice capable
+ }
+ final int[] serviceCapabilities = b.getIntArray(
+ CarrierConfigManager.KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY);
+ return Arrays.stream(serviceCapabilities).anyMatch(
+ i -> i == SubscriptionManager.SERVICE_CAPABILITY_VOICE);
+ }
+
+ /**
* Receives callback from {@link PstnPhoneCapabilitiesNotifier} when the video capabilities
* have changed.
*
@@ -823,6 +891,30 @@
}
}
+ public void updateSimultaneousCallSubSupportMap(Map<Integer,
+ Set<Integer>> simultaneousCallSubSupportMap) {
+ if (!Flags.simultaneousCallingIndications()) { return; }
+ //Check if the simultaneous call support subIds for this account have changed:
+ Set<Integer> updatedSimultaneousCallSupportSubIds = new HashSet<>(3);
+ updatedSimultaneousCallSupportSubIds.addAll(
+ simultaneousCallSubSupportMap.get(mPhone.getSubId()));
+ if (!updatedSimultaneousCallSupportSubIds.equals(mSimultaneousCallSupportedSubIds)) {
+ //If necessary, update cache and re-register mAccount:
+ mSimultaneousCallSupportedSubIds = updatedSimultaneousCallSupportSubIds;
+ synchronized (mAccountsLock) {
+ if (!mAccounts.contains(this)) {
+ // Account has already been torn down, don't try to register it again.
+ // This handles the case where teardown has already happened, and we got a
+ // simultaneous calling support update that lost the race for the
+ // mAccountsLock. In such a scenario by the time we get here, the original
+ // phone account could have been torn down.
+ return;
+ }
+ mAccount = registerPstnPhoneAccount(mIsEmergency, mIsTestAccount);
+ }
+ }
+ }
+
public void updateAdhocConfCapability(boolean isAdhocConfCapable) {
synchronized (mAccountsLock) {
if (!mAccounts.contains(this)) {
@@ -1191,7 +1283,7 @@
private int mSubscriptionListenerState = LISTENER_STATE_UNREGISTERED;
private int mServiceState = ServiceState.STATE_POWER_OFF;
private int mActiveDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- private boolean mIsPrimaryUser = true;
+ private boolean mIsPrimaryUser = UserHandle.of(ActivityManager.getCurrentUser()).isSystem();
private ExponentialBackoff mRegisterSubscriptionListenerBackoff;
private final HandlerThread mHandlerThread = new HandlerThread("TelecomAccountRegistry");
@@ -1232,7 +1324,18 @@
*/
public static synchronized TelecomAccountRegistry getInstance(Context context) {
if (sInstance == null && context != null) {
- sInstance = new TelecomAccountRegistry(context);
+ if (Flags.enforceTelephonyFeatureMappingForPublicApis()) {
+ PackageManager pm = context.getPackageManager();
+ if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+ && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING)) {
+ sInstance = new TelecomAccountRegistry(context);
+ } else {
+ Log.d(LOG_TAG, "Not initializing TelecomAccountRegistry: "
+ + "missing telephony/calling feature(s)");
+ }
+ } else {
+ sInstance = new TelecomAccountRegistry(context);
+ }
}
return sInstance;
}
@@ -1598,6 +1701,35 @@
new AccountEntry(PhoneFactory.getDefaultPhone(), true /* emergency */,
false /* isTest */));
}
+
+ // In some very rare cases, when setting the default voice sub in
+ // SubscriptionManagerService, the phone accounts here have not yet been built.
+ // So calling setUserSelectedOutgoingPhoneAccount in SubscriptionManagerService
+ // becomes a no-op. The workaround here is to reconcile and make sure the
+ // outgoing phone account is properly set in telecom.
+ int defaultVoiceSubId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+ if (SubscriptionManager.isValidSubscriptionId(defaultVoiceSubId)) {
+ PhoneAccountHandle defaultVoiceAccountHandle =
+ getPhoneAccountHandleForSubId(defaultVoiceSubId);
+ if (defaultVoiceAccountHandle != null) {
+ PhoneAccountHandle currentAccount = mTelecomManager
+ .getUserSelectedOutgoingPhoneAccount();
+ // In some rare cases, the current phone account could be non-telephony
+ // phone account. We do not override in this case.
+ boolean wasPreviousAccountSameComponentOrUnset = currentAccount == null
+ || Objects.equals(defaultVoiceAccountHandle.getComponentName(),
+ currentAccount.getComponentName());
+
+ // Set the phone account again if it's out-of-sync.
+ if (!defaultVoiceAccountHandle.equals(currentAccount)
+ && wasPreviousAccountSameComponentOrUnset) {
+ Log.d(this, "setupAccounts: Re-setup phone account "
+ + "again for default voice sub " + defaultVoiceSubId);
+ mTelecomManager.setUserSelectedOutgoingPhoneAccount(
+ defaultVoiceAccountHandle);
+ }
+ }
+ }
}
// Add a fake account entry.
diff --git a/src/com/android/services/telephony/TelephonyConference.java b/src/com/android/services/telephony/TelephonyConference.java
index 7e4693f..4a70e1c 100644
--- a/src/com/android/services/telephony/TelephonyConference.java
+++ b/src/com/android/services/telephony/TelephonyConference.java
@@ -23,6 +23,7 @@
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.Flags;
import java.util.List;
@@ -128,7 +129,11 @@
public void onHold() {
final TelephonyConnection connection = getFirstConnection();
if (connection != null) {
- connection.performHold();
+ if (Flags.conferenceHoldUnholdChangedToSendMessage()) {
+ connection.onHold();
+ } else {
+ connection.performHold();
+ }
}
}
@@ -139,7 +144,11 @@
public void onUnhold() {
final TelephonyConnection connection = getFirstConnection();
if (connection != null) {
- connection.performUnhold();
+ if (Flags.conferenceHoldUnholdChangedToSendMessage()) {
+ connection.onUnhold();
+ } else {
+ connection.performUnhold();
+ }
}
}
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 6d136b0..5bfad6b 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -83,6 +83,7 @@
import com.android.internal.telephony.d2d.RtpTransport;
import com.android.internal.telephony.d2d.Timeouts;
import com.android.internal.telephony.d2d.TransportProtocol;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.gsm.SuppServiceNotification;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneCall;
@@ -149,6 +150,8 @@
private static final int MSG_DTMF_DONE = 22;
private static final int MSG_MEDIA_ATTRIBUTES_CHANGED = 23;
private static final int MSG_ON_RTT_INITIATED = 24;
+ private static final int MSG_HOLD = 25;
+ private static final int MSG_UNHOLD = 26;
private static final String JAPAN_COUNTRY_CODE_WITH_PLUS_SIGN = "+81";
private static final String JAPAN_ISO_COUNTRY_CODE = "JP";
@@ -344,6 +347,12 @@
}
sendRttInitiationSuccess();
break;
+ case MSG_HOLD:
+ performHold();
+ break;
+ case MSG_UNHOLD:
+ performUnhold();
+ break;
}
}
};
@@ -955,7 +964,7 @@
private Integer mEmergencyServiceCategory = null;
protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection,
- String callId, @android.telecom.Call.Details.CallDirection int callDirection) {
+ String callId, int callDirection) {
setCallDirection(callDirection);
setTelecomCallId(callId);
if (originalConnection != null) {
@@ -1007,23 +1016,6 @@
mHandler.obtainMessage(MSG_HANGUP, android.telephony.DisconnectCause.LOCAL).sendToTarget();
}
- /**
- * Notifies this Connection of a request to disconnect a participant of the conference managed
- * by the connection.
- *
- * @param endpoint the {@link Uri} of the participant to disconnect.
- */
- @Override
- public void onDisconnectConferenceParticipant(Uri endpoint) {
- Log.v(this, "onDisconnectConferenceParticipant %s", endpoint);
-
- if (mOriginalConnection == null) {
- return;
- }
-
- mOriginalConnection.onDisconnectConferenceParticipant(endpoint);
- }
-
@Override
public void onSeparate() {
Log.v(this, "onSeparate");
@@ -1049,12 +1041,12 @@
@Override
public void onHold() {
- performHold();
+ mHandler.obtainMessage(MSG_HOLD).sendToTarget();
}
@Override
public void onUnhold() {
- performUnhold();
+ mHandler.obtainMessage(MSG_UNHOLD).sendToTarget();
}
@Override
@@ -1296,12 +1288,22 @@
originalConnection.sendRttModifyResponse(textStream);
}
+ private boolean answeringDropsFgCalls() {
+ if (Flags.callExtraForNonHoldSupportedCarriers()) {
+ Bundle extras = getExtras();
+ if (extras != null) {
+ return extras.getBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL);
+ }
+ }
+ return false;
+ }
+
public void performAnswer(int videoState) {
Log.v(this, "performAnswer");
if (isValidRingingCall() && getPhone() != null) {
try {
mTelephonyConnectionService.maybeDisconnectCallsOnOtherSubs(
- getPhoneAccountHandle());
+ getPhoneAccountHandle(), answeringDropsFgCalls());
getPhone().acceptCall(videoState);
} catch (CallStateException e) {
Log.e(this, e, "Failed to accept call.");
@@ -2515,8 +2517,8 @@
}
}
- if (mTelephonyConnectionService.maybeReselectDomain(this,
- mOriginalConnection.getPreciseDisconnectCause(), reasonInfo)) {
+ if (mTelephonyConnectionService.maybeReselectDomain(this, reasonInfo,
+ mShowPreciseFailedCause, mHangupDisconnectCause)) {
clearOriginalConnection();
break;
}
@@ -2558,7 +2560,9 @@
disconnectCause,
preciseDisconnectCause,
mOriginalConnection.getVendorDisconnectCause(),
- getPhone().getPhoneId(), imsReasonInfo));
+ getPhone().getPhoneId(), imsReasonInfo,
+ new FlagsAdapterImpl(),
+ shouldTreatAsEmergencyCall()));
close();
}
break;
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index bf7ce00..9390931 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -16,9 +16,13 @@
package com.android.services.telephony;
+import static android.telephony.CarrierConfigManager.KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL;
import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
import static android.telephony.TelephonyManager.HAL_SERVICE_VOICE;
+import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_GSM;
+import static com.android.internal.telephony.flags.Flags.carrierEnabledSatelliteFlag;
+
import android.annotation.NonNull;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -29,10 +33,11 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelUuid;
-import android.provider.DeviceConfig;
+import android.os.PersistableBundle;
import android.telecom.Conference;
import android.telecom.Conferenceable;
import android.telecom.Connection;
@@ -49,7 +54,7 @@
import android.telephony.DataSpecificRegistrationInfo;
import android.telephony.DomainSelectionService;
import android.telephony.DomainSelectionService.SelectionAttributes;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneNumberUtils;
import android.telephony.RadioAccessFamily;
@@ -66,6 +71,7 @@
import com.android.ims.ImsManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallFailCause;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.GsmCdmaPhone;
import com.android.internal.telephony.IccCard;
@@ -83,6 +89,7 @@
import com.android.internal.telephony.emergency.EmergencyStateTracker;
import com.android.internal.telephony.emergency.RadioOnHelper;
import com.android.internal.telephony.emergency.RadioOnStateListener;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneConnection;
@@ -114,6 +121,7 @@
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.regex.Pattern;
+import java.util.stream.Stream;
import javax.annotation.Nullable;
@@ -133,8 +141,9 @@
// Timeout before we terminate the outgoing DSDA call if HOLD did not complete in time on the
// existing call.
private static final int DEFAULT_DSDA_OUTGOING_CALL_HOLD_TIMEOUT_MS = 2000;
- private static final String KEY_DOMAIN_COMPARE_FEATURE_ENABLED_FLAG =
- "is_domain_selection_compare_feature_enabled";
+
+ // Timeout to wait for the termination of incoming call before continue with the emergency call.
+ private static final int DEFAULT_REJECT_INCOMING_CALL_TIMEOUT_MS = 10 * 1000; // 10 seconds.
// If configured, reject attempts to dial numbers matching this pattern.
private static final Pattern CDMA_ACTIVATION_CODE_REGEX_PATTERN =
@@ -223,7 +232,6 @@
private DomainSelectionResolver mDomainSelectionResolver;
private EmergencyCallDomainSelectionConnection mEmergencyCallDomainSelectionConnection;
private TelephonyConnection mEmergencyConnection;
- private String mEmergencyCallId = null;
private Executor mDomainSelectionMainExecutor;
private ImsManager mImsManager = null;
private DomainSelectionConnection mDomainSelectionConnection;
@@ -568,8 +576,7 @@
}
// Update the domain in the case that it changes,for example during initial
// setup or when there was an srvcc or internal redial.
- mEmergencyStateTracker.onEmergencyCallDomainUpdated(
- origConn.getPhoneType(), c.getTelecomCallId());
+ mEmergencyStateTracker.onEmergencyCallDomainUpdated(origConn.getPhoneType(), c);
}
@Override
@@ -582,10 +589,28 @@
+ ", state=" + state);
if (c.getState() == Connection.STATE_ACTIVE) {
mEmergencyStateTracker.onEmergencyCallStateChanged(
- c.getOriginalConnection().getState(), c.getTelecomCallId());
- releaseEmergencyCallDomainSelection(false);
+ c.getOriginalConnection().getState(), c);
+ releaseEmergencyCallDomainSelection(false, true);
}
}
+
+ @Override
+ public void onConnectionPropertiesChanged(Connection connection,
+ int connectionProperties) {
+ if ((connection == null) || (mEmergencyStateTracker == null)) {
+ return;
+ }
+ TelephonyConnection c = (TelephonyConnection) connection;
+ com.android.internal.telephony.Connection origConn = c.getOriginalConnection();
+ if ((origConn == null) || (!origConn.getState().isAlive())) {
+ // ignore if there is no original connection alive
+ Log.i(this, "onConnectionPropertiesChanged without orig connection alive");
+ return;
+ }
+ Log.i(this, "onConnectionPropertiesChanged prop=" + connectionProperties);
+ mEmergencyStateTracker.onEmergencyCallPropertiesChanged(
+ connectionProperties, c);
+ }
};
private final TelephonyConnection.TelephonyConnectionListener
@@ -616,6 +641,13 @@
}
};
+ private void clearNormalCallDomainSelectionConnection() {
+ if (mDomainSelectionConnection != null) {
+ mDomainSelectionConnection.finishSelection();
+ mDomainSelectionConnection = null;
+ }
+ }
+
/**
* A listener for calls.
*/
@@ -628,17 +660,15 @@
if (c != null) {
switch(c.getState()) {
case Connection.STATE_ACTIVE: {
- Log.d(LOG_TAG, "Call State->ACTIVE."
- + "Clearing DomainSelectionConnection");
- if (mDomainSelectionConnection != null) {
- mDomainSelectionConnection.finishSelection();
- mDomainSelectionConnection = null;
- }
+ clearNormalCallDomainSelectionConnection();
mNormalCallConnection = null;
}
break;
case Connection.STATE_DISCONNECTED: {
+ // Clear connection if the call state changes from
+ // DIALING -> DISCONNECTED without ACTIVE State.
+ clearNormalCallDomainSelectionConnection();
c.removeTelephonyConnectionListener(mNormalCallConnectionListener);
}
break;
@@ -680,6 +710,20 @@
}
}
+ private static class OnDisconnectListener extends
+ com.android.internal.telephony.Connection.ListenerBase {
+ private final CompletableFuture<Boolean> mFuture;
+
+ OnDisconnectListener(CompletableFuture<Boolean> future) {
+ mFuture = future;
+ }
+
+ @Override
+ public void onDisconnect(int cause) {
+ mFuture.complete(true);
+ }
+ };
+
private final DomainSelectionConnection.DomainSelectionConnectionCallback
mEmergencyDomainSelectionConnectionCallback =
new DomainSelectionConnection.DomainSelectionConnectionCallback() {
@@ -703,9 +747,8 @@
Phone phone = mEmergencyCallDomainSelectionConnection.getPhone();
mEmergencyConnection.removeTelephonyConnectionListener(
mEmergencyConnectionListener);
- releaseEmergencyCallDomainSelection(true);
- mEmergencyStateTracker.endCall(mEmergencyCallId);
- mEmergencyCallId = null;
+ releaseEmergencyCallDomainSelection(true, false);
+ mEmergencyStateTracker.endCall(c);
retryOutgoingOriginalConnection(c, phone, isPermanentFailure);
return;
}
@@ -725,35 +768,45 @@
public void onSelectionTerminated(@DisconnectCauses int cause) {
mDomainSelectionMainExecutor.execute(new Runnable() {
int mCause = cause;
+
@Override
public void run() {
Log.v(this, "Call domain selection terminated.");
if (mDomainSelectionConnection != null) {
- mDomainSelectionConnection = null;
- }
- if (mNormalCallConnection != null) {
- // TODO: To support ShowPreciseFailedCause, TelephonyConnection
- // .getShowPreciseFailedCause API should be added.
+ if (mNormalCallConnection != null) {
- // If cause is NOT_VALID then, it's a redial cancellation and
- // use cause code from original connection.
- com.android.internal.telephony.Connection connection =
- mNormalCallConnection.getOriginalConnection();
- if (connection != null) {
+ NormalCallDomainSelectionConnection ncdsConn =
+ (NormalCallDomainSelectionConnection)
+ mDomainSelectionConnection;
+
+ // If cause is NOT_VALID then, it's a redial cancellation
if (mCause == android.telephony.DisconnectCause.NOT_VALID) {
- mCause = connection.getDisconnectCause();
+ mCause = ncdsConn.getDisconnectCause();
}
- String reason = connection.getVendorDisconnectCause();
- int phoneId = mNormalCallConnection.getPhone().getPhoneId();
+ Log.d(this, "Call connection closed. PreciseCause: "
+ + ncdsConn.getPreciseDisconnectCause()
+ + " DisconnectCause: " + ncdsConn.getDisconnectCause()
+ + " Reason: " + ncdsConn.getReasonMessage());
+
mNormalCallConnection.setTelephonyConnectionDisconnected(
- mDisconnectCauseFactory.toTelecomDisconnectCause(
- mCause, reason, phoneId));
- Log.d(this, "Call connection closed. Cause: " + mCause
- + " Reason: " + reason);
+ DisconnectCauseUtil.toTelecomDisconnectCause(mCause,
+ ncdsConn.getPreciseDisconnectCause(),
+ ncdsConn.getReasonMessage(),
+ ncdsConn.getPhoneId(),
+ ncdsConn.getImsReasonInfo(),
+ new FlagsAdapterImpl()));
+
+ mNormalCallConnection.close();
+ mNormalCallConnection = null;
+ } else {
+ Log.v(this, "NormalCallConnection is null.");
}
- mNormalCallConnection.close();
- mNormalCallConnection = null;
+
+ mDomainSelectionConnection = null;
+
+ } else {
+ Log.v(this, "DomainSelectionConnection is null.");
}
}
});
@@ -1061,14 +1114,17 @@
final boolean isAirplaneModeOn = mDeviceState.isAirplaneModeOn(this);
- boolean needToTurnOnRadio = (isEmergencyNumber && (!isRadioOn() || isAirplaneModeOn))
- || isRadioPowerDownOnBluetooth();
boolean needToTurnOffSatellite = isSatelliteBlockingCall(isEmergencyNumber);
// Get the right phone object from the account data passed in.
final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber,
/* Note: when not an emergency, handle can be null for unknown callers */
handle == null ? null : handle.getSchemeSpecificPart());
+ ImsPhone imsPhone = phone != null ? (ImsPhone) phone.getImsPhone() : null;
+
+ boolean isPhoneWifiCallingEnabled = phone != null && phone.isWifiCallingEnabled();
+ boolean needToTurnOnRadio = (isEmergencyNumber && (!isRadioOn() || isAirplaneModeOn))
+ || (isRadioPowerDownOnBluetooth() && !isPhoneWifiCallingEnabled);
if (mDomainSelectionResolver.isDomainSelectionSupported()) {
// Normal routing emergency number shall be handled by normal call domain selctor.
@@ -1083,7 +1139,7 @@
if (needToTurnOnRadio || needToTurnOffSatellite) {
final Uri resultHandle = handle;
- final int originalPhoneType = phone.getPhoneType();
+ final int originalPhoneType = (phone == null) ? PHONE_TYPE_GSM : phone.getPhoneType();
final Connection resultConnection = getTelephonyConnection(request, numberToDial,
isEmergencyNumber, resultHandle, phone);
if (mRadioOnHelper == null) {
@@ -1095,6 +1151,7 @@
}
int timeoutToOnTimeoutCallback = mDomainSelectionResolver.isDomainSelectionSupported()
? TIMEOUT_TO_DYNAMIC_ROUTING_MS : 0;
+ final Phone phoneForEmergency = phone;
mRadioOnHelper.triggerRadioOnAndListen(new RadioOnStateListener.Callback() {
@Override
public void onComplete(RadioOnStateListener listener, boolean isRadioReady) {
@@ -1119,7 +1176,7 @@
&& phone.getHalVersion(HAL_SERVICE_VOICE)
.less(RIL.RADIO_HAL_VERSION_1_4);
if (mDomainSelectionResolver.isDomainSelectionSupported()) {
- if (isEmergencyNumber) {
+ if (isEmergencyNumber && phone == phoneForEmergency) {
// Since the domain selection service is enabled,
// dilaing normal routing emergency number only reaches here.
if (!isVoiceInService(phone, imsVoiceCapable)) {
@@ -1171,7 +1228,9 @@
}
if (!isEmergencyNumber) {
- if (mSatelliteController.isSatelliteEnabled()) {
+ if ((mSatelliteController.isSatelliteEnabled()
+ || isCallDisallowedDueToSatellite(phone))
+ && (imsPhone == null || !imsPhone.canMakeWifiCall())) {
Log.d(this, "onCreateOutgoingConnection, cannot make call in satellite mode.");
return Connection.createFailedConnection(
mDisconnectCauseFactory.toTelecomDisconnectCause(
@@ -1211,27 +1270,9 @@
final Connection resultConnection = getTelephonyConnection(request, numberToDial,
true, handle, phone);
- CompletableFuture<Void> maybeHoldFuture = CompletableFuture.completedFuture(null);
- if (mTelephonyManagerProxy.isConcurrentCallsPossible()
- && shouldHoldForEmergencyCall(phone)) {
- // If the PhoneAccountHandle was adjusted on building the TelephonyConnection,
- // the relevant PhoneAccountHandle will be updated in resultConnection.
- PhoneAccountHandle phoneAccountHandle =
- resultConnection.getPhoneAccountHandle() == null
- ? request.getAccountHandle() : resultConnection.getPhoneAccountHandle();
- Conferenceable c = maybeHoldCallsOnOtherSubs(phoneAccountHandle);
- if (c != null) {
- maybeHoldFuture = delayDialForOtherSubHold(phone, c, (success) -> {
- Log.i(this, "onCreateOutgoingConn emergency-"
- + " delayDialForOtherSubHold success = " + success);
- if (!success) {
- // Terminates the existing call to make way for the emergency call.
- hangup(c, android.telephony.DisconnectCause
- .OUTGOING_EMERGENCY_CALL_PLACED);
- }
- });
- }
- }
+ CompletableFuture<Void> maybeHoldFuture =
+ checkAndHoldCallsOnOtherSubsForEmergencyCall(request,
+ resultConnection, phone);
Consumer<Boolean> ddsSwitchConsumer = (result) -> {
Log.i(this, "onCreateOutgoingConn emergency-"
+ " delayDialForDdsSwitch result = " + result);
@@ -1243,6 +1284,32 @@
}
}
+ private CompletableFuture<Void> checkAndHoldCallsOnOtherSubsForEmergencyCall(
+ ConnectionRequest request, Connection resultConnection, Phone phone) {
+ CompletableFuture<Void> maybeHoldFuture = CompletableFuture.completedFuture(null);
+ if (mTelephonyManagerProxy.isConcurrentCallsPossible()
+ && shouldHoldForEmergencyCall(phone)) {
+ // If the PhoneAccountHandle was adjusted on building the TelephonyConnection,
+ // the relevant PhoneAccountHandle will be updated in resultConnection.
+ PhoneAccountHandle phoneAccountHandle =
+ resultConnection.getPhoneAccountHandle() == null
+ ? request.getAccountHandle() : resultConnection.getPhoneAccountHandle();
+ Conferenceable c = maybeHoldCallsOnOtherSubs(phoneAccountHandle);
+ if (c != null) {
+ maybeHoldFuture = delayDialForOtherSubHold(phone, c, (success) -> {
+ Log.i(this, "checkAndHoldCallsOnOtherSubsForEmergencyCall"
+ + " delayDialForOtherSubHold success = " + success);
+ if (!success) {
+ // Terminates the existing call to make way for the emergency call.
+ hangup(c, android.telephony.DisconnectCause
+ .OUTGOING_EMERGENCY_CALL_PLACED);
+ }
+ });
+ }
+ }
+ return maybeHoldFuture;
+ }
+
private Connection placeOutgoingConnection(ConnectionRequest request,
Connection resultConnection, Phone phone) {
// If there was a failure, the resulting connection will not be a TelephonyConnection,
@@ -1362,7 +1429,7 @@
// one and causing UI Jank.
boolean noActiveSimCard = SubscriptionManagerService.getInstance()
.getActiveSubInfoCount(phone.getContext().getOpPackageName(),
- phone.getContext().getAttributionTag()) == 0;
+ phone.getContext().getAttributionTag(), true/*isForAllProfile*/) == 0;
// If there's no active sim card and the device is in emergency mode, use E account.
addExistingConnection(mPhoneUtilsProxy.makePstnPhoneAccountHandleWithPrefix(
phone, "", isEmergencyNumber && noActiveSimCard), repConnection);
@@ -2183,28 +2250,20 @@
}
} catch (CallStateException e) {
Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e);
+ if (mDomainSelectionResolver.isDomainSelectionSupported()) {
+ // Notify EmergencyStateTracker and DomainSelector of the cancellation by exception
+ onLocalHangup(connection);
+ }
connection.unregisterForCallEvents();
handleCallStateException(e, connection, phone);
return;
}
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 ||
- phone.isUtEnabled()) {
- Log.d(this, "dialed MMI code");
- int subId = phone.getSubId();
- Log.d(this, "subId: "+subId);
- telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI;
- final Intent intent = new Intent(this, MMIDialogActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
- Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- if (SubscriptionManager.isValidSubscriptionId(subId)) {
- SubscriptionManager.putSubscriptionIdExtra(intent, subId);
- }
- startActivity(intent);
- }
Log.d(this, "placeOutgoingConnection, phone.dial returned null");
+
+ // On GSM phones, null connection means that we dialed an MMI code
+ int telephonyDisconnectCause = handleMmiCode(
+ phone, android.telephony.DisconnectCause.OUTGOING_FAILURE);
connection.setTelephonyConnectionDisconnected(
mDisconnectCauseFactory.toTelecomDisconnectCause(telephonyDisconnectCause,
"Connection is null", phone.getPhoneId()));
@@ -2224,6 +2283,25 @@
}
}
+ private int handleMmiCode(Phone phone, int telephonyDisconnectCause) {
+ int disconnectCause = telephonyDisconnectCause;
+ if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM
+ || phone.isUtEnabled()) {
+ Log.d(this, "dialed MMI code");
+ int subId = phone.getSubId();
+ Log.d(this, "subId: " + subId);
+ disconnectCause = android.telephony.DisconnectCause.DIALED_MMI;
+ final Intent intent = new Intent(this, MMIDialogActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ if (SubscriptionManager.isValidSubscriptionId(subId)) {
+ SubscriptionManager.putSubscriptionIdExtra(intent, subId);
+ }
+ startActivity(intent);
+ }
+ return disconnectCause;
+ }
+
private void handleOutgoingCallConnectionByCallDomainSelection(
int domain, Phone phone, String number, int videoState) {
Log.d(this, "Call Domain Selected : " + domain);
@@ -2233,14 +2311,6 @@
extras = new Bundle();
}
extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, domain);
- // Add flag to bundle for comparing legacy and new domain selection results. When
- // EXTRA_COMPARE_DOMAIN flag is true, legacy domain selection result is used for
- // placing the call and if both the results are not same then bug report is generated.
- DeviceConfig.Properties properties = //read all telephony properties
- DeviceConfig.getProperties(DeviceConfig.NAMESPACE_TELEPHONY);
- boolean compareDomainSelection =
- properties.getBoolean(KEY_DOMAIN_COMPARE_FEATURE_ENABLED_FLAG, false);
- extras.putBoolean(PhoneConstants.EXTRA_COMPARE_DOMAIN, compareDomainSelection);
if (phone != null) {
Log.v(LOG_TAG, "Call dialing. Domain: " + domain);
@@ -2249,11 +2319,28 @@
.setVideoState(videoState)
.setIntentExtras(extras)
.setRttTextStream(mNormalCallConnection.getRttTextStream())
- .setIsWpsCall(NormalCallDomainSelectionConnection
- .isWpsCall(number))
+ .setIsWpsCall(PhoneNumberUtils.isWpsCallNumber(number))
.build(),
mNormalCallConnection::registerForCallEvents);
+ if (connection == null) {
+ Log.d(this, "placeOutgoingConnection, phone.dial returned null");
+
+ // On GSM phones, null connection means that we dialed an MMI code
+ int telephonyDisconnectCause = handleMmiCode(
+ phone, android.telephony.DisconnectCause.OUTGOING_FAILURE);
+ if (mNormalCallConnection.getState() != Connection.STATE_DISCONNECTED) {
+ mNormalCallConnection.setTelephonyConnectionDisconnected(
+ mDisconnectCauseFactory.toTelecomDisconnectCause(
+ telephonyDisconnectCause,
+ "Connection is null",
+ phone.getPhoneId()));
+ mNormalCallConnection.close();
+ }
+ clearNormalCallDomainSelectionConnection();
+ return;
+ }
+
mNormalCallConnection.setOriginalConnection(connection);
mNormalCallConnection.addTelephonyConnectionListener(mNormalCallConnectionListener);
return;
@@ -2277,10 +2364,7 @@
e.getMessage(), phone.getPhoneId()));
mNormalCallConnection.close();
}
- if (mDomainSelectionConnection != null) {
- mDomainSelectionConnection.finishSelection();
- mDomainSelectionConnection = null;
- }
+ clearNormalCallDomainSelectionConnection();
mNormalCallConnection = null;
}
@@ -2300,6 +2384,7 @@
boolean isMmiCode = (dialPart.startsWith("*") || dialPart.startsWith("#"))
&& dialPart.endsWith("#");
boolean isSuppServiceCode = ImsPhoneMmiCode.isSuppServiceCodes(dialPart, phone);
+ boolean isPotentialUssdCode = isMmiCode && !isSuppServiceCode;
// If the number is both an MMI code and a supplementary service code,
// it shall be treated as UT. In this case, domain selection is not performed.
@@ -2308,10 +2393,14 @@
return false;
}
+ /* For USSD codes, connection is closed and MMIDialogActivity is started.
+ To avoid connection close and return false. isPotentialUssdCode is handled after
+ all condition checks. */
+
// Check and select same domain as ongoing call on the same subscription (if exists)
int activeCallDomain = getActiveCallDomain(phone.getSubId());
if (activeCallDomain != NetworkRegistrationInfo.DOMAIN_UNKNOWN
- && !NormalCallDomainSelectionConnection.isWpsCall(number)) {
+ && !PhoneNumberUtils.isWpsCallNumber(number)) {
Log.d(LOG_TAG, "Selecting same domain as ongoing call on same subId");
mNormalCallConnection = connection;
handleOutgoingCallConnectionByCallDomainSelection(
@@ -2328,7 +2417,7 @@
SelectionAttributes selectionAttributes =
new SelectionAttributes.Builder(phone.getPhoneId(), phone.getSubId(),
SELECTOR_TYPE_CALLING)
- .setNumber(number)
+ .setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null))
.setEmergency(false)
.setVideoCall(VideoProfile.isVideo(videoState))
.build();
@@ -2343,6 +2432,15 @@
mNormalCallConnection = connection;
future.thenAcceptAsync((domain) -> handleOutgoingCallConnectionByCallDomainSelection(
domain, phone, number, videoState), mDomainSelectionMainExecutor);
+
+ if (isPotentialUssdCode) {
+ Log.v(LOG_TAG, "PotentialUssdCode. Closing connection with DisconnectCause.DIALED_MMI");
+ connection.setTelephonyConnectionDisconnected(
+ mDisconnectCauseFactory.toTelecomDisconnectCause(
+ android.telephony.DisconnectCause.DIALED_MMI,
+ "Dialing USSD", phone.getPhoneId()));
+ connection.close();
+ }
return true;
}
@@ -2359,6 +2457,32 @@
Log.i(this, "placeEmergencyConnection");
mIsEmergencyCallPending = true;
+ mEmergencyConnection = (TelephonyConnection) resultConnection;
+ }
+
+ CompletableFuture<Void> maybeHoldFuture =
+ checkAndHoldCallsOnOtherSubsForEmergencyCall(request, resultConnection, phone);
+ maybeHoldFuture.thenRun(() -> placeEmergencyConnectionInternal(resultConnection,
+ phone, request, numberToDial, isTestEmergencyNumber, needToTurnOnRadio));
+
+ // Non TelephonyConnection type instance means dialing failure.
+ return resultConnection;
+ }
+
+ @SuppressWarnings("FutureReturnValueIgnored")
+ private void placeEmergencyConnectionInternal(final Connection resultConnection,
+ final Phone phone, final ConnectionRequest request,
+ final String numberToDial, final boolean isTestEmergencyNumber,
+ final boolean needToTurnOnRadio) {
+
+ if (mEmergencyConnection == null) {
+ Log.i(this, "placeEmergencyConnectionInternal dialing canceled");
+ return;
+ }
+
+ if (resultConnection instanceof TelephonyConnection) {
+ Log.i(this, "placeEmergencyConnectionInternal");
+
((TelephonyConnection) resultConnection).addTelephonyConnectionListener(
mEmergencyConnectionListener);
@@ -2366,19 +2490,18 @@
mEmergencyStateTracker = EmergencyStateTracker.getInstance();
}
- mEmergencyCallId = resultConnection.getTelecomCallId();
CompletableFuture<Integer> future = mEmergencyStateTracker.startEmergencyCall(
- phone, mEmergencyCallId, isTestEmergencyNumber);
+ phone, resultConnection, isTestEmergencyNumber);
future.thenAccept((result) -> {
Log.d(this, "startEmergencyCall-complete result=" + result);
- if (mEmergencyCallId == null) {
+ if (mEmergencyConnection == null) {
Log.i(this, "startEmergencyCall-complete dialing canceled");
return;
}
if (result == android.telephony.DisconnectCause.NOT_DISCONNECTED) {
createEmergencyConnection(phone, (TelephonyConnection) resultConnection,
- numberToDial, request, needToTurnOnRadio,
- mEmergencyStateTracker.getEmergencyRegResult());
+ numberToDial, isTestEmergencyNumber, request, needToTurnOnRadio,
+ mEmergencyStateTracker.getEmergencyRegistrationResult());
} else {
mEmergencyConnection = null;
String reason = "Couldn't setup emergency call";
@@ -2391,18 +2514,15 @@
mIsEmergencyCallPending = false;
}
});
- mEmergencyConnection = (TelephonyConnection) resultConnection;
- return resultConnection;
}
- Log.i(this, "placeEmergencyConnection returns null");
- return null;
}
@SuppressWarnings("FutureReturnValueIgnored")
private void createEmergencyConnection(final Phone phone,
final TelephonyConnection resultConnection, final String number,
+ final boolean isTestEmergencyNumber,
final ConnectionRequest request, boolean needToTurnOnRadio,
- final EmergencyRegResult regResult) {
+ final EmergencyRegistrationResult regResult) {
Log.i(this, "createEmergencyConnection");
if (phone.getImsPhone() == null) {
@@ -2442,21 +2562,27 @@
DomainSelectionService.SelectionAttributes attr =
EmergencyCallDomainSelectionConnection.getSelectionAttributes(
phone.getPhoneId(), phone.getSubId(), needToTurnOnRadio,
- request.getTelecomCallId(), number, 0, null, regResult);
+ request.getTelecomCallId(), number, isTestEmergencyNumber,
+ 0, null, regResult);
CompletableFuture<Integer> future =
mEmergencyCallDomainSelectionConnection.createEmergencyConnection(
attr, mEmergencyDomainSelectionConnectionCallback);
future.thenAcceptAsync((result) -> {
Log.d(this, "createEmergencyConnection-complete result=" + result);
- if (mEmergencyCallId == null) {
+ if (mEmergencyConnection == null) {
Log.i(this, "createEmergencyConnection-complete dialing canceled");
return;
}
Bundle extras = request.getExtras();
extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, result);
- placeOutgoingConnection(request, resultConnection, phone);
- mIsEmergencyCallPending = false;
+ CompletableFuture<Void> rejectFuture = checkAndRejectIncomingCall(phone, (ret) -> {
+ if (!ret) {
+ Log.i(this, "createEmergencyConnection reject incoming call failed");
+ }
+ });
+ rejectFuture.thenRun(() -> placeEmergencyConnectionOnSelectedDomain(request,
+ resultConnection, phone));
}, mDomainSelectionMainExecutor);
}
@@ -2467,45 +2593,67 @@
extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, NetworkRegistrationInfo.DOMAIN_CS);
mDomainSelectionMainExecutor.execute(
() -> {
- if (mEmergencyCallId == null) {
+ if (mEmergencyConnection == null) {
Log.i(this, "dialCsEmergencyCall dialing canceled");
return;
}
- placeOutgoingConnection(request, resultConnection, phone);
+ CompletableFuture<Void> future = checkAndRejectIncomingCall(phone, (ret) -> {
+ if (!ret) {
+ Log.i(this, "dialCsEmergencyCall reject incoming call failed");
+ }
+ });
+ future.thenRun(() -> placeEmergencyConnectionOnSelectedDomain(request,
+ resultConnection, phone));
});
}
- private void releaseEmergencyCallDomainSelection(boolean cancel) {
+ private void placeEmergencyConnectionOnSelectedDomain(ConnectionRequest request,
+ TelephonyConnection resultConnection, Phone phone) {
+ if (mEmergencyConnection == null) {
+ Log.i(this, "placeEmergencyConnectionOnSelectedDomain dialing canceled");
+ return;
+ }
+ placeOutgoingConnection(request, resultConnection, phone);
+ mIsEmergencyCallPending = false;
+ }
+
+ private void releaseEmergencyCallDomainSelection(boolean cancel, boolean isActive) {
if (mEmergencyCallDomainSelectionConnection != null) {
if (cancel) mEmergencyCallDomainSelectionConnection.cancelSelection();
else mEmergencyCallDomainSelectionConnection.finishSelection();
mEmergencyCallDomainSelectionConnection = null;
}
mIsEmergencyCallPending = false;
- mEmergencyConnection = null;
+ if (!isActive) {
+ mEmergencyConnection = null;
+ }
}
/**
* Determine whether reselection of domain is required or not.
* @param c the {@link Connection} instance.
- * @param callFailCause the reason why CS call is disconnected. Allowed values are defined in
* {@link com.android.internal.telephony.CallFailCause}.
* @param reasonInfo the reason why PS call is disconnected.
+ * @param showPreciseCause Indicates whether this connection supports showing precise
+ * call failed cause.
+ * @param overrideCause Provides a DisconnectCause associated with a hang up request.
* @return {@code true} if reselection of domain is required.
*/
- public boolean maybeReselectDomain(final TelephonyConnection c,
- int callFailCause, ImsReasonInfo reasonInfo) {
+ public boolean maybeReselectDomain(final TelephonyConnection c, ImsReasonInfo reasonInfo,
+ boolean showPreciseCause, int overrideCause) {
if (!mDomainSelectionResolver.isDomainSelectionSupported()) return false;
+ int callFailCause = c.getOriginalConnection().getPreciseDisconnectCause();
+
Log.i(this, "maybeReselectDomain csCause=" + callFailCause + ", psCause=" + reasonInfo);
- if (TextUtils.equals(mEmergencyCallId, c.getTelecomCallId())) {
+ if (mEmergencyConnection == c) {
if (mEmergencyCallDomainSelectionConnection != null) {
return maybeReselectDomainForEmergencyCall(c, callFailCause, reasonInfo);
}
Log.i(this, "maybeReselectDomain endCall()");
c.removeTelephonyConnectionListener(mEmergencyConnectionListener);
- mEmergencyStateTracker.endCall(c.getTelecomCallId());
- mEmergencyCallId = null;
+ releaseEmergencyCallDomainSelection(false, false);
+ mEmergencyStateTracker.endCall(c);
return false;
}
@@ -2517,10 +2665,7 @@
&& extraCode == ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY)) {
// clear normal call domain selector
c.removeTelephonyConnectionListener(mNormalCallConnectionListener);
- if (mDomainSelectionConnection != null) {
- mDomainSelectionConnection.finishSelection();
- mDomainSelectionConnection = null;
- }
+ clearNormalCallDomainSelectionConnection();
mNormalCallConnection = null;
onEmergencyRedial(c, c.getPhone().getDefaultPhone());
@@ -2528,7 +2673,7 @@
}
}
- return maybeReselectDomainForNormalCall(c, callFailCause, reasonInfo);
+ return maybeReselectDomainForNormalCall(c, reasonInfo, showPreciseCause, overrideCause);
}
private boolean maybeReselectDomainForEmergencyCall(final TelephonyConnection c,
@@ -2546,7 +2691,7 @@
EmergencyCallDomainSelectionConnection.getSelectionAttributes(
c.getPhone().getPhoneId(), c.getPhone().getSubId(), false,
c.getTelecomCallId(), c.getAddress().getSchemeSpecificPart(),
- callFailCause, reasonInfo, null);
+ false, callFailCause, reasonInfo, null);
CompletableFuture<Integer> future =
mEmergencyCallDomainSelectionConnection.reselectDomain(attr);
@@ -2555,7 +2700,7 @@
if (future != null) {
future.thenAcceptAsync((result) -> {
Log.d(this, "reselectDomain-complete");
- if (mEmergencyCallId == null) {
+ if (mEmergencyConnection == null) {
Log.i(this, "reselectDomain-complete dialing canceled");
return;
}
@@ -2567,23 +2712,68 @@
Log.i(this, "maybeReselectDomainForEmergencyCall endCall()");
c.removeTelephonyConnectionListener(mEmergencyConnectionListener);
- releaseEmergencyCallDomainSelection(true);
- mEmergencyStateTracker.endCall(c.getTelecomCallId());
- mEmergencyCallId = null;
+ releaseEmergencyCallDomainSelection(true, false);
+ mEmergencyStateTracker.endCall(c);
return false;
}
- private boolean isNormalRouting(Phone phone, String number) {
- if (phone.getEmergencyNumberTracker() != null) {
- EmergencyNumber num = phone.getEmergencyNumberTracker().getEmergencyNumber(number);
- if (num != null) {
- return num.getEmergencyCallRouting()
- == EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL;
+ private boolean isEmergencyNumberAllowedOnDialedSim(Phone phone, String number) {
+ CarrierConfigManager cfgManager = (CarrierConfigManager)
+ phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (cfgManager != null) {
+ PersistableBundle b = cfgManager.getConfigForSubId(phone.getSubId(),
+ KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL);
+ if (b == null) {
+ b = CarrierConfigManager.getDefaultConfig();
+ }
+ // We need to check only when KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL is true.
+ if (b.getBoolean(KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL, false)
+ && (phone.getEmergencyNumberTracker() != null)) {
+ if (!phone.getEmergencyNumberTracker().isEmergencyNumber(number)) {
+ Log.i(this, "isEmergencyNumberAllowedOnDialedSim false");
+ return false;
+ }
}
}
+ return true;
+ }
+
+ private boolean isNormalRouting(Phone phone, String number) {
+ // Check isEmergencyNumberAllowedOnDialedSim(): some carriers do not want to handle
+ // dial requests for numbers which are in the emergency number list on another SIM,
+ // but not on their own. Such numbers shall be handled by normal call domain selector.
+ return (isNormalRoutingNumber(phone, number)
+ || !isEmergencyNumberAllowedOnDialedSim(phone, number));
+ }
+
+ private boolean isNormalRoutingNumber(Phone phone, String number) {
+ if (phone.getEmergencyNumberTracker() != null) {
+ // Note: There can potentially be multiple instances of EmergencyNumber found; if any of
+ // them have normal routing, then use normal routing.
+ List<EmergencyNumber> nums = phone.getEmergencyNumberTracker().getEmergencyNumbers(
+ number);
+ return nums.stream().anyMatch(n ->
+ n.getEmergencyCallRouting() == EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+ }
return false;
}
+ /**
+ * Determines the phone to use for a normal routed emergency call.
+ * @param number The emergency number.
+ * @return The {@link Phone} to place the normal routed emergency call on, or {@code null} if
+ * none was found.
+ */
+ @VisibleForTesting
+ public Phone getPhoneForNormalRoutedEmergencyCall(String number) {
+ return Stream.of(mPhoneFactoryProxy.getPhones())
+ .filter(p -> p.shouldPreferInServiceSimForNormalRoutedEmergencyCall()
+ && isNormalRoutingNumber(p, number)
+ && isAvailableForEmergencyCalls(p,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL))
+ .findFirst().orElse(null);
+ }
+
private boolean isVoiceInService(Phone phone, boolean imsVoiceCapable) {
// Dialing normal call is available.
if (phone.isWifiCallingEnabled()) {
@@ -2623,25 +2813,45 @@
}
private boolean maybeReselectDomainForNormalCall(
- final TelephonyConnection c, int callFailCause, ImsReasonInfo reasonInfo) {
+ final TelephonyConnection c, ImsReasonInfo reasonInfo,
+ boolean showPreciseCause, int overrideCause) {
- Log.i(LOG_TAG, "maybeReselectDomainForNormalCall " + "csCause:" + callFailCause
- + ", psCause:" + reasonInfo);
+ Log.i(LOG_TAG, "maybeReselectDomainForNormalCall");
- if (mDomainSelectionConnection != null && c.getOriginalConnection() != null) {
+ com.android.internal.telephony.Connection originalConn = c.getOriginalConnection();
+ if (mDomainSelectionConnection != null && originalConn != null) {
Phone phone = c.getPhone().getDefaultPhone();
final String number = c.getAddress().getSchemeSpecificPart();
- int videoState = c.getOriginalConnection().getVideoState();
+ int videoState = originalConn.getVideoState();
+
SelectionAttributes selectionAttributes = NormalCallDomainSelectionConnection
.getSelectionAttributes(phone.getPhoneId(), phone.getSubId(),
c.getTelecomCallId(), number, VideoProfile.isVideo(videoState),
- callFailCause, reasonInfo);
+ originalConn.getPreciseDisconnectCause(), reasonInfo);
- Log.d(LOG_TAG, "Reselecting the domain for call");
- mNormalCallConnection = c;
CompletableFuture<Integer> future = mDomainSelectionConnection
.reselectDomain(selectionAttributes);
if (future != null) {
+ int preciseDisconnectCause = CallFailCause.NOT_VALID;
+ if (showPreciseCause) {
+ preciseDisconnectCause = originalConn.getPreciseDisconnectCause();
+ }
+
+ int disconnectCause = originalConn.getDisconnectCause();
+ if ((overrideCause != android.telephony.DisconnectCause.NOT_VALID)
+ && (overrideCause != disconnectCause)) {
+ Log.i(LOG_TAG, "setDisconnected: override cause: " + disconnectCause
+ + " -> " + overrideCause);
+ disconnectCause = overrideCause;
+ }
+
+ ((NormalCallDomainSelectionConnection) mDomainSelectionConnection)
+ .setDisconnectCause(disconnectCause, preciseDisconnectCause,
+ originalConn.getVendorDisconnectCause());
+
+ Log.d(LOG_TAG, "Reselecting the domain for call");
+ mNormalCallConnection = c;
+
future.thenAcceptAsync((result) -> {
onNormalCallRedial(c, phone, result, videoState);
}, mDomainSelectionMainExecutor);
@@ -2650,20 +2860,35 @@
}
c.removeTelephonyConnectionListener(mTelephonyConnectionListener);
- if (mDomainSelectionConnection != null) {
- mDomainSelectionConnection.finishSelection();
- mDomainSelectionConnection = null;
- }
+ clearNormalCallDomainSelectionConnection();
mNormalCallConnection = null;
Log.d(LOG_TAG, "Reselect call domain not triggered.");
return false;
}
- private void onEmergencyRedialOnDomain(TelephonyConnection connection,
+ private void onEmergencyRedialOnDomain(final TelephonyConnection connection,
final Phone phone, @NetworkRegistrationInfo.Domain int domain) {
Log.i(this, "onEmergencyRedialOnDomain phoneId=" + phone.getPhoneId()
+ ", domain=" + DomainSelectionService.getDomainName(domain));
+ final Bundle extras = new Bundle();
+ extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, domain);
+
+ CompletableFuture<Void> future = checkAndRejectIncomingCall(phone, (ret) -> {
+ if (!ret) {
+ Log.i(this, "onEmergencyRedialOnDomain reject incoming call failed");
+ }
+ });
+ future.thenRun(() -> onEmergencyRedialOnDomainInternal(connection, phone, extras));
+ }
+
+ private void onEmergencyRedialOnDomainInternal(TelephonyConnection connection,
+ Phone phone, Bundle extras) {
+ if (mEmergencyConnection == null) {
+ Log.i(this, "onEmergencyRedialOnDomainInternal dialing canceled");
+ return;
+ }
+
String number = connection.getAddress().getSchemeSpecificPart();
// Indicates undetectable emergency number with DialArgs
@@ -2672,12 +2897,9 @@
if (connection.getEmergencyServiceCategory() != null) {
isEmergency = true;
eccCategory = connection.getEmergencyServiceCategory();
- Log.i(this, "onEmergencyRedialOnDomain eccCategory=" + eccCategory);
+ Log.i(this, "onEmergencyRedialOnDomainInternal eccCategory=" + eccCategory);
}
- Bundle extras = new Bundle();
- extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, domain);
-
com.android.internal.telephony.Connection originalConnection =
connection.getOriginalConnection();
try {
@@ -2692,14 +2914,20 @@
connection::registerForCallEvents);
}
} catch (CallStateException e) {
- Log.e(this, e, "onEmergencyRedialOnDomain, exception: " + e);
+ Log.e(this, e, "onEmergencyRedialOnDomainInternal, exception: " + e);
+ onLocalHangup(connection);
+ connection.unregisterForCallEvents();
+ handleCallStateException(e, connection, phone);
+ return;
}
if (originalConnection == null) {
- Log.d(this, "onEmergencyRedialOnDomain, phone.dial returned null");
- connection.setDisconnected(
+ Log.d(this, "onEmergencyRedialOnDomainInternal, phone.dial returned null");
+ onLocalHangup(connection);
+ connection.setTelephonyConnectionDisconnected(
mDisconnectCauseFactory.toTelecomDisconnectCause(
android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
"unknown error"));
+ connection.close();
} else {
connection.setOriginalConnection(originalConnection);
}
@@ -2720,12 +2948,12 @@
mEmergencyStateTracker = EmergencyStateTracker.getInstance();
}
- mEmergencyCallId = c.getTelecomCallId();
+ mEmergencyConnection = c;
CompletableFuture<Integer> future = mEmergencyStateTracker.startEmergencyCall(
- phone, mEmergencyCallId, isTestEmergencyNumber);
+ phone, c, isTestEmergencyNumber);
future.thenAccept((result) -> {
Log.d(this, "onEmergencyRedial-complete result=" + result);
- if (mEmergencyCallId == null) {
+ if (mEmergencyConnection == null) {
Log.i(this, "onEmergencyRedial-complete dialing canceled");
return;
}
@@ -2746,15 +2974,13 @@
mEmergencyCallDomainSelectionConnection =
(EmergencyCallDomainSelectionConnection) selectConnection;
- mEmergencyConnection = c;
-
DomainSelectionService.SelectionAttributes attr =
EmergencyCallDomainSelectionConnection.getSelectionAttributes(
phone.getPhoneId(),
phone.getSubId(), false,
c.getTelecomCallId(),
- c.getAddress().getSchemeSpecificPart(),
- 0, null, mEmergencyStateTracker.getEmergencyRegResult());
+ c.getAddress().getSchemeSpecificPart(), isTestEmergencyNumber,
+ 0, null, mEmergencyStateTracker.getEmergencyRegistrationResult());
CompletableFuture<Integer> domainFuture =
mEmergencyCallDomainSelectionConnection.createEmergencyConnection(
@@ -2766,6 +2992,7 @@
mIsEmergencyCallPending = false;
}, mDomainSelectionMainExecutor);
} else {
+ mEmergencyConnection = null;
c.setTelephonyConnectionDisconnected(
mDisconnectCauseFactory.toTelecomDisconnectCause(result, "unknown error"));
c.close();
@@ -2777,7 +3004,7 @@
private void recreateEmergencyConnection(final TelephonyConnection connection,
final Phone phone, final @NetworkRegistrationInfo.Domain int result) {
Log.d(this, "recreateEmergencyConnection result=" + result);
- if (mEmergencyCallId == null) {
+ if (mEmergencyConnection == null) {
Log.i(this, "recreateEmergencyConnection dialing canceled");
return;
}
@@ -2828,15 +3055,6 @@
Bundle extras = new Bundle();
extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, domain);
- // Add flag to bundle for comparing legacy and new domain selection results. When
- // EXTRA_COMPARE_DOMAIN flag is true, legacy domain selection result is used for
- // placing the call and if both the results are not same then bug report is generated.
- DeviceConfig.Properties properties = //read all telephony properties
- DeviceConfig.getProperties(DeviceConfig.NAMESPACE_TELEPHONY);
- boolean compareDomainSelection =
- properties.getBoolean(KEY_DOMAIN_COMPARE_FEATURE_ENABLED_FLAG, false);
- extras.putBoolean(PhoneConstants.EXTRA_COMPARE_DOMAIN, compareDomainSelection);
-
com.android.internal.telephony.Connection originalConnection =
connection.getOriginalConnection();
if (originalConnection instanceof ImsPhoneConnection) {
@@ -2871,16 +3089,25 @@
}
protected void onLocalHangup(TelephonyConnection c) {
- if (TextUtils.equals(mEmergencyCallId, c.getTelecomCallId())) {
- Log.i(this, "onLocalHangup " + mEmergencyCallId);
+ if (mEmergencyConnection == c) {
+ Log.i(this, "onLocalHangup " + c.getTelecomCallId());
c.removeTelephonyConnectionListener(mEmergencyConnectionListener);
- releaseEmergencyCallDomainSelection(true);
- mEmergencyStateTracker.endCall(c.getTelecomCallId());
- mEmergencyCallId = null;
+ releaseEmergencyCallDomainSelection(true, false);
+ mEmergencyStateTracker.endCall(c);
}
}
@VisibleForTesting
+ public TelephonyConnection getEmergencyConnection() {
+ return mEmergencyConnection;
+ }
+
+ @VisibleForTesting
+ public void setEmergencyConnection(TelephonyConnection c) {
+ mEmergencyConnection = c;
+ }
+
+ @VisibleForTesting
public TelephonyConnection.TelephonyConnectionListener getEmergencyConnectionListener() {
return mEmergencyConnectionListener;
}
@@ -3031,15 +3258,34 @@
if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
int phoneId = mSubscriptionManagerProxy.getPhoneId(subId);
chosenPhone = mPhoneFactoryProxy.getPhone(phoneId);
+ Log.i(this, "getPhoneForAccount: handle=%s, subId=%s", accountHandle,
+ (chosenPhone == null ? "null" : chosenPhone.getSubId()));
}
- // If this is an emergency call and the phone we originally planned to make this call
+
+ // If this isn't an emergency call, just use the chosen phone (or null if none was found).
+ if (!isEmergency) {
+ return chosenPhone;
+ }
+
+ // Check if this call should be treated as a normal routed emergency call; we'll return null
+ // if this is not a normal routed emergency call.
+ Phone normalRoutingPhone = getPhoneForNormalRoutedEmergencyCall(emergencyNumberAddress);
+ if (normalRoutingPhone != null) {
+ Log.i(this, "getPhoneForAccount: normal routed emergency number,"
+ + "using phoneId=%d/subId=%d", normalRoutingPhone.getPhoneId(),
+ normalRoutingPhone.getSubId());
+ return normalRoutingPhone;
+ }
+
+ // Default emergency call phone selection logic:
+ // This is an emergency call and the phone we originally planned to make this call
// with is not in service or was invalid, try to find one that is in service, using the
// default as a last chance backup.
- if (isEmergency && (chosenPhone == null || !isAvailableForEmergencyCalls(chosenPhone))) {
+ if (chosenPhone == null || !isAvailableForEmergencyCalls(chosenPhone)) {
Log.d(this, "getPhoneForAccount: phone for phone acct handle %s is out of service "
+ "or invalid for emergency call.", accountHandle);
chosenPhone = getPhoneForEmergencyCall(emergencyNumberAddress);
- Log.d(this, "getPhoneForAccount: using subId: " +
+ Log.i(this, "getPhoneForAccount: emergency call - using subId: %s",
(chosenPhone == null ? "null" : chosenPhone.getSubId()));
}
return chosenPhone;
@@ -3229,6 +3475,52 @@
}
/**
+ * If needed, block until an incoming call is disconnected for outgoing emergency call,
+ * or timeout expires.
+ * @param phone The Phone to reject the incoming call
+ * @param completeConsumer The consumer to call once rejecting incoming call has been
+ * completed. {@code true} result if the operation commpletes successfully, or
+ * {@code false} if the operation timed out/failed.
+ */
+ private CompletableFuture<Void> checkAndRejectIncomingCall(Phone phone,
+ Consumer<Boolean> completeConsumer) {
+ if (phone == null) {
+ // Unexpected inputs
+ Log.i(this, "checkAndRejectIncomingCall phone is null");
+ completeConsumer.accept(false);
+ return CompletableFuture.completedFuture(null);
+ }
+
+ Call ringingCall = phone.getRingingCall();
+ if (ringingCall == null || !ringingCall.isRinging()) {
+ completeConsumer.accept(true);
+ return CompletableFuture.completedFuture(null);
+ }
+ Log.i(this, "checkAndRejectIncomingCall found a ringing call");
+
+ try {
+ ringingCall.hangup();
+ CompletableFuture<Boolean> future = new CompletableFuture<>();
+ com.android.internal.telephony.Connection cn = ringingCall.getLatestConnection();
+ cn.addListener(new OnDisconnectListener(future));
+ // A timeout that will complete the future to not block the outgoing call indefinitely.
+ CompletableFuture<Boolean> timeout = new CompletableFuture<>();
+ phone.getContext().getMainThreadHandler().postDelayed(
+ () -> timeout.complete(false), DEFAULT_REJECT_INCOMING_CALL_TIMEOUT_MS);
+ // Ensure that the Consumer is completed on the main thread.
+ return future.acceptEitherAsync(timeout, completeConsumer,
+ phone.getContext().getMainExecutor()).exceptionally((ex) -> {
+ Log.w(this, "checkAndRejectIncomingCall - exceptionally= " + ex);
+ return null;
+ });
+ } catch (Exception e) {
+ Log.w(this, "checkAndRejectIncomingCall - exception= " + e.getMessage());
+ completeConsumer.accept(false);
+ return CompletableFuture.completedFuture(null);
+ }
+ }
+
+ /**
* Get the Phone to use for an emergency call of the given emergency number address:
* a) If there are multiple Phones with the Subscriptions that support the emergency number
* address, and one of them is the default voice Phone, consider the default voice phone
@@ -3459,10 +3751,25 @@
}
}
- /**
- * Returns true if the state of the Phone is IN_SERVICE or available for emergency calling only.
- */
private boolean isAvailableForEmergencyCalls(Phone phone) {
+ return isAvailableForEmergencyCalls(phone,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+ }
+
+ /**
+ * Determines if the phone is available for an emergency call given the specified routing.
+ *
+ * @param phone the phone to check the service availability for
+ * @param routing the emergency call routing for this call
+ */
+ @VisibleForTesting
+ public boolean isAvailableForEmergencyCalls(Phone phone,
+ @EmergencyNumber.EmergencyCallRouting int routing) {
+ if (isCallDisallowedDueToSatellite(phone)) {
+ // Phone is connected to satellite due to which it is not preferred for emergency call.
+ return false;
+ }
+
if (phone.getImsRegistrationTech() == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
// When a Phone is registered to Cross-SIM calling, there must always be a Phone on the
// other sub which is registered to cellular, so that must be selected.
@@ -3470,8 +3777,17 @@
+ phone + " as it is registered to CROSS_SIM");
return false;
}
- return ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState() ||
- phone.getServiceState().isEmergencyOnly();
+
+ // In service phones are always appropriate for emergency calls.
+ if (ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState()) {
+ return true;
+ }
+
+ // If the call routing is unknown or is using emergency routing, an emergency only attach is
+ // sufficient for placing the emergency call. Normal routed emergency calls cannot be
+ // placed on an emergency-only phone.
+ return (routing != EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL
+ && phone.getServiceState().isEmergencyOnly());
}
/**
@@ -3762,10 +4078,34 @@
return origAccountHandle;
}
+ /*
+ * Returns true if both existing connections on-device and the incoming connection support HOLD,
+ * false otherwise. Assumes that a TelephonyConference supports HOLD.
+ */
+ private boolean allCallsSupportHold(@NonNull TelephonyConnection incomingConnection) {
+ if (Flags.callExtraForNonHoldSupportedCarriers()) {
+ if (getAllConnections().stream()
+ .filter(c ->
+ // Exclude multiendpoint calls as they're not on this device.
+ (c.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL)
+ == 0
+ && (c.getConnectionCapabilities()
+ & Connection.CAPABILITY_SUPPORT_HOLD) != 0).count() == 0) {
+ return false;
+ }
+ if ((incomingConnection.getConnectionCapabilities()
+ & Connection.CAPABILITY_SUPPORT_HOLD) == 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
- * For the passed in incoming {@link TelephonyConnection}, for non- dual active voice devices,
+ * For the passed in incoming {@link TelephonyConnection}, for non-dual active voice devices,
* adds {@link Connection#EXTRA_ANSWERING_DROPS_FG_CALL} if there are ongoing calls on another
- * subscription (ie phone account handle) than the one passed in.
+ * subscription (ie phone account handle) than the one passed in. For dual active voice devices,
+ * still sets the EXTRA if either subscription has connections that don't support hold.
* @param connection The connection.
* @param phoneAccountHandle The {@link PhoneAccountHandle} the incoming call originated on;
* this is passed in because
@@ -3775,10 +4115,11 @@
*/
public void maybeIndicateAnsweringWillDisconnect(@NonNull TelephonyConnection connection,
@NonNull PhoneAccountHandle phoneAccountHandle) {
- if (mTelephonyManagerProxy.isConcurrentCallsPossible()) {
- return;
- }
if (isCallPresentOnOtherSub(phoneAccountHandle)) {
+ if (mTelephonyManagerProxy.isConcurrentCallsPossible()
+ && allCallsSupportHold(connection)) {
+ return;
+ }
Log.i(this, "maybeIndicateAnsweringWillDisconnect; answering call %s will cause a call "
+ "on another subscription to drop.", connection.getTelecomCallId());
Bundle extras = new Bundle();
@@ -3803,13 +4144,16 @@
/**
* Where there are ongoing calls on another subscription other than the one specified,
- * disconnect these calls for non-DSDA devices. This is used where there is an incoming call on
- * one sub, but there are ongoing calls on another sub which need to be disconnected.
+ * disconnect these calls. This is used where there is an incoming call on one sub, but there
+ * are ongoing calls on another sub which need to be disconnected.
* @param incomingHandle The incoming {@link PhoneAccountHandle}.
+ * @param answeringDropsFgCall Whether for dual-SIM dual active devices, answering the incoming
+ * call should drop the second call.
*/
- public void maybeDisconnectCallsOnOtherSubs(@NonNull PhoneAccountHandle incomingHandle) {
+ public void maybeDisconnectCallsOnOtherSubs(
+ @NonNull PhoneAccountHandle incomingHandle, boolean answeringDropsFgCall) {
Log.i(this, "maybeDisconnectCallsOnOtherSubs: check for calls not on %s", incomingHandle);
- maybeDisconnectCallsOnOtherSubs(getAllConnections(), incomingHandle,
+ maybeDisconnectCallsOnOtherSubs(getAllConnections(), incomingHandle, answeringDropsFgCall,
mTelephonyManagerProxy);
}
@@ -3819,13 +4163,16 @@
* the core functionality.
* @param connections the calls to check.
* @param incomingHandle the incoming handle.
+ * @param answeringDropsFgCall Whether for dual-SIM dual active devices, answering the incoming
+ * call should drop the second call.
* @param telephonyManagerProxy the proxy to the {@link TelephonyManager} instance.
*/
@VisibleForTesting
public static void maybeDisconnectCallsOnOtherSubs(@NonNull Collection<Connection> connections,
@NonNull PhoneAccountHandle incomingHandle,
+ boolean answeringDropsFgCall,
TelephonyManagerProxy telephonyManagerProxy) {
- if (telephonyManagerProxy.isConcurrentCallsPossible()) {
+ if (telephonyManagerProxy.isConcurrentCallsPossible() && !answeringDropsFgCall) {
return;
}
connections.stream()
@@ -3852,6 +4199,19 @@
});
}
+ static boolean isStateActive(Conferenceable conferenceable) {
+ if (conferenceable instanceof Connection) {
+ Connection connection = (Connection) conferenceable;
+ return connection.getState() == Connection.STATE_ACTIVE;
+ } else if (conferenceable instanceof Conference) {
+ Conference conference = (Conference) conferenceable;
+ return conference.getState() == Connection.STATE_ACTIVE;
+ } else {
+ throw new IllegalArgumentException(
+ "isStateActive(): Unexpected conferenceable! " + conferenceable);
+ }
+ }
+
static void onHold(Conferenceable conferenceable) {
if (conferenceable instanceof Connection) {
Connection connection = (Connection) conferenceable;
@@ -4011,7 +4371,7 @@
TelephonyManagerProxy telephonyManagerProxy) {
Conferenceable c = maybeGetFirstConferenceableFromOtherSubscription(
connections, conferences, outgoingHandle, telephonyManagerProxy);
- if (c != null) {
+ if (c != null && isStateActive(c)) {
onHold(c);
return c;
}
@@ -4060,11 +4420,54 @@
private void handleEmergencyCallStartedForSatelliteSOSMessageRecommender(
@NonNull TelephonyConnection connection, @NonNull Phone phone) {
+ if (!phone.getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_SATELLITE)) {
+ return;
+ }
+
if (mSatelliteSOSMessageRecommender == null) {
- mSatelliteSOSMessageRecommender = new SatelliteSOSMessageRecommender(
+ mSatelliteSOSMessageRecommender = new SatelliteSOSMessageRecommender(phone.getContext(),
phone.getContext().getMainLooper());
}
connection.addTelephonyConnectionListener(mEmergencyConnectionSatelliteListener);
- mSatelliteSOSMessageRecommender.onEmergencyCallStarted(connection, phone);
+ mSatelliteSOSMessageRecommender.onEmergencyCallStarted(connection);
+ }
+
+ /**
+ * Check whether making a call is disallowed while using satellite
+ * @param phone phone object whose supported services needs to be checked
+ * @return {@code true} if network does not support calls while using satellite
+ * else {@code false}.
+ */
+ private boolean isCallDisallowedDueToSatellite(Phone phone) {
+ if (!carrierEnabledSatelliteFlag()) {
+ return false;
+ }
+
+ if (phone == null) {
+ return false;
+ }
+
+ ServiceState serviceState = phone.getServiceState();
+ if (serviceState == null) {
+ return false;
+ }
+
+ if (!serviceState.isUsingNonTerrestrialNetwork()) {
+ // Device is not connected to satellite
+ return false;
+ }
+
+ for (NetworkRegistrationInfo nri : serviceState.getNetworkRegistrationInfoList()) {
+ if (nri.isNonTerrestrialNetwork()
+ && nri.getAvailableServices().contains(
+ NetworkRegistrationInfo.SERVICE_TYPE_VOICE)) {
+ // Call is supported while using satellite
+ return false;
+ }
+ }
+
+ // Call is disallowed while using satellite
+ return true;
}
}
diff --git a/src/com/android/services/telephony/domainselection/CrossSimRedialingController.java b/src/com/android/services/telephony/domainselection/CrossSimRedialingController.java
index f1bb78c..d368d46 100644
--- a/src/com/android/services/telephony/domainselection/CrossSimRedialingController.java
+++ b/src/com/android/services/telephony/domainselection/CrossSimRedialingController.java
@@ -29,18 +29,24 @@
import android.os.Message;
import android.os.PersistableBundle;
import android.os.SystemProperties;
+import android.telephony.AccessNetworkConstants;
import android.telephony.Annotation.PreciseDisconnectCauses;
import android.telephony.CarrierConfigManager;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
import android.text.TextUtils;
import android.util.LocalLog;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneFactory;
import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
/** Controls the cross stack redialing. */
public class CrossSimRedialingController extends Handler {
@@ -53,11 +59,11 @@
/**
* Returns whether the number is an emergency number in the given modem slot.
*
- * @param slotId The slot id to be checked.
+ * @param subId The sub id to be checked.
* @param number The number.
* @return {@code true} if the number is an emergency number in the given slot.
*/
- boolean isEmergencyNumber(int slotId, String number);
+ boolean isEmergencyNumber(int subId, String number);
}
@VisibleForTesting
@@ -73,17 +79,23 @@
private EmergencyNumberHelper mEmergencyNumberHelper = new EmergencyNumberHelper() {
@Override
- public boolean isEmergencyNumber(int slotId, String number) {
- // TODO(b/258112541) Add System api to check emergency number per subscription.
+ public boolean isEmergencyNumber(int subId, String number) {
+ number = PhoneNumberUtils.stripSeparators(number);
+ if (TextUtils.isEmpty(number)) return false;
+ Map<Integer, List<EmergencyNumber>> lists = null;
try {
- Phone phone = PhoneFactory.getPhone(slotId);
- if (phone != null
- && phone.getEmergencyNumberTracker() != null
- && phone.getEmergencyNumberTracker().isEmergencyNumber(number)) {
- return true;
- }
- } catch (IllegalStateException e) {
- loge("isEmergencyNumber e=" + e);
+ lists = mTelephonyManager.getEmergencyNumberList();
+ } catch (IllegalStateException ise) {
+ loge("isEmergencyNumber ise=" + ise);
+ } catch (RuntimeException rte) {
+ loge("isEmergencyNumber rte=" + rte);
+ }
+ if (lists == null) return false;
+
+ List<EmergencyNumber> list = lists.get(subId);
+ if (list == null || list.isEmpty()) return false;
+ for (EmergencyNumber eNumber : list) {
+ if (number.equals(eNumber.getNumber())) return true;
}
return false;
}
@@ -135,7 +147,7 @@
* @param selector The instance of {@link EmergencyCallDomainSelector}.
* @param callId The call identifier.
* @param number The dialing number.
- * @param inService Indiates that normal service is available.
+ * @param inService Indicates that normal service is available.
* @param roaming Indicates that it's in roaming or non-domestic network.
* @param modemCount The number of active modem count
*/
@@ -219,12 +231,26 @@
}
/**
+ * Returns whether there is another slot with which normal service is available.
+ *
+ * @return {@code true} if there is another slot with which normal service is available.
+ * {@code false} otherwise.
+ */
+ public boolean isThereOtherSlotInService() {
+ return isThereOtherSlot(true);
+ }
+
+ /**
* Returns whether there is another slot emergency capable.
*
* @return {@code true} if there is another slot emergency capable,
* {@code false} otherwise.
*/
public boolean isThereOtherSlot() {
+ return isThereOtherSlot(false);
+ }
+
+ private boolean isThereOtherSlot(boolean networkRegisteredOnly) {
logi("isThereOtherSlot modemCount=" + mModemCount);
if (mModemCount < 2) return false;
@@ -242,17 +268,49 @@
continue;
}
- if (mEmergencyNumberHelper.isEmergencyNumber(i, mNumber)) {
- logi("isThereOtherSlot index=" + i + ", found");
- return true;
+ int subId = SubscriptionManager.getSubscriptionId(i);
+ if (mEmergencyNumberHelper.isEmergencyNumber(subId, mNumber)) {
+ logi("isThereOtherSlot index=" + i + "(" + subId + "), found");
+ if (networkRegisteredOnly) {
+ if (isNetworkRegistered(subId)) {
+ return true;
+ }
+ } else {
+ return true;
+ }
} else {
- logi("isThereOtherSlot index=" + i + ", not emergency number");
+ logi("isThereOtherSlot index=" + i + "(" + subId + "), not emergency number");
}
}
return false;
}
+ private boolean isNetworkRegistered(int subId) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) return false;
+
+ TelephonyManager tm = mTelephonyManager.createForSubscriptionId(subId);
+ ServiceState ss = tm.getServiceState();
+ if (ss != null) {
+ NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+ if (nri != null && nri.isNetworkRegistered()) {
+ // PS is IN_SERVICE state.
+ return true;
+ }
+ nri = ss.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_CS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+ if (nri != null && nri.isNetworkRegistered()) {
+ // CS is IN_SERVICE state.
+ return true;
+ }
+ }
+ logi("isNetworkRegistered subId=" + subId + " not network registered");
+ return false;
+ }
+
/**
* Caches the configuration.
*/
@@ -278,6 +336,12 @@
+ ", startQuickTimerInService=" + mStartQuickCrossStackTimerWhenInService);
}
+ /** Test purpose only. */
+ @VisibleForTesting
+ public EmergencyNumberHelper getEmergencyNumberHelper() {
+ return mEmergencyNumberHelper;
+ }
+
/** Destroys the instance. */
public void destroy() {
if (DBG) logd("destroy");
diff --git a/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java b/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
index 3388c97..fbe61eb 100644
--- a/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
+++ b/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
@@ -45,6 +45,7 @@
import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT;
import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT;
import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_SCAN_LIMITED_SERVICE_AFTER_VOLTE_FAILURE_BOOL;
import static android.telephony.CarrierConfigManager.ImsEmergency.SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE;
import static android.telephony.CarrierConfigManager.ImsEmergency.VOWIFI_REQUIRES_SETTING_ENABLED;
import static android.telephony.CarrierConfigManager.ImsEmergency.VOWIFI_REQUIRES_VALID_EID;
@@ -53,9 +54,12 @@
import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
import static android.telephony.PreciseDisconnectCause.EMERGENCY_PERM_FAILURE;
import static android.telephony.PreciseDisconnectCause.EMERGENCY_TEMP_FAILURE;
+import static android.telephony.PreciseDisconnectCause.SERVICE_OPTION_NOT_AVAILABLE;
+import static android.telephony.TelephonyManager.DATA_CONNECTED;
import android.annotation.NonNull;
import android.content.Context;
+import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -74,13 +78,11 @@
import android.telephony.DisconnectCause;
import android.telephony.DomainSelectionService;
import android.telephony.DomainSelectionService.SelectionAttributes;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
import android.telephony.NetworkRegistrationInfo;
-import android.telephony.PhoneNumberUtils;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.TransportSelectorCallback;
-import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsManager;
import android.telephony.ims.ImsMmTelManager;
import android.telephony.ims.ImsReasonInfo;
@@ -89,12 +91,11 @@
import android.util.LocalLog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.phone.R;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import java.util.function.IntFunction;
/**
@@ -117,17 +118,8 @@
private static final LocalLog sLocalLog = new LocalLog(LOG_SIZE);
- private static final ArrayList<String> sAllowOnlyWithSimReady = new ArrayList<>();
-
- static {
- // b/177967010, JP
- sAllowOnlyWithSimReady.add("jp"); // Japan
- // b/198393826, DE
- sAllowOnlyWithSimReady.add("de"); // Germany
- // b/230443699, IN and SG
- sAllowOnlyWithSimReady.add("in"); // India
- sAllowOnlyWithSimReady.add("sg"); // Singapore
- }
+ private static List<String> sSimReadyAllowList;
+ private static List<String> sPreferSlotWithNormalServiceList;
/**
* Network callback used to determine whether Wi-Fi is connected or not.
@@ -167,10 +159,11 @@
private @TransportType int mLastTransportType = TRANSPORT_TYPE_INVALID;
private @DomainSelectionService.EmergencyScanType int mScanType;
private @RadioAccessNetworkType List<Integer> mLastPreferredNetworks;
- private boolean mIsTestEmergencyNumber;
private CancellationSignal mCancelSignal;
+ private EmergencyRegistrationResult mLastRegResult;
+ // Members for carrier configuration
private @RadioAccessNetworkType int[] mImsRatsConfig;
private @RadioAccessNetworkType int[] mCsRatsConfig;
private @RadioAccessNetworkType int[] mImsRoamRatsConfig;
@@ -180,8 +173,6 @@
private List<String> mCdmaPreferredNumbers;
private boolean mPreferImsWhenCallsOnCs;
private int mVoWifiRequiresCondition;
- private boolean mIsMonitoringConnectivity;
- private boolean mWiFiAvailable;
private int mScanTimeout;
private int mMaxCellularTimeout;
private int mMaxNumOfVoWifiTries;
@@ -191,6 +182,12 @@
private boolean mRequiresImsRegistration;
private boolean mRequiresVoLteEnabled;
private boolean mLtePreferredAfterNrFailure;
+ private boolean mScanLimitedOnlyAfterVolteFailure;
+
+ // Members for states
+ private boolean mIsMonitoringConnectivity;
+ private boolean mWiFiAvailable;
+ private boolean mWasCsfbAfterPsFailure;
private boolean mTryCsWhenPsFails;
private boolean mTryEpsFallback;
private int mModemCount;
@@ -214,12 +211,14 @@
private final PowerManager.WakeLock mPartialWakeLock;
private final CrossSimRedialingController mCrossSimRedialingController;
+ private final EmergencyCallbackModeHelper mEcbmHelper;
/** Constructor. */
public EmergencyCallDomainSelector(Context context, int slotId, int subId,
@NonNull Looper looper, @NonNull ImsStateTracker imsStateTracker,
@NonNull DestroyListener destroyListener,
- @NonNull CrossSimRedialingController csrController) {
+ @NonNull CrossSimRedialingController csrController,
+ @NonNull EmergencyCallbackModeHelper ecbmHelper) {
super(context, slotId, subId, looper, imsStateTracker, destroyListener, TAG);
mImsStateTracker.addBarringInfoListener(this);
@@ -229,6 +228,7 @@
mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mCrossSimRedialingController = csrController;
+ mEcbmHelper = ecbmHelper;
acquireWakeLock();
}
@@ -246,7 +246,7 @@
break;
case MSG_NETWORK_SCAN_RESULT:
- handleScanResult((EmergencyRegResult) msg.obj);
+ handleScanResult((EmergencyRegistrationResult) msg.obj);
break;
case MSG_MAX_CELLULAR_TIMEOUT:
@@ -264,7 +264,7 @@
*
* @param result The scan result.
*/
- private void handleScanResult(EmergencyRegResult result) {
+ private void handleScanResult(EmergencyRegistrationResult result) {
logi("handleScanResult result=" + result);
if (mLastTransportType == TRANSPORT_TYPE_WLAN) {
@@ -283,7 +283,7 @@
&& (mScanType == DomainSelectionService.SCAN_TYPE_FULL_SERVICE)) {
mScanType = DomainSelectionService.SCAN_TYPE_LIMITED_SERVICE;
mWwanSelectorCallback.onRequestEmergencyNetworkScan(
- mLastPreferredNetworks, mScanType, mCancelSignal,
+ mLastPreferredNetworks, mScanType, false, mCancelSignal,
(regResult) -> {
logi("requestScan-onComplete");
sendMessage(obtainMessage(MSG_NETWORK_SCAN_RESULT, regResult));
@@ -295,9 +295,11 @@
return;
}
+ mLastRegResult = result;
removeMessages(MSG_NETWORK_SCAN_TIMEOUT);
onWwanNetworkTypeSelected(getAccessNetworkType(result));
mCancelSignal = null;
+ maybeModifyScanType(mLastNetworkType);
}
/**
@@ -306,7 +308,7 @@
* @param result The result of network scan.
* @return The selected network type.
*/
- private @RadioAccessNetworkType int getAccessNetworkType(EmergencyRegResult result) {
+ private @RadioAccessNetworkType int getAccessNetworkType(EmergencyRegistrationResult result) {
int accessNetworkType = result.getAccessNetwork();
if (accessNetworkType != EUTRAN) return accessNetworkType;
@@ -324,12 +326,6 @@
}
@Override
- public void cancelSelection() {
- logi("cancelSelection");
- finishSelection();
- }
-
- @Override
public void reselectDomain(SelectionAttributes attr) {
logi("reselectDomain attr=" + attr);
mSelectionAttributes = attr;
@@ -339,12 +335,11 @@
private void reselectDomain() {
logi("reselectDomain tryCsWhenPsFails=" + mTryCsWhenPsFails);
- int cause = mSelectionAttributes.getCsDisconnectCause();
+ int cause = getDisconnectCause();
mCrossSimRedialingController.notifyCallFailure(cause);
- // TODO(b/258112541) make EMERGENCY_PERM_FAILURE and EMERGENCY_TEMP_FAILURE public api
- if (cause == EMERGENCY_PERM_FAILURE
- || cause == EMERGENCY_TEMP_FAILURE) {
+ if ((cause == EMERGENCY_TEMP_FAILURE && mCrossSimRedialingController.isThereOtherSlot())
+ || cause == EMERGENCY_PERM_FAILURE) {
logi("reselectDomain should redial on the other subscription");
terminateSelectionForCrossSimRedialing(cause == EMERGENCY_PERM_FAILURE);
return;
@@ -356,22 +351,27 @@
return;
}
- if (mIsTestEmergencyNumber) {
- selectDomainForTestEmergencyNumber();
- return;
- }
-
if (mTryCsWhenPsFails) {
mTryCsWhenPsFails = false;
// Initial state was CSFB available and dial PS failed.
// Dial CS for CSFB instead of scanning with CS preferred network list.
logi("reselectDomain tryCs=" + accessNetworkTypeToString(mCsNetworkType));
if (mCsNetworkType != UNKNOWN) {
+ mWasCsfbAfterPsFailure = true;
onWwanNetworkTypeSelected(mCsNetworkType);
return;
}
}
+ if (mWasCsfbAfterPsFailure) {
+ mWasCsfbAfterPsFailure = false;
+ if (cause == SERVICE_OPTION_NOT_AVAILABLE) {
+ // b/299875872, combined attach but EXTENDED_SERVICE_REQUEST failed.
+ // Try CS preferred scan instead of PS preferred scan.
+ mLastNetworkType = EUTRAN;
+ }
+ }
+
if (mMaxCellularTimerExpired) {
if (mLastTransportType == TRANSPORT_TYPE_WWAN
&& maybeDialOverWlan()) {
@@ -394,8 +394,28 @@
mDomainSelected = false;
}
+ private int getDisconnectCause() {
+ int cause = mSelectionAttributes.getCsDisconnectCause();
+
+ ImsReasonInfo reasonInfo = mSelectionAttributes.getPsDisconnectCause();
+ if (reasonInfo != null) {
+ switch (reasonInfo.getCode()) {
+ case ImsReasonInfo.CODE_EMERGENCY_TEMP_FAILURE:
+ cause = EMERGENCY_TEMP_FAILURE;
+ break;
+ case ImsReasonInfo.CODE_EMERGENCY_PERM_FAILURE:
+ cause = EMERGENCY_PERM_FAILURE;
+ break;
+ default:
+ break;
+ }
+ }
+ return cause;
+ }
+
private void reselectDomainInternal() {
post(() -> {
+ if (mDestroyed) return;
requestScan(true, false, true);
mDomainSelected = false;
});
@@ -425,7 +445,7 @@
logi("selectDomain attr=" + attr);
mTransportSelectorCallback = cb;
mSelectionAttributes = attr;
- mIsTestEmergencyNumber = isTestEmergencyNumber(attr.getNumber());
+ mLastRegResult = mSelectionAttributes.getEmergencyRegistrationResult();
TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
mModemCount = tm.getActiveModemCount();
@@ -435,6 +455,7 @@
private void startDomainSelection() {
logi("startDomainSelection modemCount=" + mModemCount);
+ readResourceConfiguration();
updateCarrierConfiguration();
mDomainSelectionRequested = true;
startCrossStackTimer();
@@ -469,12 +490,37 @@
selectDomain();
}
+ private boolean isSimReady() {
+ if (!SubscriptionManager.isValidSubscriptionId(getSubId())) return false;
+ TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+ return tm.getSimState(getSlotId()) == TelephonyManager.SIM_STATE_READY;
+ }
+
/**
* Caches the configuration.
*/
private void updateCarrierConfiguration() {
CarrierConfigManager configMgr = mContext.getSystemService(CarrierConfigManager.class);
- PersistableBundle b = configMgr.getConfigForSubId(getSubId());
+ PersistableBundle b = configMgr.getConfigForSubId(getSubId(),
+ KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY,
+ KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY,
+ KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY,
+ KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY,
+ KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY,
+ KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY,
+ KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL,
+ KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT,
+ KEY_EMERGENCY_SCAN_TIMER_SEC_INT,
+ KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT,
+ KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT,
+ KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL,
+ KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT,
+ KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT,
+ KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL,
+ KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL,
+ KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL,
+ KEY_SCAN_LIMITED_SERVICE_AFTER_VOLTE_FAILURE_BOOL,
+ KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY);
if (b == null) {
b = CarrierConfigManager.getDefaultConfig();
}
@@ -483,11 +529,6 @@
b.getIntArray(KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY);
mImsRoamRatsConfig = b.getIntArray(
KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY);
- if (!SubscriptionManager.isValidSubscriptionId(getSubId())) {
- // Default configuration includes only EUTRAN . In case of no SIM, add NGRAN.
- mImsRatsConfig = new int[] { EUTRAN, NGRAN };
- mImsRoamRatsConfig = new int[] { EUTRAN, NGRAN };
- }
mCsRatsConfig =
b.getIntArray(KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY);
@@ -509,6 +550,8 @@
mRequiresVoLteEnabled = b.getBoolean(KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL);
mLtePreferredAfterNrFailure = b.getBoolean(
KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL);
+ mScanLimitedOnlyAfterVolteFailure = b.getBoolean(
+ KEY_SCAN_LIMITED_SERVICE_AFTER_VOLTE_FAILURE_BOOL);
String[] numbers = b.getStringArray(KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY);
if (mImsRatsConfig == null) mImsRatsConfig = new int[0];
@@ -544,6 +587,7 @@
+ ", requiresImsReg=" + mRequiresImsRegistration
+ ", requiresVoLteEnabled=" + mRequiresVoLteEnabled
+ ", ltePreferredAfterNr=" + mLtePreferredAfterNrFailure
+ + ", scanLimitedOnly=" + mScanLimitedOnlyAfterVolteFailure
+ ", cdmaPreferredNumbers=" + arrayToString(numbers));
mCdmaPreferredNumbers = Arrays.asList(numbers);
@@ -557,29 +601,76 @@
}
}
+ /**
+ * Caches the resource configuration.
+ */
+ private void readResourceConfiguration() {
+ if (sSimReadyAllowList == null) {
+ sSimReadyAllowList = readResourceConfiguration(
+ R.array.config_countries_require_sim_for_emergency);
+ }
+ logi("readResourceConfiguration simReadyCountries=" + sSimReadyAllowList);
+
+ if (sPreferSlotWithNormalServiceList == null) {
+ sPreferSlotWithNormalServiceList = readResourceConfiguration(
+ R.array.config_countries_prefer_normal_service_capable_subscription);
+ }
+ logi("readResourceConfiguration preferNormalServiceCountries="
+ + sPreferSlotWithNormalServiceList);
+ }
+
+ private List<String> readResourceConfiguration(int id) {
+ logi("readResourceConfiguration id=" + id);
+
+ List<String> resource = null;
+ try {
+ resource = Arrays.asList(mContext.getResources().getStringArray(id));
+ } catch (Resources.NotFoundException nfe) {
+ loge("readResourceConfiguration exception=" + nfe);
+ } catch (NullPointerException npe) {
+ loge("readResourceConfiguration exception=" + npe);
+ } finally {
+ if (resource == null) {
+ resource = new ArrayList<String>();
+ }
+ }
+ return resource;
+ }
+
+ /** For test purpose only */
+ @VisibleForTesting
+ public void clearResourceConfiguration() {
+ sSimReadyAllowList = null;
+ sPreferSlotWithNormalServiceList = null;
+ }
+
private void selectDomain() {
// State updated right after creation.
if (!mDomainSelectionRequested) return;
- // Emergency network scan requested has not been completed.
- if (mIsScanRequested) return;
-
- // Domain selection completed, {@link #reselectDomain()} will restart domain selection.
- if (mDomainSelected) return;
-
if (!mBarringInfoReceived || !mImsRegStateReceived || !mMmTelCapabilitiesReceived) {
logi("selectDomain not received"
+ " BarringInfo, IMS registration state, or MMTEL capabilities");
return;
}
- if (!allowEmergencyCalls(mSelectionAttributes.getEmergencyRegResult())) {
+ // The statements below should be executed only once to select domain from initial state.
+ // Next domain selection shall be triggered by reselectDomain().
+ // However, selectDomain() can be called by change of IMS service state and Barring status
+ // at any time. mIsScanRequested and mDomainSelected are not enough since there are cases
+ // when neither mIsScanRequested nor mDomainSelected is set though selectDomain() has been
+ // executed already.
+ // Reset mDomainSelectionRequested to avoid redundant execution of selectDomain().
+ mDomainSelectionRequested = false;
+
+ if (!allowEmergencyCalls(mSelectionAttributes.getEmergencyRegistrationResult())) {
// Detected the country and found that emergency calls are not allowed with this slot.
terminateSelectionPermanentlyForSlot();
return;
}
- if (isWifiPreferred()) {
+ if (isWifiPreferred()
+ || isInEmergencyCallbackModeOnWlan()) {
onWlanSelected();
return;
}
@@ -592,8 +683,12 @@
}
private void selectDomainFromInitialState() {
- if (mIsTestEmergencyNumber) {
- selectDomainForTestEmergencyNumber();
+ if (mDestroyed) return;
+
+ if (isInEmergencyCallbackModeOnPsWwan()) {
+ logi("selectDomain PS cellular connected in ECBM");
+ mPsNetworkType = EUTRAN;
+ onWwanNetworkTypeSelected(mPsNetworkType);
return;
}
@@ -601,13 +696,28 @@
boolean psInService = isPsInService();
if (!csInService && !psInService) {
- mPsNetworkType = getSelectablePsNetworkType(false);
- logi("selectDomain limited service ps=" + accessNetworkTypeToString(mPsNetworkType));
- if (mPsNetworkType == UNKNOWN) {
- requestScan(true);
- } else {
- onWwanNetworkTypeSelected(mPsNetworkType);
+ if (maybeRedialOnTheOtherSlotInNormalService()) {
+ return;
}
+ mCsNetworkType = getSelectableCsNetworkType();
+ mPsNetworkType = getSelectablePsNetworkType(false);
+ logi("selectDomain limited service ps=" + accessNetworkTypeToString(mPsNetworkType)
+ + ", cs=" + accessNetworkTypeToString(mCsNetworkType));
+ if (!isInRoaming()
+ && (mPreferredNetworkScanType
+ == CarrierConfigManager.ImsEmergency.SCAN_TYPE_FULL_SERVICE)) {
+ requestScan(true);
+ return;
+ }
+ // If NGRAN, request scan to trigger emergency registration.
+ if (mPsNetworkType == EUTRAN) {
+ onWwanNetworkTypeSelected(mPsNetworkType);
+ } else if (mCsNetworkType != UNKNOWN) {
+ onWwanNetworkTypeSelected(mCsNetworkType);
+ } else {
+ requestScan(true);
+ }
+ maybeModifyScanType(mLastNetworkType);
return;
}
@@ -624,7 +734,8 @@
logi("selectDomain CS={" + csInService + ", " + accessNetworkTypeToString(mCsNetworkType)
+ "}, PS={" + psInService + ", " + accessNetworkTypeToString(mPsNetworkType) + "}");
if (csAvailable && psAvailable) {
- if (mPreferImsWhenCallsOnCs || isImsRegisteredWithVoiceCapability()) {
+ if (mSelectionAttributes.isExitedFromAirplaneMode()
+ || mPreferImsWhenCallsOnCs || isImsRegisteredWithVoiceCapability()) {
mTryCsWhenPsFails = true;
onWwanNetworkTypeSelected(mPsNetworkType);
} else if (isDeactivatedSim()) {
@@ -635,7 +746,8 @@
}
} else if (psAvailable) {
mTryEpsFallback = (mPsNetworkType == NGRAN) && isEpsFallbackAvailable();
- if (!mRequiresImsRegistration || isImsRegisteredWithVoiceCapability()) {
+ if (mSelectionAttributes.isExitedFromAirplaneMode()
+ || !mRequiresImsRegistration || isImsRegisteredWithVoiceCapability()) {
onWwanNetworkTypeSelected(mPsNetworkType);
} else if (isDeactivatedSim()) {
// Deactivated SIM but PS is in service and supports emergency calls.
@@ -650,7 +762,8 @@
onWwanNetworkTypeSelected(mCsNetworkType);
} else {
// PS is in service but not supports emergency calls.
- if (mRequiresImsRegistration && !isImsRegisteredWithVoiceCapability()) {
+ if (!mSelectionAttributes.isExitedFromAirplaneMode()
+ && mRequiresImsRegistration && !isImsRegisteredWithVoiceCapability()) {
// Carrier configuration requires IMS registration for emergency services over PS,
// but not registered. Try CS emergency call.
requestScan(true, true);
@@ -659,6 +772,7 @@
requestScan(true);
}
}
+ maybeModifyScanType(mLastNetworkType);
}
/**
@@ -693,21 +807,21 @@
mCancelSignal = new CancellationSignal();
// In case dialing over Wi-Fi has failed, do not the change the domain preference.
- if (!wifiFailed) {
- mLastPreferredNetworks = getNextPreferredNetworks(csPreferred, mTryEpsFallback,
- !startVoWifiTimer);
+ if (!wifiFailed || mLastPreferredNetworks == null) {
+ mLastPreferredNetworks = getNextPreferredNetworks(csPreferred, mTryEpsFallback);
}
mTryEpsFallback = false;
if (isInRoaming()
- && (mPreferredNetworkScanType == DomainSelectionService.SCAN_TYPE_FULL_SERVICE)) {
+ && (mPreferredNetworkScanType
+ == CarrierConfigManager.ImsEmergency.SCAN_TYPE_FULL_SERVICE)) {
// FULL_SERVICE only preference is available only when not in roaming.
mScanType = DomainSelectionService.SCAN_TYPE_NO_PREFERENCE;
}
mIsScanRequested = true;
mWwanSelectorCallback.onRequestEmergencyNetworkScan(
- mLastPreferredNetworks, mScanType, mCancelSignal,
+ mLastPreferredNetworks, mScanType, false, mCancelSignal,
(result) -> {
logi("requestScan-onComplete");
sendMessage(obtainMessage(MSG_NETWORK_SCAN_RESULT, result));
@@ -733,13 +847,11 @@
*
* @param csPreferred Indicates whether CS preferred scan is requested.
* @param tryEpsFallback Indicates whether scan requested for EPS fallback.
- * @param lastScanFailed Indicates whether this a scan request due to the failure of last scan
- * request.
* @return The list of preferred network types.
*/
@VisibleForTesting
public @RadioAccessNetworkType List<Integer> getNextPreferredNetworks(boolean csPreferred,
- boolean tryEpsFallback, boolean lastScanFailed) {
+ boolean tryEpsFallback) {
if (mRequiresVoLteEnabled && !isAdvancedCallingSettingEnabled()) {
// Emergency call over IMS is not supported.
logi("getNextPreferredNetworks VoLte setting is not enabled.");
@@ -786,10 +898,13 @@
} else if (csPreferred || mLastNetworkType == EUTRAN || mLastNetworkType == NGRAN) {
if (!csPreferred && mLastNetworkType == NGRAN && mLtePreferredAfterNrFailure) {
// LTE is preferred after dialing over NR failed.
- List<Integer> imsRats = getImsNetworkTypeConfiguration();
- imsRats.remove(Integer.valueOf(NGRAN));
- preferredNetworks = generatePreferredNetworks(imsRats,
+ preferredNetworks = generatePreferredNetworks(getImsNetworkTypeConfiguration(),
getCsNetworkTypeConfiguration());
+ // Make NGRAN have the lowest priority
+ if (preferredNetworks.contains(NGRAN)) {
+ preferredNetworks.remove(Integer.valueOf(NGRAN));
+ preferredNetworks.add(NGRAN);
+ }
} else if (csPriority > NOT_SUPPORTED) {
// PS tried, generate the list with CS preferred.
preferredNetworks = generatePreferredNetworks(getCsNetworkTypeConfiguration(),
@@ -809,21 +924,9 @@
}
}
- // There can be cases that dialing IMS call failed but the modem doesn't know this
- // situation with some vendor solutions. For example, dialing failure due to the
- // emergency registration failure.
- // Remove the current RAT from the scan list to avoid modem select current PLMN.
- // If the scan fails, the next scan will include this RAT again.
- //
- // TODO (b/278183420) Replace this with a better solution by adding indication
- // of call setup failure to the scan request.
- ImsReasonInfo reasonInfo = mSelectionAttributes.getPsDisconnectCause();
- if (!lastScanFailed && reasonInfo != null
- && reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED) {
- logi("getNextPreferredNetworks remove " + mLastNetworkType);
- if (preferredNetworks.size() > 1) {
- preferredNetworks.remove(Integer.valueOf(mLastNetworkType));
- }
+ // Adds NGRAN at the end of the list if SIM is absent or locked and NGRAN is not included.
+ if (!isSimReady() && !preferredNetworks.contains(NGRAN)) {
+ preferredNetworks.add(NGRAN);
}
return preferredNetworks;
@@ -907,7 +1010,8 @@
* @return {@code true} if CS is in service.
*/
private boolean isCsInService() {
- EmergencyRegResult regResult = mSelectionAttributes.getEmergencyRegResult();
+ EmergencyRegistrationResult regResult =
+ mSelectionAttributes.getEmergencyRegistrationResult();
if (regResult == null) return false;
int regState = regResult.getRegState();
@@ -927,7 +1031,12 @@
* @return The network type of the CS network.
*/
private @RadioAccessNetworkType int getSelectableCsNetworkType() {
- EmergencyRegResult regResult = mSelectionAttributes.getEmergencyRegResult();
+ List<Integer> domains = getDomainPreference();
+ if (domains.indexOf(DOMAIN_CS) == NOT_SUPPORTED) {
+ return UNKNOWN;
+ }
+ EmergencyRegistrationResult regResult =
+ mSelectionAttributes.getEmergencyRegistrationResult();
logi("getSelectableCsNetworkType regResult=" + regResult);
if (regResult == null) return UNKNOWN;
@@ -952,7 +1061,8 @@
* @return {@code true} if PS is in service.
*/
private boolean isPsInService() {
- EmergencyRegResult regResult = mSelectionAttributes.getEmergencyRegResult();
+ EmergencyRegistrationResult regResult =
+ mSelectionAttributes.getEmergencyRegistrationResult();
if (regResult == null) return false;
int regState = regResult.getRegState();
@@ -973,7 +1083,12 @@
* @return The network type if the network supports emergency services over PS network.
*/
private @RadioAccessNetworkType int getSelectablePsNetworkType(boolean inService) {
- EmergencyRegResult regResult = mSelectionAttributes.getEmergencyRegResult();
+ List<Integer> domains = getDomainPreference();
+ if (domains.indexOf(DOMAIN_PS_3GPP) == NOT_SUPPORTED) {
+ return UNKNOWN;
+ }
+ EmergencyRegistrationResult regResult =
+ mSelectionAttributes.getEmergencyRegistrationResult();
logi("getSelectablePsNetworkType regResult=" + regResult);
if (regResult == null) return UNKNOWN;
if (mRequiresVoLteEnabled && !isAdvancedCallingSettingEnabled()) {
@@ -1003,7 +1118,8 @@
}
private boolean isEpsFallbackAvailable() {
- EmergencyRegResult regResult = mSelectionAttributes.getEmergencyRegResult();
+ EmergencyRegistrationResult regResult =
+ mSelectionAttributes.getEmergencyRegistrationResult();
if (regResult == null) return false;
List<Integer> ratList = getImsNetworkTypeConfiguration();
@@ -1035,13 +1151,13 @@
* @return {@code true} if emergency call over Wi-Fi allowed.
*/
private boolean isEmcOverWifiSupported() {
- if (SubscriptionManager.isValidSubscriptionId(getSubId())) {
+ if (isSimReady()) {
List<Integer> domains = getDomainPreference();
boolean ret = domains.contains(DOMAIN_PS_NON_3GPP);
logi("isEmcOverWifiSupported " + ret);
return ret;
} else {
- logi("isEmcOverWifiSupported invalid subId");
+ logi("isEmcOverWifiSupported invalid subId or lock state");
}
return false;
}
@@ -1139,7 +1255,8 @@
}
if (!mCdmaPreferredNumbers.isEmpty()) {
- if (mCdmaPreferredNumbers.contains(mSelectionAttributes.getNumber())) {
+ String number = mSelectionAttributes.getAddress().getSchemeSpecificPart();
+ if (mCdmaPreferredNumbers.contains(number)) {
// The number will be dialed over CDMA.
ratList.clear();
ratList.add(new Integer(CDMA2000));
@@ -1170,12 +1287,12 @@
tm = tm.createForSubscriptionId(getSubId());
String netIso = tm.getNetworkCountryIso();
- EmergencyRegResult regResult = mSelectionAttributes.getEmergencyRegResult();
+ EmergencyRegistrationResult regResult = mLastRegResult;
if (regResult != null) {
if (regResult.getRegState() == REGISTRATION_STATE_HOME) return false;
if (regResult.getRegState() == REGISTRATION_STATE_ROAMING) return true;
- String iso = regResult.getIso();
+ String iso = regResult.getCountryIso();
if (!TextUtils.isEmpty(iso)) netIso = iso;
}
@@ -1324,15 +1441,15 @@
}
}
- private boolean allowEmergencyCalls(EmergencyRegResult regResult) {
+ private boolean allowEmergencyCalls(EmergencyRegistrationResult regResult) {
if (mModemCount < 2) return true;
if (regResult == null) {
loge("allowEmergencyCalls null regResult");
return true;
}
- String iso = regResult.getIso();
- if (sAllowOnlyWithSimReady.contains(iso)) {
+ String iso = regResult.getCountryIso();
+ if (sSimReadyAllowList.contains(iso)) {
TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
int simState = tm.getSimState(getSlotId());
if (simState != TelephonyManager.SIM_STATE_READY) {
@@ -1347,6 +1464,20 @@
return true;
}
+ private boolean maybeRedialOnTheOtherSlotInNormalService() {
+ EmergencyRegistrationResult regResult =
+ mSelectionAttributes.getEmergencyRegistrationResult();
+ if (regResult == null) return false;
+
+ String iso = regResult.getCountryIso();
+ if (sPreferSlotWithNormalServiceList.contains(iso)
+ && mCrossSimRedialingController.isThereOtherSlotInService()) {
+ terminateSelectionForCrossSimRedialing(false);
+ return true;
+ }
+ return false;
+ }
+
private void terminateSelectionPermanentlyForSlot() {
logi("terminateSelectionPermanentlyForSlot");
terminateSelection(true);
@@ -1361,11 +1492,6 @@
mTransportSelectorCallback.onSelectionTerminated(permanent
? DisconnectCause.EMERGENCY_PERM_FAILURE
: DisconnectCause.EMERGENCY_TEMP_FAILURE);
-
- if (mIsScanRequested && mCancelSignal != null) {
- mCancelSignal.cancel();
- mCancelSignal = null;
- }
}
/** Starts the cross stack timer. */
@@ -1375,7 +1501,8 @@
if (mModemCount == 1) return;
- EmergencyRegResult regResult = mSelectionAttributes.getEmergencyRegResult();
+ EmergencyRegistrationResult regResult =
+ mSelectionAttributes.getEmergencyRegistrationResult();
if (regResult != null) {
int regState = regResult.getRegState();
@@ -1387,8 +1514,9 @@
inRoaming = (regState == REGISTRATION_STATE_ROAMING) || isInRoaming();
}
+ String number = mSelectionAttributes.getAddress().getSchemeSpecificPart();
mCrossSimRedialingController.startTimer(mContext, this, mSelectionAttributes.getCallId(),
- mSelectionAttributes.getNumber(), inService, inRoaming, mModemCount);
+ number, inService, inRoaming, mModemCount);
}
/** Notifies that the cross stack redilaing timer has been expired. */
@@ -1403,6 +1531,15 @@
terminateSelectionForCrossSimRedialing(false);
}
+ private void maybeModifyScanType(int selectedNetworkType) {
+ if ((mPreferredNetworkScanType
+ != CarrierConfigManager.ImsEmergency.SCAN_TYPE_FULL_SERVICE)
+ && mScanLimitedOnlyAfterVolteFailure
+ && (selectedNetworkType == EUTRAN)) {
+ mScanType = DomainSelectionService.SCAN_TYPE_LIMITED_SERVICE;
+ }
+ }
+
private static String arrayToString(int[] intArray, IntFunction<String> func) {
int length = intArray.length;
StringBuilder sb = new StringBuilder("{");
@@ -1504,35 +1641,16 @@
}
}
- private void selectDomainForTestEmergencyNumber() {
- logi("selectDomainForTestEmergencyNumber");
- if (isImsRegisteredWithVoiceCapability()) {
- onWwanNetworkTypeSelected(EUTRAN);
- } else {
- onWwanNetworkTypeSelected(UTRAN);
- }
+ private boolean isInEmergencyCallbackModeOnWlan() {
+ return mEcbmHelper.isInEmergencyCallbackMode(getSlotId())
+ && mEcbmHelper.getTransportType(getSlotId()) == TRANSPORT_TYPE_WLAN
+ && mEcbmHelper.getDataConnectionState(getSlotId()) == DATA_CONNECTED;
}
- private boolean isTestEmergencyNumber(String number) {
- number = PhoneNumberUtils.stripSeparators(number);
- Map<Integer, List<EmergencyNumber>> list = new HashMap<>();
- try {
- TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
- list = tm.getEmergencyNumberList();
- } catch (IllegalStateException ise) {
- loge("isTestEmergencyNumber ise=" + ise);
- }
-
- for (Integer sub : list.keySet()) {
- for (EmergencyNumber eNumber : list.get(sub)) {
- if (number.equals(eNumber.getNumber())
- && eNumber.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST)) {
- logd("isTestEmergencyNumber: " + number + " is a test emergency number.");
- return true;
- }
- }
- }
- return false;
+ private boolean isInEmergencyCallbackModeOnPsWwan() {
+ return mEcbmHelper.isInEmergencyCallbackMode(getSlotId())
+ && mEcbmHelper.getTransportType(getSlotId()) == TRANSPORT_TYPE_WWAN
+ && mEcbmHelper.getDataConnectionState(getSlotId()) == DATA_CONNECTED;
}
@Override
diff --git a/src/com/android/services/telephony/domainselection/EmergencyCallbackModeHelper.java b/src/com/android/services/telephony/domainselection/EmergencyCallbackModeHelper.java
new file mode 100644
index 0000000..cdf2225
--- /dev/null
+++ b/src/com/android/services/telephony/domainselection/EmergencyCallbackModeHelper.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.domainselection;
+
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL;
+import static android.telephony.SubscriptionManager.EXTRA_SLOT_INDEX;
+import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+import static android.telephony.TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED;
+import static android.telephony.TelephonyManager.EXTRA_PHONE_IN_ECM_STATE;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.SystemProperties;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.CarrierConfigManager;
+import android.telephony.PreciseDataConnectionState;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
+import android.util.ArrayMap;
+import android.util.Log;
+
+/** Helper class to cache emergency data connection state. */
+public class EmergencyCallbackModeHelper extends Handler {
+ private static final String TAG = "EmergencyCallbackModeHelper";
+ private static final boolean DBG = (SystemProperties.getInt("ro.debuggable", 0) == 1);
+
+ /**
+ * TelephonyCallback used to monitor ePDN state.
+ */
+ private static final class DataConnectionStateListener extends TelephonyCallback
+ implements TelephonyCallback.PreciseDataConnectionStateListener {
+
+ private final Handler mHandler;
+ private final TelephonyManager mTelephonyManager;
+ private final int mSubId;
+ private int mTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+ private int mState = TelephonyManager.DATA_UNKNOWN;
+
+ DataConnectionStateListener(Handler handler, TelephonyManager tm, int subId) {
+ mHandler = handler;
+ mTelephonyManager = tm;
+ mSubId = subId;
+ }
+
+ @Override
+ public void onPreciseDataConnectionStateChanged(
+ @NonNull PreciseDataConnectionState dataConnectionState) {
+ ApnSetting apnSetting = dataConnectionState.getApnSetting();
+ if ((apnSetting == null)
+ || ((apnSetting.getApnTypeBitmask() & ApnSetting.TYPE_EMERGENCY) == 0)) {
+ return;
+ }
+ mTransportType = dataConnectionState.getTransportType();
+ mState = dataConnectionState.getState();
+ Log.i(TAG, "onPreciseDataConnectionStateChanged ePDN state=" + mState
+ + ", transport=" + mTransportType);
+ }
+
+ public void registerTelephonyCallback() {
+ TelephonyManager tm = mTelephonyManager.createForSubscriptionId(mSubId);
+ tm.registerTelephonyCallback(mHandler::post, this);
+ }
+
+ public void unregisterTelephonyCallback() {
+ mTelephonyManager.unregisterTelephonyCallback(this);
+ }
+
+ public int getSubId() {
+ return mSubId;
+ }
+
+ public int getTransportType() {
+ return mTransportType;
+ }
+
+ public int getState() {
+ return mState;
+ }
+ }
+
+ private final Context mContext;
+ private final TelephonyManager mTelephonyManager;
+ private final CarrierConfigManager mConfigManager;
+
+ private final ArrayMap<Integer, DataConnectionStateListener>
+ mDataConnectionStateListeners = new ArrayMap<>();
+
+ private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener =
+ (slotIndex, subId, carrierId, specificCarrierId) -> onCarrierConfigChanged(
+ slotIndex, subId, carrierId);
+
+ /**
+ * Creates an instance.
+ *
+ * @param context The Context this is associated with.
+ * @param looper The Looper to run the EmergencyCallbackModeHelper.
+ */
+ public EmergencyCallbackModeHelper(@NonNull Context context, @NonNull Looper looper) {
+ super(looper);
+
+ mContext = context;
+ mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ mConfigManager = context.getSystemService(CarrierConfigManager.class);
+ mConfigManager.registerCarrierConfigChangeListener(this::post,
+ mCarrierConfigChangeListener);
+ }
+
+ /**
+ * Returns whether it is in emergency callback mode.
+ *
+ * @param slotIndex The logical SIM slot index.
+ * @return true if it is in emergency callback mode.
+ */
+ public boolean isInEmergencyCallbackMode(int slotIndex) {
+ DataConnectionStateListener listener =
+ mDataConnectionStateListeners.get(Integer.valueOf(slotIndex));
+ if (listener == null) return false;
+
+ Intent intent = mContext.registerReceiver(null,
+ new IntentFilter(ACTION_EMERGENCY_CALLBACK_MODE_CHANGED));
+ if (intent != null
+ && ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(intent.getAction())) {
+ boolean inEcm = intent.getBooleanExtra(EXTRA_PHONE_IN_ECM_STATE, false);
+ int index = intent.getIntExtra(EXTRA_SLOT_INDEX, INVALID_SIM_SLOT_INDEX);
+ Log.i(TAG, "isInEmergencyCallbackMode inEcm=" + inEcm + ", slotIndex=" + index);
+ return inEcm && (slotIndex == index);
+ }
+ return false;
+ }
+
+ /**
+ * Returns the transport type of emergency data connection.
+ *
+ * @param slotIndex The logical SIM slot index.
+ * @return the transport type of emergency data connection.
+ */
+ public int getTransportType(int slotIndex) {
+ DataConnectionStateListener listener =
+ mDataConnectionStateListeners.get(Integer.valueOf(slotIndex));
+ if (listener == null) return AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+ Log.i(TAG, "getTransportType " + listener.getTransportType());
+ return listener.getTransportType();
+ }
+
+ /**
+ * Returns the data connection state.
+ *
+ * @param slotIndex The logical SIM slot index.
+ * @return the data connection state.
+ */
+ public int getDataConnectionState(int slotIndex) {
+ DataConnectionStateListener listener =
+ mDataConnectionStateListeners.get(Integer.valueOf(slotIndex));
+ if (listener == null) return TelephonyManager.DATA_UNKNOWN;
+ Log.i(TAG, "getDataConnectionState " + listener.getState());
+ return listener.getState();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ default:
+ super.handleMessage(msg);
+ break;
+ }
+ }
+
+ private void onCarrierConfigChanged(int slotIndex, int subId, int carrierId) {
+ Log.i(TAG, "onCarrierConfigChanged slotIndex=" + slotIndex
+ + ", subId=" + subId + ", carrierId=" + carrierId);
+
+ if (slotIndex < 0) {
+ return;
+ }
+
+ PersistableBundle b = mConfigManager.getConfigForSubId(subId,
+ KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL);
+
+ if (b.getBoolean(KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL)) {
+ // ECBM supported
+ DataConnectionStateListener listener =
+ mDataConnectionStateListeners.get(Integer.valueOf(slotIndex));
+
+ // Remove stale listener.
+ if (listener != null && listener.getSubId() != subId) {
+ listener.unregisterTelephonyCallback();
+ listener = null;
+ }
+
+ if (listener == null) {
+ listener = new DataConnectionStateListener(this, mTelephonyManager, subId);
+ listener.registerTelephonyCallback();
+ mDataConnectionStateListeners.put(Integer.valueOf(slotIndex), listener);
+ Log.i(TAG, "onCarrierConfigChanged register callback");
+ }
+ } else {
+ // ECBM not supported
+ DataConnectionStateListener listener =
+ mDataConnectionStateListeners.get(Integer.valueOf(slotIndex));
+ if (listener != null) {
+ listener.unregisterTelephonyCallback();
+ mDataConnectionStateListeners.remove(Integer.valueOf(slotIndex));
+ Log.i(TAG, "onCarrierConfigChanged unregister callback");
+ }
+ }
+ }
+
+ /** Destroys the instance. */
+ public void destroy() {
+ if (DBG) Log.d(TAG, "destroy");
+ mConfigManager.unregisterCarrierConfigChangeListener(mCarrierConfigChangeListener);
+ mDataConnectionStateListeners.forEach((k, v) -> v.unregisterTelephonyCallback());
+ }
+}
diff --git a/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelector.java b/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelector.java
index aef193b..7f28b04 100644
--- a/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelector.java
+++ b/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelector.java
@@ -18,12 +18,17 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.os.CancellationSignal;
import android.os.Looper;
+import android.os.Message;
import android.os.PersistableBundle;
import android.telephony.AccessNetworkConstants;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
import android.telephony.BarringInfo;
import android.telephony.CarrierConfigManager;
import android.telephony.DataSpecificRegistrationInfo;
+import android.telephony.DomainSelectionService;
+import android.telephony.EmergencyRegistrationResult;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
@@ -31,11 +36,14 @@
import com.android.internal.annotations.VisibleForTesting;
+import java.util.List;
+
/**
* Implements an emergency SMS domain selector for sending an emergency SMS.
*/
public class EmergencySmsDomainSelector extends SmsDomainSelector implements
ImsStateTracker.BarringInfoListener, ImsStateTracker.ServiceStateListener {
+ protected static final int EVENT_EMERGENCY_NETWORK_SCAN_RESULT = 201;
/**
* Stores the configuration value of
* {@link CarrierConfigManager#KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL}.
@@ -46,6 +54,8 @@
private boolean mServiceStateReceived;
private BarringInfo mBarringInfo;
private boolean mBarringInfoReceived;
+ private boolean mEmergencyNetworkScanInProgress;
+ private CancellationSignal mEmergencyNetworkScanSignal;
public EmergencySmsDomainSelector(Context context, int slotId, int subId,
@NonNull Looper looper, @NonNull ImsStateTracker imsStateTracker,
@@ -68,6 +78,18 @@
}
@Override
+ public void handleMessage(@NonNull Message msg) {
+ switch (msg.what) {
+ case EVENT_EMERGENCY_NETWORK_SCAN_RESULT:
+ handleEmergencyNetworkScanResult((EmergencyRegistrationResult) msg.obj);
+ break;
+ default:
+ super.handleMessage(msg);
+ break;
+ }
+ }
+
+ @Override
public void finishSelection() {
super.finishSelection();
mServiceStateReceived = false;
@@ -75,6 +97,12 @@
mBarringInfoReceived = false;
mBarringInfo = null;
mEmergencySmsOverImsSupportedByConfig = null;
+
+ mEmergencyNetworkScanInProgress = false;
+ if (mEmergencyNetworkScanSignal != null) {
+ mEmergencyNetworkScanSignal.cancel();
+ mEmergencyNetworkScanSignal = null;
+ }
}
@Override
@@ -111,7 +139,7 @@
* when {@link CarrierConfigManager#KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL} is set
* to true.
*/
- if (isEmergencySmsOverImsSupportedIfLteLimitedOrInService()) {
+ if (isEmergencySmsOverImsSupportedIfNetworkLimitedOrInService()) {
/**
* Emergency SMS should be supported via emergency PDN.
* If this condition is false, then need to fallback to CS network
@@ -139,60 +167,132 @@
return;
}
+ if (mEmergencyNetworkScanInProgress) {
+ logi("Emergency network scan is in progress.");
+ return;
+ }
+
logi("selectDomain: " + mImsStateTracker.imsStateToString());
if (isSmsOverImsAvailable()) {
- boolean isEmergencySmsOverImsSupportedIfLteLimitedOrInService =
- isEmergencySmsOverImsSupportedIfLteLimitedOrInService();
+ boolean isEmergencySmsOverImsSupportedIfNetworkLimitedOrInService =
+ isEmergencySmsOverImsSupportedIfNetworkLimitedOrInService();
if (mImsStateTracker.isImsRegisteredOverWlan()) {
/**
* When {@link CarrierConfigManager#KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL}
- * is set to true, the emergency SMS supports on the LTE network using the
+ * is set to true, the emergency SMS supports on the LTE/NR network using the
* emergency PDN. As of now, since the emergency SMS doesn't use the emergency PDN
* over WLAN, the domain selector reports the domain as WLAN only if
- * {@code isEmergencySmsOverImsSupportedIfLteLimitedOrInService} is set to false
+ * {@code isEmergencySmsOverImsSupportedIfNetworkLimitedOrInService} is set to false
* and IMS is registered over WLAN.
* Otherwise, the domain selector reports the domain as WWAN.
*/
- if (!isEmergencySmsOverImsSupportedIfLteLimitedOrInService) {
+ if (!isEmergencySmsOverImsSupportedIfNetworkLimitedOrInService) {
notifyWlanSelected(false);
return;
}
logi("DomainSelected: WLAN >> WWAN");
}
- notifyWwanSelected(NetworkRegistrationInfo.DOMAIN_PS,
- isEmergencySmsOverImsSupportedIfLteLimitedOrInService);
+
+ /**
+ * The request of emergency network scan triggers the modem to request the emergency
+ * service fallback because NR network doesn't support the emergency service.
+ */
+ if (isEmergencySmsOverImsSupportedIfNetworkLimitedOrInService
+ && isNrEmergencyServiceFallbackRequired()) {
+ requestEmergencyNetworkScan(List.of(AccessNetworkType.EUTRAN));
+ } else {
+ notifyWwanSelected(NetworkRegistrationInfo.DOMAIN_PS,
+ isEmergencySmsOverImsSupportedIfNetworkLimitedOrInService);
+ }
} else {
notifyWwanSelected(NetworkRegistrationInfo.DOMAIN_CS, false);
}
}
+ private void requestEmergencyNetworkScan(List<Integer> preferredNetworks) {
+ mEmergencyNetworkScanInProgress = true;
+
+ if (mWwanSelectorCallback == null) {
+ mTransportSelectorCallback.onWwanSelected((callback) -> {
+ mWwanSelectorCallback = callback;
+ requestEmergencyNetworkScanInternal(preferredNetworks);
+ });
+ } else {
+ requestEmergencyNetworkScanInternal(preferredNetworks);
+ }
+ }
+
+ private void requestEmergencyNetworkScanInternal(List<Integer> preferredNetworks) {
+ logi("requestEmergencyNetworkScan: preferredNetworks=" + preferredNetworks);
+ mEmergencyNetworkScanSignal = new CancellationSignal();
+ mWwanSelectorCallback.onRequestEmergencyNetworkScan(
+ preferredNetworks,
+ DomainSelectionService.SCAN_TYPE_FULL_SERVICE, false,
+ mEmergencyNetworkScanSignal,
+ (regResult) -> {
+ logi("requestEmergencyNetworkScan-onComplete");
+ obtainMessage(EVENT_EMERGENCY_NETWORK_SCAN_RESULT, regResult).sendToTarget();
+ });
+ }
+
+ /**
+ * Handles the emergency network scan result.
+ *
+ * This triggers the emergency service fallback to modem when the emergency service is not
+ * supported but the emergency service fallback is supported in the current network.
+ *
+ * @param regResult The emergency registration result that is triggered
+ * by the emergency network scan.
+ */
+ private void handleEmergencyNetworkScanResult(EmergencyRegistrationResult regResult) {
+ logi("handleEmergencyNetworkScanResult: " + regResult);
+
+ mEmergencyNetworkScanInProgress = false;
+ mEmergencyNetworkScanSignal = null;
+
+ int accessNetworkType = regResult.getAccessNetwork();
+ int domain = NetworkRegistrationInfo.DOMAIN_CS;
+
+ if (accessNetworkType == AccessNetworkType.NGRAN) {
+ domain = NetworkRegistrationInfo.DOMAIN_PS;
+ } else if (accessNetworkType == AccessNetworkType.EUTRAN) {
+ if (regResult.getDomain() == NetworkRegistrationInfo.DOMAIN_CS) {
+ logi("PS emergency service is not supported in LTE network.");
+ } else {
+ domain = NetworkRegistrationInfo.DOMAIN_PS;
+ }
+ }
+
+ notifyWwanSelected(domain, (domain == NetworkRegistrationInfo.DOMAIN_PS));
+ }
+
/**
* Checks if the emergency SMS messages over IMS is available according to the carrier
* configuration and the current network states.
*/
private boolean isImsEmergencySmsAvailable() {
- boolean isEmergencySmsOverImsSupportedIfLteLimitedOrInService =
- isEmergencySmsOverImsSupportedIfLteLimitedOrInService();
+ boolean isEmergencySmsOverImsSupportedIfNetworkLimitedOrInService =
+ isEmergencySmsOverImsSupportedIfNetworkLimitedOrInService();
boolean networkAvailable = isNetworkAvailableForImsEmergencySms();
logi("isImsEmergencySmsAvailable: "
- + "emergencySmsOverIms=" + isEmergencySmsOverImsSupportedIfLteLimitedOrInService
+ + "emergencySmsOverIms=" + isEmergencySmsOverImsSupportedIfNetworkLimitedOrInService
+ ", mmTelFeatureAvailable=" + mImsStateTracker.isMmTelFeatureAvailable()
+ ", networkAvailable=" + networkAvailable);
- return isEmergencySmsOverImsSupportedIfLteLimitedOrInService
+ return isEmergencySmsOverImsSupportedIfNetworkLimitedOrInService
&& mImsStateTracker.isMmTelFeatureAvailable()
&& networkAvailable;
}
/**
- * Checks if sending emergency SMS messages over IMS is supported when in LTE/limited LTE
- * (Emergency only) service mode from the carrier configuration.
+ * Checks if sending emergency SMS messages over IMS is supported when in the network(LTE/NR)
+ * normal/limited(Emergency only) service mode from the carrier configuration.
*/
- private boolean isEmergencySmsOverImsSupportedIfLteLimitedOrInService() {
+ private boolean isEmergencySmsOverImsSupportedIfNetworkLimitedOrInService() {
if (mEmergencySmsOverImsSupportedByConfig == null) {
CarrierConfigManager ccm = mContext.getSystemService(CarrierConfigManager.class);
@@ -257,7 +357,8 @@
*/
private boolean isNetworkAvailableForImsEmergencySms() {
return isLteEmergencyAvailableInService()
- || isLteEmergencyAvailableInLimitedService();
+ || isLteEmergencyAvailableInLimitedService()
+ || isNrEmergencyAvailable();
}
/**
@@ -280,6 +381,22 @@
}
/**
+ * Checks if the emergency service fallback is supported by the network.
+ *
+ * @return {@code true} if the emergency service fallback is supported by the network,
+ * {@code false} otherwise.
+ */
+ private boolean isEmergencyServiceFallbackSupported(@NonNull NetworkRegistrationInfo regInfo) {
+ final DataSpecificRegistrationInfo dsRegInfo = regInfo.getDataSpecificInfo();
+ if (dsRegInfo != null) {
+ final VopsSupportInfo vopsSupportInfo = dsRegInfo.getVopsSupportInfo();
+ return vopsSupportInfo != null
+ && vopsSupportInfo.isEmergencyServiceFallbackSupported();
+ }
+ return false;
+ }
+
+ /**
* Checks if the emergency service is allowed (not barred) by the network.
*
* This checks if SystemInformationBlockType2 includes the ac-BarringInfo and
@@ -297,4 +414,45 @@
mBarringInfo.getBarringServiceInfo(BarringInfo.BARRING_SERVICE_TYPE_EMERGENCY);
return !bsi.isBarred();
}
+
+ /**
+ * Checks if the emergency service fallback is available in the NR network
+ * because the emergency service is not supported.
+ */
+ private boolean isNrEmergencyServiceFallbackRequired() {
+ if (mServiceState == null) {
+ return false;
+ }
+
+ final NetworkRegistrationInfo regInfo = mServiceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+ if (regInfo != null
+ && regInfo.getAccessNetworkTechnology() == TelephonyManager.NETWORK_TYPE_NR
+ && regInfo.isRegistered()) {
+ return !isEmergencyServiceSupported(regInfo)
+ && isEmergencyServiceFallbackSupported(regInfo);
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the emergency service is available in the NR network.
+ */
+ private boolean isNrEmergencyAvailable() {
+ if (mServiceState == null) {
+ return false;
+ }
+
+ final NetworkRegistrationInfo regInfo = mServiceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+ if (regInfo != null
+ && regInfo.getAccessNetworkTechnology() == TelephonyManager.NETWORK_TYPE_NR
+ && regInfo.isRegistered()) {
+ return isEmergencyServiceSupported(regInfo)
+ || isEmergencyServiceFallbackSupported(regInfo);
+ }
+ return false;
+ }
}
diff --git a/src/com/android/services/telephony/domainselection/NormalCallDomainSelector.java b/src/com/android/services/telephony/domainselection/NormalCallDomainSelector.java
index f176d90..31a1cc2 100644
--- a/src/com/android/services/telephony/domainselection/NormalCallDomainSelector.java
+++ b/src/com/android/services/telephony/domainselection/NormalCallDomainSelector.java
@@ -28,12 +28,13 @@
import android.telephony.DisconnectCause;
import android.telephony.DomainSelectionService.SelectionAttributes;
import android.telephony.NetworkRegistrationInfo;
+import android.telephony.PhoneNumberUtils;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TransportSelectorCallback;
import android.telephony.ims.ImsReasonInfo;
-import com.android.internal.telephony.domainselection.NormalCallDomainSelectionConnection;
+import com.android.internal.annotations.VisibleForTesting;
/**
* Implements domain selector for outgoing non-emergency calls.
@@ -43,8 +44,15 @@
private static final String LOG_TAG = "NCDS";
- private boolean mStopDomainSelection = true;
- private ServiceState mServiceState;
+ @VisibleForTesting
+ protected enum SelectorState {
+ ACTIVE,
+ INACTIVE,
+ DESTROYED
+ };
+
+ protected SelectorState mSelectorState = SelectorState.INACTIVE;
+ protected ServiceState mServiceState;
private boolean mImsRegStateReceived;
private boolean mMmTelCapabilitiesReceived;
private boolean mReselectDomain;
@@ -67,9 +75,10 @@
public void selectDomain(SelectionAttributes attributes, TransportSelectorCallback callback) {
mSelectionAttributes = attributes;
mTransportSelectorCallback = callback;
- mStopDomainSelection = false;
+ mSelectorState = SelectorState.ACTIVE;
if (callback == null) {
+ mSelectorState = SelectorState.INACTIVE;
loge("Invalid params: TransportSelectorCallback is null");
return;
}
@@ -80,7 +89,7 @@
return;
}
- int subId = attributes.getSubId();
+ int subId = attributes.getSubscriptionId();
boolean validSubscriptionId = SubscriptionManager.isValidSubscriptionId(subId);
if (attributes.getSelectorType() != SELECTOR_TYPE_CALLING || attributes.isEmergency()
|| !validSubscriptionId) {
@@ -96,6 +105,7 @@
logd("NormalCallDomainSelection triggered. Sub-id:" + subId);
post(() -> selectDomain());
} else {
+ mSelectorState = SelectorState.INACTIVE;
loge("Subscription-ids doesn't match. This instance is associated with sub-id:"
+ getSubId() + ", requested sub-id:" + subId);
// TODO: Throw anamoly here. This condition should never occur.
@@ -112,21 +122,44 @@
@Override
public synchronized void finishSelection() {
logd("finishSelection");
- mStopDomainSelection = true;
- mImsStateTracker.removeServiceStateListener(this);
- mImsStateTracker.removeImsStateListener(this);
- mSelectionAttributes = null;
- mTransportSelectorCallback = null;
+ if (mSelectorState == SelectorState.ACTIVE) {
+ // This is cancel selection case.
+ cancelSelection();
+ return;
+ }
+
+ if (mSelectorState != SelectorState.DESTROYED) {
+ mImsStateTracker.removeServiceStateListener(this);
+ mImsStateTracker.removeImsStateListener(this);
+ mSelectionAttributes = null;
+ mTransportSelectorCallback = null;
+ destroy();
+ }
}
- /**
- * Cancel an ongoing selection operation. It is up to the DomainSelectionService
- * to clean up all ongoing operations with the framework.
- */
@Override
+ public void destroy() {
+ logd("destroy");
+ switch (mSelectorState) {
+ case INACTIVE:
+ mSelectorState = SelectorState.DESTROYED;
+ super.destroy();
+ break;
+
+ case ACTIVE:
+ loge("destroy is called when selector state is in ACTIVE state");
+ cancelSelection();
+ break;
+
+ case DESTROYED:
+ super.destroy();
+ break;
+ }
+ }
+
public void cancelSelection() {
logd("cancelSelection");
- mStopDomainSelection = true;
+ mSelectorState = SelectorState.INACTIVE;
mReselectDomain = false;
if (mTransportSelectorCallback != null) {
mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_CANCELED);
@@ -165,7 +198,7 @@
private void notifyPsSelected() {
logd("notifyPsSelected");
- mStopDomainSelection = true;
+ mSelectorState = SelectorState.INACTIVE;
if (mImsStateTracker.isImsRegisteredOverWlan()) {
logd("WLAN selected");
mTransportSelectorCallback.onWlanSelected(false);
@@ -193,7 +226,7 @@
private void notifyCsSelected() {
logd("notifyCsSelected");
- mStopDomainSelection = true;
+ mSelectorState = SelectorState.INACTIVE;
if (mWwanSelectorCallback == null) {
mTransportSelectorCallback.onWwanSelected((callback) -> {
mWwanSelectorCallback = callback;
@@ -215,7 +248,7 @@
}
private void notifySelectionTerminated(@DisconnectCauses int cause) {
- mStopDomainSelection = true;
+ mSelectorState = SelectorState.INACTIVE;
if (mTransportSelectorCallback != null) {
mTransportSelectorCallback.onSelectionTerminated(cause);
finishSelection();
@@ -233,7 +266,8 @@
PersistableBundle config = null;
if (configManager != null) {
- config = configManager.getConfigForSubId(mSelectionAttributes.getSubId());
+ config = configManager.getConfigForSubId(mSelectionAttributes.getSubscriptionId(),
+ new String[] {CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL});
}
return (config != null)
@@ -260,7 +294,8 @@
PersistableBundle config = null;
if (configManager != null) {
- config = configManager.getConfigForSubId(mSelectionAttributes.getSubId());
+ config = configManager.getConfigForSubId(mSelectionAttributes.getSubscriptionId(),
+ new String[] {CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL});
}
return (config != null)
@@ -277,7 +312,7 @@
}
private synchronized void selectDomain() {
- if (mStopDomainSelection || mSelectionAttributes == null
+ if (mSelectorState != SelectorState.ACTIVE || mSelectionAttributes == null
|| mTransportSelectorCallback == null) {
logd("Domain Selection is stopped.");
return;
@@ -292,9 +327,9 @@
// IMS -> CS
ImsReasonInfo imsReasonInfo = mSelectionAttributes.getPsDisconnectCause();
if (mReselectDomain && imsReasonInfo != null) {
- logd("PsDisconnectCause:" + imsReasonInfo.mCode);
+ logd("PsDisconnectCause:" + imsReasonInfo.getCode());
mReselectDomain = false;
- if (imsReasonInfo.mCode == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED) {
+ if (imsReasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED) {
if (isOutOfService()) {
loge("Cannot place call in current ServiceState: " + mServiceState.getState());
notifySelectionTerminated(DisconnectCause.OUT_OF_SERVICE);
@@ -375,9 +410,8 @@
// Handle voice call.
if (mImsStateTracker.isImsVoiceCapable()) {
logd("IMS is voice capable");
- // TODO(b/266175810) Remove this dependency.
- if (NormalCallDomainSelectionConnection
- .isWpsCall(mSelectionAttributes.getNumber())) {
+ String number = mSelectionAttributes.getAddress().getSchemeSpecificPart();
+ if (PhoneNumberUtils.isWpsCallNumber(number)) {
handleWpsCall();
} else {
notifyPsSelected();
@@ -393,4 +427,9 @@
}
}
}
+
+ @VisibleForTesting
+ public SelectorState getSelectorState() {
+ return mSelectorState;
+ }
}
diff --git a/src/com/android/services/telephony/domainselection/OWNERS b/src/com/android/services/telephony/domainselection/OWNERS
new file mode 100644
index 0000000..2a76770
--- /dev/null
+++ b/src/com/android/services/telephony/domainselection/OWNERS
@@ -0,0 +1,9 @@
+# automatically inherit owners from fw/opt/telephony
+
+hwangoo@google.com
+forestchoi@google.com
+avinashmp@google.com
+mkoon@google.com
+seheele@google.com
+radhikaagrawal@google.com
+jdyou@google.com
diff --git a/src/com/android/services/telephony/domainselection/SmsDomainSelector.java b/src/com/android/services/telephony/domainselection/SmsDomainSelector.java
index 95b04e2..4e41e43 100644
--- a/src/com/android/services/telephony/domainselection/SmsDomainSelector.java
+++ b/src/com/android/services/telephony/domainselection/SmsDomainSelector.java
@@ -71,12 +71,6 @@
}
@Override
- public void cancelSelection() {
- logi("cancelSelection");
- finishSelection();
- }
-
- @Override
public void reselectDomain(@NonNull SelectionAttributes attr) {
if (isDomainSelectionRequested()) {
// The domain selection is already requested,
diff --git a/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java b/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
index 3a8fc86..8f1a319 100644
--- a/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
+++ b/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
@@ -17,7 +17,7 @@
package com.android.services.telephony.domainselection;
import android.annotation.NonNull;
-import android.annotation.SuppressLint;
+import android.annotation.Nullable;
import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
@@ -37,6 +37,7 @@
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -71,7 +72,8 @@
@SelectorType int selectorType, boolean isEmergency, @NonNull Looper looper,
@NonNull ImsStateTracker imsStateTracker,
@NonNull DomainSelectorBase.DestroyListener listener,
- @NonNull CrossSimRedialingController crossSimRedialingController);
+ @NonNull CrossSimRedialingController crossSimRedialingController,
+ @NonNull EmergencyCallbackModeHelper emergencyCallbackModeHelper);
}
private static final class DefaultDomainSelectorFactory implements DomainSelectorFactory {
@@ -80,7 +82,8 @@
@SelectorType int selectorType, boolean isEmergency, @NonNull Looper looper,
@NonNull ImsStateTracker imsStateTracker,
@NonNull DomainSelectorBase.DestroyListener listener,
- @NonNull CrossSimRedialingController crossSimRedialingController) {
+ @NonNull CrossSimRedialingController crossSimRedialingController,
+ @NonNull EmergencyCallbackModeHelper emergencyCallbackModeHelper) {
DomainSelectorBase selector = null;
logi("create-DomainSelector: slotId=" + slotId + ", subId=" + subId
@@ -91,7 +94,8 @@
case SELECTOR_TYPE_CALLING:
if (isEmergency) {
selector = new EmergencyCallDomainSelector(context, slotId, subId, looper,
- imsStateTracker, listener, crossSimRedialingController);
+ imsStateTracker, listener, crossSimRedialingController,
+ emergencyCallbackModeHelper);
} else {
selector = new NormalCallDomainSelector(context, slotId, subId, looper,
imsStateTracker, listener);
@@ -187,7 +191,7 @@
// Persistent Logging
private static final LocalLog sEventLog = new LocalLog(20);
- private final Context mContext;
+ private Context mContext;
// Map of slotId -> ImsStateTracker
private final SparseArray<ImsStateTracker> mImsStateTrackers = new SparseArray<>(2);
private final List<DomainSelectorContainer> mDomainSelectorContainers = new ArrayList<>();
@@ -195,21 +199,29 @@
private final DomainSelectorFactory mDomainSelectorFactory;
private Handler mServiceHandler;
private CrossSimRedialingController mCrossSimRedialingController;
+ private EmergencyCallbackModeHelper mEmergencyCallbackModeHelper;
- public TelephonyDomainSelectionService(Context context) {
- this(context, ImsStateTracker::new, new DefaultDomainSelectorFactory());
+ /** Default constructor. */
+ public TelephonyDomainSelectionService() {
+ this(ImsStateTracker::new, new DefaultDomainSelectorFactory(), null);
}
@VisibleForTesting
- public TelephonyDomainSelectionService(Context context,
+ protected TelephonyDomainSelectionService(
@NonNull ImsStateTrackerFactory imsStateTrackerFactory,
- @NonNull DomainSelectorFactory domainSelectorFactory) {
- mContext = context;
+ @NonNull DomainSelectorFactory domainSelectorFactory,
+ @Nullable EmergencyCallbackModeHelper ecbmHelper) {
mImsStateTrackerFactory = imsStateTrackerFactory;
mDomainSelectorFactory = domainSelectorFactory;
+ }
+
+ @Override
+ public void onCreate() {
+ logd("onCreate");
+ mContext = getApplicationContext();
// Create a worker thread for this domain selection service.
- getExecutor();
+ getCreateExecutor();
TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
int activeModemCount = (tm != null) ? tm.getActiveModemCount() : 1;
@@ -224,7 +236,10 @@
loge("Adding OnSubscriptionChangedListener failed");
}
- mCrossSimRedialingController = new CrossSimRedialingController(context, getLooper());
+ mCrossSimRedialingController = new CrossSimRedialingController(mContext, getLooper());
+ if (mEmergencyCallbackModeHelper == null) {
+ mEmergencyCallbackModeHelper = new EmergencyCallbackModeHelper(mContext, getLooper());
+ }
logi("TelephonyDomainSelectionService created");
}
@@ -268,6 +283,11 @@
mCrossSimRedialingController = null;
}
+ if (mEmergencyCallbackModeHelper != null) {
+ mEmergencyCallbackModeHelper.destroy();
+ mEmergencyCallbackModeHelper = null;
+ }
+
if (mServiceHandler != null) {
mServiceHandler.getLooper().quit();
mServiceHandler = null;
@@ -283,14 +303,14 @@
@Override
public void onDomainSelection(@NonNull SelectionAttributes attr,
@NonNull TransportSelectorCallback callback) {
- final int slotId = attr.getSlotId();
- final int subId = attr.getSubId();
+ final int slotId = attr.getSlotIndex();
+ final int subId = attr.getSubscriptionId();
final int selectorType = attr.getSelectorType();
final boolean isEmergency = attr.isEmergency();
ImsStateTracker ist = getImsStateTracker(slotId);
DomainSelectorBase selector = mDomainSelectorFactory.create(mContext, slotId, subId,
selectorType, isEmergency, getLooper(), ist, mDestroyListener,
- mCrossSimRedialingController);
+ mCrossSimRedialingController, mEmergencyCallbackModeHelper);
if (selector != null) {
// Ensures that ImsStateTracker is started before selecting the domain if not started
@@ -299,15 +319,21 @@
addDomainSelector(slotId, selectorType, isEmergency, selector);
} else {
loge("No proper domain selector: " + selectorTypeToString(selectorType));
- callback.onSelectionTerminated(DisconnectCause.ERROR_UNSPECIFIED);
+ // Executed through the service handler to ensure that the callbacks are not called
+ // directly in this execution flow.
+ mServiceHandler.post(() ->
+ callback.onSelectionTerminated(DisconnectCause.ERROR_UNSPECIFIED));
return;
}
- // Notify the caller that the domain selector is created.
- callback.onCreated(selector);
-
- // Performs the domain selection.
- selector.selectDomain(attr, callback);
+ // Executed through the service handler to ensure that the callbacks are not called
+ // directly in this execution flow.
+ mServiceHandler.post(() -> {
+ // Notify the caller that the domain selector is created.
+ callback.onCreated(selector);
+ // Performs the domain selection.
+ selector.selectDomain(attr, callback);
+ });
}
/**
@@ -345,8 +371,15 @@
/**
* Returns an Executor used to execute methods called remotely by the framework.
*/
- @SuppressLint("OnNameExpected")
@Override
+ public @NonNull Executor getCreateExecutor() {
+ return getExecutor();
+ }
+
+ /**
+ * Returns an Executor used to execute methods called remotely by the framework.
+ */
+ @VisibleForTesting
public @NonNull Executor getExecutor() {
if (mServiceHandler == null) {
HandlerThread handlerThread = new HandlerThread(TAG);
@@ -371,6 +404,9 @@
*/
private void handleSubscriptionsChanged() {
SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
+ if (Flags.workProfileApiSplit()) {
+ sm = sm.createForAllUserProfiles();
+ }
List<SubscriptionInfo> subsInfoList =
(sm != null) ? sm.getActiveSubscriptionInfoList() : null;
@@ -463,7 +499,6 @@
switch (selectorType) {
case SELECTOR_TYPE_CALLING: return "CALLING";
case SELECTOR_TYPE_SMS: return "SMS";
- case SELECTOR_TYPE_UT: return "UT";
default: return Integer.toString(selectorType);
}
}
diff --git a/src/com/android/services/telephony/rcs/TelephonyRcsService.java b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
index f6ba40b..63753c2 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.telephony.flags.FeatureFlags;
import com.android.internal.telephony.metrics.RcsStats;
import com.android.internal.util.IndentingPrintWriter;
import com.android.phone.ImsStateCallbackController;
@@ -63,7 +64,8 @@
/**
* @return an instance of {@link UceControllerManager} associated with the slot specified.
*/
- UceControllerManager createUceControllerManager(Context context, int slotId, int subId);
+ UceControllerManager createUceControllerManager(Context context, int slotId, int subId,
+ FeatureFlags featureFlags);
/**
* @return an instance of {@link SipTransportController} for the slot and subscription
@@ -80,8 +82,8 @@
@Override
public UceControllerManager createUceControllerManager(Context context, int slotId,
- int subId) {
- return new UceControllerManager(context, slotId, subId);
+ int subId, FeatureFlags featureFlags) {
+ return new UceControllerManager(context, slotId, subId, featureFlags);
}
@Override
@@ -112,6 +114,7 @@
private final Context mContext;
private final Object mLock = new Object();
+ private final FeatureFlags mFeatureFlags;
private int mNumSlots;
// Maps slot ID -> RcsFeatureController.
@@ -160,23 +163,26 @@
return true;
});
- public TelephonyRcsService(Context context, int numSlots) {
+ public TelephonyRcsService(Context context, int numSlots, FeatureFlags featureFlags) {
mContext = context;
mNumSlots = numSlots;
mFeatureControllers = new SparseArray<>(numSlots);
mSlotToAssociatedSubIds = new SparseArray<>(numSlots);
mRcsUceEnabled = sResourceProxy.getDeviceUceEnabled(mContext);
+ mFeatureFlags = featureFlags;
RcsStats.getInstance().registerUceCallback();
}
@VisibleForTesting
- public TelephonyRcsService(Context context, int numSlots, ResourceProxy resourceProxy) {
+ public TelephonyRcsService(Context context, int numSlots, ResourceProxy resourceProxy,
+ FeatureFlags featureFlags) {
mContext = context;
mNumSlots = numSlots;
mFeatureControllers = new SparseArray<>(numSlots);
mSlotToAssociatedSubIds = new SparseArray<>(numSlots);
sResourceProxy = resourceProxy;
mRcsUceEnabled = sResourceProxy.getDeviceUceEnabled(mContext);
+ mFeatureFlags = featureFlags;
RcsStats.getInstance().registerUceCallback();
}
@@ -310,8 +316,8 @@
private void updateSupportedFeatures(RcsFeatureController c, int slotId, int subId) {
if (isDeviceUceEnabled() && doesSubscriptionSupportPresence(subId)) {
if (c.getFeature(UceControllerManager.class) == null) {
- c.addFeature(mFeatureFactory.createUceControllerManager(mContext, slotId, subId),
- UceControllerManager.class);
+ c.addFeature(mFeatureFactory.createUceControllerManager(
+ mContext, slotId, subId, mFeatureFlags), UceControllerManager.class);
}
} else {
if (c.getFeature(UceControllerManager.class) != null) {
diff --git a/src/com/android/services/telephony/rcs/TransportSipMessageValidator.java b/src/com/android/services/telephony/rcs/TransportSipMessageValidator.java
index b15992e..3a8bdea 100644
--- a/src/com/android/services/telephony/rcs/TransportSipMessageValidator.java
+++ b/src/com/android/services/telephony/rcs/TransportSipMessageValidator.java
@@ -565,8 +565,8 @@
direction);
} else {
//Message sending fail and there is no response.
- mRcsStats.invalidatedMessageResult(mSubId, startLineSegments[0], direction,
- result.restrictedReason);
+ mRcsStats.invalidatedMessageResult(m.getCallIdParameter(), mSubId,
+ startLineSegments[0], direction, result.restrictedReason);
}
} else if (SipMessageParsingUtils.isSipResponse(m.getStartLine())) {
int statusCode = Integer.parseInt(startLineSegments[1]);
diff --git a/src/com/android/services/telephony/rcs/UceControllerManager.java b/src/com/android/services/telephony/rcs/UceControllerManager.java
index 02ae048..b7e12a3 100644
--- a/src/com/android/services/telephony/rcs/UceControllerManager.java
+++ b/src/com/android/services/telephony/rcs/UceControllerManager.java
@@ -32,6 +32,7 @@
import com.android.ims.RcsFeatureManager;
import com.android.ims.rcs.uce.UceController;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlags;
import java.io.PrintWriter;
import java.util.List;
@@ -53,15 +54,17 @@
private final int mSlotId;
private final Context mContext;
private final ExecutorService mExecutorService;
+ private final FeatureFlags mFeatureFlags;
private volatile @Nullable UceController mUceController;
private volatile @Nullable RcsFeatureManager mRcsFeatureManager;
- public UceControllerManager(Context context, int slotId, int subId) {
+ public UceControllerManager(Context context, int slotId, int subId, FeatureFlags featureFlags) {
Log.d(LOG_TAG, "create: slotId=" + slotId + ", subId=" + subId);
mSlotId = slotId;
mContext = context;
mExecutorService = Executors.newSingleThreadExecutor();
+ mFeatureFlags = featureFlags;
initUceController(subId);
}
@@ -70,11 +73,12 @@
*/
@VisibleForTesting
public UceControllerManager(Context context, int slotId, ExecutorService executor,
- UceController uceController) {
+ UceController uceController, FeatureFlags featureFlags) {
mSlotId = slotId;
mContext = context;
mExecutorService = executor;
mUceController = uceController;
+ mFeatureFlags = featureFlags;
}
@Override
@@ -440,7 +444,7 @@
if (mUceController == null) {
// Create new UceController only when the subscription ID is valid.
if (SubscriptionManager.isValidSubscriptionId(newSubId)) {
- mUceController = new UceController(mContext, newSubId);
+ mUceController = new UceController(mContext, newSubId, mFeatureFlags);
}
} else if (mUceController.getSubId() != newSubId) {
// The subscription ID is updated. Remove the old UceController instance.
@@ -448,7 +452,7 @@
mUceController = null;
// Create new UceController only when the subscription ID is valid.
if (SubscriptionManager.isValidSubscriptionId(newSubId)) {
- mUceController = new UceController(mContext, newSubId);
+ mUceController = new UceController(mContext, newSubId, mFeatureFlags);
}
}
}
diff --git a/testapps/GbaTestApp/Android.bp b/testapps/GbaTestApp/Android.bp
index 76e02a0..72f7cc4 100644
--- a/testapps/GbaTestApp/Android.bp
+++ b/testapps/GbaTestApp/Android.bp
@@ -22,7 +22,6 @@
static_libs: [
"androidx.appcompat_appcompat",
"androidx-constraintlayout_constraintlayout",
- "ub-uiautomator",
],
srcs: ["src/**/*.java"],
javacflags: ["-parameters"],
diff --git a/testapps/TestRcsApp/TestApp/Android.bp b/testapps/TestRcsApp/TestApp/Android.bp
index 40254af..ea62925 100644
--- a/testapps/TestRcsApp/TestApp/Android.bp
+++ b/testapps/TestRcsApp/TestApp/Android.bp
@@ -14,7 +14,7 @@
"androidx-constraintlayout_constraintlayout",
"aosp_test_rcs_client_base",
"androidx.appcompat_appcompat",
- "libphonenumber-platform"
+ "libphonenumber-platform",
],
libs: ["org.apache.http.legacy"],
@@ -25,12 +25,15 @@
sdk_version: "system_current",
min_sdk_version: "30",
- required: ["privapp-permissions-com.google.android.sample.rcsclient.xml"]
+ required: ["privapp-permissions-com.google.android.sample.rcsclient.xml"],
+ lint: {
+ baseline_filename: "lint-baseline.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",
+ sub_dir: "permissions",
product_specific: true,
}
diff --git a/testapps/TestRcsApp/TestApp/AndroidManifest.xml b/testapps/TestRcsApp/TestApp/AndroidManifest.xml
index 35a0822..3ec9b69 100644
--- a/testapps/TestRcsApp/TestApp/AndroidManifest.xml
+++ b/testapps/TestRcsApp/TestApp/AndroidManifest.xml
@@ -56,6 +56,7 @@
<activity android:name=".ContactListActivity" />
<activity android:name=".ProvisioningActivity" />
<activity android:name=".FileUploadActivity" />
+ <activity android:name=".carrierLock.CarrieLockModeListActivity" />
<provider
android:name=".util.ChatProvider"
@@ -117,6 +118,10 @@
</intent-filter>
</service>
+ <provider
+ android:name=".carrierLock.CarrierLockProvider"
+ android:authorities="com.sample.lockProvider"
+ android:exported="true" />
</application>
</manifest>
diff --git a/testapps/TestRcsApp/TestApp/lint-baseline.xml b/testapps/TestRcsApp/TestApp/lint-baseline.xml
index 8971388..872a626 100644
--- a/testapps/TestRcsApp/TestApp/lint-baseline.xml
+++ b/testapps/TestRcsApp/TestApp/lint-baseline.xml
@@ -1,26 +1,444 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#bootstrapAuthenticationRequest`"
- errorLine1=" telephonyManager.bootstrapAuthenticationRequest(mUiccType,"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Class requires API level 31 (current min is 30): `android.telephony.ims.stub.DelegateConnectionMessageCallback`"
+ errorLine1=" new DelegateConnectionMessageCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java"
- line="130"
- column="30"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="87"
+ column="17"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.gba.UaSecurityProtocolIdentifier.Builder#build`"
- errorLine1=" UaSecurityProtocolIdentifier spId = builder.build();"
- errorLine2=" ~~~~~">
+ message="Class requires API level 31 (current min is 30): `android.telephony.ims.stub.DelegateConnectionStateCallback`"
+ errorLine1=" new DelegateConnectionStateCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="117"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.FeatureTagState#getFeatureTag`"
+ errorLine1=' stringBuilder.append(featureTagState.getFeatureTag()).append(" ").append('
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="148"
+ column="62"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.FeatureTagState#getState`"
+ errorLine1=" featureTagState.getState());"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="149"
+ column="49"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.DelegateRegistrationState#getRegisteredFeatureTags`"
+ errorLine1=" Set<String> registeredFt = registrationState.getRegisteredFeatureTags();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="151"
+ column="66"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ImsManager#getSipDelegateManager`"
+ errorLine1=" mSipDelegateManager = imsManager.getSipDelegateManager(mDefaultSmsSubId);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="220"
+ column="46"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#createSipDelegate`"
+ errorLine1=" mSipDelegateManager.createSipDelegate(new DelegateRequest(featureTags),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="231"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `new android.telephony.ims.DelegateRequest`"
+ errorLine1=" mSipDelegateManager.createSipDelegate(new DelegateRequest(featureTags),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="231"
+ column="59"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#destroySipDelegate`"
+ errorLine1=" mSipDelegateManager.destroySipDelegate(mSipDelegateConnection,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="247"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#destroySipDelegate`"
+ errorLine1=" mSipDelegateManager.destroySipDelegate(mSipDelegateConnection,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="322"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getVersion`"
+ errorLine1=' + "mVersion=" + config.getVersion()'
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="332"
+ column="40"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getTransportType`"
+ errorLine1=' + ", \n\tmTransportType=" + config.getTransportType()'
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="333"
+ column="52"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getLocalAddress`"
+ errorLine1=' + ", \n\tmLocalIpAddr=" + config.getLocalAddress()'
+ errorLine2=" ~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="334"
+ column="50"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServerAddress`"
+ errorLine1=' + ", \n\tmSipServerAddr=" + config.getSipServerAddress()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="335"
+ column="52"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#isSipCompactFormEnabled`"
+ errorLine1=' + ", \n\tmIsSipCompactFormEnabled=" + config.isSipCompactFormEnabled()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="336"
+ column="62"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#isSipKeepaliveEnabled`"
+ errorLine1=' + ", \n\tmIsSipKeepaliveEnabled=" + config.isSipKeepaliveEnabled()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="337"
+ column="60"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getMaxUdpPayloadSizeBytes`"
+ errorLine1=' + ", \n\tmMaxUdpPayloadSize=" + config.getMaxUdpPayloadSizeBytes()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="338"
+ column="56"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicUserIdentifier`"
+ errorLine1=' + ", \n\tmPublicUserIdentifier=" + config.getPublicUserIdentifier()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="339"
+ column="59"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPrivateUserIdentifier`"
+ errorLine1=' + ", \n\tmPrivateUserIdentifier=" + config.getPrivateUserIdentifier()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="340"
+ column="60"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getHomeDomain`"
+ errorLine1=' + ", \n\tmHomeDomain=" + config.getHomeDomain()'
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="341"
+ column="49"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getImei`"
+ errorLine1=' + ", \n\tmImei=" + config.getImei()'
+ errorLine2=" ~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="342"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicGruuUri`"
+ errorLine1=' + ", \n\tmGruu=" + config.getPublicGruuUri()'
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="343"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAuthenticationHeader`"
+ errorLine1=' + ", \n\tmSipAuthHeader=" + config.getSipAuthenticationHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="344"
+ column="52"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAuthenticationNonce`"
+ errorLine1=' + ", \n\tmSipAuthNonce=" + config.getSipAuthenticationNonce()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="345"
+ column="51"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServiceRouteHeader`"
+ errorLine1=' + ", \n\tmServiceRouteHeader=" + config.getSipServiceRouteHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="346"
+ column="57"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPathHeader`"
+ errorLine1=' + ", \n\tmPathHeader=" + config.getSipPathHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="347"
+ column="49"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipUserAgentHeader`"
+ errorLine1=' + ", \n\tmUserAgentHeader=" + config.getSipUserAgentHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="348"
+ column="54"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipContactUserParameter`"
+ errorLine1=' + ", \n\tmContactUserParam=" + config.getSipContactUserParameter()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="349"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPaniHeader`"
+ errorLine1=' + ", \n\tmPaniHeader=" + config.getSipPaniHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="350"
+ column="49"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPlaniHeader`"
+ errorLine1=' + ", \n\tmPlaniHeader=" + config.getSipPlaniHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="351"
+ column="50"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipCniHeader`"
+ errorLine1=' + ", \n\tmCniHeader=" + config.getSipCniHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="352"
+ column="48"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAssociatedUriHeader`"
+ errorLine1=' + ", \n\tmAssociatedUriHeader=" + config.getSipAssociatedUriHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="353"
+ column="58"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getIpSecConfiguration`"
+ errorLine1=' + ", \n\tmIpSecConfiguration=" + config.getIpSecConfiguration()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="354"
+ column="57"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getNatSocketAddress`"
+ errorLine1=" + ", \n\tmNatConfiguration=" + config.getNatSocketAddress() + '}';"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="355"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Class requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager.RcsProvisioningCallback`"
+ errorLine1=" new RcsProvisioningCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
+ line="89"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#setRcsClientConfiguration`"
+ errorLine1=" mProvisioningManager.setRcsClientConfiguration(getDefaultClientConfiguration());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
+ line="220"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#registerRcsProvisioningCallback`"
+ errorLine1=" mProvisioningManager.registerRcsProvisioningCallback(getMainExecutor(), mCallback);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
+ line="221"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `new android.telephony.ims.RcsClientConfiguration`"
+ errorLine1=" return new RcsClientConfiguration("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
+ line="231"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#unregisterRcsProvisioningCallback`"
+ errorLine1=" mProvisioningManager.unregisterRcsProvisioningCallback(mCallback);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
+ line="348"
+ column="34"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `new android.telephony.gba.UaSecurityProtocolIdentifier.Builder`"
+ errorLine1=" new UaSecurityProtocolIdentifier.Builder();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java"
- line="129"
- column="57"/>
+ line="120"
+ column="21"/>
</issue>
<issue
@@ -58,134 +476,68 @@
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.DelegateRegistrationState#getRegisteredFeatureTags`"
- errorLine1=" Set<String> registeredFt = registrationState.getRegisteredFeatureTags();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.gba.UaSecurityProtocolIdentifier.Builder#build`"
+ errorLine1=" UaSecurityProtocolIdentifier spId = builder.build();"
+ errorLine2=" ~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="151"
- column="66"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java"
+ line="129"
+ column="57"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.FeatureTagState#getFeatureTag`"
- errorLine1=' stringBuilder.append(featureTagState.getFeatureTag()).append(" ").append('
- errorLine2=" ~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#bootstrapAuthenticationRequest`"
+ errorLine1=" telephonyManager.bootstrapAuthenticationRequest(mUiccType,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="148"
- column="62"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java"
+ line="130"
+ column="30"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.FeatureTagState#getState`"
- errorLine1=" featureTagState.getState());"
- errorLine2=" ~~~~~~~~">
+ message="Cast to `BootstrapAuthenticationCallback` requires API level 31 (current min is 30)"
+ errorLine1=" new BootstrapAuthenticationCallback() {"
+ errorLine2=" ^">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="149"
- column="49"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java"
+ line="135"
+ column="21"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ImsManager#getSipDelegateManager`"
- errorLine1=" mSipDelegateManager = imsManager.getSipDelegateManager(mDefaultSmsSubId);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyManager.BootstrapAuthenticationCallback`"
+ errorLine1=" new BootstrapAuthenticationCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="220"
- column="46"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java"
+ line="135"
+ column="25"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#isRcsVolteSingleRegistrationCapable`"
- errorLine1=" boolean capable = mProvisioningManager.isRcsVolteSingleRegistrationCapable();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Class requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager.RcsProvisioningCallback`"
+ errorLine1=" new RcsProvisioningCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
- line="204"
- column="60"/>
+ line="80"
+ column="17"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#isRcsVolteSingleRegistrationCapable`"
- errorLine1=" mProvisioningManager.isRcsVolteSingleRegistrationCapable();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `new android.telephony.ims.RcsClientConfiguration`"
+ errorLine1=" return new RcsClientConfiguration("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
- line="166"
- column="34"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#registerRcsProvisioningCallback`"
- errorLine1=" mProvisioningManager.registerRcsProvisioningCallback(mExecutorService,"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
- line="181"
- column="42"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#registerRcsProvisioningCallback`"
- errorLine1=" mProvisioningManager.registerRcsProvisioningCallback(getMainExecutor(), mCallback);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
- line="221"
- column="38"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#setRcsClientConfiguration`"
- errorLine1=" mProvisioningManager.setRcsClientConfiguration(getDefaultClientConfiguration());"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
- line="180"
- column="42"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#setRcsClientConfiguration`"
- errorLine1=" mProvisioningManager.setRcsClientConfiguration(getDefaultClientConfiguration());"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
- line="220"
- column="38"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#unregisterRcsProvisioningCallback`"
- errorLine1=" mProvisioningManager.unregisterRcsProvisioningCallback(mCallback);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
- line="195"
- column="38"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#unregisterRcsProvisioningCallback`"
- errorLine1=" mProvisioningManager.unregisterRcsProvisioningCallback(mCallback);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
- line="348"
- column="34"/>
+ line="106"
+ column="16"/>
</issue>
<issue
@@ -201,122 +553,122 @@
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getContactUri`"
- errorLine1=" b.append(t.getContactUri());"
- errorLine2=" ~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#isRcsVolteSingleRegistrationCapable`"
+ errorLine1=" mProvisioningManager.isRcsVolteSingleRegistrationCapable();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="220"
- column="28"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
+ line="166"
+ column="34"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getServiceCapabilities`"
- errorLine1=" t.getServiceCapabilities();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#setRcsClientConfiguration`"
+ errorLine1=" mProvisioningManager.setRcsClientConfiguration(getDefaultClientConfiguration());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="227"
- column="31"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
+ line="180"
+ column="42"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getServiceCapabilities`"
- errorLine1=" if (t.getServiceCapabilities() != null) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#registerRcsProvisioningCallback`"
+ errorLine1=" mProvisioningManager.registerRcsProvisioningCallback(mExecutorService,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="225"
- column="23"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
+ line="181"
+ column="42"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getServiceId`"
- errorLine1=" b.append(t.getServiceId());"
- errorLine2=" ~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#unregisterRcsProvisioningCallback`"
+ errorLine1=" mProvisioningManager.unregisterRcsProvisioningCallback(mCallback);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="222"
- column="28"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
+ line="195"
+ column="38"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getServiceVersion`"
- errorLine1=" b.append(t.getServiceVersion());"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#isRcsVolteSingleRegistrationCapable`"
+ errorLine1=" boolean capable = mProvisioningManager.isRcsVolteSingleRegistrationCapable();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="224"
- column="28"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
+ line="204"
+ column="60"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#getSupportedDuplexModes`"
- errorLine1=" b.append(servCaps.getSupportedDuplexModes());"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="233"
- column="39"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#getUnsupportedDuplexModes`"
- errorLine1=" b.append(servCaps.getUnsupportedDuplexModes());"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="235"
- column="39"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#isAudioCapable`"
- errorLine1=" b.append(servCaps.isAudioCapable());"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="229"
- column="39"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#isVideoCapable`"
- errorLine1=" b.append(servCaps.isVideoCapable());"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="231"
- column="39"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactUceCapability#getCapabilityMechanism`"
- errorLine1=" if (c.getCapabilityMechanism() == RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="216"
- column="15"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactUceCapability#getCapabilityTuples`"
- errorLine1=" for (RcsContactPresenceTuple t : c.getCapabilityTuples()) {"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsUceAdapter#requestCapabilities`"
+ errorLine1=" mImsRcsManager.getUceAdapter().requestCapabilities(contactList, getMainExecutor(),"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="218"
+ line="95"
+ column="48"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Cast to `CapabilitiesCallback` requires API level 31 (current min is 30)"
+ errorLine1=" new RcsUceAdapter.CapabilitiesCallback() {"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="96"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Class requires API level 31 (current min is 30): `android.telephony.ims.RcsUceAdapter.CapabilitiesCallback`"
+ errorLine1=" new RcsUceAdapter.CapabilitiesCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="96"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsUceAdapter#requestAvailability`"
+ errorLine1=" mImsRcsManager.getUceAdapter().requestAvailability(contactList.get(0),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="135"
+ column="48"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Cast to `CapabilitiesCallback` requires API level 31 (current min is 30)"
+ errorLine1=" getMainExecutor(), new RcsUceAdapter.CapabilitiesCallback() {"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="136"
+ column="44"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Class requires API level 31 (current min is 30): `android.telephony.ims.RcsUceAdapter.CapabilitiesCallback`"
+ errorLine1=" getMainExecutor(), new RcsUceAdapter.CapabilitiesCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="136"
column="48"/>
</issue>
@@ -355,475 +707,123 @@
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsUceAdapter#requestAvailability`"
- errorLine1=" mImsRcsManager.getUceAdapter().requestAvailability(contactList.get(0),"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactUceCapability#getCapabilityMechanism`"
+ errorLine1=" if (c.getCapabilityMechanism() == RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="216"
+ column="15"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactUceCapability#getCapabilityTuples`"
+ errorLine1=" for (RcsContactPresenceTuple t : c.getCapabilityTuples()) {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="135"
+ line="218"
column="48"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsUceAdapter#requestCapabilities`"
- errorLine1=" mImsRcsManager.getUceAdapter().requestCapabilities(contactList, getMainExecutor(),"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getContactUri`"
+ errorLine1=" b.append(t.getContactUri());"
+ errorLine2=" ~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="95"
- column="48"/>
+ line="220"
+ column="28"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getHomeDomain`"
- errorLine1=' + ", \n\tmHomeDomain=" + config.getHomeDomain()'
- errorLine2=" ~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getServiceId`"
+ errorLine1=" b.append(t.getServiceId());"
+ errorLine2=" ~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="341"
- column="49"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="222"
+ column="28"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getImei`"
- errorLine1=' + ", \n\tmImei=" + config.getImei()'
- errorLine2=" ~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getServiceVersion`"
+ errorLine1=" b.append(t.getServiceVersion());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="342"
- column="43"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="224"
+ column="28"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getIpSecConfiguration`"
- errorLine1=' + ", \n\tmIpSecConfiguration=" + config.getIpSecConfiguration()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getServiceCapabilities`"
+ errorLine1=" if (t.getServiceCapabilities() != null) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="354"
- column="57"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="225"
+ column="23"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getLocalAddress`"
- errorLine1=' + ", \n\tmLocalIpAddr=" + config.getLocalAddress()'
- errorLine2=" ~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getServiceCapabilities`"
+ errorLine1=" t.getServiceCapabilities();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="334"
- column="50"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="227"
+ column="31"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getMaxUdpPayloadSizeBytes`"
- errorLine1=' + ", \n\tmMaxUdpPayloadSize=" + config.getMaxUdpPayloadSizeBytes()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#isAudioCapable`"
+ errorLine1=" b.append(servCaps.isAudioCapable());"
+ errorLine2=" ~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="338"
- column="56"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="229"
+ column="39"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getNatSocketAddress`"
- errorLine1=" + ", \n\tmNatConfiguration=" + config.getNatSocketAddress() + '}';"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#isVideoCapable`"
+ errorLine1=" b.append(servCaps.isVideoCapable());"
+ errorLine2=" ~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="355"
- column="55"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPrivateUserIdentifier`"
- errorLine1=' + ", \n\tmPrivateUserIdentifier=" + config.getPrivateUserIdentifier()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="340"
- column="60"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicGruuUri`"
- errorLine1=' + ", \n\tmGruu=" + config.getPublicGruuUri()'
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="343"
- column="43"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicUserIdentifier`"
- errorLine1=' + ", \n\tmPublicUserIdentifier=" + config.getPublicUserIdentifier()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="339"
- column="59"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAssociatedUriHeader`"
- errorLine1=' + ", \n\tmAssociatedUriHeader=" + config.getSipAssociatedUriHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="353"
- column="58"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAuthenticationHeader`"
- errorLine1=' + ", \n\tmSipAuthHeader=" + config.getSipAuthenticationHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="344"
- column="52"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAuthenticationNonce`"
- errorLine1=' + ", \n\tmSipAuthNonce=" + config.getSipAuthenticationNonce()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="345"
- column="51"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipCniHeader`"
- errorLine1=' + ", \n\tmCniHeader=" + config.getSipCniHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="352"
- column="48"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipContactUserParameter`"
- errorLine1=' + ", \n\tmContactUserParam=" + config.getSipContactUserParameter()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="349"
- column="55"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPaniHeader`"
- errorLine1=' + ", \n\tmPaniHeader=" + config.getSipPaniHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="350"
- column="49"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPathHeader`"
- errorLine1=' + ", \n\tmPathHeader=" + config.getSipPathHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="347"
- column="49"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPlaniHeader`"
- errorLine1=' + ", \n\tmPlaniHeader=" + config.getSipPlaniHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="351"
- column="50"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServerAddress`"
- errorLine1=' + ", \n\tmSipServerAddr=" + config.getSipServerAddress()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="335"
- column="52"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServiceRouteHeader`"
- errorLine1=' + ", \n\tmServiceRouteHeader=" + config.getSipServiceRouteHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="346"
- column="57"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipUserAgentHeader`"
- errorLine1=' + ", \n\tmUserAgentHeader=" + config.getSipUserAgentHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="348"
- column="54"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getTransportType`"
- errorLine1=' + ", \n\tmTransportType=" + config.getTransportType()'
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="333"
- column="52"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getVersion`"
- errorLine1=' + "mVersion=" + config.getVersion()'
- errorLine2=" ~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="332"
- column="40"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#isSipCompactFormEnabled`"
- errorLine1=' + ", \n\tmIsSipCompactFormEnabled=" + config.isSipCompactFormEnabled()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="336"
- column="62"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#isSipKeepaliveEnabled`"
- errorLine1=' + ", \n\tmIsSipKeepaliveEnabled=" + config.isSipKeepaliveEnabled()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="337"
- column="60"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#createSipDelegate`"
- errorLine1=" mSipDelegateManager.createSipDelegate(new DelegateRequest(featureTags),"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
line="231"
- column="41"/>
+ column="39"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#destroySipDelegate`"
- errorLine1=" mSipDelegateManager.destroySipDelegate(mSipDelegateConnection,"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="247"
- column="37"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#destroySipDelegate`"
- errorLine1=" mSipDelegateManager.destroySipDelegate(mSipDelegateConnection,"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="322"
- column="33"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.telephony.gba.UaSecurityProtocolIdentifier.Builder`"
- errorLine1=" new UaSecurityProtocolIdentifier.Builder();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java"
- line="120"
- column="21"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.telephony.ims.DelegateRequest`"
- errorLine1=" mSipDelegateManager.createSipDelegate(new DelegateRequest(featureTags),"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="231"
- column="59"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.telephony.ims.RcsClientConfiguration`"
- errorLine1=" return new RcsClientConfiguration("
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
- line="231"
- column="16"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.telephony.ims.RcsClientConfiguration`"
- errorLine1=" return new RcsClientConfiguration("
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
- line="106"
- column="16"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Cast to `BootstrapAuthenticationCallback` requires API level 31 (current min is 30)"
- errorLine1=" new BootstrapAuthenticationCallback() {"
- errorLine2=" ^">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java"
- line="135"
- column="21"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Cast to `CapabilitiesCallback` requires API level 31 (current min is 30)"
- errorLine1=" getMainExecutor(), new RcsUceAdapter.CapabilitiesCallback() {"
- errorLine2=" ^">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#getSupportedDuplexModes`"
+ errorLine1=" b.append(servCaps.getSupportedDuplexModes());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="136"
- column="44"/>
+ line="233"
+ column="39"/>
</issue>
<issue
id="NewApi"
- message="Cast to `CapabilitiesCallback` requires API level 31 (current min is 30)"
- errorLine1=" new RcsUceAdapter.CapabilitiesCallback() {"
- errorLine2=" ^">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#getUnsupportedDuplexModes`"
+ errorLine1=" b.append(servCaps.getUnsupportedDuplexModes());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="96"
- column="25"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyManager.BootstrapAuthenticationCallback`"
- errorLine1=" new BootstrapAuthenticationCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java"
- line="135"
- column="25"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager.RcsProvisioningCallback`"
- errorLine1=" new RcsProvisioningCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
- line="89"
- column="17"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager.RcsProvisioningCallback`"
- errorLine1=" new RcsProvisioningCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
- line="80"
- column="17"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.ims.RcsUceAdapter.CapabilitiesCallback`"
- errorLine1=" getMainExecutor(), new RcsUceAdapter.CapabilitiesCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="136"
- column="48"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.ims.RcsUceAdapter.CapabilitiesCallback`"
- errorLine1=" new RcsUceAdapter.CapabilitiesCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="96"
- column="29"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.ims.stub.DelegateConnectionMessageCallback`"
- errorLine1=" new DelegateConnectionMessageCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="87"
- column="17"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.ims.stub.DelegateConnectionStateCallback`"
- errorLine1=" new DelegateConnectionStateCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="117"
- column="17"/>
+ line="235"
+ column="39"/>
</issue>
</issues>
\ No newline at end of file
diff --git a/testapps/TestRcsApp/TestApp/res/layout/CarrierLockListLayout.xml b/testapps/TestRcsApp/TestApp/res/layout/CarrierLockListLayout.xml
new file mode 100644
index 0000000..f07c65c
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/layout/CarrierLockListLayout.xml
@@ -0,0 +1,77 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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/noLockMode"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/no_LockMode"
+ android:textAlignment="center"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/lockToVZW"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/no_LockTo_VZW"
+ android:textAlignment="center"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/lockToATT"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/no_LockTo_ATT"
+ android:textAlignment="center"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/lockToTMO"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/no_LockTo_TMO"
+ android:textAlignment="center"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/lockToKOODOS"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/no_LockTo_KOODOS"
+ android:textAlignment="center"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/lockToTELUS"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/no_LockTo_TELUS"
+ android:textAlignment="center"
+ android:textAllCaps="false" />
+ </LinearLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/testapps/TestRcsApp/TestApp/res/layout/activity_main.xml b/testapps/TestRcsApp/TestApp/res/layout/activity_main.xml
index 939feb0..ebf5508 100644
--- a/testapps/TestRcsApp/TestApp/res/layout/activity_main.xml
+++ b/testapps/TestRcsApp/TestApp/res/layout/activity_main.xml
@@ -74,6 +74,14 @@
android:textAlignment="center"
android:textAllCaps="false" />
+ <Button
+ android:id="@+id/setCarrierLockMode"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/setCarrierLockMode"
+ android:textAlignment="center"
+ android:textAllCaps="false"/>
+
<TextView
android:id="@+id/version_info"
android:layout_width="match_parent"
diff --git a/testapps/TestRcsApp/TestApp/res/values/donottranslate_strings.xml b/testapps/TestRcsApp/TestApp/res/values/donottranslate_strings.xml
index f52b70d..b017139 100644
--- a/testapps/TestRcsApp/TestApp/res/values/donottranslate_strings.xml
+++ b/testapps/TestRcsApp/TestApp/res/values/donottranslate_strings.xml
@@ -72,6 +72,8 @@
<string name="browse">Browse</string>
<string name="upload">Upload</string>
<string name="upload_file_gba">Upload File with GBA</string>
+ <string name="setCarrierLockMode">CarrierLock</string>
+
<string name="invalid_parameters">Invalid Parameters</string>
<string name="server">Server:</string>
<string name="file_name">File Name:</string>
@@ -79,6 +81,13 @@
<string name="file_empty">File is empty</string>
<string name="version_info">Version: %s</string>
+ <string name="no_LockMode">NoLock/ UnLocked</string>
+ <string name="no_LockTo_VZW">Lock to Verizon</string>
+ <string name="no_LockTo_ATT">Lock to ATT</string>
+ <string name="no_LockTo_TMO">Lock to TMO</string>
+ <string name="no_LockTo_KOODOS">Lock to KOODO</string>
+ <string name="no_LockTo_TELUS">Lock to TELUS</string>
+
<string-array name="rcs_profile">
<item>UP_1.0</item>
<item>UP_2.3</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
index 89c5268..5d2db73 100644
--- a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/MainActivity.java
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/MainActivity.java
@@ -29,6 +29,8 @@
import androidx.appcompat.app.AppCompatActivity;
+import com.google.android.sample.rcsclient.carrierLock.CarrieLockModeListActivity;
+
/** An activity to show function list. */
public class MainActivity extends AppCompatActivity {
private static final String TAG = "TestRcsApp.MainActivity";
@@ -39,6 +41,7 @@
private Button mMessageClientButton;
private Button mFileUploadButton;
private TextView mVersionInfo;
+ private Button mCarrierLockModeListBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -56,6 +59,7 @@
mGbaButton = (Button) this.findViewById(R.id.gba);
mFileUploadButton = findViewById(R.id.uploadFile);
mVersionInfo = this.findViewById(R.id.version_info);
+ mCarrierLockModeListBtn = findViewById(R.id.setCarrierLockMode);
mProvisionButton.setOnClickListener(view -> {
Intent intent = new Intent(this, ProvisioningActivity.class);
MainActivity.this.startActivity(intent);
@@ -90,6 +94,11 @@
appVersionName);
mVersionInfo.setText(version);
}
+
+ mCarrierLockModeListBtn.setOnClickListener(view -> {
+ Intent intent = new Intent(this, CarrieLockModeListActivity.class);
+ MainActivity.this.startActivity(intent);
+ });
}
@Override
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/carrierLock/CarrieLockModeListActivity.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/carrierLock/CarrieLockModeListActivity.java
new file mode 100644
index 0000000..6547aeb
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/carrierLock/CarrieLockModeListActivity.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.carrierLock;
+
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.google.android.sample.rcsclient.R;
+
+public class CarrieLockModeListActivity extends AppCompatActivity {
+
+ private final CarrierLockProvider mCarrierLockProvider = new CarrierLockProvider();
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.CarrierLockListLayout);
+
+ Button noLockModeBtn = this.findViewById(R.id.noLockMode);
+ assert noLockModeBtn != null;
+ noLockModeBtn.setOnClickListener(view -> {
+ mCarrierLockProvider.setLockMode(CarrierRestriction.UNLOCKED);
+ Toast.makeText(this, "Lock mode set to UNLOCKED", Toast.LENGTH_LONG).show();
+ });
+
+ Button vzwLockModeBtn = this.findViewById(R.id.lockToVZW);
+ assert vzwLockModeBtn != null;
+ vzwLockModeBtn.setOnClickListener(view -> {
+ mCarrierLockProvider.setLockMode(CarrierRestriction.LOCK_TO_VZW);
+ Toast.makeText(this, "Lock mode set to VZW", Toast.LENGTH_LONG).show();
+ });
+
+ Button attLockModeBtn = this.findViewById(R.id.lockToATT);
+ assert attLockModeBtn != null;
+ attLockModeBtn.setOnClickListener(view -> {
+ mCarrierLockProvider.setLockMode(CarrierRestriction.LOCK_TO_ATT);
+ Toast.makeText(this, "Lock mode set to ATT", Toast.LENGTH_LONG).show();
+ });
+
+ Button tmoLockModeBtn = this.findViewById(R.id.lockToTMO);
+ assert tmoLockModeBtn != null;
+ tmoLockModeBtn.setOnClickListener(view -> {
+ mCarrierLockProvider.setLockMode(CarrierRestriction.LOCK_TO_TMO);
+ Toast.makeText(this, "Lock mode set to TMO", Toast.LENGTH_LONG).show();
+ });
+
+ Button koodoLockModeBtn = this.findViewById(R.id.lockToKOODOS);
+ assert koodoLockModeBtn != null;
+ koodoLockModeBtn.setOnClickListener(view -> {
+ mCarrierLockProvider.setLockMode(CarrierRestriction.LOCK_TO_KOODO);
+ Toast.makeText(this, "Lock mode set to KOODO", Toast.LENGTH_LONG).show();
+ });
+
+ Button telusLockModeBtn = this.findViewById(R.id.lockToTELUS);
+ assert telusLockModeBtn != null;
+ telusLockModeBtn.setOnClickListener(view -> {
+ mCarrierLockProvider.setLockMode(CarrierRestriction.LOCK_TO_TELUS);
+ Toast.makeText(this, "Lock mode set to TELUS", Toast.LENGTH_LONG).show();
+ });
+ }
+}
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/carrierLock/CarrierLockProvider.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/carrierLock/CarrierLockProvider.java
new file mode 100644
index 0000000..8fa3cd6
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/carrierLock/CarrierLockProvider.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.carrierLock;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
+
+public class CarrierLockProvider extends ContentProvider {
+
+ public static final String AUTHORITY = "com.sample.lockProvider";
+ public static final String TAG = "TestCarrierLockProvider";
+
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/carrierLock");
+ // content://com.sample.lockProvider/carrierLock
+
+ private static CarrierRestriction mLockMode = CarrierRestriction.UNLOCKED;
+ private static final ArrayList<Integer> mCarrierIds = new ArrayList<>();
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Bundle call(String method, String args, Bundle extras) {
+ Bundle result = new Bundle();
+ Log.d(TAG, "call query STARTED on method = " + method);
+ switch (method) {
+ case "getCarrierRestrictionStatus":
+ try {
+ if (mLockMode == CarrierRestriction.UNLOCKED) {
+ result.putInt("restriction_status", 0); // Unlocked
+ } else {
+ result.putInt("restriction_status", 2); // Locked/Restricted
+ }
+ mCarrierIds.clear();
+ Log.d(TAG, "Query come : Lock mode set to " + mLockMode);
+ switch (mLockMode) {
+ case UNLOCKED:
+ // Do Nothing
+ break;
+ case LOCK_TO_VZW:
+ mCarrierIds.add(1839);
+ break;
+ case LOCK_TO_ATT:
+ mCarrierIds.add(1187);
+ mCarrierIds.add(10021);
+ mCarrierIds.add(2119);
+ mCarrierIds.add(2120);
+ mCarrierIds.add(1779);
+ mCarrierIds.add(10028);
+ break;
+ case LOCK_TO_TMO:
+ mCarrierIds.add(1);
+ break;
+ case LOCK_TO_KOODO:
+ mCarrierIds.add(2020);
+ break;
+ case LOCK_TO_TELUS:
+ mCarrierIds.add(1404);
+ break;
+ default:
+ // Nothing
+ }
+ StringJoiner joiner = new StringJoiner(", ");
+ if (!mCarrierIds.isEmpty()) {
+ result.putIntegerArrayList("allowed_carrier_ids", mCarrierIds);
+ for (Integer num : mCarrierIds) {
+ joiner.add(num.toString());
+ }
+ result.putString("PrintableCarrierIds", joiner.toString());
+ Log.d(TAG, "Locked to carrierIds = " + joiner.toString());
+ } else {
+ result.putString("allowed_carrier_ids", "");
+ result.putString("PrintableCarrierIds", "");
+ }
+
+ } catch (Exception e) {
+ Log.e(TAG, " call :: query :: exception = " + e.getMessage());
+ }
+ return result;
+
+ case "getList:":
+ String list = String.valueOf(
+ mCarrierIds.size());
+ result.putString("carrierList", list);
+ return result;
+ default:
+ return null;
+ }
+ }
+
+ private void updateLockValue(int lockValue) {
+ Log.d(TAG, "updateLockValue through ADB to = " + lockValue);
+ switch (lockValue) {
+ case 1:
+ mLockMode = CarrierRestriction.LOCK_TO_VZW;
+ break;
+ case 2:
+ mLockMode = CarrierRestriction.LOCK_TO_ATT;
+ break;
+ case 3:
+ mLockMode = CarrierRestriction.LOCK_TO_TMO;
+ break;
+ case 4:
+ mLockMode = CarrierRestriction.LOCK_TO_KOODO;
+ break;
+ case 5:
+ mLockMode = CarrierRestriction.LOCK_TO_TELUS;
+ break;
+ default:
+ mLockMode = CarrierRestriction.UNLOCKED;
+ break;
+ }
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ Log.d(TAG, "CarrierLockProvider Query");
+ return null;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return "vnd.android.cursor.dir/vnd." + AUTHORITY + ".books";
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ Log.d(TAG, "CarrierLockProvider insert START");
+ assert values != null;
+ int newValue = values.getAsInteger("newValue");
+ updateLockValue(newValue);
+ return CONTENT_URI;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ public void setLockMode(CarrierRestriction lockMode) {
+ mLockMode = lockMode;
+ Log.d(TAG, "Setting lockMode to " + mLockMode);
+ }
+}
\ No newline at end of file
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/carrierLock/CarrierRestriction.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/carrierLock/CarrierRestriction.java
new file mode 100644
index 0000000..34f9e7b
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/carrierLock/CarrierRestriction.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.carrierLock;
+
+public enum CarrierRestriction {
+ UNLOCKED,
+ LOCK_TO_VZW,
+ LOCK_TO_ATT,
+ LOCK_TO_TMO,
+ LOCK_TO_KOODO,
+ LOCK_TO_TELUS
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/Android.bp b/testapps/TestRcsApp/aosp_test_rcsclient/Android.bp
index 34b0a12..e1de685 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/Android.bp
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/Android.bp
@@ -26,4 +26,7 @@
sdk_version: "system_current",
min_sdk_version: "30",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/lint-baseline.xml b/testapps/TestRcsApp/aosp_test_rcsclient/lint-baseline.xml
index e0c7c3e..b2110a3 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/lint-baseline.xml
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/lint-baseline.xml
@@ -1,48 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.ConnectivityManager#registerQosCallback`"
- errorLine1=" connectivityManager.registerQosCallback(new QosSocketInfo(network, socket),"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSession.java"
- line="118"
- column="33"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.ConnectivityManager#unregisterQosCallback`"
- errorLine1=" connectivityManager.unregisterQosCallback(qosCallback);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSession.java"
- line="181"
- column="29"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#bootstrapAuthenticationRequest`"
- errorLine1=" telephonyManager.bootstrapAuthenticationRequest("
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `new android.telephony.gba.UaSecurityProtocolIdentifier.Builder`"
+ errorLine1=" new UaSecurityProtocolIdentifier.Builder();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java"
- line="97"
- column="26"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.gba.UaSecurityProtocolIdentifier.Builder#build`"
- errorLine1=" UaSecurityProtocolIdentifier spId = builder.build();"
- errorLine2=" ~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java"
- line="79"
- column="53"/>
+ line="55"
+ column="17"/>
</issue>
<issue
@@ -91,673 +58,57 @@
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.DelegateRegistrationState#getRegisteredFeatureTags`"
- errorLine1=" .getRegisteredFeatureTags()"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.gba.UaSecurityProtocolIdentifier.Builder#build`"
+ errorLine1=" UaSecurityProtocolIdentifier spId = builder.build();"
+ errorLine2=" ~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="139"
- column="34"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.DelegateRegistrationState#getRegisteredFeatureTags`"
- errorLine1=" Set<String> registeredFt = registrationState.getRegisteredFeatureTags();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="223"
- column="58"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.FeatureTagState#getFeatureTag`"
- errorLine1=' stringBuilder.append(featureTagState.getFeatureTag()).append(" ").append('
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="220"
- column="54"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.FeatureTagState#getState`"
- errorLine1=" featureTagState.getState());"
- errorLine2=" ~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="221"
- column="41"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ImsManager#getSipDelegateManager`"
- errorLine1=" this.sipDelegateManager = imsManager.getSipDelegateManager(subscriptionId);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="77"
- column="46"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#isRcsVolteSingleRegistrationCapable`"
- errorLine1=" return provisioningManager.isRcsVolteSingleRegistrationCapable();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
- line="166"
- column="36"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#registerRcsProvisioningCallback`"
- errorLine1=" provisioningManager.registerRcsProvisioningCallback(executorService, callback);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
- line="147"
- column="33"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#setRcsClientConfiguration`"
- errorLine1=" provisioningManager.setRcsClientConfiguration(clientConfiguration);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
- line="111"
- column="29"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#triggerRcsReconfiguration`"
- errorLine1=" provisioningManager.triggerRcsReconfiguration();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
- line="176"
- column="29"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#unregisterRcsProvisioningCallback`"
- errorLine1=" provisioningManager.unregisterRcsProvisioningCallback(callback);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
- line="158"
- column="33"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getHomeDomain`"
- errorLine1=' + ", \n\tmHomeDomain=" + config.getHomeDomain()'
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="246"
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java"
+ line="79"
column="53"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getHomeDomain`"
- errorLine1=" return mConfiguration.getHomeDomain();"
- errorLine2=" ~~~~~~~~~~~~~">
+ message="Cast to `BootstrapAuthenticationCallback` requires API level 31 (current min is 30)"
+ errorLine1=" new TelephonyManager.BootstrapAuthenticationCallback() {"
+ errorLine2=" ^">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="317"
- column="35"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java"
+ line="81"
+ column="17"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getImei`"
- errorLine1=' + ", \n\tmImei=" + config.getImei()'
- errorLine2=" ~~~~~~~">
+ message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyManager.BootstrapAuthenticationCallback`"
+ errorLine1=" new TelephonyManager.BootstrapAuthenticationCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="247"
- column="47"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java"
+ line="81"
+ column="21"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getImei`"
- errorLine1=" return mConfiguration.getImei();"
- errorLine2=" ~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#bootstrapAuthenticationRequest`"
+ errorLine1=" telephonyManager.bootstrapAuthenticationRequest("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="357"
- column="35"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java"
+ line="97"
+ column="26"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getIpSecConfiguration`"
- errorLine1=' + ", \n\tmIpSecConfiguration=" + config.getIpSecConfiguration()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `new android.telephony.ims.SipMessage`"
+ errorLine1=" return new SipMessage(startLine, headers.toString(), rawContent);"
+ errorLine2=" ~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="259"
- column="61"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getIpSecConfiguration`"
- errorLine1=" SipDelegateConfiguration.IpSecConfiguration c = mConfiguration.getIpSecConfiguration();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="333"
- column="76"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getLocalAddress`"
- errorLine1=' + ", \n\tmLocalAddr=" + config.getLocalAddress()'
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="239"
- column="52"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getLocalAddress`"
- errorLine1=" return mConfiguration.getLocalAddress().getAddress().getHostAddress();"
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="296"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getLocalAddress`"
- errorLine1=" return mConfiguration.getLocalAddress().getPort();"
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="301"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getMaxUdpPayloadSizeBytes`"
- errorLine1=' + ", \n\tmMaxUdpPayloadSize=" + config.getMaxUdpPayloadSizeBytes()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="243"
- column="60"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getMaxUdpPayloadSizeBytes`"
- errorLine1=" ? mConfiguration.getMaxUdpPayloadSizeBytes() : 1500;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="378"
- column="38"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getMaxUdpPayloadSizeBytes`"
- errorLine1=" return mConfiguration.getMaxUdpPayloadSizeBytes() > 0"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="377"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getNatSocketAddress`"
- errorLine1=" + ", \n\tmNatConfiguration=" + config.getNatSocketAddress() + '}';"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="260"
- column="59"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPrivateUserIdentifier`"
- errorLine1=' + ", \n\tmPrivateUserIdentifier=" + config.getPrivateUserIdentifier()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="245"
- column="64"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicGruuUri`"
- errorLine1=' + ", \n\tmGruu=" + config.getPublicGruuUri()'
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="248"
- column="47"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicUserIdentifier`"
- errorLine1=' + ", \n\tmPublicUserIdentifier=" + config.getPublicUserIdentifier()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="244"
- column="63"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicUserIdentifier`"
- errorLine1=" return mConfiguration.getPublicUserIdentifier();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="312"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAssociatedUriHeader`"
- errorLine1=' + ", \n\tmAssociatedUriHeader=" + config.getSipAssociatedUriHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="258"
- column="62"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAssociatedUriHeader`"
- errorLine1=" String associatedUris = mConfiguration.getSipAssociatedUriHeader();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="322"
- column="52"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAuthenticationHeader`"
- errorLine1=' + ", \n\tmSipAuthHeader=" + config.getSipAuthenticationHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="249"
- column="56"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAuthenticationNonce`"
- errorLine1=' + ", \n\tmSipAuthNonce=" + config.getSipAuthenticationNonce()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="250"
- column="55"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipCniHeader`"
- errorLine1=' + ", \n\tmCniHeader=" + config.getSipCniHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="257"
- column="52"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipContactUserParameter`"
- errorLine1=' + ", \n\tmContactUserParam=" + config.getSipContactUserParameter()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="254"
- column="59"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipContactUserParameter`"
- errorLine1=" return mConfiguration.getSipContactUserParameter();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="352"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPaniHeader`"
- errorLine1=' + ", \n\tmPaniHeader=" + config.getSipPaniHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="255"
- column="53"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPaniHeader`"
- errorLine1=" return mConfiguration.getSipPaniHeader();"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="362"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPathHeader`"
- errorLine1=' + ", \n\tmPathHeader=" + config.getSipPathHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="252"
- column="53"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPlaniHeader`"
- errorLine1=' + ", \n\tmPlaniHeader=" + config.getSipPlaniHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="256"
- column="54"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPlaniHeader`"
- errorLine1=" return mConfiguration.getSipPlaniHeader();"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="367"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServerAddress`"
- errorLine1=' + ", \n\tmSipServerAddr=" + config.getSipServerAddress()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="240"
- column="56"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServerAddress`"
- errorLine1=" return mConfiguration.getSipServerAddress().getAddress().getHostAddress();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="286"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServerAddress`"
- errorLine1=" return mConfiguration.getSipServerAddress().getPort();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="291"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServiceRouteHeader`"
- errorLine1=' + ", \n\tmServiceRouteHeader=" + config.getSipServiceRouteHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="251"
- column="61"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServiceRouteHeader`"
- errorLine1=" mConfiguration.getSipServiceRouteHeader();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="343"
- column="36"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipUserAgentHeader`"
- errorLine1=' + ", \n\tmUserAgentHeader=" + config.getSipUserAgentHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="253"
- column="58"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipUserAgentHeader`"
- errorLine1=" return mConfiguration.getSipUserAgentHeader();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="372"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getTransportType`"
- errorLine1=' + ", \n\tmTransportType=" + config.getTransportType()'
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="238"
- column="56"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getTransportType`"
- errorLine1=" int sipTransport = mConfiguration.getTransportType();"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="306"
- column="47"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getVersion`"
- errorLine1=" + registeredSipConfig.getVersion());"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="127"
- column="63"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getVersion`"
- errorLine1=' + "mVersion=" + config.getVersion()'
- errorLine2=" ~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="237"
- column="44"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getVersion`"
- errorLine1=" return mConfiguration.getVersion();"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="281"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#isSipCompactFormEnabled`"
- errorLine1=' + ", \n\tmIsSipCompactFormEnabled=" + config.isSipCompactFormEnabled()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="241"
- column="66"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#isSipKeepaliveEnabled`"
- errorLine1=' + ", \n\tmIsSipKeepaliveEnabled=" + config.isSipKeepaliveEnabled()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="242"
- column="64"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration.IpSecConfiguration#getSipSecurityVerifyHeader`"
- errorLine1=" return c.getSipSecurityVerifyHeader();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="337"
- column="22"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConnection#sendMessage`"
- errorLine1=" sipDelegateConnection.sendMessage(MessageConverter.toPlatformMessage(message),"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="271"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#createSipDelegate`"
- errorLine1=" controller.sipDelegateManager.createSipDelegate("
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="205"
- column="47"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#destroySipDelegate`"
- errorLine1=" sipDelegateManager.destroySipDelegate(context.sipDelegateConnection,"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="92"
- column="32"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipMessage#getContent`"
- errorLine1=" return new SipMessage(message.getStartLine(), headers, message.getContent());"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="395"
- column="76"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipMessage#getHeaderSection`"
- errorLine1=' + message.getHeaderSection().substring(0, 10) + "->"'
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="392"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipMessage#getHeaderSection`"
- errorLine1=" String headers = message.getHeaderSection();"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="387"
- column="38"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipMessage#getStartLine`"
- errorLine1=" return new SipMessage(message.getStartLine(), headers, message.getContent());"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="395"
- column="43"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/MessageConverter.java"
+ line="72"
+ column="16"/>
</issue>
<issue
@@ -784,94 +135,6 @@
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.net.QosSocketInfo`"
- errorLine1=" connectivityManager.registerQosCallback(new QosSocketInfo(network, socket),"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSession.java"
- line="118"
- column="53"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.telephony.gba.UaSecurityProtocolIdentifier.Builder`"
- errorLine1=" new UaSecurityProtocolIdentifier.Builder();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java"
- line="55"
- column="17"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.telephony.ims.DelegateRequest`"
- errorLine1=" DelegateRequest request = new DelegateRequest(imsService.getFeatureTags());"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="203"
- column="39"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.telephony.ims.RcsClientConfiguration`"
- errorLine1=" return new RcsClientConfiguration("
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
- line="74"
- column="16"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.telephony.ims.SipMessage`"
- errorLine1=" return new SipMessage(message.getStartLine(), headers, message.getContent());"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="395"
- column="20"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.telephony.ims.SipMessage`"
- errorLine1=" return new SipMessage(startLine, headers.toString(), rawContent);"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/MessageConverter.java"
- line="72"
- column="16"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Cast to `BootstrapAuthenticationCallback` requires API level 31 (current min is 30)"
- errorLine1=" new TelephonyManager.BootstrapAuthenticationCallback() {"
- errorLine2=" ^">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java"
- line="81"
- column="17"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Cast to `RcsProvisioningCallback` requires API level 31 (current min is 30)"
- errorLine1=" new RcsProvisioningCallback() {"
- errorLine2=" ^">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
- line="114"
- column="17"/>
- </issue>
-
- <issue
- id="NewApi"
message="Class requires API level 31 (current min is 30): `android.net.QosCallback`"
errorLine1=" private final QosCallback qosCallback = new QosCallback() {"
errorLine2=" ~~~~~~~~~~~">
@@ -883,24 +146,68 @@
<issue
id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyManager.BootstrapAuthenticationCallback`"
- errorLine1=" new TelephonyManager.BootstrapAuthenticationCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `new android.net.QosSocketInfo`"
+ errorLine1=" connectivityManager.registerQosCallback(new QosSocketInfo(network, socket),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java"
- line="81"
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSession.java"
+ line="118"
+ column="53"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ImsManager#getSipDelegateManager`"
+ errorLine1=" this.sipDelegateManager = imsManager.getSipDelegateManager(subscriptionId);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="77"
+ column="46"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#destroySipDelegate`"
+ errorLine1=" sipDelegateManager.destroySipDelegate(context.sipDelegateConnection,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="92"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Class requires API level 31 (current min is 30): `android.telephony.ims.stub.DelegateConnectionStateCallback`"
+ errorLine1=" new DelegateConnectionStateCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="114"
column="21"/>
</issue>
<issue
id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager.RcsProvisioningCallback`"
- errorLine1=" new RcsProvisioningCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getVersion`"
+ errorLine1=" + registeredSipConfig.getVersion());"
+ errorLine2=" ~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
- line="114"
- column="21"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="127"
+ column="63"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.DelegateRegistrationState#getRegisteredFeatureTags`"
+ errorLine1=" .getRegisteredFeatureTags()"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="139"
+ column="34"/>
</issue>
<issue
@@ -916,13 +223,684 @@
<issue
id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.ims.stub.DelegateConnectionStateCallback`"
- errorLine1=" new DelegateConnectionStateCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `new android.telephony.ims.DelegateRequest`"
+ errorLine1=" DelegateRequest request = new DelegateRequest(imsService.getFeatureTags());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="203"
+ column="39"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#createSipDelegate`"
+ errorLine1=" controller.sipDelegateManager.createSipDelegate("
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="205"
+ column="47"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.FeatureTagState#getFeatureTag`"
+ errorLine1=' stringBuilder.append(featureTagState.getFeatureTag()).append(" ").append('
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="220"
+ column="54"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.FeatureTagState#getState`"
+ errorLine1=" featureTagState.getState());"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="221"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.DelegateRegistrationState#getRegisteredFeatureTags`"
+ errorLine1=" Set<String> registeredFt = registrationState.getRegisteredFeatureTags();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="223"
+ column="58"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getVersion`"
+ errorLine1=' + "mVersion=" + config.getVersion()'
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="237"
+ column="44"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getTransportType`"
+ errorLine1=' + ", \n\tmTransportType=" + config.getTransportType()'
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="238"
+ column="56"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getLocalAddress`"
+ errorLine1=' + ", \n\tmLocalAddr=" + config.getLocalAddress()'
+ errorLine2=" ~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="239"
+ column="52"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServerAddress`"
+ errorLine1=' + ", \n\tmSipServerAddr=" + config.getSipServerAddress()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="240"
+ column="56"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#isSipCompactFormEnabled`"
+ errorLine1=' + ", \n\tmIsSipCompactFormEnabled=" + config.isSipCompactFormEnabled()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="241"
+ column="66"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#isSipKeepaliveEnabled`"
+ errorLine1=' + ", \n\tmIsSipKeepaliveEnabled=" + config.isSipKeepaliveEnabled()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="242"
+ column="64"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getMaxUdpPayloadSizeBytes`"
+ errorLine1=' + ", \n\tmMaxUdpPayloadSize=" + config.getMaxUdpPayloadSizeBytes()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="243"
+ column="60"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicUserIdentifier`"
+ errorLine1=' + ", \n\tmPublicUserIdentifier=" + config.getPublicUserIdentifier()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="244"
+ column="63"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPrivateUserIdentifier`"
+ errorLine1=' + ", \n\tmPrivateUserIdentifier=" + config.getPrivateUserIdentifier()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="245"
+ column="64"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getHomeDomain`"
+ errorLine1=' + ", \n\tmHomeDomain=" + config.getHomeDomain()'
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="246"
+ column="53"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getImei`"
+ errorLine1=' + ", \n\tmImei=" + config.getImei()'
+ errorLine2=" ~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="247"
+ column="47"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicGruuUri`"
+ errorLine1=' + ", \n\tmGruu=" + config.getPublicGruuUri()'
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="248"
+ column="47"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAuthenticationHeader`"
+ errorLine1=' + ", \n\tmSipAuthHeader=" + config.getSipAuthenticationHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="249"
+ column="56"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAuthenticationNonce`"
+ errorLine1=' + ", \n\tmSipAuthNonce=" + config.getSipAuthenticationNonce()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="250"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServiceRouteHeader`"
+ errorLine1=' + ", \n\tmServiceRouteHeader=" + config.getSipServiceRouteHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="251"
+ column="61"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPathHeader`"
+ errorLine1=' + ", \n\tmPathHeader=" + config.getSipPathHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="252"
+ column="53"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipUserAgentHeader`"
+ errorLine1=' + ", \n\tmUserAgentHeader=" + config.getSipUserAgentHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="253"
+ column="58"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipContactUserParameter`"
+ errorLine1=' + ", \n\tmContactUserParam=" + config.getSipContactUserParameter()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="254"
+ column="59"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPaniHeader`"
+ errorLine1=' + ", \n\tmPaniHeader=" + config.getSipPaniHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="255"
+ column="53"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPlaniHeader`"
+ errorLine1=' + ", \n\tmPlaniHeader=" + config.getSipPlaniHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="256"
+ column="54"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipCniHeader`"
+ errorLine1=' + ", \n\tmCniHeader=" + config.getSipCniHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="257"
+ column="52"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAssociatedUriHeader`"
+ errorLine1=' + ", \n\tmAssociatedUriHeader=" + config.getSipAssociatedUriHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="258"
+ column="62"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getIpSecConfiguration`"
+ errorLine1=' + ", \n\tmIpSecConfiguration=" + config.getIpSecConfiguration()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="259"
+ column="61"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getNatSocketAddress`"
+ errorLine1=" + ", \n\tmNatConfiguration=" + config.getNatSocketAddress() + '}';"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="260"
+ column="59"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConnection#sendMessage`"
+ errorLine1=" sipDelegateConnection.sendMessage(MessageConverter.toPlatformMessage(message),"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="271"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getVersion`"
+ errorLine1=" return mConfiguration.getVersion();"
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="281"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServerAddress`"
+ errorLine1=" return mConfiguration.getSipServerAddress().getAddress().getHostAddress();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="286"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServerAddress`"
+ errorLine1=" return mConfiguration.getSipServerAddress().getPort();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="291"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getLocalAddress`"
+ errorLine1=" return mConfiguration.getLocalAddress().getAddress().getHostAddress();"
+ errorLine2=" ~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="296"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getLocalAddress`"
+ errorLine1=" return mConfiguration.getLocalAddress().getPort();"
+ errorLine2=" ~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="301"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getTransportType`"
+ errorLine1=" int sipTransport = mConfiguration.getTransportType();"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="306"
+ column="47"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicUserIdentifier`"
+ errorLine1=" return mConfiguration.getPublicUserIdentifier();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="312"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getHomeDomain`"
+ errorLine1=" return mConfiguration.getHomeDomain();"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="317"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAssociatedUriHeader`"
+ errorLine1=" String associatedUris = mConfiguration.getSipAssociatedUriHeader();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="322"
+ column="52"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getIpSecConfiguration`"
+ errorLine1=" SipDelegateConfiguration.IpSecConfiguration c = mConfiguration.getIpSecConfiguration();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="333"
+ column="76"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration.IpSecConfiguration#getSipSecurityVerifyHeader`"
+ errorLine1=" return c.getSipSecurityVerifyHeader();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="337"
+ column="22"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServiceRouteHeader`"
+ errorLine1=" mConfiguration.getSipServiceRouteHeader();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="343"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipContactUserParameter`"
+ errorLine1=" return mConfiguration.getSipContactUserParameter();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="352"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getImei`"
+ errorLine1=" return mConfiguration.getImei();"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="357"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPaniHeader`"
+ errorLine1=" return mConfiguration.getSipPaniHeader();"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="362"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPlaniHeader`"
+ errorLine1=" return mConfiguration.getSipPlaniHeader();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="367"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipUserAgentHeader`"
+ errorLine1=" return mConfiguration.getSipUserAgentHeader();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="372"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getMaxUdpPayloadSizeBytes`"
+ errorLine1=" return mConfiguration.getMaxUdpPayloadSizeBytes() > 0"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="377"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getMaxUdpPayloadSizeBytes`"
+ errorLine1=" ? mConfiguration.getMaxUdpPayloadSizeBytes() : 1500;"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="378"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipMessage#getHeaderSection`"
+ errorLine1=" String headers = message.getHeaderSection();"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="387"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipMessage#getHeaderSection`"
+ errorLine1=' + message.getHeaderSection().substring(0, 10) + "->"'
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="392"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipMessage#getContent`"
+ errorLine1=" return new SipMessage(message.getStartLine(), headers, message.getContent());"
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="395"
+ column="76"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipMessage#getStartLine`"
+ errorLine1=" return new SipMessage(message.getStartLine(), headers, message.getContent());"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="395"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `new android.telephony.ims.SipMessage`"
+ errorLine1=" return new SipMessage(message.getStartLine(), headers, message.getContent());"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="395"
+ column="20"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `new android.telephony.ims.RcsClientConfiguration`"
+ errorLine1=" return new RcsClientConfiguration("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
+ line="74"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#setRcsClientConfiguration`"
+ errorLine1=" provisioningManager.setRcsClientConfiguration(clientConfiguration);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
+ line="111"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Cast to `RcsProvisioningCallback` requires API level 31 (current min is 30)"
+ errorLine1=" new RcsProvisioningCallback() {"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
+ line="114"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Class requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager.RcsProvisioningCallback`"
+ errorLine1=" new RcsProvisioningCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
line="114"
column="21"/>
</issue>
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#registerRcsProvisioningCallback`"
+ errorLine1=" provisioningManager.registerRcsProvisioningCallback(executorService, callback);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
+ line="147"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#unregisterRcsProvisioningCallback`"
+ errorLine1=" provisioningManager.unregisterRcsProvisioningCallback(callback);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
+ line="158"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#isRcsVolteSingleRegistrationCapable`"
+ errorLine1=" return provisioningManager.isRcsVolteSingleRegistrationCapable();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
+ line="166"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#triggerRcsReconfiguration`"
+ errorLine1=" provisioningManager.triggerRcsReconfiguration();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
+ line="176"
+ column="29"/>
+ </issue>
+
</issues>
\ No newline at end of file
diff --git a/testapps/TestSatelliteApp/Android.bp b/testapps/TestSatelliteApp/Android.bp
new file mode 100644
index 0000000..78d125d
--- /dev/null
+++ b/testapps/TestSatelliteApp/Android.bp
@@ -0,0 +1,20 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+ name: "SatelliteTestApp",
+ system_ext_specific: true,
+ platform_apis: true,
+ manifest: "AndroidManifest.xml",
+ srcs: [
+ "src/**/*.java",
+ "src/**/I*.aidl",
+ ],
+ static_libs: [
+ "SatelliteClient",
+ ],
+ owner: "google",
+ privileged: true,
+ certificate: "platform",
+}
diff --git a/testapps/TestSatelliteApp/AndroidManifest.xml b/testapps/TestSatelliteApp/AndroidManifest.xml
new file mode 100644
index 0000000..fb30bf3
--- /dev/null
+++ b/testapps/TestSatelliteApp/AndroidManifest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.satellitetestapp">
+ <uses-permission android:name="android.permission.BIND_SATELLITE_SERVICE"/>
+ <uses-permission android:name="android.permission.SATELLITE_COMMUNICATION"/>
+ <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+ <application android:label="SatelliteTestApp">
+ <activity android:name=".SatelliteTestApp"
+ android:label="SatelliteTestApp"
+ 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"/>
+ </intent-filter>
+ </activity>
+
+ <service android:name=".TestSatelliteService"
+ android:directBootAware="true"
+ android:persistent="true"
+ android:permission="android.permission.BIND_SATELLITE_SERVICE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.telephony.satellite.SatelliteService"/>
+ </intent-filter>
+ </service>
+
+ <activity android:name=".SatelliteControl" />
+ <activity android:name=".Datagram" />
+ <activity android:name=".Provisioning" />
+ <activity android:name=".MultipleSendReceive" />
+ <activity android:name=".SendReceive" />
+ <activity android:name=".TestSatelliteWrapper" />
+ </application>
+</manifest>
diff --git a/testapps/TestSatelliteApp/res/layout/activity_Datagram.xml b/testapps/TestSatelliteApp/res/layout/activity_Datagram.xml
new file mode 100644
index 0000000..9e53f41
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/layout/activity_Datagram.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:paddingLeft="4dp">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:textColor="@android:color/holo_blue_dark"
+ android:textSize="20dp"
+ android:text="Datagram APIs"/>
+ <Button
+ android:id="@+id/startSatelliteTransmissionUpdates"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/startSatelliteTransmissionUpdates"/>
+ <Button
+ android:id="@+id/stopSatelliteTransmissionUpdates"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/stopSatelliteTransmissionUpdates"/>
+ <Button
+ android:id="@+id/pollPendingSatelliteDatagrams"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/pollPendingSatelliteDatagrams"/>
+ <Button
+ android:id="@+id/sendSatelliteDatagram"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/sendSatelliteDatagram"/>
+ <Button
+ android:id="@+id/registerForSatelliteDatagram"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/registerForSatelliteDatagram"/>
+ <Button
+ android:id="@+id/unregisterForSatelliteDatagram"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/unregisterForSatelliteDatagram"/>
+ <Button
+ android:id="@+id/showDatagramSendStateTransition"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/showDatagramSendStateTransition"/>
+ <Button
+ android:id="@+id/showDatagramReceiveStateTransition"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/showDatagramReceiveStateTransition"/>
+ <Button
+ android:id="@+id/registerForSatelliteModemStateChanged"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/registerForSatelliteModemStateChanged"/>
+ <Button
+ android:id="@+id/unregisterForSatelliteModemStateChanged"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/unregisterForSatelliteModemStateChanged"/>
+ <Button
+ android:id="@+id/showSatelliteModemStateTransition"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/showSatelliteModemStateTransition"/>
+ <Button
+ android:id="@+id/Back"
+ android:onClick="Back"
+ android:textColor="@android:color/holo_blue_dark"
+ android:layout_marginTop="100dp"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/Back"/>
+ <TextView
+ android:id="@+id/text_id"
+ android:layout_width="400dp"
+ android:layout_height="50dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:textSize="15dp" />
+ <TextView
+ android:id="@+id/showErrorStatus"
+ android:layout_width="400dp"
+ android:layout_height="50dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:textSize="15dp" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/testapps/TestSatelliteApp/res/layout/activity_MultipleSendReceive.xml b/testapps/TestSatelliteApp/res/layout/activity_MultipleSendReceive.xml
new file mode 100644
index 0000000..3632ecb
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/layout/activity_MultipleSendReceive.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:paddingLeft="4dp">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:textColor="@android:color/holo_blue_dark"
+ android:textSize="20dp"
+ android:text="Multiple Send and Receive APIs"/>
+ <Button
+ android:id="@+id/multiplePollPendingSatelliteDatagrams"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/multiplePollPendingSatelliteDatagrams"/>
+ <Button
+ android:id="@+id/multipleSendSatelliteDatagram"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/multipleSendSatelliteDatagram"/>
+ <Button
+ android:id="@+id/multipleSendReceiveSatelliteDatagram"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/multipleSendReceiveSatelliteDatagram"/>
+ <Button
+ android:id="@+id/Back"
+ android:onClick="Back"
+ android:textColor="@android:color/holo_blue_dark"
+ android:layout_marginTop="100dp"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/Back"/>
+ <TextView
+ android:id="@+id/text_id"
+ android:layout_width="400dp"
+ android:layout_height="50dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:textSize="15dp" />
+ <TextView
+ android:id="@+id/text_id1"
+ android:layout_width="400dp"
+ android:layout_height="65dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:layout_centerVertical="true"
+ android:textSize="15dp" />
+ <TextView
+ android:id="@+id/text_id2"
+ android:layout_width="400dp"
+ android:layout_height="65dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:layout_centerVertical="true"
+ android:textSize="15dp" />
+ <TextView
+ android:id="@+id/text_id3"
+ android:layout_width="400dp"
+ android:layout_height="65dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:layout_centerVertical="true"
+ android:textSize="15dp" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/testapps/TestSatelliteApp/res/layout/activity_Provisioning.xml b/testapps/TestSatelliteApp/res/layout/activity_Provisioning.xml
new file mode 100644
index 0000000..da5105d
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/layout/activity_Provisioning.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:paddingLeft="4dp">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:textColor="@android:color/holo_blue_dark"
+ android:textSize="20dp"
+ android:text="Provisioning APIs"/>
+ <Button
+ android:id="@+id/provisionSatelliteService"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/provisionSatelliteService"/>
+ <Button
+ android:id="@+id/deprovisionSatelliteService"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/deprovisionSatelliteService"/>
+ <Button
+ android:id="@+id/requestIsSatelliteProvisioned"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/requestIsSatelliteProvisioned"/>
+ <Button
+ android:id="@+id/registerForSatelliteProvisionStateChanged"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/registerForSatelliteProvisionStateChanged"/>
+ <Button
+ android:id="@+id/unregisterForSatelliteProvisionStateChanged"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/unregisterForSatelliteProvisionStateChanged"/>
+ <Button
+ android:id="@+id/showCurrentSatelliteProvisionState"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/showCurrentSatelliteProvisionState"/>
+ <Button
+ android:id="@+id/Back"
+ android:onClick="Back"
+ android:textColor="@android:color/holo_blue_dark"
+ android:layout_marginTop="100dp"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/Back"/>
+ <TextView
+ android:id="@+id/text_id"
+ android:layout_width="300dp"
+ android:layout_height="200dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:layout_centerVertical="true"
+ android:textSize="15dp" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/testapps/TestSatelliteApp/res/layout/activity_SatelliteControl.xml b/testapps/TestSatelliteApp/res/layout/activity_SatelliteControl.xml
new file mode 100644
index 0000000..6a79412
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/layout/activity_SatelliteControl.xml
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:paddingLeft="4dp">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:textColor="@android:color/holo_blue_dark"
+ android:textSize="20dp"
+ android:text="Satellite Control APIs"/>
+ <Button
+ android:id="@+id/enableSatellite"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/enableSatellite"/>
+ <Button
+ android:id="@+id/disableSatellite"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/disableSatellite"/>
+ <Button
+ android:id="@+id/requestIsSatelliteEnabled"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/requestIsSatelliteEnabled"/>
+ <Button
+ android:id="@+id/requestIsDemoModeEnabled"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/requestIsDemoModeEnabled"/>
+ <Button
+ android:id="@+id/requestIsSatelliteSupported"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/requestIsSatelliteSupported"/>
+ <Button
+ android:id="@+id/requestSatelliteCapabilities"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/requestSatelliteCapabilities"/>
+ <Button
+ android:id="@+id/requestIsSatelliteCommunicationAllowedForCurrentLocation"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/requestIsSatelliteCommunicationAllowedForCurrentLocation"/>
+ <Button
+ android:id="@+id/requestTimeForNextSatelliteVisibility"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/requestTimeForNextSatelliteVisibility"/>
+ <Button
+ android:id="@+id/removeUserRestrictReason"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/removeUserRestrictReason"/>
+ <Button
+ android:id="@+id/addUserRestrictReason"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/addUserRestrictReason"/>
+ <Button
+ android:id="@+id/getSatellitePlmn"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/getSatellitePlmn"/>
+ <Button
+ android:id="@+id/getAllSatellitePlmn"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/getAllSatellitePlmn"/>
+ <Button
+ android:id="@+id/isSatelliteEnabledForCarrier"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/isSatelliteEnabledForCarrier"/>
+ <Button
+ android:id="@+id/isRequestIsSatelliteEnabledForCarrier"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/isRequestIsSatelliteEnabledForCarrier"/>
+ <Button
+ android:id="@+id/Back"
+ android:onClick="Back"
+ android:textColor="@android:color/holo_blue_dark"
+ android:layout_marginTop="100dp"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/Back"/>
+ <TextView
+ android:id="@+id/text_id"
+ android:layout_width="300dp"
+ android:layout_height="200dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:layout_centerVertical="true"
+ android:textSize="15dp" />
+ </LinearLayout>
+</ScrollView>
diff --git a/testapps/TestSatelliteApp/res/layout/activity_SatelliteTestApp.xml b/testapps/TestSatelliteApp/res/layout/activity_SatelliteTestApp.xml
new file mode 100644
index 0000000..0753b82
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/layout/activity_SatelliteTestApp.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="wrap_content"
+ android:orientation="vertical"
+ android:paddingLeft="4dp">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:textColor="@android:color/holo_blue_dark"
+ android:textSize="20dp"
+ android:text="Available Satellite APIs"/>
+ <Button
+ android:id="@+id/SatelliteControl"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/SatelliteControl"/>
+ <Button
+ android:id="@+id/Datagram"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/Datagram"/>
+ <Button
+ android:id="@+id/Provisioning"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/Provisioning"/>
+ <Button
+ android:id="@+id/MultipleSendReceive"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/MultipleSendReceive"/>
+ <Button
+ android:id="@+id/SendReceive"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/SendReceive"/>
+ <Button
+ android:id="@+id/TestSatelliteWrapper"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingEnd="4dp"
+ android:text="@string/TestSatelliteWrapper"/>
+ </LinearLayout>
+</LinearLayout>
diff --git a/testapps/TestSatelliteApp/res/layout/activity_SendReceive.xml b/testapps/TestSatelliteApp/res/layout/activity_SendReceive.xml
new file mode 100644
index 0000000..6490e5d
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/layout/activity_SendReceive.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:paddingLeft="4dp">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:layout_weight="0"
+ android:textColor="@android:color/holo_blue_dark"
+ android:textSize="20dp"
+ android:text="Send and Receive APIs"/>
+ <Button
+ android:id="@+id/sendMessage"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/sendMessage"/>
+ <Button
+ android:id="@+id/receiveMessage"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/receiveMessage"/>
+ <EditText
+ android:id="@+id/enterText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:hint="Enter message to send"
+ android:inputType="text" />
+ <Button
+ android:id="@+id/Back"
+ android:onClick="Back"
+ android:textColor="@android:color/holo_blue_dark"
+ android:layout_marginTop="100dp"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/Back"/>
+ <TextView
+ android:id="@+id/showErrorStatus"
+ android:layout_width="400dp"
+ android:layout_height="50dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:textSize="15dp" />
+ <TextView
+ android:id="@+id/devicePosition"
+ android:layout_width="400dp"
+ android:layout_height="65dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:layout_centerVertical="true"
+ android:textSize="15dp" />
+ <TextView
+ android:id="@+id/satellitePosition"
+ android:layout_width="400dp"
+ android:layout_height="65dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:layout_centerVertical="true"
+ android:textSize="15dp" />
+ <TextView
+ android:id="@+id/messageStatus"
+ android:layout_width="400dp"
+ android:layout_height="65dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:layout_centerVertical="true"
+ android:textSize="15dp" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/testapps/TestSatelliteApp/res/layout/activity_TestSatelliteWrapper.xml b/testapps/TestSatelliteApp/res/layout/activity_TestSatelliteWrapper.xml
new file mode 100644
index 0000000..7f2f026
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/layout/activity_TestSatelliteWrapper.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:paddingStart="4dp">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="0"
+ android:textColor="@android:color/holo_blue_dark"
+ android:textSize="20dp"
+ android:text="Satellite Wrapper Test"/>
+ <Button
+ android:id="@+id/requestNtnSignalStrength"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/requestNtnSignalStrength"/>
+ <Button
+ android:id="@+id/registerForNtnSignalStrengthChanged"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/registerForNtnSignalStrengthChanged"/>
+ <Button
+ android:id="@+id/unregisterForNtnSignalStrengthChanged"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/unregisterForNtnSignalStrengthChanged"/>
+ <Button
+ android:id="@+id/isOnlyNonTerrestrialNetworkSubscription"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/isOnlyNonTerrestrialNetworkSubscription"/>
+ <Button
+ android:id="@+id/registerForSatelliteCapabilitiesChanged"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/registerForSatelliteCapabilitiesChanged"/>
+ <Button
+ android:id="@+id/unregisterForSatelliteCapabilitiesChanged"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/unregisterForSatelliteCapabilitiesChanged"/>
+ <Button
+ android:id="@+id/isNonTerrestrialNetwork"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/isNonTerrestrialNetwork"/>
+ <Button
+ android:id="@+id/getAvailableServices"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/getAvailableServices"/>
+ <Button
+ android:id="@+id/isUsingNonTerrestrialNetwork"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/isUsingNonTerrestrialNetwork"/>
+ <Button
+ android:id="@+id/requestAttachEnabledForCarrier_enable"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/requestAttachEnabledForCarrier_enable"/>
+ <Button
+ android:id="@+id/requestAttachEnabledForCarrier_disable"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/requestAttachEnabledForCarrier_disable"/>
+ <Button
+ android:id="@+id/requestIsAttachEnabledForCarrier"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/requestIsAttachEnabledForCarrier"/>
+ <Button
+ android:id="@+id/addAttachRestrictionForCarrier"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/addAttachRestrictionForCarrier"/>
+ <Button
+ android:id="@+id/removeAttachRestrictionForCarrier"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/removeAttachRestrictionForCarrier"/>
+ <Button
+ android:id="@+id/getAttachRestrictionReasonsForCarrier"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/getAttachRestrictionReasonsForCarrier"/>
+ <Button
+ android:id="@+id/getSatellitePlmnsForCarrier"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/getSatellitePlmnsForCarrier"/>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button
+ android:id="@+id/Back"
+ android:onClick="Back"
+ android:textColor="@android:color/holo_blue_dark"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingRight="4dp"
+ android:text="@string/Back"/>
+ <Button
+ android:id="@+id/ClearLog"
+ android:onClick="ClearLog"
+ android:textColor="@android:color/holo_blue_dark"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingRight="4dp"
+ android:text="@string/ClearLog"/>
+ </LinearLayout>
+ <ListView
+ android:id="@+id/logListView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:layout_centerVertical="true"
+ android:textSize="8dp" />
+ </LinearLayout>
+
+</ScrollView>
\ No newline at end of file
diff --git a/testapps/TestSatelliteApp/res/layout/log_textview.xml b/testapps/TestSatelliteApp/res/layout/log_textview.xml
new file mode 100644
index 0000000..a44641b
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/layout/log_textview.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/log_textview"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textColor="@android:color/holo_blue_light"
+ android:padding="10dp"/>
diff --git a/testapps/TestSatelliteApp/res/values/donottranslate_strings.xml b/testapps/TestSatelliteApp/res/values/donottranslate_strings.xml
new file mode 100644
index 0000000..20f5ca8
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/values/donottranslate_strings.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="SatelliteControl">SatelliteControl APIs</string>
+ <string name="Datagram">Datagram APIs</string>
+ <string name="Provisioning">Provisioning APIs</string>
+ <string name="MultipleSendReceive">Test multiple poll and send</string>
+ <string name="SendReceive">Send and Receive datagrams</string>
+
+ <string name="enableSatellite">enableSatellite</string>
+ <string name="disableSatellite">disableSatellite</string>
+ <string name="requestIsSatelliteEnabled">requestIsSatelliteEnabled</string>
+ <string name="requestIsDemoModeEnabled">requestIsDemoModeEnabled</string>
+ <string name="requestIsSatelliteSupported">requestIsSatelliteSupported</string>
+ <string name="requestSatelliteCapabilities">requestSatelliteCapabilities</string>
+ <string name="requestIsSatelliteCommunicationAllowedForCurrentLocation">requestIsSatelliteCommunicationAllowedForCurrentLocation</string>
+ <string name="requestTimeForNextSatelliteVisibility">requestTimeForNextSatelliteVisibility</string>
+
+ <string name="pollPendingSatelliteDatagrams">pollPendingSatelliteDatagrams</string>
+ <string name="sendSatelliteDatagram">sendSatelliteDatagram</string>
+ <string name="registerForSatelliteDatagram">registerForSatelliteDatagram</string>
+ <string name="unregisterForSatelliteDatagram">unregisterForSatelliteDatagram</string>
+ <string name="registerForSatelliteModemStateChanged">registerForSatelliteModemStateChanged</string>
+ <string name="unregisterForSatelliteModemStateChanged">unregisterForSatelliteModemStateChanged</string>
+ <string name="showSatelliteModemStateTransition">showSatelliteModemStateTransition</string>
+ <string name="startSatelliteTransmissionUpdates">startSatelliteTransmissionUpdates</string>
+ <string name="stopSatelliteTransmissionUpdates">stopSatelliteTransmissionUpdates</string>
+ <string name="showDatagramSendStateTransition">showDatagramSendStateTransition</string>
+ <string name="showDatagramReceiveStateTransition">showDatagramReceiveStateTransition</string>
+
+ <string name="provisionSatelliteService">provisionSatelliteService</string>
+ <string name="deprovisionSatelliteService">deprovisionSatelliteService</string>
+ <string name="requestIsSatelliteProvisioned">requestIsSatelliteProvisioned</string>
+ <string name="registerForSatelliteProvisionStateChanged">registerForSatelliteProvisionStateChanged</string>
+ <string name="unregisterForSatelliteProvisionStateChanged">unregisterForSatelliteProvisionStateChanged</string>
+ <string name="showCurrentSatelliteProvisionState">showCurrentSatelliteProvisionState</string>
+
+ <string name="multiplePollPendingSatelliteDatagrams">multiplePollPendingSatelliteDatagrams</string>
+ <string name="multipleSendSatelliteDatagram">multipleSendSatelliteDatagram</string>
+ <string name="multipleSendReceiveSatelliteDatagram">multipleSendReceiveSatelliteDatagram</string>
+
+ <string name="sendMessage">sendMessage</string>
+ <string name="receiveMessage">receiveMessage</string>
+
+ <string name="TestSatelliteWrapper">Test Satellite Wrapper</string>
+ <string name="requestNtnSignalStrength">requestNtnSignalStrength</string>
+ <string name="registerForNtnSignalStrengthChanged">registerForNtnSignalStrengthChanged</string>
+ <string name="unregisterForNtnSignalStrengthChanged">unregisterForNtnSignalStrengthChanged</string>
+ <string name="isOnlyNonTerrestrialNetworkSubscription">isOnlyNonTerrestrialNetworkSubscription</string>
+ <string name="registerForSatelliteCapabilitiesChanged">registerForSatelliteCapabilitiesChanged</string>
+ <string name="unregisterForSatelliteCapabilitiesChanged">unregisterForSatelliteCapabilitiesChanged</string>
+ <string name="isNonTerrestrialNetwork">isNonTerrestrialNetwork</string>
+ <string name="getAvailableServices">getAvailableServices</string>
+ <string name="isUsingNonTerrestrialNetwork">isUsingNonTerrestrialNetwork</string>
+ <string name="requestAttachEnabledForCarrier_enable">requestAttachEnabledForCarrier_enable</string>
+ <string name="requestAttachEnabledForCarrier_disable">requestAttachEnabledForCarrier_disable</string>
+ <string name="requestIsAttachEnabledForCarrier">requestIsAttachEnabledForCarrier</string>
+ <string name="addAttachRestrictionForCarrier">addAttachRestrictionForCarrier</string>
+ <string name="removeAttachRestrictionForCarrier">removeAttachRestrictionForCarrier</string>
+ <string name="getAttachRestrictionReasonsForCarrier">getAttachRestrictionReasonsForCarrier</string>
+ <string name="getSatellitePlmnsForCarrier">getSatellitePlmnsForCarrier</string>
+
+ <string name="removeUserRestrictReason">removeUserRestrictReason</string>
+ <string name="addUserRestrictReason">addUserRestrictReason</string>
+ <string name="getSatellitePlmn">getSatellitePlmn</string>
+ <string name="getAllSatellitePlmn">getAllSatellitePlmn</string>
+ <string name="isSatelliteEnabledForCarrier">isSatelliteEnabledForCarrier</string>
+ <string name="isRequestIsSatelliteEnabledForCarrier">isRequestIsSatelliteEnabledForCarrier</string>
+
+ <string name="Back">Back</string>
+ <string name="ClearLog">Clear Log</string>
+</resources>
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Datagram.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Datagram.java
new file mode 100644
index 0000000..9ea1b44
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Datagram.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellitetestapp;
+
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.telephony.satellite.EnableRequestAttributes;
+import android.telephony.satellite.PointingInfo;
+import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.SatelliteDatagramCallback;
+import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.SatelliteModemStateCallback;
+import android.telephony.satellite.SatelliteTransmissionUpdateCallback;
+import android.telephony.satellite.stub.SatelliteResult;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.TextView;
+
+import java.util.LinkedList;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Activity related to Datagram APIs.
+ */
+public class Datagram extends Activity {
+
+ private static final String TAG = "Datagram";
+ private static final int MAX_NUMBER_OF_STORED_STATES = 3;
+ private int mTransferState;
+ private int mModemState;
+ LinkedList<Integer> mModemStateQueue = new LinkedList<>();
+ LinkedList<Integer> mSendQueue = new LinkedList<>();
+ LinkedList<Integer> mReceiveQueue = new LinkedList<>();
+
+ private SatelliteManager mSatelliteManager;
+ private SatelliteDatagramCallbackTestApp mDatagramCallback;
+ private SatelliteModemStateCallbackTestApp mStateCallback;
+ private SatelliteTransmissionUpdateCallbackTestApp mCallback;
+ private android.telephony.satellite.stub.SatelliteDatagram mReceivedDatagram;
+
+ private String mShowSatelliteModemStateTransition;
+ private String mShowDatagramSendStateTransition;
+ private String mShowDatagramReceiveStateTransition;
+ private static final long TIMEOUT = 3000;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSatelliteManager = getSystemService(SatelliteManager.class);
+ mDatagramCallback = new SatelliteDatagramCallbackTestApp();
+ mStateCallback = new SatelliteModemStateCallbackTestApp();
+ mCallback = new SatelliteTransmissionUpdateCallbackTestApp();
+
+ mReceivedDatagram = new android.telephony.satellite.stub.SatelliteDatagram();
+
+ setContentView(R.layout.activity_Datagram);
+ findViewById(R.id.startSatelliteTransmissionUpdates)
+ .setOnClickListener(this::startTransmissionUpdatesApp);
+ findViewById(R.id.stopSatelliteTransmissionUpdates)
+ .setOnClickListener(this::stopTransmissionUpdatesApp);
+ findViewById(R.id.pollPendingSatelliteDatagrams)
+ .setOnClickListener(this::pollPendingDatagramsApp);
+ findViewById(R.id.sendSatelliteDatagram)
+ .setOnClickListener(this::sendDatagramApp);
+ findViewById(R.id.registerForSatelliteDatagram)
+ .setOnClickListener(this::registerForIncomingDatagramApp);
+ findViewById(R.id.unregisterForSatelliteDatagram)
+ .setOnClickListener(this::unregisterForIncomingDatagramApp);
+ findViewById(R.id.showDatagramSendStateTransition)
+ .setOnClickListener(this::showDatagramSendStateTransitionApp);
+ findViewById(R.id.showDatagramReceiveStateTransition)
+ .setOnClickListener(this::showDatagramReceiveStateTransitionApp);
+ findViewById(R.id.registerForSatelliteModemStateChanged)
+ .setOnClickListener(this::registerForModemStateChangedApp);
+ findViewById(R.id.unregisterForSatelliteModemStateChanged)
+ .setOnClickListener(this::unregisterForModemStateChangedApp);
+ findViewById(R.id.showSatelliteModemStateTransition)
+ .setOnClickListener(this::showSatelliteModemStateTransitionApp);
+
+ findViewById(R.id.Back).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(Datagram.this, SatelliteTestApp.class));
+ }
+ });
+ }
+
+ protected class SatelliteDatagramCallbackTestApp implements SatelliteDatagramCallback {
+ @Override
+ public void onSatelliteDatagramReceived(long datagramId, SatelliteDatagram datagram,
+ int pendingCount, Consumer<Void> callback) {
+ Log.d(TAG, "onSatelliteDatagramReceived in TestApp: datagramId =" + datagramId
+ + ", datagram =" + datagram + ", pendingCount=" + pendingCount);
+ }
+ }
+
+ protected class SatelliteModemStateCallbackTestApp implements SatelliteModemStateCallback {
+ @Override
+ public void onSatelliteModemStateChanged(int state) {
+ mModemState = state;
+ mModemStateQueue.addLast(state);
+ if (mModemStateQueue.size() > MAX_NUMBER_OF_STORED_STATES) {
+ mModemStateQueue.removeFirst();
+ }
+ mShowSatelliteModemStateTransition = getSatelliteModemStateTransition(mModemStateQueue);
+ Log.d(TAG, "onSatelliteModemStateChanged in TestApp: state=" + mModemState);
+ }
+ }
+ protected class SatelliteTransmissionUpdateCallbackTestApp implements
+ SatelliteTransmissionUpdateCallback {
+ @Override
+ public void onSatellitePositionChanged(PointingInfo pointingInfo) {
+ Log.d(TAG, "onSatellitePositionChanged in TestApp: pointingInfo =" + pointingInfo);
+ }
+
+ @Override
+ public void onSendDatagramStateChanged(int state, int sendPendingCount, int errorCode) {
+ mTransferState = state;
+ mSendQueue.addLast(state);
+ if (mSendQueue.size() > MAX_NUMBER_OF_STORED_STATES) {
+ mSendQueue.removeFirst();
+ }
+ mShowDatagramSendStateTransition = getTransferStateTransition(mSendQueue);
+ Log.d(TAG, "onSendDatagramStateChanged in TestApp: state =" + mTransferState
+ + ", sendPendingCount =" + sendPendingCount + ", errorCode=" + errorCode);
+ }
+
+ @Override
+ public void onReceiveDatagramStateChanged(
+ int state, int receivePendingCount, int errorCode) {
+ mTransferState = state;
+ mReceiveQueue.addLast(state);
+ if (mReceiveQueue.size() > MAX_NUMBER_OF_STORED_STATES) {
+ mReceiveQueue.removeFirst();
+ }
+ mShowDatagramReceiveStateTransition = getTransferStateTransition(mReceiveQueue);
+ Log.d(TAG, "onReceiveDatagramStateChanged in TestApp: state=" + mTransferState
+ + ", receivePendingCount=" + receivePendingCount + ", errorCode=" + errorCode);
+ }
+ }
+
+ private void startTransmissionUpdatesApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+ mSatelliteManager.requestEnabled(
+ new EnableRequestAttributes.Builder(true).setDemoMode(true).build(),
+ Runnable::run, error::offer);
+ TextView showErrorStatusTextView = findViewById(R.id.showErrorStatus);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ showErrorStatusTextView.setText("Timed out to enable the satellite");
+ return;
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ showErrorStatusTextView.setText("Failed to enable satellite, error = "
+ + SatelliteErrorUtils.mapError(value));
+ return;
+ }
+ } catch (InterruptedException e) {
+ showErrorStatusTextView.setText("Enable SatelliteService exception caught = " + e);
+ return;
+ }
+ error.clear();
+ mSatelliteManager.startTransmissionUpdates(Runnable::run, error::offer, mCallback);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ textView.setText("Timed out to startSatelliteTransmissionUpdates");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ textView.setText("Failed to startSatelliteTransmissionUpdates with error = "
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ textView.setText("startSatelliteTransmissionUpdates is successful");
+ }
+ } catch (InterruptedException e) {
+ textView.setText("startSatelliteTransmissionUpdates exception caught =" + e);
+ }
+ }
+
+ private void stopTransmissionUpdatesApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+ mSatelliteManager.stopTransmissionUpdates(mCallback, Runnable::run, error::offer);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ textView.setText("Timed out to stopSatelliteTransmissionUpdates");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ textView.setText("Failed to stopSatelliteTransmissionUpdates with error = "
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ textView.setText("stopSatelliteTransmissionUpdates is successful");
+ }
+ } catch (InterruptedException e) {
+ textView.setText("stopSatelliteTransmissionUpdates exception caught =" + e);
+ }
+ }
+ private void pollPendingDatagramsApp(View view) {
+ LinkedBlockingQueue<Integer> resultListener = new LinkedBlockingQueue<>(1);
+ TextView showErrorStatusTextView = findViewById(R.id.showErrorStatus);
+ TextView textView = findViewById(R.id.text_id);
+ mSatelliteManager.setDeviceAlignedWithSatellite(true);
+ if (SatelliteTestApp.getTestSatelliteService() != null) {
+ SatelliteTestApp.getTestSatelliteService().sendOnPendingDatagrams();
+ }
+ mSatelliteManager.requestEnabled(
+ new EnableRequestAttributes.Builder(true).setDemoMode(true).build(),
+ Runnable::run, resultListener::offer);
+ try {
+ Integer value = resultListener.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ showErrorStatusTextView.setText("Timed out to enable the satellite");
+ return;
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ showErrorStatusTextView.setText("Failed to enable satellite, error = "
+ + SatelliteErrorUtils.mapError(value));
+ return;
+ }
+ resultListener.clear();
+ Log.d(TAG, "Poll to check queue is cleared = "
+ + resultListener.poll(TIMEOUT, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ showErrorStatusTextView.setText("Enable SatelliteService exception caught = " + e);
+ return;
+ }
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
+ try {
+ Integer value = resultListener.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ textView.setText("Timed out for poll message");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ textView.setText("Failed to pollPendingDatagrams with error = "
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ textView.setText("pollPendingDatagrams is successful");
+ }
+ } catch (InterruptedException e) {
+ textView.setText("pollPendingDatagrams exception caught =" + e);
+ }
+ }
+
+ private void sendDatagramApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ mSatelliteManager.setDeviceAlignedWithSatellite(true);
+ LinkedBlockingQueue<Integer> resultListener = new LinkedBlockingQueue<>(1);
+ String mText = "This is a test datagram message";
+ SatelliteDatagram datagram = new SatelliteDatagram(mText.getBytes());
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ try {
+ Integer value = resultListener.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ textView.setText("Timed out for sendDatagram");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ textView.setText("Failed to sendDatagram with error = "
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ textView.setText("sendDatagram is successful");
+ }
+ } catch (InterruptedException e) {
+ textView.setText("sendDatagram exception caught =" + e);
+ }
+ }
+
+ private void registerForIncomingDatagramApp(View view) {
+ int result = mSatelliteManager.registerForIncomingDatagram(Runnable::run,
+ mDatagramCallback);
+ TextView textView = findViewById(R.id.text_id);
+ if (result == 0) {
+ textView.setText("registerForIncomingDatagram is successful");
+ } else {
+ textView.setText("Status for registerForIncomingDatagram : "
+ + SatelliteErrorUtils.mapError(result));
+ }
+ }
+
+ private void unregisterForIncomingDatagramApp(View view) {
+ mSatelliteManager.unregisterForIncomingDatagram(mDatagramCallback);
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("unregisterForIncomingDatagram is successful");
+ }
+
+ private void showDatagramSendStateTransitionApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Last datagram send state transition is : "
+ + mShowDatagramSendStateTransition);
+ }
+
+ private void showDatagramReceiveStateTransitionApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Last datagram receive state transition is : "
+ + mShowDatagramReceiveStateTransition);
+ }
+
+ private void registerForModemStateChangedApp(View view) {
+ int result = mSatelliteManager.registerForModemStateChanged(Runnable::run,
+ mStateCallback);
+ TextView textView = findViewById(R.id.text_id);
+ if (result == 0) {
+ textView.setText("registerForModemStateChanged is successful");
+ } else {
+ textView.setText("Status for registerForModemStateChanged : "
+ + SatelliteErrorUtils.mapError(result));
+ }
+ }
+
+ private void unregisterForModemStateChangedApp(View view) {
+ mSatelliteManager.unregisterForModemStateChanged(mStateCallback);
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("unregisterForModemStateChanged is successful");
+ }
+
+ private void showSatelliteModemStateTransitionApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText(
+ "Last modem transition state is: " + mShowSatelliteModemStateTransition);
+ }
+
+ private String getSatelliteModemStateName(@SatelliteManager.SatelliteModemState int state) {
+ switch (state) {
+ case SatelliteManager.SATELLITE_MODEM_STATE_IDLE:
+ return "IDLE";
+ case SatelliteManager.SATELLITE_MODEM_STATE_LISTENING:
+ return "LISTENING";
+ case SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING:
+ return "DATAGRAM_TRANSFERRING";
+ case SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING:
+ return "DATAGRAM_RETRYING";
+ case SatelliteManager.SATELLITE_MODEM_STATE_OFF:
+ return "OFF";
+ case SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE:
+ return "UNAVAILABLE";
+ default: return "UNKNOWN";
+ }
+ }
+
+ private String getSatelliteModemStateTransition(LinkedList<Integer> states) {
+ StringBuilder sb = new StringBuilder();
+ for (int state : states) {
+ sb.append(getSatelliteModemStateName(state));
+ sb.append("=>");
+ }
+ if (!sb.isEmpty()) {
+ sb.delete(sb.length() - 2, sb.length());
+ }
+ return sb.toString();
+ }
+
+ private String getDatagramTransferStateName(
+ @SatelliteManager.SatelliteDatagramTransferState int state) {
+ switch (state) {
+ case SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE: return "IDLE";
+ case SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING: return "SENDING";
+ case SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS: return "SEND_SUCCESS";
+ case SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED: return "SEND_FAILED";
+ case SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING: return "RECEIVING";
+ case SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS: return "RECEIVE_SUCCESS";
+ case SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE: return "RECEIVE_NONE";
+ case SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED: return "RECEIVE_FAILED";
+ default: return "UNKNOWN";
+ }
+ }
+
+ private String getTransferStateTransition(LinkedList<Integer> states) {
+ StringBuilder sb = new StringBuilder();
+ for (int state : states) {
+ sb.append(getDatagramTransferStateName(state));
+ sb.append("=>");
+ }
+ if (!sb.isEmpty()) {
+ sb.delete(sb.length() - 2, sb.length());
+ }
+ return sb.toString();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ SharedPreferences sh = getSharedPreferences("TestSatelliteSharedPref", MODE_PRIVATE);
+ String modemStateTransition = sh.getString("modem_state",
+ mShowSatelliteModemStateTransition);
+ String datagramSendStateTransition = sh.getString("datagram_send_state",
+ mShowDatagramSendStateTransition);
+ String datagramReceiveStateTransition = sh.getString("datagram_receive_state",
+ mShowDatagramReceiveStateTransition);
+
+ // Setting the fetched data
+ mShowSatelliteModemStateTransition = modemStateTransition;
+ mShowDatagramSendStateTransition = datagramSendStateTransition;
+ mShowDatagramReceiveStateTransition = datagramReceiveStateTransition;
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ SharedPreferences sharedPreferences = getSharedPreferences("TestSatelliteSharedPref",
+ MODE_PRIVATE);
+ SharedPreferences.Editor myEdit = sharedPreferences.edit();
+
+ myEdit.putString("modem_state", mShowSatelliteModemStateTransition);
+ myEdit.putString("datagram_send_state", mShowDatagramSendStateTransition);
+ myEdit.putString("datagram_receive_state", mShowDatagramReceiveStateTransition);
+ myEdit.apply();
+ }
+
+ protected void onDestroy() {
+ super.onDestroy();
+ SharedPreferences sharedPreferences = getSharedPreferences("TestSatelliteSharedPref",
+ MODE_PRIVATE);
+ final SharedPreferences.Editor sharedPrefsEditor = sharedPreferences.edit();
+
+ sharedPrefsEditor.remove("modem_state");
+ sharedPrefsEditor.remove("datagram_send_state");
+ sharedPrefsEditor.remove("datagram_receive_state");
+ sharedPrefsEditor.commit();
+ }
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/ILocalSatelliteListener.aidl b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/ILocalSatelliteListener.aidl
new file mode 100644
index 0000000..0a32432
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/ILocalSatelliteListener.aidl
@@ -0,0 +1,73 @@
+
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellitetestapp;
+
+import android.telephony.satellite.stub.SatelliteDatagram;
+
+/**
+ * {@hide}
+ */
+oneway interface ILocalSatelliteListener {
+ /**
+ * Indicates that the remote service - SatelliteModemInterface - has successfully connected to
+ * the TestSatelliteService.
+ */
+ void onRemoteServiceConnected();
+
+ /**
+ * Indicates that TestSatelliteService has just received the request
+ * startSendingSatellitePointingInfo from Telephony.
+ */
+ void onStartSendingSatellitePointingInfo();
+
+ /**
+ * Indicates that TestSatelliteService has just received the request
+ * stopSendingSatellitePointingInfo from Telephony.
+ */
+ void onStopSendingSatellitePointingInfo();
+
+ /**
+ * Indicates that TestSatelliteService has just received the request
+ * pollPendingSatelliteDatagrams from Telephony.
+ */
+ void onPollPendingSatelliteDatagrams();
+
+ /**
+ * Indicates that TestSatelliteService has just received the request
+ * sendSatelliteDatagram from Telephony.
+ */
+ void onSendSatelliteDatagram(in SatelliteDatagram datagram, in boolean isEmergency);
+
+ /**
+ * Indicates that TestSatelliteService has just received the request
+ * requestSatelliteListeningEnabled from Telephony.
+ */
+ void onSatelliteListeningEnabled(in boolean enabled);
+
+ /**
+ * Indicates that TestSatelliteService has just received the request
+ * enableCellularModemWhileSatelliteModeIsOn from Telephony.
+ */
+ void onEnableCellularModemWhileSatelliteModeIsOn(in boolean enable);
+
+ /**
+ * Indicates that MockSatelliteService has just received the request
+ * setSatellitePlmn from Telephony.
+ */
+ void onSetSatellitePlmn();
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/MultipleSendReceive.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/MultipleSendReceive.java
new file mode 100644
index 0000000..723f690
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/MultipleSendReceive.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellitetestapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telephony.satellite.EnableRequestAttributes;
+import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.SatelliteManager;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.TextView;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity related to Send and Receiving of message APIs for satellite.
+ */
+public class MultipleSendReceive extends Activity {
+
+ private static final String TAG = "MultipleSendReceive";
+
+ private SatelliteManager mSatelliteManager;
+ private android.telephony.satellite.stub.SatelliteDatagram mReceivedDatagram;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSatelliteManager = getSystemService(SatelliteManager.class);
+ mReceivedDatagram = new android.telephony.satellite.stub.SatelliteDatagram();
+
+ setContentView(R.layout.activity_MultipleSendReceive);
+ findViewById(R.id.multiplePollPendingSatelliteDatagrams)
+ .setOnClickListener(this::multiplePollPendingSatelliteDatagramsApp);
+ findViewById(R.id.multipleSendSatelliteDatagram)
+ .setOnClickListener(this::multipleSendSatelliteDatagramApp);
+ findViewById(R.id.multipleSendReceiveSatelliteDatagram)
+ .setOnClickListener(this::multipleSendReceiveSatelliteDatagramApp);
+ findViewById(R.id.Back).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(MultipleSendReceive.this, SatelliteTestApp.class));
+ }
+ });
+ }
+
+ private void multiplePollPendingSatelliteDatagramsApp(View view) {
+ mSatelliteManager.setDeviceAlignedWithSatellite(true);
+ SatelliteTestApp.getTestSatelliteService().sendOnPendingDatagrams();
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 4);
+ LinkedBlockingQueue<Integer> resultListener = new LinkedBlockingQueue<>(1);
+ mSatelliteManager.requestEnabled(
+ new EnableRequestAttributes.Builder(true).setDemoMode(true).build(),
+ Runnable::run, resultListener::offer);
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 3);
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 2);
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 1);
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 0);
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
+ try {
+ Integer value = resultListener.poll(1000, TimeUnit.MILLISECONDS);
+ TextView textView = findViewById(R.id.text_id);
+ if (value == 0) {
+ textView.setText("multiplePollPendingSatelliteDatagrams is Successful");
+ } else {
+ textView.setText("Status for multiplePollPendingSatelliteDatagrams : "
+ + SatelliteErrorUtils.mapError(value));
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "exception caught =" + e);
+ }
+ }
+
+
+ private void multipleSendSatelliteDatagramApp(View view) {
+ mSatelliteManager.setDeviceAlignedWithSatellite(true);
+ LinkedBlockingQueue<Integer> resultListener = new LinkedBlockingQueue<>(1);
+ String mText = "This is a test datagram message";
+ SatelliteDatagram datagram = new SatelliteDatagram(mText.getBytes());
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ try {
+ Integer value = resultListener.poll(1000, TimeUnit.MILLISECONDS);
+ TextView textView = findViewById(R.id.text_id);
+ if (value == 0) {
+ textView.setText("multipleSendSatelliteDatagram is Successful");
+ } else {
+ textView.setText("Status for multipleSendSatelliteDatagram : "
+ + SatelliteErrorUtils.mapError(value));
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "exception caught =" + e);
+ }
+ }
+
+ private void multipleSendReceiveSatelliteDatagramApp(View view) {
+ mSatelliteManager.setDeviceAlignedWithSatellite(true);
+ LinkedBlockingQueue<Integer> resultListener = new LinkedBlockingQueue<>(1);
+ mSatelliteManager.requestEnabled(
+ new EnableRequestAttributes.Builder(true).setDemoMode(true).build(),
+ Runnable::run, resultListener::offer);
+ String mText = "This is a test datagram message";
+ SatelliteDatagram datagram = new SatelliteDatagram(mText.getBytes());
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 4);
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 3);
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 2);
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 1);
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 0);
+ try {
+ Integer value = resultListener.poll(1000, TimeUnit.MILLISECONDS);
+ TextView textView = findViewById(R.id.text_id);
+ if (value == 0) {
+ textView.setText("multipleSendReceiveSatelliteDatagram is Successful");
+ } else {
+ textView.setText("Status for multipleSendReceiveSatelliteDatagram : "
+ + SatelliteErrorUtils.mapError(value));
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "exception caught =" + e);
+ }
+ }
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Provisioning.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Provisioning.java
new file mode 100644
index 0000000..20c5ef5
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Provisioning.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellitetestapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.SatelliteProvisionStateCallback;
+import android.telephony.satellite.stub.SatelliteResult;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.TextView;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Activity related to Provisioning APIs.
+ */
+public class Provisioning extends Activity {
+
+ private static final String TAG = "Provisioning";
+
+ private boolean mProvisioned = false;
+
+ private SatelliteManager mSatelliteManager;
+ private SatelliteProvisionStateCallbackTestApp mCallback;
+ private static final long TIMEOUT = 3000;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSatelliteManager = getSystemService(SatelliteManager.class);
+ mCallback = new SatelliteProvisionStateCallbackTestApp();
+
+ setContentView(R.layout.activity_Provisioning);
+ findViewById(R.id.provisionSatelliteService)
+ .setOnClickListener(this::provisionServiceApp);
+ findViewById(R.id.deprovisionSatelliteService)
+ .setOnClickListener(this::deprovisionServiceApp);
+ findViewById(R.id.requestIsSatelliteProvisioned)
+ .setOnClickListener(this::requestIsProvisionedApp);
+ findViewById(R.id.registerForSatelliteProvisionStateChanged)
+ .setOnClickListener(this::registerForProvisionStateChangedApp);
+ findViewById(R.id.unregisterForSatelliteProvisionStateChanged)
+ .setOnClickListener(this::unregisterForProvisionStateChangedApp);
+ findViewById(R.id.showCurrentSatelliteProvisionState)
+ .setOnClickListener(this::showCurrentSatelliteProvisionStateApp);
+ findViewById(R.id.Back).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(Provisioning.this, SatelliteTestApp.class));
+ }
+ });
+ }
+
+ protected class SatelliteProvisionStateCallbackTestApp implements
+ SatelliteProvisionStateCallback {
+ @Override
+ public void onSatelliteProvisionStateChanged(boolean provisioned) {
+ mProvisioned = provisioned;
+ Log.d(TAG, "onSatelliteProvisionStateChanged in SatelliteTestApp: provisioned="
+ + mProvisioned);
+ }
+ }
+
+ private void provisionServiceApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+ String mText = "This is test provision data.";
+ byte[] testProvisionData = mText.getBytes();
+ mSatelliteManager.provisionService("SATELLITE_TOKEN", testProvisionData,
+ cancellationSignal, Runnable::run, error::offer);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ textView.setText("Timed out to provision the satellite");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ textView.setText("Failed to provision SatelliteService with error = "
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ textView.setText("Successfully provisioned the satellite");
+ }
+ } catch (InterruptedException e) {
+ textView.setText("Provision SatelliteService exception caught =" + e);
+ }
+ }
+
+ private void deprovisionServiceApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+ mSatelliteManager.deprovisionService("SATELLITE_TOKEN", Runnable::run,
+ error::offer);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ textView.setText("Timed out to deprovision the satellite");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ textView.setText("Failed to deprovision SatelliteService with error = "
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ textView.setText("Successfully deprovisioned the satellite");
+ }
+ } catch (InterruptedException e) {
+ textView.setText("Deprovision SatelliteService exception caught =" + e);
+ }
+ }
+
+ private void requestIsProvisionedApp(View view) {
+ final AtomicReference<Boolean> enabled = new AtomicReference<>();
+ final AtomicReference<Integer> errorCode = new AtomicReference<>();
+ OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> mReceiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Boolean result) {
+ enabled.set(result);
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for requestIsSatelliteProvisioned result: "
+ + enabled.get());
+ }
+
+ @Override
+ public void onError(SatelliteManager.SatelliteException exception) {
+ errorCode.set(exception.getErrorCode());
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for requestIsSatelliteProvisioned error : "
+ + SatelliteErrorUtils.mapError(errorCode.get()));
+ }
+ };
+ mSatelliteManager.requestIsProvisioned(Runnable::run, mReceiver);
+ }
+
+ private void registerForProvisionStateChangedApp(View view) {
+ int result = mSatelliteManager.registerForProvisionStateChanged(Runnable::run,
+ mCallback);
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for registerForSatelliteProvisionStateChanged : "
+ + SatelliteErrorUtils.mapError(result));
+ }
+
+ private void unregisterForProvisionStateChangedApp(View view) {
+ mSatelliteManager.unregisterForProvisionStateChanged(mCallback);
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("unregisterForSatelliteProvisionStateChanged is successful");
+ }
+
+ private void showCurrentSatelliteProvisionStateApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Current Provision State is " + mProvisioned);
+ }
+
+ // Fetch the stored data in onResume()
+ // Because this is what will be called when the app opens again
+ @Override
+ protected void onResume() {
+ super.onResume();
+ // Fetching the stored data from the SharedPreference
+ SharedPreferences sh = getSharedPreferences("MySharedPref", MODE_PRIVATE);
+ boolean isProvisioned = sh.getBoolean("provision_state", mProvisioned);
+
+ // Setting the fetched data
+ mProvisioned = isProvisioned;
+ }
+
+ // Store the data in the SharedPreference in the onPause() method
+ // When the user closes the application onPause() will be called and data will be stored
+ @Override
+ protected void onPause() {
+ super.onPause();
+ // Creating a shared pref object with a file name "MySharedPref" in private mode
+ SharedPreferences sharedPreferences = getSharedPreferences("MySharedPref", MODE_PRIVATE);
+ SharedPreferences.Editor myEdit = sharedPreferences.edit();
+
+ // write all the data entered by the user in SharedPreference and apply
+ myEdit.putBoolean("provision_state", mProvisioned);
+ myEdit.apply();
+ }
+
+ protected void onDestroy() {
+ super.onDestroy();
+ SharedPreferences sharedPreferences = getSharedPreferences("MySharedPref", MODE_PRIVATE);
+
+ final SharedPreferences.Editor sharedPrefsEditor = sharedPreferences.edit();
+ sharedPrefsEditor.remove("provision_state");
+ sharedPrefsEditor.commit();
+ }
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteControl.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteControl.java
new file mode 100644
index 0000000..dd7b825
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteControl.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellitetestapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.OutcomeReceiver;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.satellite.EnableRequestAttributes;
+import android.telephony.satellite.SatelliteCapabilities;
+import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.stub.SatelliteResult;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.TextView;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Activity related to SatelliteControl APIs for satellite.
+ */
+public class SatelliteControl extends Activity {
+
+ private static final long TIMEOUT = 3000;
+
+ private SatelliteManager mSatelliteManager;
+ private SubscriptionManager mSubscriptionManager;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSatelliteManager = getSystemService(SatelliteManager.class);
+ mSubscriptionManager = getSystemService(SubscriptionManager.class);
+
+ setContentView(R.layout.activity_SatelliteControl);
+ findViewById(R.id.enableSatellite)
+ .setOnClickListener(this::enableSatelliteApp);
+ findViewById(R.id.disableSatellite)
+ .setOnClickListener(this::disableSatelliteApp);
+ findViewById(R.id.requestIsSatelliteEnabled)
+ .setOnClickListener(this::requestIsEnabledApp);
+ findViewById(R.id.requestIsDemoModeEnabled)
+ .setOnClickListener(this::requestIsDemoModeEnabledApp);
+ findViewById(R.id.requestIsSatelliteSupported)
+ .setOnClickListener(this::requestIsSupportedApp);
+ findViewById(R.id.requestSatelliteCapabilities)
+ .setOnClickListener(this::requestCapabilitiesApp);
+ findViewById(R.id.requestIsSatelliteCommunicationAllowedForCurrentLocation)
+ .setOnClickListener(this::requestIsCommunicationAllowedForCurrentLocationApp);
+ findViewById(R.id.requestTimeForNextSatelliteVisibility)
+ .setOnClickListener(this::requestTimeForNextSatelliteVisibilityApp);
+ findViewById(R.id.removeUserRestrictReason)
+ .setOnClickListener(this::removeUserRestrictReasonApp);
+ findViewById(R.id.addUserRestrictReason)
+ .setOnClickListener(this::addUserRestrictReasonApp);
+ findViewById(R.id.getSatellitePlmn)
+ .setOnClickListener(this::getSatellitePlmnApp);
+ findViewById(R.id.getAllSatellitePlmn)
+ .setOnClickListener(this::getAllSatellitePlmnApp);
+ findViewById(R.id.isSatelliteEnabledForCarrier)
+ .setOnClickListener(this::isSatelliteEnabledForCarrierApp);
+ findViewById(R.id.isRequestIsSatelliteEnabledForCarrier)
+ .setOnClickListener(this::isRequestIsSatelliteEnabledForCarrierApp);
+ findViewById(R.id.Back).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(SatelliteControl.this, SatelliteTestApp.class));
+ }
+ });
+ }
+
+ private void enableSatelliteApp(View view) {
+ LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+ mSatelliteManager.requestEnabled(
+ new EnableRequestAttributes.Builder(true).setDemoMode(true).build(),
+ Runnable::run, error::offer);
+ TextView textView = findViewById(R.id.text_id);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ textView.setText("Timed out to enable the satellite");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ textView.setText("Failed to enable the satellite, error ="
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ textView.setText("Successfully enabled the satellite");
+ }
+ } catch (InterruptedException e) {
+ textView.setText("Enable SatelliteService exception caught =" + e);
+ }
+ }
+
+ private void disableSatelliteApp(View view) {
+ LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+ mSatelliteManager.requestEnabled(new EnableRequestAttributes.Builder(false).build(),
+ Runnable::run, error::offer);
+ TextView textView = findViewById(R.id.text_id);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ textView.setText("Timed out to disable the satellite");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ textView.setText("Failed to disable the satellite, error ="
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ textView.setText("Successfully disabled the satellite");
+ }
+ } catch (InterruptedException e) {
+ textView.setText("Disable SatelliteService exception caught =" + e);
+ }
+ }
+
+ private void requestIsEnabledApp(View view) {
+ final AtomicReference<Boolean> enabled = new AtomicReference<>();
+ final AtomicReference<Integer> errorCode = new AtomicReference<>();
+ OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> receiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Boolean result) {
+ enabled.set(result);
+ TextView textView = findViewById(R.id.text_id);
+ if (enabled.get()) {
+ textView.setText("requestIsEnabled is true");
+ } else {
+ textView.setText("Status for requestIsEnabled result : "
+ + enabled.get());
+ }
+ }
+
+ @Override
+ public void onError(SatelliteManager.SatelliteException exception) {
+ errorCode.set(exception.getErrorCode());
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for requestIsEnabled error : "
+ + SatelliteErrorUtils.mapError(errorCode.get()));
+ }
+ };
+ mSatelliteManager.requestIsEnabled(Runnable::run, receiver);
+ }
+
+ private void requestIsDemoModeEnabledApp(View view) {
+ final AtomicReference<Boolean> enabled = new AtomicReference<>();
+ final AtomicReference<Integer> errorCode = new AtomicReference<>();
+ OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> receiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Boolean result) {
+ enabled.set(result);
+ TextView textView = findViewById(R.id.text_id);
+ if (enabled.get()) {
+ textView.setText("requestIsDemoModeEnabled is true");
+ } else {
+ textView.setText("Status for requestIsDemoModeEnabled result : "
+ + enabled.get());
+ }
+ }
+
+ @Override
+ public void onError(SatelliteManager.SatelliteException exception) {
+ errorCode.set(exception.getErrorCode());
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for requestIsDemoModeEnabled error : "
+ + SatelliteErrorUtils.mapError(errorCode.get()));
+ }
+ };
+ mSatelliteManager.requestIsDemoModeEnabled(Runnable::run, receiver);
+ }
+
+ private void requestIsSupportedApp(View view) {
+ final AtomicReference<Boolean> enabled = new AtomicReference<>();
+ final AtomicReference<Integer> errorCode = new AtomicReference<>();
+ OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> receiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Boolean result) {
+ enabled.set(result);
+ TextView textView = findViewById(R.id.text_id);
+ if (enabled.get()) {
+ textView.setText("requestIsSupported is true");
+ } else {
+ textView.setText("Status for requestIsSupported result : "
+ + enabled.get());
+ }
+ }
+
+ @Override
+ public void onError(SatelliteManager.SatelliteException exception) {
+ errorCode.set(exception.getErrorCode());
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for requestIsSupported error : "
+ + SatelliteErrorUtils.mapError(errorCode.get()));
+ }
+ };
+ mSatelliteManager.requestIsSupported(Runnable::run, receiver);
+ }
+
+ private void requestCapabilitiesApp(View view) {
+ final AtomicReference<SatelliteCapabilities> capabilities = new AtomicReference<>();
+ final AtomicReference<Integer> errorCode = new AtomicReference<>();
+ OutcomeReceiver<SatelliteCapabilities, SatelliteManager.SatelliteException> receiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(SatelliteCapabilities result) {
+ capabilities.set(result);
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for requestCapabilities result: "
+ + capabilities.get());
+ }
+
+ @Override
+ public void onError(SatelliteManager.SatelliteException exception) {
+ errorCode.set(exception.getErrorCode());
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for requestCapabilities error : "
+ + SatelliteErrorUtils.mapError(errorCode.get()));
+ }
+ };
+ mSatelliteManager.requestCapabilities(Runnable::run, receiver);
+ }
+
+ private void requestIsCommunicationAllowedForCurrentLocationApp(View view) {
+ final AtomicReference<Boolean> enabled = new AtomicReference<>();
+ final AtomicReference<Integer> errorCode = new AtomicReference<>();
+ String display = "requestIsCommunicationAllowedForCurrentLocation";
+ OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> receiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Boolean result) {
+ enabled.set(result);
+ TextView textView = findViewById(R.id.text_id);
+ if (enabled.get()) {
+ textView.setText(display + "is true");
+ } else {
+ textView.setText("Status for" + display + "result: " + enabled.get());
+ }
+
+ }
+
+ @Override
+ public void onError(SatelliteManager.SatelliteException exception) {
+ errorCode.set(exception.getErrorCode());
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for" + display + "error: "
+ + SatelliteErrorUtils.mapError(errorCode.get()));
+ }
+ };
+ mSatelliteManager.requestIsCommunicationAllowedForCurrentLocation(Runnable::run,
+ receiver);
+ }
+
+ private void requestTimeForNextSatelliteVisibilityApp(View view) {
+ final AtomicReference<Duration> nextVisibilityDuration = new AtomicReference<>();
+ final AtomicReference<Integer> errorCode = new AtomicReference<>();
+ OutcomeReceiver<Duration, SatelliteManager.SatelliteException> receiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Duration result) {
+ nextVisibilityDuration.set(result);
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for requestTimeForNextSatelliteVisibility result : "
+ + result.getSeconds());
+ }
+
+ @Override
+ public void onError(SatelliteManager.SatelliteException exception) {
+ errorCode.set(exception.getErrorCode());
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for requestTimeForNextSatelliteVisibility error : "
+ + SatelliteErrorUtils.mapError(errorCode.get()));
+ }
+ };
+ mSatelliteManager.requestTimeForNextSatelliteVisibility(Runnable::run, receiver);
+ }
+
+ private void removeUserRestrictReasonApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+ List<SubscriptionInfo> infoList = mSubscriptionManager.getAvailableSubscriptionInfoList();
+ List<Integer> subIdList = infoList.stream()
+ .map(SubscriptionInfo::getSubscriptionId)
+ .toList();
+ for (int subId : subIdList) {
+ mSatelliteManager.removeAttachRestrictionForCarrier(subId,
+ SatelliteManager.SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER,
+ Runnable::run, error::offer);
+ }
+
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ textView.setText("Timed out to removeAttachRestrictionForCarrier");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ textView.setText("Failed to removeAttachRestrictionForCarrier with error = "
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ textView.setText(subIdList == null || subIdList.isEmpty() ? "no active subId list" :
+ "removeAttachRestrictionForCarrier for all subIdList=" + subIdList);
+ }
+ } catch (InterruptedException e) {
+ textView.setText("removeAttachRestrictionForCarrier exception caught =" + e);
+ }
+ }
+
+ private void addUserRestrictReasonApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+ List<SubscriptionInfo> infoList = mSubscriptionManager.getAvailableSubscriptionInfoList();
+ List<Integer> subIdList = infoList.stream()
+ .map(SubscriptionInfo::getSubscriptionId)
+ .toList();
+ for (int subId : subIdList) {
+ mSatelliteManager.addAttachRestrictionForCarrier(subId,
+ SatelliteManager.SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER,
+ Runnable::run, error::offer);
+ }
+
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ textView.setText("Timed out to addAttachRestrictionForCarrier");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ textView.setText("Failed to addAttachRestrictionForCarrier with error = "
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ textView.setText(subIdList == null || subIdList.isEmpty() ? "no active subId list" :
+ "addAttachRestrictionForCarrier for all subIdList=" + subIdList);
+ }
+ } catch (InterruptedException e) {
+ textView.setText("addAttachRestrictionForCarrier exception caught =" + e);
+ }
+ }
+
+ private void getSatellitePlmnApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("[SatelliteService] getSatellitePlmnApp = "
+ + SatelliteTestApp.getTestSatelliteService().getCarrierPlmnList());
+ }
+
+ private void getAllSatellitePlmnApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("[SatelliteService] getAllSatellitePlmnApp = "
+ + SatelliteTestApp.getTestSatelliteService().getAllSatellitePlmnList());
+ }
+
+ private void isSatelliteEnabledForCarrierApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("[SatelliteService] isSatelliteEnabledForCarrier= "
+ + SatelliteTestApp.getTestSatelliteService().isSatelliteEnabledForCarrier());
+ }
+
+ private void isRequestIsSatelliteEnabledForCarrierApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("[SatelliteService] isRequestIsSatelliteEnabledForCarrier= "
+ + SatelliteTestApp.getTestSatelliteService()
+ .isRequestIsSatelliteEnabledForCarrier());
+ }
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteErrorUtils.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteErrorUtils.java
new file mode 100644
index 0000000..ef0c85c
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteErrorUtils.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellitetestapp;
+
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_ACCESS_BARRED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_ERROR;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_ILLEGAL_STATE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_BUSY;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_ERROR;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NETWORK_ERROR;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NETWORK_TIMEOUT;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NOT_AUTHORIZED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NO_RESOURCES;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_FAILED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_IN_PROGRESS;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SERVER_ERROR;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SERVICE_NOT_PROVISIONED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_INVALID_MODEM_STATE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_INVALID_ARGUMENTS;
+
+import android.util.Log;
+
+/**
+ * Utils class for satellite error to display as string in SatelliteTestApp UI.
+ */
+public class SatelliteErrorUtils {
+ private static final String TAG = "SatelliteErrorUtils";
+
+ /**
+ * @param error int from the satellite manager.
+ * @return The converted SatelliteError for the testapp, result of the operation.
+ */
+ public static String mapError(int error) {
+ switch (error) {
+ case SATELLITE_RESULT_SUCCESS:
+ return "SATELLITE_RESULT_SUCCESS";
+ case SATELLITE_RESULT_ERROR:
+ return "SATELLITE_RESULT_ERROR";
+ case SATELLITE_RESULT_SERVER_ERROR:
+ return "SATELLITE_RESULT_SERVER_ERROR";
+ case SATELLITE_RESULT_SERVICE_ERROR:
+ return "SATELLITE_RESULT_SERVICE_ERROR";
+ case SATELLITE_RESULT_MODEM_ERROR:
+ return "SATELLITE_RESULT_MODEM_ERROR";
+ case SATELLITE_RESULT_NETWORK_ERROR:
+ return "SATELLITE_RESULT_NETWORK_ERROR";
+ case SATELLITE_RESULT_INVALID_TELEPHONY_STATE:
+ return "SATELLITE_RESULT_INVALID_TELEPHONY_STATE";
+ case SATELLITE_RESULT_INVALID_MODEM_STATE:
+ return "SATELLITE_RESULT_INVALID_MODEM_STATE";
+ case SATELLITE_RESULT_INVALID_ARGUMENTS:
+ return "SATELLITE_RESULT_INVALID_ARGUMENTS";
+ case SATELLITE_RESULT_REQUEST_FAILED:
+ return "SATELLITE_RESULT_REQUEST_FAILED";
+ case SATELLITE_RESULT_RADIO_NOT_AVAILABLE:
+ return "SATELLITE_RESULT_RADIO_NOT_AVAILABLE";
+ case SATELLITE_RESULT_REQUEST_NOT_SUPPORTED:
+ return "SATELLITE_RESULT_REQUEST_NOT_SUPPORTED";
+ case SATELLITE_RESULT_NO_RESOURCES:
+ return "SATELLITE_RESULT_NO_RESOURCES";
+ case SATELLITE_RESULT_SERVICE_NOT_PROVISIONED:
+ return "SATELLITE_RESULT_SERVICE_NOT_PROVISIONED";
+ case SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS:
+ return "SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS";
+ case SATELLITE_RESULT_REQUEST_ABORTED:
+ return "SATELLITE_RESULT_REQUEST_ABORTED";
+ case SATELLITE_RESULT_ACCESS_BARRED:
+ return "SATELLITE_RESULT_ACCESS_BARRED";
+ case SATELLITE_RESULT_NETWORK_TIMEOUT:
+ return "SATELLITE_RESULT_NETWORK_TIMEOUT";
+ case SATELLITE_RESULT_NOT_REACHABLE:
+ return "SATELLITE_RESULT_NOT_REACHABLE";
+ case SATELLITE_RESULT_NOT_AUTHORIZED:
+ return "SATELLITE_RESULT_NOT_AUTHORIZED";
+ case SATELLITE_RESULT_NOT_SUPPORTED:
+ return "SATELLITE_RESULT_NOT_SUPPORTED";
+ case SATELLITE_RESULT_REQUEST_IN_PROGRESS:
+ return "SATELLITE_RESULT_REQUEST_IN_PROGRESS";
+ case SATELLITE_RESULT_MODEM_BUSY:
+ return "SATELLITE_RESULT_MODEM_BUSY";
+ case SATELLITE_RESULT_ILLEGAL_STATE:
+ return "SATELLITE_RESULT_ILLEGAL_STATE";
+ }
+ Log.d(TAG, "Received invalid satellite service error: " + error);
+ return "SATELLITE_RESULT_SERVICE_ERROR";
+ }
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteTestApp.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteTestApp.java
new file mode 100644
index 0000000..c8ee5fa
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteTestApp.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellitetestapp;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.telephony.satellite.stub.SatelliteDatagram;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * SatelliteTestApp main activity to navigate to other APIs related to satellite.
+ */
+public class SatelliteTestApp extends Activity {
+
+ private static final String TAG = "SatelliteTestApp";
+ public static TestSatelliteService sSatelliteService;
+ private final Object mSendDatagramLock = new Object();
+
+ private TestSatelliteServiceConnection mSatelliteServiceConn;
+ private List<SatelliteDatagram> mSentSatelliteDatagrams = new ArrayList<>();
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (mSatelliteServiceConn == null) {
+ mSatelliteServiceConn = new TestSatelliteServiceConnection();
+ getBaseContext().bindService(new Intent(getBaseContext(),
+ TestSatelliteService.class), mSatelliteServiceConn, Context.BIND_AUTO_CREATE);
+ }
+
+ setContentView(R.layout.activity_SatelliteTestApp);
+ findViewById(R.id.SatelliteControl).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(SatelliteTestApp.this, SatelliteControl.class);
+ startActivity(intent);
+ }
+ });
+ findViewById(R.id.Datagram).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(SatelliteTestApp.this, Datagram.class);
+ startActivity(intent);
+ }
+ });
+ findViewById(R.id.Provisioning).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(SatelliteTestApp.this, Provisioning.class);
+ startActivity(intent);
+ }
+ });
+ findViewById(R.id.MultipleSendReceive).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(SatelliteTestApp.this, MultipleSendReceive.class);
+ startActivity(intent);
+ }
+ });
+ findViewById(R.id.SendReceive).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(SatelliteTestApp.this, SendReceive.class);
+ startActivity(intent);
+ }
+ });
+ findViewById(R.id.TestSatelliteWrapper).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(SatelliteTestApp.this, TestSatelliteWrapper.class);
+ startActivity(intent);
+ }
+ });
+ }
+
+ private final ILocalSatelliteListener mSatelliteListener =
+ new ILocalSatelliteListener.Stub() {
+ @Override
+ public void onRemoteServiceConnected() {
+ Log.d(TAG, "onRemoteServiceConnected");
+ }
+
+ @Override
+ public void onStartSendingSatellitePointingInfo() {
+ Log.d(TAG, "onStartSendingSatellitePointingInfo");
+ }
+
+ @Override
+ public void onStopSendingSatellitePointingInfo() {
+ Log.d(TAG, "onStopSendingSatellitePointingInfo");
+ }
+
+ @Override
+ public void onPollPendingSatelliteDatagrams() {
+ Log.d(TAG, "onPollPendingSatelliteDatagrams");
+ }
+
+ @Override
+ public void onSendSatelliteDatagram(
+ SatelliteDatagram datagram, boolean isEmergency) {
+ Log.d(TAG, "onSendSatelliteDatagram");
+ synchronized (mSendDatagramLock) {
+ mSentSatelliteDatagrams.add(datagram);
+ }
+ }
+
+ @Override
+ public void onSatelliteListeningEnabled(boolean enable) {
+ Log.d(TAG, "onSatelliteListeningEnabled");
+ }
+
+ @Override
+ public void onEnableCellularModemWhileSatelliteModeIsOn(boolean enable) {
+ Log.d(TAG, "onEnableCellularModemWhileSatelliteModeIsOn");
+ }
+
+ @Override
+ public void onSetSatellitePlmn() {
+ Log.d(TAG, "onSetSatellitePlmn");
+ }
+ };
+
+ private class TestSatelliteServiceConnection implements ServiceConnection {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.d(TAG, "onServiceConnected in SatelliteTestApp");
+ sSatelliteService = ((TestSatelliteService.LocalBinder) service).getService();
+ sSatelliteService.setLocalSatelliteListener(mSatelliteListener);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.d(TAG, "onServiceDisconnected in SatelliteTestApp");
+ sSatelliteService = null;
+ }
+ }
+
+ public static TestSatelliteService getTestSatelliteService() {
+ return sSatelliteService;
+ }
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SendReceive.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SendReceive.java
new file mode 100644
index 0000000..ab7b1c4
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SendReceive.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellitetestapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+import android.telephony.satellite.EnableRequestAttributes;
+import android.telephony.satellite.PointingInfo;
+import android.telephony.satellite.SatelliteCapabilities;
+import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.SatelliteDatagramCallback;
+import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.SatelliteTransmissionUpdateCallback;
+import android.telephony.satellite.stub.SatelliteResult;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+/**
+ * Activity related to Send and Receiving of message APIs for satellite.
+ */
+public class SendReceive extends Activity {
+
+ private static final String TAG = "SendReceive";
+
+ private SatelliteManager mSatelliteManager;
+ private SendReceive.SatelliteDatagramCallbackTestApp mCallback;
+
+ private PointingInfo mPointingInfo;
+ private String mMessageInput = "";
+ private String mMessageOutput = "";
+ private static final long TIMEOUT = 3000;
+
+ private EditText mEnterMessage;
+ private TextView mMessageStatusTextView;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSatelliteManager = getSystemService(SatelliteManager.class);
+ mCallback = new SendReceive.SatelliteDatagramCallbackTestApp();
+
+ setContentView(R.layout.activity_SendReceive);
+ findViewById(R.id.sendMessage).setOnClickListener(this::sendStatusApp);
+ findViewById(R.id.receiveMessage).setOnClickListener(this::receiveStatusApp);
+ mEnterMessage = (EditText) findViewById(R.id.enterText);
+ mMessageStatusTextView = findViewById(R.id.messageStatus);
+ findViewById(R.id.Back).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(SendReceive.this, SatelliteTestApp.class));
+ }
+ });
+ }
+
+ protected class SatelliteDatagramCallbackTestApp implements SatelliteDatagramCallback {
+ @Override
+ public void onSatelliteDatagramReceived(long datagramId, SatelliteDatagram datagram,
+ int pendingCount, Consumer<Void> callback) {
+ Log.d(TAG, "onSatelliteDatagramReceived in TestApp: datagramId =" + datagramId
+ + ", datagram =" + datagram + ", pendingCount=" + pendingCount);
+ mMessageStatusTextView.setText("Last received satellite message is = "
+ + new String(datagram.getSatelliteDatagram()));
+ }
+ }
+
+ protected class SatelliteTransmissionUpdateCallbackTestApp implements
+ SatelliteTransmissionUpdateCallback {
+ @Override
+ public void onSatellitePositionChanged(PointingInfo pointingInfo) {
+ mPointingInfo = pointingInfo;
+ Log.d(TAG, "onSatellitePositionChanged in TestApp for sendReceive: pointingInfo = "
+ + mPointingInfo);
+ TextView satellitePositionTextView = findViewById(R.id.satellitePosition);
+ satellitePositionTextView.setText("Successfully received the satellite position : "
+ + mPointingInfo);
+ }
+
+ @Override
+ public void onSendDatagramStateChanged(int state, int sendPendingCount, int errorCode) {
+ Log.d(TAG, "onSendDatagramStateChanged in TestApp for sendReceive: state = "
+ + state + ", sendPendingCount =" + sendPendingCount + ", errorCode="
+ + errorCode);
+ }
+
+ @Override
+ public void onReceiveDatagramStateChanged(
+ int state, int receivePendingCount, int errorCode) {
+ Log.d(TAG, "onReceiveDatagramStateChanged in TestApp for sendReceive: state = "
+ + state + ", " + "receivePendingCount=" + receivePendingCount + ", errorCode="
+ + errorCode);
+ }
+ }
+
+ private void sendStatusApp(View view) {
+ LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+ mMessageInput = mEnterMessage.getText().toString();
+ mMessageOutput = mEnterMessage.getText().toString();
+ byte[] testProvisionData = mMessageInput.getBytes();
+ setupForTransferringDatagram(testProvisionData);
+
+ SatelliteDatagram datagram = new SatelliteDatagram(mMessageInput.getBytes());
+ //Sending Message
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, error::offer);
+ TextView messageStatusTextView = findViewById(R.id.messageStatus);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ messageStatusTextView.setText("Timed out to send the message");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ messageStatusTextView.setText("Failed to send the message, error ="
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ messageStatusTextView.setText("Successfully sent the message = "
+ + mEnterMessage.getText().toString());
+ }
+ } catch (InterruptedException e) {
+ messageStatusTextView.setText("sendDatagram exception caught = " + e);
+ }
+ }
+
+ private void receiveStatusApp(View view) {
+ LinkedBlockingQueue<Integer> resultListener = new LinkedBlockingQueue<>(1);
+ byte[] testProvisionData = mMessageOutput.getBytes();
+ setupForTransferringDatagram(testProvisionData);
+
+ int result = mSatelliteManager.registerForIncomingDatagram(Runnable::run, mCallback);
+ TextView showErrorStatusTextView = findViewById(R.id.showErrorStatus);
+ if (result != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ showErrorStatusTextView.setText("Status for registerForIncomingDatagram : "
+ + SatelliteErrorUtils.mapError(result));
+ }
+ if (SatelliteTestApp.getTestSatelliteService() != null) {
+ SatelliteTestApp.getTestSatelliteService().sendOnPendingDatagrams();
+ }
+ mSatelliteManager.requestEnabled(
+ new EnableRequestAttributes.Builder(true).setDemoMode(true).build(),
+ Runnable::run, resultListener::offer);
+ try {
+ Integer value = resultListener.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ showErrorStatusTextView.setText("Timed out to enable the satellite");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ showErrorStatusTextView.setText("Failed to enable satellite, error = "
+ + SatelliteErrorUtils.mapError(value));
+ return;
+ }
+ resultListener.clear();
+ } catch (InterruptedException e) {
+ showErrorStatusTextView.setText("Enable SatelliteService exception caught = " + e);
+ return;
+ }
+
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
+ try {
+ Integer value = resultListener.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ mMessageStatusTextView.setText("Timed out to poll pending messages");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ mMessageStatusTextView.setText("Failed to poll pending messages, error = "
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ mMessageStatusTextView.setText("Successfully polled pending messages");
+ }
+ } catch (InterruptedException e) {
+ mMessageStatusTextView.setText("pollPendingDatagrams exception caught = " + e);
+ }
+ }
+
+ private void setupForTransferringDatagram(byte[] provisionData) {
+ TextView showErrorStatusTextView = findViewById(R.id.showErrorStatus);
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+
+ //Provisioning
+ mSatelliteManager.provisionService("SATELLITE_TOKEN", provisionData,
+ cancellationSignal, Runnable::run, error::offer);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ showErrorStatusTextView.setText("Timed out to provision the satellite");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ showErrorStatusTextView.setText("Failed to provision satellite, error = "
+ + SatelliteErrorUtils.mapError(value));
+ return;
+ }
+ } catch (InterruptedException e) {
+ showErrorStatusTextView.setText("Provision SatelliteService exception caught = " + e);
+ return;
+ }
+ error.clear();
+
+ //Static position of device
+ final AtomicReference<SatelliteCapabilities> capabilities = new AtomicReference<>();
+ final AtomicReference<Integer> errorCode = new AtomicReference<>();
+ OutcomeReceiver<SatelliteCapabilities, SatelliteManager.SatelliteException> receiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(SatelliteCapabilities result) {
+ capabilities.set(result);
+ TextView devicePositionTextView = findViewById(R.id.devicePosition);
+ devicePositionTextView.setText("Successfully receive the device position : "
+ + capabilities.get().getAntennaPositionMap());
+ }
+
+ @Override
+ public void onError(SatelliteManager.SatelliteException exception) {
+ errorCode.set(exception.getErrorCode());
+ TextView devicePositionTextView = findViewById(R.id.devicePosition);
+ devicePositionTextView.setText("Unable to fetch device position error is : "
+ + SatelliteErrorUtils.mapError(errorCode.get()));
+ }
+ };
+ mSatelliteManager.requestCapabilities(Runnable::run, receiver);
+
+ //Satellite Position
+ SatelliteTransmissionUpdateCallbackTestApp callback =
+ new SatelliteTransmissionUpdateCallbackTestApp();
+ mSatelliteManager.requestEnabled(
+ new EnableRequestAttributes.Builder(true).setDemoMode(true).build(),
+ Runnable::run, error::offer);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ showErrorStatusTextView.setText("Timed out to enable the satellite");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ showErrorStatusTextView.setText("Failed to enable satellite, error = "
+ + SatelliteErrorUtils.mapError(value));
+ return;
+ }
+ } catch (InterruptedException e) {
+ showErrorStatusTextView.setText("Enable SatelliteService exception caught = " + e);
+ return;
+ }
+ error.clear();
+
+ mSatelliteManager.startTransmissionUpdates(Runnable::run, error::offer, callback);
+ // Position update
+ android.telephony.satellite.stub.PointingInfo pointingInfo =
+ new android.telephony.satellite.stub.PointingInfo();
+ pointingInfo.satelliteAzimuth = 50.5f;
+ pointingInfo.satelliteElevation = 20.36f;
+ if (SatelliteTestApp.getTestSatelliteService() != null) {
+ SatelliteTestApp.getTestSatelliteService().sendOnSatellitePositionChanged(pointingInfo);
+ }
+ TextView satellitePositionTextView = findViewById(R.id.satellitePosition);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ satellitePositionTextView.setText("Failed to register for satellite transmission"
+ + "updates");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ satellitePositionTextView.setText("Failed to register for satellite transmission "
+ + "updates, error = " + SatelliteErrorUtils.mapError(value));
+ }
+ } catch (InterruptedException e) {
+ satellitePositionTextView.setText("startSatelliteTransmissionUpdates exception caught ="
+ + e);
+ }
+ //Device is aligned with the satellite for demo mode
+ mSatelliteManager.setDeviceAlignedWithSatellite(true);
+ }
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteService.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteService.java
new file mode 100644
index 0000000..ed9fa10
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteService.java
@@ -0,0 +1,585 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellitetestapp;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.telephony.IBooleanConsumer;
+import android.telephony.IIntegerConsumer;
+import android.telephony.satellite.AntennaDirection;
+import android.telephony.satellite.AntennaPosition;
+import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer;
+import android.telephony.satellite.stub.ISatelliteListener;
+import android.telephony.satellite.stub.NTRadioTechnology;
+import android.telephony.satellite.stub.PointingInfo;
+import android.telephony.satellite.stub.SatelliteCapabilities;
+import android.telephony.satellite.stub.SatelliteDatagram;
+import android.telephony.satellite.stub.SatelliteImplBase;
+import android.telephony.satellite.stub.SatelliteModemState;
+import android.telephony.satellite.stub.SatelliteResult;
+import android.telephony.satellite.stub.SatelliteService;
+import android.util.Log;
+
+import com.android.internal.util.FunctionalUtils;
+import com.android.telephony.Rlog;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Test service for Satellite to verify end to end flow via testapp.
+ */
+public class TestSatelliteService extends SatelliteImplBase {
+ private static final String TAG = "TestSatelliteService";
+
+ // Hardcoded values below
+ private static final int SATELLITE_ALWAYS_VISIBLE = 0;
+ /** SatelliteCapabilities constant indicating that the radio technology is proprietary. */
+ private static final int[] SUPPORTED_RADIO_TECHNOLOGIES =
+ new int[]{NTRadioTechnology.PROPRIETARY};
+ /** SatelliteCapabilities constant indicating that pointing to satellite is required. */
+ private static final boolean POINTING_TO_SATELLITE_REQUIRED = true;
+ /** SatelliteCapabilities constant indicating the maximum number of characters per datagram. */
+ private static final int MAX_BYTES_PER_DATAGRAM = 339;
+ /** SatelliteCapabilities constant keys which are used to fill mAntennaPositionMap. */
+ private static final int[] ANTENNA_POSITION_KEYS = new int[]{
+ SatelliteManager.DISPLAY_MODE_OPENED, SatelliteManager.DISPLAY_MODE_CLOSED};
+ /** SatelliteCapabilities constant values which are used to fill mAntennaPositionMap. */
+ private static final AntennaPosition[] ANTENNA_POSITION_VALUES = new AntennaPosition[] {
+ new AntennaPosition(new AntennaDirection(1, 1, 1),
+ SatelliteManager.DEVICE_HOLD_POSITION_PORTRAIT),
+ new AntennaPosition(new AntennaDirection(2, 2, 2),
+ SatelliteManager.DEVICE_HOLD_POSITION_LANDSCAPE_LEFT)
+ };
+
+ @NonNull
+ private final Map<IBinder, ISatelliteListener> mRemoteListeners = new HashMap<>();
+ @Nullable private ILocalSatelliteListener mLocalListener;
+ private final LocalBinder mBinder = new LocalBinder();
+ @SatelliteResult
+ private int mErrorCode = SatelliteResult.SATELLITE_RESULT_SUCCESS;
+ private final AtomicBoolean mShouldNotifyRemoteServiceConnected =
+ new AtomicBoolean(false);
+
+ // For local access of this Service.
+ class LocalBinder extends Binder {
+ TestSatelliteService getService() {
+ return TestSatelliteService.this;
+ }
+ }
+
+ private boolean mIsCommunicationAllowedInLocation;
+ private boolean mIsEnabled;
+ private boolean mIsProvisioned;
+ private boolean mIsSupported;
+ private int mModemState;
+ private boolean mIsCellularModemEnabledMode;
+ private List<String> mCarrierPlmnList = new ArrayList<>();
+ private List<String> mAllPlmnList = new ArrayList<>();
+ private boolean mIsSatelliteEnabledForCarrier;
+ private boolean mIsRequestIsSatelliteEnabledForCarrier;
+
+ /**
+ * Create TestSatelliteService using the Executor specified for methods being called from
+ * the framework.
+ *
+ * @param executor The executor for the framework to use when executing satellite methods.
+ */
+ public TestSatelliteService(@NonNull Executor executor) {
+ super(executor);
+ mIsCommunicationAllowedInLocation = true;
+ mIsEnabled = false;
+ mIsProvisioned = false;
+ mIsSupported = true;
+ mModemState = SatelliteModemState.SATELLITE_MODEM_STATE_OFF;
+ mIsCellularModemEnabledMode = false;
+ mIsSatelliteEnabledForCarrier = false;
+ mIsRequestIsSatelliteEnabledForCarrier = false;
+ }
+
+ /**
+ * Zero-argument constructor to prevent service binding exception.
+ */
+ public TestSatelliteService() {
+ this(Runnable::run);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (SatelliteService.SERVICE_INTERFACE.equals(intent.getAction())) {
+ logd("Remote service bound");
+ return getBinder();
+ }
+ logd("Local service bound");
+ return mBinder;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ logd("onCreate");
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ logd("onDestroy");
+ }
+
+ @Override
+ public void setSatelliteListener(@NonNull ISatelliteListener listener) {
+ logd("setSatelliteListener");
+ mRemoteListeners.put(listener.asBinder(), listener);
+ notifyRemoteServiceConnected();
+ }
+
+ @Override
+ public void requestSatelliteListeningEnabled(boolean enabled, int timeout,
+ @NonNull IIntegerConsumer errorCallback) {
+ logd("requestSatelliteListeningEnabled: mErrorCode=" + mErrorCode);
+
+ if (mLocalListener != null) {
+ runWithExecutor(() -> mLocalListener.onSatelliteListeningEnabled(enabled));
+ } else {
+ loge("requestSatelliteListeningEnabled: mLocalListener is null");
+ }
+
+ if (!verifySatelliteModemState(errorCallback)) {
+ return;
+ }
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+
+ if (enabled) {
+ updateSatelliteModemState(SatelliteModemState.SATELLITE_MODEM_STATE_LISTENING);
+ } else {
+ updateSatelliteModemState(SatelliteModemState.SATELLITE_MODEM_STATE_IDLE);
+ }
+ runWithExecutor(() -> errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+ }
+
+ @Override
+ public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
+ @NonNull IIntegerConsumer errorCallback) {
+ logd("requestSatelliteEnabled: mErrorCode=" + mErrorCode + " enable = " + enableSatellite);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+
+ if (enableSatellite) {
+ enableSatellite(errorCallback);
+ } else {
+ disableSatellite(errorCallback);
+ }
+ }
+
+ private void enableSatellite(@NonNull IIntegerConsumer errorCallback) {
+ mIsEnabled = true;
+ updateSatelliteModemState(SatelliteModemState.SATELLITE_MODEM_STATE_IDLE);
+ runWithExecutor(() -> errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+ }
+
+ private void disableSatellite(@NonNull IIntegerConsumer errorCallback) {
+ mIsEnabled = false;
+ updateSatelliteModemState(SatelliteModemState.SATELLITE_MODEM_STATE_OFF);
+ runWithExecutor(() -> errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+ }
+
+ @Override
+ public void requestIsSatelliteEnabled(@NonNull IIntegerConsumer errorCallback,
+ @NonNull IBooleanConsumer callback) {
+ logd("requestIsSatelliteEnabled: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+ runWithExecutor(() -> callback.accept(mIsEnabled));
+ }
+
+ @Override
+ public void requestIsSatelliteSupported(@NonNull IIntegerConsumer errorCallback,
+ @NonNull IBooleanConsumer callback) {
+ logd("requestIsSatelliteSupported");
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+ runWithExecutor(() -> callback.accept(mIsSupported));
+ }
+
+ @Override
+ public void requestSatelliteCapabilities(@NonNull IIntegerConsumer errorCallback,
+ @NonNull ISatelliteCapabilitiesConsumer callback) {
+ logd("requestSatelliteCapabilities: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+
+ SatelliteCapabilities capabilities = new SatelliteCapabilities();
+ capabilities.supportedRadioTechnologies = SUPPORTED_RADIO_TECHNOLOGIES;
+ capabilities.isPointingRequired = POINTING_TO_SATELLITE_REQUIRED;
+ capabilities.maxBytesPerOutgoingDatagram = MAX_BYTES_PER_DATAGRAM;
+ capabilities.antennaPositionKeys = ANTENNA_POSITION_KEYS;
+ capabilities.antennaPositionValues = ANTENNA_POSITION_VALUES;
+ runWithExecutor(() -> callback.accept(capabilities));
+ }
+
+ @Override
+ public void startSendingSatellitePointingInfo(@NonNull IIntegerConsumer errorCallback) {
+ logd("startSendingSatellitePointingInfo: mErrorCode=" + mErrorCode);
+ if (!verifySatelliteModemState(errorCallback)) {
+ if (mLocalListener != null) {
+ runWithExecutor(() -> mLocalListener.onStartSendingSatellitePointingInfo());
+ } else {
+ loge("startSendingSatellitePointingInfo: mLocalListener is null");
+ }
+ return;
+ }
+
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ } else {
+ runWithExecutor(() -> errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+ }
+
+ if (mLocalListener != null) {
+ runWithExecutor(() -> mLocalListener.onStartSendingSatellitePointingInfo());
+ } else {
+ loge("startSendingSatellitePointingInfo: mLocalListener is null");
+ }
+ }
+
+ @Override
+ public void stopSendingSatellitePointingInfo(@NonNull IIntegerConsumer errorCallback) {
+ logd("stopSendingSatellitePointingInfo: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ } else {
+ runWithExecutor(() -> errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+ }
+
+ if (mLocalListener != null) {
+ runWithExecutor(() -> mLocalListener.onStopSendingSatellitePointingInfo());
+ } else {
+ loge("stopSendingSatellitePointingInfo: mLocalListener is null");
+ }
+ }
+
+ @Override
+ public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData,
+ @NonNull IIntegerConsumer errorCallback) {
+ logd("provisionSatelliteService: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+ runWithExecutor(() -> errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+ updateSatelliteProvisionState(true);
+ }
+
+ @Override
+ public void deprovisionSatelliteService(@NonNull String token,
+ @NonNull IIntegerConsumer errorCallback) {
+ logd("deprovisionSatelliteService: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+ runWithExecutor(() -> errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+ updateSatelliteProvisionState(false);
+ }
+
+ @Override
+ public void requestIsSatelliteProvisioned(@NonNull IIntegerConsumer errorCallback,
+ @NonNull IBooleanConsumer callback) {
+ logd("requestIsSatelliteProvisioned: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+ runWithExecutor(() -> callback.accept(mIsProvisioned));
+ }
+
+ @Override
+ public void pollPendingSatelliteDatagrams(@NonNull IIntegerConsumer errorCallback) {
+ logd("pollPendingSatelliteDatagrams: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ } else {
+ runWithExecutor(() -> errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+ }
+
+ if (mLocalListener != null) {
+ runWithExecutor(() -> mLocalListener.onPollPendingSatelliteDatagrams());
+ } else {
+ loge("pollPendingDatagrams: mLocalListener is null");
+ }
+ }
+
+ @Override
+ public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isEmergency,
+ @NonNull IIntegerConsumer errorCallback) {
+ logd("sendDatagram: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ } else {
+ runWithExecutor(() -> errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+ }
+
+ if (mLocalListener != null) {
+ runWithExecutor(() -> mLocalListener.onSendSatelliteDatagram(datagram, isEmergency));
+ } else {
+ loge("sendDatagram: mLocalListener is null");
+ }
+ }
+
+ @Override
+ public void requestSatelliteModemState(@NonNull IIntegerConsumer errorCallback,
+ @NonNull IIntegerConsumer callback) {
+ logd("requestSatelliteModemState: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+ runWithExecutor(() -> callback.accept(mModemState));
+ }
+
+ @Override
+ public void requestTimeForNextSatelliteVisibility(@NonNull IIntegerConsumer errorCallback,
+ @NonNull IIntegerConsumer callback) {
+ logd("requestTimeForNextSatelliteVisibility: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+ runWithExecutor(() -> callback.accept(SATELLITE_ALWAYS_VISIBLE));
+ }
+
+ @Override
+ public void setSatellitePlmn(int simLogicalSlotIndex, List<String> carrierPlmnList,
+ List<String> allSatellitePlmnList, IIntegerConsumer resultCallback) {
+ logd("setSatellitePlmn: simLogicalSlotIndex=" + simLogicalSlotIndex + " , carrierPlmnList="
+ + carrierPlmnList + " , allSatellitePlmnList=" + allSatellitePlmnList);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> resultCallback.accept(mErrorCode));
+ return;
+ }
+ runWithExecutor(() -> resultCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+
+ mCarrierPlmnList = carrierPlmnList;
+ mAllPlmnList = allSatellitePlmnList;
+
+ if (mLocalListener != null) {
+ runWithExecutor(() -> mLocalListener.onSetSatellitePlmn());
+ } else {
+ loge("setSatellitePlmn: mLocalListener is null");
+ }
+ }
+
+ @Override
+ public void setSatelliteEnabledForCarrier(int simLogicalSlotIndex, boolean satelliteEnabled,
+ IIntegerConsumer callback) {
+ logd("setSatelliteEnabledForCarrier: simLogicalSlotIndex=" + simLogicalSlotIndex
+ + ", satelliteEnabled=" + satelliteEnabled);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> callback.accept(mErrorCode));
+ return;
+ }
+
+ mIsSatelliteEnabledForCarrier = satelliteEnabled;
+ runWithExecutor(() -> callback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+ }
+
+ @Override
+ public void requestIsSatelliteEnabledForCarrier(int simLogicalSlotIndex,
+ IIntegerConsumer resultCallback, IBooleanConsumer callback) {
+ logd("requestIsSatelliteEnabledForCarrier: simLogicalSlotIndex=" + simLogicalSlotIndex);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> resultCallback.accept(mErrorCode));
+ mIsRequestIsSatelliteEnabledForCarrier = false;
+ return;
+ }
+
+ runWithExecutor(() -> callback.accept(mIsSatelliteEnabledForCarrier));
+ mIsRequestIsSatelliteEnabledForCarrier = true;
+ }
+
+ public void setLocalSatelliteListener(@NonNull ILocalSatelliteListener listener) {
+ logd("setLocalSatelliteListener: listener=" + listener);
+ mLocalListener = listener;
+ if (mShouldNotifyRemoteServiceConnected.get()) {
+ notifyRemoteServiceConnected();
+ }
+ }
+
+ public void setErrorCode(@SatelliteResult int errorCode) {
+ logd("setErrorCode: errorCode=" + errorCode);
+ mErrorCode = errorCode;
+ }
+
+ public void setSatelliteSupport(boolean supported) {
+ logd("setSatelliteSupport: supported=" + supported);
+ mIsSupported = supported;
+ }
+
+ public void sendOnSatelliteDatagramReceived(SatelliteDatagram datagram, int pendingCount) {
+ logd("sendOnSatelliteDatagramReceived");
+ mRemoteListeners.values().forEach(listener -> runWithExecutor(() ->
+ listener.onSatelliteDatagramReceived(datagram, pendingCount)));
+ }
+
+ public void sendOnPendingDatagrams() {
+ logd("sendOnPendingDatagrams");
+ mRemoteListeners.values().forEach(listener -> runWithExecutor(() ->
+ listener.onPendingDatagrams()));
+ }
+
+ public void sendOnSatellitePositionChanged(PointingInfo pointingInfo) {
+ logd("sendOnSatellitePositionChanged");
+ mRemoteListeners.values().forEach(listener -> runWithExecutor(() ->
+ listener.onSatellitePositionChanged(pointingInfo)));
+ }
+
+ /**
+ * Helper method to verify that the satellite modem is properly configured to receive
+ * requests.
+ *
+ * @param errorCallback The callback to notify of any errors preventing satellite requests.
+ * @return {@code true} if the satellite modem is configured to receive requests and
+ * {@code false} if it is not.
+ */
+ private boolean verifySatelliteModemState(@NonNull IIntegerConsumer errorCallback) {
+ if (!mIsSupported) {
+ runWithExecutor(() -> errorCallback.accept(
+ SatelliteResult.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED));
+ return false;
+ }
+ if (!mIsProvisioned) {
+ runWithExecutor(() -> errorCallback.accept(
+ SatelliteResult.SATELLITE_RESULT_SERVICE_NOT_PROVISIONED));
+ return false;
+ }
+ if (!mIsEnabled) {
+ runWithExecutor(() -> errorCallback.accept(
+ SatelliteResult.SATELLITE_RESULT_INVALID_MODEM_STATE));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Update the satellite modem state and notify listeners if it changed.
+ *
+ * @param modemState The {@link SatelliteModemState} to update.
+ */
+ private void updateSatelliteModemState(int modemState) {
+ if (modemState == mModemState) {
+ return;
+ }
+ if (mIsCellularModemEnabledMode
+ && modemState == SatelliteModemState.SATELLITE_MODEM_STATE_OFF) {
+ logd("Not updating the Modem state to Off as it is in CellularModemEnabledMode");
+ return;
+ }
+ mRemoteListeners.values().forEach(listener -> runWithExecutor(() ->
+ listener.onSatelliteModemStateChanged(modemState)));
+ mModemState = modemState;
+ }
+
+ /**
+ * Update the satellite provision state and notify listeners if it changed.
+ *
+ * @param isProvisioned {@code true} if the satellite is currently provisioned and
+ * {@code false} if it is not.
+ */
+ private void updateSatelliteProvisionState(boolean isProvisioned) {
+ logd("updateSatelliteProvisionState: isProvisioned=" + isProvisioned
+ + ", mIsProvisioned=" + mIsProvisioned);
+ if (isProvisioned == mIsProvisioned) {
+ return;
+ }
+ mIsProvisioned = isProvisioned;
+ logd("updateSatelliteProvisionState: mRemoteListeners.size=" + mRemoteListeners.size());
+ mRemoteListeners.values().forEach(listener -> runWithExecutor(() ->
+ listener.onSatelliteProvisionStateChanged(mIsProvisioned)));
+ }
+
+ /**
+ * Execute the given runnable using the executor that this service was created with.
+ *
+ * @param r A runnable that can throw an exception.
+ */
+ private void runWithExecutor(@NonNull FunctionalUtils.ThrowingRunnable r) {
+ mExecutor.execute(() -> Binder.withCleanCallingIdentity(r));
+ }
+
+ private void notifyRemoteServiceConnected() {
+ logd("notifyRemoteServiceConnected");
+ if (mLocalListener != null) {
+ runWithExecutor(() -> mLocalListener.onRemoteServiceConnected());
+ mShouldNotifyRemoteServiceConnected.set(false);
+ } else {
+ mShouldNotifyRemoteServiceConnected.set(true);
+ }
+ }
+
+ public List<String> getCarrierPlmnList() {
+ return mCarrierPlmnList;
+ }
+
+ public List<String> getAllSatellitePlmnList() {
+ return mAllPlmnList;
+ }
+
+ public boolean isSatelliteEnabledForCarrier() {
+ return mIsSatelliteEnabledForCarrier;
+ }
+
+ public boolean isRequestIsSatelliteEnabledForCarrier() {
+ return mIsRequestIsSatelliteEnabledForCarrier;
+ }
+
+ /**
+ * Log the message to the radio buffer with {@code DEBUG} priority.
+ *
+ * @param log The message to log.
+ */
+ private static void logd(@NonNull String log) {
+ Rlog.d(TAG, log);
+ }
+
+ /**
+ * Log with error attribute
+ *
+ * @param s is string log
+ */
+ protected void loge(@NonNull String s) {
+ Log.e(TAG, s);
+ }
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteWrapper.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteWrapper.java
new file mode 100644
index 0000000..4f0679d
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteWrapper.java
@@ -0,0 +1,582 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellitetestapp;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.OutcomeReceiver;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.satellite.wrapper.NtnSignalStrengthCallbackWrapper;
+import android.telephony.satellite.wrapper.NtnSignalStrengthWrapper;
+import android.telephony.satellite.wrapper.SatelliteCapabilitiesCallbackWrapper;
+import android.telephony.satellite.wrapper.SatelliteManagerWrapper;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+/**
+ * Activity related to SatelliteControl APIs for satellite.
+ */
+public class TestSatelliteWrapper extends Activity {
+ private static final String TAG = "TestSatelliteWrapper";
+ ArrayList<String> mLogMessages = new ArrayList<>();
+ ArrayAdapter<String> mAdapter;
+
+ private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+ private SatelliteManagerWrapper mSatelliteManagerWrapper;
+ private NtnSignalStrengthCallback mNtnSignalStrengthCallback = null;
+ private SatelliteCapabilitiesCallbackWrapper mSatelliteCapabilitiesCallback;
+ private SubscriptionManager mSubscriptionManager;
+ private int mSubId;
+
+ private ListView mLogListView;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSatelliteManagerWrapper = SatelliteManagerWrapper.getInstance(this);
+ mSubscriptionManager = getSystemService(SubscriptionManager.class);
+ mSubId = getActiveSubId();
+
+ setContentView(R.layout.activity_TestSatelliteWrapper);
+ findViewById(R.id.requestNtnSignalStrength)
+ .setOnClickListener(this::requestNtnSignalStrength);
+ findViewById(R.id.registerForNtnSignalStrengthChanged)
+ .setOnClickListener(this::registerForNtnSignalStrengthChanged);
+ findViewById(R.id.unregisterForNtnSignalStrengthChanged)
+ .setOnClickListener(this::unregisterForNtnSignalStrengthChanged);
+ findViewById(R.id.isOnlyNonTerrestrialNetworkSubscription)
+ .setOnClickListener(this::isOnlyNonTerrestrialNetworkSubscription);
+ findViewById(R.id.registerForSatelliteCapabilitiesChanged)
+ .setOnClickListener(this::registerForCapabilitiesChanged);
+ findViewById(R.id.unregisterForSatelliteCapabilitiesChanged)
+ .setOnClickListener(this::unregisterForCapabilitiesChanged);
+ findViewById(R.id.isNonTerrestrialNetwork)
+ .setOnClickListener(this::isNonTerrestrialNetwork);
+ findViewById(R.id.getAvailableServices)
+ .setOnClickListener(this::getAvailableServices);
+ findViewById(R.id.isUsingNonTerrestrialNetwork)
+ .setOnClickListener(this::isUsingNonTerrestrialNetwork);
+ findViewById(R.id.requestAttachEnabledForCarrier_enable)
+ .setOnClickListener(this::requestAttachEnabledForCarrier_enable);
+ findViewById(R.id.requestAttachEnabledForCarrier_disable)
+ .setOnClickListener(this::requestAttachEnabledForCarrier_disable);
+ findViewById(R.id.requestIsAttachEnabledForCarrier)
+ .setOnClickListener(this::requestIsAttachEnabledForCarrier);
+ findViewById(R.id.addAttachRestrictionForCarrier)
+ .setOnClickListener(this::addAttachRestrictionForCarrier);
+ findViewById(R.id.removeAttachRestrictionForCarrier)
+ .setOnClickListener(this::removeAttachRestrictionForCarrier);
+ findViewById(R.id.getAttachRestrictionReasonsForCarrier)
+ .setOnClickListener(this::getAttachRestrictionReasonsForCarrier);
+ findViewById(R.id.getSatellitePlmnsForCarrier)
+ .setOnClickListener(this::getSatellitePlmnsForCarrier);
+ findViewById(R.id.Back).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(TestSatelliteWrapper.this, SatelliteTestApp.class));
+ }
+ });
+ findViewById(R.id.ClearLog).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ clearListView();
+ }
+ });
+
+ mLogListView = findViewById(R.id.logListView);
+ mAdapter = new ArrayAdapter<>(this, R.layout.log_textview, mLogMessages);
+ mLogListView.setAdapter(mAdapter);
+
+ addLogMessage("TestSatelliteWrapper.onCreate()");
+ }
+
+
+ private void clearListView() {
+ mLogMessages.clear();
+ mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ if (mSatelliteManagerWrapper != null) {
+ if (mNtnSignalStrengthCallback != null) {
+ logd("unregisterForNtnSignalStrengthChanged()");
+ mSatelliteManagerWrapper.unregisterForNtnSignalStrengthChanged(
+ mNtnSignalStrengthCallback);
+ }
+ if (mSatelliteCapabilitiesCallback != null) {
+ logd("unregisterForCapabilitiesChanged()");
+ mSatelliteManagerWrapper.unregisterForCapabilitiesChanged(
+ mSatelliteCapabilitiesCallback);
+ }
+ }
+ mSubscriptionManager = null;
+ mSatelliteManagerWrapper = null;
+ mExecutor.shutdown();
+ }
+
+ private void requestNtnSignalStrength(View view) {
+ addLogMessage("requestNtnSignalStrength");
+ logd("requestNtnSignalStrength");
+ OutcomeReceiver<NtnSignalStrengthWrapper,
+ SatelliteManagerWrapper.SatelliteExceptionWrapper> receiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(NtnSignalStrengthWrapper level) {
+ if (level != null) {
+ addLogMessage("requestNtnSignalStrength level is " + level.getLevel());
+ }
+ }
+
+ @Override
+ public void onError(
+ SatelliteManagerWrapper.SatelliteExceptionWrapper exception) {
+ if (exception != null) {
+ String onError = "requestNtnSignalStrength exception: "
+ + translateResultCodeToString(exception.getErrorCode());
+ logd(onError);
+ addLogMessage(onError);
+ }
+ }
+ };
+
+ try {
+ mSatelliteManagerWrapper.requestNtnSignalStrength(mExecutor, receiver);
+ } catch (SecurityException ex) {
+ String errorMessage = "requestNtnSignalStrength: " + ex.getMessage();
+ logd(errorMessage);
+ addLogMessage(errorMessage);
+ }
+ }
+
+ private void registerForNtnSignalStrengthChanged(View view) {
+ addLogMessage("registerForNtnSignalStrengthChanged");
+ logd("registerForNtnSignalStrengthChanged()");
+ if (mNtnSignalStrengthCallback == null) {
+ logd("create new NtnSignalStrengthCallback instance.");
+ mNtnSignalStrengthCallback = new NtnSignalStrengthCallback();
+ }
+
+ try {
+ mSatelliteManagerWrapper.registerForNtnSignalStrengthChanged(mExecutor,
+ mNtnSignalStrengthCallback);
+ } catch (Exception ex) {
+ String errorMessage = "registerForNtnSignalStrengthChanged: " + ex.getMessage();
+ logd(errorMessage);
+ addLogMessage(errorMessage);
+ mNtnSignalStrengthCallback = null;
+ }
+ }
+
+ private void unregisterForNtnSignalStrengthChanged(View view) {
+ addLogMessage("unregisterForNtnSignalStrengthChanged");
+ logd("unregisterForNtnSignalStrengthChanged()");
+ if (mNtnSignalStrengthCallback != null) {
+ mSatelliteManagerWrapper.unregisterForNtnSignalStrengthChanged(
+ mNtnSignalStrengthCallback);
+ mNtnSignalStrengthCallback = null;
+ addLogMessage("mNtnSignalStrengthCallback was unregistered");
+ } else {
+ addLogMessage("mNtnSignalStrengthCallback is null, ignored.");
+ }
+ }
+
+ private void isOnlyNonTerrestrialNetworkSubscription(View view) {
+ addLogMessage("isOnlyNonTerrestrialNetworkSubscription");
+ logd("isOnlyNonTerrestrialNetworkSubscription()");
+ List<SubscriptionInfo> infoList = mSubscriptionManager.getAvailableSubscriptionInfoList();
+ List<Integer> subIdList = infoList.stream()
+ .map(SubscriptionInfo::getSubscriptionId)
+ .toList();
+
+ Map<Integer, Boolean> resultMap = subIdList.stream().collect(
+ Collectors.toMap(
+ id -> id,
+ id -> {
+ boolean result = mSatelliteManagerWrapper
+ .isOnlyNonTerrestrialNetworkSubscription(id);
+ addLogMessage("SatelliteManagerWrapper"
+ + ".isOnlyNonTerrestrialNetworkSubscription(" + id + ")");
+ return result;
+ }
+ ));
+
+ for (Map.Entry<Integer, Boolean> entry : resultMap.entrySet()) {
+ int subId = entry.getKey();
+ boolean result = entry.getValue();
+ addLogMessage("Subscription ID: " + subId + ", Result: " + result);
+ }
+ }
+
+ private void registerForCapabilitiesChanged(View view) {
+ addLogMessage("registerForCapabilitiesChanged");
+ logd("registerForCapabilitiesChanged()");
+ if (mSatelliteCapabilitiesCallback == null) {
+ mSatelliteCapabilitiesCallback =
+ SatelliteCapabilities -> {
+ String message = "Received SatelliteCapabillities : "
+ + SatelliteCapabilities;
+ logd(message);
+ runOnUiThread(() -> addLogMessage(message));
+ };
+ }
+
+ int result = mSatelliteManagerWrapper.registerForCapabilitiesChanged(mExecutor,
+ mSatelliteCapabilitiesCallback);
+ if (result != SatelliteManagerWrapper.SATELLITE_RESULT_SUCCESS) {
+ String onError = translateResultCodeToString(result);
+ logd(onError);
+ addLogMessage(onError);
+ mSatelliteCapabilitiesCallback = null;
+ }
+ }
+
+ private void unregisterForCapabilitiesChanged(View view) {
+ addLogMessage("unregisterForCapabilitiesChanged");
+ logd("unregisterForCapabilitiesChanged()");
+ if (mSatelliteCapabilitiesCallback != null) {
+ mSatelliteManagerWrapper.unregisterForCapabilitiesChanged(
+ mSatelliteCapabilitiesCallback);
+ mSatelliteCapabilitiesCallback = null;
+ addLogMessage("mSatelliteCapabilitiesCallback was unregistered");
+ } else {
+ addLogMessage("mSatelliteCapabilitiesCallback is null, ignored.");
+ }
+ }
+
+ public class NtnSignalStrengthCallback implements NtnSignalStrengthCallbackWrapper {
+ @Override
+ public void onNtnSignalStrengthChanged(
+ @NonNull NtnSignalStrengthWrapper ntnSignalStrength) {
+ String message = "Received NTN SignalStrength : " + ntnSignalStrength.getLevel();
+ logd(message);
+ runOnUiThread(() -> addLogMessage(message));
+ }
+ }
+
+ private void isNonTerrestrialNetwork(View view) {
+ boolean isNonTerrestrialNetwork = mSatelliteManagerWrapper.isNonTerrestrialNetwork(mSubId);
+ addLogMessage("isNonTerrestrialNetwork=" + isNonTerrestrialNetwork);
+ logd("isNonTerrestrialNetwork=" + isNonTerrestrialNetwork);
+ }
+
+ private void getAvailableServices(View view) {
+ List<Integer> as = mSatelliteManagerWrapper.getAvailableServices(mSubId);
+ String availableServices = as.stream().map(Object::toString).collect(
+ Collectors.joining(", "));
+ addLogMessage("getAvailableServices=" + availableServices);
+ logd("getAvailableServices=" + availableServices);
+ }
+
+ private void isUsingNonTerrestrialNetwork(View view) {
+ boolean isUsingNonTerrestrialNetwork =
+ mSatelliteManagerWrapper.isUsingNonTerrestrialNetwork(mSubId);
+ addLogMessage("isUsingNonTerrestrialNetwork=" + isUsingNonTerrestrialNetwork);
+ logd("isUsingNonTerrestrialNetwork=" + isUsingNonTerrestrialNetwork);
+ }
+
+ private void requestAttachEnabledForCarrier_enable(View view) {
+ addLogMessage("requestAttachEnabledForCarrier");
+ logd("requestAttachEnabledForCarrier");
+
+ if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ addLogMessage("requestAttachEnabledForCarrier: Subscription ID is invalid");
+ logd("requestAttachEnabledForCarrier: Subscription ID is invalid");
+ return;
+ }
+
+ Consumer<Integer> callback = result -> {
+ runOnUiThread(() -> addLogMessage("requestAttachEnabledForCarrier result: " + result));
+ logd("requestAttachEnabledForCarrier result: " + result);
+ };
+
+ try {
+ mSatelliteManagerWrapper.requestAttachEnabledForCarrier(mSubId, true, mExecutor,
+ callback);
+ } catch (SecurityException | IllegalArgumentException ex) {
+ String errorMessage = "requestAttachEnabledForCarrier: " + ex.getMessage();
+ logd(errorMessage);
+ addLogMessage(errorMessage);
+ }
+ }
+
+ private void requestAttachEnabledForCarrier_disable(View view) {
+ addLogMessage("requestAttachEnabledForCarrier");
+ logd("requestAttachEnabledForCarrier");
+
+ if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ addLogMessage("requestAttachEnabledForCarrier: Subscription ID is invalid");
+ logd("requestAttachEnabledForCarrier: Subscription ID is invalid");
+ return;
+ }
+
+ Consumer<Integer> callback = result -> {
+ runOnUiThread(() -> addLogMessage("requestAttachEnabledForCarrier result: " + result));
+ logd("requestAttachEnabledForCarrier result: " + result);
+ };
+
+ try {
+ mSatelliteManagerWrapper.requestAttachEnabledForCarrier(mSubId, false, mExecutor,
+ callback);
+ } catch (SecurityException | IllegalArgumentException ex) {
+ String errorMessage = "requestAttachEnabledForCarrier: " + ex.getMessage();
+ logd(errorMessage);
+ addLogMessage(errorMessage);
+ }
+ }
+
+ private void requestIsAttachEnabledForCarrier(View view) {
+ logd("requestIsAttachEnabledForCarrier");
+ addLogMessage("requestIsAttachEnabledForCarrier");
+
+ if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ addLogMessage("requestIsAttachEnabledForCarrier: Subscription ID is invalid");
+ logd("requestIsAttachEnabledForCarrier: Subscription ID is invalid");
+ return;
+ }
+
+ OutcomeReceiver<Boolean,
+ SatelliteManagerWrapper.SatelliteExceptionWrapper> receiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Boolean result) {
+ logd("requestIsAttachEnabledForCarrier: onResult=" + result);
+ addLogMessage("requestIsAttachEnabledForCarrier: onResult=" + result);
+ }
+
+ @Override
+ public void onError(
+ SatelliteManagerWrapper.SatelliteExceptionWrapper exception) {
+ if (exception != null) {
+ String onError = "requestIsAttachEnabledForCarrier exception: "
+ + translateResultCodeToString(exception.getErrorCode());
+ logd(onError);
+ addLogMessage(onError);
+ }
+ }
+ };
+
+ try {
+ mSatelliteManagerWrapper.requestIsAttachEnabledForCarrier(mSubId, mExecutor, receiver);
+ } catch (SecurityException | IllegalStateException | IllegalArgumentException ex) {
+ String errorMessage = "requestIsAttachEnabledForCarrier: " + ex.getMessage();
+ logd(errorMessage);
+ addLogMessage(errorMessage);
+ }
+ }
+
+ private void addAttachRestrictionForCarrier(View view) {
+ addLogMessage("addAttachRestrictionForCarrier");
+ logd("addAttachRestrictionForCarrier");
+
+ if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ addLogMessage("addAttachRestrictionForCarrier: Subscription ID is invalid");
+ logd("addAttachRestrictionForCarrier: Subscription ID is invalid");
+ return;
+ }
+
+ int reason = SatelliteManagerWrapper.SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER;
+
+ Consumer<Integer> callback = result -> {
+ runOnUiThread(() -> addLogMessage("addAttachRestrictionForCarrier result: " + result));
+ logd("addAttachRestrictionForCarrier result: " + result);
+ };
+
+ try {
+ mSatelliteManagerWrapper.addAttachRestrictionForCarrier(mSubId, reason, mExecutor,
+ callback);
+ } catch (SecurityException | IllegalArgumentException ex) {
+ String errorMessage = "addAttachRestrictionForCarrier: " + ex.getMessage();
+ logd(errorMessage);
+ addLogMessage(errorMessage);
+ }
+ }
+
+ private void removeAttachRestrictionForCarrier(View view) {
+ addLogMessage("removeAttachRestrictionForCarrier");
+ logd("removeAttachRestrictionForCarrier");
+
+ if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ addLogMessage("removeAttachRestrictionForCarrier: Subscription ID is invalid");
+ logd("removeAttachRestrictionForCarrier: Subscription ID is invalid");
+ return;
+ }
+
+ int reason = SatelliteManagerWrapper.SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER;
+
+ Consumer<Integer> callback = result -> {
+ runOnUiThread(
+ () -> addLogMessage("removeAttachRestrictionForCarrier result: " + result));
+ logd("removeAttachRestrictionForCarrier result: " + result);
+ };
+
+ try {
+ mSatelliteManagerWrapper.removeAttachRestrictionForCarrier(mSubId, reason, mExecutor,
+ callback);
+ } catch (SecurityException | IllegalArgumentException ex) {
+ String errorMessage = "removeAttachRestrictionForCarrier: " + ex.getMessage();
+ logd(errorMessage);
+ addLogMessage(errorMessage);
+ }
+ }
+
+ private void getAttachRestrictionReasonsForCarrier(View view) {
+ addLogMessage("getAttachRestrictionReasonsForCarrier");
+ logd("getAttachRestrictionReasonsForCarrier");
+
+ if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ addLogMessage("getAttachRestrictionReasonsForCarrier: Subscription ID is invalid");
+ logd("getAttachRestrictionReasonsForCarrier: Subscription ID is invalid");
+ return;
+ }
+
+ try {
+ Set<Integer> reasons = mSatelliteManagerWrapper.getAttachRestrictionReasonsForCarrier(
+ mSubId);
+ String stringReasons = reasons.stream().map(Object::toString).collect(
+ Collectors.joining(", "));
+ logd("getAttachRestrictionReasonsForCarrier=" + stringReasons);
+ addLogMessage("getAttachRestrictionReasonsForCarrier=" + stringReasons);
+ } catch (SecurityException | IllegalArgumentException ex) {
+ String errorMessage = "getAttachRestrictionReasonsForCarrier: " + ex.getMessage();
+ logd(errorMessage);
+ addLogMessage(errorMessage);
+ }
+ }
+
+ private void getSatellitePlmnsForCarrier(View view) {
+ addLogMessage("getSatellitePlmnsForCarrier");
+ logd("getSatellitePlmnsForCarrier");
+
+ if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ addLogMessage("getSatellitePlmnsForCarrier: Subscription ID is invalid");
+ logd("getSatellitePlmnsForCarrier: Subscription ID is invalid");
+ return;
+ }
+
+ try {
+ List<String> reasons = mSatelliteManagerWrapper.getSatellitePlmnsForCarrier(
+ mSubId);
+ String stringReasons = reasons.stream().collect(Collectors.joining(", "));
+ logd("getSatellitePlmnsForCarrier=" + stringReasons);
+ addLogMessage("getSatellitePlmnsForCarrier=" + stringReasons);
+ } catch (SecurityException | IllegalArgumentException ex) {
+ String errorMessage = "getSatellitePlmnsForCarrier: " + ex.getMessage();
+ logd(errorMessage);
+ addLogMessage(errorMessage);
+ }
+ }
+
+ private int getActiveSubId() {
+ int subId;
+ List<SubscriptionInfo> subscriptionInfoList =
+ mSubscriptionManager.getActiveSubscriptionInfoList();
+
+ if (subscriptionInfoList != null && subscriptionInfoList.size() > 0) {
+ subId = subscriptionInfoList.get(0).getSubscriptionId();
+ } else {
+ subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
+ logd("getActiveSubId() returns " + subId);
+ return subId;
+ }
+
+ private String translateResultCodeToString(
+ @SatelliteManagerWrapper.SatelliteResult int result) {
+ switch (result) {
+ case SatelliteManagerWrapper.SATELLITE_RESULT_SUCCESS:
+ return "SATELLITE_RESULT_SUCCESS";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_ERROR:
+ return "SATELLITE_RESULT_ERROR";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_SERVER_ERROR:
+ return "SATELLITE_RESULT_SERVER_ERROR";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_SERVICE_ERROR:
+ return "SATELLITE_RESULT_SERVICE_ERROR";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_MODEM_ERROR:
+ return "SATELLITE_RESULT_MODEM_ERROR";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_NETWORK_ERROR:
+ return "SATELLITE_RESULT_NETWORK_ERROR";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_INVALID_TELEPHONY_STATE:
+ return "SATELLITE_RESULT_INVALID_TELEPHONY_STATE";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_INVALID_MODEM_STATE:
+ return "SATELLITE_RESULT_INVALID_MODEM_STATE";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_INVALID_ARGUMENTS:
+ return "SATELLITE_RESULT_INVALID_ARGUMENTS";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_REQUEST_FAILED:
+ return "SATELLITE_RESULT_REQUEST_FAILED";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_RADIO_NOT_AVAILABLE:
+ return "SATELLITE_RESULT_RADIO_NOT_AVAILABLE";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED:
+ return "SATELLITE_RESULT_REQUEST_NOT_SUPPORTED";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_NO_RESOURCES:
+ return "SATELLITE_RESULT_NO_RESOURCES";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_SERVICE_NOT_PROVISIONED:
+ return "SATELLITE_RESULT_SERVICE_NOT_PROVISIONED";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS:
+ return "SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_REQUEST_ABORTED:
+ return "SATELLITE_RESULT_REQUEST_ABORTED";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_ACCESS_BARRED:
+ return "SATELLITE_RESULT_ACCESS_BARRED";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_NETWORK_TIMEOUT:
+ return "SATELLITE_RESULT_NETWORK_TIMEOUT";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_NOT_REACHABLE:
+ return "SATELLITE_RESULT_NOT_REACHABLE";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_NOT_AUTHORIZED:
+ return "SATELLITE_RESULT_NOT_AUTHORIZED";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_NOT_SUPPORTED:
+ return "SATELLITE_RESULT_NOT_SUPPORTED";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_REQUEST_IN_PROGRESS:
+ return "SATELLITE_RESULT_REQUEST_IN_PROGRESS";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_MODEM_BUSY:
+ return "SATELLITE_RESULT_MODEM_BUSY";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_ILLEGAL_STATE:
+ return "SATELLITE_RESULT_ILLEGAL_STATE";
+ default:
+ return "INVALID CODE: " + result;
+ }
+ }
+
+ private void addLogMessage(String message) {
+ mLogMessages.add(message);
+ mAdapter.notifyDataSetChanged();
+ mLogListView.setSelection(mAdapter.getCount() - 1);
+ }
+
+ private static void logd(String message) {
+ if (message != null) {
+ Log.d(TAG, message);
+ }
+ }
+}
diff --git a/tests/Android.bp b/tests/Android.bp
index 08cac05..6914839 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -38,6 +38,7 @@
instrumentation_for: "TeleService",
static_libs: [
+ "frameworks-base-testutils",
"androidx.test.core",
"androidx.test.espresso.core",
"androidx.test.ext.junit",
@@ -45,9 +46,14 @@
"mockito-target-minus-junit4",
"telephony-common-testing",
"testng",
- "truth-prebuilt",
+ "truth",
"testables",
"platform-compat-test-rules",
+ "flag-junit",
+ "telephony_flags_core_java_lib",
+ "satellite-s2storage-rw",
+ "satellite-s2storage-testutils",
+ "s2-geometry-library-java",
],
test_suites: [
diff --git a/tests/src/com/android/TestContext.java b/tests/src/com/android/TestContext.java
index 111df53..a96ce2e 100644
--- a/tests/src/com/android/TestContext.java
+++ b/tests/src/com/android/TestContext.java
@@ -19,6 +19,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
@@ -61,6 +62,7 @@
@Mock SubscriptionManager mMockSubscriptionManager;
@Mock ImsManager mMockImsManager;
@Mock UserManager mMockUserManager;
+ @Mock PackageManager mPackageManager;
private final SparseArray<PersistableBundle> mCarrierConfigs = new SparseArray<>();
@@ -80,6 +82,7 @@
int subId = (int) invocation.getArguments()[0];
return getTestConfigs(subId);
}).when(mMockCarrierConfigManager).getConfigForSubId(anyInt(), anyString());
+ when(mPackageManager.hasSystemFeature(anyString())).thenReturn(true);
}
@Override
@@ -145,6 +148,11 @@
}
@Override
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ @Override
public ContentResolver getContentResolver() {
return null;
}
diff --git a/tests/src/com/android/phone/CarrierConfigLoaderTest.java b/tests/src/com/android/phone/CarrierConfigLoaderTest.java
index b6f8ed8..f4197d9 100644
--- a/tests/src/com/android/phone/CarrierConfigLoaderTest.java
+++ b/tests/src/com/android/phone/CarrierConfigLoaderTest.java
@@ -21,6 +21,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -30,6 +31,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.compat.testing.PlatformCompatChangeRule;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
@@ -40,8 +42,10 @@
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.PermissionEnforcer;
import android.os.PersistableBundle;
import android.os.UserHandle;
+import android.os.test.FakePermissionEnforcer;
import android.service.carrier.CarrierIdentifier;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
@@ -54,12 +58,17 @@
import com.android.TelephonyTestBase;
import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.subscription.SubscriptionManagerService;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
@@ -74,10 +83,14 @@
*/
@RunWith(AndroidJUnit4.class)
public class CarrierConfigLoaderTest extends TelephonyTestBase {
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+ private static final String TAG = CarrierConfigLoaderTest.class.getSimpleName();
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 String PLATFORM_CARRIER_CONFIG_FEATURE = "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;
@@ -90,6 +103,7 @@
@Mock SubscriptionManagerService mSubscriptionManagerService;
@Mock SharedPreferences mSharedPreferences;
@Mock TelephonyRegistryManager mTelephonyRegistryManager;
+ @Mock FeatureFlags mFeatureFlags;
private TelephonyManager mTelephonyManager;
private CarrierConfigLoader mCarrierConfigLoader;
@@ -97,10 +111,17 @@
private HandlerThread mHandlerThread;
private TestableLooper mTestableLooper;
+ // The AIDL stub will use PermissionEnforcer to check permission from the caller.
+ private FakePermissionEnforcer mFakePermissionEnforcer = new FakePermissionEnforcer();
+
@Before
public void setUp() throws Exception {
super.setUp();
MockitoAnnotations.initMocks(this);
+ doReturn(Context.PERMISSION_ENFORCER_SERVICE).when(mContext).getSystemServiceName(
+ eq(PermissionEnforcer.class));
+ doReturn(mFakePermissionEnforcer).when(mContext).getSystemService(
+ eq(Context.PERMISSION_ENFORCER_SERVICE));
replaceInstance(SubscriptionManagerService.class, "sInstance", null,
mSubscriptionManagerService);
@@ -109,6 +130,8 @@
doReturn(Build.FINGERPRINT).when(mSharedPreferences).getString(eq("build_fingerprint"),
any());
doReturn(mPackageManager).when(mContext).getPackageManager();
+ doReturn(new String[]{TAG}).when(mPackageManager).getPackagesForUid(anyInt());
+
doReturn(mResources).when(mContext).getResources();
doReturn(InstrumentationRegistry.getTargetContext().getFilesDir()).when(
mContext).getFilesDir();
@@ -132,7 +155,8 @@
mHandlerThread.start();
mTestableLooper = new TestableLooper(mHandlerThread.getLooper());
- mCarrierConfigLoader = new CarrierConfigLoader(mContext, mTestableLooper.getLooper());
+ mCarrierConfigLoader = new CarrierConfigLoader(mContext, mTestableLooper.getLooper(),
+ mFeatureFlags);
mHandler = mCarrierConfigLoader.getHandler();
// Clear all configs to have the same starting point.
@@ -142,6 +166,9 @@
@After
public void tearDown() throws Exception {
mContext.revokeAllPermissions();
+ mFakePermissionEnforcer.revoke(android.Manifest.permission.DUMP);
+ mFakePermissionEnforcer.revoke(android.Manifest.permission.MODIFY_PHONE_STATE);
+ mFakePermissionEnforcer.revoke(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
mTestableLooper.destroy();
mHandlerThread.quit();
super.tearDown();
@@ -164,7 +191,7 @@
*/
@Test
public void testUpdateConfigForPhoneId_invalidPhoneId() throws Exception {
- mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ mFakePermissionEnforcer.grant(android.Manifest.permission.MODIFY_PHONE_STATE);
assertThrows(IllegalArgumentException.class,
() -> mCarrierConfigLoader.updateConfigForPhoneId(
@@ -182,7 +209,7 @@
if (!SubscriptionManager.isValidPhoneId(SubscriptionManager.getPhoneId(DEFAULT_SUB_ID))) {
return;
}
- mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ mFakePermissionEnforcer.grant(android.Manifest.permission.MODIFY_PHONE_STATE);
doNothing().when(mContext).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class));
// Prepare a cached config to fetch from xml
@@ -215,7 +242,7 @@
if (!SubscriptionManager.isValidPhoneId(SubscriptionManager.getPhoneId(DEFAULT_SUB_ID))) {
return;
}
- mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ mFakePermissionEnforcer.grant(android.Manifest.permission.MODIFY_PHONE_STATE);
// Prepare to make sure we can save the config into the XML file which used as cache
doReturn(PLATFORM_CARRIER_CONFIG_PACKAGE).when(mTelephonyManager)
@@ -252,7 +279,7 @@
*/
@Test
public void testOverrideConfig_invalidSubId() throws Exception {
- mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ mFakePermissionEnforcer.grant(android.Manifest.permission.MODIFY_PHONE_STATE);
assertThrows(IllegalArgumentException.class, () -> mCarrierConfigLoader.overrideConfig(
SubscriptionManager.INVALID_SUBSCRIPTION_ID, new PersistableBundle(), false));
@@ -267,7 +294,7 @@
if (!SubscriptionManager.isValidPhoneId(SubscriptionManager.getPhoneId(DEFAULT_SUB_ID))) {
return;
}
- mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ mFakePermissionEnforcer.grant(android.Manifest.permission.MODIFY_PHONE_STATE);
mCarrierConfigLoader.overrideConfig(DEFAULT_SUB_ID, null /*overrides*/,
false/*persistent*/);
@@ -288,7 +315,7 @@
if (!SubscriptionManager.isValidPhoneId(SubscriptionManager.getPhoneId(DEFAULT_SUB_ID))) {
return;
}
- mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ mFakePermissionEnforcer.grant(android.Manifest.permission.MODIFY_PHONE_STATE);
PersistableBundle config = getTestConfig();
mCarrierConfigLoader.overrideConfig(DEFAULT_SUB_ID, config /*overrides*/,
@@ -308,7 +335,7 @@
*/
@Test
public void testNotifyConfigChangedForSubId_invalidSubId() throws Exception {
- mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ mFakePermissionEnforcer.grant(STUB_PERMISSION_ENABLE_ALL);
assertThrows(IllegalArgumentException.class,
() -> mCarrierConfigLoader.notifyConfigChangedForSubId(
@@ -346,7 +373,7 @@
*/
@Test
public void testGetDefaultCarrierServicePackageName_withPermission() {
- mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ mFakePermissionEnforcer.grant(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
assertThat(mCarrierConfigLoader.getDefaultCarrierServicePackageName())
.isEqualTo(PLATFORM_CARRIER_CONFIG_PACKAGE);
@@ -400,6 +427,34 @@
assertThat(dumpContent).doesNotContain("Permission Denial:");
}
+ @Test
+ @EnableCompatChanges({TelephonyManager.ENABLE_FEATURE_MAPPING})
+ public void testGetConfigForSubIdWithFeature_withTelephonyFeatureMapping() {
+ doNothing().when(mContext).enforcePermission(
+ eq(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE),
+ anyInt(), anyInt(), anyString());
+
+ doReturn(true).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
+ doReturn(false).when(mPackageManager).hasSystemFeature(
+ eq(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
+ // Not defined required feature, expect UnsupportedOperationException
+ assertThrows(UnsupportedOperationException.class,
+ () -> mCarrierConfigLoader.getConfigForSubIdWithFeature(DEFAULT_SUB_ID,
+ PLATFORM_CARRIER_CONFIG_PACKAGE, PLATFORM_CARRIER_CONFIG_FEATURE));
+
+ doReturn(true).when(mPackageManager).hasSystemFeature(
+ eq(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
+ // Defined required feature, not expect UnsupportedOperationException
+ try {
+ mCarrierConfigLoader.getConfigForSubIdWithFeature(DEFAULT_SUB_ID,
+ PLATFORM_CARRIER_CONFIG_PACKAGE, PLATFORM_CARRIER_CONFIG_FEATURE);
+ } catch (UnsupportedOperationException e) {
+ fail("not expected UnsupportedOperationException");
+ }
+ }
+
private static PersistableBundle getTestConfig() {
PersistableBundle config = new PersistableBundle();
config.putInt(CARRIER_CONFIG_EXAMPLE_KEY, CARRIER_CONFIG_EXAMPLE_VALUE);
@@ -417,7 +472,7 @@
@Test
public void testMultiSimConfigChanged() throws Exception {
replaceInstance(TelephonyManager.class, "sInstance", null, mTelephonyManager);
- mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ mFakePermissionEnforcer.grant(android.Manifest.permission.MODIFY_PHONE_STATE);
// Changed from 1 to 2.
doReturn(2).when(mTelephonyManager).getActiveModemCount();
diff --git a/tests/src/com/android/phone/DiagnosticDataCollectorTest.java b/tests/src/com/android/phone/DiagnosticDataCollectorTest.java
index e0d89bc..983d135 100644
--- a/tests/src/com/android/phone/DiagnosticDataCollectorTest.java
+++ b/tests/src/com/android/phone/DiagnosticDataCollectorTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.verify;
import android.os.DropBoxManager;
+import android.os.SystemClock;
import android.telephony.TelephonyManager;
import org.junit.After;
@@ -94,30 +95,37 @@
@Test
public void testPersistForTelecomDumpsys() throws IOException, InterruptedException {
- TelephonyManager.EmergencyCallDiagnosticParams dp =
- new TelephonyManager.EmergencyCallDiagnosticParams();
- dp.setTelecomDumpSysCollection(true);
- mDiagnosticDataCollector.persistEmergencyDianosticData(mConfig, dp, "test_tag_telecom");
+ TelephonyManager.EmergencyCallDiagnosticData.Builder callDiagnosticBuilder =
+ new TelephonyManager.EmergencyCallDiagnosticData.Builder();
+ TelephonyManager.EmergencyCallDiagnosticData ecdData =
+ callDiagnosticBuilder.setTelecomDumpsysCollectionEnabled(true).build();
+ mDiagnosticDataCollector.persistEmergencyDianosticData(
+ mConfig, ecdData, "test_tag_telecom");
verifyCmdAndDropboxTag(TELECOM_DUMPSYS_COMMAND, "test_tag_telecom", false);
}
@Test
public void testPersistForTelephonyDumpsys() throws IOException, InterruptedException {
- TelephonyManager.EmergencyCallDiagnosticParams dp =
- new TelephonyManager.EmergencyCallDiagnosticParams();
- dp.setTelephonyDumpSysCollection(true);
- mDiagnosticDataCollector.persistEmergencyDianosticData(mConfig, dp, "test_tag_telephony");
+ TelephonyManager.EmergencyCallDiagnosticData.Builder callDiagnosticBuilder =
+ new TelephonyManager.EmergencyCallDiagnosticData.Builder();
+ TelephonyManager.EmergencyCallDiagnosticData ecdData =
+ callDiagnosticBuilder.setTelephonyDumpsysCollectionEnabled(true).build();
+ mDiagnosticDataCollector.persistEmergencyDianosticData(
+ mConfig, ecdData, "test_tag_telephony");
verifyCmdAndDropboxTag(TELEPHONY_DUMPSYS_COMMAND, "test_tag_telephony", false);
}
@Test
public void testPersistForLogcat() throws IOException, InterruptedException {
- TelephonyManager.EmergencyCallDiagnosticParams dp =
- new TelephonyManager.EmergencyCallDiagnosticParams();
- dp.setLogcatCollection(true, System.currentTimeMillis());
- mDiagnosticDataCollector.persistEmergencyDianosticData(mConfig, dp, "test_tag_logcat");
+ TelephonyManager.EmergencyCallDiagnosticData.Builder callDiagnosticBuilder =
+ new TelephonyManager.EmergencyCallDiagnosticData.Builder();
+ TelephonyManager.EmergencyCallDiagnosticData ecdData =
+ callDiagnosticBuilder.setLogcatCollectionStartTimeMillis(
+ SystemClock.elapsedRealtime()).build();
+ mDiagnosticDataCollector.persistEmergencyDianosticData(
+ mConfig, ecdData, "test_tag_logcat");
verifyCmdAndDropboxTag(LOGCAT_BINARY, "test_tag_logcat", true);
}
diff --git a/tests/src/com/android/phone/ImsProvisioningControllerTest.java b/tests/src/com/android/phone/ImsProvisioningControllerTest.java
index db83cca..e12be53 100644
--- a/tests/src/com/android/phone/ImsProvisioningControllerTest.java
+++ b/tests/src/com/android/phone/ImsProvisioningControllerTest.java
@@ -68,10 +68,11 @@
import android.telephony.ims.feature.RcsFeature.RcsImsCapabilities;
import android.telephony.ims.stub.ImsConfigImplBase;
import android.telephony.ims.stub.ImsRegistrationImplBase;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableLooper;
import android.util.Log;
+import androidx.test.filters.SmallTest;
+
import com.android.ims.FeatureConnector;
import com.android.ims.ImsConfig;
import com.android.ims.ImsManager;
diff --git a/tests/src/com/android/phone/ImsProvisioningLoaderTest.java b/tests/src/com/android/phone/ImsProvisioningLoaderTest.java
index 61cab1d..207e454 100644
--- a/tests/src/com/android/phone/ImsProvisioningLoaderTest.java
+++ b/tests/src/com/android/phone/ImsProvisioningLoaderTest.java
@@ -28,10 +28,10 @@
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.feature.RcsFeature;
import android.telephony.ims.stub.ImsRegistrationImplBase;
-import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
import org.junit.After;
import org.junit.Before;
diff --git a/tests/src/com/android/phone/ImsStateCallbackControllerTest.java b/tests/src/com/android/phone/ImsStateCallbackControllerTest.java
index 2bd87be..c86502b 100644
--- a/tests/src/com/android/phone/ImsStateCallbackControllerTest.java
+++ b/tests/src/com/android/phone/ImsStateCallbackControllerTest.java
@@ -48,10 +48,11 @@
import android.os.Looper;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyRegistryManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableLooper;
import android.util.Log;
+import androidx.test.filters.SmallTest;
+
import com.android.TelephonyTestBase;
import com.android.ims.FeatureConnector;
import com.android.ims.ImsManager;
diff --git a/tests/src/com/android/phone/NotificationMgrTest.java b/tests/src/com/android/phone/NotificationMgrTest.java
index e009446..98c6a4a 100644
--- a/tests/src/com/android/phone/NotificationMgrTest.java
+++ b/tests/src/com/android/phone/NotificationMgrTest.java
@@ -631,6 +631,7 @@
MOBILE_NETWORK_SELECTION_PACKAGE);
when(mApp.getString(R.string.mobile_network_settings_class)).thenReturn(
MOBILE_NETWORK_SELECTION_CLASS);
+ when(mSubscriptionManager.isActiveSubId(anyInt())).thenReturn(true);
}
private void moveTimeForward(long seconds) {
diff --git a/tests/src/com/android/phone/PhoneInterfaceManagerTest.java b/tests/src/com/android/phone/PhoneInterfaceManagerTest.java
index e702279..bd47b94 100644
--- a/tests/src/com/android/phone/PhoneInterfaceManagerTest.java
+++ b/tests/src/com/android/phone/PhoneInterfaceManagerTest.java
@@ -20,8 +20,11 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
@@ -31,9 +34,14 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.AppOpsManager;
+import android.compat.testing.PlatformCompatChangeRule;
import android.content.Context;
import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.permission.flags.Flags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.telephony.RadioAccessFamily;
import android.telephony.TelephonyManager;
@@ -44,13 +52,22 @@
import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.subscription.SubscriptionManagerService;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.Locale;
/**
@@ -58,33 +75,58 @@
*/
@RunWith(AndroidJUnit4.class)
public class PhoneInterfaceManagerTest extends TelephonyTestBase {
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
private PhoneInterfaceManager mPhoneInterfaceManager;
private SharedPreferences mSharedPreferences;
private IIntegerConsumer mIIntegerConsumer;
+ private static final String sDebugPackageName =
+ PhoneInterfaceManagerTest.class.getPackageName();
@Mock
PhoneGlobals mPhoneGlobals;
@Mock
Phone mPhone;
-
+ @Mock
+ FeatureFlags mFeatureFlags;
+ @Mock
+ PackageManager mPackageManager;
@Mock
private SubscriptionManagerService mSubscriptionManagerService;
+ @Mock
+ private AppOpsManager mAppOps;
+
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
@UiThreadTest
public void setUp() throws Exception {
super.setUp();
+ doReturn(sDebugPackageName).when(mPhoneGlobals).getOpPackageName();
+
// Note that PhoneInterfaceManager is a singleton. Calling init gives us a handle to the
// global singleton, but the context that is passed in is unused if the phone app is already
// alive on a test devices. You must use the spy to mock behavior. Mocks stemming from the
// passed context will remain unused.
- mPhoneInterfaceManager = spy(PhoneInterfaceManager.init(mPhoneGlobals));
+ mPhoneInterfaceManager = spy(PhoneInterfaceManager.init(mPhoneGlobals, mFeatureFlags));
doReturn(mSubscriptionManagerService).when(mPhoneInterfaceManager)
.getSubscriptionManagerService();
TelephonyManager.setupISubForTest(mSubscriptionManagerService);
mSharedPreferences = mPhoneInterfaceManager.getSharedPreferences();
mSharedPreferences.edit().remove(Phone.PREF_NULL_CIPHER_AND_INTEGRITY_ENABLED).commit();
+ mSharedPreferences.edit().remove(Phone.PREF_NULL_CIPHER_NOTIFICATIONS_ENABLED).commit();
mIIntegerConsumer = mock(IIntegerConsumer.class);
+
+ // In order not to affect the existing implementation, define a telephony features
+ // and disabled enforce_telephony_feature_mapping_for_public_apis feature flag
+ mPhoneInterfaceManager.setFeatureFlags(mFeatureFlags);
+ doReturn(false).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
+ mPhoneInterfaceManager.setPackageManager(mPackageManager);
+ doReturn(true).when(mPackageManager).hasSystemFeature(anyString());
+
+ mPhoneInterfaceManager.setAppOpsManager(mAppOps);
}
@Test
@@ -261,6 +303,108 @@
mPhoneInterfaceManager).getDefaultPhone();
}
+ @Test
+ public void setNullCipherNotificationsEnabled_allReqsMet_successfullyEnabled() {
+ setModemSupportsNullCipherNotification(true);
+ doNothing().when(mPhoneInterfaceManager).enforceModifyPermission();
+ doReturn(202).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+ assertFalse(mSharedPreferences.contains(Phone.PREF_NULL_CIPHER_NOTIFICATIONS_ENABLED));
+
+ mPhoneInterfaceManager.setNullCipherNotificationsEnabled(true);
+
+ assertTrue(
+ mSharedPreferences.getBoolean(Phone.PREF_NULL_CIPHER_NOTIFICATIONS_ENABLED, false));
+ }
+
+ @Test
+ public void setNullCipherNotificationsEnabled_allReqsMet_successfullyDisabled() {
+ setModemSupportsNullCipherNotification(true);
+ doNothing().when(mPhoneInterfaceManager).enforceModifyPermission();
+ doReturn(202).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+ assertFalse(mSharedPreferences.contains(Phone.PREF_NULL_CIPHER_NOTIFICATIONS_ENABLED));
+
+ mPhoneInterfaceManager.setNullCipherNotificationsEnabled(false);
+
+ assertFalse(
+ mSharedPreferences.getBoolean(Phone.PREF_NULL_CIPHER_NOTIFICATIONS_ENABLED, true));
+ }
+
+ @Test
+ public void setNullCipherNotificationsEnabled_lackingNecessaryHal_throwsException() {
+ setModemSupportsNullCipherNotification(true);
+ doNothing().when(mPhoneInterfaceManager).enforceModifyPermission();
+ doReturn(102).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+
+ assertThrows(UnsupportedOperationException.class,
+ () -> mPhoneInterfaceManager.setNullCipherNotificationsEnabled(true));
+ }
+
+ @Test
+ public void setNullCipherNotificationsEnabled_lackingModemSupport_throwsException() {
+ setModemSupportsNullCipherNotification(false);
+ doNothing().when(mPhoneInterfaceManager).enforceModifyPermission();
+ doReturn(202).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+
+ assertThrows(UnsupportedOperationException.class,
+ () -> mPhoneInterfaceManager.setNullCipherNotificationsEnabled(true));
+ }
+
+ @Test
+ public void setNullCipherNotificationsEnabled_lackingPermissions_throwsException() {
+ setModemSupportsNullCipherNotification(true);
+ doReturn(202).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+ doThrow(SecurityException.class).when(mPhoneInterfaceManager).enforceModifyPermission();
+
+ assertThrows(SecurityException.class, () ->
+ mPhoneInterfaceManager.setNullCipherNotificationsEnabled(true));
+ }
+
+ @Test
+ public void isNullCipherNotificationsEnabled_allReqsMet_returnsTrue() {
+ setModemSupportsNullCipherNotification(true);
+ doReturn(202).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+ doNothing().when(mPhoneInterfaceManager).enforceReadPrivilegedPermission(anyString());
+ doReturn(true).when(mPhone).getNullCipherNotificationsPreferenceEnabled();
+
+ assertTrue(mPhoneInterfaceManager.isNullCipherNotificationsEnabled());
+ }
+
+ @Test
+ public void isNullCipherNotificationsEnabled_lackingNecessaryHal_throwsException() {
+ setModemSupportsNullCipherNotification(true);
+ doReturn(102).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+ doNothing().when(mPhoneInterfaceManager).enforceReadPrivilegedPermission(anyString());
+
+ assertThrows(UnsupportedOperationException.class, () ->
+ mPhoneInterfaceManager.isNullCipherNotificationsEnabled());
+ }
+
+ @Test
+ public void isNullCipherNotificationsEnabled_lackingModemSupport_throwsException() {
+ setModemSupportsNullCipherNotification(false);
+ doReturn(202).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+ doNothing().when(mPhoneInterfaceManager).enforceReadPrivilegedPermission(anyString());
+
+ assertThrows(UnsupportedOperationException.class, () ->
+ mPhoneInterfaceManager.isNullCipherNotificationsEnabled());
+ }
+
+ @Test
+ public void isNullCipherNotificationsEnabled_lackingPermissions_throwsException() {
+ setModemSupportsNullCipherNotification(true);
+ doReturn(202).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+ doThrow(SecurityException.class).when(
+ mPhoneInterfaceManager).enforceReadPrivilegedPermission(anyString());
+
+ assertThrows(SecurityException.class, () ->
+ mPhoneInterfaceManager.isNullCipherNotificationsEnabled());
+ }
+
+ private void setModemSupportsNullCipherNotification(boolean enable) {
+ doReturn(enable).when(mPhone).isNullCipherNotificationSupported();
+ doReturn(mPhone).when(mPhoneInterfaceManager).getDefaultPhone();
+ }
+
/**
* Verify getCarrierRestrictionStatus throws exception for invalid caller package name.
*/
@@ -279,8 +423,111 @@
*/
@Test
public void getCarrierRestrictionStatus() {
- when(mPhoneInterfaceManager.validateCallerAndGetCarrierId(anyString())).thenReturn(1);
+ when(mPhoneInterfaceManager.validateCallerAndGetCarrierIds(anyString())).thenReturn(
+ Collections.singleton(1));
mPhoneInterfaceManager.getCarrierRestrictionStatus(mIIntegerConsumer,
"com.test.package");
}
+
+ @Test
+ public void notifyEnableDataWithAppOps_enableByUser_doNoteOp() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_OP_ENABLE_MOBILE_DATA_BY_USER);
+ String packageName = "INVALID_PACKAGE";
+ mPhoneInterfaceManager.setDataEnabledForReason(1,
+ TelephonyManager.DATA_ENABLED_REASON_USER, true, packageName);
+ verify(mAppOps).noteOpNoThrow(eq(AppOpsManager.OPSTR_ENABLE_MOBILE_DATA_BY_USER), anyInt(),
+ eq(packageName), isNull(), isNull());
+ }
+
+ @Test
+ public void notifyEnableDataWithAppOps_enableByCarrier_doNotNoteOp() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_OP_ENABLE_MOBILE_DATA_BY_USER);
+ String packageName = "INVALID_PACKAGE";
+ verify(mAppOps, never()).noteOpNoThrow(eq(AppOpsManager.OPSTR_ENABLE_MOBILE_DATA_BY_USER),
+ anyInt(), eq(packageName), isNull(), isNull());
+ }
+
+ @Test
+ public void notifyEnableDataWithAppOps_disableByUser_doNotNoteOp() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_OP_ENABLE_MOBILE_DATA_BY_USER);
+ String packageName = "INVALID_PACKAGE";
+ String error = "";
+ try {
+ mPhoneInterfaceManager.setDataEnabledForReason(1,
+ TelephonyManager.DATA_ENABLED_REASON_USER, false, packageName);
+ } catch (SecurityException expected) {
+ // The test doesn't have access to note the op, but we're just interested that it makes
+ // the attempt.
+ error = expected.getMessage();
+ }
+ assertEquals("Expected error to be empty, was " + error, error, "");
+ }
+
+ @Test
+ public void notifyEnableDataWithAppOps_noPackageNameAndEnableByUser_doNotnoteOp() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_OP_ENABLE_MOBILE_DATA_BY_USER);
+ String error = "";
+ try {
+ mPhoneInterfaceManager.setDataEnabledForReason(1,
+ TelephonyManager.DATA_ENABLED_REASON_USER, false, null);
+ } catch (SecurityException expected) {
+ // The test doesn't have access to note the op, but we're just interested that it makes
+ // the attempt.
+ error = expected.getMessage();
+ }
+ assertEquals("Expected error to be empty, was " + error, error, "");
+ }
+
+ @Test
+ @EnableCompatChanges({TelephonyManager.ENABLE_FEATURE_MAPPING})
+ public void testWithTelephonyFeatureAndCompatChanges() throws Exception {
+ doReturn(true).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
+ mPhoneInterfaceManager.setFeatureFlags(mFeatureFlags);
+ doNothing().when(mPhoneInterfaceManager).enforceModifyPermission();
+
+ try {
+ // FEATURE_TELEPHONY_CALLING
+ mPhoneInterfaceManager.handlePinMmiForSubscriber(1, "123456789");
+
+ // FEATURE_TELEPHONY_RADIO_ACCESS
+ mPhoneInterfaceManager.toggleRadioOnOffForSubscriber(1);
+ } catch (Exception e) {
+ fail("Not expect exception " + e.getMessage());
+ }
+ }
+
+ @Test
+ @EnableCompatChanges({TelephonyManager.ENABLE_FEATURE_MAPPING})
+ public void testWithoutTelephonyFeatureAndCompatChanges() throws Exception {
+ // telephony features is not defined, expect UnsupportedOperationException.
+ doReturn(false).when(mPackageManager).hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_CALLING);
+ doReturn(false).when(mPackageManager).hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS);
+ mPhoneInterfaceManager.setPackageManager(mPackageManager);
+ doReturn(true).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
+ mPhoneInterfaceManager.setFeatureFlags(mFeatureFlags);
+ doNothing().when(mPhoneInterfaceManager).enforceModifyPermission();
+
+ assertThrows(UnsupportedOperationException.class,
+ () -> mPhoneInterfaceManager.handlePinMmiForSubscriber(1, "123456789"));
+ assertThrows(UnsupportedOperationException.class,
+ () -> mPhoneInterfaceManager.toggleRadioOnOffForSubscriber(1));
+ }
+
+ @Test
+ public void testGetCurrentPackageNameWithNoKnownPackage() throws Exception {
+ Field field = PhoneInterfaceManager.class.getDeclaredField("mApp");
+ field.setAccessible(true);
+ Field modifiersField = Field.class.getDeclaredField("accessFlags");
+ modifiersField.setAccessible(true);
+ modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+ field.set(mPhoneInterfaceManager, mPhoneGlobals);
+
+ doReturn(mPackageManager).when(mPhoneGlobals).getPackageManager();
+ doReturn(null).when(mPackageManager).getPackagesForUid(anyInt());
+
+ String packageName = mPhoneInterfaceManager.getCurrentPackageName();
+ assertEquals(null, packageName);
+ }
}
diff --git a/tests/src/com/android/phone/RcsProvisioningMonitorTest.java b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
index 57f9f6b..fe13d56 100644
--- a/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
+++ b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
@@ -61,10 +61,11 @@
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 androidx.test.filters.SmallTest;
+
import com.android.ims.FeatureConnector;
import com.android.ims.RcsFeatureManager;
import com.android.internal.telephony.ITelephony;
diff --git a/tests/src/com/android/phone/ServiceStateProviderTest.java b/tests/src/com/android/phone/ServiceStateProviderTest.java
index 4bbde79..ab26e94 100644
--- a/tests/src/com/android/phone/ServiceStateProviderTest.java
+++ b/tests/src/com/android/phone/ServiceStateProviderTest.java
@@ -64,9 +64,9 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.test.mock.MockContentResolver;
-import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
import libcore.junit.util.compat.CoreCompatChangeRule;
@@ -123,6 +123,11 @@
public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
throw new TestNotifierException();
}
+ @Override
+ public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork,
+ int userHandle) {
+ throw new TestNotifierException();
+ }
};
doReturn(mContentResolver).when(mContext).getContentResolver();
diff --git a/tests/src/com/android/phone/satellite/accesscontrol/S2RangeSatelliteOnDeviceAccessControllerTest.java b/tests/src/com/android/phone/satellite/accesscontrol/S2RangeSatelliteOnDeviceAccessControllerTest.java
new file mode 100644
index 0000000..16a256d
--- /dev/null
+++ b/tests/src/com/android/phone/satellite/accesscontrol/S2RangeSatelliteOnDeviceAccessControllerTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellite.accesscontrol;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.storage.s2.S2LevelRange;
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+import com.android.telephony.sats2range.utils.TestUtils;
+import com.android.telephony.sats2range.write.SatS2RangeFileWriter;
+
+import com.google.common.geometry.S2CellId;
+import com.google.common.geometry.S2LatLng;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class S2RangeSatelliteOnDeviceAccessControllerTest {
+ private File mFile;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mFile = File.createTempFile("test", ".data");
+ assertTrue(mFile.exists());
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ if (mFile != null && mFile.exists()) {
+ assertTrue(mFile.delete());
+ }
+ }
+
+ @Test
+ public void testSatelliteAccessControl_AllowedList() throws Exception {
+ testSatelliteAccessControl(true);
+ }
+
+ @Test
+ public void testSatelliteAccessControl_DisallowedList() throws Exception {
+ testSatelliteAccessControl(false);
+ }
+
+ private void testSatelliteAccessControl(boolean isAllowedList) throws Exception {
+ SatS2RangeFileFormat fileFormat = null;
+ try {
+ fileFormat = createSatS2File(mFile, isAllowedList);
+ } catch (Exception ex) {
+ fail("Got unexpected exception in createSatS2File, ex=" + ex);
+ }
+
+ // Validate the output block file
+ SatelliteOnDeviceAccessController accessController = null;
+ try {
+ accessController = SatelliteOnDeviceAccessController.create(mFile);
+ int s2Level = accessController.getS2Level();
+
+ // Verify an edge cell of range 1 not in the output file
+ S2CellId s2CellId = new S2CellId(TestUtils.createCellId(fileFormat, 1, 1000, 999));
+ S2LatLng s2LatLng = s2CellId.toLatLng();
+ SatelliteOnDeviceAccessController.LocationToken locationToken =
+ SatelliteOnDeviceAccessController.createLocationTokenForLatLng(
+ s2LatLng.latDegrees(), s2LatLng.lngDegrees(), s2Level);
+ boolean isAllowed = accessController.isSatCommunicationAllowedAtLocation(locationToken);
+ assertTrue(isAllowed != isAllowedList);
+
+ // Verify cells in range1 present in the output file
+ for (int suffix = 1000; suffix < 2000; suffix++) {
+ s2CellId = new S2CellId(TestUtils.createCellId(fileFormat, 1, 1000, suffix));
+ s2LatLng = s2CellId.toLatLng();
+
+ // Lookup using location token
+ locationToken = SatelliteOnDeviceAccessController.createLocationTokenForLatLng(
+ s2LatLng.latDegrees(), s2LatLng.lngDegrees(), s2Level);
+ isAllowed = accessController.isSatCommunicationAllowedAtLocation(locationToken);
+ assertTrue(isAllowed == isAllowedList);
+ }
+
+ // Verify the middle cell not in the output file
+ s2CellId = new S2CellId(TestUtils.createCellId(fileFormat, 1, 1000, 2000));
+ s2LatLng = s2CellId.toLatLng();
+ locationToken = SatelliteOnDeviceAccessController.createLocationTokenForLatLng(
+ s2LatLng.latDegrees(), s2LatLng.lngDegrees(), s2Level);
+ isAllowed = accessController.isSatCommunicationAllowedAtLocation(locationToken);
+ assertTrue(isAllowed != isAllowedList);
+
+ // Verify cells in range2 present in the output file
+ for (int suffix = 2001; suffix < 3000; suffix++) {
+ s2CellId = new S2CellId(TestUtils.createCellId(fileFormat, 1, 1000, suffix));
+ s2LatLng = s2CellId.toLatLng();
+ locationToken = SatelliteOnDeviceAccessController.createLocationTokenForLatLng(
+ s2LatLng.latDegrees(), s2LatLng.lngDegrees(), s2Level);
+ isAllowed = accessController.isSatCommunicationAllowedAtLocation(locationToken);
+ assertTrue(isAllowed == isAllowedList);
+ }
+
+ // Verify an edge cell of range 2 not in the output file
+ s2CellId = new S2CellId(TestUtils.createCellId(fileFormat, 1, 1000, 3000));
+ s2LatLng = s2CellId.toLatLng();
+ locationToken = SatelliteOnDeviceAccessController.createLocationTokenForLatLng(
+ s2LatLng.latDegrees(), s2LatLng.lngDegrees(), s2Level);
+ isAllowed = accessController.isSatCommunicationAllowedAtLocation(locationToken);
+ assertTrue(isAllowed != isAllowedList);
+
+ // Verify an edge cell of range 3 not in the output file
+ s2CellId = new S2CellId(TestUtils.createCellId(fileFormat, 1, 1001, 999));
+ s2LatLng = s2CellId.toLatLng();
+ locationToken = SatelliteOnDeviceAccessController.createLocationTokenForLatLng(
+ s2LatLng.latDegrees(), s2LatLng.lngDegrees(), s2Level);
+ isAllowed = accessController.isSatCommunicationAllowedAtLocation(locationToken);
+ assertTrue(isAllowed != isAllowedList);
+
+ // Verify cells in range1 present in the output file
+ for (int suffix = 1000; suffix < 2000; suffix++) {
+ s2CellId = new S2CellId(TestUtils.createCellId(fileFormat, 1, 1001, suffix));
+ s2LatLng = s2CellId.toLatLng();
+ locationToken = SatelliteOnDeviceAccessController.createLocationTokenForLatLng(
+ s2LatLng.latDegrees(), s2LatLng.lngDegrees(), s2Level);
+ isAllowed = accessController.isSatCommunicationAllowedAtLocation(locationToken);
+ assertTrue(isAllowed == isAllowedList);
+ }
+
+ // Verify an edge cell of range 3 not in the output file
+ s2CellId = new S2CellId(TestUtils.createCellId(fileFormat, 1, 1001, 2000));
+ s2LatLng = s2CellId.toLatLng();
+ locationToken = SatelliteOnDeviceAccessController.createLocationTokenForLatLng(
+ s2LatLng.latDegrees(), s2LatLng.lngDegrees(), s2Level);
+ isAllowed = accessController.isSatCommunicationAllowedAtLocation(locationToken);
+ assertTrue(isAllowed != isAllowedList);
+ } catch (Exception ex) {
+ fail("Unexpected exception when validating the output ex=" + ex);
+ } finally {
+ if (accessController != null) {
+ accessController.close();
+ }
+ }
+ }
+
+ private SatS2RangeFileFormat createSatS2File(
+ File file, boolean isAllowedList) throws Exception {
+ SatS2RangeFileFormat fileFormat;
+ S2LevelRange range1, range2, range3;
+ try (SatS2RangeFileWriter satS2RangeFileWriter = SatS2RangeFileWriter.open(
+ file, TestUtils.createS2RangeFileFormat(isAllowedList))) {
+ fileFormat = satS2RangeFileWriter.getFileFormat();
+
+ // Two ranges that share a prefix.
+ range1 = new S2LevelRange(
+ TestUtils.createCellId(fileFormat, 1, 1000, 1000),
+ TestUtils.createCellId(fileFormat, 1, 1000, 2000));
+ range2 = new S2LevelRange(
+ TestUtils.createCellId(fileFormat, 1, 1000, 2001),
+ TestUtils.createCellId(fileFormat, 1, 1000, 3000));
+ // This range has a different prefix, so will be in a different suffix table.
+ range3 = new S2LevelRange(
+ TestUtils.createCellId(fileFormat, 1, 1001, 1000),
+ TestUtils.createCellId(fileFormat, 1, 1001, 2000));
+
+ List<S2LevelRange> ranges = new ArrayList<>();
+ ranges.add(range1);
+ ranges.add(range2);
+ ranges.add(range3);
+ satS2RangeFileWriter.createSortedSuffixBlocks(ranges.iterator());
+ }
+ assertTrue(file.length() > 0);
+ return fileFormat;
+ }
+}
diff --git a/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java b/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java
new file mode 100644
index 0000000..8df0603
--- /dev/null
+++ b/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java
@@ -0,0 +1,651 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellite.accesscontrol;
+
+import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_ERROR;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
+
+import static com.android.phone.satellite.accesscontrol.SatelliteAccessController.EVENT_CONFIG_DATA_UPDATED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+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.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+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.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.location.Location;
+import android.location.LocationManager;
+import android.location.LocationRequest;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ResultReceiver;
+import android.telecom.TelecomManager;
+import android.telephony.satellite.SatelliteManager;
+import android.testing.TestableLooper;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyCountryDetector;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.satellite.SatelliteConfig;
+import com.android.internal.telephony.satellite.SatelliteConfigParser;
+import com.android.internal.telephony.satellite.SatelliteController;
+import com.android.internal.telephony.satellite.SatelliteModemInterface;
+
+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 org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/** Unit test for {@link SatelliteAccessController} */
+@RunWith(AndroidJUnit4.class)
+public class SatelliteAccessControllerTest {
+ private static final String TAG = "SatelliteAccessControllerTest";
+ private static final String[] TEST_SATELLITE_COUNTRY_CODES = {"US", "CA", "UK"};
+ private static final String TEST_SATELLITE_S2_FILE = "sat_s2_file.dat";
+ private static final boolean TEST_SATELLITE_ALLOW = true;
+ private static final int TEST_LOCATION_FRESH_DURATION_SECONDS = 10;
+ private static final long TEST_LOCATION_FRESH_DURATION_NANOS =
+ TimeUnit.SECONDS.toNanos(TEST_LOCATION_FRESH_DURATION_SECONDS);
+ private static final long TIMEOUT = 500;
+ private static final List<String> EMPTY_STRING_LIST = new ArrayList<>();
+ private static final List<String> LOCATION_PROVIDERS =
+ listOf(LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER);
+ private static final int SUB_ID = 0;
+
+ @Mock
+ private LocationManager mMockLocationManager;
+ @Mock
+ private TelecomManager mMockTelecomManager;
+ @Mock
+ private TelephonyCountryDetector mMockCountryDetector;
+ @Mock
+ private SatelliteController mMockSatelliteController;
+ @Mock
+ private SatelliteModemInterface mMockSatelliteModemInterface;
+ @Mock
+ private Context mMockContext;
+ @Mock private Phone mMockPhone;
+ @Mock private Phone mMockPhone2;
+ @Mock private FeatureFlags mMockFeatureFlags;
+ @Mock private Resources mMockResources;
+ @Mock private SatelliteOnDeviceAccessController mMockSatelliteOnDeviceAccessController;
+ @Mock Location mMockLocation0;
+ @Mock Location mMockLocation1;
+ @Mock File mMockSatS2File;
+
+ private Looper mLooper;
+ private TestableLooper mTestableLooper;
+ private Phone[] mPhones;
+ private TestSatelliteAccessController mSatelliteAccessControllerUT;
+
+ @Captor
+ private ArgumentCaptor<CancellationSignal> mLocationRequestCancellationSignalCaptor;
+ @Captor
+ private ArgumentCaptor<Consumer<Location>> mLocationRequestConsumerCaptor;
+ @Captor
+ private ArgumentCaptor<Handler> mConfigUpdateHandlerCaptor;
+ @Captor
+ private ArgumentCaptor<Integer> mConfigUpdateIntCaptor;
+ @Captor
+ private ArgumentCaptor<Object> mConfigUpdateObjectCaptor;
+
+ private boolean mQueriedSatelliteAllowed = false;
+ private int mQueriedSatelliteAllowedResultCode = SATELLITE_RESULT_SUCCESS;
+ private Semaphore mSatelliteAllowedSemaphore = new Semaphore(0);
+ private ResultReceiver mSatelliteAllowedReceiver = new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ mQueriedSatelliteAllowedResultCode = resultCode;
+ if (resultCode == SATELLITE_RESULT_SUCCESS) {
+ if (resultData.containsKey(KEY_SATELLITE_COMMUNICATION_ALLOWED)) {
+ mQueriedSatelliteAllowed = resultData.getBoolean(
+ KEY_SATELLITE_COMMUNICATION_ALLOWED);
+ } else {
+ logd("KEY_SATELLITE_COMMUNICATION_ALLOWED does not exist.");
+ mQueriedSatelliteAllowed = false;
+ }
+ } else {
+ logd("mSatelliteAllowedReceiver: resultCode=" + resultCode);
+ mQueriedSatelliteAllowed = false;
+ }
+ try {
+ mSatelliteAllowedSemaphore.release();
+ } catch (Exception ex) {
+ fail("mSatelliteAllowedReceiver: Got exception in releasing semaphore, ex=" + ex);
+ }
+ }
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ logd("setUp");
+ MockitoAnnotations.initMocks(this);
+
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ HandlerThread handlerThread = new HandlerThread("SatelliteAccessControllerTest");
+ handlerThread.start();
+ mLooper = handlerThread.getLooper();
+ mTestableLooper = new TestableLooper(mLooper);
+ when(mMockContext.getSystemServiceName(LocationManager.class)).thenReturn(
+ Context.LOCATION_SERVICE);
+ when(mMockContext.getSystemServiceName(TelecomManager.class)).thenReturn(
+ Context.TELECOM_SERVICE);
+ when(mMockContext.getSystemService(LocationManager.class)).thenReturn(
+ mMockLocationManager);
+ when(mMockContext.getSystemService(TelecomManager.class)).thenReturn(
+ mMockTelecomManager);
+ mPhones = new Phone[] {mMockPhone, mMockPhone2};
+ replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+ replaceInstance(SatelliteController.class, "sInstance", null,
+ mMockSatelliteController);
+ replaceInstance(SatelliteModemInterface.class, "sInstance", null,
+ mMockSatelliteModemInterface);
+ replaceInstance(TelephonyCountryDetector.class, "sInstance", null,
+ mMockCountryDetector);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockResources.getStringArray(
+ com.android.internal.R.array.config_oem_enabled_satellite_country_codes))
+ .thenReturn(TEST_SATELLITE_COUNTRY_CODES);
+ when(mMockResources.getBoolean(
+ com.android.internal.R.bool.config_oem_enabled_satellite_access_allow))
+ .thenReturn(TEST_SATELLITE_ALLOW);
+ when(mMockResources.getString(
+ com.android.internal.R.string.config_oem_enabled_satellite_s2cell_file))
+ .thenReturn(TEST_SATELLITE_S2_FILE);
+ when(mMockResources.getInteger(com.android.internal.R.integer
+ .config_oem_enabled_satellite_location_fresh_duration))
+ .thenReturn(TEST_LOCATION_FRESH_DURATION_SECONDS);
+
+ when(mMockLocationManager.getProviders(true)).thenReturn(LOCATION_PROVIDERS);
+ when(mMockLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER))
+ .thenReturn(mMockLocation0);
+ when(mMockLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER))
+ .thenReturn(mMockLocation1);
+ when(mMockLocation0.getLatitude()).thenReturn(0.0);
+ when(mMockLocation0.getLongitude()).thenReturn(0.0);
+ when(mMockLocation1.getLatitude()).thenReturn(1.0);
+ when(mMockLocation1.getLongitude()).thenReturn(1.0);
+ when(mMockSatelliteOnDeviceAccessController.isSatCommunicationAllowedAtLocation(
+ any(SatelliteOnDeviceAccessController.LocationToken.class))).thenReturn(true);
+
+ mSatelliteAccessControllerUT = new TestSatelliteAccessController(mMockContext,
+ mMockFeatureFlags, mLooper, mMockLocationManager, mMockTelecomManager,
+ mMockSatelliteOnDeviceAccessController, mMockSatS2File);
+ mTestableLooper.processAllMessages();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ logd("tearDown");
+ if (mTestableLooper != null) {
+ mTestableLooper.destroy();
+ mTestableLooper = null;
+ }
+
+ if (mLooper != null) {
+ mLooper.quit();
+ mLooper = null;
+ }
+ }
+
+ @Test
+ public void testGetInstance() {
+ SatelliteAccessController inst1 =
+ SatelliteAccessController.getOrCreateInstance(mMockContext, mMockFeatureFlags);
+ SatelliteAccessController inst2 =
+ SatelliteAccessController.getOrCreateInstance(mMockContext, mMockFeatureFlags);
+ assertEquals(inst1, inst2);
+ }
+
+ @Test
+ public void testRequestIsSatelliteCommunicationAllowedForCurrentLocation() throws Exception {
+ // OEM-enabled satellite is not supported
+ when(mMockFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(false);
+ mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
+ SUB_ID, mSatelliteAllowedReceiver);
+ mTestableLooper.processAllMessages();
+ assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
+ mSatelliteAllowedSemaphore, 1));
+ assertEquals(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, mQueriedSatelliteAllowedResultCode);
+
+ // OEM-enabled satellite is supported
+ when(mMockFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+
+ // Satellite is not supported
+ setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+ clearAllInvocations();
+ mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
+ SUB_ID, mSatelliteAllowedReceiver);
+ mTestableLooper.processAllMessages();
+ assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
+ mSatelliteAllowedSemaphore, 1));
+ assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteAllowedResultCode);
+ assertFalse(mQueriedSatelliteAllowed);
+
+ // Failed to query whether satellite is supported or not
+ setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_MODEM_ERROR);
+ clearAllInvocations();
+ mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
+ SUB_ID, mSatelliteAllowedReceiver);
+ mTestableLooper.processAllMessages();
+ assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
+ mSatelliteAllowedSemaphore, 1));
+ assertEquals(SATELLITE_RESULT_MODEM_ERROR, mQueriedSatelliteAllowedResultCode);
+
+ // Network country codes are available.
+ setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+ clearAllInvocations();
+ when(mMockCountryDetector.getCurrentNetworkCountryIso()).thenReturn(listOf("US", "CA"));
+ mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
+ SUB_ID, mSatelliteAllowedReceiver);
+ mTestableLooper.processAllMessages();
+ assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
+ mSatelliteAllowedSemaphore, 1));
+ assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteAllowedResultCode);
+ assertTrue(mQueriedSatelliteAllowed);
+
+ // Network country codes are not available. TelecomManager.isInEmergencyCall() returns true.
+ // On-device access controller will be used. Last known location is available and fresh.
+ clearAllInvocations();
+ when(mMockCountryDetector.getCurrentNetworkCountryIso()).thenReturn(EMPTY_STRING_LIST);
+ when(mMockTelecomManager.isInEmergencyCall()).thenReturn(true);
+ mSatelliteAccessControllerUT.elapsedRealtimeNanos = TEST_LOCATION_FRESH_DURATION_NANOS + 1;
+ when(mMockLocation0.getElapsedRealtimeNanos()).thenReturn(2L);
+ when(mMockLocation1.getElapsedRealtimeNanos()).thenReturn(0L);
+ mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
+ SUB_ID, mSatelliteAllowedReceiver);
+ mTestableLooper.processAllMessages();
+ assertTrue(
+ mSatelliteAccessControllerUT.isKeepOnDeviceAccessControllerResourcesTimerStarted());
+ verify(mMockSatelliteOnDeviceAccessController).isSatCommunicationAllowedAtLocation(
+ any(SatelliteOnDeviceAccessController.LocationToken.class));
+ assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
+ mSatelliteAllowedSemaphore, 1));
+ assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteAllowedResultCode);
+ assertTrue(mQueriedSatelliteAllowed);
+
+ // Move time forward and verify resources are cleaned up
+ clearAllInvocations();
+ mTestableLooper.moveTimeForward(mSatelliteAccessControllerUT
+ .getKeepOnDeviceAccessControllerResourcesTimeoutMillis());
+ mTestableLooper.processAllMessages();
+ assertFalse(
+ mSatelliteAccessControllerUT.isKeepOnDeviceAccessControllerResourcesTimerStarted());
+ assertTrue(mSatelliteAccessControllerUT.isSatelliteOnDeviceAccessControllerReset());
+ verify(mMockSatelliteOnDeviceAccessController).close();
+
+ // Restore SatelliteOnDeviceAccessController for next verification
+ mSatelliteAccessControllerUT.setSatelliteOnDeviceAccessController(
+ mMockSatelliteOnDeviceAccessController);
+
+ // Network country codes are not available. TelecomManager.isInEmergencyCall() returns
+ // false. Phone0 is in ECM. On-device access controller will be used. Last known location is
+ // not fresh.
+ clearAllInvocations();
+ when(mMockCountryDetector.getCurrentNetworkCountryIso()).thenReturn(EMPTY_STRING_LIST);
+ when(mMockTelecomManager.isInEmergencyCall()).thenReturn(false);
+ when(mMockPhone.isInEcm()).thenReturn(true);
+ mSatelliteAccessControllerUT.elapsedRealtimeNanos = TEST_LOCATION_FRESH_DURATION_NANOS + 1;
+ when(mMockLocation0.getElapsedRealtimeNanos()).thenReturn(0L);
+ when(mMockLocation1.getElapsedRealtimeNanos()).thenReturn(0L);
+ mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
+ SUB_ID, mSatelliteAllowedReceiver);
+ mTestableLooper.processAllMessages();
+ assertFalse(
+ mSatelliteAccessControllerUT.isKeepOnDeviceAccessControllerResourcesTimerStarted());
+ verify(mMockLocationManager).getCurrentLocation(eq(LocationManager.GPS_PROVIDER),
+ any(LocationRequest.class), mLocationRequestCancellationSignalCaptor.capture(),
+ any(Executor.class), mLocationRequestConsumerCaptor.capture());
+ assertTrue(mSatelliteAccessControllerUT.isWaitForCurrentLocationTimerStarted());
+ sendLocationRequestResult(mMockLocation0);
+ assertFalse(mSatelliteAccessControllerUT.isWaitForCurrentLocationTimerStarted());
+ // The LocationToken should be already in the cache
+ verify(mMockSatelliteOnDeviceAccessController, never()).isSatCommunicationAllowedAtLocation(
+ any(SatelliteOnDeviceAccessController.LocationToken.class));
+ assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
+ mSatelliteAllowedSemaphore, 1));
+ assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteAllowedResultCode);
+ assertTrue(mQueriedSatelliteAllowed);
+
+ // Timed out to wait for current location. No cached country codes.
+ clearAllInvocations();
+ when(mMockCountryDetector.getCurrentNetworkCountryIso()).thenReturn(EMPTY_STRING_LIST);
+ when(mMockTelecomManager.isInEmergencyCall()).thenReturn(false);
+ when(mMockPhone.isInEcm()).thenReturn(true);
+ mSatelliteAccessControllerUT.elapsedRealtimeNanos = TEST_LOCATION_FRESH_DURATION_NANOS + 1;
+ when(mMockLocation0.getElapsedRealtimeNanos()).thenReturn(0L);
+ when(mMockLocation1.getElapsedRealtimeNanos()).thenReturn(0L);
+ when(mMockCountryDetector.getCachedLocationCountryIsoInfo()).thenReturn(new Pair<>("", 0L));
+ when(mMockCountryDetector.getCachedNetworkCountryIsoInfo()).thenReturn(new HashMap<>());
+ mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
+ SUB_ID, mSatelliteAllowedReceiver);
+ mTestableLooper.processAllMessages();
+ assertFalse(
+ mSatelliteAccessControllerUT.isKeepOnDeviceAccessControllerResourcesTimerStarted());
+ verify(mMockLocationManager).getCurrentLocation(anyString(), any(LocationRequest.class),
+ any(CancellationSignal.class), any(Executor.class), any(Consumer.class));
+ assertTrue(mSatelliteAccessControllerUT.isWaitForCurrentLocationTimerStarted());
+ // Timed out
+ mTestableLooper.moveTimeForward(
+ mSatelliteAccessControllerUT.getWaitForCurrentLocationTimeoutMillis());
+ mTestableLooper.processAllMessages();
+ assertFalse(mSatelliteAccessControllerUT.isWaitForCurrentLocationTimerStarted());
+ verify(mMockSatelliteOnDeviceAccessController, never()).isSatCommunicationAllowedAtLocation(
+ any(SatelliteOnDeviceAccessController.LocationToken.class));
+ verifyCountryDetectorApisCalled();
+ assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
+ mSatelliteAllowedSemaphore, 1));
+ assertEquals(SATELLITE_RESULT_SUCCESS,
+ mQueriedSatelliteAllowedResultCode);
+ assertFalse(mQueriedSatelliteAllowed);
+
+ // Network country codes are not available. TelecomManager.isInEmergencyCall() returns
+ // false. No phone is in ECM. Last known location is not fresh. Cached country codes should
+ // be used for verifying satellite allow. No cached country codes are available.
+ clearAllInvocations();
+ when(mMockCountryDetector.getCurrentNetworkCountryIso()).thenReturn(EMPTY_STRING_LIST);
+ when(mMockCountryDetector.getCachedLocationCountryIsoInfo()).thenReturn(new Pair<>("", 0L));
+ when(mMockCountryDetector.getCachedNetworkCountryIsoInfo()).thenReturn(new HashMap<>());
+ when(mMockTelecomManager.isInEmergencyCall()).thenReturn(false);
+ when(mMockPhone.isInEcm()).thenReturn(false);
+ when(mMockPhone2.isInEcm()).thenReturn(false);
+ mSatelliteAccessControllerUT.elapsedRealtimeNanos = TEST_LOCATION_FRESH_DURATION_NANOS + 1;
+ when(mMockLocation0.getElapsedRealtimeNanos()).thenReturn(0L);
+ when(mMockLocation1.getElapsedRealtimeNanos()).thenReturn(0L);
+ mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
+ SUB_ID, mSatelliteAllowedReceiver);
+ mTestableLooper.processAllMessages();
+ verify(mMockLocationManager, never()).getCurrentLocation(anyString(),
+ any(LocationRequest.class), any(CancellationSignal.class), any(Executor.class),
+ any(Consumer.class));
+ verify(mMockSatelliteOnDeviceAccessController, never()).isSatCommunicationAllowedAtLocation(
+ any(SatelliteOnDeviceAccessController.LocationToken.class));
+ verifyCountryDetectorApisCalled();
+ assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
+ mSatelliteAllowedSemaphore, 1));
+ assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteAllowedResultCode);
+ assertFalse(mQueriedSatelliteAllowed);
+
+ // Network country codes are not available. TelecomManager.isInEmergencyCall() returns
+ // false. No phone is in ECM. Last known location is not fresh. Cached country codes should
+ // be used for verifying satellite allow. Cached country codes are available.
+ clearAllInvocations();
+ when(mMockCountryDetector.getCurrentNetworkCountryIso()).thenReturn(EMPTY_STRING_LIST);
+ when(mMockCountryDetector.getCachedLocationCountryIsoInfo())
+ .thenReturn(new Pair<>("US", 5L));
+ Map<String, Long> cachedNetworkCountryCodes = new HashMap<>();
+ cachedNetworkCountryCodes.put("UK", 1L);
+ cachedNetworkCountryCodes.put("US", 3L);
+ when(mMockCountryDetector.getCachedNetworkCountryIsoInfo())
+ .thenReturn(cachedNetworkCountryCodes);
+ when(mMockTelecomManager.isInEmergencyCall()).thenReturn(false);
+ when(mMockPhone.isInEcm()).thenReturn(false);
+ when(mMockPhone2.isInEcm()).thenReturn(false);
+ mSatelliteAccessControllerUT.elapsedRealtimeNanos = TEST_LOCATION_FRESH_DURATION_NANOS + 1;
+ when(mMockLocation0.getElapsedRealtimeNanos()).thenReturn(0L);
+ when(mMockLocation1.getElapsedRealtimeNanos()).thenReturn(0L);
+ mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
+ SUB_ID, mSatelliteAllowedReceiver);
+ mTestableLooper.processAllMessages();
+ verify(mMockLocationManager, never()).getCurrentLocation(anyString(),
+ any(LocationRequest.class), any(CancellationSignal.class), any(Executor.class),
+ any(Consumer.class));
+ verify(mMockSatelliteOnDeviceAccessController, never()).isSatCommunicationAllowedAtLocation(
+ any(SatelliteOnDeviceAccessController.LocationToken.class));
+ verifyCountryDetectorApisCalled();
+ assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
+ mSatelliteAllowedSemaphore, 1));
+ assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteAllowedResultCode);
+ assertTrue(mQueriedSatelliteAllowed);
+ }
+
+ @Test
+ public void testUpdateSatelliteConfigData() {
+ verify(mMockSatelliteController).registerForConfigUpdateChanged(
+ mConfigUpdateHandlerCaptor.capture(), mConfigUpdateIntCaptor.capture(),
+ mConfigUpdateObjectCaptor.capture());
+
+ assertSame(mConfigUpdateHandlerCaptor.getValue(), mSatelliteAccessControllerUT);
+ assertSame(mConfigUpdateIntCaptor.getValue(), EVENT_CONFIG_DATA_UPDATED);
+ assertSame(mConfigUpdateObjectCaptor.getValue(), mMockContext);
+
+ // Verify the case when the configParser is not exist.
+ SatelliteConfigParser spyConfigParserNull =
+ spy(new SatelliteConfigParser((byte[]) null));
+ doReturn(spyConfigParserNull).when(mMockSatelliteController).getSatelliteConfigParser();
+
+ sendConfigUpdateChangedEvent(mMockContext);
+
+ assertNull(spyConfigParserNull.getConfig());
+
+ // Verify the case when the configParser exist but empty.
+ SatelliteConfigParser spyConfigParserEmpty =
+ spy(new SatelliteConfigParser("test".getBytes()));
+ doReturn(spyConfigParserEmpty).when(mMockSatelliteController).getSatelliteConfigParser();
+
+ sendConfigUpdateChangedEvent(mMockContext);
+
+ assertNull(spyConfigParserEmpty.getConfig());
+
+ // Verify the case when the configParser exists and has valid data
+ SatelliteConfig mockSatelliteConfig = mock(SatelliteConfig.class);
+ Path mockTargetSatS2FilePath = mock(Path.class);
+ File mockS2CellFile = mock(File.class);
+ doReturn(mockS2CellFile).when(mockTargetSatS2FilePath).toFile();
+ doReturn(true).when(mockS2CellFile).exists();
+ doReturn(false).when(mockSatelliteConfig).isFileExist(any());
+ doReturn(mockTargetSatS2FilePath).when(mockSatelliteConfig)
+ .copySatS2FileToPhoneDirectory(any(), any());
+ doReturn(Arrays.asList("US")).when(mockSatelliteConfig).getDeviceSatelliteCountryCodes();
+ doReturn(false).when(mockSatelliteConfig).isSatelliteDataForAllowedRegion();
+ doReturn(mockTargetSatS2FilePath).when(mockSatelliteConfig).getSatelliteS2CellFile(any());
+ doReturn(mockSatelliteConfig).when(mMockSatelliteController).getSatelliteConfig();
+
+ mSatelliteAccessControllerUT.setSatelliteOnDeviceAccessController(
+ mMockSatelliteOnDeviceAccessController);
+ sendConfigUpdateChangedEvent(mMockContext);
+
+ verify(mockSatelliteConfig, times(2)).getDeviceSatelliteCountryCodes();
+ verify(mockSatelliteConfig, times(2)).isSatelliteDataForAllowedRegion();
+ verify(mockSatelliteConfig, times(2)).getSatelliteS2CellFile(any());
+ assertTrue(mSatelliteAccessControllerUT.isSatelliteOnDeviceAccessControllerReset());
+ }
+
+ private void sendConfigUpdateChangedEvent(Context context) {
+ Message msg = mSatelliteAccessControllerUT.obtainMessage(EVENT_CONFIG_DATA_UPDATED);
+ msg.obj = new AsyncResult(context, SATELLITE_RESULT_SUCCESS, null);
+ msg.sendToTarget();
+ mTestableLooper.processAllMessages();
+ }
+
+ private void clearAllInvocations() {
+ clearInvocations(mMockSatelliteController);
+ clearInvocations(mMockSatelliteOnDeviceAccessController);
+ clearInvocations(mMockLocationManager);
+ clearInvocations(mMockCountryDetector);
+ }
+
+ private void verifyCountryDetectorApisCalled() {
+ verify(mMockCountryDetector).getCurrentNetworkCountryIso();
+ verify(mMockCountryDetector).getCachedLocationCountryIsoInfo();
+ verify(mMockCountryDetector).getCachedLocationCountryIsoInfo();
+ }
+
+ private boolean waitForRequestIsSatelliteAllowedForCurrentLocationResult(Semaphore semaphore,
+ int expectedNumberOfEvents) {
+ for (int i = 0; i < expectedNumberOfEvents; i++) {
+ try {
+ if (!semaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) {
+ logd("Timeout to receive "
+ + "requestIsCommunicationAllowedForCurrentLocation()"
+ + " callback");
+ return false;
+ }
+ } catch (Exception ex) {
+ logd("waitForRequestIsSatelliteSupportedResult: Got exception=" + ex);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void sendLocationRequestResult(Location location) {
+ mLocationRequestConsumerCaptor.getValue().accept(location);
+ mTestableLooper.processAllMessages();
+ }
+
+ private void setUpResponseForRequestIsSatelliteSupported(
+ boolean isSatelliteSupported, @SatelliteManager.SatelliteResult int error) {
+ doAnswer(invocation -> {
+ ResultReceiver resultReceiver = invocation.getArgument(1);
+ if (error == SATELLITE_RESULT_SUCCESS) {
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(SatelliteManager.KEY_SATELLITE_SUPPORTED, isSatelliteSupported);
+ resultReceiver.send(error, bundle);
+ } else {
+ resultReceiver.send(error, Bundle.EMPTY);
+ }
+ return null;
+ }).when(mMockSatelliteController).requestIsSatelliteSupported(anyInt(),
+ any(ResultReceiver.class));
+ }
+
+ @SafeVarargs
+ private static <E> List<E> listOf(E... values) {
+ return Arrays.asList(values);
+ }
+
+ private static void logd(String message) {
+ Log.d(TAG, message);
+ }
+
+ private static void replaceInstance(final Class c,
+ final String instanceName, final Object obj, final Object newValue) throws Exception {
+ Field field = c.getDeclaredField(instanceName);
+ field.setAccessible(true);
+ field.set(obj, newValue);
+ }
+
+ private static class TestSatelliteAccessController extends SatelliteAccessController {
+ public long elapsedRealtimeNanos = 0;
+
+ /**
+ * Create a SatelliteAccessController instance.
+ *
+ * @param context The context associated with the
+ * {@link SatelliteAccessController} instance.
+ * @param featureFlags The FeatureFlags that are supported.
+ * @param looper The Looper to run the SatelliteAccessController
+ * on.
+ * @param locationManager The LocationManager for querying current
+ * location of the
+ * device.
+ * @param satelliteOnDeviceAccessController The on-device satellite access controller
+ * instance.
+ */
+ protected TestSatelliteAccessController(Context context, FeatureFlags featureFlags,
+ Looper looper, LocationManager locationManager, TelecomManager telecomManager,
+ SatelliteOnDeviceAccessController satelliteOnDeviceAccessController,
+ File s2CellFile) {
+ super(context, featureFlags, looper, locationManager, telecomManager,
+ satelliteOnDeviceAccessController, s2CellFile);
+ }
+
+ @Override
+ protected long getElapsedRealtimeNanos() {
+ return elapsedRealtimeNanos;
+ }
+
+ public boolean isKeepOnDeviceAccessControllerResourcesTimerStarted() {
+ return hasMessages(EVENT_KEEP_ON_DEVICE_ACCESS_CONTROLLER_RESOURCES_TIMEOUT);
+ }
+
+ public boolean isSatelliteOnDeviceAccessControllerReset() {
+ synchronized (mLock) {
+ return (mSatelliteOnDeviceAccessController == null);
+ }
+ }
+
+ public void setSatelliteOnDeviceAccessController(
+ @Nullable SatelliteOnDeviceAccessController accessController) {
+ synchronized (mLock) {
+ mSatelliteOnDeviceAccessController = accessController;
+ }
+ }
+
+ public long getKeepOnDeviceAccessControllerResourcesTimeoutMillis() {
+ return KEEP_ON_DEVICE_ACCESS_CONTROLLER_RESOURCES_TIMEOUT_MILLIS;
+ }
+
+ public long getWaitForCurrentLocationTimeoutMillis() {
+ return WAIT_FOR_CURRENT_LOCATION_TIMEOUT_MILLIS;
+ }
+
+ public boolean isWaitForCurrentLocationTimerStarted() {
+ return hasMessages(EVENT_WAIT_FOR_CURRENT_LOCATION_TIMEOUT);
+ }
+ }
+}
diff --git a/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementApiTest.java b/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementApiTest.java
new file mode 100644
index 0000000..f096e0d
--- /dev/null
+++ b/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementApiTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellite.entitlement;
+
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_DISABLED;
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_ENABLED;
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE;
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_PROVISIONING;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyVararg;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+
+import android.content.Context;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.libraries.entitlement.ServiceEntitlement;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class SatelliteEntitlementApiTest {
+ private static final String TEST_URL = "https://test.url";
+ private static final List<String> TEST_PLMN_ALLOWED = Arrays.asList("31026", "302820");
+ @Mock
+ Context mContext;
+ @Mock
+ ServiceEntitlement mServiceEntitlement;
+ @Mock
+ CarrierConfigManager mCarrierConfigManager;
+ @Mock
+ TelephonyManager mTelephonyManager;
+ private PersistableBundle mCarrierConfigBundle;
+ private SatelliteEntitlementApi mSatelliteEntitlementAPI;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(Context.CARRIER_CONFIG_SERVICE).when(mContext).getSystemServiceName(
+ CarrierConfigManager.class);
+ doReturn(mCarrierConfigManager).when(mContext).getSystemService(
+ Context.CARRIER_CONFIG_SERVICE);
+ mCarrierConfigBundle = new PersistableBundle();
+ doReturn(mCarrierConfigBundle)
+ .when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyVararg());
+ doReturn(Context.TELEPHONY_SERVICE).when(mContext).getSystemServiceName(
+ TelephonyManager.class);
+ doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE);
+ doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+
+ mSatelliteEntitlementAPI = new SatelliteEntitlementApi(mContext, mCarrierConfigBundle,
+ SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+ }
+
+ @Test
+ public void testCheckEntitlementStatus() throws Exception {
+ mCarrierConfigBundle.putString(
+ CarrierConfigManager.ImsServiceEntitlement.KEY_ENTITLEMENT_SERVER_URL_STRING,
+ TEST_URL);
+ Field fieldServiceEntitlement = SatelliteEntitlementApi.class.getDeclaredField(
+ "mServiceEntitlement");
+ fieldServiceEntitlement.setAccessible(true);
+ fieldServiceEntitlement.set(mSatelliteEntitlementAPI, mServiceEntitlement);
+
+ // Get the EntitlementStatus to DISABLED
+ int expectedEntitlementStatus = SATELLITE_ENTITLEMENT_STATUS_DISABLED;
+ doReturn(getResponse(SATELLITE_ENTITLEMENT_STATUS_DISABLED))
+ .when(mServiceEntitlement)
+ .queryEntitlementStatus(eq(ServiceEntitlement.APP_SATELLITE_ENTITLEMENT), any());
+ SatelliteEntitlementResult result =
+ mSatelliteEntitlementAPI.checkEntitlementStatus();
+ assertNotNull(result);
+ assertEquals(expectedEntitlementStatus, result.getEntitlementStatus());
+ assertTrue(result.getAllowedPLMNList().size() == 0);
+
+ // Get the EntitlementStatus to ENABLED
+ expectedEntitlementStatus = SATELLITE_ENTITLEMENT_STATUS_ENABLED;
+ doReturn(getResponse(SATELLITE_ENTITLEMENT_STATUS_ENABLED))
+ .when(mServiceEntitlement)
+ .queryEntitlementStatus(eq(ServiceEntitlement.APP_SATELLITE_ENTITLEMENT), any());
+ result = mSatelliteEntitlementAPI.checkEntitlementStatus();
+ assertNotNull(result);
+ assertEquals(expectedEntitlementStatus, result.getEntitlementStatus());
+ assertEquals(TEST_PLMN_ALLOWED, result.getAllowedPLMNList());
+
+ // Get the EntitlementStatus to INCOMPATIBLE
+ expectedEntitlementStatus = SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE;
+ doReturn(getResponse(SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE))
+ .when(mServiceEntitlement)
+ .queryEntitlementStatus(eq(ServiceEntitlement.APP_SATELLITE_ENTITLEMENT), any());
+ result = mSatelliteEntitlementAPI.checkEntitlementStatus();
+ assertNotNull(result);
+ assertEquals(expectedEntitlementStatus, result.getEntitlementStatus());
+ assertTrue(result.getAllowedPLMNList().size() == 0);
+
+ // Get the EntitlementStatus to PROVISIONING
+ expectedEntitlementStatus = SATELLITE_ENTITLEMENT_STATUS_PROVISIONING;
+ doReturn(getResponse(SATELLITE_ENTITLEMENT_STATUS_PROVISIONING))
+ .when(mServiceEntitlement)
+ .queryEntitlementStatus(eq(ServiceEntitlement.APP_SATELLITE_ENTITLEMENT), any());
+ result = mSatelliteEntitlementAPI.checkEntitlementStatus();
+ assertNotNull(result);
+ assertEquals(expectedEntitlementStatus, result.getEntitlementStatus());
+ assertTrue(result.getAllowedPLMNList().size() == 0);
+ }
+
+ private String getResponse(int entitlementStatus) {
+ return "{\"VERS\":{\"version\":\"1\",\"validity\":\"172800\"},"
+ + "\"TOKEN\":{\"token\":\"ASH127AHHA88SF\"},\""
+ + ServiceEntitlement.APP_SATELLITE_ENTITLEMENT + "\":{"
+ + "\"EntitlementStatus\":\"" + entitlementStatus + "\""
+ + getPLMNListOrEmpty(entitlementStatus)
+ + "}}";
+ }
+
+ private String getPLMNListOrEmpty(int entitlementStatus) {
+ return entitlementStatus == SATELLITE_ENTITLEMENT_STATUS_ENABLED ? ","
+ + "\"PLMNAllowed\":[{\"PLMN\":\"31026\",\"DataPlanType\":\"unmetered\"},"
+ + "{\"PLMN\":\"302820\",\"DataPlanType\":\"metered\"}]" : "";
+ }
+}
diff --git a/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementControllerTest.java b/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementControllerTest.java
new file mode 100644
index 0000000..18a0284
--- /dev/null
+++ b/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementControllerTest.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellite.entitlement;
+
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_ENABLED;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyVararg;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.wifi.WifiInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
+import android.testing.TestableLooper;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+import com.android.internal.telephony.ExponentialBackoff;
+import com.android.internal.telephony.satellite.SatelliteController;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidJUnit4.class)
+public class SatelliteEntitlementControllerTest extends TelephonyTestBase {
+ private static final String TAG = "SatelliteEntitlementControllerTest";
+ private static final int SUB_ID = 0;
+ private static final int SUB_ID_2 = 1;
+ private static final int[] ACTIVE_SUB_ID = {SUB_ID};
+ private static final int DEFAULT_QUERY_REFRESH_DAY = 30;
+ private static final List<String> PLMN_ALLOWED_LIST = Arrays.asList("31026", "302820");
+ @Mock
+ CarrierConfigManager mCarrierConfigManager;
+ @Mock
+ ConnectivityManager mConnectivityManager;
+ @Mock Network mNetwork;
+ @Mock TelephonyManager mTelephonyManager;
+ @Mock SubscriptionManagerService mMockSubscriptionManagerService;
+ @Mock SatelliteEntitlementApi mSatelliteEntitlementApi;
+ @Mock SatelliteEntitlementResult mSatelliteEntitlementResult;
+ @Mock SatelliteController mSatelliteController;
+ @Mock ExponentialBackoff mExponentialBackoff;
+ private PersistableBundle mCarrierConfigBundle;
+ private TestSatelliteEntitlementController mSatelliteEntitlementController;
+ private Handler mHandler;
+ private TestableLooper mTestableLooper;
+ private List<Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener>>
+ mCarrierConfigChangedListenerList = new ArrayList<>();
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+
+ replaceInstance(SubscriptionManagerService.class, "sInstance", null,
+ mMockSubscriptionManagerService);
+ replaceInstance(SatelliteController.class, "sInstance", null, mSatelliteController);
+
+ HandlerThread handlerThread = new HandlerThread("SatelliteEntitlementController");
+ handlerThread.start();
+ mHandler = new Handler(handlerThread.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ }
+ };
+ mTestableLooper = new TestableLooper(mHandler.getLooper());
+ doReturn(Context.TELEPHONY_SERVICE).when(mContext).getSystemServiceName(
+ TelephonyManager.class);
+ doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE);
+ doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+ doReturn(Context.CARRIER_CONFIG_SERVICE).when(mContext).getSystemServiceName(
+ CarrierConfigManager.class);
+ doReturn(mCarrierConfigManager).when(mContext).getSystemService(
+ Context.CARRIER_CONFIG_SERVICE);
+ doAnswer(invocation -> {
+ Executor executor = invocation.getArgument(0);
+ CarrierConfigManager.CarrierConfigChangeListener listener = invocation.getArgument(1);
+ mCarrierConfigChangedListenerList.add(new Pair<>(executor, listener));
+ return null;
+ }).when(mCarrierConfigManager).registerCarrierConfigChangeListener(
+ any(Executor.class),
+ any(CarrierConfigManager.CarrierConfigChangeListener.class));
+ mCarrierConfigBundle = new PersistableBundle();
+ mCarrierConfigBundle.putInt(
+ CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT,
+ DEFAULT_QUERY_REFRESH_DAY);
+ mCarrierConfigBundle.putBoolean(
+ CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
+ doReturn(mCarrierConfigBundle)
+ .when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyVararg());
+ doReturn(Context.CONNECTIVITY_SERVICE).when(mContext).getSystemServiceName(
+ ConnectivityManager.class);
+ doReturn(mConnectivityManager).when(mContext).getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ doReturn(mNetwork).when(mConnectivityManager).getActiveNetwork();
+ doReturn(ACTIVE_SUB_ID).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
+ mSatelliteEntitlementController = new TestSatelliteEntitlementController(mContext,
+ mHandler.getLooper(), mSatelliteEntitlementApi);
+ mSatelliteEntitlementController = spy(mSatelliteEntitlementController);
+ doReturn(mSatelliteEntitlementResult).when(
+ mSatelliteEntitlementApi).checkEntitlementStatus();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ public void testIsQueryAvailable() throws Exception {
+ doReturn(ACTIVE_SUB_ID).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
+
+ // Verify don't start the query when KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL is false.
+ mCarrierConfigBundle.putBoolean(
+ CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);
+ mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+ verify(mSatelliteEntitlementApi, never()).checkEntitlementStatus();
+ verify(mSatelliteController, never()).onSatelliteEntitlementStatusUpdated(anyInt(),
+ anyBoolean(), anyList(), any());
+
+ mCarrierConfigBundle.putBoolean(
+ CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
+ // Verify don't start the query when ExponentialBackoff is in progressed.
+ replaceInstance(SatelliteEntitlementController.class, "mExponentialBackoffPerSub",
+ mSatelliteEntitlementController, Map.of(SUB_ID, mExponentialBackoff));
+ mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+ verify(mSatelliteEntitlementApi, never()).checkEntitlementStatus();
+ verify(mSatelliteController, never()).onSatelliteEntitlementStatusUpdated(anyInt(),
+ anyBoolean(), anyList(), any());
+
+ replaceInstance(SatelliteEntitlementController.class, "mExponentialBackoffPerSub",
+ mSatelliteEntitlementController, new HashMap<>());
+ // Verify don't start the query when Internet is disconnected.
+ doReturn(ACTIVE_SUB_ID).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
+ setInternetConnected(false);
+ mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+ verify(mSatelliteEntitlementApi, never()).checkEntitlementStatus();
+ verify(mSatelliteController, never()).onSatelliteEntitlementStatusUpdated(anyInt(),
+ anyBoolean(), anyList(), any());
+
+ setInternetConnected(true);
+ // Verify don't start the query when last query refresh time is not expired.
+ setLastQueryTime(System.currentTimeMillis());
+ mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+ verify(mSatelliteEntitlementApi, never()).checkEntitlementStatus();
+ verify(mSatelliteController, never()).onSatelliteEntitlementStatusUpdated(anyInt(),
+ anyBoolean(), anyList(), any());
+
+ // Verify start the query when isQueryAvailable return true
+ setLastQueryTime(0L);
+ doReturn(mSatelliteEntitlementResult).when(
+ mSatelliteEntitlementApi).checkEntitlementStatus();
+ setSatelliteEntitlementResult(SATELLITE_ENTITLEMENT_STATUS_ENABLED, PLMN_ALLOWED_LIST);
+ mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+ verify(mSatelliteEntitlementApi).checkEntitlementStatus();
+ verify(mSatelliteController).onSatelliteEntitlementStatusUpdated(anyInt(),
+ anyBoolean(), anyList(), any());
+ }
+
+ @Test
+ public void testCheckSatelliteEntitlementStatus() throws Exception {
+ setIsQueryAvailableTrue();
+ // Verify don't call the checkSatelliteEntitlementStatus when getActiveSubIdList is empty.
+ doReturn(new int[]{}).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
+ mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+ verify(mSatelliteEntitlementApi, never()).checkEntitlementStatus();
+ // Verify don't call the updateSatelliteEntitlementStatus.
+ verify(mSatelliteController, never()).onSatelliteEntitlementStatusUpdated(anyInt(),
+ anyBoolean(), anyList(), any());
+
+ // Verify call the checkSatelliteEntitlementStatus with invalid response.
+ setIsQueryAvailableTrue();
+ doReturn(mSatelliteEntitlementResult).when(
+ mSatelliteEntitlementApi).checkEntitlementStatus();
+ replaceInstance(SatelliteEntitlementController.class,
+ "mSatelliteEntitlementResultPerSub", mSatelliteEntitlementController,
+ new HashMap<>());
+ mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+ verify(mSatelliteEntitlementApi).checkEntitlementStatus();
+ // Verify call the updateSatelliteEntitlementStatus with satellite service is disabled
+ // and empty PLMNAllowed
+ verify(mSatelliteController).onSatelliteEntitlementStatusUpdated(eq(SUB_ID),
+ eq(false), eq(new ArrayList<>()), any());
+
+ // Verify call the checkSatelliteEntitlementStatus with the subscribed result.
+ clearInvocationsForMock();
+ setIsQueryAvailableTrue();
+ doReturn(mSatelliteEntitlementResult).when(
+ mSatelliteEntitlementApi).checkEntitlementStatus();
+ setSatelliteEntitlementResult(SATELLITE_ENTITLEMENT_STATUS_ENABLED, PLMN_ALLOWED_LIST);
+ mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+ verify(mSatelliteEntitlementApi).checkEntitlementStatus();
+ // Verify call the updateSatelliteEntitlementStatus with satellite service is enable and
+ // availablePLMNAllowedList
+ verify(mSatelliteController).onSatelliteEntitlementStatusUpdated(eq(SUB_ID), eq(true),
+ eq(PLMN_ALLOWED_LIST), any());
+
+ // Change subId and verify call the updateSatelliteEntitlementStatus with satellite
+ // service is enable and availablePLMNAllowedList
+ clearInvocationsForMock();
+ doReturn(new int[]{SUB_ID_2}).when(mMockSubscriptionManagerService).getActiveSubIdList(
+ true);
+ mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+ verify(mSatelliteEntitlementApi).checkEntitlementStatus();
+ verify(mSatelliteController).onSatelliteEntitlementStatusUpdated(eq(SUB_ID_2), eq(true),
+ eq(PLMN_ALLOWED_LIST), any());
+ }
+
+ @Test
+ public void testCheckSatelliteEntitlementStatusWhenInternetConnected() throws Exception {
+ Field fieldNetworkCallback = SatelliteEntitlementController.class.getDeclaredField(
+ "mNetworkCallback");
+ fieldNetworkCallback.setAccessible(true);
+ ConnectivityManager.NetworkCallback networkCallback =
+ (ConnectivityManager.NetworkCallback) fieldNetworkCallback.get(
+ mSatelliteEntitlementController);
+ Network mockNetwork = mock(Network.class);
+
+ // Verify the called the checkSatelliteEntitlementStatus when Internet is connected.
+ setInternetConnected(true);
+ setLastQueryTime(0L);
+ setSatelliteEntitlementResult(SATELLITE_ENTITLEMENT_STATUS_ENABLED, PLMN_ALLOWED_LIST);
+
+ networkCallback.onAvailable(mockNetwork);
+ mTestableLooper.processAllMessages();
+
+ verify(mSatelliteEntitlementApi).checkEntitlementStatus();
+ // Verify call the updateSatelliteEntitlementStatus with satellite service is available.
+ verify(mSatelliteController).onSatelliteEntitlementStatusUpdated(eq(SUB_ID), eq(true),
+ eq(PLMN_ALLOWED_LIST), any());
+ }
+
+ @Test
+ public void testCheckSatelliteEntitlementStatusWhenCarrierConfigChanged() throws Exception {
+ // Verify the called the checkSatelliteEntitlementStatus when CarrierConfigChanged
+ // occurred and Internet is connected.
+ setInternetConnected(true);
+ setLastQueryTime(0L);
+ setSatelliteEntitlementResult(SATELLITE_ENTITLEMENT_STATUS_ENABLED, PLMN_ALLOWED_LIST);
+ triggerCarrierConfigChanged();
+
+ verify(mSatelliteEntitlementApi).checkEntitlementStatus();
+ // Verify call the updateSatelliteEntitlementStatus with satellite service is available.
+ verify(mSatelliteController).onSatelliteEntitlementStatusUpdated(eq(SUB_ID), eq(true),
+ eq(PLMN_ALLOWED_LIST), any());
+ }
+
+ private void triggerCarrierConfigChanged() {
+ for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+ : mCarrierConfigChangedListenerList) {
+ pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+ /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+ );
+ }
+ mTestableLooper.processAllMessages();
+ }
+
+ private void clearInvocationsForMock() {
+ clearInvocations(mSatelliteEntitlementApi);
+ clearInvocations(mSatelliteController);
+ }
+
+ private void setIsQueryAvailableTrue() throws Exception {
+ doReturn(ACTIVE_SUB_ID).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
+ mCarrierConfigBundle.putBoolean(
+ CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
+ replaceInstance(SatelliteEntitlementController.class, "mRetryCountPerSub",
+ mSatelliteEntitlementController, new HashMap<>());
+ setInternetConnected(true);
+ setLastQueryTime(0L);
+ replaceInstance(SatelliteEntitlementController.class,
+ "mSatelliteEntitlementResultPerSub", mSatelliteEntitlementController,
+ new HashMap<>());
+ }
+
+ private void setInternetConnected(boolean connected) {
+ NetworkCapabilities networkCapabilities = new NetworkCapabilities.Builder().build();
+
+ if (connected) {
+ networkCapabilities = new NetworkCapabilities.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .setTransportInfo(mock(WifiInfo.class))
+ .build();
+ }
+ doReturn(networkCapabilities).when(mConnectivityManager).getNetworkCapabilities(mNetwork);
+ }
+
+ private void setSatelliteEntitlementResult(int entitlementStatus,
+ List<String> plmnAllowedList) {
+ doReturn(entitlementStatus).when(mSatelliteEntitlementResult).getEntitlementStatus();
+ doReturn(plmnAllowedList).when(mSatelliteEntitlementResult).getAllowedPLMNList();
+ }
+
+ private void setLastQueryTime(Long lastQueryTime) throws Exception {
+ Map<Integer, Long> lastQueryTimePerSub = new HashMap<>();
+ replaceInstance(SatelliteEntitlementController.class, "mLastQueryTimePerSub",
+ mSatelliteEntitlementController, lastQueryTimePerSub);
+ lastQueryTimePerSub.put(SUB_ID, lastQueryTime);
+ }
+
+ public static class TestSatelliteEntitlementController extends SatelliteEntitlementController {
+ private SatelliteEntitlementApi mInjectSatelliteEntitlementApi;
+
+ TestSatelliteEntitlementController(@NonNull Context context, @NonNull Looper looper,
+ SatelliteEntitlementApi api) {
+ super(context, looper);
+ mInjectSatelliteEntitlementApi = api;
+ }
+
+ @Override
+ public SatelliteEntitlementApi getSatelliteEntitlementApi(int subId) {
+ logd("getSatelliteEntitlementApi");
+ return mInjectSatelliteEntitlementApi;
+ }
+ }
+
+ private static void logd(String log) {
+ Log.d(TAG, log);
+ }
+}
diff --git a/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementResponseTest.java b/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementResponseTest.java
new file mode 100644
index 0000000..45e2a71
--- /dev/null
+++ b/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementResponseTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.satellite.entitlement;
+
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_DISABLED;
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_ENABLED;
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE;
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_PROVISIONING;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.telephony.satellite.SatelliteNetworkInfo;
+import com.android.libraries.entitlement.ServiceEntitlement;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class SatelliteEntitlementResponseTest {
+ private static final String TEST_OTHER_APP_ID = "ap201x";
+ private static final List<SatelliteNetworkInfo> TEST_PLMN_DATA_PLAN_TYPE_LIST = Arrays.asList(
+ new SatelliteNetworkInfo("31026", "unmetered"),
+ new SatelliteNetworkInfo("302820", "metered"));
+ private static final List<String> TEST_PLMN_BARRED_LIST = Arrays.asList("31017", "302020");
+ private static final String RESPONSE_WITHOUT_SATELLITE_APP_ID =
+ "{\"VERS\":{\"version\":\"1\",\"validity\":\"172800\"},"
+ + "\"TOKEN\":{\"token\":\"ASH127AHHA88SF\"},\""
+ + TEST_OTHER_APP_ID + "\":{"
+ + "\"EntitlementStatus\":\"" + SATELLITE_ENTITLEMENT_STATUS_ENABLED + "\"}}";
+ private static final String RESPONSE_WITHOUT_ENTITLEMENT_STATUS =
+ "{\"VERS\":{\"version\":\"1\",\"validity\":\"172800\"},"
+ + "\"TOKEN\":{\"token\":\"ASH127AHHA88SF\"},\""
+ + ServiceEntitlement.APP_SATELLITE_ENTITLEMENT + "\":{}}";
+
+ @Test
+ public void testGetSatelliteEntitlementResponse() throws Exception {
+ // Received the body with satellite service enabled.
+ SatelliteEntitlementResponse response = new SatelliteEntitlementResponse(
+ getResponse(SATELLITE_ENTITLEMENT_STATUS_ENABLED));
+ assertEquals(SATELLITE_ENTITLEMENT_STATUS_ENABLED, response.getEntitlementStatus());
+ assertEquals(TEST_PLMN_DATA_PLAN_TYPE_LIST.get(0).mPlmn,
+ response.getPlmnAllowed().get(0).mPlmn);
+ assertEquals(TEST_PLMN_DATA_PLAN_TYPE_LIST.get(0).mDataPlanType,
+ response.getPlmnAllowed().get(0).mDataPlanType);
+ assertEquals(TEST_PLMN_DATA_PLAN_TYPE_LIST.get(1).mPlmn,
+ response.getPlmnAllowed().get(1).mPlmn);
+ assertEquals(TEST_PLMN_DATA_PLAN_TYPE_LIST.get(1).mDataPlanType,
+ response.getPlmnAllowed().get(1).mDataPlanType);
+ assertEquals(TEST_PLMN_BARRED_LIST, response.getPlmnBarredList());
+
+ // Received the empty body.
+ response = new SatelliteEntitlementResponse("");
+ assertEquals(SATELLITE_ENTITLEMENT_STATUS_DISABLED, response.getEntitlementStatus());
+ assertTrue(response.getPlmnAllowed().size() == 0);
+ assertTrue(response.getPlmnBarredList().size() == 0);
+
+ // Received the body without satellite app id.
+ response = new SatelliteEntitlementResponse(RESPONSE_WITHOUT_SATELLITE_APP_ID);
+ assertEquals(SATELLITE_ENTITLEMENT_STATUS_DISABLED, response.getEntitlementStatus());
+ assertTrue(response.getPlmnAllowed().size() == 0);
+ assertTrue(response.getPlmnBarredList().size() == 0);
+
+ // Received the body without EntitlementStatus.
+ response = new SatelliteEntitlementResponse(RESPONSE_WITHOUT_ENTITLEMENT_STATUS);
+ assertEquals(SATELLITE_ENTITLEMENT_STATUS_DISABLED, response.getEntitlementStatus());
+ assertTrue(response.getPlmnAllowed().size() == 0);
+ assertTrue(response.getPlmnBarredList().size() == 0);
+
+ // Received the body with an entitlementStatus value of DISABLED.
+ response = new SatelliteEntitlementResponse(
+ getResponse(SATELLITE_ENTITLEMENT_STATUS_DISABLED));
+ assertEquals(SATELLITE_ENTITLEMENT_STATUS_DISABLED, response.getEntitlementStatus());
+ assertTrue(response.getPlmnAllowed().size() == 0);
+ assertTrue(response.getPlmnBarredList().size() == 0);
+
+ // Received the body with an entitlementStatus value of INCOMPATIBLE.
+ response = new SatelliteEntitlementResponse(
+ getResponse(SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE));
+ assertEquals(SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE, response.getEntitlementStatus());
+ assertTrue(response.getPlmnAllowed().size() == 0);
+ assertTrue(response.getPlmnBarredList().size() == 0);
+
+ // Received the body with an entitlementStatus value of PROVISIONING.
+ response = new SatelliteEntitlementResponse(
+ getResponse(SATELLITE_ENTITLEMENT_STATUS_PROVISIONING));
+ assertEquals(SATELLITE_ENTITLEMENT_STATUS_PROVISIONING, response.getEntitlementStatus());
+ assertTrue(response.getPlmnAllowed().size() == 0);
+ assertTrue(response.getPlmnBarredList().size() == 0);
+ }
+
+ private String getResponse(int entitlementStatus) {
+ return "{\"VERS\":{\"version\":\"1\",\"validity\":\"172800\"},"
+ + "\"TOKEN\":{\"token\":\"ASH127AHHA88SF\"},\""
+ + ServiceEntitlement.APP_SATELLITE_ENTITLEMENT + "\":{"
+ + "\"EntitlementStatus\":\"" + entitlementStatus + "\""
+ + getPLMNListOrEmpty(entitlementStatus)
+ + "}}";
+ }
+
+ private String getPLMNListOrEmpty(int entitlementStatus) {
+ return entitlementStatus == SATELLITE_ENTITLEMENT_STATUS_ENABLED ? ","
+ + "\"PLMNAllowed\":[{\"PLMN\":\"31026\",\"DataPlanType\":\"unmetered\"},"
+ + "{\"PLMN\":\"302820\",\"DataPlanType\":\"metered\"}],"
+ + "\"PLMNBarred\":[{\"PLMN\":\"31017\"},"
+ + "{\"PLMN\":\"302020\"}]" : "";
+ }
+}
diff --git a/tests/src/com/android/phone/security/SafetySourceReceiverTest.java b/tests/src/com/android/phone/security/SafetySourceReceiverTest.java
new file mode 100644
index 0000000..305e698
--- /dev/null
+++ b/tests/src/com/android/phone/security/SafetySourceReceiverTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.security;
+
+import static android.safetycenter.SafetyCenterManager.ACTION_REFRESH_SAFETY_SOURCES;
+import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+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.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.Flags;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class SafetySourceReceiverTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ Context mContext;
+
+ SafetySourceReceiver mSafetySourceReceiver;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ SafetySourceReceiver receiver = new SafetySourceReceiver();
+ mSafetySourceReceiver = spy(receiver);
+
+ when(mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY)).thenReturn(true);
+ }
+
+ @Test
+ public void testOnReceive() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS,
+ Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY_UNSOL_EVENTS,
+ Flags.FLAG_ENFORCE_TELEPHONY_FEATURE_MAPPING_FOR_PUBLIC_APIS);
+ Phone mockPhone = mock(Phone.class);
+ when(mSafetySourceReceiver.getDefaultPhone()).thenReturn(mockPhone);
+
+ Intent intent = new Intent(ACTION_REFRESH_SAFETY_SOURCES);
+ intent.putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, "aBroadcastId");
+ mSafetySourceReceiver.onReceive(mContext, intent);
+
+ verify(mockPhone, times(1)).refreshSafetySources("aBroadcastId");
+ }
+
+ @Test
+ public void testOnReceive_featureFlagsOff() {
+ mSetFlagsRule.disableFlags(
+ Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS,
+ Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY_UNSOL_EVENTS,
+ Flags.FLAG_ENFORCE_TELEPHONY_FEATURE_MAPPING_FOR_PUBLIC_APIS);
+
+ Intent intent = new Intent(ACTION_REFRESH_SAFETY_SOURCES);
+ intent.putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, "aBroadcastId");
+ mSafetySourceReceiver.onReceive(mContext, intent);
+
+ verify(mSafetySourceReceiver, never()).getDefaultPhone();
+ }
+
+ @Test
+ public void testOnReceive_phoneNotReadyYet() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS,
+ Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY_UNSOL_EVENTS,
+ Flags.FLAG_ENFORCE_TELEPHONY_FEATURE_MAPPING_FOR_PUBLIC_APIS);
+ when(mSafetySourceReceiver.getDefaultPhone()).thenReturn(null);
+
+ Intent intent = new Intent(ACTION_REFRESH_SAFETY_SOURCES);
+ intent.putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, "aBroadcastId");
+
+ // this call succeeding without a NPE means this test has passed. There are no observable
+ // side effects to a null Phone, because all side effects happen on the Phone instance.
+ mSafetySourceReceiver.onReceive(mContext, intent);
+ }
+
+ @Test
+ public void testOnReceive_noTelephonyFeature() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY_UNSOL_EVENTS,
+ Flags.FLAG_ENABLE_MODEM_CIPHER_TRANSPARENCY_UNSOL_EVENTS,
+ Flags.FLAG_ENFORCE_TELEPHONY_FEATURE_MAPPING_FOR_PUBLIC_APIS);
+
+ when(mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY)).thenReturn(false);
+
+ Phone mockPhone = mock(Phone.class);
+ when(mSafetySourceReceiver.getDefaultPhone()).thenReturn(mockPhone);
+
+ Intent intent = new Intent(ACTION_REFRESH_SAFETY_SOURCES);
+ intent.putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, "aBroadcastId");
+ mSafetySourceReceiver.onReceive(mContext, intent);
+
+ verify(mockPhone, never()).refreshSafetySources(any());
+ }
+}
diff --git a/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java b/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
index b2a4a9f..5637c3a 100644
--- a/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
+++ b/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
@@ -34,6 +34,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -50,6 +51,9 @@
import android.telephony.TelephonyManager;
import android.telephony.data.NetworkSliceInfo;
import android.telephony.data.NetworkSlicingConfig;
+import android.telephony.data.RouteSelectionDescriptor;
+import android.telephony.data.TrafficDescriptor;
+import android.telephony.data.UrspRule;
import android.testing.TestableLooper;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -58,6 +62,7 @@
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.data.DataSettingsManager;
+import com.android.internal.telephony.flags.FeatureFlags;
import org.junit.Before;
import org.junit.Test;
@@ -66,7 +71,9 @@
import org.mockito.Mockito;
import java.time.LocalDate;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
@RunWith(AndroidJUnit4.class)
@@ -86,6 +93,7 @@
private static final long THROTTLE_TIMEOUT = 4000;
@Mock Phone mPhone;
+ @Mock FeatureFlags mFeatureFlags;
@Mock CarrierConfigManager mCarrierConfigManager;
@Mock CommandsInterface mCommandsInterface;
@Mock ServiceState mServiceState;
@@ -147,7 +155,7 @@
// create a spy to mock final PendingIntent methods
SlicePurchaseController slicePurchaseController =
- new SlicePurchaseController(mPhone, mHandler.getLooper());
+ new SlicePurchaseController(mPhone, mFeatureFlags, mHandler.getLooper());
mSlicePurchaseController = spy(slicePurchaseController);
doReturn(null).when(mSlicePurchaseController).createPendingIntent(
anyString(), anyInt(), anyBoolean());
@@ -578,7 +586,7 @@
intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
intent.putExtra(SlicePurchaseController.EXTRA_FAILURE_CODE,
- SlicePurchaseController.FAILURE_CODE_SERVER_UNREACHABLE);
+ SlicePurchaseController.FAILURE_CODE_CARRIER_URL_UNAVAILABLE);
mContext.getBroadcastReceiver().onReceive(mContext, intent);
mTestableLooper.processAllMessages();
assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR, mResult);
@@ -619,6 +627,7 @@
public void testPurchasePremiumCapabilityResultNotDefaultDataSubscriptionResponse() {
sendValidPurchaseRequest();
+ // broadcast NOT_DEFAULT_DATA_SUBSCRIPTION response from slice purchase application
Intent intent = new Intent();
intent.setAction("com.android.phone.slice.action."
+ "SLICE_PURCHASE_APP_RESPONSE_NOT_DEFAULT_DATA_SUBSCRIPTION");
@@ -636,6 +645,35 @@
}
@Test
+ public void testPurchasePremiumCapabilityResultNotificationsDisabled() {
+ doReturn(true).when(mFeatureFlags).slicingAdditionalErrorCodes();
+ sendValidPurchaseRequest();
+
+ // broadcast NOTIFICATIONS_DISABLED response from slice purchase application
+ Intent intent = new Intent();
+ intent.setAction("com.android.phone.slice.action."
+ + "SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED");
+ intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
+ intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+ TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+ mContext.getBroadcastReceiver().onReceive(mContext, intent);
+ mTestableLooper.processAllMessages();
+ assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED, mResult);
+
+ // retry to verify throttled
+ mSlicePurchaseController.purchasePremiumCapability(
+ TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, mHandler.obtainMessage());
+ mTestableLooper.processAllMessages();
+ assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED, mResult);
+
+ // retry to verify unthrottled
+ mTestableLooper.moveTimeForward(THROTTLE_TIMEOUT);
+ mTestableLooper.processAllMessages();
+
+ testPurchasePremiumCapabilityResultSuccess();
+ }
+
+ @Test
public void testPurchasePremiumCapabilityResultNotificationThrottled() {
mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR, MONTH, DATE));
mSlicePurchaseController.updateNotificationCounts();
@@ -670,6 +708,69 @@
assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED, mResult);
}
+ @Test
+ public void testIsSlicingConfigActive_emptyUrspRules() {
+ int capability = TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY;
+ NetworkSliceInfo sliceInfo = createNetworkSliceInfo(
+ getRandomSliceServiceType(capability), true);
+ NetworkSlicingConfig slicingConfig = new NetworkSlicingConfig(
+ Collections.emptyList(), Collections.singletonList(sliceInfo));
+ mSlicePurchaseController.setSlicingConfig(slicingConfig);
+
+ assertFalse(mSlicePurchaseController.isSlicingConfigActive(capability));
+ }
+
+ @Test
+ public void testIsSlicingConfigActive_noMatchingTrafficDescriptor() {
+ int capability = TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY;
+ NetworkSliceInfo sliceInfo = createNetworkSliceInfo(
+ getRandomSliceServiceType(capability), true);
+ TrafficDescriptor trafficDescriptor = createTrafficDescriptor("ENTERPRISE");
+ RouteSelectionDescriptor routeSelectionDescriptor = createRouteSelectionDescriptor(
+ Collections.singletonList(sliceInfo));
+ NetworkSlicingConfig slicingConfig = createNetworkSlicingConfig(
+ Collections.singletonList(sliceInfo),
+ Collections.singletonList(trafficDescriptor),
+ Collections.singletonList(routeSelectionDescriptor));
+ mSlicePurchaseController.setSlicingConfig(slicingConfig);
+
+ assertFalse(mSlicePurchaseController.isSlicingConfigActive(capability));
+ }
+
+ @Test
+ public void testIsSlicingConfigActive_multipleElements() {
+ int capability = TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY;
+ NetworkSliceInfo sliceInfo1 = createNetworkSliceInfo(
+ getRandomSliceServiceType(SlicePurchaseController.PREMIUM_CAPABILITY_INVALID),
+ false);
+ NetworkSliceInfo sliceInfo2 = createNetworkSliceInfo(
+ getRandomSliceServiceType(capability), true);
+ List<NetworkSliceInfo> sliceInfos = new ArrayList<>();
+ sliceInfos.add(sliceInfo1);
+ sliceInfos.add(sliceInfo2);
+
+ TrafficDescriptor trafficDescriptor1 = createTrafficDescriptor("ENTERPRISE");
+ TrafficDescriptor trafficDescriptor2 = createTrafficDescriptor(
+ SlicePurchaseController.getAppId(capability));
+ List<TrafficDescriptor> trafficDescriptors = new ArrayList<>();
+ trafficDescriptors.add(trafficDescriptor1);
+ trafficDescriptors.add(trafficDescriptor2);
+
+ RouteSelectionDescriptor routeSelectionDescriptor1 = createRouteSelectionDescriptor(
+ Collections.emptyList());
+ RouteSelectionDescriptor routeSelectionDescriptor2 = createRouteSelectionDescriptor(
+ sliceInfos);
+ List<RouteSelectionDescriptor> routeSelectionDescriptors = new ArrayList<>();
+ routeSelectionDescriptors.add(routeSelectionDescriptor1);
+ routeSelectionDescriptors.add(routeSelectionDescriptor2);
+
+ NetworkSlicingConfig slicingConfig = createNetworkSlicingConfig(
+ sliceInfos, trafficDescriptors, routeSelectionDescriptors);
+ mSlicePurchaseController.setSlicingConfig(slicingConfig);
+
+ assertTrue(mSlicePurchaseController.isSlicingConfigActive(capability));
+ }
+
private void completeSuccessfulPurchase() {
sendValidPurchaseRequest();
@@ -726,7 +827,7 @@
mEntitlementResponse.mEntitlementStatus =
PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED;
mEntitlementResponse.mProvisionStatus =
- PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_PROVISION_STATUS_PROVISIONED;
+ PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_PROVISION_STATUS_NOT_PROVISIONED;
// send purchase request
mSlicePurchaseController.purchasePremiumCapability(
@@ -744,18 +845,61 @@
}
private void sendNetworkSlicingConfig(int capability, boolean configActive) {
- int sliceServiceType = capability == TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY
- ? NetworkSliceInfo.SLICE_SERVICE_TYPE_URLLC
- : NetworkSliceInfo.SLICE_SERVICE_TYPE_NONE;
- NetworkSliceInfo sliceInfo = new NetworkSliceInfo.Builder()
- .setStatus(configActive ? NetworkSliceInfo.SLICE_STATUS_ALLOWED
- : NetworkSliceInfo.SLICE_STATUS_UNKNOWN)
- .setSliceServiceType(sliceServiceType)
- .build();
- NetworkSlicingConfig slicingConfig = new NetworkSlicingConfig(Collections.emptyList(),
+ NetworkSliceInfo sliceInfo = createNetworkSliceInfo(
+ getRandomSliceServiceType(capability), configActive);
+ TrafficDescriptor trafficDescriptor = createTrafficDescriptor(
+ SlicePurchaseController.getAppId(capability));
+ RouteSelectionDescriptor routeSelectionDescriptor = createRouteSelectionDescriptor(
Collections.singletonList(sliceInfo));
+ NetworkSlicingConfig slicingConfig = createNetworkSlicingConfig(
+ Collections.singletonList(sliceInfo),
+ Collections.singletonList(trafficDescriptor),
+ Collections.singletonList(routeSelectionDescriptor));
mSlicePurchaseController.obtainMessage(2 /* EVENT_SLICING_CONFIG_CHANGED */,
new AsyncResult(null, slicingConfig, null)).sendToTarget();
mTestableLooper.processAllMessages();
}
+
+ @NetworkSliceInfo.SliceServiceType private int getRandomSliceServiceType(
+ @TelephonyManager.PremiumCapability int capability) {
+ for (int sliceServiceType : SlicePurchaseController.getSliceServiceTypes(capability)) {
+ // Get a random valid sst from the set
+ return sliceServiceType;
+ }
+ return NetworkSliceInfo.SLICE_SERVICE_TYPE_NONE;
+ }
+
+ @NonNull private NetworkSliceInfo createNetworkSliceInfo(
+ @NetworkSliceInfo.SliceServiceType int sliceServiceType, boolean active) {
+ return new NetworkSliceInfo.Builder()
+ .setStatus(active ? NetworkSliceInfo.SLICE_STATUS_ALLOWED
+ : NetworkSliceInfo.SLICE_STATUS_UNKNOWN)
+ .setSliceServiceType(sliceServiceType)
+ .build();
+ }
+
+ @NonNull private TrafficDescriptor createTrafficDescriptor(@NonNull String appId) {
+ TrafficDescriptor.OsAppId osAppId = new TrafficDescriptor.OsAppId(
+ TrafficDescriptor.OsAppId.ANDROID_OS_ID, appId);
+ return new TrafficDescriptor.Builder()
+ .setOsAppId(osAppId.getBytes())
+ .build();
+ }
+
+ @NonNull private RouteSelectionDescriptor createRouteSelectionDescriptor(
+ @NonNull List<NetworkSliceInfo> sliceInfos) {
+ return new RouteSelectionDescriptor(
+ RouteSelectionDescriptor.MIN_ROUTE_PRECEDENCE,
+ RouteSelectionDescriptor.SESSION_TYPE_IPV4,
+ RouteSelectionDescriptor.ROUTE_SSC_MODE_1,
+ sliceInfos, Collections.emptyList());
+ }
+
+ @NonNull private NetworkSlicingConfig createNetworkSlicingConfig(
+ @NonNull List<NetworkSliceInfo> sliceInfos,
+ @NonNull List<TrafficDescriptor> trafficDescriptors,
+ @NonNull List<RouteSelectionDescriptor> routeSelectionDescriptors) {
+ UrspRule urspRule = new UrspRule(0, trafficDescriptors, routeSelectionDescriptors);
+ return new NetworkSlicingConfig(Collections.singletonList(urspRule), sliceInfos);
+ }
}
diff --git a/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java b/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java
index 969622a..71a23e6 100644
--- a/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java
+++ b/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java
@@ -22,6 +22,10 @@
import static junit.framework.Assert.assertNotNull;
import static junit.framework.TestCase.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -33,16 +37,16 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.TelephonyTestBase;
+import com.android.internal.telephony.CallFailCause;
import com.android.internal.telephony.GsmCdmaPhone;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
-import com.android.phone.common.R;
+import com.android.phone.R;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.Mockito;
import java.util.Locale;
@@ -60,11 +64,18 @@
@Mock
private GsmCdmaPhone mMockPhone;
+ private final FlagsAdapter mFeatureFlags = new FlagsAdapter(){
+ @Override
+ public boolean doNotOverridePreciseLabel() {
+ return true;
+ }
+ };
+
@Before
public void setUp() throws Exception {
super.setUp();
// objects that call static getInstance()
- mMockPhone = Mockito.mock(GsmCdmaPhone.class);
+ mMockPhone = mock(GsmCdmaPhone.class);
mContext = InstrumentationRegistry.getTargetContext();
// set mocks
setSinglePhone();
@@ -91,7 +102,7 @@
@Test
public void testDefaultDisconnectCauseBehaviorForCauseNotInCarrierBusyToneArray() {
android.telecom.DisconnectCause tcCause = DisconnectCauseUtil.toTelecomDisconnectCause(
- DisconnectCause.ERROR_UNSPECIFIED, EMPTY_STRING, PHONE_ID);
+ DisconnectCause.ERROR_UNSPECIFIED, -1, EMPTY_STRING, PHONE_ID, null, mFeatureFlags);
// CODE
assertEquals(android.telecom.DisconnectCause.ERROR, tcCause.getCode());
// LABEL
@@ -101,29 +112,145 @@
}
/**
- * Simulate a Carrier classifying the DisconnectCause.ERROR_UNSPECIFIED as a
- * DisconnectCause.BUSY. The code, label, and tone should match DisconnectCause.BUSY.
+ * verify that if a precise label is given Telephony, the label is not overridden by Telecom
*/
@Test
- public void testCarrierSetDisconnectCauseInBusyToneArray() {
- int[] carrierBusyArr = {DisconnectCause.BUSY, DisconnectCause.ERROR_UNSPECIFIED};
+ public void testDefaultPhoneConfig_NoPreciseLabelGiven() {
+ android.telecom.DisconnectCause tcCause =
+ DisconnectCauseUtil.toTelecomDisconnectCause(DisconnectCause.BUSY,
+ -1 /* precise label is NOT given */,
+ EMPTY_STRING, PHONE_ID, null /* carrier config is NOT set */,
+ mFeatureFlags);
+ assertBusyCauseWithTargetLabel(R.string.callFailed_userBusy, tcCause);
+ }
+
+ /**
+ * verify that if a precise label is given Telephony, the label is not overridden by Telecom
+ */
+ @Test
+ public void testDefaultPhoneConfig_PreciseLabelProvided() {
+ android.telecom.DisconnectCause tcCause =
+ DisconnectCauseUtil.toTelecomDisconnectCause(DisconnectCause.BUSY,
+ CallFailCause.USER_BUSY /* Telephony defined a precise label */,
+ EMPTY_STRING, PHONE_ID, null /* carrier config is NOT set */,
+ mFeatureFlags);
+ // Note: The precise label should not be overridden even though the carrier defined
+ // the cause to play a busy tone
+ assertBusyCauseWithTargetLabel(R.string.clh_callFailed_user_busy_txt, tcCause);
+ }
+
+ /**
+ * special case: The Carrier has re-defined a disconnect code that should play a busy tone.
+ * Thus, the code, label, and tone should be remapped.
+ * <p>
+ * <p>
+ * Verify that if the disconnect cause is in the carrier busy tone array that the expected
+ * label, tone, and code are returned.
+ */
+ @Test
+ public void testCarrierSetBusyToneArray_NoPreciseLabelGiven() {
+ android.telecom.DisconnectCause tcCause =
+ DisconnectCauseUtil.toTelecomDisconnectCause(
+ DisconnectCause.BUSY, -1 /* precise label is NOT given */,
+ EMPTY_STRING, PHONE_ID, null, getBundleWithBusyToneArray(), mFeatureFlags,
+ false);
+
+ assertBusyCauseWithTargetLabel(R.string.callFailed_userBusy, tcCause);
+ }
+
+ /**
+ * special case: The Carrier has re-defined a disconnect code that should play a busy tone.
+ * Thus, the code, label, and tone should be remapped.
+ * <p>
+ * <p>
+ * Verify that if the disconnect cause is in the carrier busy tone array and the Telephony
+ * stack has provided a precise label, the label is not overridden.
+ */
+ @Test
+ public void testCarrierSetBusyToneArray_PreciseLabelProvided() {
+ android.telecom.DisconnectCause tcCause =
+ DisconnectCauseUtil.toTelecomDisconnectCause(DisconnectCause.BUSY,
+ CallFailCause.USER_BUSY /* Telephony defined a precise label */,
+ EMPTY_STRING, PHONE_ID, null, getBundleWithBusyToneArray(), mFeatureFlags,
+ false);
+ // Note: The precise label should not be overridden even though the carrier defined
+ // the cause to play a busy tone
+ assertBusyCauseWithTargetLabel(R.string.clh_callFailed_user_busy_txt, tcCause);
+ }
+
+ /**
+ * Ensure the helper doesCarrierClassifyDisconnectCauseAsBusyCause does not hit a NPE if a
+ * NULL carrier config is passed in.
+ */
+ @Test
+ public void testDoesCarrierClassifyDisconnectCauseAsBusyCause_nullConfig() {
+ assertFalse(DisconnectCauseUtil.doesCarrierClassifyDisconnectCauseAsBusyCause(-1, null));
+ }
+
+ /**
+ * Ensure the helper doesCarrierClassifyDisconnectCauseAsBusyCause does not hit a NPE if an
+ * EMPTY carrier config is passed in.
+ */
+ @Test
+ public void testDoesCarrierClassifyDisconnectCauseAsBusyCause_ConfigDoesNotDefineArray() {
PersistableBundle config = new PersistableBundle();
+ assertFalse(DisconnectCauseUtil.doesCarrierClassifyDisconnectCauseAsBusyCause(-1, config));
+ }
+
+ /**
+ * Ensure the helper doesCarrierClassifyDisconnectCauseAsBusyCause does not hit a NPE if an
+ * EMPTY array is defined for KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY.
+ */
+ @Test
+ public void testDoesCarrierClassifyDisconnectCauseAsBusyCause_ConfigHasEmptyArray() {
+ PersistableBundle config = new PersistableBundle();
+ int[] carrierBusyArr = {}; // NOTE: This is intentionally let empty
config.putIntArray(
CarrierConfigManager.KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
carrierBusyArr);
- android.telecom.DisconnectCause tcCause =
- DisconnectCauseUtil.toTelecomDisconnectCause(
- DisconnectCause.ERROR_UNSPECIFIED, -1,
- EMPTY_STRING, PHONE_ID, null, config);
+ assertFalse(DisconnectCauseUtil.doesCarrierClassifyDisconnectCauseAsBusyCause(-1, config));
+ }
- // CODE
- assertEquals(android.telecom.DisconnectCause.BUSY, tcCause.getCode());
- // LABEL
- safeAssertLabel(R.string.callFailed_userBusy, tcCause);
- // TONE
- assertEquals(TONE_SUP_BUSY, tcCause.getTone());
+ /**
+ * Ensure {@link DisconnectCauseUtil#doesCarrierClassifyDisconnectCauseAsBusyCause} returns
+ * FALSE is the passed in disconnect cause is NOT the busy tone array
+ */
+ @Test
+ public void testDoesCarrierClassifyDisconnectCauseAsBusyCause_ConfigHasBusyToneButNotMatch() {
+ assertFalse(DisconnectCauseUtil.doesCarrierClassifyDisconnectCauseAsBusyCause(-1,
+ getBundleWithBusyToneArray()));
+ }
+
+ /**
+ * Ensure {@link DisconnectCauseUtil#doesCarrierClassifyDisconnectCauseAsBusyCause} returns
+ * TRUE if the disconnect cause is defined in the busy tone array (by the Carrier)
+ */
+ @Test
+ public void testDoesCarrierClassifyDisconnectCauseAsBusyCause_ConfigHasBusyTone() {
+ assertTrue(DisconnectCauseUtil.doesCarrierClassifyDisconnectCauseAsBusyCause(
+ DisconnectCause.BUSY, getBundleWithBusyToneArray()));
+ }
+
+ private void assertBusyCauseWithTargetLabel(Integer targetLabel,
+ android.telecom.DisconnectCause disconnectCause) {
+ // CODE: Describes the cause of a disconnected call
+ assertEquals(android.telecom.DisconnectCause.BUSY, disconnectCause.getCode());
+ // LABEL: This is the label that the user sees
+ safeAssertLabel(targetLabel, disconnectCause);
+ // TONE: This is the DTMF tone being played to the user
+ assertEquals(TONE_SUP_BUSY, disconnectCause.getTone());
+ }
+
+ private PersistableBundle getBundleWithBusyToneArray() {
+ int[] carrierBusyArr = {DisconnectCause.BUSY};
+ PersistableBundle config = new PersistableBundle();
+
+ config.putIntArray(
+ CarrierConfigManager.KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
+ carrierBusyArr);
+ return config;
}
private void setSinglePhone() throws Exception {
diff --git a/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java b/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java
index a9207e6..b1572f1 100644
--- a/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java
+++ b/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java
@@ -27,7 +27,8 @@
import android.content.ComponentName;
import android.os.Looper;
import android.telecom.PhoneAccountHandle;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
diff --git a/tests/src/com/android/services/telephony/ImsConferenceTest.java b/tests/src/com/android/services/telephony/ImsConferenceTest.java
index e2a199b..ca16bc7 100644
--- a/tests/src/com/android/services/telephony/ImsConferenceTest.java
+++ b/tests/src/com/android/services/telephony/ImsConferenceTest.java
@@ -40,7 +40,8 @@
import android.telecom.StatusHints;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import com.android.ims.internal.ConferenceParticipant;
diff --git a/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java b/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java
index b7fe988..c7080b4 100644
--- a/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java
@@ -28,7 +28,8 @@
import android.telecom.Conference;
import android.telecom.Connection;
import android.telecom.PhoneAccount;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
index 4f9b879..e791d3c 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
@@ -16,6 +16,7 @@
package com.android.services.telephony;
+import static android.telecom.Connection.PROPERTY_WIFI;
import static android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE;
import static android.telephony.DisconnectCause.EMERGENCY_TEMP_FAILURE;
import static android.telephony.DisconnectCause.ERROR_UNSPECIFIED;
@@ -32,9 +33,11 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
+import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -43,6 +46,7 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
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;
@@ -57,10 +61,12 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.telecom.Conference;
import android.telecom.Conferenceable;
import android.telecom.ConnectionRequest;
import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.AccessNetworkConstants;
@@ -74,8 +80,10 @@
import android.telephony.TelephonyManager;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.util.ArrayMap;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.TelephonyTestBase;
@@ -97,6 +105,7 @@
import com.android.internal.telephony.emergency.EmergencyStateTracker;
import com.android.internal.telephony.emergency.RadioOnHelper;
import com.android.internal.telephony.emergency.RadioOnStateListener;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.gsm.SuppServiceNotification;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.satellite.SatelliteController;
@@ -104,6 +113,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -116,8 +126,10 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Unit tests for TelephonyConnectionService.
@@ -125,6 +137,35 @@
@RunWith(AndroidJUnit4.class)
public class TelephonyConnectionServiceTest extends TelephonyTestBase {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ private static final String NORMAL_ROUTED_EMERGENCY_NUMBER = "110";
+ private static final String EMERGENCY_ROUTED_EMERGENCY_NUMBER = "911";
+ private static final EmergencyNumber MOCK_NORMAL_NUMBER = new EmergencyNumber(
+ NORMAL_ROUTED_EMERGENCY_NUMBER,
+ "US" /* country */,
+ null /* mcc */,
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+ Collections.emptyList() /* categories */,
+ EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+ private static final EmergencyNumber MOCK_NORMAL_NUMBER_WITH_UNKNOWN_ROUTING =
+ new EmergencyNumber(
+ NORMAL_ROUTED_EMERGENCY_NUMBER,
+ "US" /* country */,
+ "455" /* mcc */,
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+ Collections.emptyList() /* categories */,
+ EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+ private static final EmergencyNumber MOCK_EMERGENCY_NUMBER = new EmergencyNumber(
+ EMERGENCY_ROUTED_EMERGENCY_NUMBER,
+ "US" /* country */,
+ null /* mcc */,
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+ Collections.emptyList() /* categories */,
+ EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+
/**
* Unlike {@link TestTelephonyConnection}, a bare minimal {@link TelephonyConnection} impl
* that does not try to configure anything.
@@ -174,8 +215,8 @@
private static final ComponentName TEST_COMPONENT_NAME = new ComponentName(
"com.android.phone.tests", TelephonyConnectionServiceTest.class.getName());
- private static final String TEST_ACCOUNT_ID1 = "id1";
- private static final String TEST_ACCOUNT_ID2 = "id2";
+ private static final String TEST_ACCOUNT_ID1 = "0"; // subid 0
+ private static final String TEST_ACCOUNT_ID2 = "1"; // subid 1
private static final PhoneAccountHandle PHONE_ACCOUNT_HANDLE_1 = new PhoneAccountHandle(
TEST_COMPONENT_NAME, TEST_ACCOUNT_ID1);
private static final PhoneAccountHandle PHONE_ACCOUNT_HANDLE_2 = new PhoneAccountHandle(
@@ -263,12 +304,12 @@
mTestConnectionService, mEmergencyStateTracker);
replaceInstance(TelephonyConnectionService.class, "mSatelliteSOSMessageRecommender",
mTestConnectionService, mSatelliteSOSMessageRecommender);
- doNothing().when(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), any());
+ doNothing().when(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
doNothing().when(mSatelliteSOSMessageRecommender).onEmergencyCallConnectionStateChanged(
anyString(), anyInt());
doReturn(CompletableFuture.completedFuture(NOT_DISCONNECTED))
.when(mEmergencyStateTracker)
- .startEmergencyCall(any(), anyString(), eq(false));
+ .startEmergencyCall(any(), any(), eq(false));
replaceInstance(TelephonyConnectionService.class,
"mDomainSelectionMainExecutor", mTestConnectionService, getExecutor());
doReturn(false).when(mDomainSelectionResolver).isDomainSelectionSupported();
@@ -277,10 +318,17 @@
replaceInstance(TelephonyConnectionService.class,
"mSatelliteController", mTestConnectionService, mSatelliteController);
mBinderStub = (IConnectionService.Stub) mTestConnectionService.onBind(null);
+ mSetFlagsRule.disableFlags(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG);
+ mSetFlagsRule.enableFlags(Flags.FLAG_DO_NOT_OVERRIDE_PRECISE_LABEL);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CALL_EXTRA_FOR_NON_HOLD_SUPPORTED_CARRIERS);
}
@After
public void tearDown() throws Exception {
+ if (mTestConnectionService != null
+ && mTestConnectionService.getEmergencyConnection() != null) {
+ mTestConnectionService.onLocalHangup(mTestConnectionService.getEmergencyConnection());
+ }
mTestConnectionService = null;
super.tearDown();
}
@@ -1293,7 +1341,83 @@
// This shouldn't happen
fail();
}
- verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), any());
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
+ }
+
+ /**
+ * Test that the TelephonyConnectionService successfully dials an outgoing normal routed
+ * emergency call on an in-service sim.
+ */
+ @Test
+ @SmallTest
+ public void testCreateOutgoingConnectionForNormalRoutedEmergencyCall()
+ throws CallStateException {
+ // A whole load of annoying mocks to set up to test this scenario.
+ // We'll purposely try to start the call on the limited service phone.
+ ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
+ .setAccountHandle(PHONE_ACCOUNT_HANDLE_1)
+ .setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, NORMAL_ROUTED_EMERGENCY_NUMBER,
+ null))
+ .build();
+
+ // First phone is in limited service.
+ Phone testPhone0 = makeTestPhone(0 /*phoneId*/, ServiceState.STATE_EMERGENCY_ONLY,
+ true /*isEmergencyOnly*/);
+ doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_LTE).when(testPhone0)
+ .getImsRegistrationTech();
+ doReturn(0).when(testPhone0).getSubId();
+ setupMockEmergencyNumbers(testPhone0, List.of(MOCK_NORMAL_NUMBER,
+ MOCK_NORMAL_NUMBER_WITH_UNKNOWN_ROUTING, MOCK_EMERGENCY_NUMBER));
+
+ // Second phone is in full service; this is ultimately the one we want to pick.
+ Phone testPhone1 = makeTestPhone(1 /*phoneId*/, ServiceState.STATE_IN_SERVICE,
+ false /*isEmergencyOnly*/);
+ doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_LTE).when(testPhone1)
+ .getImsRegistrationTech();
+ doReturn(1).when(testPhone1).getSubId();
+ setupMockEmergencyNumbers(testPhone1, List.of(MOCK_NORMAL_NUMBER,
+ MOCK_NORMAL_NUMBER_WITH_UNKNOWN_ROUTING, MOCK_EMERGENCY_NUMBER));
+
+ // Make sure both phones are going to prefer in service for normal routed ecalls.
+ doReturn(true).when(testPhone0).shouldPreferInServiceSimForNormalRoutedEmergencyCall();
+ doReturn(true).when(testPhone1).shouldPreferInServiceSimForNormalRoutedEmergencyCall();
+
+ // A whole load of other stuff that needs to be setup for this to work.
+ doReturn(GSM_PHONE).when(testPhone0).getPhoneType();
+ doReturn(GSM_PHONE).when(testPhone1).getPhoneType();
+ List<Phone> phones = new ArrayList<>(2);
+ doReturn(true).when(testPhone0).isRadioOn();
+ doReturn(true).when(testPhone1).isRadioOn();
+ phones.add(testPhone0);
+ phones.add(testPhone1);
+ setPhones(phones);
+ doReturn(0).when(mPhoneUtilsProxy).getSubIdForPhoneAccountHandle(
+ eq(PHONE_ACCOUNT_HANDLE_1));
+ doReturn(1).when(mPhoneUtilsProxy).getSubIdForPhoneAccountHandle(
+ eq(PHONE_ACCOUNT_HANDLE_2));
+ setupHandleToPhoneMap(PHONE_ACCOUNT_HANDLE_1, testPhone0);
+ setupHandleToPhoneMap(PHONE_ACCOUNT_HANDLE_2, testPhone1);
+ setupDeviceConfig(testPhone0, testPhone1, 0);
+ doReturn(true).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(
+ eq(NORMAL_ROUTED_EMERGENCY_NUMBER));
+ HashMap<Integer, List<EmergencyNumber>> emergencyNumbers = new HashMap<>(1);
+ List<EmergencyNumber> numbers = new ArrayList<>();
+ numbers.add(MOCK_EMERGENCY_NUMBER);
+ numbers.add(MOCK_NORMAL_NUMBER);
+ numbers.add(MOCK_NORMAL_NUMBER_WITH_UNKNOWN_ROUTING);
+ emergencyNumbers.put(0 /*subId*/, numbers);
+ doReturn(emergencyNumbers).when(mTelephonyManagerProxy).getCurrentEmergencyNumberList();
+ doReturn(2).when(mTelephonyManagerProxy).getPhoneCount();
+
+ // All of that for... this.
+ mConnection = mTestConnectionService.onCreateOutgoingConnection(
+ PHONE_ACCOUNT_HANDLE_1, connectionRequest);
+ assertNotNull("test connection was not set up correctly.", mConnection);
+
+ // Lets make sure we DID try to place the call on phone 1, which is the in service phone.
+ verify(testPhone1).dial(anyString(), any(DialArgs.class), any(Consumer.class));
+ // And make sure we DID NOT try to place the call on phone 0, which is in limited service.
+ verify(testPhone0, never()).dial(anyString(), any(DialArgs.class), any(Consumer.class));
}
/**
@@ -1329,7 +1453,107 @@
// This shouldn't happen
fail();
}
- verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), any());
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
+ }
+
+ /**
+ * Test that the TelephonyConnectionService successfully turns radio on before placing the
+ * call when radio off because bluetooth on and wifi calling is not enabled
+ */
+ @Test
+ @SmallTest
+ public void testCreateOutgoingCall_turnOnRadio_bluetoothOn() {
+ doReturn(true).when(mDeviceState).isRadioPowerDownAllowedOnBluetooth(any());
+ doReturn(PhoneConstants.CELL_ON_FLAG).when(mDeviceState).getCellOnStatus(any());
+ Phone testPhone0 = makeTestPhone(0 /*phoneId*/, ServiceState.STATE_POWER_OFF,
+ false /*isEmergencyOnly*/);
+ Phone testPhone1 = makeTestPhone(1 /*phoneId*/, ServiceState.STATE_POWER_OFF,
+ false /*isEmergencyOnly*/);
+ doReturn(false).when(testPhone0).isRadioOn();
+ doReturn(false).when(testPhone0).isWifiCallingEnabled();
+ doReturn(false).when(testPhone1).isRadioOn();
+ doReturn(false).when(testPhone1).isWifiCallingEnabled();
+ List<Phone> phones = new ArrayList<>(2);
+ phones.add(testPhone0);
+ phones.add(testPhone1);
+ setPhones(phones);
+ setupHandleToPhoneMap(PHONE_ACCOUNT_HANDLE_1, testPhone0);
+ ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
+ .setAccountHandle(PHONE_ACCOUNT_HANDLE_1)
+ .setAddress(TEST_ADDRESS)
+ .build();
+ mConnection = mTestConnectionService.onCreateOutgoingConnection(
+ PHONE_ACCOUNT_HANDLE_1, connectionRequest);
+
+ verify(mRadioOnHelper).triggerRadioOnAndListen(any(), eq(false),
+ eq(testPhone0), eq(false), eq(0));
+ }
+
+ /**
+ * Test that the TelephonyConnectionService successfully turns radio on before placing the
+ * call when phone is null, radio off because bluetooth on and wifi calling is not enabled
+ */
+ @Test
+ @SmallTest
+ public void testCreateOutgoingCall_forWearWatch_whenPhoneIsNull() {
+ doReturn(-1).when(mPhoneUtilsProxy).getSubIdForPhoneAccountHandle(any());
+ doReturn(true).when(mDeviceState).isRadioPowerDownAllowedOnBluetooth(any());
+ doReturn(PhoneConstants.CELL_ON_FLAG).when(mDeviceState).getCellOnStatus(any());
+ Phone testPhone0 = makeTestPhone(0 /*phoneId*/, ServiceState.STATE_POWER_OFF,
+ false /*isEmergencyOnly*/);
+ Phone testPhone1 = makeTestPhone(1 /*phoneId*/, ServiceState.STATE_POWER_OFF,
+ false /*isEmergencyOnly*/);
+ doReturn(false).when(testPhone0).isRadioOn();
+ doReturn(false).when(testPhone0).isWifiCallingEnabled();
+ doReturn(false).when(testPhone1).isRadioOn();
+ doReturn(false).when(testPhone1).isWifiCallingEnabled();
+ List<Phone> phones = new ArrayList<>(2);
+ phones.add(testPhone0);
+ phones.add(testPhone1);
+ setPhones(phones);
+ setupHandleToPhoneMap(PHONE_ACCOUNT_HANDLE_1, testPhone0);
+ ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
+ .setAccountHandle(PHONE_ACCOUNT_HANDLE_1)
+ .setAddress(TEST_ADDRESS)
+ .build();
+ mConnection = mTestConnectionService.onCreateOutgoingConnection(
+ PHONE_ACCOUNT_HANDLE_1, connectionRequest);
+
+ verify(mRadioOnHelper).triggerRadioOnAndListen(any(), eq(false),
+ eq(testPhone0), eq(false), eq(0));
+ }
+
+ /**
+ * Test that the TelephonyConnectionService will not turns radio on before placing the
+ * call when radio off because bluetooth on and wifi calling is enabled
+ */
+ @Test
+ @SmallTest
+ public void testCreateOutgoingCall_notTurnOnRadio_bluetoothOnWifiCallingEnabled() {
+ doReturn(true).when(mDeviceState).isRadioPowerDownAllowedOnBluetooth(any());
+ doReturn(PhoneConstants.CELL_ON_FLAG).when(mDeviceState).getCellOnStatus(any());
+ Phone testPhone0 = makeTestPhone(0 /*phoneId*/, ServiceState.STATE_POWER_OFF,
+ false /*isEmergencyOnly*/);
+ Phone testPhone1 = makeTestPhone(1 /*phoneId*/, ServiceState.STATE_POWER_OFF,
+ false /*isEmergencyOnly*/);
+ doReturn(false).when(testPhone0).isRadioOn();
+ doReturn(true).when(testPhone0).isWifiCallingEnabled();
+ doReturn(false).when(testPhone1).isRadioOn();
+ doReturn(true).when(testPhone1).isWifiCallingEnabled();
+ List<Phone> phones = new ArrayList<>(2);
+ phones.add(testPhone0);
+ phones.add(testPhone1);
+ setPhones(phones);
+ setupHandleToPhoneMap(PHONE_ACCOUNT_HANDLE_1, testPhone0);
+ ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
+ .setAccountHandle(PHONE_ACCOUNT_HANDLE_1)
+ .setAddress(TEST_ADDRESS)
+ .build();
+ mConnection = mTestConnectionService.onCreateOutgoingConnection(
+ PHONE_ACCOUNT_HANDLE_1, connectionRequest);
+
+ verify(mRadioOnHelper, times(0)).triggerRadioOnAndListen(any(),
+ eq(true), eq(testPhone0), eq(false), eq(0));
}
/**
@@ -1598,7 +1822,7 @@
SimpleTelephonyConnection tc1 = createTestConnection(SUB1_HANDLE, 0, false);
tcs.add(tc1);
TelephonyConnectionService.maybeDisconnectCallsOnOtherSubs(
- tcs, SUB1_HANDLE, mTelephonyManagerProxy);
+ tcs, SUB1_HANDLE, false, mTelephonyManagerProxy);
// Would've preferred to use mockito, but can't mock out TelephonyConnection/Connection
// easily.
assertFalse(tc1.wasDisconnected);
@@ -1611,7 +1835,7 @@
SimpleTelephonyConnection tc1 = createTestConnection(SUB1_HANDLE, 0, true);
tcs.add(tc1);
TelephonyConnectionService.maybeDisconnectCallsOnOtherSubs(
- tcs, SUB2_HANDLE, mTelephonyManagerProxy);
+ tcs, SUB2_HANDLE, false, mTelephonyManagerProxy);
// Other call is an emergency call, so don't disconnect it.
assertFalse(tc1.wasDisconnected);
}
@@ -1624,7 +1848,7 @@
android.telecom.Connection.PROPERTY_IS_EXTERNAL_CALL, false);
tcs.add(tc1);
TelephonyConnectionService.maybeDisconnectCallsOnOtherSubs(
- tcs, SUB2_HANDLE, mTelephonyManagerProxy);
+ tcs, SUB2_HANDLE, false, mTelephonyManagerProxy);
// Other call is an external call, so don't disconnect it.
assertFalse(tc1.wasDisconnected);
}
@@ -1636,7 +1860,7 @@
SimpleTelephonyConnection tc1 = createTestConnection(SUB1_HANDLE, 0, false);
tcs.add(tc1);
TelephonyConnectionService.maybeDisconnectCallsOnOtherSubs(
- tcs, SUB2_HANDLE, mTelephonyManagerProxy);
+ tcs, SUB2_HANDLE, false, mTelephonyManagerProxy);
assertTrue(tc1.wasDisconnected);
}
@@ -1650,14 +1874,14 @@
tcs.add(tc1);
tcs.add(tc2);
TelephonyConnectionService.maybeDisconnectCallsOnOtherSubs(
- tcs, SUB2_HANDLE, mTelephonyManagerProxy);
+ tcs, SUB2_HANDLE, false, mTelephonyManagerProxy);
assertTrue(tc1.wasDisconnected);
assertTrue(tc2.wasDisconnected);
}
/**
* Verifies that DSDA or virtual DSDA-enabled devices can support active non-emergency calls on
- * separate subs.
+ * separate subs, when the extra EXTRA_ANSWERING_DROPS_FG_CALL is not set on the incoming call.
*/
@Test
@SmallTest
@@ -1668,10 +1892,26 @@
SimpleTelephonyConnection tc1 = createTestConnection(SUB1_HANDLE, 0, false);
tcs.add(tc1);
TelephonyConnectionService.maybeDisconnectCallsOnOtherSubs(
- tcs, SUB2_HANDLE, mTelephonyManagerProxy);
+ tcs, SUB2_HANDLE, false, mTelephonyManagerProxy);
assertFalse(tc1.wasDisconnected);
}
+ /**
+ * Verifies that DSDA or virtual DSDA-enabled devices will disconnect the existing call when the
+ * call extra EXTRA_ANSWERING_DROPS_FG_CALL is set on the incoming call on a different sub.
+ */
+ @Test
+ @SmallTest
+ public void testDisconnectDifferentSubForVirtualDsdaDevice_ifCallExtraSet() {
+ when(mTelephonyManagerProxy.isConcurrentCallsPossible()).thenReturn(true);
+
+ ArrayList<android.telecom.Connection> tcs = new ArrayList<>();
+ SimpleTelephonyConnection tc1 = createTestConnection(SUB1_HANDLE, 0, false);
+ tcs.add(tc1);
+ TelephonyConnectionService.maybeDisconnectCallsOnOtherSubs(
+ tcs, SUB2_HANDLE, true, mTelephonyManagerProxy);
+ assertTrue(tc1.wasDisconnected);
+ }
/**
* For calls on the same sub, the Dialer implements the 'swap' functionality to perform hold and
@@ -1734,8 +1974,8 @@
}
/**
- * For DSDA devices, placing an outgoing call on a 2nd sub will hold the existing connection on
- * the first sub.
+ * For DSDA devices, placing an outgoing call on a 2nd sub will hold the existing ACTIVE
+ * connection on the first sub.
*/
@Test
@SmallTest
@@ -1744,13 +1984,55 @@
ArrayList<android.telecom.Connection> tcs = new ArrayList<>();
SimpleTelephonyConnection tc1 = createTestConnection(SUB1_HANDLE, 0, false);
+ tc1.setTelephonyConnectionActive();
tcs.add(tc1);
+
Conferenceable c = TelephonyConnectionService.maybeHoldCallsOnOtherSubs(
tcs, new ArrayList<>(), SUB2_HANDLE, mTelephonyManagerProxy);
assertTrue(c.equals(tc1));
assertTrue(tc1.wasHeld);
}
+ /**
+ * For DSDA devices with AP domain selection service enabled, placing an outgoing call
+ * on a 2nd sub will hold the existing ACTIVE connection on the first sub.
+ */
+ @Test
+ @SmallTest
+ public void testHoldOnOtherSubForVirtualDsdaDeviceWithDomainSelectionEnabled() {
+ when(mTelephonyManagerProxy.isConcurrentCallsPossible()).thenReturn(true);
+ doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported();
+
+ ArrayList<android.telecom.Connection> tcs = new ArrayList<>();
+ SimpleTelephonyConnection tc1 = createTestConnection(SUB1_HANDLE, 0, false);
+ tc1.setTelephonyConnectionActive();
+ tcs.add(tc1);
+
+ Conferenceable c = TelephonyConnectionService.maybeHoldCallsOnOtherSubs(
+ tcs, new ArrayList<>(), SUB2_HANDLE, mTelephonyManagerProxy);
+ assertTrue(c.equals(tc1));
+ assertTrue(tc1.wasHeld);
+ }
+
+ /**
+ * For DSDA devices, if the existing connection was already held, placing an outgoing call on a
+ * 2nd sub will not attempt to hold the existing connection on the first sub.
+ */
+ @Test
+ @SmallTest
+ public void testNoHold_ifExistingConnectionAlreadyHeld_ForVirtualDsdaDevice() {
+ when(mTelephonyManagerProxy.isConcurrentCallsPossible()).thenReturn(true);
+
+ ArrayList<android.telecom.Connection> tcs = new ArrayList<>();
+ SimpleTelephonyConnection tc1 = createTestConnection(SUB1_HANDLE, 0, false);
+ tc1.setTelephonyConnectionOnHold();
+ tcs.add(tc1);
+
+ Conferenceable c = TelephonyConnectionService.maybeHoldCallsOnOtherSubs(
+ tcs, new ArrayList<>(), SUB2_HANDLE, mTelephonyManagerProxy);
+ assertNull(c);
+ }
+
// For 'Virtual DSDA' devices, if there is an existing call on sub1, an outgoing call on sub2
// will place the sub1 call on hold.
@Test
@@ -1885,13 +2167,22 @@
createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+ ArgumentCaptor<android.telecom.Connection> connectionCaptor =
+ ArgumentCaptor.forClass(android.telecom.Connection.class);
+
verify(mDomainSelectionResolver)
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
verify(mEmergencyStateTracker)
- .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
- verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
+ .startEmergencyCall(eq(mPhone0), connectionCaptor.capture(), eq(false));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
+ android.telecom.Connection tc = connectionCaptor.getValue();
+
+ assertNotNull(tc);
+ assertEquals(TELECOM_CALL_ID1, tc.getTelecomCallId());
+ assertEquals(mTestConnectionService.getEmergencyConnection(), tc);
+
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
@@ -1915,13 +2206,22 @@
createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+ ArgumentCaptor<android.telecom.Connection> connectionCaptor =
+ ArgumentCaptor.forClass(android.telecom.Connection.class);
+
verify(mDomainSelectionResolver)
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
verify(mEmergencyStateTracker)
- .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
- verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
+ .startEmergencyCall(eq(mPhone0), connectionCaptor.capture(), eq(false));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
+ android.telecom.Connection tc = connectionCaptor.getValue();
+
+ assertNotNull(tc);
+ assertEquals(TELECOM_CALL_ID1, tc.getTelecomCallId());
+ assertEquals(mTestConnectionService.getEmergencyConnection(), tc);
+
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
@@ -1948,14 +2248,23 @@
createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+ ArgumentCaptor<android.telecom.Connection> connectionCaptor =
+ ArgumentCaptor.forClass(android.telecom.Connection.class);
+
verify(mEmergencyStateTracker, times(1))
- .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
- verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
+ .startEmergencyCall(eq(mPhone0), connectionCaptor.capture(), eq(false));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
verify(mDomainSelectionResolver, times(0))
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
verify(mEmergencyCallDomainSelectionConnection, times(0))
.createEmergencyConnection(any(), any());
+ android.telecom.Connection tc = connectionCaptor.getValue();
+
+ assertNotNull(tc);
+ assertEquals(TELECOM_CALL_ID1, tc.getTelecomCallId());
+ assertEquals(mTestConnectionService.getEmergencyConnection(), tc);
+
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
@@ -1977,13 +2286,13 @@
TestTelephonyConnection c = setupForReDialForDomainSelection(
mPhone0, selectedDomain, preciseDisconnectCause, disconnectCause, true);
- assertTrue(mTestConnectionService.maybeReselectDomain(c, preciseDisconnectCause, null));
+ assertTrue(mTestConnectionService.maybeReselectDomain(c, null, true,
+ android.telephony.DisconnectCause.NOT_VALID));
verify(mEmergencyCallDomainSelectionConnection).reselectDomain(any());
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
- Connection nc = Mockito.mock(Connection.class);
- doReturn(nc).when(mPhone0).dial(anyString(), any(), any());
+ doReturn(mInternalConnection).when(mPhone0).dial(anyString(), any(), any());
verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
DialArgs dialArgs = argsCaptor.getValue();
@@ -2005,13 +2314,137 @@
TestTelephonyConnection c = setupForReDialForDomainSelection(
mPhone0, selectedDomain, preciseDisconnectCause, disconnectCause, true);
- assertTrue(mTestConnectionService.maybeReselectDomain(c, preciseDisconnectCause, null));
+ assertTrue(mTestConnectionService.maybeReselectDomain(c, null, true,
+ android.telephony.DisconnectCause.NOT_VALID));
verify(mEmergencyCallDomainSelectionConnection).reselectDomain(any());
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
- Connection nc = Mockito.mock(Connection.class);
- doReturn(nc).when(mPhone0).dial(anyString(), any(), any());
+ doReturn(mInternalConnection).when(mPhone0).dial(anyString(), any(), any());
+
+ verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
+ DialArgs dialArgs = argsCaptor.getValue();
+ assertNotNull("DialArgs param is null", dialArgs);
+ assertNotNull("intentExtras is null", dialArgs.intentExtras);
+ assertTrue(dialArgs.intentExtras.containsKey(PhoneConstants.EXTRA_DIAL_DOMAIN));
+ assertEquals(selectedDomain,
+ dialArgs.intentExtras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN, -1));
+ }
+
+ @Test
+ public void testDomainSelectionRedialFailedWithException() throws Exception {
+ setupForCallTest();
+
+ int preciseDisconnectCause = com.android.internal.telephony.CallFailCause.ERROR_UNSPECIFIED;
+ int disconnectCause = android.telephony.DisconnectCause.ERROR_UNSPECIFIED;
+ int selectedDomain = DOMAIN_CS;
+
+ TestTelephonyConnection c = setupForReDialForDomainSelection(
+ mPhone0, selectedDomain, preciseDisconnectCause, disconnectCause, true);
+
+ CallStateException cse = new CallStateException(CallStateException.ERROR_CALLING_DISABLED,
+ "Calling disabled via ro.telephony.disable-call property");
+ doThrow(cse).when(mPhone0).dial(anyString(), any(), any());
+
+ assertTrue(mTestConnectionService.maybeReselectDomain(c, null, true,
+ android.telephony.DisconnectCause.NOT_VALID));
+ verify(mEmergencyCallDomainSelectionConnection).reselectDomain(any());
+ verify(mEmergencyCallDomainSelectionConnection).cancelSelection();
+ verify(mEmergencyStateTracker).endCall(any());
+ }
+
+ @Test
+ public void testDomainSelectionRejectIncoming() throws Exception {
+ setupForCallTest();
+
+ int selectedDomain = DOMAIN_CS;
+
+ setupForDialForDomainSelection(mPhone0, selectedDomain, true);
+
+ doReturn(mInternalConnection2).when(mCall).getLatestConnection();
+ doReturn(true).when(mCall).isRinging();
+ doReturn(mCall).when(mPhone0).getRingingCall();
+
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
+ TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+
+ ArgumentCaptor<android.telecom.Connection> connectionCaptor =
+ ArgumentCaptor.forClass(android.telecom.Connection.class);
+
+ verify(mDomainSelectionResolver)
+ .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
+ verify(mEmergencyStateTracker)
+ .startEmergencyCall(eq(mPhone0), connectionCaptor.capture(), eq(false));
+ verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
+
+ android.telecom.Connection tc = connectionCaptor.getValue();
+
+ assertNotNull(tc);
+ assertEquals(TELECOM_CALL_ID1, tc.getTelecomCallId());
+ assertEquals(mTestConnectionService.getEmergencyConnection(), tc);
+
+ ArgumentCaptor<Connection.Listener> listenerCaptor =
+ ArgumentCaptor.forClass(Connection.Listener.class);
+
+ verify(mInternalConnection2).addListener(listenerCaptor.capture());
+ verify(mCall).hangup();
+ verify(mPhone0, never()).dial(anyString(), any(), any());
+
+ Connection.Listener listener = listenerCaptor.getValue();
+
+ assertNotNull(listener);
+
+ listener.onDisconnect(0);
+
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
+
+ ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
+
+ verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
+
+ DialArgs dialArgs = argsCaptor.getValue();
+
+ assertNotNull("DialArgs param is null", dialArgs);
+ assertNotNull("intentExtras is null", dialArgs.intentExtras);
+ assertTrue(dialArgs.intentExtras.containsKey(PhoneConstants.EXTRA_DIAL_DOMAIN));
+ assertEquals(selectedDomain,
+ dialArgs.intentExtras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN, -1));
+ }
+
+ @Test
+ public void testDomainSelectionRedialRejectIncoming() throws Exception {
+ setupForCallTest();
+
+ int preciseDisconnectCause = com.android.internal.telephony.CallFailCause.ERROR_UNSPECIFIED;
+ int disconnectCause = android.telephony.DisconnectCause.ERROR_UNSPECIFIED;
+ int selectedDomain = DOMAIN_CS;
+
+ TestTelephonyConnection c = setupForReDialForDomainSelection(
+ mPhone0, selectedDomain, preciseDisconnectCause, disconnectCause, true);
+
+ doReturn(mInternalConnection2).when(mCall).getLatestConnection();
+ doReturn(true).when(mCall).isRinging();
+ doReturn(mCall).when(mPhone0).getRingingCall();
+
+ assertTrue(mTestConnectionService.maybeReselectDomain(c, null, true,
+ android.telephony.DisconnectCause.NOT_VALID));
+ verify(mEmergencyCallDomainSelectionConnection).reselectDomain(any());
+
+ ArgumentCaptor<Connection.Listener> listenerCaptor =
+ ArgumentCaptor.forClass(Connection.Listener.class);
+
+ verify(mInternalConnection2).addListener(listenerCaptor.capture());
+ verify(mCall).hangup();
+ verify(mPhone0, never()).dial(anyString(), any(), any());
+
+ Connection.Listener listener = listenerCaptor.getValue();
+
+ assertNotNull(listener);
+
+ listener.onDisconnect(0);
+
+ ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
DialArgs dialArgs = argsCaptor.getValue();
@@ -2036,6 +2469,8 @@
setupForDialForDomainSelection(mPhone0, selectedDomain, false);
doReturn(true).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(anyString());
doReturn(emergencyNumber).when(mEmergencyNumberTracker).getEmergencyNumber(anyString());
+ doReturn(Arrays.asList(emergencyNumber)).when(mEmergencyNumberTracker).getEmergencyNumbers(
+ anyString());
mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
@@ -2044,7 +2479,98 @@
verify(mDomainSelectionResolver)
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
verify(mNormalCallDomainSelectionConnection).createNormalConnection(any(), any());
- verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
+
+ ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
+
+ verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
+ DialArgs dialArgs = argsCaptor.getValue();
+ assertNotNull("DialArgs param is null", dialArgs);
+ assertNotNull("intentExtras is null", dialArgs.intentExtras);
+ assertTrue(dialArgs.intentExtras.containsKey(PhoneConstants.EXTRA_DIAL_DOMAIN));
+ assertEquals(
+ selectedDomain, dialArgs.intentExtras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN, -1));
+ }
+
+ @Test
+ public void testDomainSelectionDialedSimEmergencyNumberOnlyFalse() throws Exception {
+ setupForCallTest();
+
+ int selectedDomain = DOMAIN_PS;
+
+ EmergencyNumber emergencyNumber = new EmergencyNumber(TEST_EMERGENCY_NUMBER, "", "",
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+ Collections.emptyList(),
+ EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+
+ setupForDialForDomainSelection(mPhone0, selectedDomain, true);
+ doReturn(emergencyNumber).when(mEmergencyNumberTracker).getEmergencyNumber(anyString());
+ doReturn(Arrays.asList(emergencyNumber)).when(mEmergencyNumberTracker).getEmergencyNumbers(
+ anyString());
+ doReturn(false).when(mEmergencyNumberTracker).isEmergencyNumber(anyString());
+ getTestContext().getCarrierConfig(0 /*subId*/).putBoolean(
+ CarrierConfigManager.KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL, false);
+
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
+ TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+
+ ArgumentCaptor<android.telecom.Connection> connectionCaptor =
+ ArgumentCaptor.forClass(android.telecom.Connection.class);
+
+ verify(mDomainSelectionResolver)
+ .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
+ verify(mEmergencyStateTracker)
+ .startEmergencyCall(eq(mPhone0), connectionCaptor.capture(), eq(false));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
+ verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
+
+ android.telecom.Connection tc = connectionCaptor.getValue();
+
+ assertNotNull(tc);
+ assertEquals(TELECOM_CALL_ID1, tc.getTelecomCallId());
+ assertEquals(mTestConnectionService.getEmergencyConnection(), tc);
+
+ ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
+
+ verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
+ DialArgs dialArgs = argsCaptor.getValue();
+ assertNotNull("DialArgs param is null", dialArgs);
+ assertNotNull("intentExtras is null", dialArgs.intentExtras);
+ assertTrue(dialArgs.intentExtras.containsKey(PhoneConstants.EXTRA_DIAL_DOMAIN));
+ assertEquals(selectedDomain,
+ dialArgs.intentExtras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN, -1));
+ }
+
+ @Test
+ public void testDomainSelectionDialedSimEmergencyNumberOnlyTrue() throws Exception {
+ setupForCallTest();
+ int selectedDomain = DOMAIN_PS;
+
+ EmergencyNumber emergencyNumber = new EmergencyNumber(TEST_EMERGENCY_NUMBER, "", "",
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+ Collections.emptyList(),
+ EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+
+ setupForDialForDomainSelection(mPhone0, selectedDomain, false);
+ doReturn(true).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(anyString());
+ doReturn(emergencyNumber).when(mEmergencyNumberTracker).getEmergencyNumber(anyString());
+ doReturn(Arrays.asList(emergencyNumber)).when(mEmergencyNumberTracker).getEmergencyNumbers(
+ anyString());
+ doReturn(false).when(mEmergencyNumberTracker).isEmergencyNumber(anyString());
+ getTestContext().getCarrierConfig(0 /*subId*/).putBoolean(
+ CarrierConfigManager.KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL, true);
+
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
+ TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+
+ verify(mDomainSelectionResolver)
+ .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
+ verify(mNormalCallDomainSelectionConnection).createNormalConnection(any(), any());
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
@@ -2060,126 +2586,83 @@
@Test
public void testDomainSelectionNormalRoutingEmergencyNumber_exitingApm_InService()
throws Exception {
- setupForCallTest();
-
- doReturn(false).when(mPhone0).isRadioOn();
- ServiceState ss = new ServiceState();
- ss.setState(ServiceState.STATE_POWER_OFF);
- when(mPhone0.getServiceState()).thenReturn(ss);
- when(mSST.getServiceState()).thenReturn(ss);
-
- setupForDialForDomainSelection(mPhone0, DOMAIN_CS, false);
-
- EmergencyNumber emergencyNumber = new EmergencyNumber(TEST_EMERGENCY_NUMBER, "", "",
- EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
- Collections.emptyList(),
- EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
- EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
-
- doReturn(true).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(anyString());
- doReturn(emergencyNumber).when(mEmergencyNumberTracker).getEmergencyNumber(anyString());
-
when(mDeviceState.isAirplaneModeOn(any())).thenReturn(true);
-
- mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
- createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
- TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+ Phone testPhone = setupConnectionServiceInApmForDomainSelection(true);
ArgumentCaptor<RadioOnStateListener.Callback> callback =
ArgumentCaptor.forClass(RadioOnStateListener.Callback.class);
verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true),
- any(), eq(false), eq(TIMEOUT_TO_DYNAMIC_ROUTING_MS));
+ eq(testPhone), eq(false), eq(TIMEOUT_TO_DYNAMIC_ROUTING_MS));
+
+ ServiceState ss = new ServiceState();
+ ss.setState(ServiceState.STATE_OUT_OF_SERVICE);
+ when(testPhone.getServiceState()).thenReturn(ss);
+ when(mSST.getServiceState()).thenReturn(ss);
assertFalse(callback.getValue()
- .isOkToCall(mPhone0, ServiceState.STATE_OUT_OF_SERVICE, false));
+ .isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
when(mSST.isRadioOn()).thenReturn(true);
assertFalse(callback.getValue()
- .isOkToCall(mPhone0, ServiceState.STATE_OUT_OF_SERVICE, false));
+ .isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
+
+ Phone otherPhone = makeTestPhone(1, ServiceState.STATE_OUT_OF_SERVICE, false);
+ when(otherPhone.getServiceState()).thenReturn(ss);
+
+ // Radio on is OK for other phone
+ assertTrue(callback.getValue()
+ .isOkToCall(otherPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
ss.setState(ServiceState.STATE_IN_SERVICE);
assertTrue(callback.getValue()
- .isOkToCall(mPhone0, ServiceState.STATE_IN_SERVICE, false));
+ .isOkToCall(testPhone, ServiceState.STATE_IN_SERVICE, false));
+
+ mConnection.setDisconnected(null);
}
@Test
public void testDomainSelectionNormalRoutingEmergencyNumber_exitingApm_Timeout()
throws Exception {
- setupForCallTest();
-
- doReturn(false).when(mPhone0).isRadioOn();
- ServiceState ss = new ServiceState();
- ss.setState(ServiceState.STATE_POWER_OFF);
- when(mPhone0.getServiceState()).thenReturn(ss);
- when(mSST.getServiceState()).thenReturn(ss);
-
- setupForDialForDomainSelection(mPhone0, DOMAIN_CS, false);
-
- EmergencyNumber emergencyNumber = new EmergencyNumber(TEST_EMERGENCY_NUMBER, "", "",
- EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
- Collections.emptyList(),
- EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
- EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
-
- doReturn(true).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(anyString());
- doReturn(emergencyNumber).when(mEmergencyNumberTracker).getEmergencyNumber(anyString());
-
when(mDeviceState.isAirplaneModeOn(any())).thenReturn(true);
-
- mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
- createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
- TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+ Phone testPhone = setupConnectionServiceInApmForDomainSelection(true);
ArgumentCaptor<RadioOnStateListener.Callback> callback =
ArgumentCaptor.forClass(RadioOnStateListener.Callback.class);
verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true),
- any(), eq(false), eq(TIMEOUT_TO_DYNAMIC_ROUTING_MS));
+ eq(testPhone), eq(false), eq(TIMEOUT_TO_DYNAMIC_ROUTING_MS));
+ ServiceState ss = new ServiceState();
+ ss.setState(ServiceState.STATE_OUT_OF_SERVICE);
+ when(testPhone.getServiceState()).thenReturn(ss);
+ when(mSST.getServiceState()).thenReturn(ss);
when(mSST.isRadioOn()).thenReturn(true);
assertFalse(callback.getValue()
- .isOkToCall(mPhone0, ServiceState.STATE_OUT_OF_SERVICE, false));
+ .isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
assertTrue(callback.getValue()
- .onTimeout(mPhone0, ServiceState.STATE_OUT_OF_SERVICE, false));
+ .onTimeout(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
+
+ mConnection.setDisconnected(null);
}
@Test
public void testDomainSelectionNormalRoutingEmergencyNumber_exitingApm_CombinedAttach()
throws Exception {
- setupForCallTest();
-
- doReturn(false).when(mPhone0).isRadioOn();
- ServiceState ss = new ServiceState();
- ss.setState(ServiceState.STATE_POWER_OFF);
- when(mPhone0.getServiceState()).thenReturn(ss);
- when(mSST.getServiceState()).thenReturn(ss);
-
- setupForDialForDomainSelection(mPhone0, DOMAIN_CS, false);
-
- EmergencyNumber emergencyNumber = new EmergencyNumber(TEST_EMERGENCY_NUMBER, "", "",
- EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
- Collections.emptyList(),
- EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
- EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
-
- doReturn(true).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(anyString());
- doReturn(emergencyNumber).when(mEmergencyNumberTracker).getEmergencyNumber(anyString());
-
when(mDeviceState.isAirplaneModeOn(any())).thenReturn(true);
-
- mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
- createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
- TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+ Phone testPhone = setupConnectionServiceInApmForDomainSelection(true);
ArgumentCaptor<RadioOnStateListener.Callback> callback =
ArgumentCaptor.forClass(RadioOnStateListener.Callback.class);
verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true),
- any(), eq(false), eq(TIMEOUT_TO_DYNAMIC_ROUTING_MS));
+ eq(testPhone), eq(false), eq(TIMEOUT_TO_DYNAMIC_ROUTING_MS));
- when(mSST.isRadioOn()).thenReturn(true);
+ ServiceState ss = new ServiceState();
ss.setState(ServiceState.STATE_IN_SERVICE);
+ when(testPhone.getServiceState()).thenReturn(ss);
+ when(mSST.getServiceState()).thenReturn(ss);
+ when(mSST.isRadioOn()).thenReturn(true);
DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(3)
.setLteAttachResultType(DataSpecificRegistrationInfo.LTE_ATTACH_TYPE_COMBINED)
@@ -2195,44 +2678,27 @@
ss.addNetworkRegistrationInfo(nri);
assertTrue(callback.getValue()
- .isOkToCall(mPhone0, ServiceState.STATE_IN_SERVICE, false));
+ .isOkToCall(testPhone, ServiceState.STATE_IN_SERVICE, false));
+
+ mConnection.setDisconnected(null);
}
@Test
public void testDomainSelectionNormalRoutingEmergencyNumber_exitingApm_PsOnly()
throws Exception {
- setupForCallTest();
-
- doReturn(false).when(mPhone0).isRadioOn();
- ServiceState ss = new ServiceState();
- ss.setState(ServiceState.STATE_POWER_OFF);
- when(mPhone0.getServiceState()).thenReturn(ss);
- when(mSST.getServiceState()).thenReturn(ss);
-
- setupForDialForDomainSelection(mPhone0, DOMAIN_CS, false);
-
- EmergencyNumber emergencyNumber = new EmergencyNumber(TEST_EMERGENCY_NUMBER, "", "",
- EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
- Collections.emptyList(),
- EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
- EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
-
- doReturn(true).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(anyString());
- doReturn(emergencyNumber).when(mEmergencyNumberTracker).getEmergencyNumber(anyString());
-
when(mDeviceState.isAirplaneModeOn(any())).thenReturn(true);
-
- mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
- createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
- TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+ Phone testPhone = setupConnectionServiceInApmForDomainSelection(true);
ArgumentCaptor<RadioOnStateListener.Callback> callback =
ArgumentCaptor.forClass(RadioOnStateListener.Callback.class);
verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true),
- any(), eq(false), eq(TIMEOUT_TO_DYNAMIC_ROUTING_MS));
+ eq(testPhone), eq(false), eq(TIMEOUT_TO_DYNAMIC_ROUTING_MS));
- when(mSST.isRadioOn()).thenReturn(true);
+ ServiceState ss = new ServiceState();
ss.setState(ServiceState.STATE_IN_SERVICE);
+ when(testPhone.getServiceState()).thenReturn(ss);
+ when(mSST.getServiceState()).thenReturn(ss);
+ when(mSST.isRadioOn()).thenReturn(true);
DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(3)
.build();
@@ -2247,46 +2713,29 @@
ss.addNetworkRegistrationInfo(nri);
assertFalse(callback.getValue()
- .isOkToCall(mPhone0, ServiceState.STATE_IN_SERVICE, false));
+ .isOkToCall(testPhone, ServiceState.STATE_IN_SERVICE, false));
assertTrue(callback.getValue()
- .onTimeout(mPhone0, ServiceState.STATE_IN_SERVICE, false));
+ .onTimeout(testPhone, ServiceState.STATE_IN_SERVICE, false));
+
+ mConnection.setDisconnected(null);
}
@Test
public void testDomainSelectionNormalRoutingEmergencyNumber_exitingApm_PsOnly_ImsRegistered()
throws Exception {
- setupForCallTest();
-
- doReturn(false).when(mPhone0).isRadioOn();
- ServiceState ss = new ServiceState();
- ss.setState(ServiceState.STATE_POWER_OFF);
- when(mPhone0.getServiceState()).thenReturn(ss);
- when(mSST.getServiceState()).thenReturn(ss);
-
- setupForDialForDomainSelection(mPhone0, DOMAIN_CS, false);
-
- EmergencyNumber emergencyNumber = new EmergencyNumber(TEST_EMERGENCY_NUMBER, "", "",
- EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
- Collections.emptyList(),
- EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
- EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
-
- doReturn(true).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(anyString());
- doReturn(emergencyNumber).when(mEmergencyNumberTracker).getEmergencyNumber(anyString());
-
when(mDeviceState.isAirplaneModeOn(any())).thenReturn(true);
-
- mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
- createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
- TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+ Phone testPhone = setupConnectionServiceInApmForDomainSelection(true);
ArgumentCaptor<RadioOnStateListener.Callback> callback =
ArgumentCaptor.forClass(RadioOnStateListener.Callback.class);
verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true),
- any(), eq(false), eq(TIMEOUT_TO_DYNAMIC_ROUTING_MS));
+ eq(testPhone), eq(false), eq(TIMEOUT_TO_DYNAMIC_ROUTING_MS));
- when(mSST.isRadioOn()).thenReturn(true);
+ ServiceState ss = new ServiceState();
ss.setState(ServiceState.STATE_IN_SERVICE);
+ when(testPhone.getServiceState()).thenReturn(ss);
+ when(mSST.getServiceState()).thenReturn(ss);
+ when(mSST.isRadioOn()).thenReturn(true);
DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(3)
.build();
@@ -2301,9 +2750,11 @@
ss.addNetworkRegistrationInfo(nri);
assertFalse(callback.getValue()
- .isOkToCall(mPhone0, ServiceState.STATE_IN_SERVICE, false));
+ .isOkToCall(testPhone, ServiceState.STATE_IN_SERVICE, false));
assertTrue(callback.getValue()
- .isOkToCall(mPhone0, ServiceState.STATE_IN_SERVICE, true));
+ .isOkToCall(testPhone, ServiceState.STATE_IN_SERVICE, true));
+
+ mConnection.setDisconnected(null);
}
@Test
@@ -2317,6 +2768,7 @@
setupForDialForDomainSelection(mPhone0, selectedDomain, true);
doReturn(mPhone0).when(mImsPhone).getDefaultPhone();
+ doReturn(mInternalConnection).when(mPhone0).dial(anyString(), any(), any());
TestTelephonyConnection c = setupForReDialForDomainSelection(
mImsPhone, selectedDomain, preciseDisconnectCause, disconnectCause, false);
@@ -2324,16 +2776,25 @@
c.setAddress(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED);
ImsReasonInfo reasonInfo = new ImsReasonInfo(CODE_SIP_ALTERNATE_EMERGENCY_CALL, 0, null);
- assertTrue(mTestConnectionService.maybeReselectDomain(c,
- preciseDisconnectCause, reasonInfo));
+ assertTrue(mTestConnectionService.maybeReselectDomain(c, reasonInfo, true,
+ android.telephony.DisconnectCause.NOT_VALID));
+
+ ArgumentCaptor<android.telecom.Connection> connectionCaptor =
+ ArgumentCaptor.forClass(android.telecom.Connection.class);
verify(mDomainSelectionResolver)
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
verify(mEmergencyStateTracker)
- .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
- verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
+ .startEmergencyCall(eq(mPhone0), connectionCaptor.capture(), eq(false));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
+ android.telecom.Connection tc = connectionCaptor.getValue();
+
+ assertNotNull(tc);
+ assertEquals(TELECOM_CALL_ID1, tc.getTelecomCallId());
+ assertEquals(mTestConnectionService.getEmergencyConnection(), tc);
+
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
@@ -2358,6 +2819,7 @@
setupForDialForDomainSelection(mPhone0, selectedDomain, true);
doReturn(mPhone0).when(mImsPhone).getDefaultPhone();
+ doReturn(mInternalConnection).when(mPhone0).dial(anyString(), any(), any());
TestTelephonyConnection c = setupForReDialForDomainSelection(
mImsPhone, selectedDomain, preciseDisconnectCause, disconnectCause, false);
@@ -2365,16 +2827,25 @@
c.setAddress(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED);
ImsReasonInfo reasonInfo = new ImsReasonInfo(CODE_SIP_ALTERNATE_EMERGENCY_CALL, 0, null);
- assertTrue(mTestConnectionService.maybeReselectDomain(c,
- preciseDisconnectCause, reasonInfo));
+ assertTrue(mTestConnectionService.maybeReselectDomain(c, reasonInfo, true,
+ android.telephony.DisconnectCause.NOT_VALID));
+
+ ArgumentCaptor<android.telecom.Connection> connectionCaptor =
+ ArgumentCaptor.forClass(android.telecom.Connection.class);
verify(mDomainSelectionResolver)
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
verify(mEmergencyStateTracker)
- .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
- verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
+ .startEmergencyCall(eq(mPhone0), connectionCaptor.capture(), eq(false));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
+ android.telecom.Connection tc = connectionCaptor.getValue();
+
+ assertNotNull(tc);
+ assertEquals(TELECOM_CALL_ID1, tc.getTelecomCallId());
+ assertEquals(mTestConnectionService.getEmergencyConnection(), tc);
+
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
@@ -2431,7 +2902,7 @@
DomainSelectionService.SelectionAttributes attr = attrCaptor.getValue();
- assertEquals(mPhone1.getPhoneId(), attr.getSlotId());
+ assertEquals(mPhone1.getPhoneId(), attr.getSlotIndex());
}
@Test
@@ -2477,7 +2948,7 @@
DomainSelectionService.SelectionAttributes attr = attrCaptor.getValue();
- assertEquals(mPhone1.getPhoneId(), attr.getSlotId());
+ assertEquals(mPhone1.getPhoneId(), attr.getSlotIndex());
}
@Test
@@ -2496,6 +2967,10 @@
createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+ android.telecom.Connection c = mTestConnectionService.getEmergencyConnection();
+
+ assertNotNull(c);
+
ArgumentCaptor<DomainSelectionConnection.DomainSelectionConnectionCallback> callbackCaptor =
ArgumentCaptor.forClass(
DomainSelectionConnection.DomainSelectionConnectionCallback.class);
@@ -2511,7 +2986,29 @@
callback.onSelectionTerminated(ERROR_UNSPECIFIED);
verify(mEmergencyCallDomainSelectionConnection).cancelSelection();
- verify(mEmergencyStateTracker).endCall(eq(TELECOM_CALL_ID1));
+ verify(mEmergencyStateTracker).endCall(eq(c));
+ }
+
+ @Test
+ public void testDomainSelectionDialFailedByException() throws Exception {
+ setupForCallTest();
+
+ int selectedDomain = DOMAIN_CS;
+
+ setupForDialForDomainSelection(mPhone0, selectedDomain, true);
+
+ CallStateException cse = new CallStateException(CallStateException.ERROR_CALLING_DISABLED,
+ "Calling disabled via ro.telephony.disable-call property");
+ doThrow(cse).when(mPhone0).dial(anyString(), any(), any());
+
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
+ TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+
+ verify(mEmergencyStateTracker)
+ .startEmergencyCall(any(), any(), anyBoolean());
+ verify(mEmergencyCallDomainSelectionConnection).cancelSelection();
+ verify(mEmergencyStateTracker).endCall(any());
}
@Test
@@ -2524,26 +3021,23 @@
CompletableFuture<Integer> future = new CompletableFuture<>();
doReturn(future).when(mEmergencyStateTracker)
- .startEmergencyCall(any(), anyString(), eq(false));
+ .startEmergencyCall(any(), any(), eq(false));
mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
verify(mEmergencyStateTracker)
- .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
-
- TelephonyConnection c = new TestTelephonyConnection();
- c.setTelecomCallId(TELECOM_CALL_ID1);
+ .startEmergencyCall(eq(mPhone0), any(), eq(false));
// dialing is canceled
- mTestConnectionService.onLocalHangup(c);
+ mTestConnectionService.onLocalHangup(mTestConnectionService.getEmergencyConnection());
// startEmergencyCall has completed
future.complete(NOT_DISCONNECTED);
// verify that createEmergencyConnection is discarded
- verify(mEmergencyCallDomainSelectionConnection, times(0))
+ verify(mEmergencyCallDomainSelectionConnection, never())
.createEmergencyConnection(any(), any());
}
@@ -2565,17 +3059,14 @@
verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
- TelephonyConnection c = new TestTelephonyConnection();
- c.setTelecomCallId(TELECOM_CALL_ID1);
-
// dialing is canceled
- mTestConnectionService.onLocalHangup(c);
+ mTestConnectionService.onLocalHangup(mTestConnectionService.getEmergencyConnection());
// domain selection has completed
future.complete(selectedDomain);
// verify that dialing is discarded
- verify(mPhone0, times(0)).dial(anyString(), any(), any());
+ verify(mPhone0, never()).dial(anyString(), any(), any());
}
@Test
@@ -2588,13 +3079,13 @@
TestTelephonyConnection c = setupForReDialForDomainSelection(
mPhone0, selectedDomain, preciseDisconnectCause, disconnectCause, true);
- c.setTelecomCallId(TELECOM_CALL_ID1);
CompletableFuture<Integer> future = new CompletableFuture<>();
doReturn(future).when(mEmergencyCallDomainSelectionConnection)
.reselectDomain(any());
- assertTrue(mTestConnectionService.maybeReselectDomain(c, preciseDisconnectCause, null));
+ assertTrue(mTestConnectionService.maybeReselectDomain(c, null, true,
+ android.telephony.DisconnectCause.NOT_VALID));
verify(mEmergencyCallDomainSelectionConnection).reselectDomain(any());
// dialing is canceled
@@ -2624,19 +3115,27 @@
mImsPhone, selectedDomain, preciseDisconnectCause, disconnectCause, false);
c.setEmergencyServiceCategory(eccCategory);
c.setAddress(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED);
- c.setTelecomCallId(TELECOM_CALL_ID1);
CompletableFuture<Integer> future = new CompletableFuture<>();
doReturn(future).when(mEmergencyStateTracker)
- .startEmergencyCall(any(), anyString(), eq(false));
+ .startEmergencyCall(any(), any(), eq(false));
ImsReasonInfo reasonInfo = new ImsReasonInfo(CODE_SIP_ALTERNATE_EMERGENCY_CALL, 0, null);
- assertTrue(mTestConnectionService.maybeReselectDomain(c,
- preciseDisconnectCause, reasonInfo));
+ assertTrue(mTestConnectionService.maybeReselectDomain(c, reasonInfo, true,
+ android.telephony.DisconnectCause.NOT_VALID));
+
+ ArgumentCaptor<android.telecom.Connection> connectionCaptor =
+ ArgumentCaptor.forClass(android.telecom.Connection.class);
verify(mEmergencyStateTracker)
- .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
- verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
+ .startEmergencyCall(eq(mPhone0), connectionCaptor.capture(), eq(false));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
+
+ android.telecom.Connection tc = connectionCaptor.getValue();
+
+ assertNotNull(tc);
+ assertEquals(TELECOM_CALL_ID1, tc.getTelecomCallId());
+ assertEquals(mTestConnectionService.getEmergencyConnection(), tc);
// dialing is canceled
mTestConnectionService.onLocalHangup(c);
@@ -2666,15 +3165,14 @@
mImsPhone, selectedDomain, preciseDisconnectCause, disconnectCause, false);
c.setEmergencyServiceCategory(eccCategory);
c.setAddress(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED);
- c.setTelecomCallId(TELECOM_CALL_ID1);
CompletableFuture<Integer> future = new CompletableFuture<>();
doReturn(future).when(mEmergencyCallDomainSelectionConnection)
.createEmergencyConnection(any(), any());
ImsReasonInfo reasonInfo = new ImsReasonInfo(CODE_SIP_ALTERNATE_EMERGENCY_CALL, 0, null);
- assertTrue(mTestConnectionService.maybeReselectDomain(c,
- preciseDisconnectCause, reasonInfo));
+ assertTrue(mTestConnectionService.maybeReselectDomain(c, reasonInfo, true,
+ android.telephony.DisconnectCause.NOT_VALID));
verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
@@ -2685,7 +3183,7 @@
future.complete(selectedDomain);
// verify that dialing is discarded
- verify(mPhone0, times(0)).dial(anyString(), any(), any());
+ verify(mPhone0, never()).dial(anyString(), any(), any());
}
@Test
@@ -2700,15 +3198,25 @@
createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+ ArgumentCaptor<android.telecom.Connection> connectionCaptor =
+ ArgumentCaptor.forClass(android.telecom.Connection.class);
+
verify(mDomainSelectionResolver)
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
verify(mEmergencyStateTracker)
- .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
- verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
+ .startEmergencyCall(eq(mPhone0), connectionCaptor.capture(), eq(false));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
verify(mPhone0).dial(anyString(), any(), any());
+ android.telecom.Connection tc = connectionCaptor.getValue();
+
+ assertNotNull(tc);
+ assertEquals(TELECOM_CALL_ID1, tc.getTelecomCallId());
+ assertEquals(mTestConnectionService.getEmergencyConnection(), tc);
+
TestTelephonyConnection c = new TestTelephonyConnection();
+ mTestConnectionService.setEmergencyConnection(c);
c.setTelecomCallId(TELECOM_CALL_ID1);
c.setIsImsConnection(true);
Connection orgConn = c.getOriginalConnection();
@@ -2722,10 +3230,10 @@
connectionListener.onOriginalConnectionConfigured(c);
verify(mEmergencyStateTracker, times(1)).onEmergencyCallDomainUpdated(
- eq(PhoneConstants.PHONE_TYPE_IMS), eq(TELECOM_CALL_ID1));
+ eq(PhoneConstants.PHONE_TYPE_IMS), eq(c));
verify(mEmergencyStateTracker, times(0)).onEmergencyCallStateChanged(
- any(), eq(TELECOM_CALL_ID1));
+ any(), eq(c));
verify(mSatelliteSOSMessageRecommender, times(0))
.onEmergencyCallConnectionStateChanged(eq(TELECOM_CALL_ID1), anyInt());
@@ -2736,7 +3244,7 @@
// ACTIVE sate is notified
verify(mEmergencyStateTracker, times(1)).onEmergencyCallStateChanged(
- eq(Call.State.ACTIVE), eq(TELECOM_CALL_ID1));
+ eq(Call.State.ACTIVE), eq(c));
verify(mSatelliteSOSMessageRecommender, times(1))
.onEmergencyCallConnectionStateChanged(eq(TELECOM_CALL_ID1),
eq(android.telecom.Connection.STATE_ACTIVE));
@@ -2749,7 +3257,7 @@
// state change not notified any more after CONNECTED once
verify(mEmergencyStateTracker, times(1)).onEmergencyCallStateChanged(
- any(), eq(TELECOM_CALL_ID1));
+ any(), eq(c));
verify(mSatelliteSOSMessageRecommender, times(1))
.onEmergencyCallConnectionStateChanged(eq(TELECOM_CALL_ID1), anyInt());
@@ -2761,7 +3269,7 @@
// state change not notified any more after CONNECTED once
verify(mEmergencyStateTracker, times(1)).onEmergencyCallStateChanged(
- any(), eq(TELECOM_CALL_ID1));
+ any(), eq(c));
verify(mSatelliteSOSMessageRecommender, times(1))
.onEmergencyCallConnectionStateChanged(eq(TELECOM_CALL_ID1), anyInt());
@@ -2773,7 +3281,7 @@
// domain change notified
verify(mEmergencyStateTracker, times(1)).onEmergencyCallDomainUpdated(
- eq(PhoneConstants.PHONE_TYPE_GSM), eq(TELECOM_CALL_ID1));
+ eq(PhoneConstants.PHONE_TYPE_GSM), eq(c));
// state change to DISCONNECTED
c.setDisconnected(null);
@@ -2783,12 +3291,67 @@
// state change not notified
verify(mEmergencyStateTracker, times(1)).onEmergencyCallStateChanged(
- any(), eq(TELECOM_CALL_ID1));
+ any(), eq(c));
verify(mSatelliteSOSMessageRecommender, times(1))
.onEmergencyCallConnectionStateChanged(eq(TELECOM_CALL_ID1), anyInt());
}
@Test
+ public void testDomainSelectionListenOriginalConnectionPropertiesChange() throws Exception {
+ setupForCallTest();
+
+ int selectedDomain = DOMAIN_PS;
+
+ setupForDialForDomainSelection(mPhone0, selectedDomain, true);
+
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
+ TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+
+ ArgumentCaptor<android.telecom.Connection> connectionCaptor =
+ ArgumentCaptor.forClass(android.telecom.Connection.class);
+
+ verify(mDomainSelectionResolver)
+ .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
+ verify(mEmergencyStateTracker)
+ .startEmergencyCall(eq(mPhone0), connectionCaptor.capture(), eq(false));
+ verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
+ verify(mPhone0).dial(anyString(), any(), any());
+
+ android.telecom.Connection tc = connectionCaptor.getValue();
+
+ assertNotNull(tc);
+ assertEquals(TELECOM_CALL_ID1, tc.getTelecomCallId());
+ assertEquals(mTestConnectionService.getEmergencyConnection(), tc);
+
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ mTestConnectionService.setEmergencyConnection(c);
+ c.setIsImsConnection(true);
+ Connection orgConn = c.getOriginalConnection();
+ doReturn(PhoneConstants.PHONE_TYPE_IMS).when(orgConn).getPhoneType();
+
+ TelephonyConnection.TelephonyConnectionListener connectionListener =
+ mTestConnectionService.getEmergencyConnectionListener();
+
+ doReturn(Call.State.DISCONNECTING).when(orgConn).getState();
+ connectionListener.onConnectionPropertiesChanged(c, PROPERTY_WIFI);
+
+ verify(mEmergencyStateTracker, times(0)).onEmergencyCallPropertiesChanged(
+ anyInt(), any());
+
+ doReturn(Call.State.ACTIVE).when(orgConn).getState();
+ connectionListener.onConnectionPropertiesChanged(c, PROPERTY_WIFI);
+
+ verify(mEmergencyStateTracker, times(1)).onEmergencyCallPropertiesChanged(
+ eq(PROPERTY_WIFI), eq(c));
+
+ connectionListener.onConnectionPropertiesChanged(c, 0);
+
+ verify(mEmergencyStateTracker, times(1)).onEmergencyCallPropertiesChanged(
+ eq(0), eq(c));
+ }
+
+ @Test
public void testDomainSelectionTempFailure() throws Exception {
setupForCallTest();
@@ -2803,7 +3366,8 @@
doReturn(new CompletableFuture()).when(mEmergencyCallDomainSelectionConnection)
.reselectDomain(any());
- assertTrue(mTestConnectionService.maybeReselectDomain(c, preciseDisconnectCause, null));
+ assertTrue(mTestConnectionService.maybeReselectDomain(c, null, true,
+ android.telephony.DisconnectCause.NOT_VALID));
verify(mEmergencyCallDomainSelectionConnection).reselectDomain(any());
}
@@ -2822,7 +3386,8 @@
doReturn(new CompletableFuture()).when(mEmergencyCallDomainSelectionConnection)
.reselectDomain(any());
- assertTrue(mTestConnectionService.maybeReselectDomain(c, preciseDisconnectCause, null));
+ assertTrue(mTestConnectionService.maybeReselectDomain(c, null, true,
+ android.telephony.DisconnectCause.NOT_VALID));
verify(mEmergencyCallDomainSelectionConnection).reselectDomain(any());
}
@@ -2850,7 +3415,7 @@
verify(mDomainSelectionResolver)
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
verify(mNormalCallDomainSelectionConnection).createNormalConnection(any(), any());
- verify(mSatelliteSOSMessageRecommender, never()).onEmergencyCallStarted(any(), any());
+ verify(mSatelliteSOSMessageRecommender, never()).onEmergencyCallStarted(any());
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
@@ -2875,7 +3440,7 @@
verify(mDomainSelectionResolver)
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
verify(mNormalCallDomainSelectionConnection).createNormalConnection(any(), any());
- verify(mSatelliteSOSMessageRecommender, never()).onEmergencyCallStarted(any(), any());
+ verify(mSatelliteSOSMessageRecommender, never()).onEmergencyCallStarted(any());
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
@@ -2892,6 +3457,7 @@
public void testNormalCallSatelliteEnabled() {
setupForCallTest();
doReturn(true).when(mSatelliteController).isSatelliteEnabled();
+
mConnection = mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
createConnectionRequest(PHONE_ACCOUNT_HANDLE_1, "1234", TELECOM_CALL_ID1));
DisconnectCause disconnectCause = mConnection.getDisconnectCause();
@@ -2899,6 +3465,275 @@
disconnectCause.getTelephonyDisconnectCause());
}
+ @Test
+ public void testNormalCallUsingNonTerrestrialNetwork_enableFlag() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG);
+
+ setupForCallTest();
+ // Call is not supported while using satellite
+ NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+ .setIsNonTerrestrialNetwork(true)
+ .setAvailableServices(List.of(NetworkRegistrationInfo.SERVICE_TYPE_DATA))
+ .build();
+ ServiceState ss = new ServiceState();
+ ss.addNetworkRegistrationInfo(nri);
+ when(mPhone0.getServiceState()).thenReturn(ss);
+ mConnection = mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1, "1234", TELECOM_CALL_ID1));
+ DisconnectCause disconnectCause = mConnection.getDisconnectCause();
+ assertEquals(android.telephony.DisconnectCause.SATELLITE_ENABLED,
+ disconnectCause.getTelephonyDisconnectCause());
+
+ // Call is supported while using satellite
+ nri = new NetworkRegistrationInfo.Builder()
+ .setIsNonTerrestrialNetwork(true)
+ .setAvailableServices(List.of(NetworkRegistrationInfo.SERVICE_TYPE_VOICE))
+ .build();
+ ss.addNetworkRegistrationInfo(nri);
+ mConnection = mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1, "1234", "TC@2"));
+ disconnectCause = mConnection.getDisconnectCause();
+ assertNotEquals(android.telephony.DisconnectCause.SATELLITE_ENABLED,
+ disconnectCause.getTelephonyDisconnectCause());
+ }
+
+ @Test
+ public void testNormalCallUsingNonTerrestrialNetwork_disableFlag() throws Exception {
+ mSetFlagsRule.disableFlags(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG);
+
+ setupForCallTest();
+ // Flag is disabled, so call is supported while using satellite
+ NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+ .setIsNonTerrestrialNetwork(true)
+ .setAvailableServices(List.of(NetworkRegistrationInfo.SERVICE_TYPE_DATA))
+ .build();
+ ServiceState ss = new ServiceState();
+ ss.addNetworkRegistrationInfo(nri);
+ when(mPhone0.getServiceState()).thenReturn(ss);
+ mConnection = mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1, "1234", TELECOM_CALL_ID1));
+ DisconnectCause disconnectCause = mConnection.getDisconnectCause();
+ assertNotEquals(android.telephony.DisconnectCause.SATELLITE_ENABLED,
+ disconnectCause.getTelephonyDisconnectCause());
+ }
+
+ @Test
+ public void testNormalCallUsingNonTerrestrialNetwork_canMakeWifiCall() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG);
+
+ setupForCallTest();
+ // Call is not supported while using satellite
+ NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+ .setIsNonTerrestrialNetwork(true)
+ .setAvailableServices(List.of(NetworkRegistrationInfo.SERVICE_TYPE_DATA))
+ .build();
+ ServiceState ss = new ServiceState();
+ ss.addNetworkRegistrationInfo(nri);
+ when(mPhone0.getServiceState()).thenReturn(ss);
+ // Wi-Fi call is possible
+ doReturn(true).when(mImsPhone).canMakeWifiCall();
+ when(mPhone0.getImsPhone()).thenReturn(mImsPhone);
+
+ mConnection = mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1, "1234", TELECOM_CALL_ID1));
+ DisconnectCause disconnectCause = mConnection.getDisconnectCause();
+ assertNotEquals(android.telephony.DisconnectCause.SATELLITE_ENABLED,
+ disconnectCause.getTelephonyDisconnectCause());
+ }
+
+ @Test
+ public void testIsAvailableForEmergencyCallsNotForCrossSim() {
+ Phone mockPhone = Mockito.mock(Phone.class);
+ when(mockPhone.getImsRegistrationTech()).thenReturn(
+ ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM);
+ assertFalse(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY));
+ assertFalse(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL));
+ assertFalse(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN));
+ }
+
+ @Test
+ public void testIsAvailableForEmergencyCallsUsingNonTerrestrialNetwork_enableFlag() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG);
+
+ Phone mockPhone = Mockito.mock(Phone.class);
+ NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+ .setIsNonTerrestrialNetwork(true)
+ .setAvailableServices(List.of(NetworkRegistrationInfo.SERVICE_TYPE_DATA))
+ .build();
+ ServiceState ss = new ServiceState();
+ ss.addNetworkRegistrationInfo(nri);
+ ss.setEmergencyOnly(true);
+ ss.setState(ServiceState.STATE_EMERGENCY_ONLY);
+ when(mockPhone.getServiceState()).thenReturn(ss);
+
+ assertFalse(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY));
+ assertFalse(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL));
+ assertFalse(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN));
+ }
+
+ @Test
+ public void testIsAvailableForEmergencyCallsUsingNonTerrestrialNetwork_disableFlag() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG);
+
+ Phone mockPhone = Mockito.mock(Phone.class);
+ NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+ .setIsNonTerrestrialNetwork(true)
+ .setAvailableServices(List.of(NetworkRegistrationInfo.SERVICE_TYPE_VOICE))
+ .build();
+ ServiceState ss = new ServiceState();
+ ss.addNetworkRegistrationInfo(nri);
+ ss.setEmergencyOnly(true);
+ ss.setState(ServiceState.STATE_EMERGENCY_ONLY);
+ when(mockPhone.getServiceState()).thenReturn(ss);
+
+ assertTrue(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY));
+ assertFalse(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL));
+ assertTrue(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN));
+ }
+
+ @Test
+ public void testIsAvailableForEmergencyCallsForEmergencyRoutingInEmergencyOnly() {
+ ServiceState mockService = Mockito.mock(ServiceState.class);
+ when(mockService.isEmergencyOnly()).thenReturn(true);
+ when(mockService.getState()).thenReturn(ServiceState.STATE_EMERGENCY_ONLY);
+
+ Phone mockPhone = Mockito.mock(Phone.class);
+ when(mockPhone.getImsRegistrationTech()).thenReturn(
+ ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+ when(mockPhone.getServiceState()).thenReturn(mockService);
+
+ assertTrue(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY));
+ assertFalse(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL));
+ assertTrue(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN));
+ }
+
+ @Test
+ public void testIsAvailableForEmergencyCallsForEmergencyRoutingInService() {
+ ServiceState mockService = Mockito.mock(ServiceState.class);
+ when(mockService.isEmergencyOnly()).thenReturn(false);
+ when(mockService.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+
+ Phone mockPhone = Mockito.mock(Phone.class);
+ when(mockPhone.getImsRegistrationTech()).thenReturn(
+ ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+ when(mockPhone.getServiceState()).thenReturn(mockService);
+
+ assertTrue(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY));
+ assertTrue(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL));
+ assertTrue(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN));
+ }
+
+ /**
+ * Verify that is the carrier config indicates that the carrier does not prefer to use an in
+ * service sim for a normal routed emergency call that we'll get no result.
+ */
+ @Test
+ public void testGetPhoneForNormalRoutedEmergencyCallWhenCarrierDoesntSupport() {
+ ServiceState mockService = Mockito.mock(ServiceState.class);
+ when(mockService.isEmergencyOnly()).thenReturn(false);
+ when(mockService.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+
+ Phone mockPhone = Mockito.mock(Phone.class);
+ when(mockPhone.shouldPreferInServiceSimForNormalRoutedEmergencyCall()).thenReturn(
+ false);
+ setupMockEmergencyNumbers(mockPhone, List.of(MOCK_NORMAL_NUMBER));
+ when(mPhoneFactoryProxy.getPhones()).thenReturn(new Phone[] {mockPhone});
+
+ assertNull(mTestConnectionService.getPhoneForNormalRoutedEmergencyCall(
+ NORMAL_ROUTED_EMERGENCY_NUMBER));
+ }
+
+ /**
+ * Verify that is the carrier config indicates that the carrier prefers to use an in service sim
+ * for a normal routed emergency call that we'll get the in service sim that supports it.
+ */
+ @Test
+ public void testGetPhoneForNormalRoutedEmergencyCallWhenCarrierDoesSupport() {
+ ServiceState mockService = Mockito.mock(ServiceState.class);
+ when(mockService.isEmergencyOnly()).thenReturn(false);
+ when(mockService.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+
+ Phone mockPhone = Mockito.mock(Phone.class);
+ when(mockPhone.shouldPreferInServiceSimForNormalRoutedEmergencyCall()).thenReturn(
+ true);
+ when(mockPhone.getServiceState()).thenReturn(mockService);
+ setupMockEmergencyNumbers(mockPhone, List.of(MOCK_NORMAL_NUMBER));
+ when(mPhoneFactoryProxy.getPhones()).thenReturn(new Phone[] {mockPhone});
+
+ assertEquals(mockPhone,
+ mTestConnectionService.getPhoneForNormalRoutedEmergencyCall(
+ NORMAL_ROUTED_EMERGENCY_NUMBER));
+ }
+
+ /**
+ * Verify where there are two sims, one in limited service, and another in full service, if the
+ * carrier prefers to use an in-service sim, we choose the in-service sim.
+ */
+ @Test
+ public void testGetPhoneForNormalRoutedEmergencyCallWhenCarrierDoesSupportMultiSim() {
+ ServiceState mockInService = Mockito.mock(ServiceState.class);
+ when(mockInService.isEmergencyOnly()).thenReturn(false);
+ when(mockInService.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+ ServiceState mockLimitedService = Mockito.mock(ServiceState.class);
+ when(mockLimitedService.isEmergencyOnly()).thenReturn(true);
+ when(mockLimitedService.getState()).thenReturn(ServiceState.STATE_EMERGENCY_ONLY);
+
+ Phone mockInservicePhone = Mockito.mock(Phone.class);
+ when(mockInservicePhone.shouldPreferInServiceSimForNormalRoutedEmergencyCall()).thenReturn(
+ true);
+ when(mockInservicePhone.getServiceState()).thenReturn(mockInService);
+ setupMockEmergencyNumbers(mockInservicePhone, List.of(MOCK_NORMAL_NUMBER));
+
+ Phone mockLimitedServicePhone = Mockito.mock(Phone.class);
+ when(mockLimitedServicePhone.shouldPreferInServiceSimForNormalRoutedEmergencyCall())
+ .thenReturn(true);
+ when(mockLimitedServicePhone.getServiceState()).thenReturn(mockLimitedService);
+ setupMockEmergencyNumbers(mockLimitedServicePhone, List.of(MOCK_NORMAL_NUMBER));
+
+ when(mPhoneFactoryProxy.getPhones()).thenReturn(new Phone[] {mockLimitedServicePhone,
+ mockInservicePhone});
+
+ assertEquals(mockInservicePhone,
+ mTestConnectionService.getPhoneForNormalRoutedEmergencyCall(
+ NORMAL_ROUTED_EMERGENCY_NUMBER));
+ }
+
+ private void setupMockEmergencyNumbers(Phone mockPhone, List<EmergencyNumber> numbers) {
+ EmergencyNumberTracker emergencyNumberTracker = Mockito.mock(EmergencyNumberTracker.class);
+ // Yuck. There should really be a fake emergency number class which makes it easy to inject
+ // the numbers for testing.
+ ArrayMap<String, List<EmergencyNumber>> numbersMap = new ArrayMap<>();
+ for (EmergencyNumber number : numbers) {
+ when(emergencyNumberTracker.getEmergencyNumber(eq(number.getNumber())))
+ .thenReturn(number);
+ if (!numbersMap.containsKey(number.getNumber())) {
+ numbersMap.put(number.getNumber(), new ArrayList<>());
+ }
+ numbersMap.get(number.getNumber()).add(number);
+ }
+ // Double yuck.
+ for (Map.Entry<String, List<EmergencyNumber>> entry : numbersMap.entrySet()) {
+ when(emergencyNumberTracker.getEmergencyNumbers(eq(entry.getKey()))).thenReturn(
+ entry.getValue());
+ }
+ when(mockPhone.getEmergencyNumberTracker()).thenReturn(emergencyNumberTracker);
+ }
+
private void setupForDialForDomainSelection(Phone mockPhone, int domain, boolean isEmergency) {
if (isEmergency) {
doReturn(mEmergencyCallDomainSelectionConnection).when(mDomainSelectionResolver)
@@ -2920,9 +3755,54 @@
doReturn(mImsPhone).when(mockPhone).getImsPhone();
}
+ private Phone setupConnectionServiceInApmForDomainSelection(boolean normalRouting) {
+ ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
+ .setAccountHandle(PHONE_ACCOUNT_HANDLE_1)
+ .setAddress(TEST_ADDRESS)
+ .build();
+ Phone testPhone0 = makeTestPhone(0 /*phoneId*/, ServiceState.STATE_POWER_OFF,
+ false /*isEmergencyOnly*/);
+ Phone testPhone1 = makeTestPhone(1 /*phoneId*/, ServiceState.STATE_POWER_OFF,
+ false /*isEmergencyOnly*/);
+ doReturn(GSM_PHONE).when(testPhone0).getPhoneType();
+ doReturn(GSM_PHONE).when(testPhone1).getPhoneType();
+ List<Phone> phones = new ArrayList<>(2);
+ doReturn(false).when(testPhone0).isRadioOn();
+ doReturn(false).when(testPhone1).isRadioOn();
+ phones.add(testPhone0);
+ phones.add(testPhone1);
+ setPhones(phones);
+ setupHandleToPhoneMap(PHONE_ACCOUNT_HANDLE_1, testPhone0);
+ setupDeviceConfig(testPhone0, testPhone1, 0);
+ setupForDialForDomainSelection(testPhone0, DOMAIN_CS, false);
+
+ EmergencyNumber emergencyNumber;
+ if (normalRouting) {
+ emergencyNumber = new EmergencyNumber(TEST_ADDRESS.getSchemeSpecificPart(), "", "",
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+ Collections.emptyList(),
+ EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+ } else {
+ emergencyNumber = setupEmergencyNumber(TEST_ADDRESS);
+ }
+ doReturn(true).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(anyString());
+ doReturn(emergencyNumber).when(mEmergencyNumberTracker).getEmergencyNumber(anyString());
+ doReturn(Arrays.asList(emergencyNumber)).when(mEmergencyNumberTracker).getEmergencyNumbers(
+ anyString());
+ doReturn(2).when(mTelephonyManagerProxy).getPhoneCount();
+
+ mConnection = mTestConnectionService.onCreateOutgoingConnection(
+ PHONE_ACCOUNT_HANDLE_1, connectionRequest);
+ assertNotNull("test connection was not set up correctly.", mConnection);
+
+ return testPhone0;
+ }
+
private TestTelephonyConnection setupForReDialForDomainSelection(
Phone mockPhone, int domain, int preciseDisconnectCause,
int disconnectCause, boolean fromEmergency) throws Exception {
+ TestTelephonyConnection c = new TestTelephonyConnection();
try {
if (fromEmergency) {
doReturn(CompletableFuture.completedFuture(domain))
@@ -2931,8 +3811,8 @@
replaceInstance(TelephonyConnectionService.class,
"mEmergencyCallDomainSelectionConnection",
mTestConnectionService, mEmergencyCallDomainSelectionConnection);
- replaceInstance(TelephonyConnectionService.class, "mEmergencyCallId",
- mTestConnectionService, TELECOM_CALL_ID1);
+ replaceInstance(TelephonyConnectionService.class, "mEmergencyConnection",
+ mTestConnectionService, c);
} else {
doReturn(CompletableFuture.completedFuture(domain))
.when(mNormalCallDomainSelectionConnection).reselectDomain(any());
@@ -2946,7 +3826,6 @@
doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported();
- TestTelephonyConnection c = new TestTelephonyConnection();
c.setTelecomCallId(TELECOM_CALL_ID1);
c.setMockPhone(mockPhone);
c.setAddress(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED);
@@ -3107,10 +3986,14 @@
}
private void setupHandleToPhoneMap(PhoneAccountHandle handle, Phone phone) {
- // use subId 0
- when(mPhoneUtilsProxy.getSubIdForPhoneAccountHandle(eq(handle))).thenReturn(0);
- when(mSubscriptionManagerProxy.getPhoneId(eq(0))).thenReturn(0);
- when(mPhoneFactoryProxy.getPhone(eq(0))).thenReturn(phone);
+ // The specified handle has an id which is subID
+ doReturn(Integer.parseInt(handle.getId())).when(mPhoneUtilsProxy)
+ .getSubIdForPhoneAccountHandle(eq(handle));
+ // The specified sub id in the passed handle will correspond to the phone's phone id.
+ doReturn(phone.getPhoneId()).when(mSubscriptionManagerProxy)
+ .getPhoneId(eq(Integer.parseInt(handle.getId())));
+ int phoneId = phone.getPhoneId();
+ doReturn(phone).when(mPhoneFactoryProxy).getPhone(eq(phoneId));
}
private AsyncResult getSuppServiceNotification(int notificationType, int code) {
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
index bf9fa01..c659d5e 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
@@ -14,6 +14,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
@@ -280,7 +281,7 @@
c.updateState();
verify(mTelephonyConnectionService)
- .maybeReselectDomain(any(), anyInt(), any());
+ .maybeReselectDomain(any(), any(), anyBoolean(), anyInt());
}
@Test
@@ -291,7 +292,7 @@
.getState();
c.setTelephonyConnectionService(mTelephonyConnectionService);
doReturn(false).when(mTelephonyConnectionService)
- .maybeReselectDomain(any(), anyInt(), any());
+ .maybeReselectDomain(any(), any(), anyBoolean(), anyInt());
c.updateState();
assertEquals(STATE_DISCONNECTED, c.getState());
@@ -306,7 +307,7 @@
.getState();
c.setTelephonyConnectionService(mTelephonyConnectionService);
doReturn(true).when(mTelephonyConnectionService)
- .maybeReselectDomain(any(), anyInt(), any());
+ .maybeReselectDomain(any(), any(), anyBoolean(), anyInt());
c.resetOriginalConnectionCleared();
c.updateState();
@@ -327,7 +328,7 @@
.when(mImsPhoneConnection).getEmergencyNumberInfo();
c.setTelephonyConnectionService(mTelephonyConnectionService);
doReturn(true).when(mTelephonyConnectionService)
- .maybeReselectDomain(any(), anyInt(), any());
+ .maybeReselectDomain(any(), any(), anyBoolean(), anyInt());
c.updateState();
Integer serviceCategory = c.getEmergencyServiceCategory();
@@ -351,7 +352,7 @@
.when(mImsPhoneConnection).getEmergencyNumberInfo();
c.setTelephonyConnectionService(mTelephonyConnectionService);
doReturn(true).when(mTelephonyConnectionService)
- .maybeReselectDomain(any(), anyInt(), any());
+ .maybeReselectDomain(any(), any(), anyBoolean(), anyInt());
c.updateState();
Integer serviceCategory = c.getEmergencyServiceCategory();
diff --git a/tests/src/com/android/services/telephony/domainselection/CrossSimRedialingControllerTest.java b/tests/src/com/android/services/telephony/domainselection/CrossSimRedialingControllerTest.java
index a32329d..2ed91b8 100644
--- a/tests/src/com/android/services/telephony/domainselection/CrossSimRedialingControllerTest.java
+++ b/tests/src/com/android/services/telephony/domainselection/CrossSimRedialingControllerTest.java
@@ -27,6 +27,7 @@
import static com.android.services.telephony.domainselection.CrossSimRedialingController.MSG_QUICK_CROSS_STACK_TIMEOUT;
import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -42,6 +43,7 @@
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
import android.testing.TestableLooper;
import android.util.Log;
@@ -54,6 +56,11 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
/**
* Unit tests for CrossSimRedialingController
*/
@@ -66,8 +73,6 @@
private static final String TELECOM_CALL_ID1 = "TC1";
private static final String TEST_EMERGENCY_NUMBER = "911";
- @Mock private CarrierConfigManager mCarrierConfigManager;
- @Mock private TelephonyManager mTelephonyManager;
@Mock private EmergencyCallDomainSelector mEcds;
@Mock private CrossSimRedialingController.EmergencyNumberHelper mEmergencyNumberHelper;
@@ -76,6 +81,8 @@
private HandlerThread mHandlerThread;
private TestableLooper mLooper;
private CrossSimRedialingController mCsrController;
+ private CarrierConfigManager mCarrierConfigManager;
+ private TelephonyManager mTelephonyManager;
@Before
public void setUp() throws Exception {
@@ -462,6 +469,42 @@
verify(mEcds, times(0)).notifyCrossStackTimerExpired();
}
+ @Test
+ public void testEmergencyNumberHelper() throws Exception {
+ mCsrController = new CrossSimRedialingController(mContext,
+ mHandlerThread.getLooper());
+
+ CrossSimRedialingController.EmergencyNumberHelper helper =
+ mCsrController.getEmergencyNumberHelper();
+
+ assertNotNull(helper);
+
+ EmergencyNumber num1 = new EmergencyNumber(TEST_EMERGENCY_NUMBER, "us", "",
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE, new ArrayList<String>(),
+ EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+
+ EmergencyNumber num2 = new EmergencyNumber("119", "jp", "",
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE, new ArrayList<String>(),
+ EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+
+ Map<Integer, List<EmergencyNumber>> lists = new HashMap<>();
+ List<EmergencyNumber> list = new ArrayList<>();
+ list.add(num1);
+ lists.put(1, list);
+
+ list = new ArrayList<>();
+ list.add(num2);
+ lists.put(2, list);
+
+ doReturn(lists).when(mTelephonyManager).getEmergencyNumberList();
+
+ assertTrue(helper.isEmergencyNumber(1, TEST_EMERGENCY_NUMBER));
+ assertFalse(helper.isEmergencyNumber(2, TEST_EMERGENCY_NUMBER));
+ assertFalse(helper.isEmergencyNumber(3, TEST_EMERGENCY_NUMBER));
+ }
+
private void createController() throws Exception {
mCsrController = new CrossSimRedialingController(mContext,
mHandlerThread.getLooper(), mEmergencyNumberHelper);
diff --git a/tests/src/com/android/services/telephony/domainselection/DomainSelectorBaseTest.java b/tests/src/com/android/services/telephony/domainselection/DomainSelectorBaseTest.java
index 74c3311..872238c 100644
--- a/tests/src/com/android/services/telephony/domainselection/DomainSelectorBaseTest.java
+++ b/tests/src/com/android/services/telephony/domainselection/DomainSelectorBaseTest.java
@@ -26,8 +26,8 @@
import android.os.Looper;
import android.telephony.DomainSelectionService.SelectionAttributes;
import android.telephony.TransportSelectorCallback;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.TestContext;
@@ -52,11 +52,6 @@
}
@Override
- public void cancelSelection() {
- // No operations.
- }
-
- @Override
public void reselectDomain(@NonNull SelectionAttributes attr) {
// No operations.
}
diff --git a/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java b/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
index 9be85ed..3079ebd 100644
--- a/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
+++ b/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
@@ -16,6 +16,8 @@
package com.android.services.telephony.domainselection;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
import static android.telephony.AccessNetworkConstants.AccessNetworkType.EUTRAN;
import static android.telephony.AccessNetworkConstants.AccessNetworkType.GERAN;
import static android.telephony.AccessNetworkConstants.AccessNetworkType.NGRAN;
@@ -40,6 +42,7 @@
import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT;
import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT;
import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_SCAN_LIMITED_SERVICE_AFTER_VOLTE_FAILURE_BOOL;
import static android.telephony.CarrierConfigManager.ImsEmergency.SCAN_TYPE_FULL_SERVICE;
import static android.telephony.CarrierConfigManager.ImsEmergency.SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE;
import static android.telephony.CarrierConfigManager.ImsEmergency.SCAN_TYPE_NO_PREFERENCE;
@@ -51,7 +54,11 @@
import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS;
import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN;
+import static android.telephony.PreciseDisconnectCause.SERVICE_OPTION_NOT_AVAILABLE;
+import static android.telephony.TelephonyManager.DATA_CONNECTED;
+import static android.telephony.TelephonyManager.SIM_ACTIVATION_STATE_DEACTIVATED;
import static com.android.services.telephony.domainselection.EmergencyCallDomainSelector.MSG_MAX_CELLULAR_TIMEOUT;
import static com.android.services.telephony.domainselection.EmergencyCallDomainSelector.MSG_NETWORK_SCAN_TIMEOUT;
@@ -64,10 +71,12 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyVararg;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
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 static org.mockito.Mockito.when;
@@ -75,8 +84,11 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkRequest;
+import android.net.Uri;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IPowerManager;
@@ -84,6 +96,7 @@
import android.os.Looper;
import android.os.PersistableBundle;
import android.os.PowerManager;
+import android.telecom.PhoneAccount;
import android.telephony.AccessNetworkConstants;
import android.telephony.BarringInfo;
import android.telephony.CarrierConfigManager;
@@ -91,26 +104,28 @@
import android.telephony.DisconnectCause;
import android.telephony.DomainSelectionService;
import android.telephony.DomainSelectionService.SelectionAttributes;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.PreciseDisconnectCause;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.TransportSelectorCallback;
import android.telephony.WwanSelectorCallback;
import android.telephony.ims.ImsManager;
import android.telephony.ims.ImsMmTelManager;
-import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ProvisioningManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableLooper;
import android.util.Log;
import android.util.SparseArray;
+import androidx.test.filters.SmallTest;
+
import com.android.TestContext;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
@@ -128,6 +143,7 @@
private static final int SLOT_0 = 0;
private static final int SLOT_0_SUB_ID = 1;
+ private static final Uri TEST_URI = Uri.fromParts(PhoneAccount.SCHEME_TEL, "911", null);
@Mock private CarrierConfigManager mCarrierConfigManager;
@Mock private ConnectivityManager mConnectivityManager;
@@ -139,6 +155,8 @@
@Mock private DomainSelectorBase.DestroyListener mDestroyListener;
@Mock private ProvisioningManager mProvisioningManager;
@Mock private CrossSimRedialingController mCsrdCtrl;
+ @Mock private EmergencyCallbackModeHelper mEcbmHelper;
+ @Mock private Resources mResources;
private Context mContext;
@@ -149,7 +167,7 @@
private @AccessNetworkConstants.RadioAccessNetworkType List<Integer> mAccessNetwork;
private PowerManager mPowerManager;
private ConnectivityManager.NetworkCallback mNetworkCallback;
- private Consumer<EmergencyRegResult> mResultConsumer;
+ private Consumer<EmergencyRegistrationResult> mResultConsumer;
@Before
public void setUp() throws Exception {
@@ -188,6 +206,11 @@
public String getOpPackageName() {
return "";
}
+
+ @Override
+ public Resources getResources() {
+ return mResources;
+ }
};
if (Looper.myLooper() == null) {
@@ -207,9 +230,11 @@
when(mTelephonyManager.createForSubscriptionId(anyInt()))
.thenReturn(mTelephonyManager);
when(mTelephonyManager.getNetworkCountryIso()).thenReturn("");
+ when(mTelephonyManager.getSimState(anyInt())).thenReturn(TelephonyManager.SIM_STATE_READY);
+ when(mTelephonyManager.getActiveModemCount()).thenReturn(1);
mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
- when(mCarrierConfigManager.getConfigForSubId(anyInt()))
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg()))
.thenReturn(getDefaultPersistableBundle());
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
@@ -233,7 +258,6 @@
doReturn(mProvisioningManager).when(imsManager).getProvisioningManager(anyInt());
doReturn(null).when(mProvisioningManager).getProvisioningStringValue(anyInt());
- when(mTransportSelectorCallback.onWwanSelected()).thenReturn(mWwanSelectorCallback);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
@@ -248,11 +272,16 @@
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
mAccessNetwork = (List<Integer>) invocation.getArguments()[0];
- mResultConsumer = (Consumer<EmergencyRegResult>) invocation.getArguments()[3];
+ mResultConsumer =
+ (Consumer<EmergencyRegistrationResult>) invocation.getArguments()[4];
return null;
}
}).when(mWwanSelectorCallback).onRequestEmergencyNetworkScan(
- any(), anyInt(), any(), any());
+ any(), anyInt(), anyBoolean(), any(), any());
+
+ when(mResources.getStringArray(anyInt())).thenReturn(null);
+
+ doReturn(false).when(mCsrdCtrl).isThereOtherSlot();
}
@After
@@ -274,16 +303,175 @@
createSelector(SLOT_0_SUB_ID);
verify(mWwanSelectorCallback, times(0)).onRequestEmergencyNetworkScan(
- any(), anyInt(), any(), any());
+ any(), anyInt(), anyBoolean(), any(), any());
verify(mWwanSelectorCallback, times(0)).onDomainSelected(anyInt(), eq(true));
}
@Test
+ public void testDestroyed() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UTRAN,
+ REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ mDomainSelector.destroy();
+ mDomainSelector.handleMessage(mDomainSelector.obtainMessage(Integer.MAX_VALUE));
+ unsolBarringInfoChanged(false);
+
+ verify(mTransportSelectorCallback, never()).onWwanSelected(any());
+ }
+
+ @Test
+ public void testDomainPreferenceConfigurationError() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ int[] domainPreference = new int[] {
+ CarrierConfigManager.ImsEmergency.DOMAIN_PS_NON_3GPP,
+ };
+ bundle.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY, domainPreference);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(
+ UNKNOWN, REGISTRATION_STATE_UNKNOWN, 0, false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyScanCsPreferred();
+ }
+
+ @Test
+ public void testNullEmergencyRegistrationResult() throws Exception {
+ doReturn(2).when(mTelephonyManager).getActiveModemCount();
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, null);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
+ public void testNoRedundantDomainSelectionFromInitialState() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+ unsolBarringInfoChanged(false);
+
+ processAllMessages();
+
+ verify(mTransportSelectorCallback, times(1)).onWwanSelected(any());
+ verify(mWwanSelectorCallback, times(1)).onDomainSelected(anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testNoUnexpectedTransportChangeFromInitialState() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ int[] domainPreference = new int[] {
+ CarrierConfigManager.ImsEmergency.DOMAIN_PS_NON_3GPP,
+ CarrierConfigManager.ImsEmergency.DOMAIN_PS_3GPP,
+ CarrierConfigManager.ImsEmergency.DOMAIN_CS
+ };
+ bundle.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY, domainPreference);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+ bindImsService(true);
+
+ processAllMessages();
+
+ verify(mTransportSelectorCallback, times(1)).onWwanSelected(any());
+ verify(mTransportSelectorCallback, times(0)).onWlanSelected(anyBoolean());
+ }
+
+ @Test
+ public void testNoRedundantScanRequestFromInitialState() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(
+ UNKNOWN, REGISTRATION_STATE_UNKNOWN, 0, false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+ unsolBarringInfoChanged(false);
+
+ processAllMessages();
+
+ verify(mTransportSelectorCallback, times(1)).onWwanSelected(any());
+ verify(mWwanSelectorCallback, times(1)).onRequestEmergencyNetworkScan(
+ any(), anyInt(), anyBoolean(), any(), any());
+ }
+
+ @Test
+ public void testNoRedundantTerminationFromInitialState() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+ doReturn(2).when(mTelephonyManager).getActiveModemCount();
+ doReturn(TelephonyManager.SIM_STATE_PIN_REQUIRED)
+ .when(mTelephonyManager).getSimState(anyInt());
+ doReturn(true).when(mCsrdCtrl).isThereOtherSlot();
+ doReturn(new String[] {"jp"}).when(mResources).getStringArray(anyInt());
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(
+ UNKNOWN, REGISTRATION_STATE_UNKNOWN, 0, false, false, 0, 0, "", "", "jp");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+ unsolBarringInfoChanged(false);
+
+ verify(mTransportSelectorCallback, times(0)).onWlanSelected(anyBoolean());
+ verify(mTransportSelectorCallback, times(0)).onWwanSelected(any());
+ verify(mTransportSelectorCallback, times(1)).onSelectionTerminated(anyInt());
+ }
+
+ @Test
public void testDefaultCombinedImsRegisteredBarredSelectCs() throws Exception {
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -300,7 +488,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -317,7 +506,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -335,11 +525,102 @@
}
@Test
+ public void testDefaultCombinedImsRegisteredSelectPsThenExtendedServiceRequestFails()
+ throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+
+ verifyPsDialed();
+
+ mDomainSelector.reselectDomain(attr);
+ processAllMessages();
+
+ verifyCsDialed();
+
+ //Extended service request failed
+ SelectionAttributes.Builder builder =
+ new SelectionAttributes.Builder(SLOT_0, SLOT_0_SUB_ID, SELECTOR_TYPE_CALLING)
+ .setAddress(TEST_URI)
+ .setCsDisconnectCause(SERVICE_OPTION_NOT_AVAILABLE)
+ .setEmergency(true)
+ .setEmergencyRegistrationResult(regResult);
+ attr = builder.build();
+ mDomainSelector.reselectDomain(attr);
+ processAllMessages();
+
+ verifyScanCsPreferred();
+ }
+
+ @Test
+ public void testDefaultCombinedImsRegisteredSelectPsThenNotExtendedServiceRequestFails()
+ throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+
+ verifyPsDialed();
+
+ mDomainSelector.reselectDomain(attr);
+ processAllMessages();
+
+ verifyCsDialed();
+
+ SelectionAttributes.Builder builder =
+ new SelectionAttributes.Builder(SLOT_0, SLOT_0_SUB_ID, SELECTOR_TYPE_CALLING)
+ .setEmergency(true)
+ .setEmergencyRegistrationResult(regResult);
+ attr = builder.build();
+ mDomainSelector.reselectDomain(attr);
+ processAllMessages();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
+ public void testDefaultCombinedImsNotRegisteredDeactivatedSimSelectPs() throws Exception {
+ doReturn(SIM_ACTIVATION_STATE_DEACTIVATED).when(mTelephonyManager).getDataActivationState();
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyPsDialed();
+ }
+
+ @Test
public void testDefaultCombinedImsNotRegisteredSelectCs() throws Exception {
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -352,16 +633,69 @@
}
@Test
- public void testNoCsCombinedImsNotRegisteredSelectPs() throws Exception {
+ public void testAirplaneDefaultCombinedImsNotRegisteredSelectPs() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = new SelectionAttributes.Builder(
+ SLOT_0, SLOT_0_SUB_ID, SELECTOR_TYPE_CALLING)
+ .setAddress(TEST_URI)
+ .setEmergency(true)
+ .setEmergencyRegistrationResult(regResult)
+ .setExitedFromAirplaneMode(true)
+ .build();
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyPsDialed();
+ }
+
+ @Test
+ public void testAirplaneRequiresRegCombinedImsNotRegisteredSelectPs() throws Exception {
PersistableBundle bundle = getDefaultPersistableBundle();
- bundle.putIntArray(KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY,
- new int[0]);
- when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+ bundle.putBoolean(KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL, true);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = new SelectionAttributes.Builder(
+ SLOT_0, SLOT_0_SUB_ID, SELECTOR_TYPE_CALLING)
+ .setAddress(TEST_URI)
+ .setEmergency(true)
+ .setEmergencyRegistrationResult(regResult)
+ .setExitedFromAirplaneMode(true)
+ .build();
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyPsDialed();
+ }
+
+ @Test
+ public void testNoCsCombinedImsNotRegisteredSelectPs() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putIntArray(KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY,
+ new int[0]);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -378,7 +712,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -395,7 +730,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
true, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -412,7 +748,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
true, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -429,7 +766,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
true, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -446,7 +784,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
true, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -463,7 +802,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
false, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -480,7 +820,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
false, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -497,7 +838,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
false, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -514,7 +856,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
false, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -531,7 +874,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
false, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -548,7 +892,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
false, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -565,7 +910,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
false, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -582,7 +928,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
false, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -599,7 +946,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(UTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -612,11 +960,96 @@
}
@Test
+ public void testNotSupportPsEmergency() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ int[] domainPreference = new int[] {
+ CarrierConfigManager.ImsEmergency.DOMAIN_CS
+ };
+ bundle.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY, domainPreference);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UTRAN,
+ REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS,
+ false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyCsDialed();
+
+ mDomainSelector.reselectDomain(attr);
+ processAllMessages();
+
+ verify(mWwanSelectorCallback, times(1)).onRequestEmergencyNetworkScan(
+ any(), anyInt(), anyBoolean(), any(), any());
+ assertEquals(2, mAccessNetwork.size());
+ assertEquals(UTRAN, (int) mAccessNetwork.get(0));
+ assertEquals(GERAN, (int) mAccessNetwork.get(1));
+ }
+
+ @Test
+ public void testNotSupportPsCombinedImsRegisteredSelectCs() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ int[] domainPreference = new int[] {
+ CarrierConfigManager.ImsEmergency.DOMAIN_CS
+ };
+ bundle.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY, domainPreference);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testNotSupportCsCombinedImsNotRegisteredSelectPs() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ int[] domainPreference = new int[] {
+ CarrierConfigManager.ImsEmergency.DOMAIN_PS_3GPP
+ };
+ bundle.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY, domainPreference);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyPsDialed();
+ }
+
+ @Test
public void testDefaultEpsImsRegisteredBarredScanPsPreferred() throws Exception {
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -633,7 +1066,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -650,7 +1084,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -667,7 +1102,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -684,7 +1120,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
true, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -701,7 +1138,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
true, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -718,7 +1156,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
true, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -735,7 +1174,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
true, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -752,7 +1192,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
false, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -769,7 +1210,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
false, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -786,7 +1228,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
false, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -803,7 +1246,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
false, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -820,7 +1264,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
false, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -837,7 +1282,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
false, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -854,7 +1300,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
false, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -871,7 +1318,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
false, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -888,7 +1336,7 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(
UNKNOWN, REGISTRATION_STATE_UNKNOWN, 0, false, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
@@ -903,12 +1351,13 @@
public void testVoLteOnEpsImsNotRegisteredSelectPs() throws Exception {
PersistableBundle bundle = getDefaultPersistableBundle();
bundle.putBoolean(KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL, true);
- when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -925,7 +1374,7 @@
public void testVoLteOffEpsImsNotRegisteredScanCsPreferred() throws Exception {
PersistableBundle bundle = getDefaultPersistableBundle();
bundle.putBoolean(KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL, true);
- when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
// Disable VoLTE.
when(mMmTelManager.isAdvancedCallingSettingEnabled()).thenReturn(false);
@@ -933,7 +1382,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -950,12 +1400,13 @@
public void testRequiresRegEpsImsNotRegisteredScanCsPreferred() throws Exception {
PersistableBundle bundle = getDefaultPersistableBundle();
bundle.putBoolean(KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL, true);
- when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -968,15 +1419,64 @@
}
@Test
+ public void testRequiresRegEpsImsNotRegisteredDeactivatedSimSelectPs() throws Exception {
+ doReturn(SIM_ACTIVATION_STATE_DEACTIVATED).when(mTelephonyManager).getDataActivationState();
+
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putBoolean(KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL, true);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_PS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyPsDialed();
+ }
+
+ @Test
+ public void testRequiresRegEpsImsNotRegisteredEmcNotSupportedScanCsPreferred()
+ throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putBoolean(KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL, true);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_PS,
+ true, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyScanCsPreferred();
+ }
+
+ @Test
public void testDefaultEpsImsRegisteredBarredScanTimeoutWifi() throws Exception {
PersistableBundle bundle = getDefaultPersistableBundle();
bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
- when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
+ mResultConsumer = null;
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -997,6 +1497,40 @@
mDomainSelector.handleMessage(mDomainSelector.obtainMessage(MSG_NETWORK_SCAN_TIMEOUT));
verify(mTransportSelectorCallback, times(1)).onWlanSelected(eq(true));
+
+ assertNotNull(mResultConsumer);
+
+ mResultConsumer.accept(regResult);
+ processAllMessages();
+
+ // Ignore the stale result
+ verify(mWwanSelectorCallback, never()).onDomainSelected(anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testSimLockEpsImsRegisteredBarredScanNoTimeoutWifi() throws Exception {
+ when(mTelephonyManager.getSimState(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_PIN_REQUIRED);
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_PS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService(true);
+
+ verifyScanPsPreferred();
+
+ assertFalse(mDomainSelector.hasMessages(MSG_NETWORK_SCAN_TIMEOUT));
}
@Test
@@ -1004,12 +1538,13 @@
PersistableBundle bundle = getDefaultPersistableBundle();
bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
bundle.putInt(KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT, VOWIFI_REQUIRES_SETTING_ENABLED);
- when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -1043,12 +1578,13 @@
PersistableBundle bundle = getDefaultPersistableBundle();
bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
bundle.putInt(KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT, VOWIFI_REQUIRES_VALID_EID);
- when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -1081,7 +1617,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -1114,7 +1651,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_PS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -1150,8 +1688,10 @@
doReturn(TelephonyManager.SIM_STATE_PIN_REQUIRED)
.when(mTelephonyManager).getSimState(anyInt());
doReturn(true).when(mCsrdCtrl).isThereOtherSlot();
+ doReturn(new String[] {"jp"}).when(mResources).getStringArray(anyInt());
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_UNKNOWN,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_UNKNOWN,
0, false, false, 0, 0, "", "", "jp");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
@@ -1165,6 +1705,37 @@
}
@Test
+ public void testDualSimInvalidSubscriptionAfterScan() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+ doReturn(2).when(mTelephonyManager).getActiveModemCount();
+ doReturn(TelephonyManager.SIM_STATE_PIN_REQUIRED)
+ .when(mTelephonyManager).getSimState(anyInt());
+ doReturn(true).when(mCsrdCtrl).isThereOtherSlot();
+ doReturn(new String[] {"jp"}).when(mResources).getStringArray(anyInt());
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UNKNOWN,
+ REGISTRATION_STATE_UNKNOWN,
+ 0, false, false, 0, 0, "", "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+ processAllMessages();
+
+ assertNotNull(mResultConsumer);
+
+ regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_UNKNOWN,
+ 0, false, false, 0, 0, "", "", "jp");
+ mResultConsumer.accept(regResult);
+ processAllMessages();
+
+ verify(mTransportSelectorCallback, times(1))
+ .onSelectionTerminated(eq(DisconnectCause.EMERGENCY_PERM_FAILURE));
+ }
+
+ @Test
public void testDualSimInvalidSubscriptionButNoOtherSlot() throws Exception {
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
@@ -1172,8 +1743,10 @@
doReturn(TelephonyManager.SIM_STATE_PIN_REQUIRED)
.when(mTelephonyManager).getSimState(anyInt());
doReturn(false).when(mCsrdCtrl).isThereOtherSlot();
+ doReturn(new String[] {"jp"}).when(mResources).getStringArray(anyInt());
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_UNKNOWN,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_UNKNOWN,
0, false, false, 0, 0, "", "", "jp");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
@@ -1188,10 +1761,59 @@
}
@Test
+ public void testDualSimNormalServiceOnTheOtherSubscription() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+ doReturn(2).when(mTelephonyManager).getActiveModemCount();
+ doReturn(true).when(mCsrdCtrl).isThereOtherSlotInService();
+ doReturn(new String[] {"in"}).when(mResources).getStringArray(anyInt());
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_UNKNOWN,
+ 0, false, false, 0, 0, "", "", "in");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+ processAllMessages();
+
+ verify(mTransportSelectorCallback, times(1))
+ .onSelectionTerminated(eq(DisconnectCause.EMERGENCY_TEMP_FAILURE));
+ }
+
+ @Test
public void testEutranWithCsDomainOnly() throws Exception {
setupForHandleScanResult();
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
+ DOMAIN_CS, false, false, 0, 0, "", "");
+ mResultConsumer.accept(regResult);
+ processAllMessages();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testEutranWithPsDomainOnly() throws Exception {
+ setupForHandleScanResult();
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
+ DOMAIN_PS, false, false, 0, 0, "", "");
+ mResultConsumer.accept(regResult);
+ processAllMessages();
+
+ verifyPsDialed();
+ }
+
+ @Test
+ public void testUtran() throws Exception {
+ setupForHandleScanResult();
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UTRAN,
+ REGISTRATION_STATE_HOME,
DOMAIN_CS, false, false, 0, 0, "", "");
mResultConsumer.accept(regResult);
processAllMessages();
@@ -1203,13 +1825,14 @@
public void testFullService() throws Exception {
PersistableBundle bundle = getDefaultPersistableBundle();
bundle.putInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT, SCAN_TYPE_FULL_SERVICE);
- when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
mResultConsumer = null;
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(UNKNOWN, REGISTRATION_STATE_UNKNOWN,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UNKNOWN,
+ REGISTRATION_STATE_UNKNOWN,
0, false, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
@@ -1219,28 +1842,53 @@
processAllMessages();
verify(mWwanSelectorCallback, times(1)).onRequestEmergencyNetworkScan(
- any(), eq(DomainSelectionService.SCAN_TYPE_FULL_SERVICE), any(), any());
+ any(), eq(DomainSelectionService.SCAN_TYPE_FULL_SERVICE), eq(false), any(), any());
assertNotNull(mResultConsumer);
mResultConsumer.accept(regResult);
processAllMessages();
verify(mWwanSelectorCallback, times(2)).onRequestEmergencyNetworkScan(
- any(), eq(DomainSelectionService.SCAN_TYPE_FULL_SERVICE), any(), any());
+ any(), eq(DomainSelectionService.SCAN_TYPE_FULL_SERVICE), eq(false), any(), any());
}
@Test
- public void testFullServiceThenLimtedService() throws Exception {
+ public void testFullServiceInRoaming() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT, SCAN_TYPE_FULL_SERVICE);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_ROAMING,
+ 0, true, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+ processAllMessages();
+
+ verify(mWwanSelectorCallback).onRequestEmergencyNetworkScan(
+ any(), eq(DomainSelectionService.SCAN_TYPE_NO_PREFERENCE),
+ anyBoolean(), any(), any());
+ }
+
+ @Test
+ public void testFullServiceThenLimitedService() throws Exception {
PersistableBundle bundle = getDefaultPersistableBundle();
bundle.putInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT,
SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE);
- when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
mResultConsumer = null;
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(UNKNOWN, REGISTRATION_STATE_UNKNOWN,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UNKNOWN,
+ REGISTRATION_STATE_UNKNOWN,
0, false, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
@@ -1250,14 +1898,15 @@
processAllMessages();
verify(mWwanSelectorCallback, times(1)).onRequestEmergencyNetworkScan(
- any(), eq(DomainSelectionService.SCAN_TYPE_FULL_SERVICE), any(), any());
+ any(), eq(DomainSelectionService.SCAN_TYPE_FULL_SERVICE), eq(false), any(), any());
assertNotNull(mResultConsumer);
mResultConsumer.accept(regResult);
processAllMessages();
verify(mWwanSelectorCallback, times(1)).onRequestEmergencyNetworkScan(
- any(), eq(DomainSelectionService.SCAN_TYPE_LIMITED_SERVICE), any(), any());
+ any(), eq(DomainSelectionService.SCAN_TYPE_LIMITED_SERVICE),
+ eq(false), any(), any());
}
@Test
@@ -1271,7 +1920,7 @@
setupForScanListTest(bundle);
- verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+ verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false));
}
@Test
@@ -1285,7 +1934,7 @@
setupForScanListTest(bundle);
- verifyPsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+ verifyPsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false));
}
@Test
@@ -1300,7 +1949,7 @@
setupForScanListTest(bundle);
- verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+ verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(false, false));
}
@Test
@@ -1315,7 +1964,7 @@
setupForScanListTest(bundle);
- verifyCsOnlyScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+ verifyCsOnlyScanList(mDomainSelector.getNextPreferredNetworks(false, false));
}
@@ -1330,7 +1979,7 @@
setupForScanListTest(bundle);
- verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+ verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false));
}
@Test
@@ -1344,7 +1993,7 @@
setupForScanListTest(bundle);
- verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+ verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false));
}
@Test
@@ -1360,7 +2009,7 @@
setupForScanListTest(bundle);
- verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+ verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(true, false));
}
@Test
@@ -1375,7 +2024,7 @@
setupForScanListTest(bundle);
- verifyCsOnlyScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+ verifyCsOnlyScanList(mDomainSelector.getNextPreferredNetworks(true, false));
}
@Test
@@ -1392,7 +2041,7 @@
bindImsService();
processAllMessages();
- verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+ verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false));
}
@Test
@@ -1409,7 +2058,7 @@
bindImsService();
processAllMessages();
- verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+ verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false));
}
@Test
@@ -1427,7 +2076,7 @@
bindImsService();
processAllMessages();
- verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+ verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(false, false));
}
@Test
@@ -1444,7 +2093,7 @@
bindImsService();
processAllMessages();
- verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+ verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false));
}
@Test
@@ -1461,7 +2110,7 @@
bindImsService();
processAllMessages();
- verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+ verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false));
}
@Test
@@ -1480,7 +2129,7 @@
bindImsService();
processAllMessages();
- verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+ verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(true, false));
}
@Test
@@ -1496,7 +2145,7 @@
setupForScanListTest(bundle);
- List<Integer> networks = mDomainSelector.getNextPreferredNetworks(false, true, false);
+ List<Integer> networks = mDomainSelector.getNextPreferredNetworks(false, true);
assertFalse(networks.isEmpty());
assertTrue(networks.contains(EUTRAN));
@@ -1509,11 +2158,43 @@
}
@Test
- public void testStartCrossStackTimer() throws Exception {
+ public void testScanLimitedOnlyAfterVoLteFailure() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putBoolean(KEY_SCAN_LIMITED_SERVICE_AFTER_VOLTE_FAILURE_BOOL,
+ true);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
+
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_PS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+ processAllMessages();
+
+ verify(mWwanSelectorCallback).onDomainSelected(eq(DOMAIN_PS), anyBoolean());
+
+ mDomainSelector.reselectDomain(attr);
+ processAllMessages();
+
+ verify(mWwanSelectorCallback).onRequestEmergencyNetworkScan(
+ any(), eq(DomainSelectionService.SCAN_TYPE_LIMITED_SERVICE),
+ anyBoolean(), any(), any());
+ }
+
+ @Test
+ public void testStartCrossStackTimer() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+ doReturn(2).when(mTelephonyManager).getActiveModemCount();
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(
UNKNOWN, REGISTRATION_STATE_UNKNOWN, 0, false, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
@@ -1527,16 +2208,6 @@
}
@Test
- public void testStopCrossStackTimerOnCancel() throws Exception {
- createSelector(SLOT_0_SUB_ID);
- unsolBarringInfoChanged(false);
-
- mDomainSelector.cancelSelection();
-
- verify(mCsrdCtrl).stopTimer();
- }
-
- @Test
public void testStopCrossStackTimerOnFinish() throws Exception {
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
@@ -1550,8 +2221,10 @@
public void testCrossStackTimerTempFailure() throws Exception {
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
+ doReturn(true).when(mCsrdCtrl).isThereOtherSlot();
- EmergencyRegResult regResult = getEmergencyRegResult(UTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -1563,8 +2236,9 @@
verifyCsDialed();
attr = new SelectionAttributes.Builder(SLOT_0, SLOT_0_SUB_ID, SELECTOR_TYPE_CALLING)
+ .setAddress(TEST_URI)
.setEmergency(true)
- .setEmergencyRegResult(regResult)
+ .setEmergencyRegistrationResult(regResult)
.setCsDisconnectCause(PreciseDisconnectCause.EMERGENCY_TEMP_FAILURE)
.build();
@@ -1572,14 +2246,18 @@
processAllMessages();
verify(mCsrdCtrl).notifyCallFailure(eq(PreciseDisconnectCause.EMERGENCY_TEMP_FAILURE));
+ verify(mTransportSelectorCallback)
+ .onSelectionTerminated(eq(DisconnectCause.EMERGENCY_TEMP_FAILURE));
}
@Test
- public void testCrossStackTimerPermFailure() throws Exception {
+ public void testCrossStackTimerTempFailureNoValidSubscription() throws Exception {
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
+ doReturn(false).when(mCsrdCtrl).isThereOtherSlot();
- EmergencyRegResult regResult = getEmergencyRegResult(UTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -1591,8 +2269,42 @@
verifyCsDialed();
attr = new SelectionAttributes.Builder(SLOT_0, SLOT_0_SUB_ID, SELECTOR_TYPE_CALLING)
+ .setAddress(TEST_URI)
.setEmergency(true)
- .setEmergencyRegResult(regResult)
+ .setEmergencyRegistrationResult(regResult)
+ .setCsDisconnectCause(PreciseDisconnectCause.EMERGENCY_TEMP_FAILURE)
+ .build();
+
+ mDomainSelector.reselectDomain(attr);
+ processAllMessages();
+
+ verify(mCsrdCtrl).notifyCallFailure(eq(PreciseDisconnectCause.EMERGENCY_TEMP_FAILURE));
+ verify(mTransportSelectorCallback, never())
+ .onSelectionTerminated(eq(DisconnectCause.EMERGENCY_TEMP_FAILURE));
+ }
+
+ @Test
+ public void testCrossStackTimerPermFailure() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+ doReturn(true).when(mCsrdCtrl).isThereOtherSlot();
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UTRAN,
+ REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyCsDialed();
+
+ attr = new SelectionAttributes.Builder(SLOT_0, SLOT_0_SUB_ID, SELECTOR_TYPE_CALLING)
+ .setAddress(TEST_URI)
+ .setEmergency(true)
+ .setEmergencyRegistrationResult(regResult)
.setCsDisconnectCause(PreciseDisconnectCause.EMERGENCY_PERM_FAILURE)
.build();
@@ -1600,6 +2312,41 @@
processAllMessages();
verify(mCsrdCtrl).notifyCallFailure(eq(PreciseDisconnectCause.EMERGENCY_PERM_FAILURE));
+ verify(mTransportSelectorCallback)
+ .onSelectionTerminated(eq(DisconnectCause.EMERGENCY_PERM_FAILURE));
+ }
+
+ @Test
+ public void testCrossStackTimerPermFailureNoValidSubscription() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+ doReturn(false).when(mCsrdCtrl).isThereOtherSlot();
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UTRAN,
+ REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyCsDialed();
+
+ attr = new SelectionAttributes.Builder(SLOT_0, SLOT_0_SUB_ID, SELECTOR_TYPE_CALLING)
+ .setAddress(TEST_URI)
+ .setEmergency(true)
+ .setEmergencyRegistrationResult(regResult)
+ .setCsDisconnectCause(PreciseDisconnectCause.EMERGENCY_PERM_FAILURE)
+ .build();
+
+ mDomainSelector.reselectDomain(attr);
+ processAllMessages();
+
+ verify(mCsrdCtrl).notifyCallFailure(eq(PreciseDisconnectCause.EMERGENCY_PERM_FAILURE));
+ verify(mTransportSelectorCallback)
+ .onSelectionTerminated(eq(DisconnectCause.EMERGENCY_PERM_FAILURE));
}
@Test
@@ -1607,20 +2354,31 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(
UNKNOWN, REGISTRATION_STATE_UNKNOWN, 0, false, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
processAllMessages();
bindImsServiceUnregistered();
+ processAllMessages();
- verifyScanPsPreferred();
+ ArgumentCaptor<CancellationSignal> cancelCaptor =
+ ArgumentCaptor.forClass(CancellationSignal.class);
+
+ verify(mWwanSelectorCallback, times(1)).onRequestEmergencyNetworkScan(
+ any(), eq(DomainSelectionService.SCAN_TYPE_NO_PREFERENCE),
+ anyBoolean(), cancelCaptor.capture(), any());
+ assertEquals(EUTRAN, (int) mAccessNetwork.get(0));
mDomainSelector.notifyCrossStackTimerExpired();
verify(mTransportSelectorCallback)
.onSelectionTerminated(eq(DisconnectCause.EMERGENCY_TEMP_FAILURE));
+
+ CancellationSignal cancelSignal = cancelCaptor.getValue();
+ assertNotNull(cancelSignal);
+ assertFalse(cancelSignal.isCanceled());
}
@Test
@@ -1628,7 +2386,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(UTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -1652,41 +2411,11 @@
}
@Test
- public void testDefaultEpsImsRegisteredSelectPsEmergencyRegFailed() throws Exception {
- createSelector(SLOT_0_SUB_ID);
- unsolBarringInfoChanged(false);
-
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
- NetworkRegistrationInfo.DOMAIN_PS,
- true, true, 0, 0, "", "");
- SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
- mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
- processAllMessages();
-
- bindImsService();
-
- verifyPsDialed();
-
- attr = new SelectionAttributes.Builder(SLOT_0, SLOT_0_SUB_ID, SELECTOR_TYPE_CALLING)
- .setEmergency(true)
- .setEmergencyRegResult(regResult)
- .setPsDisconnectCause(
- new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED, 0, null))
- .build();
- mDomainSelector.reselectDomain(attr);
- processAllMessages();
-
- verify(mWwanSelectorCallback, times(1)).onRequestEmergencyNetworkScan(
- any(), anyInt(), any(), any());
- assertFalse(mAccessNetwork.contains(EUTRAN));
- }
-
- @Test
public void testMaxCellularTimeout() throws Exception {
PersistableBundle bundle = getDefaultPersistableBundle();
bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
bundle.putInt(KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT, 20);
- when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
setupForHandleScanResult();
@@ -1706,13 +2435,49 @@
verify(mTransportSelectorCallback, times(1)).onWlanSelected(anyBoolean());
}
+ @Test
+ public void testMaxCellularTimeoutWifiNotAvailable() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
+ bundle.putInt(KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT, 20);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
+
+ setupForHandleScanResult();
+
+ assertTrue(mDomainSelector.hasMessages(MSG_NETWORK_SCAN_TIMEOUT));
+ assertTrue(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
+
+ verify(mTransportSelectorCallback, never()).onWlanSelected(anyBoolean());
+
+ // Max cellular timer expired
+ mDomainSelector.removeMessages(MSG_MAX_CELLULAR_TIMEOUT);
+ mDomainSelector.handleMessage(mDomainSelector.obtainMessage(MSG_MAX_CELLULAR_TIMEOUT));
+
+ assertTrue(mDomainSelector.hasMessages(MSG_NETWORK_SCAN_TIMEOUT));
+ verify(mTransportSelectorCallback, never()).onWlanSelected(anyBoolean());
+ }
+
+ @Test
+ public void testSimLockNoMaxCellularTimeout() throws Exception {
+ when(mTelephonyManager.getSimState(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_PIN_REQUIRED);
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
+ bundle.putInt(KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT, 20);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
+
+ setupForHandleScanResult();
+
+ assertFalse(mDomainSelector.hasMessages(MSG_NETWORK_SCAN_TIMEOUT));
+ assertFalse(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
+ }
@Test
public void testMaxCellularTimeoutScanTimeout() throws Exception {
PersistableBundle bundle = getDefaultPersistableBundle();
bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
bundle.putInt(KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT, 20);
- when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
setupForHandleScanResult();
@@ -1737,12 +2502,13 @@
PersistableBundle bundle = getDefaultPersistableBundle();
bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
bundle.putInt(KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT, 5);
- when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(UTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -1759,6 +2525,8 @@
mDomainSelector.reselectDomain(attr);
processAllMessages();
+ verify(mWwanSelectorCallback, times(1)).onRequestEmergencyNetworkScan(
+ any(), anyInt(), anyBoolean(), any(), any());
assertTrue(mDomainSelector.hasMessages(MSG_NETWORK_SCAN_TIMEOUT));
assertTrue(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
@@ -1773,10 +2541,92 @@
mDomainSelector.handleMessage(mDomainSelector.obtainMessage(MSG_MAX_CELLULAR_TIMEOUT));
processAllMessages();
+ assertFalse(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
+ verify(mTransportSelectorCallback, times(1)).onWlanSelected(anyBoolean());
+
mDomainSelector.reselectDomain(attr);
processAllMessages();
+ verify(mWwanSelectorCallback, times(2)).onRequestEmergencyNetworkScan(
+ any(), anyInt(), anyBoolean(), any(), any());
+ }
+
+ @Test
+ public void testMaxCellularTimeoutWhileDialingOnCellularWhileDialing() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
+ bundle.putInt(KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT, 5);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UTRAN,
+ REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyCsDialed();
+
+ assertFalse(mDomainSelector.hasMessages(MSG_NETWORK_SCAN_TIMEOUT));
assertFalse(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
+
+ mResultConsumer = null;
+ mDomainSelector.reselectDomain(attr);
+ processAllMessages();
+
+ verify(mWwanSelectorCallback, times(1)).onRequestEmergencyNetworkScan(
+ any(), anyInt(), anyBoolean(), any(), any());
+ assertTrue(mDomainSelector.hasMessages(MSG_NETWORK_SCAN_TIMEOUT));
+ assertTrue(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
+
+ assertNotNull(mResultConsumer);
+
+ // Scan result received and redialing on cellular
+ mResultConsumer.accept(regResult);
+ processAllMessages();
+
+ // Wi-Fi is connected.
+ mNetworkCallback.onAvailable(null);
+ processAllMessages();
+
+ verify(mTransportSelectorCallback, times(0)).onWlanSelected(anyBoolean());
+
+ // Max cellular timer expired
+ mDomainSelector.removeMessages(MSG_MAX_CELLULAR_TIMEOUT);
+ mDomainSelector.handleMessage(mDomainSelector.obtainMessage(MSG_MAX_CELLULAR_TIMEOUT));
+ processAllMessages();
+
+ assertFalse(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
+
+ // Waiting for reselectDomain since there is a dialing on going.
+ verify(mTransportSelectorCallback, times(0)).onWlanSelected(anyBoolean());
+
+ // Wi-Fi is disconnected.
+ mNetworkCallback.onUnavailable();
+ processAllMessages();
+
+ mResultConsumer = null;
+ mDomainSelector.reselectDomain(attr);
+ processAllMessages();
+
+ verify(mTransportSelectorCallback, times(0)).onWlanSelected(anyBoolean());
+ verify(mWwanSelectorCallback, times(2)).onRequestEmergencyNetworkScan(
+ any(), anyInt(), anyBoolean(), any(), any());
+
+ // Wi-Fi is re-connected.
+ mNetworkCallback.onAvailable(null);
+ processAllMessages();
+
+ mResultConsumer = null;
+ mDomainSelector.reselectDomain(attr);
+ processAllMessages();
+
verify(mTransportSelectorCallback, times(1)).onWlanSelected(anyBoolean());
}
@@ -1785,7 +2635,7 @@
PersistableBundle bundle = getDefaultPersistableBundle();
bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
bundle.putInt(KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT, 20);
- when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
setupForHandleScanResult();
@@ -1811,7 +2661,7 @@
bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
bundle.putInt(KEY_MAXIMUM_CELLULAR_SEARCH_TIMER_SEC_INT, 20);
bundle.putInt(KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT, 2);
- when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
setupForHandleScanResult();
@@ -1830,7 +2680,8 @@
verify(mTransportSelectorCallback, times(1)).onWlanSelected(anyBoolean());
assertFalse(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
- EmergencyRegResult regResult = getEmergencyRegResult(UTRAN, REGISTRATION_STATE_HOME,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UTRAN,
+ REGISTRATION_STATE_HOME,
NetworkRegistrationInfo.DOMAIN_CS,
true, true, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
@@ -1847,16 +2698,418 @@
verify(mTransportSelectorCallback, times(2)).onWlanSelected(anyBoolean());
}
+ @Test
+ public void testSimLockScanPsPreferredWithNrAtTheEnd() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ when(mTelephonyManager.getSimState(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_PIN_REQUIRED);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(
+ UNKNOWN, REGISTRATION_STATE_UNKNOWN, 0, false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+ processAllMessages();
+
+ verify(mWwanSelectorCallback, times(1)).onRequestEmergencyNetworkScan(
+ any(), anyInt(), anyBoolean(), any(), any());
+ assertEquals(4, mAccessNetwork.size());
+ assertEquals(EUTRAN, (int) mAccessNetwork.get(0));
+ assertEquals(UTRAN, (int) mAccessNetwork.get(1));
+ assertEquals(GERAN, (int) mAccessNetwork.get(2));
+ assertEquals(NGRAN, (int) mAccessNetwork.get(3));
+ }
+
+ @Test
+ public void testInvalidSubscriptionScanPsPreferredWithNrAtTheEnd() throws Exception {
+ createSelector(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(
+ UNKNOWN, REGISTRATION_STATE_UNKNOWN, 0, false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+ processAllMessages();
+
+ verify(mWwanSelectorCallback, times(1)).onRequestEmergencyNetworkScan(
+ any(), anyInt(), anyBoolean(), any(), any());
+ assertEquals(4, mAccessNetwork.size());
+ assertEquals(EUTRAN, (int) mAccessNetwork.get(0));
+ assertEquals(UTRAN, (int) mAccessNetwork.get(1));
+ assertEquals(GERAN, (int) mAccessNetwork.get(2));
+ assertEquals(NGRAN, (int) mAccessNetwork.get(3));
+ }
+
+ @Test
+ public void testDefaultLimitedServiceEutran() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_UNKNOWN,
+ 0, false, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyPsDialed();
+ }
+
+ @Test
+ public void testDefaultLimitedServiceScanTypeFullService() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT, SCAN_TYPE_FULL_SERVICE);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_UNKNOWN,
+ 0, false, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyScanPreferred(DomainSelectionService.SCAN_TYPE_FULL_SERVICE, EUTRAN);
+ }
+
+ @Test
+ public void testScanLtePreferredAfterNgranFailure() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putIntArray(KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY,
+ new int[] { NGRAN, EUTRAN });
+ bundle.putBoolean(KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL, true);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(NGRAN,
+ REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_PS, true, false, 1, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyPsDialed();
+
+ mDomainSelector.reselectDomain(attr);
+ processAllMessages();
+
+ verify(mWwanSelectorCallback, times(1)).onRequestEmergencyNetworkScan(
+ any(), anyInt(), anyBoolean(), any(), any());
+ assertEquals(4, mAccessNetwork.size());
+ assertEquals(EUTRAN, (int) mAccessNetwork.get(0));
+ assertEquals(UTRAN, (int) mAccessNetwork.get(1));
+ assertEquals(GERAN, (int) mAccessNetwork.get(2));
+ assertEquals(NGRAN, (int) mAccessNetwork.get(3));
+ }
+
+ @Test
+ public void testDefaultLimitedServiceNgran() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(NGRAN,
+ REGISTRATION_STATE_UNKNOWN,
+ 0, false, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
+ public void testTestEmergencyNumberOverCs() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UTRAN,
+ REGISTRATION_STATE_UNKNOWN,
+ 0, false, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID,
+ true /*isTestEmergencyNumber*/, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testTestEmergencyNumberOverPs() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_UNKNOWN,
+ 0, false, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID,
+ true /*isTestEmergencyNumber*/, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyPsDialed();
+ }
+
+ @Test
+ public void testTestEmergencyNumberScanRequest() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UNKNOWN,
+ REGISTRATION_STATE_UNKNOWN,
+ 0, false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID,
+ true /*isTestEmergencyNumber*/, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService(true);
+ processAllMessages();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
+ public void testLimitedServiceDialCs() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UTRAN,
+ REGISTRATION_STATE_UNKNOWN,
+ 0, false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testWhileInEcbmOnWwan() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ doReturn(true).when(mEcbmHelper).isInEmergencyCallbackMode(anyInt());
+ doReturn(TRANSPORT_TYPE_WWAN).when(mEcbmHelper).getTransportType(anyInt());
+ doReturn(DATA_CONNECTED).when(mEcbmHelper).getDataConnectionState(anyInt());
+
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Consumer<WwanSelectorCallback> consumer =
+ (Consumer<WwanSelectorCallback>) invocation.getArguments()[0];
+ consumer.accept(mWwanSelectorCallback);
+ return null;
+ }
+ }).when(mTransportSelectorCallback).onWwanSelected(any());
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UNKNOWN,
+ REGISTRATION_STATE_UNKNOWN,
+ 0, false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+ processAllMessages();
+
+ verify(mTransportSelectorCallback, never()).onWlanSelected(anyBoolean());
+ verify(mTransportSelectorCallback).onWwanSelected(any());
+ verify(mWwanSelectorCallback).onDomainSelected(eq(DOMAIN_PS), eq(true));
+ }
+
+ @Test
+ public void testWhileInEcbmOnWlanConnected() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ doReturn(true).when(mEcbmHelper).isInEmergencyCallbackMode(anyInt());
+ doReturn(TRANSPORT_TYPE_WLAN).when(mEcbmHelper).getTransportType(anyInt());
+ doReturn(DATA_CONNECTED).when(mEcbmHelper).getDataConnectionState(anyInt());
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UNKNOWN,
+ REGISTRATION_STATE_UNKNOWN,
+ 0, false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+ processAllMessages();
+
+ verify(mTransportSelectorCallback).onWlanSelected(anyBoolean());
+ verify(mTransportSelectorCallback, never()).onWwanSelected(any());
+ }
+
+ @Test
+ public void testWhileInEcbmOnWlanNotConnected() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ doReturn(true).when(mEcbmHelper).isInEmergencyCallbackMode(anyInt());
+ doReturn(TRANSPORT_TYPE_WLAN).when(mEcbmHelper).getTransportType(anyInt());
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UNKNOWN,
+ REGISTRATION_STATE_UNKNOWN,
+ 0, false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+ processAllMessages();
+
+ verify(mTransportSelectorCallback, never()).onWlanSelected(anyBoolean());
+ verify(mTransportSelectorCallback).onWwanSelected(any());
+ }
+
+ @Test
+ public void testNotInEcbmOnWlanConnected() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ doReturn(false).when(mEcbmHelper).isInEmergencyCallbackMode(anyInt());
+ doReturn(TRANSPORT_TYPE_WLAN).when(mEcbmHelper).getTransportType(anyInt());
+ doReturn(DATA_CONNECTED).when(mEcbmHelper).getDataConnectionState(anyInt());
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UNKNOWN,
+ REGISTRATION_STATE_UNKNOWN,
+ 0, false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+ processAllMessages();
+
+ verify(mTransportSelectorCallback, never()).onWlanSelected(anyBoolean());
+ verify(mTransportSelectorCallback).onWwanSelected(any());
+ }
+
+ @Test
+ public void testNotInEcbmOnWwanConnected() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ doReturn(false).when(mEcbmHelper).isInEmergencyCallbackMode(anyInt());
+ doReturn(TRANSPORT_TYPE_WLAN).when(mEcbmHelper).getTransportType(anyInt());
+ doReturn(DATA_CONNECTED).when(mEcbmHelper).getDataConnectionState(anyInt());
+
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Consumer<WwanSelectorCallback> consumer =
+ (Consumer<WwanSelectorCallback>) invocation.getArguments()[0];
+ consumer.accept(mWwanSelectorCallback);
+ return null;
+ }
+ }).when(mTransportSelectorCallback).onWwanSelected(any());
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UNKNOWN,
+ REGISTRATION_STATE_UNKNOWN,
+ 0, false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+ processAllMessages();
+
+ verify(mTransportSelectorCallback, never()).onWlanSelected(anyBoolean());
+ verify(mTransportSelectorCallback).onWwanSelected(any());
+ verify(mWwanSelectorCallback, never()).onDomainSelected(anyInt(), anyBoolean());
+ verify(mWwanSelectorCallback).onRequestEmergencyNetworkScan(
+ any(), anyInt(), anyBoolean(), any(), any());
+ }
+
+ @Test
+ public void testIsInRoaming() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ int[] domainPreference = new int[] {
+ CarrierConfigManager.ImsEmergency.DOMAIN_PS_3GPP,
+ };
+ bundle.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY, domainPreference);
+ int[] domainPreferenceRoam = new int[] {
+ CarrierConfigManager.ImsEmergency.DOMAIN_PS_3GPP,
+ CarrierConfigManager.ImsEmergency.DOMAIN_CS,
+ };
+ bundle.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY, domainPreferenceRoam);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
+ doReturn("").when(mTelephonyManager).getNetworkCountryIso();
+ doReturn("us").when(mTelephonyManager).getSimCountryIso();
+
+ mResultConsumer = null;
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(UNKNOWN,
+ REGISTRATION_STATE_UNKNOWN,
+ 0, false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+ processAllMessages();
+
+ verify(mWwanSelectorCallback, times(1)).onRequestEmergencyNetworkScan(
+ any(), anyInt(), eq(false), any(), any());
+ assertEquals(1, mAccessNetwork.size());
+ assertEquals(EUTRAN, (int) mAccessNetwork.get(0));
+ assertNotNull(mResultConsumer);
+
+ regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_UNKNOWN,
+ 0, false, false, 0, 0, "", "", "zz");
+ mResultConsumer.accept(regResult);
+ processAllMessages();
+
+ mDomainSelector.reselectDomain(attr);
+ processAllMessages();
+
+ verify(mWwanSelectorCallback, times(2)).onRequestEmergencyNetworkScan(
+ any(), anyInt(), eq(false), any(), any());
+ assertEquals(3, mAccessNetwork.size());
+ assertEquals(UTRAN, (int) mAccessNetwork.get(0));
+ assertEquals(GERAN, (int) mAccessNetwork.get(1));
+ assertEquals(EUTRAN, (int) mAccessNetwork.get(2));
+ }
+
private void setupForScanListTest(PersistableBundle bundle) throws Exception {
setupForScanListTest(bundle, false);
}
private void setupForScanListTest(PersistableBundle bundle, boolean psFailed) throws Exception {
- when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt(), anyVararg())).thenReturn(bundle);
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_UNKNOWN,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_UNKNOWN,
0, false, false, 0, 0, "", "");
if (psFailed) {
regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
@@ -1903,7 +3156,8 @@
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_UNKNOWN,
+ EmergencyRegistrationResult regResult = getEmergencyRegResult(EUTRAN,
+ REGISTRATION_STATE_UNKNOWN,
0, false, false, 0, 0, "", "");
SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
@@ -1913,15 +3167,15 @@
processAllMessages();
verify(mWwanSelectorCallback, times(1)).onRequestEmergencyNetworkScan(
- any(), anyInt(), any(), any());
+ any(), anyInt(), anyBoolean(), any(), any());
assertNotNull(mResultConsumer);
}
private void createSelector(int subId) throws Exception {
mDomainSelector = new EmergencyCallDomainSelector(
mContext, SLOT_0, subId, mHandlerThread.getLooper(),
- mImsStateTracker, mDestroyListener, mCsrdCtrl);
-
+ mImsStateTracker, mDestroyListener, mCsrdCtrl, mEcbmHelper);
+ mDomainSelector.clearResourceConfiguration();
replaceInstance(DomainSelectorBase.class,
"mWwanSelectorCallback", mDomainSelector, mWwanSelectorCallback);
}
@@ -1947,7 +3201,7 @@
private void verifyScanPreferred(int scanType, int expectedPreferredAccessNetwork) {
processAllMessages();
verify(mWwanSelectorCallback, times(1)).onRequestEmergencyNetworkScan(
- any(), eq(scanType), any(), any());
+ any(), eq(scanType), anyBoolean(), any(), any());
assertEquals(expectedPreferredAccessNetwork, (int) mAccessNetwork.get(0));
}
@@ -1979,7 +3233,7 @@
mDomainSelector.onImsMmTelCapabilitiesChanged();
}
- private static EmergencyRegResult getEmergencyRegResult(
+ private static EmergencyRegistrationResult getEmergencyRegResult(
@AccessNetworkConstants.RadioAccessNetworkType int accessNetwork,
@NetworkRegistrationInfo.RegistrationState int regState,
@NetworkRegistrationInfo.Domain int domain,
@@ -1989,13 +3243,13 @@
isEmcBearerSupported, emc, emf, mcc, mnc, "");
}
- private static EmergencyRegResult getEmergencyRegResult(
+ private static EmergencyRegistrationResult getEmergencyRegResult(
@AccessNetworkConstants.RadioAccessNetworkType int accessNetwork,
@NetworkRegistrationInfo.RegistrationState int regState,
@NetworkRegistrationInfo.Domain int domain,
boolean isVopsSupported, boolean isEmcBearerSupported, int emc, int emf,
@NonNull String mcc, @NonNull String mnc, @NonNull String iso) {
- return new EmergencyRegResult(accessNetwork, regState,
+ return new EmergencyRegistrationResult(accessNetwork, regState,
domain, isVopsSupported, isEmcBearerSupported,
emc, emf, mcc, mnc, iso);
}
@@ -2089,12 +3343,19 @@
return bundle;
}
- public static SelectionAttributes getSelectionAttributes(int slotId, int subId,
- EmergencyRegResult regResult) {
+ private static SelectionAttributes getSelectionAttributes(int slotId, int subId,
+ EmergencyRegistrationResult regResult) {
+ return getSelectionAttributes(slotId, subId, false, regResult);
+ }
+
+ private static SelectionAttributes getSelectionAttributes(int slotId, int subId,
+ boolean isTestEmergencyNumber, EmergencyRegistrationResult regResult) {
SelectionAttributes.Builder builder =
new SelectionAttributes.Builder(slotId, subId, SELECTOR_TYPE_CALLING)
+ .setAddress(TEST_URI)
.setEmergency(true)
- .setEmergencyRegResult(regResult);
+ .setTestEmergencyNumber(isTestEmergencyNumber)
+ .setEmergencyRegistrationResult(regResult);
return builder.build();
}
diff --git a/tests/src/com/android/services/telephony/domainselection/EmergencyCallbackModeHelperTest.java b/tests/src/com/android/services/telephony/domainselection/EmergencyCallbackModeHelperTest.java
new file mode 100644
index 0000000..9a4e0d8
--- /dev/null
+++ b/tests/src/com/android/services/telephony/domainselection/EmergencyCallbackModeHelperTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.domainselection;
+
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.assertNotNull;
+
+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.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.testing.TestableLooper;
+import android.util.Log;
+
+import com.android.TestContext;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Unit tests for EmergencyCallbackModeHelper
+ */
+public class EmergencyCallbackModeHelperTest {
+ private static final String TAG = "EmergencyCallbackModeHelperTest";
+
+ private static final int SLOT_0 = 0;
+ private static final int SLOT_1 = 1;
+ private static final int SUB_1 = 1;
+ private static final int SUB_2 = 2;
+
+ private Context mContext;
+ private HandlerThread mHandlerThread;
+ private TestableLooper mLooper;
+ private EmergencyCallbackModeHelper mEcbmHelper;
+ private CarrierConfigManager mCarrierConfigManager;
+ private TelephonyManager mTelephonyManager;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext = new TestContext() {
+ private Intent mIntent;
+
+ @Override
+ public String getSystemServiceName(Class<?> serviceClass) {
+ if (serviceClass == TelephonyManager.class) {
+ return Context.TELEPHONY_SERVICE;
+ } else if (serviceClass == CarrierConfigManager.class) {
+ return Context.CARRIER_CONFIG_SERVICE;
+ }
+ return super.getSystemServiceName(serviceClass);
+ }
+
+ @Override
+ public String getOpPackageName() {
+ return "";
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ return mIntent;
+ }
+
+ @Override
+ public void sendStickyBroadcast(Intent intent) {
+ mIntent = intent;
+ }
+ };
+
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ mHandlerThread = new HandlerThread("EmergencyCallbackModeHelperTest");
+ mHandlerThread.start();
+
+ try {
+ mLooper = new TestableLooper(mHandlerThread.getLooper());
+ } catch (Exception e) {
+ logd("Unable to create looper from handler.");
+ }
+
+ mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+ doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+
+ mEcbmHelper = new EmergencyCallbackModeHelper(mContext, mHandlerThread.getLooper());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mEcbmHelper != null) {
+ mEcbmHelper.destroy();
+ mEcbmHelper = null;
+ }
+
+ if (mLooper != null) {
+ mLooper.destroy();
+ mLooper = null;
+ }
+ }
+
+ @Test
+ public void testInit() throws Exception {
+ ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> callbackCaptor =
+ ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
+ ArgumentCaptor<Executor> executorCaptor = ArgumentCaptor.forClass(Executor.class);
+
+ verify(mCarrierConfigManager).registerCarrierConfigChangeListener(executorCaptor.capture(),
+ callbackCaptor.capture());
+ assertNotNull(executorCaptor.getValue());
+ assertNotNull(callbackCaptor.getValue());
+ }
+
+ @Test
+ public void testEmergencyCallbackModeNotSupported() throws Exception {
+ ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> callbackCaptor =
+ ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
+
+ verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
+ callbackCaptor.capture());
+
+ CarrierConfigManager.CarrierConfigChangeListener callback = callbackCaptor.getValue();
+
+ assertNotNull(callback);
+
+ // ECBM not supported
+ PersistableBundle b = getPersistableBundle(false);
+ doReturn(b).when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyString());
+ callback.onCarrierConfigChanged(SLOT_0, SUB_1, 0, 0);
+
+ // No TelephonyCallback registered
+ verify(mTelephonyManager, never()).registerTelephonyCallback(any(), any());
+ }
+
+ @Test
+ public void testEmergencyCallbackModeSupported() throws Exception {
+ ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> callbackCaptor =
+ ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
+
+ verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
+ callbackCaptor.capture());
+
+ CarrierConfigManager.CarrierConfigChangeListener callback = callbackCaptor.getValue();
+
+ assertNotNull(callback);
+
+ // ECBM supported
+ PersistableBundle b = getPersistableBundle(true);
+ doReturn(b).when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyString());
+ callback.onCarrierConfigChanged(SLOT_0, SUB_1, 0, 0);
+
+ verify(mTelephonyManager).createForSubscriptionId(eq(SUB_1));
+
+ ArgumentCaptor<TelephonyCallback> telephonyCallbackCaptor =
+ ArgumentCaptor.forClass(TelephonyCallback.class);
+
+ // TelephonyCallback registered
+ verify(mTelephonyManager).registerTelephonyCallback(any(),
+ telephonyCallbackCaptor.capture());
+
+ assertNotNull(telephonyCallbackCaptor.getValue());
+ }
+
+ @Test
+ public void testEmergencyCallbackModeChanged() throws Exception {
+ ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> callbackCaptor =
+ ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
+
+ verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
+ callbackCaptor.capture());
+
+ CarrierConfigManager.CarrierConfigChangeListener callback = callbackCaptor.getValue();
+
+ assertNotNull(callback);
+
+ // ECBM supported
+ PersistableBundle b = getPersistableBundle(true);
+ doReturn(b).when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyString());
+ callback.onCarrierConfigChanged(SLOT_0, SUB_1, 0, 0);
+
+ verify(mTelephonyManager).createForSubscriptionId(eq(SUB_1));
+
+ ArgumentCaptor<TelephonyCallback> telephonyCallbackCaptor =
+ ArgumentCaptor.forClass(TelephonyCallback.class);
+
+ // TelephonyCallback registered
+ verify(mTelephonyManager).registerTelephonyCallback(any(),
+ telephonyCallbackCaptor.capture());
+
+ TelephonyCallback telephonyCallback = telephonyCallbackCaptor.getValue();
+
+ assertNotNull(telephonyCallback);
+
+ // Carrier config changes, ECBM not supported
+ b = getPersistableBundle(false);
+ doReturn(b).when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyString());
+ callback.onCarrierConfigChanged(SLOT_0, SUB_1, 0, 0);
+
+ // TelephonyCallback unregistered
+ verify(mTelephonyManager).unregisterTelephonyCallback(eq(telephonyCallback));
+ }
+
+ @Test
+ public void testEmergencyCallbackModeEnter() throws Exception {
+ ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> callbackCaptor =
+ ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
+
+ verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
+ callbackCaptor.capture());
+
+ CarrierConfigManager.CarrierConfigChangeListener callback = callbackCaptor.getValue();
+
+ assertNotNull(callback);
+
+ // ECBM supported
+ PersistableBundle b = getPersistableBundle(true);
+ doReturn(b).when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyString());
+ callback.onCarrierConfigChanged(SLOT_0, SUB_1, 0, 0);
+ callback.onCarrierConfigChanged(SLOT_1, SUB_2, 0, 0);
+
+ // Enter ECBM on slot 1
+ mContext.sendStickyBroadcast(getIntent(true, SLOT_1));
+
+ assertFalse(mEcbmHelper.isInEmergencyCallbackMode(SLOT_0));
+ assertTrue(mEcbmHelper.isInEmergencyCallbackMode(SLOT_1));
+ }
+
+ @Test
+ public void testEmergencyCallbackModeExit() throws Exception {
+ ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> callbackCaptor =
+ ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
+
+ verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
+ callbackCaptor.capture());
+
+ CarrierConfigManager.CarrierConfigChangeListener callback = callbackCaptor.getValue();
+
+ assertNotNull(callback);
+
+ // ECBM supported
+ PersistableBundle b = getPersistableBundle(true);
+ doReturn(b).when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyString());
+ callback.onCarrierConfigChanged(SLOT_0, SUB_1, 0, 0);
+
+ // Exit ECBM
+ mContext.sendStickyBroadcast(getIntent(false, SLOT_0));
+
+ assertFalse(mEcbmHelper.isInEmergencyCallbackMode(SLOT_0));
+ }
+
+ private static Intent getIntent(boolean inEcm, int slotIndex) {
+ Intent intent = new Intent(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+ intent.putExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, inEcm);
+ intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, slotIndex);
+ return intent;
+ }
+
+ private static PersistableBundle getPersistableBundle(boolean supported) {
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putBoolean(KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL, supported);
+ return bundle;
+ }
+
+ private static void logd(String str) {
+ Log.d(TAG, str);
+ }
+}
diff --git a/tests/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelectorTest.java b/tests/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelectorTest.java
index ed064cb..a900fda 100644
--- a/tests/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelectorTest.java
+++ b/tests/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelectorTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
@@ -39,16 +40,17 @@
import android.telephony.CarrierConfigManager;
import android.telephony.DataSpecificRegistrationInfo;
import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.EmergencyRegistrationResult;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.telephony.TransportSelectorCallback;
import android.telephony.VopsSupportInfo;
import android.telephony.WwanSelectorCallback;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableLooper;
import android.util.SparseArray;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.TestContext;
@@ -92,6 +94,7 @@
private ImsStateTracker.BarringInfoListener mBarringInfoListener;
private ImsStateTracker.ServiceStateListener mServiceStateListener;
private EmergencySmsDomainSelector mDomainSelector;
+ private EmergencyRegistrationResult mEmergencyRegistrationResult;
@Before
public void setUp() throws Exception {
@@ -151,6 +154,7 @@
mLooper = null;
}
+ mEmergencyRegistrationResult = null;
mDomainSelector = null;
mNetworkRegistrationInfo = null;
mVopsSupportInfo = null;
@@ -246,6 +250,26 @@
@Test
@SmallTest
+ public void testIsSmsOverImsAvailableWhenImsRegisteredAndConfigEnabledAndNrAvailable() {
+ setUpImsStateTracker(AccessNetworkType.NGRAN);
+ setUpCarrierConfig(true);
+ setUpNrInService(false, false, true, false);
+
+ assertTrue(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenImsRegisteredAndConfigEnabledAndNrNotAvailable() {
+ setUpImsStateTracker(AccessNetworkType.NGRAN);
+ setUpCarrierConfig(true);
+ setUpNrInService(false, false, false, false);
+
+ assertFalse(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
public void testIsSmsOverImsAvailableWhenCarrierConfigManagerIsNull() {
setUpImsStateTracker(AccessNetworkType.UNKNOWN);
mCarrierConfigManagerNullTest = true;
@@ -291,7 +315,7 @@
@Test
@SmallTest
- public void testIsSmsOverImsAvailableWhenNoLte() {
+ public void testIsSmsOverImsAvailableWhenNoLteOrNr() {
setUpImsStateTracker(AccessNetworkType.UNKNOWN);
setUpCarrierConfig(true);
mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
@@ -395,6 +419,56 @@
@Test
@SmallTest
+ public void testIsSmsOverImsAvailableWhenNrNotRegisteredOrEmergencyNotEnabled() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpCarrierConfig(true);
+ setUpNrInService(false, false, false, false);
+
+ assertFalse(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenNrInServiceAndNoDataSpecificRegistrationInfo() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpCarrierConfig(true);
+ setUpNrInService(true, true, false, false);
+
+ assertFalse(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenNrInServiceAndNoVopsSupportInfo() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpCarrierConfig(true);
+ setUpNrInService(false, true, false, false);
+
+ assertFalse(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenNrInServiceAndEmergencyServiceSupported() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpCarrierConfig(true);
+ setUpNrInService(false, false, true, false);
+
+ assertTrue(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenNrInServiceAndEmergencyServiceFallbackSupported() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpCarrierConfig(true);
+ setUpNrInService(false, false, false, true);
+
+ assertTrue(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
public void testSelectDomainWhilePreviousRequestInProgress() {
setUpImsStateTracker(AccessNetworkType.EUTRAN);
setUpWwanSelectorCallback();
@@ -675,6 +749,116 @@
eq(true));
}
+ @Test
+ @SmallTest
+ public void testSelectDomainWhileEmergencyNetworkScanInProgress() {
+ setUpImsStateTracker(AccessNetworkType.NGRAN);
+ setUpEmergencyRegResult(AccessNetworkType.NGRAN, NetworkRegistrationInfo.DOMAIN_PS, 1, 0);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpNrInService(false, false, false, true);
+ setUpImsStateListener(true, true, true);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ // Call the domain selection before completing the emergency network scan.
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // onRequestEmergencyNetworkScan is invoked only once.
+ verify(mWwanSelectorCallback).onRequestEmergencyNetworkScan(any(), anyInt(),
+ anyBoolean(), any(), any());
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenNrEmergencyServiceSupported() {
+ setUpImsStateTracker(AccessNetworkType.NGRAN);
+ setUpEmergencyRegResult(AccessNetworkType.NGRAN, NetworkRegistrationInfo.DOMAIN_PS, 1, 0);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpNrInService(false, false, true, false);
+ setUpImsStateListener(true, true, true);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: PS network
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS),
+ eq(true));
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenEmergencyRegistrationResultNgranAndPsDomain() {
+ setUpImsStateTracker(AccessNetworkType.NGRAN);
+ setUpEmergencyRegResult(AccessNetworkType.NGRAN, NetworkRegistrationInfo.DOMAIN_PS, 1, 0);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpNrInService(false, false, false, true);
+ setUpImsStateListener(true, true, true);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: PS network
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS),
+ eq(true));
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenEmergencyRegistrationResultEutranAndPsDomain() {
+ setUpImsStateTracker(AccessNetworkType.NGRAN);
+ setUpEmergencyRegResult(AccessNetworkType.EUTRAN, NetworkRegistrationInfo.DOMAIN_PS, 0, 0);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpNrInService(false, false, false, true);
+ setUpImsStateListener(true, true, true);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: PS network
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS),
+ eq(true));
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenEmergencyRegistrationResultEutranAndCsDomain() {
+ setUpImsStateTracker(AccessNetworkType.NGRAN);
+ setUpEmergencyRegResult(AccessNetworkType.EUTRAN, NetworkRegistrationInfo.DOMAIN_CS, 0, 0);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpNrInService(false, false, false, true);
+ setUpImsStateListener(true, true, true);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: CS network
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS),
+ eq(false));
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenEmergencyRegistrationResultUtranAndCsDomain() {
+ setUpImsStateTracker(AccessNetworkType.NGRAN);
+ setUpEmergencyRegResult(AccessNetworkType.UTRAN, NetworkRegistrationInfo.DOMAIN_CS, 0, 0);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpNrInService(false, false, false, true);
+ setUpImsStateListener(true, true, true);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: CS network
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS),
+ eq(false));
+ }
+
private void setUpCarrierConfig(boolean supported) {
PersistableBundle b = new PersistableBundle();
b.putBoolean(CarrierConfigManager.KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL, supported);
@@ -760,6 +944,29 @@
mBarringInfoListener.onBarringInfoUpdated(barringInfo);
}
+ private void setUpNrInService(boolean noDataSpecificRegistrationInfo,
+ boolean noVopsSupportInfo, boolean emergencyServiceSupported,
+ boolean emergencyServiceFallbackSupported) {
+ DataSpecificRegistrationInfo dsri = noDataSpecificRegistrationInfo
+ ? null : new DataSpecificRegistrationInfo(
+ 8, false, false, false, noVopsSupportInfo ? null : mVopsSupportInfo);
+
+ mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+ .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_NR)
+ .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+ .setDataSpecificInfo(dsri)
+ .build();
+ when(mServiceState.getNetworkRegistrationInfo(
+ anyInt(), eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
+ .thenReturn(mNetworkRegistrationInfo);
+ when(mVopsSupportInfo.isEmergencyServiceSupported()).thenReturn(emergencyServiceSupported);
+ when(mVopsSupportInfo.isEmergencyServiceFallbackSupported())
+ .thenReturn(emergencyServiceFallbackSupported);
+
+ mServiceStateListener.onServiceStateUpdated(mServiceState);
+ mBarringInfoListener.onBarringInfoUpdated(null);
+ }
+
private void setUpImsStateTracker(@RadioAccessNetworkType int accessNetworkType) {
setUpImsStateTracker(accessNetworkType, true, true);
}
@@ -783,6 +990,23 @@
callback.accept(mWwanSelectorCallback);
return null;
}).when(mTransportSelectorCallback).onWwanSelected(any(Consumer.class));
+
+ doAnswer((invocation) -> {
+ Object[] args = invocation.getArguments();
+ final Consumer<EmergencyRegistrationResult> result =
+ (Consumer<EmergencyRegistrationResult>) args[4];
+ result.accept(mEmergencyRegistrationResult);
+ return null;
+ }).when(mWwanSelectorCallback).onRequestEmergencyNetworkScan(
+ any(), anyInt(), anyBoolean(), any(), any());
+ }
+
+ private void setUpEmergencyRegResult(
+ @AccessNetworkConstants.RadioAccessNetworkType int accessNetwork,
+ @NetworkRegistrationInfo.Domain int domain, int nrEs, int nrEsfb) {
+ mEmergencyRegistrationResult = new EmergencyRegistrationResult(accessNetwork,
+ NetworkRegistrationInfo.REGISTRATION_STATE_HOME,
+ domain, true, true, nrEs, nrEsfb, "001", "01", "");
}
private void setUpImsStateListener(boolean notifyMmTelFeatureAvailable,
diff --git a/tests/src/com/android/services/telephony/domainselection/ImsStateTrackerTest.java b/tests/src/com/android/services/telephony/domainselection/ImsStateTrackerTest.java
index 430adea..f9a56a8 100644
--- a/tests/src/com/android/services/telephony/domainselection/ImsStateTrackerTest.java
+++ b/tests/src/com/android/services/telephony/domainselection/ImsStateTrackerTest.java
@@ -33,6 +33,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.SystemClock;
import android.telephony.AccessNetworkConstants.AccessNetworkType;
import android.telephony.BarringInfo;
import android.telephony.ServiceState;
@@ -46,8 +47,8 @@
import android.telephony.ims.RegistrationManager;
import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities;
import android.telephony.ims.stub.ImsRegistrationImplBase;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.TestContext;
@@ -74,6 +75,7 @@
private static final int SUB_1 = 1;
private static final int SUB_2 = 2;
private static final long TIMEOUT_MS = 100;
+ private static final long MSG_PROCESS_DELAY_MS = 10;
@Mock private ImsMmTelManager mMmTelManager;
@Mock private ImsMmTelManager mMmTelManager2;
@@ -262,6 +264,9 @@
@Test
@SmallTest
public void testAddAndRemoveServiceStateListener() {
+ mImsStateTracker.getHandler().post(() -> {
+ SystemClock.sleep(MSG_PROCESS_DELAY_MS);
+ });
mImsStateTracker.updateServiceState(mServiceState);
mImsStateTracker.addServiceStateListener(mServiceStateListener);
mImsStateTracker.removeServiceStateListener(mServiceStateListener);
@@ -307,6 +312,9 @@
@Test
@SmallTest
public void testAddAndRemoveBarringInfoListener() {
+ mImsStateTracker.getHandler().post(() -> {
+ SystemClock.sleep(MSG_PROCESS_DELAY_MS);
+ });
mImsStateTracker.updateBarringInfo(mBarringInfo);
mImsStateTracker.addBarringInfoListener(mBarringInfoListener);
mImsStateTracker.removeBarringInfoListener(mBarringInfoListener);
diff --git a/tests/src/com/android/services/telephony/domainselection/NormalCallDomainSelectorTest.java b/tests/src/com/android/services/telephony/domainselection/NormalCallDomainSelectorTest.java
index 4dd1f3c..6e438bf 100644
--- a/tests/src/com/android/services/telephony/domainselection/NormalCallDomainSelectorTest.java
+++ b/tests/src/com/android/services/telephony/domainselection/NormalCallDomainSelectorTest.java
@@ -17,7 +17,6 @@
package com.android.services.telephony.domainselection;
import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
-import static android.telephony.DomainSelectionService.SELECTOR_TYPE_UT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -28,17 +27,19 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.net.Uri;
import android.os.CancellationSignal;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.PersistableBundle;
+import android.telecom.PhoneAccount;
import android.telecom.TelecomManager;
import android.telephony.AccessNetworkConstants;
import android.telephony.CarrierConfigManager;
import android.telephony.DisconnectCause;
import android.telephony.DomainSelectionService;
import android.telephony.DomainSelector;
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
@@ -69,10 +70,12 @@
public class NormalCallDomainSelectorTest {
private static final String TAG = "NormalCallDomainSelectorTest";
+ private static final int SELECTOR_TYPE_UT = 3;
private static final int SLOT_ID = 0;
private static final int SUB_ID_1 = 1;
private static final int SUB_ID_2 = 2;
private static final String TEST_CALLID = "01234";
+ private static final Uri TEST_URI = Uri.fromParts(PhoneAccount.SCHEME_TEL, "123456789", null);
private HandlerThread mHandlerThread;
private NormalCallDomainSelector mNormalCallDomainSelector;
@@ -150,13 +153,54 @@
}
@Test
- public void testSelectDomainInputParams() {
+ public void testInitialState() {
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.INACTIVE);
+ }
+
+ @Test
+ public void testDestroyedState() {
+ mNormalCallDomainSelector.destroy();
+
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.DESTROYED);
+ }
+
+ @Test
+ public void testDestroyedDuringActiveState() {
MockTransportSelectorCallback transportSelectorCallback =
- new MockTransportSelectorCallback();
+ new MockTransportSelectorCallback(mNormalCallDomainSelector);
DomainSelectionService.SelectionAttributes attributes =
new DomainSelectionService.SelectionAttributes.Builder(
SLOT_ID, SUB_ID_1, SELECTOR_TYPE_CALLING)
+ .setAddress(TEST_URI)
+ .setCallId(TEST_CALLID)
+ .setEmergency(false)
+ .setVideoCall(true)
+ .setExitedFromAirplaneMode(false)
+ .build();
+
+ mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.ACTIVE);
+
+ mNormalCallDomainSelector.destroy();
+
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.DESTROYED);
+ }
+
+ @Test
+ public void testSelectDomainInputParams() {
+ MockTransportSelectorCallback transportSelectorCallback =
+ new MockTransportSelectorCallback(mNormalCallDomainSelector);
+
+ DomainSelectionService.SelectionAttributes attributes =
+ new DomainSelectionService.SelectionAttributes.Builder(
+ SLOT_ID, SUB_ID_1, SELECTOR_TYPE_CALLING)
+ .setAddress(TEST_URI)
.setCallId(TEST_CALLID)
.setEmergency(false)
.setVideoCall(true)
@@ -164,6 +208,8 @@
.build();
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.ACTIVE);
// Case 1: null inputs
try {
@@ -172,6 +218,9 @@
fail("Invalid input params not handled." + e.getMessage());
}
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.INACTIVE);
+
// Case 2: null TransportSelectorCallback
try {
mNormalCallDomainSelector.selectDomain(attributes, null);
@@ -179,6 +228,9 @@
fail("Invalid params (SelectionAttributes) not handled." + e.getMessage());
}
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.INACTIVE);
+
// Case 3: null SelectionAttributes
transportSelectorCallback.mSelectionTerminated = false;
try {
@@ -190,9 +242,13 @@
assertTrue(transportSelectorCallback
.verifyOnSelectionTerminated(DisconnectCause.OUTGOING_FAILURE));
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.DESTROYED);
+
// Case 4: Invalid Subscription-id
attributes = new DomainSelectionService.SelectionAttributes.Builder(
SLOT_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID, SELECTOR_TYPE_CALLING)
+ .setAddress(TEST_URI)
.setCallId(TEST_CALLID)
.setEmergency(false)
.setVideoCall(true)
@@ -207,10 +263,14 @@
assertTrue(transportSelectorCallback
.verifyOnSelectionTerminated(DisconnectCause.OUTGOING_FAILURE));
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.DESTROYED);
+
// Case 5: Invalid SELECTOR_TYPE
attributes =
new DomainSelectionService.SelectionAttributes.Builder(
SLOT_ID, SUB_ID_1, SELECTOR_TYPE_UT)
+ .setAddress(TEST_URI)
.setCallId(TEST_CALLID)
.setEmergency(false)
.setVideoCall(true)
@@ -225,9 +285,13 @@
assertTrue(transportSelectorCallback
.verifyOnSelectionTerminated(DisconnectCause.OUTGOING_FAILURE));
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.DESTROYED);
+
// Case 6: Emergency Call
attributes = new DomainSelectionService.SelectionAttributes.Builder(
SLOT_ID, SUB_ID_1, SELECTOR_TYPE_CALLING)
+ .setAddress(TEST_URI)
.setCallId(TEST_CALLID)
.setEmergency(true)
.setVideoCall(true)
@@ -239,6 +303,9 @@
fail("Invalid params (SelectionAttributes) not handled." + e.getMessage());
}
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.DESTROYED);
+
assertTrue(transportSelectorCallback
.verifyOnSelectionTerminated(DisconnectCause.OUTGOING_FAILURE));
}
@@ -246,30 +313,38 @@
@Test
public void testOutOfService() {
MockTransportSelectorCallback transportSelectorCallback =
- new MockTransportSelectorCallback();
+ new MockTransportSelectorCallback(mNormalCallDomainSelector);
DomainSelectionService.SelectionAttributes attributes =
new DomainSelectionService.SelectionAttributes.Builder(
SLOT_ID, SUB_ID_1, SELECTOR_TYPE_CALLING)
+ .setAddress(TEST_URI)
.setCallId(TEST_CALLID)
.setEmergency(false)
.setVideoCall(true)
.setExitedFromAirplaneMode(false)
.build();
+
ServiceState serviceState = new ServiceState();
serviceState.setStateOutOfService();
initialize(serviceState, false, false, false, false);
+
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+
assertTrue(transportSelectorCallback
.verifyOnSelectionTerminated(DisconnectCause.OUT_OF_SERVICE));
+
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.DESTROYED);
}
@Test
public void testDomainSelection() {
MockTransportSelectorCallback transportSelectorCallback =
- new MockTransportSelectorCallback();
+ new MockTransportSelectorCallback(mNormalCallDomainSelector);
DomainSelectionService.SelectionAttributes attributes =
new DomainSelectionService.SelectionAttributes.Builder(
SLOT_ID, SUB_ID_1, SELECTOR_TYPE_CALLING)
+ .setAddress(TEST_URI)
.setCallId(TEST_CALLID)
.setEmergency(false)
.setVideoCall(false)
@@ -280,32 +355,49 @@
ServiceState serviceState = new ServiceState();
serviceState.setState(ServiceState.STATE_IN_SERVICE);
initialize(serviceState, true, true, true, true);
+
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+
assertTrue(transportSelectorCallback.verifyOnWlanSelected());
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.INACTIVE);
+
// Case 2: 5G
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+
initialize(serviceState, true, false, true, true);
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+
assertTrue(transportSelectorCallback.verifyOnWwanSelected());
+
assertTrue(transportSelectorCallback
.verifyOnDomainSelected(NetworkRegistrationInfo.DOMAIN_PS));
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.INACTIVE);
+
// Case 3: PS -> CS redial
ImsReasonInfo imsReasonInfo = new ImsReasonInfo();
imsReasonInfo.mCode = ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED;
attributes = new DomainSelectionService.SelectionAttributes.Builder(
SLOT_ID, SUB_ID_1, SELECTOR_TYPE_CALLING)
+ .setAddress(TEST_URI)
.setCallId(TEST_CALLID)
.setEmergency(false)
.setVideoCall(false)
.setExitedFromAirplaneMode(false)
.setPsDisconnectCause(imsReasonInfo)
.build();
+
mNormalCallDomainSelector.reselectDomain(attributes);
+
assertTrue(transportSelectorCallback
.verifyOnDomainSelected(NetworkRegistrationInfo.DOMAIN_CS));
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.INACTIVE);
+
// Case 4: CS call
NetworkRegistrationInfo nwRegistrationInfo = new NetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
@@ -313,28 +405,40 @@
AccessNetworkConstants.AccessNetworkType.UTRAN, 0, false,
null, null, null, false, 0, 0, 0);
serviceState.addNetworkRegistrationInfo(nwRegistrationInfo);
+
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+
initialize(serviceState, false, false, false, false);
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+
assertTrue(transportSelectorCallback.verifyOnWwanSelected());
+
assertTrue(transportSelectorCallback
.verifyOnDomainSelected(NetworkRegistrationInfo.DOMAIN_CS));
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.INACTIVE);
+
//Case 5: Backup calling
serviceState.setStateOutOfService();
initialize(serviceState, true, true, true, true);
+
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+
assertTrue(transportSelectorCallback.verifyOnWlanSelected());
+
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.ACTIVE);
}
@Test
public void testWPSCallDomainSelection() {
MockTransportSelectorCallback transportSelectorCallback =
- new MockTransportSelectorCallback();
+ new MockTransportSelectorCallback(mNormalCallDomainSelector);
DomainSelectionService.SelectionAttributes attributes =
new DomainSelectionService.SelectionAttributes.Builder(
SLOT_ID, SUB_ID_1, SELECTOR_TYPE_CALLING)
- .setNumber("*272121")
+ .setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, "*272121", null))
.setCallId(TEST_CALLID)
.setEmergency(false)
.setVideoCall(false)
@@ -344,39 +448,59 @@
//Case 1: WPS not supported by IMS
PersistableBundle config = new PersistableBundle();
config.putBoolean(CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL, false);
- doReturn(config).when(mMockCarrierConfigMgr).getConfigForSubId(SUB_ID_1);
+ doReturn(config).when(mMockCarrierConfigMgr).getConfigForSubId(SUB_ID_1,
+ new String[]{CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL});
+
ServiceState serviceState = new ServiceState();
serviceState.setState(ServiceState.STATE_IN_SERVICE);
initialize(serviceState, true, true, true, true);
+
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+
assertTrue(transportSelectorCallback.verifyOnWwanSelected());
+
assertTrue(transportSelectorCallback
.verifyOnDomainSelected(NetworkRegistrationInfo.DOMAIN_CS));
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.INACTIVE);
+
//Case 2: WPS supported by IMS and WLAN registered
config.putBoolean(CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL, true);
serviceState.setState(ServiceState.STATE_IN_SERVICE);
initialize(serviceState, true, true, true, true);
+
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+
assertTrue(transportSelectorCallback.verifyOnWlanSelected());
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.INACTIVE);
+
//Case 2: WPS supported by IMS and LTE registered
config.putBoolean(CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL, true);
serviceState.setState(ServiceState.STATE_IN_SERVICE);
initialize(serviceState, true, false, true, true);
+
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+
assertTrue(transportSelectorCallback.verifyOnWwanSelected());
+
assertTrue(transportSelectorCallback
.verifyOnDomainSelected(NetworkRegistrationInfo.DOMAIN_PS));
+
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.INACTIVE);
}
@Test
public void testTtyCallDomainSelection() {
MockTransportSelectorCallback transportSelectorCallback =
- new MockTransportSelectorCallback();
+ new MockTransportSelectorCallback(mNormalCallDomainSelector);
DomainSelectionService.SelectionAttributes attributes =
new DomainSelectionService.SelectionAttributes.Builder(
SLOT_ID, SUB_ID_1, SELECTOR_TYPE_CALLING)
+ .setAddress(TEST_URI)
.setCallId(TEST_CALLID)
.setEmergency(false)
.setVideoCall(false)
@@ -387,30 +511,50 @@
doReturn(TelecomManager.TTY_MODE_FULL).when(mMockTelecomManager).getCurrentTtyMode();
PersistableBundle config = new PersistableBundle();
config.putBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, false);
- doReturn(config).when(mMockCarrierConfigMgr).getConfigForSubId(SUB_ID_1);
+ doReturn(config).when(mMockCarrierConfigMgr).getConfigForSubId(SUB_ID_1,
+ new String[]{CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL});
+
ServiceState serviceState = new ServiceState();
serviceState.setState(ServiceState.STATE_IN_SERVICE);
initialize(serviceState, true, false, true, true);
+
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+
assertTrue(transportSelectorCallback.verifyOnWwanSelected());
+
assertTrue(transportSelectorCallback
.verifyOnDomainSelected(NetworkRegistrationInfo.DOMAIN_CS));
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.INACTIVE);
+
//Case 2: TTY supported by IMS and TTY enabled
config.putBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true);
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+
assertTrue(transportSelectorCallback.verifyOnWwanSelected());
+
assertTrue(transportSelectorCallback
.verifyOnDomainSelected(NetworkRegistrationInfo.DOMAIN_PS));
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.INACTIVE);
+
//Case 3: TTY supported by IMS and TTY disabled
doReturn(TelecomManager.TTY_MODE_OFF).when(mMockTelecomManager).getCurrentTtyMode();
mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+
assertTrue(transportSelectorCallback.verifyOnWwanSelected());
+
assertTrue(transportSelectorCallback
.verifyOnDomainSelected(NetworkRegistrationInfo.DOMAIN_PS));
+
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.INACTIVE);
}
+
+
static class MockTransportSelectorCallback implements TransportSelectorCallback,
WwanSelectorCallback {
public boolean mCreated;
@@ -420,11 +564,20 @@
public boolean mDomainSelected;
int mCauseCode;
int mSelectedDomain;
+ NormalCallDomainSelector mNormalCallDomainSelector;
+
+ MockTransportSelectorCallback(NormalCallDomainSelector normalCallDomainSelector) {
+ mNormalCallDomainSelector = normalCallDomainSelector;
+ }
@Override
public synchronized void onCreated(DomainSelector selector) {
Log.d(TAG, "onCreated");
mCreated = true;
+
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.INACTIVE);
+
notifyAll();
}
@@ -439,6 +592,10 @@
public synchronized void onWlanSelected(boolean useEmergencyPdn) {
Log.d(TAG, "onWlanSelected");
mWlanSelected = true;
+
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.INACTIVE);
+
notifyAll();
}
@@ -449,18 +606,14 @@
}
@Override
- public synchronized WwanSelectorCallback onWwanSelected() {
- mWwanSelected = true;
- notifyAll();
- return (WwanSelectorCallback) this;
- }
-
- @Override
public void onWwanSelected(final Consumer<WwanSelectorCallback> consumer) {
mWwanSelected = true;
Executors.newSingleThreadExecutor().execute(() -> {
consumer.accept(this);
});
+
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.INACTIVE);
}
public boolean verifyOnWwanSelected() {
@@ -473,6 +626,10 @@
Log.i(TAG, "onSelectionTerminated - called");
mCauseCode = cause;
mSelectionTerminated = true;
+
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.INACTIVE);
+
notifyAll();
}
@@ -497,9 +654,10 @@
@Override
public void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
- int scanType,
- @NonNull CancellationSignal signal,
- @NonNull Consumer<EmergencyRegResult> consumer) {
+ int scanType,
+ boolean resetScan,
+ @NonNull CancellationSignal signal,
+ @NonNull Consumer<EmergencyRegistrationResult> consumer) {
Log.i(TAG, "onRequestEmergencyNetworkScan - called");
}
@@ -509,6 +667,10 @@
Log.i(TAG, "onDomainSelected - called");
mSelectedDomain = domain;
mDomainSelected = true;
+
+ assertEquals(mNormalCallDomainSelector.getSelectorState(),
+ NormalCallDomainSelector.SelectorState.INACTIVE);
+
notifyAll();
}
diff --git a/tests/src/com/android/services/telephony/domainselection/OWNERS b/tests/src/com/android/services/telephony/domainselection/OWNERS
new file mode 100644
index 0000000..2a76770
--- /dev/null
+++ b/tests/src/com/android/services/telephony/domainselection/OWNERS
@@ -0,0 +1,9 @@
+# automatically inherit owners from fw/opt/telephony
+
+hwangoo@google.com
+forestchoi@google.com
+avinashmp@google.com
+mkoon@google.com
+seheele@google.com
+radhikaagrawal@google.com
+jdyou@google.com
diff --git a/tests/src/com/android/services/telephony/domainselection/SmsDomainSelectorTest.java b/tests/src/com/android/services/telephony/domainselection/SmsDomainSelectorTest.java
index 8f78a58..fc577c4 100644
--- a/tests/src/com/android/services/telephony/domainselection/SmsDomainSelectorTest.java
+++ b/tests/src/com/android/services/telephony/domainselection/SmsDomainSelectorTest.java
@@ -37,9 +37,9 @@
import android.telephony.NetworkRegistrationInfo;
import android.telephony.TransportSelectorCallback;
import android.telephony.WwanSelectorCallback;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableLooper;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.TestContext;
@@ -190,21 +190,6 @@
@Test
@SmallTest
- public void testCancelSelection() {
- setUpImsStateTracker(AccessNetworkType.EUTRAN);
-
- mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
-
- assertTrue(mDomainSelector.isDomainSelectionRequested());
-
- mDomainSelector.cancelSelection();
-
- assertFalse(mDomainSelector.isDomainSelectionRequested());
- verify(mDomainSelectorDestroyListener).onDomainSelectorDestroyed(eq(mDomainSelector));
- }
-
- @Test
- @SmallTest
public void testFinishSelection() {
setUpImsStateTracker(AccessNetworkType.EUTRAN);
diff --git a/tests/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionServiceTest.java b/tests/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionServiceTest.java
index f340e94..40a4616 100644
--- a/tests/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionServiceTest.java
@@ -20,12 +20,14 @@
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.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
@@ -38,12 +40,13 @@
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TransportSelectorCallback;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableLooper;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.TestContext;
+import com.android.internal.telephony.flags.Flags;
import org.junit.After;
import org.junit.Before;
@@ -78,11 +81,11 @@
@SelectorType int selectorType, boolean isEmergency,
@NonNull Looper looper, @NonNull ImsStateTracker imsStateTracker,
@NonNull DomainSelectorBase.DestroyListener listener,
- @NonNull CrossSimRedialingController crossSimRedialingController) {
+ @NonNull CrossSimRedialingController crossSimRedialingController,
+ @NonNull EmergencyCallbackModeHelper ecbmHelper) {
switch (selectorType) {
case DomainSelectionService.SELECTOR_TYPE_CALLING: // fallthrough
- case DomainSelectionService.SELECTOR_TYPE_SMS: // fallthrough
- case DomainSelectionService.SELECTOR_TYPE_UT:
+ case DomainSelectionService.SELECTOR_TYPE_SMS:
mDomainSelectorDestroyListener = listener;
if (subId == SUB_1) {
return mDomainSelectorBase1;
@@ -94,6 +97,25 @@
}
}
};
+ private static class TestTelephonyDomainSelectionService
+ extends TelephonyDomainSelectionService {
+ private final Context mContext;
+
+ TestTelephonyDomainSelectionService(Context context,
+ @NonNull ImsStateTrackerFactory imsStateTrackerFactory,
+ @NonNull DomainSelectorFactory domainSelectorFactory,
+ @Nullable EmergencyCallbackModeHelper ecbmHelper) {
+ super(imsStateTrackerFactory, domainSelectorFactory, ecbmHelper);
+ mContext = context;
+ }
+
+ @Override
+ public void onCreate() {
+ // attach test context.
+ attachBaseContext(mContext);
+ super.onCreate();
+ }
+ }
private static final int SLOT_0 = 0;
private static final int SUB_1 = 1;
private static final int SUB_2 = 2;
@@ -107,6 +129,7 @@
@Mock private TransportSelectorCallback mSelectorCallback1;
@Mock private TransportSelectorCallback mSelectorCallback2;
@Mock private ImsStateTracker mImsStateTracker;
+ @Mock private EmergencyCallbackModeHelper mEcbmHelper;
private final ServiceState mServiceState = new ServiceState();
private final BarringInfo mBarringInfo = new BarringInfo();
@@ -127,12 +150,16 @@
}
mContext = new TestContext();
- mDomainSelectionService = new TelephonyDomainSelectionService(mContext,
- mImsStateTrackerFactory, mDomainSelectorFactory);
+ mDomainSelectionService = new TestTelephonyDomainSelectionService(mContext,
+ mImsStateTrackerFactory, mDomainSelectorFactory, mEcbmHelper);
+ mDomainSelectionService.onCreate();
mServiceHandler = new Handler(mDomainSelectionService.getLooper());
mTestableLooper = new TestableLooper(mDomainSelectionService.getLooper());
mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
+ if (Flags.workProfileApiSplit()) {
+ doReturn(mSubscriptionManager).when(mSubscriptionManager).createForAllUserProfiles();
+ }
ArgumentCaptor<OnSubscriptionsChangedListener> listenerCaptor =
ArgumentCaptor.forClass(OnSubscriptionsChangedListener.class);
verify(mSubscriptionManager).addOnSubscriptionsChangedListener(
@@ -177,9 +204,7 @@
.setCallId(CALL_ID)
.setEmergency(true)
.build();
- mServiceHandler.post(() -> {
- mDomainSelectionService.onDomainSelection(attr1, mSelectorCallback1);
- });
+ mDomainSelectionService.onDomainSelection(attr1, mSelectorCallback1);
processAllMessages();
verify(mImsStateTracker).start(eq(SUB_1));
@@ -196,9 +221,7 @@
.setCallId(CALL_ID)
.setEmergency(true)
.build();
- mServiceHandler.post(() -> {
- mDomainSelectionService.onDomainSelection(attr1, mSelectorCallback1);
- });
+ mDomainSelectionService.onDomainSelection(attr1, mSelectorCallback1);
processAllMessages();
verify(mImsStateTracker, never()).start(anyInt());
@@ -215,9 +238,7 @@
.setCallId(CALL_ID)
.setEmergency(true)
.build();
- mServiceHandler.post(() -> {
- mDomainSelectionService.onDomainSelection(attr1, mSelectorCallback1);
- });
+ mDomainSelectionService.onDomainSelection(attr1, mSelectorCallback1);
processAllMessages();
verify(mImsStateTracker).start(eq(SUB_1));
@@ -233,9 +254,7 @@
.setCallId(CALL_ID)
.setEmergency(true)
.build();
- mServiceHandler.post(() -> {
- mDomainSelectionService.onDomainSelection(attr2, mSelectorCallback2);
- });
+ mDomainSelectionService.onDomainSelection(attr2, mSelectorCallback2);
processAllMessages();
verify(mImsStateTracker).start(eq(SUB_2));
@@ -252,9 +271,7 @@
.setCallId(CALL_ID)
.setEmergency(true)
.build();
- mServiceHandler.post(() -> {
- mDomainSelectionService.onDomainSelection(attr1, mSelectorCallback1);
- });
+ mDomainSelectionService.onDomainSelection(attr1, mSelectorCallback1);
processAllMessages();
verify(mImsStateTracker).start(eq(SUB_1));
@@ -267,9 +284,7 @@
.setCallId(CALL_ID)
.setEmergency(true)
.build();
- mServiceHandler.post(() -> {
- mDomainSelectionService.onDomainSelection(attr2, mSelectorCallback2);
- });
+ mDomainSelectionService.onDomainSelection(attr2, mSelectorCallback2);
processAllMessages();
verify(mImsStateTracker).start(eq(SUB_2));
@@ -302,9 +317,7 @@
.setCallId(CALL_ID)
.setEmergency(true)
.build();
- mServiceHandler.post(() -> {
- mDomainSelectionService.onDomainSelection(attr1, mSelectorCallback1);
- });
+ mDomainSelectionService.onDomainSelection(attr1, mSelectorCallback1);
processAllMessages();
mDomainSelectionService.onDestroy();
diff --git a/tests/src/com/android/services/telephony/rcs/TelephonyRcsServiceTest.java b/tests/src/com/android/services/telephony/rcs/TelephonyRcsServiceTest.java
index d575d77..4cabf95 100644
--- a/tests/src/com/android/services/telephony/rcs/TelephonyRcsServiceTest.java
+++ b/tests/src/com/android/services/telephony/rcs/TelephonyRcsServiceTest.java
@@ -39,6 +39,7 @@
import com.android.ims.FeatureConnector;
import com.android.ims.RcsFeatureManager;
import com.android.internal.telephony.ISub;
+import com.android.internal.telephony.flags.FeatureFlags;
import org.junit.After;
import org.junit.Before;
@@ -68,6 +69,8 @@
@Mock
private TelephonyManager mTelephonyManager;
+ @Mock FeatureFlags mFeatureFlags;
+
private RcsFeatureController mFeatureControllerSlot0;
private RcsFeatureController mFeatureControllerSlot1;
@@ -91,9 +94,9 @@
doReturn(mFeatureControllerSlot1).when(mFeatureFactory).createController(any(), eq(1),
anyInt());
doReturn(mMockUceSlot0).when(mFeatureFactory).createUceControllerManager(any(), eq(0),
- anyInt());
+ anyInt(), any());
doReturn(mMockUceSlot1).when(mFeatureFactory).createUceControllerManager(any(), eq(1),
- anyInt());
+ anyInt(), any());
doReturn(mMockSipTransportSlot0).when(mFeatureFactory).createSipTransportController(any(),
eq(0), anyInt());
doReturn(mMockSipTransportSlot1).when(mFeatureFactory).createSipTransportController(any(),
@@ -363,7 +366,8 @@
}
private TelephonyRcsService createRcsService(int numSlots) {
- TelephonyRcsService service = new TelephonyRcsService(mContext, numSlots, mResourceProxy);
+ TelephonyRcsService service = new TelephonyRcsService(mContext, numSlots, mResourceProxy,
+ mFeatureFlags);
service.setFeatureFactory(mFeatureFactory);
service.initialize();
verify(mContext).registerReceiver(mReceiverCaptor.capture(), any());
diff --git a/tests/src/com/android/services/telephony/rcs/UceControllerManagerTest.java b/tests/src/com/android/services/telephony/rcs/UceControllerManagerTest.java
index 17decb9..e506931 100644
--- a/tests/src/com/android/services/telephony/rcs/UceControllerManagerTest.java
+++ b/tests/src/com/android/services/telephony/rcs/UceControllerManagerTest.java
@@ -35,6 +35,7 @@
import com.android.TestExecutorService;
import com.android.ims.RcsFeatureManager;
import com.android.ims.rcs.uce.UceController;
+import com.android.internal.telephony.flags.FeatureFlags;
import org.junit.After;
import org.junit.Before;
@@ -52,6 +53,7 @@
@Mock private UceController mUceController;
@Mock private RcsFeatureManager mRcsFeatureManager;
+ @Mock private FeatureFlags mFeatureFlags;
private final ExecutorService mExecutorService = new TestExecutorService();
@@ -260,7 +262,7 @@
private UceControllerManager getUceControllerManager() {
UceControllerManager manager = new UceControllerManager(mContext, mSlotId,
- mExecutorService, mUceController);
+ mExecutorService, mUceController, mFeatureFlags);
return manager;
}
}
diff --git a/utils/satellite/README.md b/utils/satellite/README.md
new file mode 100644
index 0000000..77ee0fb
--- /dev/null
+++ b/utils/satellite/README.md
@@ -0,0 +1,67 @@
+This directory contains code and tools for generating and debugging binary
+satellite s2 file.
+
+Directory structure
+=
+
+`s2storage`
+- `src/write` S2 write code used by tools to write the s2 cells into a
+ binary file. This code is also used by `TeleServiceTests`.
+- `src/readonly` S2 read-only code used by the above read-write code and the class
+ `S2RangeSatelliteOnDeviceAccessController`.
+
+`tools`
+- `src/main` Contains the tools for generating binary satellite s2 file, and tools
+ for dumping the binary file into human-readable format.
+- `src/test` Contains the test code for the tools.
+
+Run unit tests
+=
+- Build the tools and test code: Go to the tool directory (`packages/services/Telephony/tools/
+ satellite`) in the local workspace and run `mm`, e.g.,
+- Run unit tests: `$atest SatelliteToolsTests`
+
+Data file generate tools
+=
+
+`satellite_createsats2file`
+- Runs the `satellite_createsats2file` to create a binary satellite S2 file from a
+ list of S2 cells ID.
+- Command: `$satellite_createsats2file --input-file <s2cells.txt> --s2-level <12>
+ --is-allowed-list <true> --output-file <sats2.dat>`
+ - `--input-file` Each line in the file contains a `unsigned-64bit` number which represents
+ the ID of a S2 cell.
+ - `--s2-level` The S2 level of all the cells in the input file.
+ - `--is-allowed-list` Should be either `trrue` or `false`
+ - `true` The input file contains a list of S2 cells where satellite services are allowed.
+ - `false` The input file contains a list of S2 cells where satellite services are disallowed.
+ - `--output-file` The created binary satellite S2 file, which will be used by
+ the `SatelliteAccessController` module in determining if satellite communication
+ is allowed at a location.
+- Build the tools: Go to the tool directory (`packages/services/Telephony/tools/satellite`)
+ in the local workspace and run `mm`.
+- Example run command: `$satellite_createsats2file --input-file s2cells.txt --s2-level 12
+ --is-allowed-list true --output-file sats2.dat`
+
+Debug tools
+=
+
+`satellite_createsats2file_test`
+- Create a test binary satellite S2 file with the following ranges:
+ - [(prefix=0b100_11111111, suffix=1000), (prefix=0b100_11111111, suffix=2000))
+ - [(prefix=0b100_11111111, suffix=2000), (prefix=0b100_11111111, suffix=3000))
+ - [(prefix=0b101_11111111, suffix=1000), (prefix=0b101_11111111, suffix=2000))
+- Run the test tool: `satellite_createsats2file_test /tmp/foo.dat`
+ - This command will generate the binary satellite S2 cell file `/tmp/foo.dat` with
+ the above S2 ranges.
+
+`satellite_dumpsats2file`
+- Dump the input binary satellite S2 cell file into human-readable text format.
+- Run the tool: `$satellite_dumpsats2file /tmp/foo.dat /tmp/foo`
+ - `/tmp/foo.dat` Input binary satellite S2 cell file.
+ - `/tmp/foo` Output directory which contains the output text files.
+
+`satellite_location_lookup`
+- Check if a location is present in the input satellite S2 file.
+- Run the tool: `$satellite_location_lookup --input-file <...> --lat-degrees <...>
+ --lng-degrees <...>`
\ No newline at end of file
diff --git a/utils/satellite/s2storage/Android.bp b/utils/satellite/s2storage/Android.bp
new file mode 100644
index 0000000..64882ee
--- /dev/null
+++ b/utils/satellite/s2storage/Android.bp
@@ -0,0 +1,77 @@
+// 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.
+
+// Library for read-only access to Sat S2 data files.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Library for read-only access to satellite S2 data files.
+java_library {
+ name: "satellite-s2storage-ro",
+ host_supported: true,
+ srcs: [
+ "src/readonly/java/**/*.java",
+ ],
+ static_libs: [
+ "s2storage_ro",
+ ],
+}
+
+// Library for read/write access to satellite S2 data files.
+// This can be a java_library_host since it is used by satellite tools, which runs on host. However,
+// it is also used by the unit test S2RangeFileBasedSatelliteLocationLookupTest, which runs on
+// device. Thus, we need to make it a java_library to support the device-side usage,
+// `host_supported: true` to support the host-side usage.
+java_library {
+ name: "satellite-s2storage-rw",
+ host_supported: true,
+ srcs: [
+ "src/write/java/**/*.java",
+ ],
+ static_libs: [
+ "satellite-s2storage-ro",
+ "s2storage_rw",
+ ],
+}
+
+// Library for access to satellite S2 utils.
+java_library {
+ name: "satellite-s2storage-testutils",
+ host_supported: true,
+ srcs: [
+ "src/testutils/java/**/*.java",
+ ],
+ static_libs: [
+ "junit",
+ "satellite-s2storage-ro",
+ ],
+}
+
+// Tests for the satellite S2 storage code.
+java_test_host {
+ name: "SatelliteS2StorageTests",
+ srcs: ["src/test/java/**/*.java"],
+ static_libs: [
+ "junit",
+ "mockito",
+ "objenesis",
+ "satellite-s2storage-rw",
+ "satellite-s2storage-testutils",
+ ],
+ test_options: {
+ unit_test: true,
+ },
+ test_suites: ["general-tests"],
+}
\ No newline at end of file
diff --git a/utils/satellite/s2storage/TEST_MAPPING b/utils/satellite/s2storage/TEST_MAPPING
new file mode 100644
index 0000000..7d0fba8
--- /dev/null
+++ b/utils/satellite/s2storage/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "SatelliteS2StorageTests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/HeaderBlock.java b/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/HeaderBlock.java
new file mode 100644
index 0000000..9895d1a
--- /dev/null
+++ b/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/HeaderBlock.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range.read;
+
+import com.android.storage.block.read.BlockData;
+import com.android.storage.util.Visitor;
+
+/**
+ * Wraps a {@link BlockData}, interpreting it as a satellite S2 data file header (block 0). This
+ * class provides typed access to the information held in the header for use when reading a
+ * satellite S2 data file.
+ */
+public final class HeaderBlock {
+ /** Used for converting from bool type to int type */
+ public static final int TRUE = 1;
+ public static final int FALSE = 0;
+
+ private final SatS2RangeFileFormat mFileFormat;
+
+ private HeaderBlock(BlockData blockData) {
+ int offset = 0;
+
+ // Read the format information.
+ int dataS2Level = blockData.getUnsignedByte(offset++);
+ int prefixBitCount = blockData.getUnsignedByte(offset++);
+ int suffixBitCount = blockData.getUnsignedByte(offset++);
+ int suffixRecordBitCount = blockData.getUnsignedByte(offset++);
+ int suffixTableBlockIdOffset = blockData.getUnsignedByte(offset++);
+ boolean isAllowedList = (blockData.getUnsignedByte(offset) == TRUE);
+ mFileFormat = new SatS2RangeFileFormat(
+ dataS2Level, prefixBitCount, suffixBitCount, suffixTableBlockIdOffset,
+ suffixRecordBitCount, isAllowedList);
+ }
+
+ /** Creates a {@link HeaderBlock} from low-level block data from a block file. */
+ public static HeaderBlock wrap(BlockData blockData) {
+ return new HeaderBlock(blockData);
+ }
+
+ /** Returns the {@link SatS2RangeFileFormat} for the file. */
+ public SatS2RangeFileFormat getFileFormat() {
+ return mFileFormat;
+ }
+
+ /** A {@link Visitor} for the {@link HeaderBlock}. See {@link #visit} */
+ public interface HeaderBlockVisitor extends Visitor {
+
+ /** Called after {@link #begin()}, once. */
+ void visitFileFormat(SatS2RangeFileFormat fileFormat);
+ }
+
+ /**
+ * Issues callbacks to the supplied {@link HeaderBlockVisitor} containing information from the
+ * header block.
+ */
+ public void visit(HeaderBlockVisitor visitor) throws Visitor.VisitException {
+ try {
+ visitor.begin();
+ visitor.visitFileFormat(mFileFormat);
+ } finally {
+ visitor.end();
+ }
+ }
+}
diff --git a/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/PopulatedSuffixTableBlock.java b/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/PopulatedSuffixTableBlock.java
new file mode 100644
index 0000000..9aa56b2
--- /dev/null
+++ b/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/PopulatedSuffixTableBlock.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range.read;
+
+import static com.android.storage.s2.S2Support.MAX_FACE_ID;
+import static com.android.storage.s2.S2Support.cellIdToString;
+import static com.android.storage.util.Conditions.checkStateInRange;
+
+import com.android.storage.s2.S2LevelRange;
+import com.android.storage.table.packed.read.IntValueTypedPackedTable;
+import com.android.storage.table.reader.IntValueTable;
+
+import java.util.Objects;
+
+/**
+ * An implementation of {@link SuffixTableBlock.SuffixTableBlockDelegate} for tables that are backed
+ * by real block data, i.e. have one or more entries.
+ *
+ * <p>Logically, each populated suffix table block holds one or more entries for S2 ranges, e.g.:
+ * <pre>
+ * startCellId=X, endCellId=Y
+ * </pre>
+ *
+ * <p>The storage of the range entries is as follows:
+ * <ul>
+ * <li>The prefix bits are all the same so need not be stored in the individual entries. Only
+ * the suffix bits of X are stored. The prefix determines the block ID when first locating the
+ * suffix table, but it is also (redundantly) stored in the table's header for simplicity /
+ * easy debugging.</li>
+ * <li>Each range is expected to be relatively short, so Y is stored as a offset adjustment to
+ * X, i.e. Y is calculated by advancing X by {length of range} cell IDs.</li>
+ * </ul>
+ */
+final class PopulatedSuffixTableBlock implements SuffixTableBlock.SuffixTableBlockDelegate {
+
+ private final SatS2RangeFileFormat mFileFormat;
+
+ private final IntValueTypedPackedTable mPackedTable;
+
+ private final SuffixTableSharedData mSuffixTableSharedData;
+
+ private final int mPrefix;
+
+ PopulatedSuffixTableBlock(
+ SatS2RangeFileFormat fileFormat, IntValueTypedPackedTable packedTable) {
+ mFileFormat = Objects.requireNonNull(fileFormat);
+ mPackedTable = Objects.requireNonNull(packedTable);
+ mSuffixTableSharedData = SuffixTableSharedData.fromBytes(packedTable.getSharedData());
+
+ // Obtain the prefix. All cellIds in this table will share the same prefix except for end
+ // range values (which are exclusive so can be for mPrefix + 1 with a suffix value of 0).
+ mPrefix = mSuffixTableSharedData.getTablePrefix();
+ }
+
+ @Override
+ public int getPrefix() {
+ return mPrefix;
+ }
+
+ @Override
+ public SuffixTableBlock.Entry findEntryByCellId(long cellId) {
+ int suffixValue = mFileFormat.extractSuffixValueFromCellId(cellId);
+ S2CellMatcher matcher = new S2CellMatcher(mFileFormat, suffixValue);
+ return findEntryWithMatcher(matcher);
+ }
+
+ @Override
+ public SuffixTableBlock.Entry findEntryByIndex(int i) {
+ return new Entry(mPackedTable.getEntryByIndex(i));
+ }
+
+ @Override
+ public int getEntryCount() {
+ return mPackedTable.getEntryCount();
+ }
+
+ /**
+ * Returns an entry that matches the supplied matcher. If multiple entries match, an arbitrary
+ * matching entry is returned. If no entries match then {@code null} is returned.
+ */
+ private SuffixTableBlock.Entry findEntryWithMatcher(
+ IntValueTable.IntValueEntryMatcher matcher) {
+ IntValueTable.TableEntry suffixTableEntry = mPackedTable.findEntry(matcher);
+ if (suffixTableEntry == null) {
+ return null;
+ }
+ return new Entry(suffixTableEntry);
+ }
+
+ /**
+ * An {@link IntValueTable.IntValueEntryMatcher} capable of interpreting and matching the
+ * key/value from the underlying table against a search suffix value.
+ */
+ private static final class S2CellMatcher implements IntValueTable.IntValueEntryMatcher {
+
+ private final SatS2RangeFileFormat mFileFormat;
+
+ private final int mSuffixSearchValue;
+
+ S2CellMatcher(SatS2RangeFileFormat fileFormat, int suffixSearchValue) {
+ mFileFormat = Objects.requireNonNull(fileFormat);
+ mSuffixSearchValue = suffixSearchValue;
+ }
+
+ @Override
+ public int compare(int key, int value) {
+ int rangeStartCellIdOffset = key;
+ if (mSuffixSearchValue < rangeStartCellIdOffset) {
+ return -1;
+ } else {
+ int rangeLength = mFileFormat.extractRangeLengthFromTableEntryValue(value);
+ int rangeEndCellIdOffset = rangeStartCellIdOffset + rangeLength;
+ if (mSuffixSearchValue >= rangeEndCellIdOffset) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+ }
+
+ /**
+ * An entry from the {@link SuffixTableBlock}. Use {@link #getSuffixTableRange()} to get the
+ * full, interpreted entry data.
+ */
+ public final class Entry extends SuffixTableBlock.Entry {
+
+ private final IntValueTable.TableEntry mSuffixTableEntry;
+
+ private S2LevelRange mSuffixTableRange;
+
+ Entry(IntValueTable.TableEntry suffixTableEntry) {
+ mSuffixTableEntry = Objects.requireNonNull(suffixTableEntry);
+ }
+
+ @Override
+ public int getIndex() {
+ return mSuffixTableEntry.getIndex();
+ }
+
+ /** Returns the data for this entry. */
+ @Override
+ public S2LevelRange getSuffixTableRange() {
+ // Creating SuffixTableRange is relatively expensive so it is created lazily and
+ // memoized.
+ if (mSuffixTableRange == null) {
+ // Create the range to return.
+ int startCellIdSuffix = mSuffixTableEntry.getKey();
+ checkStateInRange("startCellIdSuffixBits", startCellIdSuffix,
+ "minSuffixValue", 0, "maxSuffixValue", mFileFormat.getMaxSuffixValue());
+ long startCellId = mFileFormat.createCellId(mPrefix, startCellIdSuffix);
+
+ int tableEntryValue = mSuffixTableEntry.getValue();
+ int rangeLength =
+ mFileFormat.extractRangeLengthFromTableEntryValue(tableEntryValue);
+ checkStateInRange("rangeLength", rangeLength, "minRangeLength", 0, "maxRangeLength",
+ mFileFormat.getTableEntryMaxRangeLengthValue());
+ int endCellIdSuffix = startCellIdSuffix + rangeLength;
+
+ int endCellPrefixValue = mPrefix;
+ if (endCellIdSuffix > mFileFormat.getMaxSuffixValue()) {
+ // Handle the special case where the range ends in the next prefix. This is
+ // because the range end is exclusive, so the end value is allowed to be first
+ // cell ID from the next prefix.
+ if (endCellIdSuffix != mFileFormat.getMaxSuffixValue() + 1) {
+ throw new IllegalStateException("Range exceeds allowable cell IDs:"
+ + " startCellId=" + cellIdToString(startCellId)
+ + ", rangeLength=" + rangeLength);
+ }
+ endCellPrefixValue += 1;
+
+ // Check to see if the face ID has overflowed, and wrap to face zero if it has.
+ if (mFileFormat.extractFaceIdFromPrefix(endCellPrefixValue) > MAX_FACE_ID) {
+ endCellPrefixValue = 0;
+ }
+ endCellIdSuffix = 0;
+ }
+ long endCellId = mFileFormat.createCellId(endCellPrefixValue, endCellIdSuffix);
+ mSuffixTableRange = new S2LevelRange(startCellId, endCellId);
+ }
+ return mSuffixTableRange;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Entry entry = (Entry) o;
+ return mSuffixTableEntry.equals(entry.mSuffixTableEntry);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSuffixTableEntry);
+ }
+
+ @Override
+ public String toString() {
+ return "Entry{"
+ + "mSuffixTableEntry=" + mSuffixTableEntry
+ + '}';
+ }
+ }
+}
diff --git a/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/SatS2RangeFileFormat.java b/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/SatS2RangeFileFormat.java
new file mode 100644
index 0000000..39507aa
--- /dev/null
+++ b/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/SatS2RangeFileFormat.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range.read;
+
+import static com.android.storage.s2.S2Support.FACE_BIT_COUNT;
+import static com.android.storage.s2.S2Support.MAX_FACE_ID;
+import static com.android.storage.s2.S2Support.MAX_S2_LEVEL;
+
+import com.android.storage.s2.S2Support;
+import com.android.storage.util.BitwiseUtils;
+import com.android.storage.util.Conditions;
+
+import java.util.Objects;
+
+/**
+ * Holds information about the format of a satellite S2 data file, which is a type of block file
+ * (see {@link com.android.storage.block.read.BlockFileReader}).
+ * Some information is hardcoded and some is parameterized using information read from the file's
+ * header block. This class contains useful methods for validation, interpretation and storage of
+ * data in a file with the specified format.
+ */
+public final class SatS2RangeFileFormat {
+
+ /** The block type of the satellite S2 data file header (held in block 0). */
+ public static final int BLOCK_TYPE_HEADER = 1;
+
+ /**
+ * The block type used for padding between the header and suffix tables that allows for future
+ * expansion. See {@link #getSuffixTableBlockIdOffset()}.
+ */
+ public static final int BLOCK_TYPE_PADDING = 20;
+
+ /** The block type of a populated suffix table. */
+ public static final int BLOCK_TYPE_SUFFIX_TABLE = 10;
+
+ /** The expected magic value of a satellite S2 data file. */
+ public static final char MAGIC = 0xCFAF;
+
+ /** The format version of the satellite S2 data file, read and written. */
+ public static final int VERSION = 1;
+
+ private final int mDataS2Level;
+
+ private final int mPrefixBitCount;
+
+ private final int mMaxPrefixValue;
+
+ private final int mSuffixTableBlockIdOffset;
+
+ private final int mSuffixBitCount;
+
+ private final int mMaxSuffixValue;
+
+ private final int mTableEntryByteCount;
+
+ private final int mTableEntryBitCount;
+
+ private final int mTableEntryRangeLengthBitCount;
+
+ private final int mTableEntryMaxRangeLengthValue;
+
+ /**
+ * The number of bits in a cell ID of the data storage level that have fixed values, i.e. the
+ * final "1" followed by all zeros.
+ */
+ private final int mUnusedCellIdBitCount;
+
+ /**
+ * Whether the satellite S2 file contains an allowed list of S2 cells.
+ * {@code true} means allowed list.
+ * {@code false} means disallowed list.
+ */
+ private final boolean mIsAllowedList;
+
+ /**
+ * Creates a new file format. This constructor validates the values against various hard-coded
+ * constraints and will throw an {@link IllegalArgumentException} if they are not satisfied.
+ */
+ public SatS2RangeFileFormat(int s2Level, int prefixBitCount, int suffixBitCount,
+ int suffixTableBlockIdOffset, int tableEntryBitCount, boolean isAllowedList) {
+
+ Conditions.checkArgInRange("s2Level", s2Level, 0, MAX_S2_LEVEL);
+
+ // prefixBitCount must include at least the face bits and one more, it makes the logic
+ // for mMaxPrefixValue easier below. We also assume that prefix and suffix will be 31-bits
+ // as that makes sure they can be represented, unsigned in a Java int. A prefix / suffix
+ // of 31-bits (each) should be enough for anyone(TM). 31-bits = ~15 S2 levels.
+ // Anything more than level 18 for geo data (i.e. prefix PLUS suffix) will be very
+ // detailed, so it's unlikely this constraint will be a problem.
+ Conditions.checkArgInRange("prefixBitCount", prefixBitCount, 4, Integer.SIZE - 1);
+
+ // The suffix table uses fixed length records that are broken into a key and a value. The
+ // implementation requires the key is an unsigned int value, i.e. 31-bits, and the value can
+ // be held in an int value.
+
+ // The key of a suffix table entry is used to store the suffix for the cell ID at the start
+ // of a range of S2 cells (the prefix for that cell ID is implicit and the same for every
+ // entry in the table, see prefixBitCount).
+ int tableEntryKeyBitCount = suffixBitCount;
+ Conditions.checkArgInRange(
+ "tableEntryKeyBitCount", tableEntryKeyBitCount, 1, Integer.SIZE - 1);
+
+ // The value of a suffix table entry is used to hold both the range length and the entry
+ // value. See methods below that extract these components from an int. Hence, we check they
+ // will fit.
+ int tableEntryValueBitCount = tableEntryBitCount - tableEntryKeyBitCount;
+ Conditions.checkArgInRange(
+ "tableEntryValueBitCount", tableEntryValueBitCount, 1, Integer.SIZE);
+
+ if (S2Support.storageBitCountForLevel(s2Level) != prefixBitCount + suffixBitCount) {
+ // s2Level implies cellIds have a certain number of "storage bits", the prefix and
+ // suffix must consume all the bits.
+ throw new IllegalArgumentException("prefixBitCount=" + prefixBitCount
+ + " + suffixBitCount=" + suffixBitCount + " must be correct for the s2Level ("
+ + S2Support.storageBitCountForLevel(s2Level) + ")");
+ }
+ if (suffixTableBlockIdOffset < 1) {
+ // The format includes a header block, so there will always be an adjustment for at
+ // least that one block.
+ throw new IllegalArgumentException(
+ "suffixTableBlockIdOffset=" + suffixTableBlockIdOffset + " must be >= 1");
+ }
+ if (tableEntryBitCount < 0 || tableEntryBitCount % Byte.SIZE != 0
+ || tableEntryBitCount > Long.SIZE) {
+ // The classes used to read suffix tables only support entries that are a multiples of
+ // a byte. They also restrict to up a maximum of 8-bytes per table entry.
+ throw new IllegalArgumentException(
+ "suffixTableEntryBitCount=" + tableEntryBitCount
+ + " must be >= 0, be divisible by 8, and be no more than 64 bits");
+ }
+
+ // Everything in a suffix table entry that isn't the suffix is the range length, so we can
+ // calculate it from the information given.
+ int entryRangeLengthBitCount = tableEntryBitCount - suffixBitCount;
+ // For simplicity below we ensure we can hold the maximum range length value in an unsigned
+ // Java int, so up to 31-bits.
+ Conditions.checkArgInRange(
+ "entryRangeLengthBitCount", entryRangeLengthBitCount, 2, Integer.SIZE - 1);
+
+ // Set all the fields. The fields are either set directly from parameters or derived from
+ // the values given.
+
+ mDataS2Level = s2Level;
+ mPrefixBitCount = prefixBitCount;
+
+ // Prefix value: contains the face ID plus one or more bits for the index.
+ int cellIdIndexBitCount = prefixBitCount - FACE_BIT_COUNT;
+ mMaxPrefixValue = (int)
+ ((((long) MAX_FACE_ID) << cellIdIndexBitCount)
+ | BitwiseUtils.maxUnsignedValue(cellIdIndexBitCount));
+
+ mSuffixBitCount = suffixBitCount;
+ mMaxSuffixValue = (int) BitwiseUtils.maxUnsignedValue(suffixBitCount);
+
+ // prefixBitCount + suffixBitCount are all the "useful" bits. The remaining bits in a 64-bit
+ // cell ID are the trailing "1" (which we don't need to store) and the rest are zeros.
+ mUnusedCellIdBitCount = Long.SIZE - (prefixBitCount + suffixBitCount);
+
+ mTableEntryBitCount = tableEntryBitCount;
+ mTableEntryByteCount = tableEntryBitCount / 8;
+
+ mTableEntryRangeLengthBitCount = entryRangeLengthBitCount;
+ mTableEntryMaxRangeLengthValue =
+ (int) BitwiseUtils.maxUnsignedValue(entryRangeLengthBitCount);
+
+ mSuffixTableBlockIdOffset = suffixTableBlockIdOffset;
+
+ mIsAllowedList = isAllowedList;
+ }
+
+ /** Returns the S2 level of all geo data stored in the file. */
+ public int getS2Level() {
+ return mDataS2Level;
+ }
+
+ /**
+ * Returns the number of prefix bits from an S2 cell ID used to identify the block containing
+ * ranges.
+ */
+ public int getPrefixBitCount() {
+ return mPrefixBitCount;
+ }
+
+ /**
+ * Returns the maximum valid value that {@link #getPrefixBitCount()} can represent. Note: This
+ * is not just the number of bits: the prefix contains the face ID which can only be 0 - 5
+ * (inclusive).
+ */
+ public int getMaxPrefixValue() {
+ return mMaxPrefixValue;
+ }
+
+ /**
+ * Returns the number of "useful" bits of an S2 cell ID in the data after
+ * {@link #getPrefixBitCount()}, i.e. not including the trailing "1".
+ * Dependent on the {@link #getS2Level()}, which dictates the number of storage bits in every
+ * cell ID in a file, and {@link #getPrefixBitCount()}.
+ */
+ public int getSuffixBitCount() {
+ return mSuffixBitCount;
+ }
+
+ /**
+ * Returns the maximum value that {@link #getSuffixBitCount()} can represent.
+ */
+ public int getMaxSuffixValue() {
+ return mMaxSuffixValue;
+ }
+
+ /**
+ * Returns the number of bits in each suffix table entry. i.e.
+ * {@link #getTableEntryByteCount()} * 8
+ */
+ public int getTableEntryBitCount() {
+ return mTableEntryBitCount;
+ }
+
+ /** Returns the number of bytes in each suffix table entry. */
+ public int getTableEntryByteCount() {
+ return mTableEntryByteCount;
+ }
+
+ /** Return the number of bits in each suffix table entry used to store the length of a range. */
+ public int getTableEntryRangeLengthBitCount() {
+ return mTableEntryRangeLengthBitCount;
+ }
+
+ /** Returns the maximum value that {@link #getTableEntryRangeLengthBitCount()} can represent. */
+ public int getTableEntryMaxRangeLengthValue() {
+ return mTableEntryMaxRangeLengthValue;
+ }
+
+ /**
+ * Returns the offset to apply to the prefix value to compute the block ID holding the data for
+ * that prefix. Always >= 1 to account for the header block.
+ */
+ public int getSuffixTableBlockIdOffset() {
+ return mSuffixTableBlockIdOffset;
+ }
+
+ /**
+ * @return {@code true} if the satellite S2 file contains an allowed list of S2 cells.
+ * {@code false} if the satellite S2 file contains a disallowed list of S2 cells.
+ */
+ public boolean isAllowedList() {
+ return mIsAllowedList;
+ }
+
+ /** Extracts the prefix bits from a cell ID and returns them as an unsigned int. */
+ public int extractPrefixValueFromCellId(long cellId) {
+ checkS2Level("cellId", cellId);
+ return (int) (cellId >>> (mSuffixBitCount + mUnusedCellIdBitCount));
+ }
+
+ /** Extracts the suffix bits from a cell ID and returns them as an unsigned int. */
+ public int extractSuffixValueFromCellId(long cellId) {
+ checkS2Level("cellId", cellId);
+ return (int) (cellId >>> (mUnusedCellIdBitCount)) & mMaxSuffixValue;
+ }
+
+ /** Extracts the range length from a table entry value. */
+ public int extractRangeLengthFromTableEntryValue(int value) {
+ long mask = BitwiseUtils.getLowBitsMask(mTableEntryRangeLengthBitCount);
+ return (int) (value & mask);
+ }
+
+ /** Creates a table entry value from a range length. */
+ public long createSuffixTableValue(int rangeLength) {
+ Conditions.checkArgInRange(
+ "rangeLength", rangeLength, 0, getTableEntryMaxRangeLengthValue());
+ return rangeLength;
+ }
+
+ /** Creates a cell ID from a prefix and a suffix component. */
+ public long createCellId(int prefixValue, int suffixValue) {
+ Conditions.checkArgInRange("prefixValue", prefixValue, 0, mMaxPrefixValue);
+ Conditions.checkArgInRange("suffixValue", suffixValue, 0, mMaxSuffixValue);
+ long cellId = prefixValue;
+ cellId <<= mSuffixBitCount;
+ cellId |= suffixValue;
+ cellId <<= 1;
+ cellId |= 1;
+ cellId <<= mUnusedCellIdBitCount - 1;
+
+ checkS2Level("cellId", cellId);
+ return cellId;
+ }
+
+ /** Extracts the face ID bits from a prefix value. */
+ public int extractFaceIdFromPrefix(int prefixValue) {
+ return prefixValue >>> (mPrefixBitCount - FACE_BIT_COUNT);
+ }
+
+ /**
+ * Calculates the number of cell IDs in the given range. Throws {@link IllegalArgumentException}
+ * if {@code rangeStartCellId} is "higher" than {@code rangeEndCellId} or the range length would
+ * be outside of the int range.
+ *
+ * @param rangeStartCellId the start of the range (inclusive)
+ * @param rangeEndCellId the end of the range (exclusive)
+ */
+ public int calculateRangeLength(long rangeStartCellId, long rangeEndCellId) {
+ checkS2Level("rangeStartCellId", rangeStartCellId);
+ checkS2Level("rangeEndCellId", rangeEndCellId);
+
+ // Convert to numeric values that can just be subtracted without worrying about sign
+ // issues.
+ long rangeEndCellIdNumeric = rangeEndCellId >>> mUnusedCellIdBitCount;
+ long rangeStartCellIdNumeric = rangeStartCellId >>> mUnusedCellIdBitCount;
+ if (rangeStartCellIdNumeric >= rangeEndCellIdNumeric) {
+ throw new IllegalArgumentException(
+ "rangeStartCellId=" + cellIdToString(rangeStartCellId)
+ + " is >= rangeEndCellId=" + cellIdToString(rangeEndCellId));
+ }
+ long differenceNumeric = rangeEndCellIdNumeric - rangeStartCellIdNumeric;
+ if (differenceNumeric < 0 || differenceNumeric > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException("rangeLength=" + differenceNumeric
+ + " is outside of expected range");
+ }
+ return (int) differenceNumeric;
+ }
+
+ /** Formats a cellId in terms of prefix and suffix values. */
+ public String cellIdToString(long cellId) {
+ int prefix = extractPrefixValueFromCellId(cellId);
+ int suffix = extractSuffixValueFromCellId(cellId);
+ String prefixBitsString = BitwiseUtils.toUnsignedString(mPrefixBitCount, prefix);
+ String suffixBitsString = BitwiseUtils.toUnsignedString(mSuffixBitCount, suffix);
+ return "cellId{prefix=" + prefix + " (" + prefixBitsString + ")"
+ + ", suffix=" + suffix + " (" + suffixBitsString + ")"
+ + "}";
+ }
+
+ @Override
+ public String toString() {
+ return "SatS2RangeFileFormat{"
+ + "mDataS2Level=" + mDataS2Level
+ + ", mPrefixBitCount=" + mPrefixBitCount
+ + ", mMaxPrefixValue=" + mMaxPrefixValue
+ + ", mSuffixBitCount=" + mSuffixBitCount
+ + ", mMaxSuffixValue=" + mMaxSuffixValue
+ + ", mTableEntryBitCount=" + mTableEntryBitCount
+ + ", mTableEntryRangeLengthBitCount=" + mTableEntryRangeLengthBitCount
+ + ", mTableEntryMaxRangeLengthValue=" + mTableEntryMaxRangeLengthValue
+ + ", mSuffixTableBlockIdOffset=" + mSuffixTableBlockIdOffset
+ + ", mUnusedCellIdBitCount=" + mUnusedCellIdBitCount
+ + ", mIsAllowedList=" + mIsAllowedList
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ SatS2RangeFileFormat that = (SatS2RangeFileFormat) o;
+ return mDataS2Level == that.mDataS2Level
+ && mPrefixBitCount == that.mPrefixBitCount
+ && mMaxPrefixValue == that.mMaxPrefixValue
+ && mSuffixBitCount == that.mSuffixBitCount
+ && mMaxSuffixValue == that.mMaxSuffixValue
+ && mTableEntryBitCount == that.mTableEntryBitCount
+ && mTableEntryRangeLengthBitCount == that.mTableEntryRangeLengthBitCount
+ && mTableEntryMaxRangeLengthValue == that.mTableEntryMaxRangeLengthValue
+ && mSuffixTableBlockIdOffset == that.mSuffixTableBlockIdOffset
+ && mIsAllowedList == that.mIsAllowedList
+ && mUnusedCellIdBitCount == that.mUnusedCellIdBitCount;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDataS2Level, mPrefixBitCount, mMaxPrefixValue, mSuffixBitCount,
+ mMaxSuffixValue, mTableEntryBitCount, mTableEntryRangeLengthBitCount,
+ mTableEntryMaxRangeLengthValue, mSuffixTableBlockIdOffset, mIsAllowedList,
+ mUnusedCellIdBitCount);
+ }
+
+ private void checkS2Level(String name, long cellId) {
+ if (S2Support.getS2Level(cellId) != mDataS2Level) {
+ throw new IllegalArgumentException(
+ name + "=" + S2Support.cellIdToString(cellId) + " is at the wrong level");
+ }
+ }
+}
diff --git a/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/SatS2RangeFileReader.java b/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/SatS2RangeFileReader.java
new file mode 100644
index 0000000..ecfa0a9
--- /dev/null
+++ b/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/SatS2RangeFileReader.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range.read;
+
+import com.android.storage.block.read.Block;
+import com.android.storage.block.read.BlockFileReader;
+import com.android.storage.block.read.BlockInfo;
+import com.android.storage.s2.S2LevelRange;
+import com.android.storage.s2.S2Support;
+import com.android.storage.util.Conditions;
+import com.android.storage.util.Visitor;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Objects;
+
+/** Provides access to the content of a satellite S2 data file. */
+public final class SatS2RangeFileReader implements AutoCloseable {
+
+ private final BlockFileReader mBlockFileReader;
+
+ private HeaderBlock mHeaderBlock;
+
+ private SuffixTableExtraInfo[] mSuffixTableExtraInfos;
+
+ /** Convenience field to avoid calling {@link HeaderBlock#getFileFormat()} repeatedly. */
+ private SatS2RangeFileFormat mFileFormat;
+
+ private boolean mClosed;
+
+ private SatS2RangeFileReader(BlockFileReader blockFileReader) {
+ mBlockFileReader = Objects.requireNonNull(blockFileReader);
+ }
+
+ /**
+ * Opens the specified file. Throws {@link IOException} in the event of a access problem reading
+ * the file. Throws {@link IllegalArgumentException} if the file has a format / syntax problem.
+ *
+ * <p>After open, use methods like {@link #findEntryByCellId(long)} to access the data.
+ */
+ public static SatS2RangeFileReader open(File file) throws IOException {
+ boolean memoryMapBlocks = false;
+ BlockFileReader blockFileReader = BlockFileReader.open(
+ memoryMapBlocks, file, SatS2RangeFileFormat.MAGIC, SatS2RangeFileFormat.VERSION);
+ SatS2RangeFileReader satS2RangeFileReader = new SatS2RangeFileReader(blockFileReader);
+ satS2RangeFileReader.initialize();
+ return satS2RangeFileReader;
+ }
+
+ private void initialize() throws IOException {
+ // Check the BlockInfo for the header block is what we expect.
+ int headerBlockId = 0;
+ BlockInfo firstBlockInfo = mBlockFileReader.getBlockInfo(headerBlockId);
+ if (firstBlockInfo.getType() != SatS2RangeFileFormat.BLOCK_TYPE_HEADER) {
+ throw new IllegalArgumentException("headerBlockInfo.getType()="
+ + firstBlockInfo.getType() + " must be "
+ + SatS2RangeFileFormat.BLOCK_TYPE_HEADER);
+ }
+
+ // So far so good. Open the header block itself and extract the information held there.
+ Block firstBlock = mBlockFileReader.getBlock(headerBlockId);
+ if (firstBlock.getType() != SatS2RangeFileFormat.BLOCK_TYPE_HEADER) {
+ throw new IllegalArgumentException("firstBlock.getType()=" + firstBlock.getType()
+ + " must be " + SatS2RangeFileFormat.BLOCK_TYPE_HEADER);
+ }
+ mHeaderBlock = HeaderBlock.wrap(firstBlock.getData());
+
+ // Optimization: hold a direct reference to fileFormat since it is referenced often.
+ mFileFormat = mHeaderBlock.getFileFormat();
+
+ // Read all the BlockInfos for data blocks and precache the SuffixTableBlock.Info instances.
+ mSuffixTableExtraInfos = new SuffixTableExtraInfo[mFileFormat.getMaxPrefixValue() + 1];
+ for (int prefix = 0; prefix < mSuffixTableExtraInfos.length; prefix++) {
+ int blockId = prefix + mFileFormat.getSuffixTableBlockIdOffset();
+ BlockInfo blockInfo = mBlockFileReader.getBlockInfo(blockId);
+ int type = blockInfo.getType();
+ if (type == SatS2RangeFileFormat.BLOCK_TYPE_SUFFIX_TABLE) {
+ mSuffixTableExtraInfos[prefix] =
+ SuffixTableExtraInfo.create(mFileFormat, blockInfo);
+ } else {
+ throw new IllegalStateException("Unknown block type=" + type);
+ }
+ }
+ }
+
+ /** A {@link Visitor} for the {@link SatS2RangeFileReader}. See {@link #visit} */
+ public interface SatS2RangeFileVisitor extends Visitor {
+
+ /** Called after {@link #begin()}, once only. */
+ void visitHeaderBlock(HeaderBlock headerBlock) throws VisitException;
+
+ /**
+ * Called after {@link #visitHeaderBlock(HeaderBlock)}}, once for each suffix table in the
+ * file.
+ */
+ void visitSuffixTableExtraInfo(SuffixTableExtraInfo suffixTableExtraInfo)
+ throws VisitException;
+
+ /**
+ * Called after {@link #visitHeaderBlock(HeaderBlock)}, once per suffix table in the file.
+ */
+ void visitSuffixTableBlock(SuffixTableBlock suffixTableBlock) throws VisitException;
+ }
+
+ /**
+ * Issues callbacks to the supplied {@link SatS2RangeFileVisitor} containing information from
+ * the satellite S2 data file.
+ */
+ public void visit(SatS2RangeFileVisitor visitor) throws Visitor.VisitException {
+ try {
+ visitor.begin();
+
+ visitor.visitHeaderBlock(mHeaderBlock);
+
+ for (int i = 0; i < mSuffixTableExtraInfos.length; i++) {
+ visitor.visitSuffixTableExtraInfo(mSuffixTableExtraInfos[i]);
+ }
+
+ try {
+ for (int i = 0; i < mSuffixTableExtraInfos.length; i++) {
+ SuffixTableBlock suffixTableBlock = getSuffixTableBlockForPrefix(i);
+ visitor.visitSuffixTableBlock(suffixTableBlock);
+ }
+ } catch (IOException e) {
+ throw new Visitor.VisitException(e);
+ }
+ } finally {
+ visitor.end();
+ }
+ }
+
+ /**
+ * Finds an {@link S2LevelRange} associated with a range covering {@code cellId}.
+ * Returns {@code null} if no range exists. Throws {@link IllegalArgumentException} if
+ * {@code cellId} is not the correct S2 level for the file. See {@link #getS2Level()}.
+ */
+ public S2LevelRange findEntryByCellId(long cellId) throws IOException {
+ checkNotClosed();
+ int dataS2Level = mFileFormat.getS2Level();
+ int searchS2Level = S2Support.getS2Level(cellId);
+ if (dataS2Level != searchS2Level) {
+ throw new IllegalArgumentException(
+ "data S2 level=" + dataS2Level + ", search S2 level=" + searchS2Level);
+ }
+
+ int prefix = mFileFormat.extractPrefixValueFromCellId(cellId);
+ SuffixTableBlock suffixTableBlock = getSuffixTableBlockForPrefix(prefix);
+ SuffixTableBlock.Entry suffixTableEntry = suffixTableBlock.findEntryByCellId(cellId);
+ if (suffixTableEntry == null) {
+ return null;
+ }
+ return suffixTableEntry.getSuffixTableRange();
+ }
+
+ private SuffixTableExtraInfo getSuffixTableExtraInfoForPrefix(int prefixValue) {
+ Conditions.checkArgInRange(
+ "prefixValue", prefixValue, "minPrefixValue", 0, "maxPrefixValue",
+ mFileFormat.getMaxPrefixValue());
+
+ return mSuffixTableExtraInfos[prefixValue];
+ }
+
+ private SuffixTableBlock getSuffixTableBlockForPrefix(int prefix) throws IOException {
+ SuffixTableExtraInfo suffixTableExtraInfo = getSuffixTableExtraInfoForPrefix(prefix);
+ if (suffixTableExtraInfo.isEmpty()) {
+ return SuffixTableBlock.createEmpty(mFileFormat, prefix);
+ }
+ Block block = mBlockFileReader.getBlock(prefix + mFileFormat.getSuffixTableBlockIdOffset());
+ SuffixTableBlock suffixTableBlock =
+ SuffixTableBlock.createPopulated(mFileFormat, block.getData());
+ if (prefix != suffixTableBlock.getPrefix()) {
+ throw new IllegalArgumentException("prefixValue=" + prefix
+ + " != suffixTableBlock.getPrefix()=" + suffixTableBlock.getPrefix());
+ }
+ return suffixTableBlock;
+ }
+
+ @Override
+ public void close() throws IOException {
+ mClosed = true;
+ mHeaderBlock = null;
+ mBlockFileReader.close();
+ }
+
+ private void checkNotClosed() throws IOException {
+ if (mClosed) {
+ throw new IOException("Closed");
+ }
+ }
+
+ /** Returns the S2 level for the file. See also {@link #findEntryByCellId(long)}. */
+ public int getS2Level() throws IOException {
+ checkNotClosed();
+ return mHeaderBlock.getFileFormat().getS2Level();
+ }
+
+ /**
+ * @return {@code true} if the satellite S2 file contains an allowed list of S2 cells.
+ * {@code false} if the satellite S2 file contains a disallowed list of S2 cells.
+ */
+ public boolean isAllowedList() {
+ return mFileFormat.isAllowedList();
+ }
+}
diff --git a/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/SuffixTableBlock.java b/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/SuffixTableBlock.java
new file mode 100644
index 0000000..1a6fad7
--- /dev/null
+++ b/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/SuffixTableBlock.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range.read;
+
+import static com.android.storage.s2.S2Support.cellIdToString;
+import static com.android.storage.s2.S2Support.getS2Level;
+
+import com.android.storage.block.read.BlockData;
+import com.android.storage.s2.S2LevelRange;
+import com.android.storage.table.packed.read.IntValueTypedPackedTable;
+import com.android.storage.util.BitwiseUtils;
+import com.android.storage.util.Visitor;
+
+import java.util.Objects;
+
+/**
+ * The main type of block for a satellite S2 data file.
+ *
+ * <p>Logically, each suffix table block holds zero or more entries for S2 ranges, e.g.:
+ * <pre>
+ * startCellId=X, endCellId=Y
+ * </pre>
+ *
+ * <p>Tables are generated so that all entries in the table have the same S2 level and "prefix
+ * bits" for the S2 cell IDs they describe, i.e. if the table's assigned prefix is "1011000", then
+ * every cell ID included in every range entry (i.e. from X to Y) must start with "1011000". The
+ * entries in the table are ordered by startCellId and ranges cannot overlap. There is only one
+ * block / suffix table for each possible prefix.
+ *
+ * <p>Note that because the endCellId is <em>exclusive</em>, the last entry's endCellId <em>can</em>
+ * refer the first S2 cell ID from the next prefix, or wrap around to face 0 for the last entry
+ * for face ID 5.
+ *
+ * <p>Any S2 cell id with a prefix that is not covered by a range entry in the associated table can
+ * be inferred to have a value of zero.
+ *
+ * <p>Entries can be obtained by called methods such as {@link #getEntryByIndex(int)},
+ * {@link #findEntryByCellId(long)}.
+ */
+public final class SuffixTableBlock {
+
+ private final SatS2RangeFileFormat mFileFormat;
+
+ private final SuffixTableBlockDelegate mDelegate;
+
+ private final int mPrefix;
+
+ /**
+ * The implementation of the suffix table block. Suffix table blocks have two main
+ * implementations: zero-length blocks used to represent empty tables, and blocks containing
+ * {@link IntValueTypedPackedTable} data. Since they are so different they are implemented
+ * independently.
+ */
+ interface SuffixTableBlockDelegate {
+
+ /** Returns the prefix for cell IDs in this table. */
+ int getPrefix();
+
+ /**
+ * Returns the entry containing the specified cell ID, or {@code null} if there isn't one.
+ */
+ Entry findEntryByCellId(long cellId);
+
+ /**
+ * Returns the entry with the specified index. Throws {@link IndexOutOfBoundsException} if
+ * the index is invalid.
+ */
+ Entry findEntryByIndex(int i);
+
+ /** Returns the number of entries in the table. */
+ int getEntryCount();
+ }
+
+ private SuffixTableBlock(SatS2RangeFileFormat fileFormat, SuffixTableBlockDelegate delegate) {
+ mFileFormat = Objects.requireNonNull(fileFormat);
+ mDelegate = Objects.requireNonNull(delegate);
+ mPrefix = delegate.getPrefix();
+ }
+
+ /**
+ * Creates a populated {@link SuffixTableBlock} by interpreting {@link BlockData} and using
+ * the supplied format information.
+ */
+ public static SuffixTableBlock createPopulated(
+ SatS2RangeFileFormat fileFormat, BlockData blockData) {
+ if (blockData.getSize() == 0) {
+ throw new IllegalArgumentException("blockData=" + blockData + ", is zero length");
+ }
+ IntValueTypedPackedTable packedTable = new IntValueTypedPackedTable(blockData);
+ PopulatedSuffixTableBlock delegate = new PopulatedSuffixTableBlock(fileFormat, packedTable);
+ return new SuffixTableBlock(fileFormat, delegate);
+ }
+
+ /**
+ * Creates an unpopulated {@link SuffixTableBlock} for the supplied prefix and using
+ * the supplied format information.
+ */
+ public static SuffixTableBlock createEmpty(SatS2RangeFileFormat fileFormat, int prefix) {
+ return new SuffixTableBlock(fileFormat, new UnpopulatedSuffixTableBlock(prefix));
+ }
+
+ /** Returns the prefix for this table. */
+ public int getPrefix() {
+ return mDelegate.getPrefix();
+ }
+
+ /**
+ * Returns the entry for a given cell ID or {@code null} if there isn't one. The
+ * {@code cellId} must be the same level as the table and have the same prefix otherwise an
+ * {@link IllegalArgumentException} is thrown.
+ */
+ public Entry findEntryByCellId(long cellId) {
+ if (getS2Level(cellId) != mFileFormat.getS2Level()) {
+ throw new IllegalArgumentException(
+ cellIdToString(cellId) + " s2 level is not "
+ + mFileFormat.getS2Level());
+ }
+ if (mFileFormat.extractPrefixValueFromCellId(cellId) != mPrefix) {
+ String prefixBitString =
+ BitwiseUtils.toUnsignedString(mFileFormat.getPrefixBitCount(), mPrefix);
+ throw new IllegalArgumentException(
+ cellId + "(" + mFileFormat.cellIdToString(cellId)
+ + ") does not have prefix bits " + mPrefix
+ + " (" + prefixBitString + ")");
+ }
+
+ return mDelegate.findEntryByCellId(cellId);
+ }
+
+ /** Returns the entry at the specified index. */
+ public Entry getEntryByIndex(int i) {
+ return mDelegate.findEntryByIndex(i);
+ }
+
+ /** Returns the number of entries in the table. */
+ public int getEntryCount() {
+ return mDelegate.getEntryCount();
+ }
+
+ /** A {@link Visitor} for the {@link SuffixTableBlock}. See {@link #visit} */
+ public interface SuffixTableBlockVisitor extends Visitor {
+
+ /** Called after {@link #begin()}, once. */
+ void visit(SuffixTableBlock suffixTableBlock) throws VisitException;
+ }
+
+ /**
+ * Issues callbacks to the supplied {@link SuffixTableBlockVisitor}.
+ */
+ public void visit(SuffixTableBlockVisitor visitor) throws Visitor.VisitException {
+ try {
+ visitor.begin();
+ visitor.visit(this);
+ } finally {
+ visitor.end();
+ }
+ }
+
+ /**
+ * An entry from the {@link SuffixTableBlock}. Use {@link #getSuffixTableRange()} to get the
+ * full, interpreted entry data.
+ */
+ public abstract static class Entry {
+
+ /** Returns the position of this entry in the table. */
+ public abstract int getIndex();
+
+ /** Returns the data for this entry. */
+ public abstract S2LevelRange getSuffixTableRange();
+ }
+}
diff --git a/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/SuffixTableExtraInfo.java b/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/SuffixTableExtraInfo.java
new file mode 100644
index 0000000..2b179fe
--- /dev/null
+++ b/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/SuffixTableExtraInfo.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range.read;
+
+import com.android.storage.block.read.BlockInfo;
+import com.android.storage.io.read.TypedInputStream;
+import com.android.storage.util.Conditions;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+/**
+ * Information about a suffix table block held in the header of a satellite S2 data file. It can be
+ * used to work out whether to read the associated block data.
+ */
+public final class SuffixTableExtraInfo {
+
+ /**
+ * The suffix table's S2 cell ID prefix. This information is not stored in the block info
+ * directly; during file read it is calculated from the block ID, i.e. {block id} - {suffix
+ * table block id offset}.
+ */
+ private final int mPrefix;
+
+ private final int mEntryCount;
+
+ /** Creates metadata about a suffix table. */
+ public SuffixTableExtraInfo(int prefix, int entryCount) {
+ if (prefix < 0) {
+ throw new IllegalArgumentException("prefix=" + prefix + " must be >= 0");
+ }
+ mPrefix = prefix;
+
+ if (entryCount < 0) {
+ throw new IllegalArgumentException("entryCount=" + entryCount + " must be >= 0");
+ }
+ mEntryCount = entryCount;
+ }
+
+ /**
+ * Creates a {@link SuffixTableExtraInfo} from a {@link BlockInfo}. Throws an
+ * {@link IllegalArgumentException} if the block info is the wrong type or malformed.
+ */
+ public static SuffixTableExtraInfo create(
+ SatS2RangeFileFormat fileFormat, BlockInfo blockInfo) {
+ if (blockInfo.getType() != SatS2RangeFileFormat.BLOCK_TYPE_SUFFIX_TABLE) {
+ throw new IllegalArgumentException("blockType=" + blockInfo.getType()
+ + " is not of expected type=" + SatS2RangeFileFormat.BLOCK_TYPE_SUFFIX_TABLE);
+ }
+ int prefix = blockInfo.getId() - fileFormat.getSuffixTableBlockIdOffset();
+ if (blockInfo.getBlockSizeBytes() == 0) {
+ // Empty blocks have no data and no extra bytes but we know they have zero elements.
+ return new SuffixTableExtraInfo(prefix, 0 /* entryCount */);
+ }
+
+ byte[] extraBytes = blockInfo.getExtraBytes();
+ if (extraBytes == null || extraBytes.length == 0) {
+ throw new IllegalArgumentException(
+ "Extra bytes null or empty in blockInfo=" + blockInfo);
+ }
+
+ try (TypedInputStream typedInputStream = new TypedInputStream(
+ new ByteArrayInputStream(extraBytes))) {
+ int entryCount = typedInputStream.readInt();
+ Conditions.checkStateInRange(
+ "entryCount", entryCount, "minSuffixValue", 0, "maxSuffixValue",
+ fileFormat.getMaxSuffixValue());
+ return new SuffixTableExtraInfo(prefix, entryCount);
+ } catch (IOException e) {
+ // This shouldn't happen with a byte[]
+ throw new IllegalStateException("Unexpected exception while reading a byte[]", e);
+ }
+ }
+
+ /** Returns the prefix of the associated suffix table. */
+ public int getPrefix() {
+ return mPrefix;
+ }
+
+ /** Returns the number of entries in the associated suffix table. */
+ public int getEntryCount() {
+ return mEntryCount;
+ }
+
+ /** Returns true if the number of entries in the associated suffix table is zero. */
+ public boolean isEmpty() {
+ return mEntryCount == 0;
+ }
+}
diff --git a/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/SuffixTableSharedData.java b/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/SuffixTableSharedData.java
new file mode 100644
index 0000000..2221b2c
--- /dev/null
+++ b/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/SuffixTableSharedData.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range.read;
+
+import com.android.storage.io.read.TypedInputStream;
+import com.android.storage.table.reader.Table;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Shared data for a suffix table held in a suffix table block: the information applies to all
+ * entries in the table and is required when interpreting the table's block data.
+ */
+public final class SuffixTableSharedData {
+
+ private final int mTablePrefix;
+
+ /**
+ * Creates a {@link SuffixTableSharedData}. See also {@link #fromBytes(byte[])}.
+ */
+ public SuffixTableSharedData(int tablePrefix) {
+ mTablePrefix = tablePrefix;
+ }
+
+ /**
+ * Returns the S2 cell ID prefix associated with the table. i.e. all S2 ranges in the table will
+ * have this prefix.
+ */
+ public int getTablePrefix() {
+ return mTablePrefix;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ SuffixTableSharedData that = (SuffixTableSharedData) o;
+ return mTablePrefix == that.mTablePrefix;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTablePrefix);
+ }
+
+ @Override
+ public String toString() {
+ return "SuffixTableSharedData{"
+ + "mTablePrefix=" + mTablePrefix
+ + '}';
+ }
+
+ /**
+ * Creates a {@link SuffixTableSharedData} using shared data from a {@link Table}.
+ */
+ public static SuffixTableSharedData fromBytes(byte[] bytes) {
+ try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+ TypedInputStream tis = new TypedInputStream(bis)) {
+ int tablePrefixValue = tis.readInt();
+ return new SuffixTableSharedData(tablePrefixValue);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/UnpopulatedSuffixTableBlock.java b/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/UnpopulatedSuffixTableBlock.java
new file mode 100644
index 0000000..56730c2
--- /dev/null
+++ b/utils/satellite/s2storage/src/readonly/java/com/android/telephony/sats2range/read/UnpopulatedSuffixTableBlock.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range.read;
+
+/**
+ * An implementation of {@link SuffixTableBlock.SuffixTableBlockDelegate} for tables that are not
+ * backed by real block data, i.e. have zero entries.
+ */
+final class UnpopulatedSuffixTableBlock implements SuffixTableBlock.SuffixTableBlockDelegate {
+
+ private final int mPrefix;
+
+ UnpopulatedSuffixTableBlock(int prefix) {
+ mPrefix = prefix;
+ }
+
+ @Override
+ public int getPrefix() {
+ return mPrefix;
+ }
+
+ @Override
+ public SuffixTableBlock.Entry findEntryByCellId(long cellId) {
+ return null;
+ }
+
+ @Override
+ public SuffixTableBlock.Entry findEntryByIndex(int i) {
+ throw new IndexOutOfBoundsException("Unpopulated table");
+ }
+
+ @Override
+ public int getEntryCount() {
+ return 0;
+ }
+}
diff --git a/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/HeaderBlockTest.java b/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/HeaderBlockTest.java
new file mode 100644
index 0000000..e7bad01
--- /dev/null
+++ b/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/HeaderBlockTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import com.android.storage.block.write.BlockWriter;
+import com.android.telephony.sats2range.read.HeaderBlock;
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+import com.android.telephony.sats2range.utils.TestUtils;
+import com.android.telephony.sats2range.write.HeaderBlockWriter;
+
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+
+/** Tests for {@link HeaderBlockWriter} and {@link HeaderBlock}. */
+public class HeaderBlockTest {
+ @Test
+ public void readWrite() throws IOException {
+ SatS2RangeFileFormat fileFormat = TestUtils.createS2RangeFileFormat(true);
+
+ // Create header data using HeaderBlockWriter.
+ HeaderBlockWriter headerBlockWriter = HeaderBlockWriter.create(fileFormat);
+ BlockWriter.ReadBack readBack = headerBlockWriter.close();
+ assertEquals(SatS2RangeFileFormat.BLOCK_TYPE_HEADER, readBack.getType());
+ assertArrayEquals(new byte[0], readBack.getExtraBytes());
+
+ // Read the data back and confirm it matches what we expected.
+ HeaderBlock headerBlock = HeaderBlock.wrap(readBack.getBlockData());
+ assertEquals(fileFormat, headerBlock.getFileFormat());
+ }
+
+ @Test
+ public void visit() throws Exception {
+ SatS2RangeFileFormat fileFormat = TestUtils.createS2RangeFileFormat(true);
+
+ // Create header data using HeaderBlockWriter.
+ HeaderBlockWriter headerBlockWriter = HeaderBlockWriter.create(fileFormat);
+ BlockWriter.ReadBack readBack = headerBlockWriter.close();
+
+ // Read the data back and confirm it matches what we expected.
+ HeaderBlock headerBlock = HeaderBlock.wrap(readBack.getBlockData());
+
+ HeaderBlock.HeaderBlockVisitor mockVisitor =
+ Mockito.mock(HeaderBlock.HeaderBlockVisitor.class);
+
+ headerBlock.visit(mockVisitor);
+
+ InOrder inOrder = Mockito.inOrder(mockVisitor);
+ inOrder.verify(mockVisitor).begin();
+ inOrder.verify(mockVisitor).visitFileFormat(fileFormat);
+ inOrder.verify(mockVisitor).end();
+ }
+}
diff --git a/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/PushBackIteratorTest.java b/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/PushBackIteratorTest.java
new file mode 100644
index 0000000..84a960a
--- /dev/null
+++ b/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/PushBackIteratorTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import com.android.telephony.sats2range.write.PushBackIterator;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/** Tests for {@link PushBackIterator}. */
+public class PushBackIteratorTest {
+
+ @Test
+ public void test() {
+ List<String> values = listOf("One", "Two", "Three", "Four");
+ PushBackIterator<String> iterator = new PushBackIterator<>(values.iterator());
+ assertTrue(iterator.hasNext());
+
+ // iterator = One, Two, Three, Four
+ iterator.pushBack("Zero");
+ assertTrue(iterator.hasNext());
+ assertEquals("Zero", iterator.next());
+
+ // iterator = One, Two, Three, Four
+ assertTrue(iterator.hasNext());
+ assertEquals("One", iterator.next());
+
+ // iterator = Two, Three, Four
+ iterator.pushBack("One");
+ iterator.pushBack("Zero");
+ assertTrue(iterator.hasNext());
+ assertEquals("Zero", iterator.next());
+ assertTrue(iterator.hasNext());
+ assertEquals("One", iterator.next());
+
+ // iterator = Two, Three, Four
+ assertTrue(iterator.hasNext());
+ assertEquals("Two", iterator.next());
+ assertTrue(iterator.hasNext());
+ assertEquals("Three", iterator.next());
+ assertTrue(iterator.hasNext());
+ assertEquals("Four", iterator.next());
+ assertFalse(iterator.hasNext());
+
+ assertThrows(NoSuchElementException.class, iterator::next);
+
+ // iterator = Empty
+ iterator.pushBack("Four");
+ assertTrue(iterator.hasNext());
+ assertEquals("Four", iterator.next());
+ }
+
+ @Test
+ public void removeNotSupported() {
+ List<String> values = listOf("One", "Two", "Three", "Four");
+ PushBackIterator<String> iterator = new PushBackIterator<>(values.iterator());
+ assertEquals("One", iterator.next());
+
+ assertThrows(UnsupportedOperationException.class, iterator::remove);
+
+ iterator.pushBack("One");
+ iterator.pushBack("Zero");
+
+ assertThrows(UnsupportedOperationException.class, iterator::remove);
+
+ assertEquals("Zero", iterator.next());
+ assertThrows(UnsupportedOperationException.class, iterator::remove);
+ }
+
+ /** Returns a list from a varargs param. */
+ @SafeVarargs
+ private static <E> List<E> listOf(E... values) {
+ return Arrays.asList(values);
+ }
+}
diff --git a/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/SatS2RangeFileFormatTest.java b/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/SatS2RangeFileFormatTest.java
new file mode 100644
index 0000000..80ef467
--- /dev/null
+++ b/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/SatS2RangeFileFormatTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range;
+
+import static com.android.storage.s2.S2Support.cellId;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+
+import org.junit.Test;
+
+/** Tests for {@link SatS2RangeFileFormat}. */
+public class SatS2RangeFileFormatTest {
+ @Test
+ public void accessors() {
+ int s2Level = 12;
+ int prefixBitCount = 11;
+ int suffixBitCount = 16;
+ int suffixTableBlockIdOffset = 5;
+ int tableEntryBitCount = 24;
+ int entryRangeLengthBitCount = 8;
+ boolean isAllowedList = true;
+ SatS2RangeFileFormat satS2RangeFileFormat = new SatS2RangeFileFormat(s2Level,
+ prefixBitCount, suffixBitCount, suffixTableBlockIdOffset, tableEntryBitCount,
+ isAllowedList);
+
+ assertEquals(s2Level, satS2RangeFileFormat.getS2Level());
+ assertEquals(prefixBitCount, satS2RangeFileFormat.getPrefixBitCount());
+ assertEquals(suffixBitCount, satS2RangeFileFormat.getSuffixBitCount());
+ assertEquals(suffixTableBlockIdOffset, satS2RangeFileFormat.getSuffixTableBlockIdOffset());
+ assertEquals(tableEntryBitCount, satS2RangeFileFormat.getTableEntryBitCount());
+ assertEquals(entryRangeLengthBitCount,
+ satS2RangeFileFormat.getTableEntryRangeLengthBitCount());
+
+ // Derived values
+ assertEquals((6 * intPow2(prefixBitCount - 3)) - 1,
+ satS2RangeFileFormat.getMaxPrefixValue());
+ assertEquals(maxValForBits(suffixBitCount), satS2RangeFileFormat.getMaxSuffixValue());
+ assertEquals(tableEntryBitCount / 8, satS2RangeFileFormat.getTableEntryByteCount());
+ assertEquals(maxValForBits(entryRangeLengthBitCount),
+ satS2RangeFileFormat.getTableEntryMaxRangeLengthValue());
+ assertTrue(satS2RangeFileFormat.isAllowedList());
+ }
+
+ @Test
+ public void calculateRangeLength() {
+ int s2Level = 12;
+ int prefixBitCount = 11;
+ int suffixBitCount = 16;
+ int suffixTableBlockIdOffset = 5;
+ int suffixTableEntryBitCount = 24;
+ boolean isAllowedList = false;
+ SatS2RangeFileFormat satS2RangeFileFormat = new SatS2RangeFileFormat(s2Level,
+ prefixBitCount, suffixBitCount, suffixTableBlockIdOffset, suffixTableEntryBitCount,
+ isAllowedList);
+
+ assertEquals(2, satS2RangeFileFormat.calculateRangeLength(
+ cellId(s2Level, 0, 0), cellId(s2Level, 0, 2)));
+ assertEquals(2, satS2RangeFileFormat.calculateRangeLength(
+ cellId(s2Level, 0, 2), cellId(s2Level, 0, 4)));
+
+ int cellsPerFace = intPow2(s2Level * 2);
+ assertEquals(cellsPerFace + 2,
+ satS2RangeFileFormat.calculateRangeLength(
+ cellId(s2Level, 0, 2), cellId(s2Level, 1, 4)));
+ assertFalse(satS2RangeFileFormat.isAllowedList());
+ }
+
+ @Test
+ public void createCellId() {
+ int s2Level = 12;
+ int prefixBitCount = 11;
+ int suffixBitCount = 16;
+ int suffixTableBlockIdOffset = 5;
+ int suffixTableEntryBitCount = 24;
+ boolean isAllowedList = true;
+ SatS2RangeFileFormat satS2RangeFileFormat = new SatS2RangeFileFormat(s2Level,
+ prefixBitCount, suffixBitCount, suffixTableBlockIdOffset, suffixTableEntryBitCount,
+ isAllowedList);
+
+ // Too many bits for prefixValue
+ assertThrows(IllegalArgumentException.class,
+ () -> satS2RangeFileFormat.createCellId(0b1000_00000000, 0b10000000_00000000));
+
+ // Too many bits for suffixValue
+ assertThrows(IllegalArgumentException.class,
+ () -> satS2RangeFileFormat.createCellId(0b1000_00000000, 0b100000000_00000000));
+
+ // Some valid cases.
+ assertEquals(cellId(s2Level, 4, 0),
+ satS2RangeFileFormat.createCellId(0b100_00000000, 0b00000000_00000000));
+ assertEquals(cellId(s2Level, 4, 1),
+ satS2RangeFileFormat.createCellId(0b100_00000000, 0b00000000_00000001));
+
+ assertEquals(cellId(s2Level, 5, intPow2(0)),
+ satS2RangeFileFormat.createCellId(0b101_00000000, 0b00000000_00000001));
+ assertEquals(cellId(s2Level, 5, intPow2(8)),
+ satS2RangeFileFormat.createCellId(0b101_00000000, 0b00000001_00000000));
+ assertEquals(cellId(s2Level, 5, intPow2(16)),
+ satS2RangeFileFormat.createCellId(0b101_00000001, 0b00000000_00000000));
+ assertTrue(satS2RangeFileFormat.isAllowedList());
+ }
+
+ @Test
+ public void extractFaceIdFromPrefix() {
+ int s2Level = 12;
+ int prefixBitCount = 11;
+ int suffixBitCount = 16;
+ int suffixTableBlockIdOffset = 5;
+ int suffixTableEntryBitCount = 24;
+ boolean isAllowedList = true;
+ SatS2RangeFileFormat satS2RangeFileFormat = new SatS2RangeFileFormat(s2Level,
+ prefixBitCount, suffixBitCount, suffixTableBlockIdOffset, suffixTableEntryBitCount,
+ isAllowedList);
+
+ assertEquals(0, satS2RangeFileFormat.extractFaceIdFromPrefix(0b00000000000));
+ assertEquals(5, satS2RangeFileFormat.extractFaceIdFromPrefix(0b10100000000));
+ // We require this (invalid) face ID to work, since this method is used to detect face ID
+ // overflow.
+ assertEquals(6, satS2RangeFileFormat.extractFaceIdFromPrefix(0b11000000000));
+ assertTrue(satS2RangeFileFormat.isAllowedList());
+ }
+
+ @Test
+ public void createSuffixTableValue() {
+ int s2Level = 12;
+ int prefixBitCount = 11;
+ int suffixBitCount = 16;
+ int suffixTableBlockIdOffset = 5;
+ int suffixTableEntryBitCount = 24;
+ boolean isAllowedList = true;
+ SatS2RangeFileFormat satS2RangeFileFormat = new SatS2RangeFileFormat(s2Level,
+ prefixBitCount, suffixBitCount, suffixTableBlockIdOffset, suffixTableEntryBitCount,
+ isAllowedList);
+
+ // Too many bits for rangeLength
+ assertThrows(IllegalArgumentException.class,
+ () -> satS2RangeFileFormat.createSuffixTableValue(0b100000000));
+
+ // Some valid cases.
+ assertEquals(0b10101, satS2RangeFileFormat.createSuffixTableValue(0b10101));
+ assertEquals(0b00000, satS2RangeFileFormat.createSuffixTableValue(0b00000));
+ assertTrue(satS2RangeFileFormat.isAllowedList());
+ }
+
+ private static int maxValForBits(int bits) {
+ return intPow2(bits) - 1;
+ }
+
+ private static int intPow2(int value) {
+ return (int) Math.pow(2, value);
+ }
+}
diff --git a/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/SatS2RangeFileReaderTest.java b/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/SatS2RangeFileReaderTest.java
new file mode 100644
index 0000000..bbfaef7
--- /dev/null
+++ b/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/SatS2RangeFileReaderTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.storage.s2.S2LevelRange;
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+import com.android.telephony.sats2range.read.SatS2RangeFileReader;
+import com.android.telephony.sats2range.utils.TestUtils;
+import com.android.telephony.sats2range.write.SatS2RangeFileWriter;
+
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class SatS2RangeFileReaderTest {
+ @Test
+ public void findEntryByCellId() throws IOException {
+ File file = File.createTempFile("test", ".dat");
+
+ SatS2RangeFileFormat fileFormat;
+ boolean isAllowedList = true;
+ S2LevelRange expectedRange1, expectedRange2, expectedRange3;
+ try (SatS2RangeFileWriter satS2RangeFileWriter = SatS2RangeFileWriter.open(
+ file, TestUtils.createS2RangeFileFormat(isAllowedList))) {
+ fileFormat = satS2RangeFileWriter.getFileFormat();
+
+ // Two ranges that share a prefix.
+ expectedRange1 = new S2LevelRange(
+ TestUtils.createCellId(fileFormat, 1, 1000, 1000),
+ TestUtils.createCellId(fileFormat, 1, 1000, 2000));
+ expectedRange2 = new S2LevelRange(
+ TestUtils.createCellId(fileFormat, 1, 1000, 2000),
+ TestUtils.createCellId(fileFormat, 1, 1000, 3000));
+ // This range has a different prefix, so will be in a different suffix table.
+ expectedRange3 = new S2LevelRange(
+ TestUtils.createCellId(fileFormat, 1, 1001, 1000),
+ TestUtils.createCellId(fileFormat, 1, 1001, 2000));
+
+ List<S2LevelRange> ranges = new ArrayList<>();
+ ranges.add(expectedRange1);
+ ranges.add(expectedRange2);
+ ranges.add(expectedRange3);
+ satS2RangeFileWriter.createSortedSuffixBlocks(ranges.iterator());
+ }
+
+ try (SatS2RangeFileReader satS2RangeFileReader = SatS2RangeFileReader.open(file)) {
+ assertEquals(isAllowedList, satS2RangeFileReader.isAllowedList());
+
+ S2LevelRange range1 = satS2RangeFileReader.findEntryByCellId(
+ TestUtils.createCellId(fileFormat, 1, 1000, 1500));
+ assertEquals(expectedRange1, range1);
+
+ S2LevelRange range2 = satS2RangeFileReader.findEntryByCellId(
+ TestUtils.createCellId(fileFormat, 1, 1000, 2500));
+ assertEquals(expectedRange2, range2);
+
+ S2LevelRange range3 = satS2RangeFileReader.findEntryByCellId(
+ TestUtils.createCellId(fileFormat, 1, 1001, 1500));
+ assertEquals(expectedRange3, range3);
+ }
+ }
+}
diff --git a/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/SuffixTableBlockMatcher.java b/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/SuffixTableBlockMatcher.java
new file mode 100644
index 0000000..483d5f5
--- /dev/null
+++ b/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/SuffixTableBlockMatcher.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range;
+
+import com.android.telephony.sats2range.read.SuffixTableBlock;
+
+import org.mockito.ArgumentMatcher;
+
+import java.util.Objects;
+
+/** A matcher for {@link SuffixTableBlock} - checks all the various fields and content. */
+public class SuffixTableBlockMatcher implements ArgumentMatcher<SuffixTableBlock> {
+
+ private final SuffixTableBlock mSuffixTableBlock;
+
+ public SuffixTableBlockMatcher(SuffixTableBlock suffixTableBlock) {
+ mSuffixTableBlock = suffixTableBlock;
+ }
+
+ @Override
+ public boolean matches(SuffixTableBlock block) {
+ if (mSuffixTableBlock.getPrefix() != block.getPrefix()
+ || mSuffixTableBlock.getEntryCount() != block.getEntryCount()) {
+ return false;
+ }
+ for (int i = 0; i < mSuffixTableBlock.getEntryCount(); i++) {
+ SuffixTableBlock.Entry expectedEntry = mSuffixTableBlock.getEntryByIndex(i);
+ SuffixTableBlock.Entry actualEntry = block.getEntryByIndex(i);
+ if (!Objects.equals(expectedEntry, actualEntry)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/SuffixTableBlockTest.java b/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/SuffixTableBlockTest.java
new file mode 100644
index 0000000..04b915b
--- /dev/null
+++ b/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/SuffixTableBlockTest.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.argThat;
+
+import com.android.storage.block.write.BlockWriter;
+import com.android.storage.s2.S2LevelRange;
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+import com.android.telephony.sats2range.read.SuffixTableBlock;
+import com.android.telephony.sats2range.read.SuffixTableSharedData;
+import com.android.telephony.sats2range.utils.TestUtils;
+import com.android.telephony.sats2range.write.SuffixTableWriter;
+
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+
+/** Tests for {@link SuffixTableWriter} and {@link SuffixTableBlock}. */
+public class SuffixTableBlockTest {
+ @Test
+ public void writer_createEmptyBlockWriter() throws Exception {
+ BlockWriter blockWriter = SuffixTableWriter.createEmptyBlockWriter();
+ BlockWriter.ReadBack readBack = blockWriter.close();
+ assertEquals(SatS2RangeFileFormat.BLOCK_TYPE_SUFFIX_TABLE, readBack.getType());
+ assertArrayEquals(new byte[0], readBack.getExtraBytes());
+ assertEquals(0, readBack.getBlockData().getSize());
+ }
+
+ @Test
+ public void writer_createPopulatedBlockWriter_noEntriesThrows() throws Exception {
+ SatS2RangeFileFormat fileFormat = TestUtils.createS2RangeFileFormat(true);
+ assertEquals(13, fileFormat.getPrefixBitCount());
+
+ int tablePrefixValue = 0b0010011_00110100;
+ SuffixTableSharedData suffixTableSharedData = new SuffixTableSharedData(tablePrefixValue);
+
+ SuffixTableWriter suffixTableWriter =
+ SuffixTableWriter.createPopulated(fileFormat, suffixTableSharedData);
+ // IllegalStateException is thrown because there is no entry in the block
+ assertThrows(IllegalStateException.class, suffixTableWriter::close);
+ }
+
+ @Test
+ public void writer_createPopulatedBlockWriter_addRange() throws Exception {
+ SatS2RangeFileFormat fileFormat = TestUtils.createS2RangeFileFormat(true);
+ assertEquals(13, fileFormat.getPrefixBitCount());
+ assertEquals(14, fileFormat.getSuffixBitCount());
+
+ int tablePrefixValue = 0b0010011_00110100;
+ int maxSuffixValue = 0b00111111_11111111;
+ SuffixTableSharedData suffixTableSharedData = new SuffixTableSharedData(tablePrefixValue);
+
+ SuffixTableWriter suffixTableWriter =
+ SuffixTableWriter.createPopulated(fileFormat, suffixTableSharedData);
+
+ long invalidStartCellId = fileFormat.createCellId(tablePrefixValue - 1, 0);
+ long validStartCellId = fileFormat.createCellId(tablePrefixValue, 0);
+ long invalidEndCellId = fileFormat.createCellId(tablePrefixValue + 1, maxSuffixValue);
+ long validEndCellId = fileFormat.createCellId(tablePrefixValue, maxSuffixValue);
+ {
+ S2LevelRange badStartCellId = new S2LevelRange(invalidStartCellId, validEndCellId);
+ assertThrows(IllegalArgumentException.class,
+ () -> suffixTableWriter.addRange(badStartCellId));
+ }
+ {
+ S2LevelRange badEndCellId = new S2LevelRange(validStartCellId, invalidEndCellId);
+ assertThrows(IllegalArgumentException.class,
+ () -> suffixTableWriter.addRange(badEndCellId));
+ }
+ }
+
+ @Test
+ public void writer_createPopulatedBlockWriter_rejectOverlappingRanges() throws Exception {
+ SatS2RangeFileFormat fileFormat = TestUtils.createS2RangeFileFormat(true);
+ assertEquals(13, fileFormat.getPrefixBitCount());
+ assertEquals(14, fileFormat.getSuffixBitCount());
+
+ int tablePrefixValue = 0b0010011_00110100;
+ int maxSuffixValue = 0b00111111_11111111;
+ SuffixTableSharedData suffixTableSharedData = new SuffixTableSharedData(tablePrefixValue);
+
+ SuffixTableWriter suffixTableWriter =
+ SuffixTableWriter.createPopulated(fileFormat, suffixTableSharedData);
+ S2LevelRange suffixTableRange1 = new S2LevelRange(
+ fileFormat.createCellId(tablePrefixValue, 1000),
+ fileFormat.createCellId(tablePrefixValue, 1001));
+ suffixTableWriter.addRange(suffixTableRange1);
+
+ // It's fine to add a range that starts adjacent to the last one.
+ S2LevelRange suffixTableRange2 = new S2LevelRange(
+ fileFormat.createCellId(tablePrefixValue, 1001),
+ fileFormat.createCellId(tablePrefixValue, 1002));
+ suffixTableWriter.addRange(suffixTableRange2);
+
+ // IllegalArgumentException is thrown because suffixTableRange2 already exists
+ assertThrows(IllegalArgumentException.class,
+ () -> suffixTableWriter.addRange(suffixTableRange2));
+
+ // Try similar checks at the top end of the table.
+ S2LevelRange suffixTableRange3 = new S2LevelRange(
+ fileFormat.createCellId(tablePrefixValue, maxSuffixValue - 1),
+ fileFormat.createCellId(tablePrefixValue, maxSuffixValue));
+ suffixTableWriter.addRange(suffixTableRange3);
+
+ // IllegalArgumentException is thrown because ranges already exist
+ assertThrows(IllegalArgumentException.class,
+ () -> suffixTableWriter.addRange(suffixTableRange1));
+ assertThrows(IllegalArgumentException.class,
+ () -> suffixTableWriter.addRange(suffixTableRange2));
+ assertThrows(IllegalArgumentException.class,
+ () -> suffixTableWriter.addRange(suffixTableRange3));
+
+ // Now "complete" the table: there can be no entry after this one.
+ S2LevelRange suffixTableRange4 = new S2LevelRange(
+ fileFormat.createCellId(tablePrefixValue, maxSuffixValue),
+ fileFormat.createCellId(tablePrefixValue + 1, 0));
+ suffixTableWriter.addRange(suffixTableRange4);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> suffixTableWriter.addRange(suffixTableRange4));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> suffixTableWriter.addRange(suffixTableRange1));
+ assertThrows(IllegalArgumentException.class,
+ () -> suffixTableWriter.addRange(suffixTableRange2));
+ assertThrows(IllegalArgumentException.class,
+ () -> suffixTableWriter.addRange(suffixTableRange3));
+ assertThrows(IllegalArgumentException.class,
+ () -> suffixTableWriter.addRange(suffixTableRange4));
+ }
+
+ @Test
+ public void suffixTableBlock_empty() {
+ SatS2RangeFileFormat fileFormat = TestUtils.createS2RangeFileFormat(true);
+ assertEquals(13, fileFormat.getPrefixBitCount());
+ int tablePrefix = 0b10011_00110100;
+
+ SuffixTableBlock suffixTableBlock = SuffixTableBlock.createEmpty(fileFormat, tablePrefix);
+ assertEquals(tablePrefix, suffixTableBlock.getPrefix());
+ assertNull(suffixTableBlock.findEntryByCellId(fileFormat.createCellId(tablePrefix, 1)));
+ assertEquals(0, suffixTableBlock.getEntryCount());
+ assertThrows(IndexOutOfBoundsException.class,
+ () -> suffixTableBlock.getEntryByIndex(0));
+ assertThrows(IndexOutOfBoundsException.class,
+ () -> suffixTableBlock.getEntryByIndex(1));
+ }
+
+ @Test
+ public void suffixTableBlock_populated_findEntryByCellId() throws Exception {
+ SatS2RangeFileFormat fileFormat = TestUtils.createS2RangeFileFormat(true);
+ assertEquals(13, fileFormat.getPrefixBitCount());
+ assertEquals(14, fileFormat.getSuffixBitCount());
+
+ int tablePrefix = 0b10011_00110100;
+ int maxSuffix = 0b111111_11111111;
+ SuffixTableSharedData suffixTableSharedData = new SuffixTableSharedData(tablePrefix);
+
+ SuffixTableWriter suffixTableWriter =
+ SuffixTableWriter.createPopulated(fileFormat, suffixTableSharedData);
+
+ long entry1StartCellId = fileFormat.createCellId(tablePrefix, 1000);
+ long entry1EndCellId = fileFormat.createCellId(tablePrefix, 2000);
+ S2LevelRange entry1 = new S2LevelRange(entry1StartCellId, entry1EndCellId);
+ suffixTableWriter.addRange(entry1);
+
+ long entry2StartCellId = fileFormat.createCellId(tablePrefix, 2000);
+ long entry2EndCellId = fileFormat.createCellId(tablePrefix, 3000);
+ S2LevelRange entry2 = new S2LevelRange(entry2StartCellId, entry2EndCellId);
+ suffixTableWriter.addRange(entry2);
+
+ // There is a deliberate gap here between entry2 and entry3.
+ long entry3StartCellId = fileFormat.createCellId(tablePrefix, 4000);
+ long entry3EndCellId = fileFormat.createCellId(tablePrefix, 5000);
+ S2LevelRange entry3 = new S2LevelRange(entry3StartCellId, entry3EndCellId);
+ suffixTableWriter.addRange(entry3);
+
+ long entry4StartCellId = fileFormat.createCellId(tablePrefix, maxSuffix - 999);
+ long entry4EndCellId = fileFormat.createCellId(tablePrefix + 1, 0);
+ S2LevelRange entry4 = new S2LevelRange(entry4StartCellId, entry4EndCellId);
+ suffixTableWriter.addRange(entry4);
+
+ BlockWriter.ReadBack blockReadback = suffixTableWriter.close();
+ SuffixTableBlock suffixTableBlock =
+ SuffixTableBlock.createPopulated(fileFormat, blockReadback.getBlockData());
+ assertEquals(tablePrefix, suffixTableBlock.getPrefix());
+
+ assertNull(findEntryByCellId(fileFormat, suffixTableBlock, tablePrefix, 999));
+ assertEquals(entry1, findEntryByCellId(fileFormat, suffixTableBlock, tablePrefix, 1000));
+ assertEquals(entry1, findEntryByCellId(fileFormat, suffixTableBlock, tablePrefix, 1001));
+ assertEquals(entry1, findEntryByCellId(fileFormat, suffixTableBlock, tablePrefix, 1999));
+ assertEquals(entry2, findEntryByCellId(fileFormat, suffixTableBlock, tablePrefix, 2000));
+ assertEquals(entry2, findEntryByCellId(fileFormat, suffixTableBlock, tablePrefix, 2001));
+ assertEquals(entry2, findEntryByCellId(fileFormat, suffixTableBlock, tablePrefix, 2999));
+ assertNull(findEntryByCellId(fileFormat, suffixTableBlock, tablePrefix, 3000));
+ assertNull(findEntryByCellId(fileFormat, suffixTableBlock, tablePrefix, 3999));
+ assertEquals(entry3, findEntryByCellId(fileFormat, suffixTableBlock, tablePrefix, 4000));
+ assertEquals(entry3, findEntryByCellId(fileFormat, suffixTableBlock, tablePrefix, 4999));
+ assertNull(findEntryByCellId(fileFormat, suffixTableBlock, tablePrefix, maxSuffix - 1000));
+ assertEquals(
+ entry4,
+ findEntryByCellId(fileFormat, suffixTableBlock, tablePrefix, maxSuffix - 999));
+ assertEquals(
+ entry4,
+ findEntryByCellId(fileFormat, suffixTableBlock, tablePrefix, maxSuffix));
+
+ assertEquals(4, suffixTableBlock.getEntryCount());
+ assertThrows(IndexOutOfBoundsException.class,
+ () -> suffixTableBlock.getEntryByIndex(-1));
+ assertThrows(IndexOutOfBoundsException.class,
+ () -> suffixTableBlock.getEntryByIndex(4));
+
+ assertEquals(entry1, suffixTableBlock.getEntryByIndex(0).getSuffixTableRange());
+ assertEquals(entry2, suffixTableBlock.getEntryByIndex(1).getSuffixTableRange());
+ assertEquals(entry3, suffixTableBlock.getEntryByIndex(2).getSuffixTableRange());
+ assertEquals(entry4, suffixTableBlock.getEntryByIndex(3).getSuffixTableRange());
+ }
+
+ @Test
+ public void suffixTableBlock_populated_findEntryByCellId_cellIdOutOfRange() throws Exception {
+ SatS2RangeFileFormat fileFormat = TestUtils.createS2RangeFileFormat(true);
+
+ int tablePrefix = 0b10011_00110100;
+ assertEquals(13, fileFormat.getPrefixBitCount());
+
+ int tzIdSetBankId = 5;
+ assertTrue(tzIdSetBankId <= fileFormat.getMaxPrefixValue());
+
+ SuffixTableSharedData suffixTableSharedData = new SuffixTableSharedData(tablePrefix);
+
+ SuffixTableWriter suffixTableWriter =
+ SuffixTableWriter.createPopulated(fileFormat, suffixTableSharedData);
+ long entry1StartCellId = fileFormat.createCellId(tablePrefix, 1000);
+ long entry1EndCellId = fileFormat.createCellId(tablePrefix, 2000);
+ S2LevelRange entry1 = new S2LevelRange(entry1StartCellId, entry1EndCellId);
+ suffixTableWriter.addRange(entry1);
+ BlockWriter.ReadBack blockReadback = suffixTableWriter.close();
+
+ SuffixTableBlock suffixTableBlock =
+ SuffixTableBlock.createPopulated(fileFormat, blockReadback.getBlockData());
+
+ assertThrows(IllegalArgumentException.class, () -> suffixTableBlock.findEntryByCellId(
+ fileFormat.createCellId(tablePrefix - 1, 0)));
+ assertThrows(IllegalArgumentException.class, () -> suffixTableBlock.findEntryByCellId(
+ fileFormat.createCellId(tablePrefix + 1, 0)));
+ }
+
+ @Test
+ public void suffixTableBlock_visit() throws Exception {
+ SatS2RangeFileFormat fileFormat = TestUtils.createS2RangeFileFormat(true);
+
+ int tablePrefix = 0b10011_00110100;
+ assertEquals(13, fileFormat.getPrefixBitCount());
+
+ SuffixTableSharedData sharedData = new SuffixTableSharedData(tablePrefix);
+
+ SuffixTableWriter suffixTableWriter =
+ SuffixTableWriter.createPopulated(fileFormat, sharedData);
+
+ S2LevelRange entry1 = new S2LevelRange(
+ fileFormat.createCellId(tablePrefix, 1001),
+ fileFormat.createCellId(tablePrefix, 1101));
+ suffixTableWriter.addRange(entry1);
+
+ S2LevelRange entry2 = new S2LevelRange(
+ fileFormat.createCellId(tablePrefix, 2001),
+ fileFormat.createCellId(tablePrefix, 2101));
+ suffixTableWriter.addRange(entry2);
+
+ BlockWriter.ReadBack readBack = suffixTableWriter.close();
+
+ // Read the data back and confirm it matches what we expected.
+ SuffixTableBlock suffixTableBlock =
+ SuffixTableBlock.createPopulated(fileFormat, readBack.getBlockData());
+ SuffixTableBlock.SuffixTableBlockVisitor mockVisitor =
+ Mockito.mock(SuffixTableBlock.SuffixTableBlockVisitor.class);
+
+ suffixTableBlock.visit(mockVisitor);
+
+ InOrder inOrder = Mockito.inOrder(mockVisitor);
+ inOrder.verify(mockVisitor).begin();
+ inOrder.verify(mockVisitor).visit(argThat(new SuffixTableBlockMatcher(suffixTableBlock)));
+ inOrder.verify(mockVisitor).end();
+ }
+
+ private S2LevelRange findEntryByCellId(SatS2RangeFileFormat fileFormat,
+ SuffixTableBlock suffixTableBlock, int prefix, int suffix) {
+ long cellId = fileFormat.createCellId(prefix, suffix);
+ SuffixTableBlock.Entry entry = suffixTableBlock.findEntryByCellId(cellId);
+ return entry == null ? null : entry.getSuffixTableRange();
+ }
+}
diff --git a/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/SuffixTableExtraInfoTest.java b/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/SuffixTableExtraInfoTest.java
new file mode 100644
index 0000000..f992ae7
--- /dev/null
+++ b/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/SuffixTableExtraInfoTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.storage.block.read.BlockInfo;
+import com.android.storage.block.write.BlockWriter;
+import com.android.storage.s2.S2LevelRange;
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+import com.android.telephony.sats2range.read.SuffixTableExtraInfo;
+import com.android.telephony.sats2range.read.SuffixTableSharedData;
+import com.android.telephony.sats2range.utils.TestUtils;
+import com.android.telephony.sats2range.write.SuffixTableWriter;
+
+import org.junit.Test;
+public class SuffixTableExtraInfoTest {
+
+ @Test
+ public void create_emptyBlock() throws Exception {
+ // Generate a real suffix table block info and an empty block.
+ SatS2RangeFileFormat fileFormat = TestUtils.createS2RangeFileFormat(true);
+ BlockWriter emptyBlockWriter =
+ SuffixTableWriter.createEmptyBlockWriter();
+ BlockWriter.ReadBack readBack = emptyBlockWriter.close();
+
+ // Read back the block info.
+ BlockInfo blockInfo = createBlockInfo(readBack);
+
+ SuffixTableExtraInfo extraInfo = SuffixTableExtraInfo.create(fileFormat, blockInfo);
+ assertEquals(0, extraInfo.getEntryCount());
+ }
+
+ @Test
+ public void create_nonEmptyBlock() throws Exception {
+ // Generate a real suffix table block info and block containing some elements.
+ SatS2RangeFileFormat fileFormat = TestUtils.createS2RangeFileFormat(true);
+ SuffixTableSharedData suffixTableSharedData = createSuffixTableSharedData();
+ SuffixTableWriter suffixTableWriter =
+ SuffixTableWriter.createPopulated(fileFormat, suffixTableSharedData);
+
+ int tablePrefix = suffixTableSharedData.getTablePrefix();
+ S2LevelRange range1 = new S2LevelRange(
+ fileFormat.createCellId(tablePrefix, 1000),
+ fileFormat.createCellId(tablePrefix, 1001));
+ S2LevelRange range2 = new S2LevelRange(
+ fileFormat.createCellId(tablePrefix, 1002),
+ fileFormat.createCellId(tablePrefix, 1003));
+ S2LevelRange range3 = new S2LevelRange(
+ fileFormat.createCellId(tablePrefix, 1004),
+ fileFormat.createCellId(tablePrefix, 1005));
+
+ suffixTableWriter.addRange(range1);
+ suffixTableWriter.addRange(range2);
+ suffixTableWriter.addRange(range3);
+ BlockWriter.ReadBack readBack = suffixTableWriter.close();
+
+ // Read back the block info.
+ BlockInfo blockInfo = createBlockInfo(readBack);
+
+ SuffixTableExtraInfo extraInfo = SuffixTableExtraInfo.create(fileFormat, blockInfo);
+ assertEquals(3, extraInfo.getEntryCount());
+ }
+
+ private static SuffixTableSharedData createSuffixTableSharedData() {
+ int arbitraryPrefixValue = 1111;
+ return new SuffixTableSharedData(arbitraryPrefixValue);
+ }
+
+ /** Creates a BlockInfo for a written block. */
+ private static BlockInfo createBlockInfo(BlockWriter.ReadBack readBack) {
+ int arbitraryBlockId = 2222;
+ long arbitraryByteOffset = 12345L;
+ return new BlockInfo(
+ arbitraryBlockId, readBack.getType(), arbitraryByteOffset,
+ readBack.getBlockData().getSize(), readBack.getExtraBytes());
+ }
+}
diff --git a/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/SuffixTableSharedDataTest.java b/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/SuffixTableSharedDataTest.java
new file mode 100644
index 0000000..2baefa9
--- /dev/null
+++ b/utils/satellite/s2storage/src/test/java/com/android/telephony/sats2range/SuffixTableSharedDataTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.telephony.sats2range.read.SuffixTableSharedData;
+import com.android.telephony.sats2range.write.SuffixTableSharedDataWriter;
+
+import org.junit.Test;
+
+/** Tests for {@link SuffixTableSharedData} and {@link SuffixTableSharedDataWriter}. */
+public class SuffixTableSharedDataTest {
+ @Test
+ public void testSuffixTableSharedData() {
+ int prefix = 321;
+ SuffixTableSharedData sharedData = new SuffixTableSharedData(prefix);
+ byte[] bytes = SuffixTableSharedDataWriter.toBytes(sharedData);
+
+ assertEquals(sharedData, SuffixTableSharedData.fromBytes(bytes));
+ }
+}
+
diff --git a/utils/satellite/s2storage/src/testutils/java/com/android/telephony/sats2range/testutils/TestUtils.java b/utils/satellite/s2storage/src/testutils/java/com/android/telephony/sats2range/testutils/TestUtils.java
new file mode 100644
index 0000000..3cf2c78
--- /dev/null
+++ b/utils/satellite/s2storage/src/testutils/java/com/android/telephony/sats2range/testutils/TestUtils.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range.utils;
+
+import static com.android.storage.s2.S2Support.FACE_BIT_COUNT;
+
+import static org.junit.Assert.assertFalse;
+
+import com.android.storage.util.BitwiseUtils;
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+/** A utility class for satellite tests */
+public class TestUtils {
+ public static final int TEST_S2_LEVEL = 12;
+
+ /** Returns a valid {@link SatS2RangeFileFormat}. */
+ public static SatS2RangeFileFormat createS2RangeFileFormat(boolean isAllowedList) {
+ int dataS2Level = TEST_S2_LEVEL;
+ int faceIdBits = 3;
+ int bitCountPerLevel = 2;
+ int s2LevelBitCount = (dataS2Level * bitCountPerLevel) + faceIdBits;
+ int prefixLevel = 5;
+ int prefixBitCount = faceIdBits + (prefixLevel * bitCountPerLevel);
+ int suffixBitCount = s2LevelBitCount - prefixBitCount;
+ int suffixTableEntryBitCount = 4 * Byte.SIZE;
+ int suffixTableBlockIdOffset = 5;
+ return new SatS2RangeFileFormat(dataS2Level, prefixBitCount, suffixBitCount,
+ suffixTableBlockIdOffset, suffixTableEntryBitCount, isAllowedList);
+ }
+
+ /** Create an S2 cell ID */
+ public static long createCellId(
+ SatS2RangeFileFormat fileFormat, int faceId, int otherPrefixBits, int suffixBits) {
+ int prefixBitCount = fileFormat.getPrefixBitCount();
+ int otherPrefixBitsCount = prefixBitCount - FACE_BIT_COUNT;
+ int maxOtherPrefixBits = (int) BitwiseUtils.getLowBitsMask(otherPrefixBitsCount);
+ if (otherPrefixBits > maxOtherPrefixBits) {
+ throw new IllegalArgumentException("otherPrefixBits=" + otherPrefixBits
+ + " (" + Integer.toBinaryString(otherPrefixBits) + ")"
+ + " has more bits than otherPrefixBitsCount=" + otherPrefixBitsCount
+ + " allows");
+ }
+
+ int prefixValue = faceId;
+ prefixValue <<= otherPrefixBitsCount;
+ prefixValue |= otherPrefixBits;
+
+ int suffixBitCount = fileFormat.getSuffixBitCount();
+ if (suffixBits > BitwiseUtils.getLowBitsMask(suffixBitCount)) {
+ throw new IllegalArgumentException(
+ "suffixBits=" + suffixBits + " (" + Integer.toBinaryString(suffixBits)
+ + ") has more bits than " + suffixBitCount + " bits allows");
+ }
+ return fileFormat.createCellId(prefixValue, suffixBits);
+ }
+
+ /** Create a temporary directory */
+ public static Path createTempDir(Class<?> testClass) throws IOException {
+ return Files.createTempDirectory(testClass.getSimpleName());
+ }
+
+ /** Delete a directory */
+ public static void deleteDirectory(Path dir) throws IOException {
+ Files.walkFileTree(dir, new SimpleFileVisitor<>() {
+ @Override
+ public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes)
+ throws IOException {
+ Files.deleteIfExists(path);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path path, IOException e) throws IOException {
+ Files.delete(path);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ assertFalse(Files.exists(dir));
+ }
+
+ /** Create a valid test satellite S2 cell file */
+ public static void createValidTestS2CellFile(
+ File outputFile, SatS2RangeFileFormat fileFormat) throws Exception {
+ try (PrintStream printer = new PrintStream(outputFile)) {
+ // Range 1
+ for (int suffix = 1000; suffix < 2000; suffix++) {
+ printer.println(
+ Long.toUnsignedString(fileFormat.createCellId(0b100_11111111, suffix)));
+ }
+
+ // Range 2
+ for (int suffix = 2001; suffix < 3000; suffix++) {
+ printer.println(
+ Long.toUnsignedString(fileFormat.createCellId(0b100_11111111, suffix)));
+ }
+
+ // Range 3
+ for (int suffix = 1000; suffix < 2000; suffix++) {
+ printer.println(
+ Long.toUnsignedString(fileFormat.createCellId(0b101_11111111, suffix)));
+ }
+ printer.print(Long.toUnsignedString(fileFormat.createCellId(0b101_11111111, 2000)));
+
+ printer.close();
+ }
+ }
+
+ /** Create a invalid test satellite S2 cell file */
+ public static void createInvalidTestS2CellFile(
+ File outputFile, SatS2RangeFileFormat fileFormat) throws Exception {
+ try (PrintStream printer = new PrintStream(outputFile)) {
+ // Valid line
+ printer.println(Long.toUnsignedString(fileFormat.createCellId(0b100_11111111, 100)));
+
+ // Invalid line
+ printer.print("Invalid line");
+
+ // Another valid line
+ printer.println(Long.toUnsignedString(fileFormat.createCellId(0b100_11111111, 200)));
+
+ printer.close();
+ }
+ }
+}
diff --git a/utils/satellite/s2storage/src/write/java/com/android/telephony/sats2range/write/HeaderBlockWriter.java b/utils/satellite/s2storage/src/write/java/com/android/telephony/sats2range/write/HeaderBlockWriter.java
new file mode 100644
index 0000000..d4e9310
--- /dev/null
+++ b/utils/satellite/s2storage/src/write/java/com/android/telephony/sats2range/write/HeaderBlockWriter.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range.write;
+
+import com.android.storage.block.read.BlockData;
+import com.android.storage.block.write.BlockWriter;
+import com.android.storage.io.write.TypedOutputStream;
+import com.android.telephony.sats2range.read.HeaderBlock;
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.StandardOpenOption;
+
+/** A {@link BlockWriter} that can generate a satellite S2 data file header block. */
+public final class HeaderBlockWriter implements BlockWriter {
+
+ private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
+ private final File mFile;
+
+ private final SatS2RangeFileFormat mFileFormat;
+
+ private boolean mIsOpen = true;
+
+ private HeaderBlockWriter(SatS2RangeFileFormat fileFormat, File file) {
+ mFileFormat = fileFormat;
+ mFile = file;
+ }
+
+ /** Creates a new {@link HeaderBlockWriter}. */
+ public static HeaderBlockWriter create(SatS2RangeFileFormat fileFormat) throws IOException {
+ return new HeaderBlockWriter(fileFormat, File.createTempFile("header", ".bin"));
+ }
+
+ @Override
+ public ReadBack close() throws IOException {
+ checkIsOpen();
+ mIsOpen = false;
+
+ try (TypedOutputStream tos = new TypedOutputStream(new FileOutputStream(mFile))) {
+ tos.writeUnsignedByte(mFileFormat.getS2Level());
+ tos.writeUnsignedByte(mFileFormat.getPrefixBitCount());
+ tos.writeUnsignedByte(mFileFormat.getSuffixBitCount());
+ tos.writeUnsignedByte(mFileFormat.getTableEntryBitCount());
+ tos.writeUnsignedByte(mFileFormat.getSuffixTableBlockIdOffset());
+ tos.writeUnsignedByte(mFileFormat.isAllowedList()
+ ? HeaderBlock.TRUE : HeaderBlock.FALSE);
+ }
+
+ FileChannel fileChannel = FileChannel.open(mFile.toPath(), StandardOpenOption.READ);
+ MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, mFile.length());
+ fileChannel.close();
+ BlockData blockData = new BlockData(map);
+ return new ReadBack() {
+ @Override
+ public byte[] getExtraBytes() {
+ return EMPTY_BYTE_ARRAY;
+ }
+
+ @Override
+ public int getType() {
+ return SatS2RangeFileFormat.BLOCK_TYPE_HEADER;
+ }
+
+ @Override
+ public BlockData getBlockData() {
+ return blockData;
+ }
+ };
+ }
+
+ private void checkIsOpen() {
+ if (!mIsOpen) {
+ throw new IllegalStateException("Writer is closed.");
+ }
+ }
+}
diff --git a/utils/satellite/s2storage/src/write/java/com/android/telephony/sats2range/write/PushBackIterator.java b/utils/satellite/s2storage/src/write/java/com/android/telephony/sats2range/write/PushBackIterator.java
new file mode 100644
index 0000000..7bc375e
--- /dev/null
+++ b/utils/satellite/s2storage/src/write/java/com/android/telephony/sats2range/write/PushBackIterator.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range.write;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * An iterator that can have elements pushed back onto it. {@link #remove()} is not supported.
+ *
+ * @param <E> The type of the element returned by this iterator
+ */
+public final class PushBackIterator<E> implements Iterator<E> {
+
+ private final ArrayList<E> mPushBackStack = new ArrayList<>();
+
+ private final Iterator<E> mIterator;
+
+ public PushBackIterator(Iterator<E> iterator) {
+ mIterator = iterator;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return !mPushBackStack.isEmpty() || mIterator.hasNext();
+ }
+
+ @Override
+ public E next() {
+ if (!mPushBackStack.isEmpty()) {
+ return mPushBackStack.remove(mPushBackStack.size() - 1);
+ }
+ return mIterator.next();
+ }
+
+ /**
+ * Pushes the element to the front of the iterator again.
+ */
+ public void pushBack(E element) {
+ mPushBackStack.add(element);
+ }
+}
diff --git a/utils/satellite/s2storage/src/write/java/com/android/telephony/sats2range/write/SatS2RangeFileWriter.java b/utils/satellite/s2storage/src/write/java/com/android/telephony/sats2range/write/SatS2RangeFileWriter.java
new file mode 100644
index 0000000..9b3c20e
--- /dev/null
+++ b/utils/satellite/s2storage/src/write/java/com/android/telephony/sats2range/write/SatS2RangeFileWriter.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range.write;
+
+import com.android.storage.block.write.BlockFileWriter;
+import com.android.storage.block.write.BlockWriter;
+import com.android.storage.block.write.EmptyBlockWriter;
+import com.android.storage.s2.S2LevelRange;
+import com.android.storage.s2.S2Support;
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+import com.android.telephony.sats2range.read.SuffixTableSharedData;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.List;
+
+/** Writes a satellite S2 data file. */
+public final class SatS2RangeFileWriter implements AutoCloseable {
+
+ private final HeaderBlockWriter mHeaderBlockWriter;
+
+ private final List<BlockWriter> mSuffixTableBlockWriters = new ArrayList<>();
+
+ private final BlockFileWriter mBlockFileWriter;
+
+ private final SatS2RangeFileFormat mFileFormat;
+
+ private SatS2RangeFileWriter(SatS2RangeFileFormat fileFormat, BlockFileWriter blockFileWriter)
+ throws IOException {
+ mBlockFileWriter = blockFileWriter;
+ mFileFormat = fileFormat;
+
+ mHeaderBlockWriter = HeaderBlockWriter.create(fileFormat);
+ }
+
+ /** Opens a file for writing with the specified format. */
+ public static SatS2RangeFileWriter open(File outFile, SatS2RangeFileFormat fileFormat)
+ throws IOException {
+ BlockFileWriter writer = BlockFileWriter.open(
+ SatS2RangeFileFormat.MAGIC, SatS2RangeFileFormat.VERSION, outFile);
+ return new SatS2RangeFileWriter(fileFormat, writer);
+ }
+
+ /**
+ * Group the sorted ranges into contiguous suffix blocks. Big ranges might get split as
+ * needed to fit them into suffix blocks. The ranges must be of the expected S2 level
+ * and ordered by cell ID.
+ */
+ public void createSortedSuffixBlocks(Iterator<S2LevelRange> ranges) throws IOException {
+ PushBackIterator<S2LevelRange> pushBackIterator = new PushBackIterator<>(ranges);
+
+ // For each prefix value, collect all the ranges that match.
+ for (int currentPrefix = 0;
+ currentPrefix <= mFileFormat.getMaxPrefixValue();
+ currentPrefix++) {
+
+ // Step 1:
+ // populate samePrefixRanges, which holds ranges that have a prefix of currentPrefix.
+ List<S2LevelRange> samePrefixRanges =
+ collectSamePrefixRanges(pushBackIterator, currentPrefix);
+
+ // Step 2: Write samePrefixRanges to a suffix table.
+ BlockWriter blockWriter = writeSamePrefixRanges(currentPrefix, samePrefixRanges);
+ mSuffixTableBlockWriters.add(blockWriter);
+ }
+
+ // At this point there should be no data left.
+ if (pushBackIterator.hasNext()) {
+ throw new IllegalStateException("Unexpected ranges left at the end.");
+ }
+ }
+
+ private List<S2LevelRange> collectSamePrefixRanges(
+ PushBackIterator<S2LevelRange> pushBackIterator, int currentPrefix) {
+ List<S2LevelRange> samePrefixRanges = new ArrayList<>();
+ while (pushBackIterator.hasNext()) {
+ S2LevelRange currentRange = pushBackIterator.next();
+
+ long startCellId = currentRange.getStartCellId();
+ if (mFileFormat.getS2Level() != S2Support.getS2Level(startCellId)) {
+ throw new IllegalArgumentException(
+ "Input data level does not match file format level");
+ }
+ int startCellPrefix = mFileFormat.extractPrefixValueFromCellId(startCellId);
+ if (startCellPrefix != currentPrefix) {
+ if (startCellPrefix < currentPrefix) {
+ throw new IllegalStateException("Prefix out of order:"
+ + " currentPrefixValue=" + currentPrefix
+ + " startCellPrefixValue=" + startCellPrefix);
+ }
+ // The next range is for a later prefix. Put it back and move to step 2.
+ pushBackIterator.pushBack(currentRange);
+ break;
+ }
+
+ long endCellId = currentRange.getEndCellId();
+ if (mFileFormat.getS2Level() != S2Support.getS2Level(endCellId)) {
+ throw new IllegalArgumentException("endCellId in range " + currentRange
+ + " has the wrong S2 level");
+ }
+
+ // Split ranges if they span a prefix.
+ int endCellPrefixValue = mFileFormat.extractPrefixValueFromCellId(endCellId);
+ if (startCellPrefix != endCellPrefixValue) {
+ // Create a range for the current prefix.
+ {
+ long newEndCellId = mFileFormat.createCellId(startCellPrefix + 1, 0);
+ S2LevelRange satS2Range = new S2LevelRange(startCellId, newEndCellId);
+ samePrefixRanges.add(satS2Range);
+ }
+
+ Deque<S2LevelRange> otherRanges = new ArrayDeque<>();
+ // Intermediate prefixes.
+ startCellPrefix = startCellPrefix + 1;
+ while (startCellPrefix != endCellPrefixValue) {
+ long newStartCellId = mFileFormat.createCellId(startCellPrefix, 0);
+ long newEndCellId = mFileFormat.createCellId(startCellPrefix + 1, 0);
+ S2LevelRange satS2Range = new S2LevelRange(newStartCellId, newEndCellId);
+ otherRanges.add(satS2Range);
+ startCellPrefix++;
+ }
+
+ // Final prefix.
+ {
+ long newStartCellId = mFileFormat.createCellId(endCellPrefixValue, 0);
+ if (newStartCellId != endCellId) {
+ S2LevelRange satS2Range = new S2LevelRange(newStartCellId, endCellId);
+ otherRanges.add(satS2Range);
+ }
+ }
+
+ // Push back the ranges in reverse order so they come back out in sorted order.
+ while (!otherRanges.isEmpty()) {
+ pushBackIterator.pushBack(otherRanges.removeLast());
+ }
+ break;
+ } else {
+ samePrefixRanges.add(currentRange);
+ }
+ }
+ return samePrefixRanges;
+ }
+
+ private BlockWriter writeSamePrefixRanges(
+ int currentPrefix, List<S2LevelRange> samePrefixRanges) throws IOException {
+ BlockWriter blockWriter;
+ if (samePrefixRanges.size() == 0) {
+ // Add an empty block.
+ blockWriter = SuffixTableWriter.createEmptyBlockWriter();
+ } else {
+ // Create a suffix table block.
+ SuffixTableSharedData sharedData = new SuffixTableSharedData(currentPrefix);
+ SuffixTableWriter suffixTableWriter =
+ SuffixTableWriter.createPopulated(mFileFormat, sharedData);
+ S2LevelRange lastRange = null;
+ for (S2LevelRange currentRange : samePrefixRanges) {
+ // Validate ranges don't overlap.
+ if (lastRange != null) {
+ if (lastRange.overlaps(currentRange)) {
+ throw new IllegalStateException("lastRange=" + lastRange + " overlaps"
+ + " currentRange=" + currentRange);
+ }
+ }
+ lastRange = currentRange;
+
+ // Split the range so it fits.
+ final int maxRangeLength = mFileFormat.getTableEntryMaxRangeLengthValue();
+ long startCellId = currentRange.getStartCellId();
+ long endCellId = currentRange.getEndCellId();
+ int rangeLength = mFileFormat.calculateRangeLength(startCellId, endCellId);
+ while (rangeLength > maxRangeLength) {
+ long newEndCellId = S2Support.offsetCellId(startCellId, maxRangeLength);
+ S2LevelRange suffixTableRange = new S2LevelRange(startCellId, newEndCellId);
+ suffixTableWriter.addRange(suffixTableRange);
+ startCellId = newEndCellId;
+ rangeLength = mFileFormat.calculateRangeLength(startCellId, endCellId);
+ }
+ S2LevelRange suffixTableRange = new S2LevelRange(startCellId, endCellId);
+ suffixTableWriter.addRange(suffixTableRange);
+ }
+ blockWriter = suffixTableWriter;
+ }
+ return blockWriter;
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ BlockWriter.ReadBack headerReadBack = mHeaderBlockWriter.close();
+ mBlockFileWriter.addBlock(headerReadBack.getType(), headerReadBack.getExtraBytes(),
+ headerReadBack.getBlockData());
+
+ // Add empty blocks padding.
+ EmptyBlockWriter emptyBlockWriterHelper =
+ new EmptyBlockWriter(SatS2RangeFileFormat.BLOCK_TYPE_PADDING);
+ BlockWriter.ReadBack emptyBlockReadBack = emptyBlockWriterHelper.close();
+ for (int i = 0; i < mFileFormat.getSuffixTableBlockIdOffset() - 1; i++) {
+ mBlockFileWriter.addBlock(
+ emptyBlockReadBack.getType(), emptyBlockReadBack.getExtraBytes(),
+ emptyBlockReadBack.getBlockData());
+ }
+
+ // Add the suffix tables.
+ for (BlockWriter blockWriter : mSuffixTableBlockWriters) {
+ BlockWriter.ReadBack readBack = blockWriter.close();
+
+ mBlockFileWriter.addBlock(readBack.getType(), readBack.getExtraBytes(),
+ readBack.getBlockData());
+ }
+ } finally {
+ mBlockFileWriter.close();
+ }
+ }
+
+ /** Returns the{@link SatS2RangeFileFormat} for the file being written. */
+ public SatS2RangeFileFormat getFileFormat() {
+ return mFileFormat;
+ }
+}
diff --git a/utils/satellite/s2storage/src/write/java/com/android/telephony/sats2range/write/SuffixTableSharedDataWriter.java b/utils/satellite/s2storage/src/write/java/com/android/telephony/sats2range/write/SuffixTableSharedDataWriter.java
new file mode 100644
index 0000000..5499148
--- /dev/null
+++ b/utils/satellite/s2storage/src/write/java/com/android/telephony/sats2range/write/SuffixTableSharedDataWriter.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range.write;
+
+import com.android.storage.io.write.TypedOutputStream;
+import com.android.telephony.sats2range.read.SuffixTableSharedData;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Converts a {@link SuffixTableSharedData} to a byte[] for writing.
+ * See also {@link SuffixTableSharedData#fromBytes(byte[])}.
+ */
+public final class SuffixTableSharedDataWriter {
+
+ private SuffixTableSharedDataWriter() {
+ }
+
+ /** Returns the byte[] for the supplied {@link SuffixTableSharedData} */
+ public static byte[] toBytes(SuffixTableSharedData suffixTableSharedData) {
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ TypedOutputStream tos = new TypedOutputStream(baos)) {
+ tos.writeInt(suffixTableSharedData.getTablePrefix());
+ tos.flush();
+ return baos.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/utils/satellite/s2storage/src/write/java/com/android/telephony/sats2range/write/SuffixTableWriter.java b/utils/satellite/s2storage/src/write/java/com/android/telephony/sats2range/write/SuffixTableWriter.java
new file mode 100644
index 0000000..d9e4575
--- /dev/null
+++ b/utils/satellite/s2storage/src/write/java/com/android/telephony/sats2range/write/SuffixTableWriter.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.sats2range.write;
+
+import static com.android.storage.s2.S2Support.cellIdToString;
+
+import com.android.storage.block.read.BlockData;
+import com.android.storage.block.write.BlockWriter;
+import com.android.storage.block.write.EmptyBlockWriter;
+import com.android.storage.io.write.TypedOutputStream;
+import com.android.storage.s2.S2LevelRange;
+import com.android.storage.s2.S2Support;
+import com.android.storage.table.packed.write.PackedTableWriter;
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+import com.android.telephony.sats2range.read.SuffixTableExtraInfo;
+import com.android.telephony.sats2range.read.SuffixTableSharedData;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.StandardOpenOption;
+
+/**
+ * A class used to generate suffix tables block info and block data.
+ * To write empty tables use {@link #createEmptyBlockWriter()}.
+ * To write populated tables use {@link
+ * #createPopulated(SatS2RangeFileFormat, SuffixTableSharedData)} and add entries with
+ * {@link #addRange(S2LevelRange)}
+ */
+public final class SuffixTableWriter implements BlockWriter {
+
+ private final SuffixTableSharedData mSharedData;
+
+ private final SatS2RangeFileFormat mFileFormat;
+
+ private final PackedTableWriter mPackedTableWriter;
+
+ private final File mFile;
+
+ private S2LevelRange mLastRangeAdded;
+
+ private SuffixTableWriter(SatS2RangeFileFormat fileFormat, SuffixTableSharedData sharedData)
+ throws IOException {
+ mFileFormat = fileFormat;
+ mSharedData = sharedData;
+
+ int keySizeBits = fileFormat.getSuffixBitCount();
+ int entrySizeByteCount = fileFormat.getTableEntryByteCount();
+ mFile = File.createTempFile("suffixtablewriter", ".packed");
+
+ byte[] blockSharedData = SuffixTableSharedDataWriter.toBytes(sharedData);
+ FileOutputStream fileOutputStream = new FileOutputStream(mFile);
+ boolean signedValue = false;
+ mPackedTableWriter = PackedTableWriter.create(
+ fileOutputStream, entrySizeByteCount, keySizeBits, signedValue, blockSharedData);
+ }
+
+ /** Returns a {@link BlockWriter} capable of generating the block data for an empty table. */
+ public static BlockWriter createEmptyBlockWriter() {
+ return new EmptyBlockWriter(SatS2RangeFileFormat.BLOCK_TYPE_SUFFIX_TABLE);
+ }
+
+ /** Returns a {@link BlockWriter} capable of generating the block data for a populated table. */
+ public static SuffixTableWriter createPopulated(
+ SatS2RangeFileFormat fileFormat, SuffixTableSharedData sharedData) throws IOException {
+ return new SuffixTableWriter(fileFormat, sharedData);
+ }
+
+ /**
+ * Adds the supplied range to the table. The range must start after any previously added range,
+ * no overlap is allowed. Gaps are permitted. The range must have the expected S2 cell ID
+ * prefix. Invalid ranges will cause {@link IllegalArgumentException}. This method must be
+ * called at least once. See {@link SuffixTableWriter#createEmptyBlockWriter()} for empty
+ * tables.
+ */
+ public void addRange(S2LevelRange suffixTableRange) throws IOException {
+ checkIsOpen();
+
+ long rangeStartCellId = suffixTableRange.getStartCellId();
+ long rangeEndCellId = suffixTableRange.getEndCellId();
+
+ // Check range belongs in this table.
+ int rangeStartPrefixValue = mFileFormat.extractPrefixValueFromCellId(rangeStartCellId);
+ int rangeStartSuffixValue = mFileFormat.extractSuffixValueFromCellId(rangeStartCellId);
+ if (rangeStartPrefixValue != mSharedData.getTablePrefix()) {
+ throw new IllegalArgumentException(
+ "rangeStartCellId=" + cellIdToString(rangeStartCellId)
+ + " has a different prefix=" + rangeStartPrefixValue
+ + " than the table prefix=" + mSharedData.getTablePrefix());
+ }
+
+ long rangeEndCellIdInclusive = S2Support.offsetCellId(rangeEndCellId, -1);
+ int rangeEndPrefixValue = mFileFormat.extractPrefixValueFromCellId(rangeEndCellIdInclusive);
+ if (rangeEndPrefixValue != rangeStartPrefixValue) {
+ // Because SuffixTableRange has an exclusive end value, rangeEndPrefixValue is allowed
+ // to be the next prefix value if the rangeEndSuffixValue == 0.
+ int rangeEndSuffixValue = mFileFormat.extractSuffixValueFromCellId(rangeEndCellId);
+ if (!(rangeEndPrefixValue == rangeStartPrefixValue + 1 && rangeEndSuffixValue == 0)) {
+ throw new IllegalArgumentException("rangeEndPrefixValue=" + rangeEndPrefixValue
+ + " != rangeStartPrefixValue=" + rangeStartPrefixValue);
+ }
+ }
+
+ // Confirm the new range starts after the end of the last one that was added, if any.
+ if (mLastRangeAdded != null) {
+ long lastRangeAddedEndCellId = mLastRangeAdded.getEndCellId();
+ int lastRangeEndPrefixValue =
+ mFileFormat.extractPrefixValueFromCellId(lastRangeAddedEndCellId);
+ if (lastRangeEndPrefixValue != mSharedData.getTablePrefix()) {
+ // Deal with the special case where the last range added completed the table.
+ throw new IllegalArgumentException(
+ "Suffix table is full: last range added=" + mLastRangeAdded);
+ } else {
+ int lastRangeEndSuffixValue =
+ mFileFormat.extractSuffixValueFromCellId(lastRangeAddedEndCellId);
+ if (rangeStartSuffixValue < lastRangeEndSuffixValue) {
+ throw new IllegalArgumentException("suffixTableRange=" + suffixTableRange
+ + " overlaps with last range added=" + mLastRangeAdded);
+ }
+ }
+ }
+
+ int rangeLength = mFileFormat.calculateRangeLength(rangeStartCellId, rangeEndCellId);
+
+ long value = mFileFormat.createSuffixTableValue(rangeLength);
+ mPackedTableWriter.addEntry(rangeStartSuffixValue, value);
+ mLastRangeAdded = suffixTableRange;
+ }
+
+ @Override
+ public ReadBack close() throws IOException {
+ checkIsOpen();
+ mPackedTableWriter.close();
+ mLastRangeAdded = null;
+
+ int entryCount = mPackedTableWriter.getEntryCount();
+ if (entryCount == 0) {
+ throw new IllegalStateException("No ranges added. For an empty suffix table, use"
+ + " createEmptySuffixTableBlockWriter()");
+ }
+
+ FileChannel fileChannel = FileChannel.open(mFile.toPath(), StandardOpenOption.READ);
+ MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, mFile.length());
+ fileChannel.close();
+
+ // Writes the number of entries into the extra bytes stored in the BlockInfo. This means the
+ // number of entries can be known without reading the block data at all.
+ SuffixTableExtraInfo suffixTableExtraInfo =
+ new SuffixTableExtraInfo(mSharedData.getTablePrefix(), entryCount);
+ byte[] blockInfoExtraBytes = generateBlockInfoExtraBytes(suffixTableExtraInfo);
+ BlockData blockData = new BlockData(map);
+ return new ReadBack() {
+ @Override
+ public byte[] getExtraBytes() {
+ return blockInfoExtraBytes;
+ }
+
+ @Override
+ public int getType() {
+ return SatS2RangeFileFormat.BLOCK_TYPE_SUFFIX_TABLE;
+ }
+
+ @Override
+ public BlockData getBlockData() {
+ return blockData;
+ }
+ };
+ }
+
+ private void checkIsOpen() {
+ if (!mPackedTableWriter.isOpen()) {
+ throw new IllegalStateException("Writer is closed.");
+ }
+ }
+
+ private static byte[] generateBlockInfoExtraBytes(SuffixTableExtraInfo suffixTableBlockInfo) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (TypedOutputStream tos = new TypedOutputStream(baos)) {
+ tos.writeInt(suffixTableBlockInfo.getEntryCount());
+ } catch (IOException e) {
+ throw new IllegalStateException("Unexpected IOException writing to byte array", e);
+ }
+ return baos.toByteArray();
+ }
+}
diff --git a/utils/satellite/tools/Android.bp b/utils/satellite/tools/Android.bp
new file mode 100644
index 0000000..d48b911
--- /dev/null
+++ b/utils/satellite/tools/Android.bp
@@ -0,0 +1,80 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library_host {
+ name: "satellite-s2storage-tools",
+ srcs: [
+ "src/main/java/**/*.java",
+ ],
+ static_libs: [
+ "jcommander",
+ "guava",
+ "satellite-s2storage-rw",
+ "s2storage_tools",
+ "s2-geometry-library-java",
+ ],
+}
+
+// A tool to create a binary satellite S2 file.
+java_binary_host {
+ name: "satellite_createsats2file",
+ main_class: "com.android.telephony.tools.sats2.CreateSatS2File",
+ static_libs: [
+ "satellite-s2storage-tools",
+ ],
+}
+
+// A tool to look up a location in the input binary satellite S2 file.
+java_binary_host {
+ name: "satellite_location_lookup",
+ main_class: "com.android.telephony.tools.sats2.SatS2LocationLookup",
+ static_libs: [
+ "satellite-s2storage-tools",
+ ],
+}
+
+// A tool to create a test satellite S2 file.
+java_binary_host {
+ name: "satellite_createsats2file_test",
+ main_class: "com.android.telephony.tools.sats2.CreateTestSatS2File",
+ static_libs: [
+ "satellite-s2storage-tools",
+ ],
+}
+
+// A tool to dump a satellite S2 file as text for debugging.
+java_binary_host {
+ name: "satellite_dumpsats2file",
+ main_class: "com.android.telephony.tools.sats2.DumpSatS2File",
+ static_libs: [
+ "satellite-s2storage-tools",
+ ],
+}
+
+// Tests for CreateSatS2File.
+java_test_host {
+ name: "SatelliteToolsTests",
+ srcs: ["src/test/java/**/*.java"],
+ static_libs: [
+ "junit",
+ "satellite-s2storage-tools",
+ "s2-geometry-library-java",
+ "satellite-s2storage-testutils"
+ ],
+ test_suites: ["general-tests"],
+}
\ No newline at end of file
diff --git a/utils/satellite/tools/TEST_MAPPING b/utils/satellite/tools/TEST_MAPPING
new file mode 100644
index 0000000..df9511a
--- /dev/null
+++ b/utils/satellite/tools/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "SatelliteToolsTests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/CreateSatS2File.java b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/CreateSatS2File.java
new file mode 100644
index 0000000..f82cd5c
--- /dev/null
+++ b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/CreateSatS2File.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.tools.sats2;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParameterException;
+
+/** Creates a Sat S2 file from the list of S2 cells. */
+public final class CreateSatS2File {
+ /**
+ * Usage:
+ * CreateSatS2File <[input] s2 cells file> <[input] s2 level of input data>
+ * <[input] whether s2 cells is an allowed list> <[output] sat s2 file>
+ */
+ public static void main(String[] args) throws Exception {
+ Arguments arguments = new Arguments();
+ JCommander.newBuilder()
+ .addObject(arguments)
+ .build()
+ .parse(args);
+ String inputFile = arguments.inputFile;
+ int s2Level = arguments.s2Level;
+ String outputFile = arguments.outputFile;
+ boolean isAllowedList = Arguments.getBooleanValue(arguments.isAllowedList);
+ SatS2FileCreator.create(inputFile, s2Level, isAllowedList, outputFile);
+ }
+
+ private static class Arguments {
+ @Parameter(names = "--input-file",
+ description = "s2 cells file",
+ required = true)
+ public String inputFile;
+
+ @Parameter(names = "--s2-level",
+ description = "s2 level of input data",
+ required = true)
+ public int s2Level;
+
+ @Parameter(names = "--is-allowed-list",
+ description = "whether s2 cells file contains an allowed list of cells",
+ required = true)
+ public String isAllowedList;
+
+ @Parameter(names = "--output-file",
+ description = "sat s2 file",
+ required = true)
+ public String outputFile;
+
+ public static Boolean getBooleanValue(String value) {
+ if ("false".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value)) {
+ return Boolean.parseBoolean(value);
+ } else {
+ throw new ParameterException("Invalid boolean string:" + value);
+ }
+ }
+ }
+}
diff --git a/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/CreateTestSatS2File.java b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/CreateTestSatS2File.java
new file mode 100644
index 0000000..f9a9347
--- /dev/null
+++ b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/CreateTestSatS2File.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.tools.sats2;
+
+import com.android.storage.s2.S2LevelRange;
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+import com.android.telephony.sats2range.write.SatS2RangeFileWriter;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+/** Creates a Sat S2 file with a small amount of test data. Useful for testing other tools. */
+public final class CreateTestSatS2File {
+
+ /**
+ * Usage:
+ * CreateTestSatS2File <file name>
+ */
+ public static void main(String[] args) throws Exception {
+ File file = new File(args[0]);
+
+ SatS2RangeFileFormat fileFormat = FileFormats.getFileFormatForLevel(12, true);
+ if (fileFormat.getPrefixBitCount() != 11) {
+ throw new IllegalStateException("Fake data requires 11 prefix bits");
+ }
+
+ try (SatS2RangeFileWriter satS2RangeFileWriter =
+ SatS2RangeFileWriter.open(file, fileFormat)) {
+ // Two ranges that share a prefix.
+ S2LevelRange range1 = new S2LevelRange(
+ fileFormat.createCellId(0b100_11111111, 1000),
+ fileFormat.createCellId(0b100_11111111, 2000));
+ S2LevelRange range2 = new S2LevelRange(
+ fileFormat.createCellId(0b100_11111111, 2000),
+ fileFormat.createCellId(0b100_11111111, 3000));
+ // This range has a different face, so a different prefix, and will be in a different
+ // suffix table.
+ S2LevelRange range3 = new S2LevelRange(
+ fileFormat.createCellId(0b101_11111111, 1000),
+ fileFormat.createCellId(0b101_11111111, 2000));
+ List<S2LevelRange> allRanges = listOf(range1, range2, range3);
+ satS2RangeFileWriter.createSortedSuffixBlocks(allRanges.iterator());
+ }
+ }
+
+ @SafeVarargs
+ private static <E> List<E> listOf(E... values) {
+ return Arrays.asList(values);
+ }
+}
diff --git a/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/DumpSatS2File.java b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/DumpSatS2File.java
new file mode 100644
index 0000000..2a9ce37
--- /dev/null
+++ b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/DumpSatS2File.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.tools.sats2;
+
+import com.android.storage.tools.block.DumpBlockFile;
+import com.android.telephony.sats2range.read.SatS2RangeFileReader;
+import com.android.telephony.tools.sats2.dump.SatS2RangeFileDumper;
+
+import java.io.File;
+
+/**
+ * Dumps information about a Sat S2 data file. Like {@link DumpBlockFile} but it knows details about
+ * the Sat S2 format and can provide more detailed information.
+ */
+public final class DumpSatS2File {
+
+ /**
+ * Usage:
+ * DumpSatFile <[input] sat s2 file name> <[output] output directory name>
+ */
+ public static void main(String[] args) throws Exception {
+ String satS2FileName = args[0];
+ String outputDirName = args[1];
+
+ File outputDir = new File(outputDirName);
+ outputDir.mkdirs();
+
+ File satS2File = new File(satS2FileName);
+ try (SatS2RangeFileReader reader = SatS2RangeFileReader.open(satS2File)) {
+ reader.visit(new SatS2RangeFileDumper(outputDir));
+ }
+ }
+}
diff --git a/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/FileFormats.java b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/FileFormats.java
new file mode 100644
index 0000000..b800897
--- /dev/null
+++ b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/FileFormats.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.tools.sats2;
+
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+
+/** Some sample file formats. */
+public final class FileFormats {
+
+ // level 12: 27 S2 cell ID bits split 11 + 16,
+ // suffix table: 24 bits, 16/24 for cell id suffix, 8/24 dedicated to range
+ private static final SatS2RangeFileFormat FILE_FORMAT_12_ALLOWED_LIST =
+ new SatS2RangeFileFormat(12, 11, 16, 1, 24, true);
+ private static final SatS2RangeFileFormat FILE_FORMAT_12_DISALLOWED_LIST =
+ new SatS2RangeFileFormat(12, 11, 16, 1, 24, false);
+
+ // level 14: 31 S2 cell ID bits split 13 + 18,
+ // suffix table: 32 bits, 18/32 for cell id suffix, 14/32 dedicated to range
+ private static final SatS2RangeFileFormat FILE_FORMAT_14_ALLOWED_LIST =
+ new SatS2RangeFileFormat(14, 13, 18, 1, 32, true);
+ private static final SatS2RangeFileFormat FILE_FORMAT_14_DISALLOWED_LIST =
+ new SatS2RangeFileFormat(14, 13, 18, 1, 32, false);
+
+ // level 16: 35 S2 cell ID bits split 13 + 22,
+ // suffix table: 32 bits, 22/32 for cell id suffix, 10/32 dedicated to range
+ private static final SatS2RangeFileFormat FILE_FORMAT_16_ALLOWED_LIST =
+ new SatS2RangeFileFormat(16, 13, 22, 1, 32, true);
+ private static final SatS2RangeFileFormat FILE_FORMAT_16_DISALLOWED_LIST =
+ new SatS2RangeFileFormat(16, 13, 22, 1, 32, false);
+
+ /** Maps an S2 level to one of the file format constants declared on by class. */
+ public static SatS2RangeFileFormat getFileFormatForLevel(int s2Level, boolean isAllowedList) {
+ switch (s2Level) {
+ case 12:
+ return isAllowedList ? FILE_FORMAT_12_ALLOWED_LIST : FILE_FORMAT_12_DISALLOWED_LIST;
+ case 14:
+ return isAllowedList ? FILE_FORMAT_14_ALLOWED_LIST : FILE_FORMAT_14_DISALLOWED_LIST;
+ case 16:
+ return isAllowedList ? FILE_FORMAT_16_ALLOWED_LIST : FILE_FORMAT_16_DISALLOWED_LIST;
+ default:
+ throw new IllegalArgumentException("s2Level=" + s2Level
+ + ", isAllowedList=" + isAllowedList + " not mapped");
+ }
+ }
+}
diff --git a/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/SatS2FileCreator.java b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/SatS2FileCreator.java
new file mode 100644
index 0000000..bc25d6b
--- /dev/null
+++ b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/SatS2FileCreator.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.tools.sats2;
+
+import com.android.storage.s2.S2LevelRange;
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+import com.android.telephony.sats2range.read.SatS2RangeFileReader;
+import com.android.telephony.sats2range.write.SatS2RangeFileWriter;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.geometry.S2CellId;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Scanner;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/** A util class for creating a satellite S2 file from the list of S2 cells. */
+public final class SatS2FileCreator {
+ /**
+ * @param inputFile The input text file containing the list of S2 Cell IDs. Each line in the
+ * file contains a number in the range of an unsigned-64bit number which
+ * represents the ID of a S2 cell.
+ * @param s2Level The S2 level of all S2 cells in the input file.
+ * @param isAllowedList {@code true} means the input file contains an allowed list of S2 cells.
+ * {@code false} means the input file contains a disallowed list of S2
+ * cells.
+ * @param outputFile The output file to which the satellite S2 data in block format will be
+ * written.
+ */
+ public static void create(String inputFile, int s2Level, boolean isAllowedList,
+ String outputFile) throws Exception {
+ // Read a list of S2 cells from input file
+ List<Long> s2Cells = readS2CellsFromFile(inputFile);
+ System.out.println("Number of S2 cells read from file:" + s2Cells.size());
+
+ // Convert the input list of S2 Cells into the list of sorted S2CellId
+ System.out.println("Denormalizing S2 Cell IDs to the expected s2 level=" + s2Level);
+ List<S2CellId> sortedS2CellIds = denormalize(s2Cells, s2Level);
+ // IDs of S2CellId are converted to unsigned long numbers, which will be then used to
+ // compare S2CellId.
+ Collections.sort(sortedS2CellIds);
+ System.out.println("Number of S2 cell IDs:" + sortedS2CellIds.size());
+
+ // Compress the list of S2CellId into S2 ranges
+ List<SatS2Range> satS2Ranges = createSatS2Ranges(sortedS2CellIds, s2Level);
+
+ // Write the S2 ranges into a block file
+ SatS2RangeFileFormat fileFormat =
+ FileFormats.getFileFormatForLevel(s2Level, isAllowedList);
+ try (SatS2RangeFileWriter satS2RangeFileWriter =
+ SatS2RangeFileWriter.open(new File(outputFile), fileFormat)) {
+ Iterator<S2LevelRange> s2LevelRangeIterator = satS2Ranges
+ .stream()
+ .map(x -> new S2LevelRange(x.rangeStart.id(), x.rangeEnd.id()))
+ .iterator();
+ /*
+ * Group the sorted ranges into contiguous suffix blocks. Big ranges might get split as
+ * needed to fit them into suffix blocks.
+ */
+ satS2RangeFileWriter.createSortedSuffixBlocks(s2LevelRangeIterator);
+ }
+
+ // Validate the output block file
+ System.out.println("Validating the output block file...");
+ try (SatS2RangeFileReader satS2RangeFileReader =
+ SatS2RangeFileReader.open(new File(outputFile))) {
+ if (isAllowedList != satS2RangeFileReader.isAllowedList()) {
+ throw new IllegalStateException("isAllowedList="
+ + satS2RangeFileReader.isAllowedList() + " does not match the input "
+ + "argument=" + isAllowedList);
+ }
+
+ // Verify that all input S2 cells are present in the output block file
+ for (S2CellId s2CellId : sortedS2CellIds) {
+ if (satS2RangeFileReader.findEntryByCellId(s2CellId.id()) == null) {
+ throw new IllegalStateException("s2CellId=" + s2CellId
+ + " is not present in the output sat s2 file");
+ }
+ }
+
+ // Verify the cell right before the first cell in the sortedS2CellIds is not present in
+ // the output block file
+ S2CellId prevCell = sortedS2CellIds.get(0).prev();
+ if (!sortedS2CellIds.contains(prevCell)
+ && satS2RangeFileReader.findEntryByCellId(prevCell.id()) != null) {
+ throw new IllegalStateException("The cell " + prevCell + ", which is right "
+ + "before the first cell is unexpectedly present in the output sat s2"
+ + " file");
+ } else {
+ System.out.println("prevCell=" + prevCell + " is in the sortedS2CellIds");
+ }
+
+ // Verify the cell right after the last cell in the sortedS2CellIds is not present in
+ // the output block file
+ S2CellId nextCell = sortedS2CellIds.get(sortedS2CellIds.size() - 1).next();
+ if (!sortedS2CellIds.contains(nextCell)
+ && satS2RangeFileReader.findEntryByCellId(nextCell.id()) != null) {
+ throw new IllegalStateException("The cell " + nextCell + ", which is right "
+ + "after the last cell is unexpectedly present in the output sat s2"
+ + " file");
+ } else {
+ System.out.println("nextCell=" + nextCell + " is in the sortedS2CellIds");
+ }
+ }
+ System.out.println("Successfully validated the output block file");
+ }
+
+ /**
+ * Read a list of S2 cells from the inputFile.
+ *
+ * @param inputFile A file containing the list of S2 cells. Each line in the inputFile contains
+ * an unsigned long number - the ID of a S2 cell.
+ * @return A list of S2 cells.
+ */
+ private static List<Long> readS2CellsFromFile(String inputFile) throws Exception {
+ List<Long> s2Cells = new ArrayList();
+ InputStream inputStream = new FileInputStream(inputFile);
+ try (Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name())) {
+ while (scanner.hasNextLine()) {
+ String line = scanner.nextLine();
+ try {
+ s2Cells.add(Long.parseUnsignedLong(line));
+ } catch (Exception ex) {
+ throw new IllegalStateException("Input s2 cell file has invalid format, "
+ + "current line=" + line);
+ }
+ }
+ }
+ return s2Cells;
+ }
+
+ /**
+ * Convert the list of S2 Cell numbers into the list of S2 Cell IDs at the expected level.
+ */
+ private static List<S2CellId> denormalize(List<Long> s2CellNumbers, int s2Level) {
+ Set<S2CellId> result = new HashSet<>();
+ for (long s2CellNumber : s2CellNumbers) {
+ S2CellId s2CellId = new S2CellId(s2CellNumber);
+ if (s2CellId.level() == s2Level) {
+ if (!result.contains(s2CellId)) {
+ result.add(s2CellId);
+ }
+ } else if (s2CellId.level() < s2Level) {
+ S2CellId childEnd = s2CellId.childEnd(s2Level);
+ for (s2CellId = s2CellId.childBegin(s2Level); !s2CellId.equals(childEnd);
+ s2CellId = s2CellId.next()) {
+ if (!result.contains(s2CellId)) {
+ result.add(s2CellId);
+ }
+ }
+ } else {
+ S2CellId parent = s2CellId.parent(s2Level);
+ if (!result.contains(parent)) {
+ result.add(parent);
+ }
+ }
+ }
+ return new ArrayList(result);
+ }
+
+ /**
+ * Compress the list of sorted S2CellId into S2 ranges.
+ *
+ * @param sortedS2CellIds List of S2CellId sorted in ascending order.
+ * @param s2Level The level of all S2CellId.
+ * @return List of S2 ranges.
+ */
+ private static List<SatS2Range> createSatS2Ranges(List<S2CellId> sortedS2CellIds, int s2Level) {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ List<SatS2Range> ranges = new ArrayList<>();
+ if (sortedS2CellIds != null && sortedS2CellIds.size() > 0) {
+ S2CellId rangeStart = null;
+ S2CellId rangeEnd = null;
+ for (int i = 0; i < sortedS2CellIds.size(); i++) {
+ S2CellId currentS2CellId = sortedS2CellIds.get(i);
+ checkCellIdIsAtLevel(currentS2CellId, s2Level);
+
+ SatS2Range currentRange = createS2Range(currentS2CellId, s2Level);
+ S2CellId currentS2CellRangeStart = currentRange.rangeStart;
+ S2CellId currentS2CellRangeEnd = currentRange.rangeEnd;
+
+ if (rangeStart == null) {
+ // First time round the loop initialize rangeStart / rangeEnd only.
+ rangeStart = currentS2CellRangeStart;
+ } else if (rangeEnd.id() != currentS2CellRangeStart.id()) {
+ // If there's a gap between cellIds, store the range we have so far and start a
+ // new range.
+ ranges.add(new SatS2Range(rangeStart, rangeEnd));
+ rangeStart = currentS2CellRangeStart;
+ }
+ rangeEnd = currentS2CellRangeEnd;
+ }
+ ranges.add(new SatS2Range(rangeStart, rangeEnd));
+ }
+
+ // Sorting the ranges is not necessary. As the input is sorted , it will already be sorted.
+ System.out.printf("Created %s SatS2Ranges in %s milliseconds\n",
+ ranges.size(), stopwatch.elapsed(TimeUnit.MILLISECONDS));
+ return ranges;
+ }
+
+ /**
+ * @return A pair of S2CellId for the range [s2CellId, s2CellId's next sibling)
+ */
+ private static SatS2Range createS2Range(
+ S2CellId s2CellId, int s2Level) {
+ // Since s2CellId is at s2Level, s2CellId.childBegin(s2Level) returns itself.
+ S2CellId firstS2CellRangeStart = s2CellId.childBegin(s2Level);
+ // Get the immediate next sibling of s2CellId
+ S2CellId firstS2CellRangeEnd = s2CellId.childEnd(s2Level);
+
+ if (firstS2CellRangeEnd.face() < firstS2CellRangeStart.face()
+ || !firstS2CellRangeEnd.isValid()) {
+ // Fix this if it becomes an issue.
+ throw new IllegalStateException("firstS2CellId=" + s2CellId
+ + ", childEnd(" + s2Level + ") produced an unsupported"
+ + " value=" + firstS2CellRangeEnd);
+ }
+ return new SatS2Range(firstS2CellRangeStart, firstS2CellRangeEnd);
+ }
+
+ private static void checkCellIdIsAtLevel(S2CellId cellId, int s2Level) {
+ if (cellId.level() != s2Level) {
+ throw new IllegalStateException("Bad level for cellId=" + cellId
+ + ". Must be s2Level=" + s2Level);
+ }
+ }
+
+ /**
+ * A range of S2 cell IDs at a fixed S2 level. The range is expressed as a start cell ID
+ * (inclusive) and an end cell ID (exclusive).
+ */
+ private static class SatS2Range {
+ public final S2CellId rangeStart;
+ public final S2CellId rangeEnd;
+
+ /**
+ * Creates an instance. If the range is invalid or the cell IDs are from different levels
+ * this method throws an {@link IllegalArgumentException}.
+ */
+ SatS2Range(S2CellId rangeStart, S2CellId rangeEnd) {
+ this.rangeStart = Objects.requireNonNull(rangeStart);
+ this.rangeEnd = Objects.requireNonNull(rangeEnd);
+ if (rangeStart.level() != rangeEnd.level()) {
+ throw new IllegalArgumentException(
+ "Levels differ: rangeStart=" + rangeStart + ", rangeEnd=" + rangeEnd);
+ }
+ if (rangeStart.greaterOrEquals(rangeEnd)) {
+ throw new IllegalArgumentException(
+ "Range start (" + rangeStart + " >= range end (" + rangeEnd + ")");
+ }
+ }
+ }
+}
diff --git a/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/SatS2LocationLookup.java b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/SatS2LocationLookup.java
new file mode 100644
index 0000000..444ff8d
--- /dev/null
+++ b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/SatS2LocationLookup.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.tools.sats2;
+
+import com.android.telephony.sats2range.read.SatS2RangeFileReader;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.google.common.geometry.S2CellId;
+import com.google.common.geometry.S2LatLng;
+
+import java.io.File;
+
+/** A util class for checking if a location is in the input satellite S2 file. */
+public final class SatS2LocationLookup {
+ /**
+ * A util method for checking if a location is in the input satellite S2 file.
+ */
+ public static void main(String[] args) throws Exception {
+ Arguments arguments = new Arguments();
+ JCommander.newBuilder()
+ .addObject(arguments)
+ .build()
+ .parse(args);
+
+ try (SatS2RangeFileReader satS2RangeFileReader =
+ SatS2RangeFileReader.open(new File(arguments.inputFile))) {
+ S2CellId s2CellId = getS2CellId(arguments.latDegrees, arguments.lngDegrees,
+ satS2RangeFileReader.getS2Level());
+ System.out.println("s2CellId=" + Long.toUnsignedString(s2CellId.id()));
+ if (satS2RangeFileReader.findEntryByCellId(s2CellId.id()) == null) {
+ System.out.println("The input file does not contain the input location");
+ } else {
+ System.out.println("The input file contains the input location");
+ }
+ }
+ }
+
+ private static S2CellId getS2CellId(double latDegrees, double lngDegrees, int s2Level) {
+ // Create the leaf S2 cell containing the given S2LatLng
+ S2CellId cellId = S2CellId.fromLatLng(S2LatLng.fromDegrees(latDegrees, lngDegrees));
+
+ // Return the S2 cell at the expected S2 level
+ return cellId.parent(s2Level);
+ }
+
+ private static class Arguments {
+ @Parameter(names = "--input-file",
+ description = "sat s2 file",
+ required = true)
+ public String inputFile;
+
+ @Parameter(names = "--lat-degrees",
+ description = "lat degress of the location",
+ required = true)
+ public double latDegrees;
+
+ @Parameter(names = "--lng-degrees",
+ description = "lng degress of the location",
+ required = true)
+ public double lngDegrees;
+ }
+}
diff --git a/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/dump/HeaderBlockDumper.java b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/dump/HeaderBlockDumper.java
new file mode 100644
index 0000000..69a3f70
--- /dev/null
+++ b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/dump/HeaderBlockDumper.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.tools.sats2.dump;
+
+import com.android.storage.tools.block.dump.SingleFileDumper;
+import com.android.telephony.sats2range.read.HeaderBlock;
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+
+import java.io.File;
+
+/** A {@link HeaderBlock.HeaderBlockVisitor} that dumps information to a file. */
+final class HeaderBlockDumper extends SingleFileDumper implements HeaderBlock.HeaderBlockVisitor {
+
+ HeaderBlockDumper(File headerBlockFile) {
+ super(headerBlockFile);
+ }
+
+ @Override
+ public void visitFileFormat(SatS2RangeFileFormat fileFormat) {
+ println("File format");
+ println("===========");
+ println(fileFormat.toString());
+ println();
+ }
+}
+
diff --git a/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/dump/SatS2RangeFileDumper.java b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/dump/SatS2RangeFileDumper.java
new file mode 100644
index 0000000..307275a
--- /dev/null
+++ b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/dump/SatS2RangeFileDumper.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.tools.sats2.dump;
+
+import static com.android.storage.tools.block.dump.DumpUtils.binaryStringLength;
+import static com.android.storage.tools.block.dump.DumpUtils.hexStringLength;
+import static com.android.storage.tools.block.dump.DumpUtils.zeroPadBinary;
+import static com.android.storage.tools.block.dump.DumpUtils.zeroPadHex;
+
+import com.android.storage.tools.block.dump.SingleFileDumper;
+import com.android.telephony.sats2range.read.HeaderBlock;
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+import com.android.telephony.sats2range.read.SatS2RangeFileReader;
+import com.android.telephony.sats2range.read.SuffixTableBlock;
+import com.android.telephony.sats2range.read.SuffixTableExtraInfo;
+
+import java.io.File;
+
+/** A {@link SatS2RangeFileReader.SatS2RangeFileVisitor} that dumps information to a file. */
+public final class SatS2RangeFileDumper implements SatS2RangeFileReader.SatS2RangeFileVisitor {
+
+ private final File mOutputDir;
+
+ private int mMaxPrefix;
+
+ private int mMaxPrefixBinaryLength;
+
+ private int mMaxPrefixHexLength;
+
+ private SingleFileDumper mExtraInfoDumper;
+
+ public SatS2RangeFileDumper(File outputDir) {
+ mOutputDir = outputDir;
+ }
+
+ @Override
+ public void begin() throws VisitException {
+ mExtraInfoDumper = new SingleFileDumper(new File(mOutputDir, "suffixtable_extrainfo.txt"));
+ mExtraInfoDumper.begin();
+ }
+
+ @Override
+ public void visitSuffixTableExtraInfo(SuffixTableExtraInfo suffixTableExtraInfo) {
+ int prefix = suffixTableExtraInfo.getPrefix();
+ mExtraInfoDumper.println("prefix=" + zeroPadBinary(mMaxPrefixBinaryLength, prefix)
+ + "(" + zeroPadHex(mMaxPrefixHexLength, prefix) + ")"
+ + ", entryCount=" + suffixTableExtraInfo.getEntryCount());
+ }
+
+ @Override
+ public void visitHeaderBlock(HeaderBlock headerBlock) throws VisitException {
+ File headerFile = new File(mOutputDir, "header.txt");
+ headerBlock.visit(new HeaderBlockDumper(headerFile));
+ SatS2RangeFileFormat fileFormat = headerBlock.getFileFormat();
+ mMaxPrefix = fileFormat.getMaxPrefixValue();
+ mMaxPrefixBinaryLength = binaryStringLength(mMaxPrefix);
+ mMaxPrefixHexLength = hexStringLength(mMaxPrefix);
+ }
+
+ @Override
+ public void visitSuffixTableBlock(SuffixTableBlock suffixTableBlock)
+ throws VisitException {
+ suffixTableBlock.visit(new SuffixTableBlockDumper(mOutputDir, mMaxPrefix));
+ }
+
+ @Override
+ public void end() throws VisitException {
+ mExtraInfoDumper.end();
+ }
+}
+
diff --git a/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/dump/SuffixTableBlockDumper.java b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/dump/SuffixTableBlockDumper.java
new file mode 100644
index 0000000..a5d75b4
--- /dev/null
+++ b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/dump/SuffixTableBlockDumper.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.tools.sats2.dump;
+
+import static com.android.storage.tools.block.dump.DumpUtils.binaryStringLength;
+import static com.android.storage.tools.block.dump.DumpUtils.createPrintWriter;
+import static com.android.storage.tools.block.dump.DumpUtils.generateDumpFile;
+import static com.android.storage.tools.block.dump.DumpUtils.hexStringLength;
+import static com.android.storage.tools.block.dump.DumpUtils.zeroPadBinary;
+import static com.android.storage.tools.block.dump.DumpUtils.zeroPadHex;
+
+import com.android.telephony.sats2range.read.SuffixTableBlock;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/** A {@link SuffixTableBlock.SuffixTableBlockVisitor} that dumps information to a file. */
+public final class SuffixTableBlockDumper implements SuffixTableBlock.SuffixTableBlockVisitor {
+
+ private final File mOutputDir;
+
+ private final int mMaxPrefix;
+
+ public SuffixTableBlockDumper(File outputDir, int maxPrefix) {
+ mOutputDir = Objects.requireNonNull(outputDir);
+ mMaxPrefix = maxPrefix;
+ }
+
+ @Override
+ public void visit(SuffixTableBlock suffixTableBlock) throws VisitException {
+ int tablePrefix = suffixTableBlock.getPrefix();
+ int prefixHexLength = hexStringLength(tablePrefix);
+ int prefixBinaryLength = binaryStringLength(tablePrefix);
+ File suffixTableFile =
+ generateDumpFile(mOutputDir, "suffixtable_", tablePrefix, mMaxPrefix);
+ try (PrintWriter writer = createPrintWriter(suffixTableFile)) {
+ writer.println("Prefix value=" + zeroPadBinary(prefixBinaryLength, tablePrefix)
+ + " (" + zeroPadHex(prefixHexLength, tablePrefix) + ")");
+ int entryCount = suffixTableBlock.getEntryCount();
+ writer.println("Entry count=" + entryCount);
+ if (entryCount > 0) {
+ for (int i = 0; i < entryCount; i++) {
+ writer.println(
+ "[" + i + "]=" + suffixTableBlock.getEntryByIndex(i)
+ .getSuffixTableRange());
+ }
+ }
+ }
+ }
+}
+
diff --git a/utils/satellite/tools/src/test/java/com/android/telephony/tools/sats2/CreateSatS2FileTest.java b/utils/satellite/tools/src/test/java/com/android/telephony/tools/sats2/CreateSatS2FileTest.java
new file mode 100644
index 0000000..80c1807
--- /dev/null
+++ b/utils/satellite/tools/src/test/java/com/android/telephony/tools/sats2/CreateSatS2FileTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.tools.sats2;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+import com.android.telephony.sats2range.read.SatS2RangeFileReader;
+import com.android.telephony.sats2range.utils.TestUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+
+/** Tests for {@link CreateSatS2File} */
+public final class CreateSatS2FileTest {
+ private Path mTempDirPath;
+
+ @Before
+ public void setUp() throws IOException {
+ mTempDirPath = TestUtils.createTempDir(this.getClass());
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ if (mTempDirPath != null) {
+ TestUtils.deleteDirectory(mTempDirPath);
+ }
+ }
+
+ @Test
+ public void testCreateSatS2FileWithValidInput_AllowedList() throws Exception {
+ testCreateSatS2FileWithValidInput(true);
+ }
+
+ @Test
+ public void testCreateSatS2FileWithValidInput_DisallowedList() throws Exception {
+ testCreateSatS2FileWithValidInput(false);
+ }
+
+ @Test
+ public void testCreateSatS2FileWithInvalidInput() throws Exception {
+ int s2Level = 12;
+ boolean isAllowedList = true;
+ Path inputDirPath = mTempDirPath.resolve("input");
+ Files.createDirectory(inputDirPath);
+ Path inputFilePath = inputDirPath.resolve("s2cells.txt");
+
+ Path outputDirPath = mTempDirPath.resolve("output");
+ Files.createDirectory(outputDirPath);
+ Path outputFilePath = outputDirPath.resolve("sats2.dat");
+
+ // Create test input S2 cell file
+ SatS2RangeFileFormat fileFormat = FileFormats.getFileFormatForLevel(s2Level, isAllowedList);
+ TestUtils.createInvalidTestS2CellFile(inputFilePath.toFile(), fileFormat);
+
+ // Commandline input arguments
+ String[] args = {
+ "--input-file", inputFilePath.toAbsolutePath().toString(),
+ "--s2-level", String.valueOf(s2Level),
+ "--is-allowed-list", isAllowedList ? "true" : "false",
+ "--output-file", outputFilePath.toAbsolutePath().toString()
+ };
+
+ // Execute the tool CreateSatS2File and expect exception
+ try {
+ CreateSatS2File.main(args);
+ } catch (Exception ex) {
+ // Expected exception
+ return;
+ }
+ fail("Exception should have been caught");
+ }
+
+ private void testCreateSatS2FileWithValidInput(boolean isAllowedList) throws Exception {
+ int s2Level = 12;
+ Path inputDirPath = mTempDirPath.resolve("input");
+ Files.createDirectory(inputDirPath);
+ Path inputFilePath = inputDirPath.resolve("s2cells.txt");
+
+ Path outputDirPath = mTempDirPath.resolve("output");
+ Files.createDirectory(outputDirPath);
+ Path outputFilePath = outputDirPath.resolve("sats2.dat");
+
+ /*
+ * Create test input S2 cell file with the following ranges:
+ * 1) [(prefix=0b100_11111111, suffix=1000), (prefix=0b100_11111111, suffix=2000))
+ * 2) [(prefix=0b100_11111111, suffix=2001), (prefix=0b100_11111111, suffix=3000))
+ * 3) [(prefix=0b101_11111111, suffix=1000), (prefix=0b101_11111111, suffix=2001))
+ */
+ SatS2RangeFileFormat fileFormat = FileFormats.getFileFormatForLevel(s2Level, isAllowedList);
+ TestUtils.createValidTestS2CellFile(inputFilePath.toFile(), fileFormat);
+
+ // Commandline input arguments
+ String[] args = {
+ "--input-file", inputFilePath.toAbsolutePath().toString(),
+ "--s2-level", String.valueOf(s2Level),
+ "--is-allowed-list", isAllowedList ? "true" : "false",
+ "--output-file", outputFilePath.toAbsolutePath().toString()
+ };
+
+ // Execute the tool CreateSatS2File and expect successful result
+ try {
+ CreateSatS2File.main(args);
+ } catch (Exception ex) {
+ fail("Unexpected exception when executing the tool ex=" + ex);
+ }
+
+ // Validate the output block file
+ try {
+ SatS2RangeFileReader satS2RangeFileReader =
+ SatS2RangeFileReader.open(outputFilePath.toFile());
+ if (isAllowedList != satS2RangeFileReader.isAllowedList()) {
+ fail("isAllowedList="
+ + satS2RangeFileReader.isAllowedList() + " does not match the input "
+ + "argument=" + isAllowedList);
+ }
+
+ // Verify an edge cell (prefix=0b100_11111111, suffix=100)
+ long s2CellId = fileFormat.createCellId(0b100_11111111, 100);
+ assertNull(satS2RangeFileReader.findEntryByCellId(s2CellId));
+
+ // Verify a middle cell (prefix=0b100_11111111, suffix=2000)
+ s2CellId = fileFormat.createCellId(0b100_11111111, 2000);
+ assertNull(satS2RangeFileReader.findEntryByCellId(s2CellId));
+
+ // Verify an edge cell (prefix=0b100_11111111, suffix=4000)
+ s2CellId = fileFormat.createCellId(0b100_11111111, 4000);
+ assertNull(satS2RangeFileReader.findEntryByCellId(s2CellId));
+
+ // Verify an edge cell (prefix=0b101_11111111, suffix=500)
+ s2CellId = fileFormat.createCellId(0b101_11111111, 500);
+ assertNull(satS2RangeFileReader.findEntryByCellId(s2CellId));
+
+ // Verify an edge cell (prefix=0b101_11111111, suffix=2001)
+ s2CellId = fileFormat.createCellId(0b101_11111111, 2500);
+ assertNull(satS2RangeFileReader.findEntryByCellId(s2CellId));
+
+ // Verify an edge cell (prefix=0b101_11111111, suffix=2500)
+ s2CellId = fileFormat.createCellId(0b101_11111111, 2500);
+ assertNull(satS2RangeFileReader.findEntryByCellId(s2CellId));
+ } catch (Exception ex) {
+ fail("Unexpected exception when validating the output ex=" + ex);
+ }
+ }
+}