Merge "Support emergency SMS domain selection in NR network" into main
diff --git a/Android.bp b/Android.bp
index 122015e..a943299 100644
--- a/Android.bp
+++ b/Android.bp
@@ -41,10 +41,14 @@
"com.android.phone.common-lib",
"guava",
"PlatformProperties",
+ "modules-utils-fastxmlserializer",
"modules-utils-os",
"nist-sip",
"service-entitlement",
"telephony_flags_core_java_lib",
+ "android.permission.flags-aconfig-java",
+ "satellite-s2storage-ro",
+ "s2-geometry-library-java",
],
srcs: [
@@ -88,7 +92,7 @@
libs: [
"telephony-common",
"service-entitlement"
- ],
+ ],
}
platform_compat_config {
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index eef01fa..b2548f1 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,8 @@
<!-- 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 bind the domain selection service. -->
+ <uses-permission android:name="android.permission.BIND_DOMAIN_SELECTION_SERVICE" />
<application android:name="PhoneApp"
android:persistent="true"
@@ -619,17 +620,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 +627,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 2c7aed3..53b9401 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,3 +1,5 @@
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
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/eccdata.txt b/ecc/input/eccdata.txt
index fe11383..c4edc9e 100644
--- a/ecc/input/eccdata.txt
+++ b/ecc/input/eccdata.txt
@@ -2359,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/eccdata b/ecc/output/eccdata
index 55d8151..482ed79 100644
--- a/ecc/output/eccdata
+++ b/ecc/output/eccdata
Binary files differ
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 6675ae7..992f1b2 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -596,7 +596,7 @@
<string name="failedToImportSingleContactMsg" msgid="228095510489830266">"Uvoz kontakta nije uspio"</string>
<string name="hac_mode_title" msgid="4127986689621125468">"Slušni aparat"</string>
<string name="hac_mode_summary" msgid="7774989500136009881">"Uključite kompatibilnost za slušni aparat"</string>
- <string name="rtt_mode_title" msgid="3075948111362818043">"Pozivanje sa slanjem SMS-ova u stvarnom vremenu (RTT)"</string>
+ <string name="rtt_mode_title" msgid="3075948111362818043">"Poziv sa SMS-ovima u stvarnom vremenu (RTT)"</string>
<string name="rtt_mode_summary" msgid="8631541375609989562">"Dozvolite razmjenu poruka tokom glasovnog poziva"</string>
<string name="rtt_mode_more_information" msgid="587500128658756318">"RTT pomaže pozivaocima koji su gluhi, imaju probleme sa sluhom ili govorom te onima kojima treba više od samog glasa.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>Saznajte više</a>\n <br><br> - RTT pozivi se pohranjuju kao transkripti poruka\n <br> - RTT nije dostupan za video pozive"</string>
<string name="no_rtt_when_roaming" msgid="5268008247378355389">"Napomena: RTT nije dostupan u romingu"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index a9dd327..801eb12 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -596,9 +596,9 @@
<string name="failedToImportSingleContactMsg" msgid="228095510489830266">"Kontaktpersonen kunne ikke importeres"</string>
<string name="hac_mode_title" msgid="4127986689621125468">"Høreapparater"</string>
<string name="hac_mode_summary" msgid="7774989500136009881">"Slå høreapparatskompatibilitet til"</string>
- <string name="rtt_mode_title" msgid="3075948111362818043">"Opkald via sms i realtid"</string>
+ <string name="rtt_mode_title" msgid="3075948111362818043">"Opkald via beskeder i realtid"</string>
<string name="rtt_mode_summary" msgid="8631541375609989562">"Tillad afsendelse af sms-beskeder i et taleopkald"</string>
- <string name="rtt_mode_more_information" msgid="587500128658756318">"Sms i realtid hjælper personer, som er døve, hørehæmmede, talehandicappede, eller som har brug for mere end bare tale, med at foretage opkald.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>Få flere oplysninger</a>\n <br><br> – Opkald via sms i realtid gemmes som en beskedtransskription\n <br> – Sms i realtid er ikke tilgængeligt til videoopkald"</string>
+ <string name="rtt_mode_more_information" msgid="587500128658756318">"Beskeder i realtid hjælper personer, som er døve, hørehæmmede, talehandicappede, eller som har brug for mere end bare tale, med at foretage opkald.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>Få flere oplysninger</a>\n <br><br> – Opkald via beskeder i realtid gemmes som en beskedtransskription\n <br> – Beskeder i realtid er ikke tilgængeligt til videoopkald"</string>
<string name="no_rtt_when_roaming" msgid="5268008247378355389">"Bemærk! RTT er ikke tilgængeligt under roaming"</string>
<string-array name="tty_mode_entries">
<item msgid="3238070884803849303">"TTY fra"</item>
@@ -862,7 +862,7 @@
<string name="radioInfo_phone_offhook" msgid="7564601639749936170">"Opkald i gang"</string>
<string name="radioInfo_data_disconnected" msgid="8085447971880814541">"Afbrudt"</string>
<string name="radioInfo_data_connecting" msgid="925092271092152472">"Forbindelsen oprettes"</string>
- <string name="radioInfo_data_connected" msgid="7637335645634239508">"Tilsluttet"</string>
+ <string name="radioInfo_data_connected" msgid="7637335645634239508">"Forbundet"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Suspenderet"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Ukendt"</string>
<string name="radioInfo_imei_primary" msgid="5948747378637224400">"Primær"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 39b16ce..7378480 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -598,7 +598,7 @@
<string name="hac_mode_summary" msgid="7774989500136009881">"Hörhilfekompatibilität aktivieren"</string>
<string name="rtt_mode_title" msgid="3075948111362818043">"RTT-Anruf"</string>
<string name="rtt_mode_summary" msgid="8631541375609989562">"Nachrichten in Sprachanrufen erlauben"</string>
- <string name="rtt_mode_more_information" msgid="587500128658756318">"Echtzeittext (Real-Time Text, RTT) dient als Unterstützung für Anrufer, die gehörlos, schwerhörig oder sprachgeschädigt sind oder zusätzlich zur gesprochenen Sprache weitere Informationen benötigen.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>Weitere Informationen</a>\n <br><br> – RTT-Anrufe werden als Nachrichtentranskripte gespeichert\n <br> – RTT ist nicht für Videoanrufe verfügbar"</string>
+ <string name="rtt_mode_more_information" msgid="587500128658756318">"Echtzeittext (Real-Time Text, RTT) dient als Unterstützung für Anrufer mit Hör- oder Sprachbehinderung oder Anrufer, die zusätzlich zur gesprochenen Sprache weitere Informationen benötigen.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>Weitere Informationen</a>\n <br><br> – RTT-Anrufe werden als Nachrichtentranskripte gespeichert\n <br> – RTT ist nicht für Videoanrufe verfügbar"</string>
<string name="no_rtt_when_roaming" msgid="5268008247378355389">"Hinweis: RTT ist während des Roamings nicht verfügbar"</string>
<string-array name="tty_mode_entries">
<item msgid="3238070884803849303">"TTY aus"</item>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index c52cb86..76ae668 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -661,7 +661,7 @@
<string name="description_dial_button" msgid="8614631902795087259">"شماره گیری"</string>
<string name="description_dialpad_button" msgid="7395114120463883623">"نمایش صفحه شماره گیری"</string>
<string name="pane_title_emergency_dialpad" msgid="3627372514638694401">"صفحه شمارهگیری اضطراری"</string>
- <string name="voicemail_visual_voicemail_switch_title" msgid="6610414098912832120">"پست صوتی دیداری"</string>
+ <string name="voicemail_visual_voicemail_switch_title" msgid="6610414098912832120">"پست صوتی تصویری"</string>
<string name="voicemail_set_pin_dialog_title" msgid="7005128605986960003">"تنظیم پین"</string>
<string name="voicemail_change_pin_dialog_title" msgid="4633077715231764435">"تغییر پین"</string>
<string name="preference_category_ringtone" msgid="8787281191375434976">"آهنگ زنگ و لرزش"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 85a5a93..8b3f378 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -595,7 +595,7 @@
<string name="singleContactImportedMsg" msgid="3619804066300998934">"Contact importé"</string>
<string name="failedToImportSingleContactMsg" msgid="228095510489830266">"Échec de l\'importation du contact."</string>
<string name="hac_mode_title" msgid="4127986689621125468">"Assistance auditive"</string>
- <string name="hac_mode_summary" msgid="7774989500136009881">"Activer la compatibilité avec les prothèses auditives"</string>
+ <string name="hac_mode_summary" msgid="7774989500136009881">"Activer la compatibilité avec les appareils auditifs"</string>
<string name="rtt_mode_title" msgid="3075948111362818043">"Appel texte en temps réel"</string>
<string name="rtt_mode_summary" msgid="8631541375609989562">"Autoriser l\'échange de messages pendant les appels vocaux"</string>
<string name="rtt_mode_more_information" msgid="587500128658756318">"La fonctionnalité de texte en temps réel vient en aide aux personnes sourdes, malentendantes, qui ont un trouble de la parole, ou qui ont besoin d\'une transcription en plus de la voix.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>En savoir plus</a>\n <br><br> - Les appels texte en temps réel sont enregistrés sous forme transcrite\n <br> - Le mode texte en temps réel n\'est pas disponible pour les appels vidéo"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 5d03fe8..febd572 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -165,7 +165,7 @@
<string name="voicemail_default" msgid="6427575113775462077">"आपको सेवा देने वाली कंपनी"</string>
<string name="vm_change_pin_old_pin" msgid="7154951790929009241">"पुराना पिन"</string>
<string name="vm_change_pin_new_pin" msgid="2656200418481288069">"नया पिन"</string>
- <string name="vm_change_pin_progress_message" msgid="626015184502739044">"कृपया प्रतीक्षा करें."</string>
+ <string name="vm_change_pin_progress_message" msgid="626015184502739044">"कृपया इंतज़ार करें."</string>
<string name="vm_change_pin_error_too_short" msgid="1789139338449945483">"नया पिन बहुत छोटा है."</string>
<string name="vm_change_pin_error_too_long" msgid="3634907034310018954">"नया पिन बहुत बड़ा है."</string>
<string name="vm_change_pin_error_too_weak" msgid="8581892952627885719">"नया पिन बहुत कमज़ोर है. किसी सशक्त पासवर्ड में निरंतर क्रम या अंकों का दोहराव नहीं होना चाहिए."</string>
@@ -314,7 +314,7 @@
<string name="video_calling_settings_title" msgid="342829454913266078">"वाहक वीडियो कॉलिंग"</string>
<string name="gsm_umts_options" msgid="4968446771519376808">"GSM/UMTS विकल्प"</string>
<string name="cdma_options" msgid="3669592472226145665">"CDMA विकल्प"</string>
- <string name="throttle_data_usage" msgid="1944145350660420711">"डेटा उपयोग"</string>
+ <string name="throttle_data_usage" msgid="1944145350660420711">"डेटा खर्च"</string>
<string name="throttle_current_usage" msgid="7483859109708658613">"वर्तमान अवधि में उपयोग किया गया डेटा"</string>
<string name="throttle_time_frame" msgid="1813452485948918791">"डेटा उपयोग अवधि"</string>
<string name="throttle_rate" msgid="7641913901133634905">"डेटा दर नीति"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 25e1414..9484f54 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -289,7 +289,7 @@
<string name="roaming_enabled_message" msgid="9022249120750897">"Biaya roaming dapat berlaku. Ketuk untuk mengubah."</string>
<string name="roaming_notification_title" msgid="3590348480688047320">"Koneksi data seluler terputus"</string>
<string name="roaming_on_notification_title" msgid="7451473196411559173">"Roaming data aktif"</string>
- <string name="roaming_warning" msgid="7855681468067171971">"Anda dapat dikenakan biaya yang cukup besar."</string>
+ <string name="roaming_warning" msgid="7855681468067171971">"Anda dapat dikenai biaya yang cukup besar."</string>
<string name="roaming_check_price_warning" msgid="8212484083990570215">"Hubungi penyedia jaringan untuk mengetahui harganya."</string>
<string name="roaming_alert_title" msgid="5689615818220960940">"Izinkan roaming data?"</string>
<string name="limited_sim_function_notification_title" msgid="612715399099846281">"Fungsi SIM terbatas"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index fe39ede..011b30b 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -466,7 +466,7 @@
<string name="get_pin2" msgid="4221654606863196332">"PIN2 ಅನ್ನು ಟೈಪ್ ಮಾಡಿ"</string>
<string name="name" msgid="1347432469852527784">"ಹೆಸರು"</string>
<string name="number" msgid="1564053487748491000">"ಸಂಖ್ಯೆ"</string>
- <string name="save" msgid="983805790346099749">"ಉಳಿಸು"</string>
+ <string name="save" msgid="983805790346099749">"ಸೇವ್ ಮಾಡಿ"</string>
<string name="add_fdn_contact" msgid="1169713422306640887">"ಸ್ಥಿರ ಡಯಲಿಂಗ್ ಸಂಖ್ಯೆಯನ್ನು ಸೇರಿಸಿ"</string>
<string name="adding_fdn_contact" msgid="3112531600824361259">"ಸ್ಥಿರ ಡಯಲಿಂಗ್ ಸಂಖ್ಯೆಯನ್ನು ಸೇರಿಸಲಾಗುತ್ತಿದೆ…"</string>
<string name="fdn_contact_added" msgid="2840016151693394596">"ಸ್ಥಿರ ಡಯಲಿಂಗ್ ಸಂಖ್ಯೆಯನ್ನು ಸೇರಿಸಲಾಗಿದೆ."</string>
@@ -598,7 +598,7 @@
<string name="hac_mode_summary" msgid="7774989500136009881">"ಶ್ರವಣ ಸಾಧನ ಹೊಂದಾಣಿಕೆಯನ್ನು ಆನ್ ಮಾಡಿ"</string>
<string name="rtt_mode_title" msgid="3075948111362818043">"ನೈಜ-ಸಮಯ ಪಠ್ಯ (RTT) ಕರೆ"</string>
<string name="rtt_mode_summary" msgid="8631541375609989562">"ಧ್ವನಿ ಕರೆಯ ಒಳಗೆ ಸಂದೇಶ ಕಳುಹಿಸುವಿಕೆಗೆ ಅನುಮತಿಸಿ"</string>
- <string name="rtt_mode_more_information" msgid="587500128658756318">"ಕಿವುಡರು, ಆಲಿಸುವಿಕೆಯ ದೋಷಗಳನ್ನು ಹೊಂದಿದವರು, ಮಾತನಾಡುವಿಕೆಯಲ್ಲಿ ದೋಷಗಳನ್ನು ಹೊಂದಿದವರು ಅಥವಾ ಧ್ವನಿ ಮೀರಿ ಬೇರೆ ರೀತಿಯಲ್ಲಿ ಕರೆ ಮಾಡಲು ಕಠಿಣರಾಗಿರುವವರಿಗೆ RTT ಮೋಡ್ ಸಹಾಯ ಮಾಡುತ್ತದೆ.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ</a>\n <br><br> - RTT ಕರೆಗಳನ್ನು ಸಂದೇಶ ಪ್ರತಿಲಿಪಿಗಳಂತೆ ಉಳಿಸಲಾಗಿದೆ \n <br> - ವೀಡಿಯೊ ಕರೆಗಳಿಗೆ RTT ಲಭ್ಯವಿಲ್ಲ"</string>
+ <string name="rtt_mode_more_information" msgid="587500128658756318">"ಕಿವುಡರು, ಆಲಿಸುವಿಕೆಯ ದೋಷಗಳನ್ನು ಹೊಂದಿದವರು, ಮಾತನಾಡುವಿಕೆಯಲ್ಲಿ ದೋಷಗಳನ್ನು ಹೊಂದಿದವರು ಅಥವಾ ಧ್ವನಿ ಮೀರಿ ಬೇರೆ ರೀತಿಯಲ್ಲಿ ಕರೆ ಮಾಡಲು ಕಠಿಣರಾಗಿರುವವರಿಗೆ RTT ಮೋಡ್ ಸಹಾಯ ಮಾಡುತ್ತದೆ.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ</a>\n <br><br> - RTT ಕರೆಗಳನ್ನು ಸಂದೇಶ ಪ್ರತಿಲಿಪಿಗಳಂತೆ ಸೇವ್ ಮಾಡಲಾಗಿದೆ \n <br> - ವೀಡಿಯೊ ಕರೆಗಳಿಗೆ RTT ಲಭ್ಯವಿಲ್ಲ"</string>
<string name="no_rtt_when_roaming" msgid="5268008247378355389">"ಗಮನಿಸಿ: ರೋಮಿಂಗ್ನಲ್ಲಿ RTT ಲಭ್ಯವಿಲ್ಲ"</string>
<string-array name="tty_mode_entries">
<item msgid="3238070884803849303">"TTY ಆಫ್"</item>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 7bd3d32..3fca1ed 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -385,9 +385,9 @@
<string name="enable_disable_lafs" msgid="7448060358300805661">"Жергиликтүү аэропорттун учуу тартиби"</string>
<string name="lafs_enable" msgid="3125783406052655690">"Жергиликтүү аэропорттун учуу тартиби иштетилген"</string>
<string name="lafs_disable" msgid="7326815066813851447">"Жергиликтүү аэропорттун учуу тартиби өчүрүлгөн"</string>
- <string name="enable_disable_restaurants" msgid="3873247081569423019">"Ресторандар"</string>
- <string name="restaurants_enable" msgid="5810452674239139572">"Ресторандар иштетилген"</string>
- <string name="restaurants_disable" msgid="2733507854548413505">"Ресторандар өчүрүлгөн"</string>
+ <string name="enable_disable_restaurants" msgid="3873247081569423019">"Тамактануучу жайлар"</string>
+ <string name="restaurants_enable" msgid="5810452674239139572">"Тамактануучу жайлар иштетилген"</string>
+ <string name="restaurants_disable" msgid="2733507854548413505">"Тамактануучу жайлар өчүрүлгөн"</string>
<string name="enable_disable_lodgings" msgid="7849168585821435109">"Турак жайлар"</string>
<string name="lodgings_enable" msgid="2020598411398609514">"Турак жайлар иштетилген"</string>
<string name="lodgings_disable" msgid="5145649659459722661">"Турак жайлар өчүрүлгөн"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index f8bef4a..90165b8 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -908,7 +908,7 @@
<string name="radio_info_smsc_update_label" msgid="5141996256097115753">"Atjaunināt"</string>
<string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"Atsvaidzināt"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"Pārslēgt DNS pārbaudi"</string>
- <string name="oem_radio_info_label" msgid="2914167475119997456">"OEM raksturīga informācija/iestatījumi"</string>
+ <string name="oem_radio_info_label" msgid="2914167475119997456">"OAR raksturīga informācija/iestatījumi"</string>
<string name="radio_info_endc_available" msgid="2983767110681230019">"EN-DC pieejamība (NSA):"</string>
<string name="radio_info_dcnr_restricted" msgid="7147511536420148173">"DCNR ierobežojums (NSA):"</string>
<string name="radio_info_nr_available" msgid="3383388088451237182">"NR pieejamība (NSA):"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 0ab1b47..2c68ef1 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -318,7 +318,7 @@
<string name="throttle_current_usage" msgid="7483859109708658613">"Податоци кои се користат во тековниот период"</string>
<string name="throttle_time_frame" msgid="1813452485948918791">"Период на потрошен интернет"</string>
<string name="throttle_rate" msgid="7641913901133634905">"Политика на стапка на податоци"</string>
- <string name="throttle_help" msgid="2624535757028809735">"Дознај повеќе"</string>
+ <string name="throttle_help" msgid="2624535757028809735">"Дознајте повеќе"</string>
<string name="throttle_status_subtext" msgid="1110276415078236687">"<xliff:g id="USED_0">%1$s</xliff:g> (<xliff:g id="USED_1">%2$d</xliff:g> ٪) од максимум <xliff:g id="USED_2">%3$s</xliff:g> за периодот\nСледниот период започнува за <xliff:g id="USED_3">%4$d</xliff:g> дена (<xliff:g id="USED_4">%5$s</xliff:g>)"</string>
<string name="throttle_data_usage_subtext" msgid="3185429653996709840">"<xliff:g id="USED_0">%1$s</xliff:g> (<xliff:g id="USED_1">%2$d</xliff:g>٪) од <xliff:g id="USED_2">%3$s</xliff:g> максимален период"</string>
<string name="throttle_data_rate_reduced_subtext" msgid="8369839346277847725">"<xliff:g id="USED_0">%1$s</xliff:g> максимум е надминат\nСтапката на податоци е намалена на <xliff:g id="USED_1">%2$d</xliff:g> Kb/s"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 8576e1f..b4c7573 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -675,7 +675,7 @@
<string name="sim_description_default" msgid="7474671114363724971">"SIM ကတ်၊ အပေါက်: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
<string name="accessibility_settings_activity_title" msgid="7883415189273700298">"အများသုံးနိုင်မှု"</string>
<string name="status_hint_label_incoming_wifi_call" msgid="2606052595898044071">"အောက်ပါမှ Wi-Fi ခေါ်ခြင်း"</string>
- <string name="status_hint_label_wifi_call" msgid="942993035689809853">"ဝိုင်ဖိုင်ခေါ်ဆိုမှု"</string>
+ <string name="status_hint_label_wifi_call" msgid="942993035689809853">"Wi-Fi ခေါ်ဆိုမှု"</string>
<string name="message_decode_error" msgid="1061856591500290887">"စာကို ကုဒ်ဖွင့်နေစဉ် အမှားရှိခဲ့သည်။"</string>
<string name="callFailed_cdma_activation" msgid="5392057031552253550">"SIM ကဒ်သည် သင့် ဖုန်းဝန်ဆောင်မှုအား အသက်သွင်းခဲ့ပြီး သင့်ဖုန်း၏ ကွန်ယက်ပြင်ပဒေတာသုံးနိုင်စွမ်းအား ပြင်ဆင်မွမ်းမံပြီးဖြစ်၏။"</string>
<string name="callFailed_cdma_call_limit" msgid="1074219746093031412">"လက်ရှိခေါ်ဆိုမှုများ အလွန်များနေပါသည်။ ခေါ်ဆိုမှုအသစ်တစ်ခု မပြုလုပ်ခင် လက်ရှိဖုန်းခေါ်ဆိုမှုများကို အဆုံးသတ် (သို့) ပေါင်း လိုက်ပါ။"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 5eea570..fd8e728 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -19,7 +19,7 @@
<string name="phoneAppLabel" product="tablet" msgid="1916019789885839910">"ମୋବାଇଲ୍ ଡାଟା"</string>
<string name="phoneAppLabel" product="default" msgid="130465039375347763">"ଫୋନ୍ ସେବା"</string>
<string name="emergencyDialerIconLabel" msgid="8668005772339436680">"ଜରୁରୀକାଳିନ ଡାଏଲର୍"</string>
- <string name="phoneIconLabel" msgid="3015941229249651419">"ଫୋନ୍"</string>
+ <string name="phoneIconLabel" msgid="3015941229249651419">"ଫୋନ"</string>
<string name="fdnListLabel" msgid="4119121875004244097">"FDN ତାଲିକା"</string>
<string name="unknown" msgid="8279698889921830815">"ଅଜଣା"</string>
<string name="private_num" msgid="4487990167889159992">"ବ୍ୟକ୍ତିଗତ ନମ୍ବର୍"</string>
@@ -674,8 +674,8 @@
<string name="sim_description_emergency_calls" msgid="5146872803938897296">"କେବଳ ଜରୁରିକାଳୀନ କଲ୍ ପାଇଁ"</string>
<string name="sim_description_default" msgid="7474671114363724971">"SIM କାର୍ଡ, ସ୍ଲଟ୍: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
<string name="accessibility_settings_activity_title" msgid="7883415189273700298">"ଆକ୍ସେସିବିଲିଟୀ"</string>
- <string name="status_hint_label_incoming_wifi_call" msgid="2606052595898044071">"ଠାରୁ ୱାଇ-ଫାଇ କଲ୍ କରନ୍ତୁ"</string>
- <string name="status_hint_label_wifi_call" msgid="942993035689809853">"ୱାଇ-ଫାଇ କଲ୍"</string>
+ <string name="status_hint_label_incoming_wifi_call" msgid="2606052595898044071">"ୱାଇ-ଫାଇ କଲ କରିଛନ୍ତି"</string>
+ <string name="status_hint_label_wifi_call" msgid="942993035689809853">"ୱାଇ-ଫାଇ କଲ"</string>
<string name="message_decode_error" msgid="1061856591500290887">"ମେସେଜ୍କୁ ଡିକୋଡ୍ କରିବା ବେଳେ ଗୋଟିଏ ତ୍ରୁଟି ଦେଖାଦେଲା।"</string>
<string name="callFailed_cdma_activation" msgid="5392057031552253550">"ଗୋଟିଏ SIM କାର୍ଡ ଆପଣଙ୍କର ସେବାକୁ କାର୍ଯ୍ୟକ୍ଷମ କରିଛି ଏବଂ ଆପଣଙ୍କ ଫୋନ୍ର ରୋମିଙ୍ଗ କ୍ଷମତାକୁ ଅପଡେଟ୍ କରିଛି।"</string>
<string name="callFailed_cdma_call_limit" msgid="1074219746093031412">"ଏଠାରେ ଅନେକ ସକ୍ରିୟ କଲ୍ ଅଛି। ଗୋଟିଏ ନୂଆ କଲ୍କୁ ସ୍ଥାପନ କରିବା ପୂର୍ବରୁ ଦୟାକରି ବିଦ୍ୟମାନ ଥିବା କଲ୍କୁ ସମାପ୍ତ କିମ୍ବା ମର୍ଜ କରନ୍ତୁ।"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 1f86b13..975cdc7 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -168,7 +168,7 @@
<string name="vm_change_pin_progress_message" msgid="626015184502739044">"Počakajte."</string>
<string name="vm_change_pin_error_too_short" msgid="1789139338449945483">"Nova koda PIN je prekratka."</string>
<string name="vm_change_pin_error_too_long" msgid="3634907034310018954">"Nova koda PIN je predolga."</string>
- <string name="vm_change_pin_error_too_weak" msgid="8581892952627885719">"Nova koda PIN je prešibka. Zapleteno geslo ne sme vsebovati zaporednih ali ponavljajočih se števk."</string>
+ <string name="vm_change_pin_error_too_weak" msgid="8581892952627885719">"Nova koda PIN je prešibka. Močno geslo ne sme vsebovati zaporednih ali ponavljajočih se števk."</string>
<string name="vm_change_pin_error_mismatch" msgid="5364847280026257331">"Stara koda PIN se ne ujema."</string>
<string name="vm_change_pin_error_invalid" msgid="5230002671175580674">"Nova koda PIN vsebuje neveljavne znake."</string>
<string name="vm_change_pin_error_system_error" msgid="9116483527909681791">"Ni mogoče spremeniti kode PIN"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 2cbcafa..9e1f6fe 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -45,8 +45,8 @@
<string name="pause_prompt_no" msgid="2145264674774138579">"Hapana"</string>
<string name="wild_prompt_str" msgid="5858910969703305375">"Badilisha kibambo egemezi na"</string>
<string name="no_vm_number" msgid="6623853880546176930">"Nambari ya sauti inayokosekana"</string>
- <string name="no_vm_number_msg" msgid="5165161462411372504">"Hakuna nambari ya ujumbe wa sauti iliyohifadhiwa katika SIM kadi."</string>
- <string name="add_vm_number_str" msgid="7368168964435881637">"Ongeza nambari"</string>
+ <string name="no_vm_number_msg" msgid="5165161462411372504">"Hakuna namba ya ujumbe wa sauti iliyohifadhiwa katika SIM kadi."</string>
+ <string name="add_vm_number_str" msgid="7368168964435881637">"Ongeza namba"</string>
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Mipangilio ya ujumbe wa sauti inaweza kubadilishwa na Mtumiaji wa Msingi Pekee."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"Kadi yako ya simu imefunguliwa. Simu yangu inafungua…."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN ya kufungua mtandao wa SIM"</string>
@@ -82,7 +82,7 @@
<string name="voicemail_abbreviated" msgid="7746778673131551185">"VM:"</string>
<string name="make_and_receive_calls" msgid="4868913166494621109">"Kupiga na kupokea simu"</string>
<string name="smart_forwarding_settings_menu" msgid="8850429887958938540">"Usambazaji Mahiri"</string>
- <string name="smart_forwarding_settings_menu_summary" msgid="5096947726032885325">"Ikiwa nambari moja haiwezi kufikiwa, sambaza simu kwa nambari nyingine kila wakati"</string>
+ <string name="smart_forwarding_settings_menu_summary" msgid="5096947726032885325">"Ikiwa namba moja haiwezi kufikiwa, sambaza simu kwa namba nyingine kila wakati"</string>
<string name="voicemail_notifications_preference_title" msgid="7829238858063382977">"Arifa"</string>
<string name="cell_broadcast_settings" msgid="8135324242541809924">"Matangazo ya dharura"</string>
<string name="call_settings" msgid="3677282690157603818">"Mipangilio ya simu"</string>
@@ -96,7 +96,7 @@
<string name="sum_loading_settings" msgid="434063780286688775">"Mipangilio inapakia..."</string>
<string name="sum_hide_caller_id" msgid="131100328602371933">"Nambari imefichwa kwa simu unayopiga"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"Namba inaonekana kwa simu zinazopigwa"</string>
- <string name="sum_default_caller_id" msgid="1767070797135682959">"Tumia mipangilio ya mtoa huduma chaguomsingi kuonyesha nambari kwa simu unazopiga"</string>
+ <string name="sum_default_caller_id" msgid="1767070797135682959">"Tumia mipangilio ya mtoa huduma chaguomsingi kuonyesha namba kwa simu unazopiga"</string>
<string name="labelCW" msgid="8449327023861428622">"Simu inayosubiri kupokewa"</string>
<string name="sum_cw_enabled" msgid="3977308526187139996">"Wakati ninapokea simu, niarifu kuhusu simu zingine zinazoingia"</string>
<string name="sum_cw_disabled" msgid="3658094589461768637">"Wakati ninapokea simu, niarifu kuhusu simu zingine zinazoingia"</string>
@@ -104,7 +104,7 @@
<string name="call_forwarding_settings_with_label" msgid="2345432813399564272">"Mipangilio ya kusambaza simu (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="labelCF" msgid="3578719437928476078">"Kusambaza simu"</string>
<string name="labelCFU" msgid="8870170873036279706">"Sambaza kila wakati"</string>
- <string name="messageCFU" msgid="1361806450979589744">"Kila wakati tumia nambari hii"</string>
+ <string name="messageCFU" msgid="1361806450979589744">"Kila wakati tumia namba hii"</string>
<string name="sum_cfu_enabled_indicator" msgid="9030139213402432776">"Inasambaza simu zote"</string>
<string name="sum_cfu_enabled" msgid="5806923046528144526">"Inasambaza simu zote kwa <xliff:g id="PHONENUMBER">{0}</xliff:g>"</string>
<string name="sum_cfu_enabled_no_number" msgid="7287752761743377930">"Nambari haipatikani"</string>
@@ -145,7 +145,7 @@
<string name="stk_cc_ss_to_ussd_error" msgid="8330749347425752192">"Imebadilisha ombi la SS kuwa ombi la USSD"</string>
<string name="stk_cc_ss_to_ss_error" msgid="8297155544652134278">"Imebadilishwa kuwa ombi jipya la SS"</string>
<string name="stk_cc_ss_to_dial_video_error" msgid="4255261231466032505">"Imebadilisha ombi la SS kuwa simu ya video"</string>
- <string name="fdn_check_failure" msgid="1833769746374185247">"Mipangilio ya programu ya simu yako ya nambari za simu zilizobainishwa pekee imewashwa. Kutokana na hayo, baadhi ya vipengele vya kupiga simu havifanyi kazi."</string>
+ <string name="fdn_check_failure" msgid="1833769746374185247">"Mipangilio ya programu ya simu yako ya namba za simu zilizobainishwa pekee imewashwa. Kutokana na hayo, baadhi ya vipengele vya kupiga simu havifanyi kazi."</string>
<string name="radio_off_error" msgid="8321564164914232181">"Washa redio kabla ya kutazama mipangilio hii."</string>
<string name="close_dialog" msgid="1074977476136119408">"Sawa"</string>
<string name="enable" msgid="2636552299455477603">"Washa"</string>
@@ -153,13 +153,13 @@
<string name="change_num" msgid="6982164494063109334">"Sasisha"</string>
<string-array name="clir_display_values">
<item msgid="8477364191403806960">"Chaguomsingi la mtandao"</item>
- <item msgid="6813323051965618926">"Ficha nambari"</item>
- <item msgid="9150034130629852635">"Onyesha nambari"</item>
+ <item msgid="6813323051965618926">"Ficha namba"</item>
+ <item msgid="9150034130629852635">"Onyesha namba"</item>
</string-array>
<string name="vm_changed" msgid="4739599044379692505">"Nambari ya ujumbe wa sauti haijabadilishwa"</string>
<string name="vm_change_failed" msgid="7877733929455763566">"Haikuweza kubadilisha namba ya ujumbe wa sauti.\nWasiliana na mtoa huduma wako shida hii ikiendelea."</string>
<string name="fw_change_failed" msgid="9179241823460192148">"Haikuweza kubadilisha namba ya kusambaza.\nWasiliana na mtoa huduma wako shida hii ikiendelea."</string>
- <string name="fw_get_in_vm_failed" msgid="2432678237218183844">"Haikuweza kuepua na kuhifadhi mipangilio ya nambari ya usambazaji. \n Hata hivyo swichi kwa mtoahuduma mpya?"</string>
+ <string name="fw_get_in_vm_failed" msgid="2432678237218183844">"Haikuweza kuepua na kuhifadhi mipangilio ya namba ya usambazaji. \n Hata hivyo swichi kwa mtoahuduma mpya?"</string>
<string name="no_change" msgid="3737264882821031892">"Hakuna mabadiliko yaliyofanywa"</string>
<string name="sum_voicemail_choose_provider" msgid="6750824719081403773">"Chagua huduma ya barua ya sauti"</string>
<string name="voicemail_default" msgid="6427575113775462077">"Mtoa huduma wako"</string>
@@ -449,9 +449,9 @@
<string name="change_pin2" msgid="3110844547237754871">"Badilisha PIN2"</string>
<string name="enable_fdn_ok" msgid="5080925177369329827">"Lemaza FDN"</string>
<string name="disable_fdn_ok" msgid="3745475926874838676">"Washa FDN"</string>
- <string name="sum_fdn" msgid="6152246141642323582">"Dhibiti nambari za simu zilizobainishwa"</string>
+ <string name="sum_fdn" msgid="6152246141642323582">"Dhibiti namba za simu zilizobainishwa"</string>
<string name="sum_fdn_change_pin" msgid="3510994280557335727">"Badilisha nenosiri la kufikia FDN"</string>
- <string name="sum_fdn_manage_list" msgid="3311397063233992907">"Dhibiti orodha ya nambari za simu"</string>
+ <string name="sum_fdn_manage_list" msgid="3311397063233992907">"Dhibiti orodha ya namba za simu"</string>
<string name="voice_privacy" msgid="7346935172372181951">"Faragha ya sauti"</string>
<string name="voice_privacy_summary" msgid="3556460926168473346">"Wezesha gumzo ya faragha iliyoboreshwa"</string>
<string name="tty_mode_option_title" msgid="3843817710032641703">"Hali ya TTY"</string>
@@ -462,22 +462,22 @@
<string name="menu_add" msgid="5616487894975773141">"Ongeza anwani"</string>
<string name="menu_edit" msgid="3593856941552460706">"Hariri anwani"</string>
<string name="menu_delete" msgid="6326861853830546488">"Futa anwani"</string>
- <string name="menu_dial" msgid="4178537318419450012">"Piga nambari ya unayewasiliana naye"</string>
+ <string name="menu_dial" msgid="4178537318419450012">"Piga namba ya unayewasiliana naye"</string>
<string name="get_pin2" msgid="4221654606863196332">"Chapa PIN2"</string>
<string name="name" msgid="1347432469852527784">"Jina"</string>
<string name="number" msgid="1564053487748491000">"Nambari"</string>
<string name="save" msgid="983805790346099749">"Hifadhi"</string>
- <string name="add_fdn_contact" msgid="1169713422306640887">"Ongeza nambari za upigaji simu uliobanwa"</string>
- <string name="adding_fdn_contact" msgid="3112531600824361259">"Inaongeza nambari ya upigaji uliobanwa..."</string>
+ <string name="add_fdn_contact" msgid="1169713422306640887">"Ongeza namba za upigaji simu uliobanwa"</string>
+ <string name="adding_fdn_contact" msgid="3112531600824361259">"Inaongeza namba ya upigaji uliobanwa..."</string>
<string name="fdn_contact_added" msgid="2840016151693394596">"Nambari ya upigaji simu uliobanwa imeongezwa."</string>
- <string name="edit_fdn_contact" msgid="6030829994819587408">"Hariri nambari za kudumu"</string>
- <string name="updating_fdn_contact" msgid="6989341376868227150">"Inasasisha nambari ya upigaji simu uliobanwa..."</string>
+ <string name="edit_fdn_contact" msgid="6030829994819587408">"Hariri namba za kudumu"</string>
+ <string name="updating_fdn_contact" msgid="6989341376868227150">"Inasasisha namba ya upigaji simu uliobanwa..."</string>
<string name="fdn_contact_updated" msgid="6876330243323118937">"Nambari ya upigaji simu uliobanwa imesasishwa."</string>
<string name="delete_fdn_contact" msgid="7027405651994507077">"Futa namba ya upigaji simu ya kudumu"</string>
- <string name="deleting_fdn_contact" msgid="6872320570844460428">"Inafuta nambari ya upigaji simu uliobanwa..."</string>
+ <string name="deleting_fdn_contact" msgid="6872320570844460428">"Inafuta namba ya upigaji simu uliobanwa..."</string>
<string name="fdn_contact_deleted" msgid="1680714996763848838">"Nambari ya upigaji simu uliobanwa imefutwa"</string>
<string name="pin2_invalid" msgid="2313954262684494442">"FDN haikusasishwa kwa sababu uliweka PIN isiyo sahihi."</string>
- <string name="fdn_invalid_number" msgid="9067189814657840439">"FDN haijasasishwa kwa sababu nambari inazidi tarakimu <xliff:g id="FDN_NUMBER_LIMIT_LENGTH">%d</xliff:g>."</string>
+ <string name="fdn_invalid_number" msgid="9067189814657840439">"FDN haijasasishwa kwa sababu namba inazidi tarakimu <xliff:g id="FDN_NUMBER_LIMIT_LENGTH">%d</xliff:g>."</string>
<string name="pin2_or_fdn_invalid" msgid="7542639487955868181">"FDN haikusasishwa. PIN2 haikuwa sahihi, au namba ya simu ilikataliwa."</string>
<string name="fdn_failed" msgid="216592346853420250">"Utendakazi wa FDN ulishindwa."</string>
<string name="simContacts_emptyLoading" msgid="4989040293858675483">"Inasoma kutoka kwa SIM kadi…"</string>
@@ -492,7 +492,7 @@
<string name="confirmPinLabel" msgid="7783531218662473778">"Thibitisha PIN mpya"</string>
<string name="badPin" msgid="4549286285015892321">"PIN ya zamani uliyochapa sio sahihi. Jaribu tena."</string>
<string name="mismatchPin" msgid="1467254768290323845">"PIN ulizochapa hazilingani. Jaribu tena."</string>
- <string name="invalidPin" msgid="7363723429414001979">"Chapisha nenosiri lenye nambari kati ya 4 na 8."</string>
+ <string name="invalidPin" msgid="7363723429414001979">"Chapisha nenosiri lenye namba kati ya 4 na 8."</string>
<string name="disable_sim_pin" msgid="3112303905548613752">"Ondoa PIN ya SIM"</string>
<string name="enable_sim_pin" msgid="445461050748318980">"Weka PIN ya SIM"</string>
<string name="enable_in_progress" msgid="4135305985717272592">"Inaweka PIN…"</string>
@@ -508,8 +508,8 @@
<string name="badPuk2" msgid="6438182906645832235">"PUK2 si sahihi. Jaribu tena."</string>
<string name="badPin2" msgid="2760917538643074635">"PIN2 ya zamani si sahihi. Jaribu tena."</string>
<string name="mismatchPin2" msgid="4952718725266700631">"PIN2 hazilingani. Jaribu tena."</string>
- <string name="invalidPin2" msgid="6467957903056379343">"Weka PIN2 iliyo na kati ya nambari 4 hadi 8."</string>
- <string name="invalidPuk2" msgid="713729511903849544">"Weka PUK2 yenye nambari 8."</string>
+ <string name="invalidPin2" msgid="6467957903056379343">"Weka PIN2 iliyo na kati ya namba 4 hadi 8."</string>
+ <string name="invalidPuk2" msgid="713729511903849544">"Weka PUK2 yenye namba 8."</string>
<string name="pin2_changed" msgid="5710551850481287821">"PIN2 imesasishwa"</string>
<string name="label_puk2_code" msgid="2852217004288085562">"Weka msimbo wa PUK2"</string>
<string name="fdn_enable_puk2_requested" msgid="5793652792131588041">"Nenosiri si sahihi. PIN2 sasa Imezuiwa. Ili ujaribu tena, badilisha PIN 2."</string>
@@ -543,7 +543,7 @@
<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_wfc" msgid="4497663185857190885">"Mtandao wa simu za mkononi haupatikani. Unganisha kwenye mtandao pasiwaya ili upige simu."</string>
- <string name="incall_error_no_phone_number_supplied" msgid="8680831089508851894">"Ili upige simu, weka nambari sahihi."</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>
<string name="incall_error_supp_service_unknown" msgid="8751177117194592623">"Huduma haiwezi kutumika."</string>
@@ -568,8 +568,8 @@
<string name="emergency_enable_radio_dialog_message" msgid="1695305158151408629">"Inawasha redio..."</string>
<string name="emergency_enable_radio_dialog_retry" msgid="4329131876852608587">"Hakuna huduma. Inajaribu tena..."</string>
<string name="radio_off_during_emergency_call" msgid="8011154134040481609">"Huwezi kuingia katika hali ya ndegeni huku simu ya dharura inaendelea."</string>
- <string name="dial_emergency_error" msgid="825822413209026039">"Haiwezi kupiga simu. <xliff:g id="NON_EMERGENCY_NUMBER">%s</xliff:g> si nambari ya dharura."</string>
- <string name="dial_emergency_empty_error" msgid="2785803395047793634">"Haiwezi kupiga simu. Piga nambari ya dharura."</string>
+ <string name="dial_emergency_error" msgid="825822413209026039">"Haiwezi kupiga simu. <xliff:g id="NON_EMERGENCY_NUMBER">%s</xliff:g> si namba ya dharura."</string>
+ <string name="dial_emergency_empty_error" msgid="2785803395047793634">"Haiwezi kupiga simu. Piga namba ya dharura."</string>
<string name="dial_emergency_calling_not_available" msgid="6485846193794727823">"Upigaji simu ya dharura haupatikani"</string>
<string name="pin_puk_system_user_only" msgid="1045147220686867922">"Mmiliki wa kifaa pekee ndiye anaweza kuweka misimbo ya PIN/PUK."</string>
<string name="police_type_description" msgid="2819533883972081757">"Polisi"</string>
@@ -692,7 +692,7 @@
<string name="change_pin_enter_old_pin_header" msgid="853151335217594829">"Thibitisha PIN yako ya awali"</string>
<string name="change_pin_enter_old_pin_hint" msgid="8801292976275169367">"Weka PIN yako ya ujumbe wa sauti ili uendelee."</string>
<string name="change_pin_enter_new_pin_header" msgid="4739465616733486118">"Weka PIN mpya"</string>
- <string name="change_pin_enter_new_pin_hint" msgid="2326038476516364210">"PIN lazima iwe na nambari <xliff:g id="MIN">%1$d</xliff:g>-<xliff:g id="MAX">%2$d</xliff:g>."</string>
+ <string name="change_pin_enter_new_pin_hint" msgid="2326038476516364210">"PIN lazima iwe na namba <xliff:g id="MIN">%1$d</xliff:g>-<xliff:g id="MAX">%2$d</xliff:g>."</string>
<string name="change_pin_confirm_pin_header" msgid="2606303906320705726">"Thibitisha PIN yako"</string>
<string name="change_pin_confirm_pins_dont_match" msgid="305164501222587215">"PIN hazilingani"</string>
<string name="change_pin_succeeded" msgid="2504705600693014403">"PIN ya ujumbe wa sauti imesasishwa"</string>
@@ -716,55 +716,55 @@
<string name="clh_callFailed_simError_txt" msgid="5128538525762326413">"Imeshindwa kufikia SIM kadi"</string>
<string name="clh_incall_error_out_of_service_txt" msgid="2736010617446749869">"Mtandao wa simu za mkononi haupatikani"</string>
<string name="clh_callFailed_satelliteEnabled_txt" msgid="1675517238240377396">"Hali ya sateliti imewashwa"</string>
- <string name="clh_callFailed_unassigned_number_txt" msgid="141967660286695682">"Nambari ya simu unayojaribu kupiga ina hitilafu. Msimbo wa hitilafu nambari 1."</string>
- <string name="clh_callFailed_no_route_to_destination_txt" msgid="4805015149822352308">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 3."</string>
- <string name="clh_callFailed_channel_unacceptable_txt" msgid="4062754579408613021">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 6."</string>
- <string name="clh_callFailed_operator_determined_barring_txt" msgid="4202077821465974286">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 8."</string>
- <string name="clh_callFailed_normal_call_clearing_txt" msgid="5677987959062976462">"Imeshidwa kupiga simu. Msimbo wa hitilafu nambari 16."</string>
+ <string name="clh_callFailed_unassigned_number_txt" msgid="141967660286695682">"Nambari ya simu unayojaribu kupiga ina hitilafu. Msimbo wa hitilafu namba 1."</string>
+ <string name="clh_callFailed_no_route_to_destination_txt" msgid="4805015149822352308">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 3."</string>
+ <string name="clh_callFailed_channel_unacceptable_txt" msgid="4062754579408613021">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 6."</string>
+ <string name="clh_callFailed_operator_determined_barring_txt" msgid="4202077821465974286">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 8."</string>
+ <string name="clh_callFailed_normal_call_clearing_txt" msgid="5677987959062976462">"Imeshidwa kupiga simu. Msimbo wa hitilafu namba 16."</string>
<string name="clh_callFailed_user_busy_txt" msgid="8886432858568086854">"Mtumiaji ana shughuli"</string>
<string name="clh_callFailed_no_user_responding_txt" msgid="341100226919865128">"Mtumiaji hajibu"</string>
- <string name="clh_callFailed_user_alerting_txt" msgid="896082976264427969">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 19."</string>
+ <string name="clh_callFailed_user_alerting_txt" msgid="896082976264427969">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 19."</string>
<string name="clh_callFailed_call_rejected_txt" msgid="3439435671153341709">"Simu imekataliwa"</string>
<string name="clh_callFailed_number_changed_txt" msgid="2868476949771441667">"Nambari imebadilishwa"</string>
- <string name="clh_callFailed_pre_emption_txt" msgid="8887998866342162724">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 25."</string>
- <string name="clh_callFailed_non_selected_user_clearing_txt" msgid="4804529874810197550">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 26."</string>
- <string name="clh_callFailed_destination_out_of_order_txt" msgid="1130697076352728824">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 27."</string>
- <string name="clh_callFailed_invalid_number_format_txt" msgid="3171016382987224989">"Muundo usio sahihi wa nambari (nambari haijakamilika)"</string>
- <string name="clh_callFailed_facility_rejected_txt" msgid="1054386430010898993">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 29."</string>
- <string name="clh_callFailed_response_to_STATUS_ENQUIRY_txt" msgid="2763172551412307536">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 30."</string>
- <string name="clh_callFailed_normal_unspecified_txt" msgid="978119938935737419">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 31."</string>
- <string name="clh_callFailed_no_circuit_available_txt" msgid="1519684050419134605">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 34."</string>
- <string name="clh_callFailed_network_out_of_order_txt" msgid="8689826504394592289">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 38."</string>
- <string name="clh_callFailed_temporary_failure_txt" msgid="5065091554509067874">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 41."</string>
- <string name="clh_callFailed_switching_equipment_congestion_txt" msgid="8681599376741988769">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 42."</string>
- <string name="clh_callFailed_access_information_discarded_txt" msgid="2476199425130545428">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 43."</string>
- <string name="clh_callFailed_requested_circuit_txt" msgid="7497497808928490219">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 44."</string>
- <string name="clh_callFailed_resources_unavailable_unspecified_txt" msgid="144010529672928445">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 47."</string>
- <string name="clh_callFailed_quality_of_service_unavailable_txt" msgid="4650329342288289290">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 49."</string>
- <string name="clh_callFailed_requested_facility_not_subscribed_txt" msgid="9107977008516882170">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 50."</string>
- <string name="clh_callFailed_incoming_calls_barred_within_the_CUG_txt" msgid="501037491908315591">"Imeshidwa kupiga simu. Msimbo wa hitilafu nambari 55."</string>
- <string name="clh_callFailed_bearer_capability_not_authorized_txt" msgid="4344366517528362620">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 57."</string>
- <string name="clh_callFailed_bearer_capability_not_presently_available_txt" msgid="1436957294571545381">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 58."</string>
- <string name="clh_callFailed_service_or_option_not_available_unspecified_txt" msgid="2149878874722675428">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 63."</string>
- <string name="clh_callFailed_bearer_service_not_implemented_txt" msgid="1074983013965612410">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 65."</string>
- <string name="clh_callFailed_ACM_equal_to_or_greater_than_ACMmax_txt" msgid="7889034195264205333">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 68."</string>
- <string name="clh_callFailed_requested_facility_not_implemented_txt" msgid="7996646684699167978">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 69."</string>
- <string name="clh_callFailed_only_restricted_digital_information_bearer_capability_is_available_txt" msgid="2358958110447385682">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 70."</string>
- <string name="clh_callFailed_service_or_option_not_implemented_unspecified_txt" msgid="3046428509531159481">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 79."</string>
- <string name="clh_callFailed_invalid_transaction_identifier_value_txt" msgid="1727401871777396619">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 81."</string>
- <string name="clh_callFailed_user_not_member_of_CUG_txt" msgid="442282135105229307">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 87."</string>
- <string name="clh_callFailed_incompatible_destination_txt" msgid="5900394706344969020">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 88."</string>
- <string name="clh_callFailed_invalid_transit_network_selection_txt" msgid="6274621838349037741">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 91."</string>
- <string name="clh_callFailed_semantically_incorrect_message_txt" msgid="7000705190197981937">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 95."</string>
- <string name="clh_callFailed_invalid_mandatory_information_txt" msgid="3609204152671052123">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 96."</string>
- <string name="clh_callFailed_message_type_non_existent_or_not_implemented_txt" msgid="1552110431052032814">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 97."</string>
- <string name="clh_callFailed_message_type_not_compatible_with_protocol_state_txt" msgid="7717048934226300032">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 98."</string>
- <string name="clh_callFailed_information_element_non_existent_or_not_implemented_txt" msgid="8931396541061612169">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 99."</string>
- <string name="clh_callFailed_conditional_IE_error_txt" msgid="4630685477888727741">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 100."</string>
- <string name="clh_callFailed_message_not_compatible_with_protocol_state_txt" msgid="3014075977395922947">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 101."</string>
- <string name="clh_callFailed_recovery_on_timer_expiry_txt" msgid="5637581978978731672">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 102."</string>
- <string name="clh_callFailed_protocol_Error_unspecified_txt" msgid="9203320572562697755">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 111."</string>
- <string name="clh_callFailed_interworking_unspecified_txt" msgid="7969686413930847182">"Imeshindwa kupiga simu. Msimbo wa hitilafu nambari 127."</string>
+ <string name="clh_callFailed_pre_emption_txt" msgid="8887998866342162724">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 25."</string>
+ <string name="clh_callFailed_non_selected_user_clearing_txt" msgid="4804529874810197550">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 26."</string>
+ <string name="clh_callFailed_destination_out_of_order_txt" msgid="1130697076352728824">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 27."</string>
+ <string name="clh_callFailed_invalid_number_format_txt" msgid="3171016382987224989">"Muundo usio sahihi wa namba (namba haijakamilika)"</string>
+ <string name="clh_callFailed_facility_rejected_txt" msgid="1054386430010898993">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 29."</string>
+ <string name="clh_callFailed_response_to_STATUS_ENQUIRY_txt" msgid="2763172551412307536">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 30."</string>
+ <string name="clh_callFailed_normal_unspecified_txt" msgid="978119938935737419">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 31."</string>
+ <string name="clh_callFailed_no_circuit_available_txt" msgid="1519684050419134605">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 34."</string>
+ <string name="clh_callFailed_network_out_of_order_txt" msgid="8689826504394592289">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 38."</string>
+ <string name="clh_callFailed_temporary_failure_txt" msgid="5065091554509067874">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 41."</string>
+ <string name="clh_callFailed_switching_equipment_congestion_txt" msgid="8681599376741988769">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 42."</string>
+ <string name="clh_callFailed_access_information_discarded_txt" msgid="2476199425130545428">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 43."</string>
+ <string name="clh_callFailed_requested_circuit_txt" msgid="7497497808928490219">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 44."</string>
+ <string name="clh_callFailed_resources_unavailable_unspecified_txt" msgid="144010529672928445">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 47."</string>
+ <string name="clh_callFailed_quality_of_service_unavailable_txt" msgid="4650329342288289290">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 49."</string>
+ <string name="clh_callFailed_requested_facility_not_subscribed_txt" msgid="9107977008516882170">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 50."</string>
+ <string name="clh_callFailed_incoming_calls_barred_within_the_CUG_txt" msgid="501037491908315591">"Imeshidwa kupiga simu. Msimbo wa hitilafu namba 55."</string>
+ <string name="clh_callFailed_bearer_capability_not_authorized_txt" msgid="4344366517528362620">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 57."</string>
+ <string name="clh_callFailed_bearer_capability_not_presently_available_txt" msgid="1436957294571545381">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 58."</string>
+ <string name="clh_callFailed_service_or_option_not_available_unspecified_txt" msgid="2149878874722675428">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 63."</string>
+ <string name="clh_callFailed_bearer_service_not_implemented_txt" msgid="1074983013965612410">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 65."</string>
+ <string name="clh_callFailed_ACM_equal_to_or_greater_than_ACMmax_txt" msgid="7889034195264205333">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 68."</string>
+ <string name="clh_callFailed_requested_facility_not_implemented_txt" msgid="7996646684699167978">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 69."</string>
+ <string name="clh_callFailed_only_restricted_digital_information_bearer_capability_is_available_txt" msgid="2358958110447385682">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 70."</string>
+ <string name="clh_callFailed_service_or_option_not_implemented_unspecified_txt" msgid="3046428509531159481">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 79."</string>
+ <string name="clh_callFailed_invalid_transaction_identifier_value_txt" msgid="1727401871777396619">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 81."</string>
+ <string name="clh_callFailed_user_not_member_of_CUG_txt" msgid="442282135105229307">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 87."</string>
+ <string name="clh_callFailed_incompatible_destination_txt" msgid="5900394706344969020">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 88."</string>
+ <string name="clh_callFailed_invalid_transit_network_selection_txt" msgid="6274621838349037741">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 91."</string>
+ <string name="clh_callFailed_semantically_incorrect_message_txt" msgid="7000705190197981937">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 95."</string>
+ <string name="clh_callFailed_invalid_mandatory_information_txt" msgid="3609204152671052123">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 96."</string>
+ <string name="clh_callFailed_message_type_non_existent_or_not_implemented_txt" msgid="1552110431052032814">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 97."</string>
+ <string name="clh_callFailed_message_type_not_compatible_with_protocol_state_txt" msgid="7717048934226300032">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 98."</string>
+ <string name="clh_callFailed_information_element_non_existent_or_not_implemented_txt" msgid="8931396541061612169">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 99."</string>
+ <string name="clh_callFailed_conditional_IE_error_txt" msgid="4630685477888727741">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 100."</string>
+ <string name="clh_callFailed_message_not_compatible_with_protocol_state_txt" msgid="3014075977395922947">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 101."</string>
+ <string name="clh_callFailed_recovery_on_timer_expiry_txt" msgid="5637581978978731672">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 102."</string>
+ <string name="clh_callFailed_protocol_Error_unspecified_txt" msgid="9203320572562697755">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 111."</string>
+ <string name="clh_callFailed_interworking_unspecified_txt" msgid="7969686413930847182">"Imeshindwa kupiga simu. Msimbo wa hitilafu namba 127."</string>
<string name="labelCallBarring" msgid="4180377113052853173">"Kuzuia upigaji simu"</string>
<string name="sum_call_barring_enabled" msgid="5184331188926370824">"Washa"</string>
<string name="sum_call_barring_disabled" msgid="5699448000600153096">"Zima"</string>
@@ -802,7 +802,7 @@
<string name="supp_service_notification_call_deflected" msgid="4980942818105909813">"Simu unayopiga imeelekezwa kwingine."</string>
<string name="supp_service_notification_call_forwarded" msgid="7102930311735433088">"Simu imesambazwa."</string>
<string name="supp_service_notification_call_waiting" msgid="4577403881609445324">"Simu inasubiri."</string>
- <string name="supp_service_clir_suppression_rejected" msgid="6105737020194776121">"Ombi la kuzuia nambari limekataliwa."</string>
+ <string name="supp_service_clir_suppression_rejected" msgid="6105737020194776121">"Ombi la kuzuia namba limekataliwa."</string>
<string name="supp_service_closed_user_group_call" msgid="2811636666505250689">"Simu kwenye kikundi cha watumiaji teule."</string>
<string name="supp_service_incoming_calls_barred" msgid="2034627421274447674">"Simu zinazoingia zimezuiwa."</string>
<string name="supp_service_outgoing_calls_barred" msgid="5205725332394087112">"Simu unazopiga zimezuiwa."</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index ba65302..7cd4e18 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -318,7 +318,27 @@
<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 carriers that don't care about NGRAN's preference in the preferred emergency
+ network scan list after SIM is removed. -->
+ <integer-array name="config_carriers_ignore_ngran_preference_when_sim_removed">
+ <!-- 001-01 Test SIM -->
+ <item>1911</item>
+ </integer-array>
+
+ <!-- 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>
+
+ <!-- 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/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 3eafb24..ef71016 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;
@@ -66,6 +69,7 @@
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;
@@ -685,12 +689,17 @@
@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 =
@@ -719,6 +728,8 @@
TelephonyManager.from(context).registerCarrierPrivilegesCallback(phoneId,
new HandlerExecutor(mHandler), mCarrierServiceChangeCallbacks[phoneId]);
}
+ mFeatureFlags = featureFlags;
+ mPackageManager = context.getPackageManager();
logd("CarrierConfigLoader has started");
PhoneConfigurationManager.registerForMultiSimConfigChange(
@@ -733,10 +744,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);
@@ -1323,6 +1335,8 @@
return new PersistableBundle();
}
+ enforceTelephonyFeatureWithException(callingPackage, "getConfigForSubIdWithFeature");
+
int phoneId = SubscriptionManager.getPhoneId(subscriptionId);
PersistableBundle retConfig = CarrierConfigManager.getDefaultConfig();
if (SubscriptionManager.isValidPhoneId(phoneId)) {
@@ -1367,6 +1381,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
@@ -1416,6 +1433,9 @@
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);
@@ -1468,6 +1488,9 @@
"Invalid phoneId " + phoneId + " for subId " + subscriptionId);
}
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ "notifyConfigChangedForSubId");
+
logdWithLocalLog("Notified carrier config changed. phoneId=" + phoneId
+ ", subId=" + subscriptionId);
@@ -1488,6 +1511,9 @@
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:
@@ -1509,6 +1535,10 @@
@NonNull
public String getDefaultCarrierServicePackageName() {
getDefaultCarrierServicePackageName_enforcePermission();
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ "getDefaultCarrierServicePackageName");
+
return mPlatformCarrierConfigPackage;
}
@@ -1819,6 +1849,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 e468c00..4f94b58 100644
--- a/src/com/android/phone/CdmaCallOptions.java
+++ b/src/com/android/phone/CdmaCallOptions.java
@@ -32,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";
@@ -85,7 +86,7 @@
UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
boolean mobileNetworkConfigsRestricted =
userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
- if (mobileNetworkConfigsRestricted) {
+ if (Flags.ensureAccessToCallSettingsIsRestricted() && mobileNetworkConfigsRestricted) {
Log.i(LOG_TAG, "Mobile network configs are restricted, hiding CDMA call forwarding "
+ "and CDMA call waiting options.");
}
@@ -93,7 +94,8 @@
mCallForwardingPref = getPreferenceScreen().findPreference(CALL_FORWARDING_KEY);
if (carrierConfig != null && carrierConfig.getBoolean(
CarrierConfigManager.KEY_CALL_FORWARDING_VISIBILITY_BOOL) &&
- !mobileNetworkConfigsRestricted) {
+ (!mobileNetworkConfigsRestricted ||
+ !Flags.ensureAccessToCallSettingsIsRestricted())) {
mCallForwardingPref.setIntent(
subInfoHelper.getIntent(CdmaCallForwardOptions.class));
} else {
@@ -105,7 +107,8 @@
.findPreference(CALL_WAITING_KEY);
if (carrierConfig == null || !carrierConfig.getBoolean(
CarrierConfigManager.KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL) ||
- mobileNetworkConfigsRestricted) {
+ (Flags.ensureAccessToCallSettingsIsRestricted() &&
+ mobileNetworkConfigsRestricted)) {
getPreferenceScreen().removePreference(mCallWaitingPref);
mCallWaitingPref = null;
}
diff --git a/src/com/android/phone/DiagnosticDataCollector.java b/src/com/android/phone/DiagnosticDataCollector.java
index e997270..bdd9ce9 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;
@@ -84,7 +83,7 @@
persistTelecomState(dc, tag);
}
if (edp.isLogcatCollectionEnabled()) {
- persistLogcat(dc, tag, edp.getLogcatStartTime());
+ persistLogcat(dc, tag, edp.getLogcatCollectionStartTimeMillis());
}
}
diff --git a/src/com/android/phone/GsmUmtsCallOptions.java b/src/com/android/phone/GsmUmtsCallOptions.java
index 8ff7ecc..be5295d 100644
--- a/src/com/android/phone/GsmUmtsCallOptions.java
+++ b/src/com/android/phone/GsmUmtsCallOptions.java
@@ -29,6 +29,7 @@
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";
@@ -88,7 +89,7 @@
.getSystemService(Context.USER_SERVICE);
boolean mobileNetworkConfigsRestricted =
userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
- if (mobileNetworkConfigsRestricted) {
+ if (Flags.ensureAccessToCallSettingsIsRestricted() && mobileNetworkConfigsRestricted) {
Log.i(LOG_TAG, "Mobile network configs are restricted, hiding GSM call "
+ "forwarding, additional call settings, and call options.");
}
@@ -97,7 +98,8 @@
if (callForwardingPref != null) {
if (b != null && b.getBoolean(
CarrierConfigManager.KEY_CALL_FORWARDING_VISIBILITY_BOOL) &&
- !mobileNetworkConfigsRestricted) {
+ (!Flags.ensureAccessToCallSettingsIsRestricted() ||
+ !mobileNetworkConfigsRestricted)) {
callForwardingPref.setIntent(
subInfoHelper.getIntent(GsmUmtsCallForwardOptions.class));
callForwardingPref.setEnabled(isAirplaneModeOff);
@@ -113,7 +115,8 @@
CarrierConfigManager.KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL)
|| b.getBoolean(
CarrierConfigManager.KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL)) &&
- !mobileNetworkConfigsRestricted) {
+ (!Flags.ensureAccessToCallSettingsIsRestricted() ||
+ !mobileNetworkConfigsRestricted)) {
additionalGsmSettingsPref.setIntent(
subInfoHelper.getIntent(GsmUmtsAdditionalCallOptions.class));
additionalGsmSettingsPref.setEnabled(isAirplaneModeOff);
@@ -125,7 +128,8 @@
Preference callBarringPref = prefScreen.findPreference(CALL_BARRING_KEY);
if (callBarringPref != null) {
if (b != null && b.getBoolean(CarrierConfigManager.KEY_CALL_BARRING_VISIBILITY_BOOL) &&
- !mobileNetworkConfigsRestricted) {
+ (!Flags.ensureAccessToCallSettingsIsRestricted() ||
+ !mobileNetworkConfigsRestricted)) {
callBarringPref.setIntent(subInfoHelper.getIntent(GsmUmtsCallBarringOptions.class));
callBarringPref.setEnabled(isAirplaneModeOff);
} else {
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/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index 518003f..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();
}
/**
@@ -828,6 +834,12 @@
* @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();
@@ -868,6 +880,62 @@
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 =
+ 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();
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 4773a83..7fba651 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -17,6 +17,7 @@
package com.android.phone;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.app.Activity;
import android.app.KeyguardManager;
import android.app.ProgressDialog;
@@ -49,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;
@@ -81,7 +83,6 @@
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;
@@ -164,7 +165,6 @@
public ImsStateCallbackController mImsStateCallbackController;
public ImsProvisioningController mImsProvisioningController;
CarrierConfigLoader configLoader;
- TelephonyDomainSelectionService mDomainSelectionService;
private Phone phoneInEcm;
@@ -217,6 +217,14 @@
*/
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<>();
+
private WakeState mWakeState = WakeState.SLEEP;
private PowerManager mPowerManager;
@@ -240,6 +248,8 @@
// fine or coarse location since we only use ServiceState for
private PhoneAppCallback[] mTelephonyCallbacks;
+ private FeatureFlags mFeatureFlags;
+
private class PhoneAppCallback extends TelephonyCallback implements
TelephonyCallback.ServiceStateListener {
private final int mSubId;
@@ -385,11 +395,19 @@
//TODO: handle message here;
break;
case EVENT_DATA_ROAMING_SETTINGS_CHANGED:
- updateDataRoamingStatus(
- ROAMING_NOTIFICATION_REASON_DATA_ROAMING_SETTING_CHANGED);
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ updateDataRoamingStatus(
+ ROAMING_NOTIFICATION_REASON_DATA_ROAMING_SETTING_CHANGED);
+ } else {
+ updateDataRoamingStatusForFeatureDisabled(null);
+ }
break;
case EVENT_MOBILE_DATA_SETTINGS_CHANGED:
- updateDataRoamingStatus(ROAMING_NOTIFICATION_REASON_DATA_SETTING_CHANGED);
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ updateDataRoamingStatus(ROAMING_NOTIFICATION_REASON_DATA_SETTING_CHANGED);
+ } else {
+ updateDataRoamingStatusForFeatureDisabled(null);
+ }
break;
case EVENT_CARRIER_CONFIG_CHANGED:
int subId = (Integer) msg.obj;
@@ -476,18 +494,18 @@
// 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
- FeatureFlags featureFlags = new FeatureFlagsImpl();
- PhoneFactory.makeDefaultPhones(this, featureFlags);
+ mFeatureFlags = new FeatureFlagsImpl();
+ 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);
@@ -539,7 +557,7 @@
// Create the SatelliteController singleton, which acts as a backend service for
// {@link android.telephony.satellite.SatelliteManager}.
- SatelliteController.make(this, featureFlags);
+ SatelliteController.make(this, mFeatureFlags);
// Create an instance of CdmaPhoneCallState and initialize it to IDLE
cdmaPhoneCallState = new CdmaPhoneCallState();
@@ -554,11 +572,11 @@
mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
- phoneMgr = PhoneInterfaceManager.init(this, featureFlags);
+ 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 =
@@ -601,7 +619,9 @@
registerReceiver(mReceiver, intentFilter);
int defaultDataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
if (SubscriptionManager.isValidSubscriptionId(defaultDataSubId)) {
- if (VDBG) Log.v(LOG_TAG, "Loaded initial default data sub: " + defaultDataSubId);
+ if (VDBG) {
+ Log.v(LOG_TAG, "Loaded initial default data sub: " + defaultDataSubId);
+ }
mDefaultDataSubId = defaultDataSubId;
registerSettingsObserver();
updateDataRoamingStatus(ROAMING_NOTIFICATION_REASON_DEFAULT_DATA_SUBS_CHANGED);
@@ -808,7 +828,11 @@
/** Clear fields on power off radio **/
private void clearCacheOnRadioOff() {
// Re-show is-roaming notifications after APM mode
- mShownNotificationReasons.clear();
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ mShownNotificationReasons.clear();
+ } else {
+ mPrevRoamingOperatorNumerics.clear();
+ }
}
private void setRadioPowerOn() {
@@ -905,7 +929,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(ROAMING_NOTIFICATION_REASON_CARRIER_CONFIG_CHANGED);
+ 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);
@@ -920,7 +948,12 @@
registerSettingsObserver();
Phone phone = getPhone(mDefaultDataSubId);
if (phone != null) {
- updateDataRoamingStatus(ROAMING_NOTIFICATION_REASON_DEFAULT_DATA_SUBS_CHANGED);
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ updateDataRoamingStatus(
+ ROAMING_NOTIFICATION_REASON_DEFAULT_DATA_SUBS_CHANGED);
+ } else {
+ updateDataRoamingStatusForFeatureDisabled(null);
+ }
}
}
}
@@ -936,7 +969,11 @@
+ mDefaultDataSubId + ", ss roaming=" + serviceState.getDataRoaming());
}
if (subId == mDefaultDataSubId) {
- updateDataRoamingStatus(ROAMING_NOTIFICATION_REASON_SERVICE_STATE_CHANGED);
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ updateDataRoamingStatus(ROAMING_NOTIFICATION_REASON_SERVICE_STATE_CHANGED);
+ } else {
+ updateDataRoamingStatusForFeatureDisabled(serviceState.getOperatorNumeric());
+ }
}
}
@@ -955,6 +992,11 @@
}
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;
@@ -1021,16 +1063,14 @@
&& reason == ROAMING_NOTIFICATION_REASON_CARRIER_CONFIG_CHANGED) {
mShownNotificationReasons.add(callingReason);
}
- boolean isShowRoamingNotificationEnabled = getCarrierConfigForSubId(mDefaultDataSubId)
- .getBoolean(CarrierConfigManager
- .KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL);
+ 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 && isShowRoamingNotificationEnabled) {
+ if (!shownInThisNumeric && !shownForThisReason && shouldShowRoamingNotification) {
updateDataRoamingNotification(ROAMING_NOTIFICATION_CONNECTED);
} else {
// Don't show roaming notification if we've already shown for this MccMnc or
@@ -1038,7 +1078,7 @@
Log.d(LOG_TAG, "Skip roaming connected notification since already"
+ " shownInThisNumeric:" + shownInThisNumeric
+ " shownForThisReason:" + shownForThisReason
- + " isShowRoamingNotificationEnabled:" + isShowRoamingNotificationEnabled);
+ + " shouldShowRoamingNotification:" + shouldShowRoamingNotification);
// Dismiss notification if the other notification is shown.
if (getCurrentRoamingNotification() != ROAMING_NOTIFICATION_NO_NOTIFICATION) {
updateDataRoamingNotification(ROAMING_NOTIFICATION_NO_NOTIFICATION);
@@ -1083,6 +1123,87 @@
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
+ * changes, we need to dismiss the notification.
+ * @param roamingOperatorNumeric The operator numeric for the current roaming. {@code null} if
+ * the current roaming operator numeric didn't change.
+ */
+ 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);
+ return;
+ }
+
+ boolean dataAllowed;
+ boolean notAllowedDueToRoamingOff;
+ List<DataDisallowedReason> reasons = phone.getDataNetworkController()
+ .getInternetDataDisallowedReasons();
+ dataAllowed = reasons.isEmpty();
+ notAllowedDueToRoamingOff = (reasons.size() == 1
+ && reasons.contains(DataDisallowedReason.ROAMING_DISABLED));
+ mDataRoamingNotifLog.log("dataAllowed=" + dataAllowed + ", reasons=" + reasons
+ + ", roamingOperatorNumeric=" + roamingOperatorNumeric);
+ if (VDBG) {
+ Log.v(LOG_TAG, "dataAllowed=" + dataAllowed + ", reasons=" + reasons
+ + ", roamingOperatorNumeric=" + roamingOperatorNumeric);
+ }
+
+ if (!dataAllowed && notAllowedDueToRoamingOff) {
+ // Don't show roaming notification if we've already shown for this MccMnc
+ if (roamingOperatorNumeric != null
+ && !mPrevRoamingOperatorNumerics.add(roamingOperatorNumeric)) {
+ Log.d(LOG_TAG, "Skip roaming disconnected notification since already shown in "
+ + "MccMnc " + roamingOperatorNumeric);
+ return;
+ }
+ // No need to show it again if we never cancelled it explicitly.
+ if (mPrevRoamingNotification == 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.
+ mPrevRoamingNotification = ROAMING_NOTIFICATION_DISCONNECTED;
+ Log.d(LOG_TAG, "Show roaming disconnected notification");
+ mDataRoamingNotifLog.log("Show roaming off.");
+ Message msg = mHandler.obtainMessage(EVENT_DATA_ROAMING_DISCONNECTED);
+ msg.arg1 = mDefaultDataSubId;
+ msg.sendToTarget();
+ } else if (dataAllowed && dataIsNowRoaming(mDefaultDataSubId)) {
+ if (!shouldShowRoamingNotification(roamingOperatorNumeric)) {
+ 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)) {
+ Log.d(LOG_TAG, "Skip roaming connected notification since already shown in "
+ + "MccMnc " + roamingOperatorNumeric);
+ return;
+ }
+ // No need to show it again if we never cancelled it explicitly, or carrier config
+ // indicates this is not needed.
+ if (mPrevRoamingNotification == ROAMING_NOTIFICATION_CONNECTED) return;
+ mPrevRoamingNotification = ROAMING_NOTIFICATION_CONNECTED;
+ Log.d(LOG_TAG, "Show roaming connected notification");
+ mDataRoamingNotifLog.log("Show roaming on.");
+ Message msg = mHandler.obtainMessage(EVENT_DATA_ROAMING_CONNECTED);
+ msg.arg1 = mDefaultDataSubId;
+ msg.sendToTarget();
+ } else if (mPrevRoamingNotification != 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.
+ mPrevRoamingNotification = ROAMING_NOTIFICATION_NO_NOTIFICATION;
+ Log.d(LOG_TAG, "Dismiss roaming notification");
+ mDataRoamingNotifLog.log("Hide. data allowed=" + dataAllowed);
+ mHandler.sendEmptyMessage(EVENT_DATA_ROAMING_OK);
+ }
+ }
+
/**
*
* @param subId to check roaming on
@@ -1092,6 +1213,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
@@ -1179,7 +1340,18 @@
IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
pw.println("------- PhoneGlobals -------");
pw.increaseIndent();
- pw.println("mCurrentRoamingNotification=" + mCurrentRoamingNotification);
+ 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("isSmsCapable=" + TelephonyManager.from(this).isSmsCapable());
pw.println("mDataRoamingNotifLog:");
@@ -1216,11 +1388,12 @@
e.printStackTrace();
}
pw.decreaseIndent();
- if (mDomainSelectionService != null) {
- mDomainSelectionService.dump(fd, pw, args);
- }
pw.decreaseIndent();
- pw.println("mShownNotificationReasons=" + mShownNotificationReasons);
+ 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 4e673d8..43011e8 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -16,9 +16,16 @@
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_SUCCESS;
import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_CDMA;
import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_GSM;
@@ -147,10 +154,13 @@
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;
@@ -203,6 +213,7 @@
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;
@@ -236,6 +247,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;
@@ -397,28 +410,33 @@
// 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 final FeatureFlags mFeatureFlags;
+ private FeatureFlags mFeatureFlags;
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 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_";
@@ -2454,9 +2472,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
@@ -2548,6 +2573,9 @@
}
public void dial(String number) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "dial");
+
dialForSubscriber(getPreferredVoiceSubscription(), number);
}
@@ -2593,6 +2621,9 @@
return;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "call");
+
final long identity = Binder.clearCallingIdentity();
try {
String url = createTelUrl(number);
@@ -2636,6 +2667,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);
@@ -2650,6 +2685,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);
@@ -2855,6 +2893,9 @@
return false;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "isRadioOnWithFeature");
+
final long identity = Binder.clearCallingIdentity();
try {
return isRadioOnForSubscriber(subId);
@@ -2884,6 +2925,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);
@@ -2919,6 +2963,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.
@@ -2940,6 +2988,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++) {
@@ -3021,6 +3072,9 @@
@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();
@@ -3051,6 +3105,9 @@
@TelephonyManager.RadioPowerReason int reason) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "clearRadioPowerOffForReason");
+
final long identity = Binder.clearCallingIdentity();
try {
boolean result = false;
@@ -3078,6 +3135,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 {
@@ -3103,6 +3163,9 @@
public boolean enableDataConnectivity(String callingPackage) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_DATA, "enableDataConnectivity");
+
final long identity = Binder.clearCallingIdentity();
try {
int subId = SubscriptionManager.getDefaultDataSubscriptionId();
@@ -3124,6 +3187,9 @@
public boolean disableDataConnectivity(String callingPackage) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_DATA, "disableDataConnectivity");
+
final long identity = Binder.clearCallingIdentity();
try {
int subId = SubscriptionManager.getDefaultDataSubscriptionId();
@@ -3142,6 +3208,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);
@@ -3162,6 +3231,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)) {
@@ -3177,6 +3249,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)) {
@@ -3224,6 +3299,10 @@
+ "targeting API level 31+.");
}
}
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getCallStateForSubscription");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subId);
@@ -3241,6 +3320,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);
@@ -3262,6 +3344,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);
@@ -3299,6 +3384,9 @@
? new CellIdentityCdma() : new CellIdentityGsm();
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getCellLocation");
+
WorkSource workSource = getWorkSource(Binder.getCallingUid());
final long identity = Binder.clearCallingIdentity();
try {
@@ -3312,6 +3400,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();
@@ -3382,6 +3473,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);
@@ -3435,6 +3529,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();
@@ -3503,6 +3600,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);
@@ -3541,6 +3640,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_GSM, "getImeiForSlot");
+
final long identity = Binder.clearCallingIdentity();
try {
return phone.getImei();
@@ -3556,6 +3658,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()) {
@@ -3571,6 +3677,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) {
@@ -3605,6 +3714,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CDMA, "getMeidForSlot");
+
final long identity = Binder.clearCallingIdentity();
try {
return phone.getMeid();
@@ -3615,6 +3727,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) {
@@ -3644,6 +3759,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY, "getDeviceSoftwareVersionForSlot");
+
final long identity = Binder.clearCallingIdentity();
try {
return phone.getDeviceSvn();
@@ -3654,6 +3772,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);
@@ -3665,6 +3786,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);
@@ -3676,6 +3800,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);
@@ -3688,6 +3815,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);
@@ -3706,6 +3837,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);
@@ -3826,6 +3961,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);
@@ -3857,6 +3995,10 @@
return -1;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CDMA,
+ "getCdmaEriIconIndexForSubscriber");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -3942,6 +4084,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "getCdmaMdn");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CDMA, "getCdmaMdn");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -3964,6 +4109,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "getCdmaMin");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CDMA, "getCdmaMin");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -3993,6 +4141,9 @@
+ ", configured package: " + authorizedPackage);
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "requestNumberVerification");
+
if (range == null) {
throw new NullPointerException("Range must be non-null");
}
@@ -4007,6 +4158,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();
@@ -4023,6 +4177,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,
@@ -4042,6 +4199,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);
@@ -4064,6 +4224,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getVisualVoicemailPackageName");
+
final long identity = Binder.clearCallingIdentity();
try {
return RemoteVvmTaskManager.getRemotePackage(mApp, subId).getPackageName();
@@ -4077,6 +4240,9 @@
VisualVoicemailSmsFilterSettings settings) {
mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "enableVisualVoicemailSmsFilter");
+
final long identity = Binder.clearCallingIdentity();
try {
VisualVoicemailSmsFilterConfig.enableVisualVoicemailSmsFilter(
@@ -4090,6 +4256,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(
@@ -4133,6 +4302,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);
@@ -4146,6 +4319,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "setVoiceActivationState");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "setVoiceActivationState");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -4167,6 +4343,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "setDataActivationState");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "setDataActivationState");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -4187,6 +4366,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 {
@@ -4207,6 +4389,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 {
@@ -4250,6 +4435,9 @@
*/
@Override
public boolean isConcurrentVoiceAndDataAllowed(int subId) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "isConcurrentVoiceAndDataAllowed");
+
final long identity = Binder.clearCallingIdentity();
try {
return getPhoneFromSubIdOrDefault(subId).isConcurrentVoiceAndDataAllowed();
@@ -4274,6 +4462,9 @@
getDefaultSubscription(), "sendDialerSpecialCode");
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "sendDialerSpecialCode");
+
final long identity = Binder.clearCallingIdentity();
try {
defaultPhone.sendDialerSpecialCode(inputCode);
@@ -4287,6 +4478,10 @@
TelephonyPermissions
.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getNetworkSelectionMode");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getNetworkSelectionMode");
+
final long identity = Binder.clearCallingIdentity();
try {
if (!isActiveSubscription(subId)) {
@@ -4301,6 +4496,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()) {
@@ -4385,6 +4584,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
@@ -4525,6 +4794,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);
@@ -4544,6 +4817,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);
@@ -4607,6 +4884,9 @@
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isAdvancedCallingSettingEnabled");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isAdvancedCallingSettingEnabled");
+
final long token = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -4623,6 +4903,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);
@@ -4644,6 +4928,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);
@@ -4660,6 +4948,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);
@@ -4681,6 +4973,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);
@@ -4697,6 +4993,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);
@@ -4719,6 +5019,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);
@@ -4742,6 +5046,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);
@@ -4764,6 +5072,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);
@@ -4780,6 +5092,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);
@@ -4797,6 +5113,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);
@@ -4817,6 +5137,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);
@@ -4833,6 +5157,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);
@@ -4849,6 +5177,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);
@@ -4865,6 +5197,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);
@@ -4882,6 +5218,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);
@@ -4903,6 +5243,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);
@@ -5028,6 +5372,9 @@
boolean isProvisioned) {
checkModifyPhoneStatePermission(subId, "setRcsProvisioningStatusForCapability");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "setRcsProvisioningStatusForCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsProvisioningController controller = ImsProvisioningController.getInstance();
@@ -5048,6 +5395,9 @@
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getRcsProvisioningStatusForCapability");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "getRcsProvisioningStatusForCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsProvisioningController controller = ImsProvisioningController.getInstance();
@@ -5068,6 +5418,9 @@
boolean isProvisioned) {
checkModifyPhoneStatePermission(subId, "setImsProvisioningStatusForCapability");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "setImsProvisioningStatusForCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsProvisioningController controller = ImsProvisioningController.getInstance();
@@ -5087,6 +5440,9 @@
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getProvisioningStatusForCapability");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "getImsProvisioningStatusForCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsProvisioningController controller = ImsProvisioningController.getInstance();
@@ -5107,6 +5463,9 @@
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isProvisioningRequiredForCapability");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isProvisioningRequiredForCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsProvisioningController controller = ImsProvisioningController.getInstance();
@@ -5127,6 +5486,9 @@
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isProvisioningRequiredForCapability");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isRcsProvisioningRequiredForCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsProvisioningController controller = ImsProvisioningController.getInstance();
@@ -5150,6 +5512,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.
@@ -5190,6 +5555,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.
@@ -5217,6 +5585,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.
@@ -5256,6 +5627,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.
@@ -5329,6 +5704,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);
@@ -5366,6 +5744,9 @@
}
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getDataNetworkTypeForSubscriber");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -5394,6 +5775,9 @@
}
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getVoiceNetworkTypeForSubscriber");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -5421,6 +5805,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);
@@ -5458,6 +5845,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);
@@ -5539,6 +5929,7 @@
@Override
public IccOpenLogicalChannelResponse iccOpenLogicalChannel(
@NonNull IccLogicalChannelRequest request) {
+
Phone phone = getPhoneFromValidIccLogicalChannelRequest(request,
/*message=*/ "iccOpenLogicalChannel");
@@ -5546,6 +5937,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);
}
@@ -5592,6 +5986,9 @@
@Override
public boolean iccCloseLogicalChannel(@NonNull IccLogicalChannelRequest request) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "iccCloseLogicalChannel");
+
Phone phone = getPhoneFromValidIccLogicalChannelRequest(request,
/*message=*/"iccCloseLogicalChannel");
@@ -5639,6 +6036,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="
@@ -5652,6 +6053,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="
@@ -5693,6 +6099,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);
@@ -5706,6 +6116,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="
@@ -5758,6 +6172,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "iccExchangeSimIO");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "iccExchangeSimIO");
+
final long identity = Binder.clearCallingIdentity();
try {
if (DBG) {
@@ -5803,6 +6220,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getForbiddenPlmns");
+
final long identity = Binder.clearCallingIdentity();
try {
if (appType != TelephonyManager.APPTYPE_USIM
@@ -5839,6 +6259,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");
@@ -5868,6 +6291,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);
@@ -5973,6 +6399,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);
@@ -5999,6 +6428,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);
@@ -6019,6 +6451,9 @@
public void resetIms(int slotIndex) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_IMS, "resetIms");
+
final long identity = Binder.clearCallingIdentity();
try {
if (mImsResolver == null) {
@@ -6298,6 +6733,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "setNetworkSelectionModeAutomatic");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "setNetworkSelectionModeAutomatic");
+
final long identity = Binder.clearCallingIdentity();
try {
if (!isActiveSubscription(subId)) {
@@ -6328,6 +6766,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "setNetworkSelectionModeManual");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "setNetworkSelectionModeManual");
+
final long identity = Binder.clearCallingIdentity();
if (!isActiveSubscription(subId)) {
return false;
@@ -6358,6 +6799,9 @@
.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getManualNetworkSelectionPlmn");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getManualNetworkSelectionPlmn");
+
final long identity = Binder.clearCallingIdentity();
try {
if (!isActiveSubscription(subId)) {
@@ -6420,6 +6864,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) {
@@ -6472,6 +6920,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) {
@@ -6505,6 +6957,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);
@@ -6556,6 +7012,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);
@@ -6652,6 +7112,10 @@
}
}
}
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "requestNetworkScan");
+
int callingUid = Binder.getCallingUid();
int callingPid = Binder.getCallingPid();
final long identity = Binder.clearCallingIdentity();
@@ -6727,6 +7191,9 @@
.enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getAllowedNetworkTypesBitmask");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getAllowedNetworkTypesBitmask");
+
final long identity = Binder.clearCallingIdentity();
try {
if (DBG) log("getAllowedNetworkTypesBitmask");
@@ -6751,6 +7218,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);
@@ -6844,6 +7315,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;
@@ -6889,6 +7364,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 {
@@ -6991,6 +7470,9 @@
enforceReadPrivilegedPermission(functionName);
}
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "isDataEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
int phoneId = SubscriptionManager.getPhoneId(subId);
@@ -7037,6 +7519,8 @@
}
}
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "isDataEnabledForReason");
final long identity = Binder.clearCallingIdentity();
try {
@@ -7065,6 +7549,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());
}
@@ -7072,6 +7559,10 @@
@Override
public int getCarrierPrivilegeStatusForUid(int subId, int uid) {
enforceReadPrivilegedPermission("getCarrierPrivilegeStatusForUid");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getCarrierPrivilegeStatusForUid");
+
return getCarrierPrivilegeStatusForUidWithPermission(subId, uid);
}
@@ -7092,6 +7583,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;
}
@@ -7111,6 +7606,11 @@
@Override
public int checkCarrierPrivilegesForPackageAnyPhone(String pkgName) {
enforceReadPrivilegedPermission("checkCarrierPrivilegesForPackageAnyPhone");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+ "checkCarrierPrivilegesForPackageAnyPhone");
+
return checkCarrierPrivilegesForPackageAnyPhoneWithPermission(pkgName);
}
@@ -7139,6 +7639,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();
@@ -7167,6 +7672,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 {
@@ -7183,6 +7693,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;
@@ -7211,6 +7725,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);
@@ -7234,6 +7751,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);
@@ -7256,6 +7776,10 @@
TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(mApp,
subId, "setLine1NumberForDisplayForSubscriber");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+ "setLine1NumberForDisplayForSubscriber");
+
final long identity = Binder.clearCallingIdentity();
try {
final String iccId = getIccId(subId);
@@ -7312,6 +7836,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getLine1NumberForDisplay");
+
final long identity = Binder.clearCallingIdentity();
try {
String iccId = getIccId(subId);
@@ -7439,6 +7966,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(
@@ -7484,6 +8014,9 @@
TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(mApp,
subId, "setOperatorBrandOverride");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "setOperatorBrandOverride");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -7530,6 +8063,9 @@
throw e;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getRadioAccessFamily");
+
final long identity = Binder.clearCallingIdentity();
try {
raf = ProxyController.getInstance().getRadioAccessFamily(phoneId);
@@ -7550,6 +8086,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)) {
@@ -7636,6 +8176,9 @@
final Phone defaultPhone = getDefaultPhone();
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_IMS, "enableVideoCalling");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsManager.getInstance(defaultPhone.getContext(),
@@ -7653,6 +8196,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
@@ -7678,6 +8224,9 @@
return false;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "canChangeDtmfToneLength");
+
final long identity = Binder.clearCallingIdentity();
try {
CarrierConfigManager configManager =
@@ -7696,6 +8245,9 @@
return false;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY, "isWorldPhone");
+
final long identity = Binder.clearCallingIdentity();
try {
CarrierConfigManager configManager =
@@ -7715,6 +8267,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);
@@ -7731,6 +8286,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) {
@@ -7740,8 +8298,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);
@@ -7756,6 +8315,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;
@@ -7845,6 +8410,10 @@
subscriptionId,
"getPhoneAccountHandleForSubscriptionId, " + "subscriptionId: "
+ subscriptionId);
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getPhoneAccountHandleForSubscriptionId");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subscriptionId);
@@ -7912,6 +8481,10 @@
@Override
public void factoryReset(int subId, String callingPackage) {
enforceSettingsPermission();
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY, "factoryReset");
+
if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)) {
return;
}
@@ -7984,6 +8557,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");
@@ -8049,7 +8626,7 @@
*/
private List<SubscriptionInfo> getActiveSubscriptionInfoListPrivileged() {
return getSubscriptionManagerService().getActiveSubscriptionInfoList(
- mApp.getOpPackageName(), mApp.getAttributionTag());
+ mApp.getOpPackageName(), mApp.getAttributionTag(), true/*isForAllProfile*/);
}
private ActivityStatsTechSpecificInfo[] mLastModemActivitySpecificInfo = null;
@@ -8066,6 +8643,10 @@
@Override
public void requestModemActivityInfo(ResultReceiver result) {
enforceModifyPermission();
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY, "requestModemActivityInfo");
+
WorkSource workSource = getWorkSource(Binder.getCallingUid());
final long identity = Binder.clearCallingIdentity();
@@ -8210,6 +8791,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getServiceStateForSubscriber");
+
boolean hasFinePermission = false;
boolean hasCoarsePermission = false;
if (!renounceFineLocationAccess) {
@@ -8289,6 +8873,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);
@@ -8325,6 +8912,9 @@
"setVoicemailRingtoneUri");
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "setVoicemailRingtoneUri");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = PhoneUtils.getPhoneForPhoneAccountHandle(phoneAccountHandle);
@@ -8346,6 +8936,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);
@@ -8382,6 +8975,9 @@
"setVoicemailVibrationEnabled");
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "setVoicemailVibrationEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = PhoneUtils.getPhoneForPhoneAccountHandle(phoneAccountHandle);
@@ -8458,6 +9054,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();
@@ -8515,6 +9115,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();
@@ -8564,6 +9168,10 @@
@TelephonyManager.SetCarrierRestrictionResult
public int setAllowedCarriers(CarrierRestrictionRules carrierRestrictionRules) {
enforceModifyPermission();
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CARRIERLOCK, "setAllowedCarriers");
+
WorkSource workSource = getWorkSource(Binder.getCallingUid());
if (carrierRestrictionRules == null) {
@@ -8590,6 +9198,10 @@
@Override
public CarrierRestrictionRules getAllowedCarriers() {
enforceReadPrivilegedPermission("getAllowedCarriers");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CARRIERLOCK, "getAllowedCarriers");
+
WorkSource workSource = getWorkSource(Binder.getCallingUid());
final long identity = Binder.clearCallingIdentity();
@@ -8620,6 +9232,10 @@
@Override
public void getCarrierRestrictionStatus(IIntegerConsumer callback, String packageName) {
enforceReadPermission("getCarrierRestrictionStatus");
+
+ enforceTelephonyFeatureWithException(packageName,
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getCarrierRestrictionStatus");
+
int carrierId = validateCallerAndGetCarrierId(packageName);
if (carrierId == CarrierAllowListInfo.INVALID_CARRIER_ID) {
Rlog.e(LOG_TAG, "getCarrierRestrictionStatus: caller is not registered");
@@ -8742,6 +9358,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();
@@ -8766,6 +9387,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);
@@ -8828,6 +9453,15 @@
enforceModifyPermission();
}
+ if (reason == TelephonyManager.DATA_ENABLED_REASON_USER && enabled
+ && null != callingPackage && opEnableMobileDataByUser()) {
+ mAppOps.noteOp(AppOpsManager.OPSTR_ENABLE_MOBILE_DATA_BY_USER, Binder.getCallingUid(),
+ callingPackage, null, null);
+ }
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_DATA, "setDataEnabledForReason");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subId);
@@ -8898,6 +9532,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());
@@ -8927,6 +9565,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());
@@ -8964,6 +9607,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();
@@ -8981,6 +9628,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);
@@ -9010,6 +9660,9 @@
return TelephonyManager.RADIO_POWER_UNAVAILABLE;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getRadioPowerState");
+
final long identity = Binder.clearCallingIdentity();
try {
return phone.getRadioPowerState();
@@ -9050,6 +9703,9 @@
mApp, subId, functionName);
}
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "isDataRoamingEnabled");
+
boolean isEnabled = false;
final long identity = Binder.clearCallingIdentity();
try {
@@ -9077,6 +9733,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "setDataRoamingEnabled");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "setDataRoamingEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subId);
@@ -9094,6 +9753,9 @@
.enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isManualNetworkSelectionAllowed");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "isManualNetworkSelectionAllowed");
+
boolean isAllowed = true;
final long identity = Binder.clearCallingIdentity();
try {
@@ -9140,6 +9802,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())) {
@@ -9250,6 +9916,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())) {
@@ -9352,6 +10021,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<>();
@@ -9371,6 +10043,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);
@@ -9381,6 +10056,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();
@@ -9547,6 +10225,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);
@@ -9560,6 +10241,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);
@@ -9574,6 +10258,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);
@@ -9587,6 +10274,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);
@@ -9603,6 +10293,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<>();
@@ -9628,6 +10322,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()) {
@@ -9720,6 +10418,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);
@@ -9737,6 +10438,9 @@
public void notifyOtaEmergencyNumberDbInstalled() {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "notifyOtaEmergencyNumberDbInstalled");
+
final long identity = Binder.clearCallingIdentity();
try {
for (Phone phone: PhoneFactory.getPhones()) {
@@ -9754,6 +10458,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()) {
@@ -9771,6 +10478,9 @@
public void resetOtaEmergencyNumberDbFilePath() {
enforceActiveEmergencySessionPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "resetOtaEmergencyNumberDbFilePath");
+
final long identity = Binder.clearCallingIdentity();
try {
for (Phone phone: PhoneFactory.getPhones()) {
@@ -9811,6 +10521,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);
@@ -9839,6 +10552,9 @@
throw new SecurityException("Requires READ_PHONE_STATE permission.");
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY, "isModemEnabledForSlot");
+
final long identity = Binder.clearCallingIdentity();
try {
try {
@@ -9855,6 +10571,9 @@
public void setMultiSimCarrierRestriction(boolean isMultiSimCarrierRestricted) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CARRIERLOCK, "setMultiSimCarrierRestriction");
+
final long identity = Binder.clearCallingIdentity();
try {
mTelephonySharedPreferences.edit()
@@ -9874,6 +10593,9 @@
return TelephonyManager.MULTISIM_NOT_SUPPORTED_BY_HARDWARE;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "isMultiSimSupported");
+
final long identity = Binder.clearCallingIdentity();
try {
return isMultiSimSupportedInternal();
@@ -9925,6 +10647,10 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, "switchMultiSimConfig");
}
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "switchMultiSimConfig");
+
final long identity = Binder.clearCallingIdentity();
try {
@@ -9942,6 +10668,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;
@@ -9978,6 +10708,11 @@
"doesSwitchMultiSimConfigTriggerReboot")) {
return false;
}
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+ "doesSwitchMultiSimConfigTriggerReboot");
+
final long identity = Binder.clearCallingIdentity();
try {
return mPhoneConfigurationManager.isRebootRequiredForModemConfigChange();
@@ -9998,6 +10733,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 {
@@ -10042,11 +10781,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];
}
/**
@@ -10068,6 +10810,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 {
@@ -10090,6 +10835,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 {
@@ -10107,6 +10855,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);
@@ -10141,6 +10893,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 {
@@ -10159,6 +10915,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);
}
@@ -10206,6 +10966,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 {
@@ -10223,6 +10986,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 {
@@ -10242,6 +11008,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);
@@ -10258,6 +11027,9 @@
boolean enabled) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "setMobileDataPolicyEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subscriptionId);
@@ -10312,10 +11084,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();
@@ -10330,6 +11111,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 {
@@ -10359,6 +11143,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;
@@ -10391,6 +11178,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;
@@ -10461,6 +11251,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");
@@ -10477,6 +11270,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) {
@@ -10493,6 +11290,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
@@ -10652,6 +11453,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. "
@@ -10882,9 +11687,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();
@@ -10913,10 +11725,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();
@@ -10939,10 +11760,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();
@@ -10964,9 +11792,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();
@@ -11380,6 +12215,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)
@@ -11406,6 +12244,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)
@@ -11472,6 +12313,10 @@
@Override
public PhoneCapability getPhoneCapability() {
enforceReadPrivilegedPermission("getPhoneCapability");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY, "getPhoneCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
return mPhoneConfigurationManager.getCurrentPhoneCapability();
@@ -11490,6 +12335,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);
@@ -11510,6 +12358,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();
@@ -11538,6 +12390,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);
@@ -11580,6 +12435,9 @@
throw new SecurityException("purchasePremiumCapability requires permission INTERNET.");
}
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "purchasePremiumCapability");
+
Phone phone = getPhone(subId);
if (phone == null) {
try {
@@ -11715,8 +12573,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
@@ -11792,6 +12651,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.
@@ -11860,6 +12740,10 @@
boolean updateIfNeeded) {
enforceInteractAcrossUsersPermission("getDefaultRespondViaMessageApplication");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_MESSAGING,
+ "getDefaultRespondViaMessageApplication");
+
Context context = getPhoneFromSubIdOrDefault(subId).getContext();
UserHandle userHandle = null;
@@ -11928,6 +12812,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}
@@ -11937,6 +12844,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;
@@ -11966,11 +12876,15 @@
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);
+ TelephonyManager.EmergencyCallDiagnosticParams.Builder edpBuilder =
+ new TelephonyManager.EmergencyCallDiagnosticParams.Builder();
+ edpBuilder
+ .setTelecomDumpSysCollectionEnabled(enableTelecomDump)
+ .setTelephonyDumpSysCollectionEnabled(enableTelephonyDump);
+ if (enableLogcat) {
+ edpBuilder.setLogcatCollectionStartTimeMillis(logcatStartTimestampMillis);
+ }
+ TelephonyManager.EmergencyCallDiagnosticParams edp = edpBuilder.build();
Log.d(LOG_TAG, "persisting with Params " + edp.toString());
DiagnosticDataCollector ddc = new DiagnosticDataCollector(Runtime.getRuntime(),
Executors.newCachedThreadPool(), db,
@@ -12012,6 +12926,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();
@@ -12031,6 +12949,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);
@@ -12086,8 +13008,41 @@
public void requestSatelliteEnabled(int subId, boolean enableSatellite, boolean enableDemoMode,
@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);
+ }
}
/**
@@ -12291,7 +13246,7 @@
*/
@Override
@SatelliteManager.SatelliteResult public int registerForSatelliteModemStateChanged(int subId,
- @NonNull ISatelliteStateCallback callback) {
+ @NonNull ISatelliteModemStateCallback callback) {
enforceSatelliteCommunicationPermission("registerForSatelliteModemStateChanged");
return mSatelliteController.registerForSatelliteModemStateChanged(subId, callback);
}
@@ -12302,15 +13257,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);
}
/**
@@ -12324,10 +13279,10 @@
* @throws SecurityException if the caller doesn't have the required permission.
*/
@Override
- @SatelliteManager.SatelliteResult 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);
}
/**
@@ -12336,15 +13291,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);
}
/**
@@ -12359,10 +13314,9 @@
*
* @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);
}
/**
@@ -12384,12 +13338,12 @@
* @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);
}
/**
@@ -12404,11 +13358,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);
}
@@ -12454,13 +13407,13 @@
*
* @throws SecurityException if the caller doesn't have required permission.
*/
- public void addSatelliteAttachRestrictionForCarrier(int subId,
+ public void addAttachRestrictionForCarrier(int subId,
@SatelliteManager.SatelliteCommunicationRestrictionReason int reason,
@NonNull IIntegerConsumer callback) {
- enforceSatelliteCommunicationPermission("addSatelliteAttachRestrictionForCarrier");
+ enforceSatelliteCommunicationPermission("addAttachRestrictionForCarrier");
final long identity = Binder.clearCallingIdentity();
try {
- mSatelliteController.addSatelliteAttachRestrictionForCarrier(subId, reason, callback);
+ mSatelliteController.addAttachRestrictionForCarrier(subId, reason, callback);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -12477,14 +13430,13 @@
*
* @throws SecurityException if the caller doesn't have required permission.
*/
- public void removeSatelliteAttachRestrictionForCarrier(int subId,
+ public void removeAttachRestrictionForCarrier(int subId,
@SatelliteManager.SatelliteCommunicationRestrictionReason int reason,
@NonNull IIntegerConsumer callback) {
- enforceSatelliteCommunicationPermission("removeSatelliteAttachRestrictionForCarrier");
+ enforceSatelliteCommunicationPermission("removeAttachRestrictionForCarrier");
final long identity = Binder.clearCallingIdentity();
try {
- mSatelliteController.removeSatelliteAttachRestrictionForCarrier(subId, reason,
- callback);
+ mSatelliteController.removeAttachRestrictionForCarrier(subId, reason, callback);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -12500,13 +13452,13 @@
*
* @throws SecurityException if the caller doesn't have the required permission.
*/
- public @NonNull int[] getSatelliteAttachRestrictionReasonsForCarrier(
+ public @NonNull int[] getAttachRestrictionReasonsForCarrier(
int subId) {
- enforceSatelliteCommunicationPermission("getSatelliteAttachRestrictionReasonsForCarrier");
+ enforceSatelliteCommunicationPermission("getAttachRestrictionReasonsForCarrier");
final long identity = Binder.clearCallingIdentity();
try {
Set<Integer> reasonSet =
- mSatelliteController.getSatelliteAttachRestrictionReasonsForCarrier(subId);
+ mSatelliteController.getAttachRestrictionReasonsForCarrier(subId);
return reasonSet.stream().mapToInt(i->i).toArray();
} finally {
Binder.restoreCallingIdentity(identity);
@@ -12534,22 +13486,27 @@
}
/**
- * Registers for NTN signal strength changed from satellite modem.
+ * 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.
+ * @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.
*
- * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
- *
- * @throws SecurityException if the caller doesn't have the required permission.
+ * @throws SecurityException If the caller doesn't have the required permission.
+ * @throws ServiceSpecificException If the callback registration operation fails.
*/
@Override
- @SatelliteManager.SatelliteResult public int registerForNtnSignalStrengthChanged(
- int subId, @NonNull INtnSignalStrengthCallback callback) {
+ public void registerForNtnSignalStrengthChanged(int subId,
+ @NonNull INtnSignalStrengthCallback callback) throws RemoteException {
enforceSatelliteCommunicationPermission("registerForNtnSignalStrengthChanged");
final long identity = Binder.clearCallingIdentity();
try {
- return mSatelliteController.registerForNtnSignalStrengthChanged(subId, callback);
+ mSatelliteController.registerForNtnSignalStrengthChanged(subId, callback);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -12559,7 +13516,8 @@
* 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 provision state changed.
+ * @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)}
*
@@ -12578,6 +13536,50 @@
}
/**
+ * 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);
+ }
+ }
+
+ /**
* This API can be used by only CTS to update satellite vendor service package name.
*
* @param servicePackageName The package name of the satellite vendor service.
@@ -12667,6 +13669,207 @@
}
/**
+ * 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);
+ }
+
+ /**
+ * 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();
+ }
+
+ /**
* Check whether the caller (or self, if not processing an IPC) can read device identifiers.
*
* <p>This method behaves in one of the following ways:
@@ -12717,4 +13920,47 @@
return mCarrierId;
}
}
+
+ /*
+ * 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 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/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 1ee48c4..f7a3640d 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -24,6 +24,8 @@
import static java.util.Map.entry;
import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.net.Uri;
import android.os.Binder;
@@ -65,6 +67,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;
@@ -189,6 +192,15 @@
"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_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 INVALID_ENTRY_ERROR = "An emergency number (only allow '0'-'9', "
+ "'*', '#' or '+') needs to be specified after -a in the command ";
@@ -380,6 +392,16 @@
return handleSetSatellitePointingUiClassNameCommand();
case SET_SATELLITE_DEVICE_ALIGNED_TIMEOUT_DURATION:
return handleSettSatelliteDeviceAlignedTimeoutDuration();
+ 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);
}
@@ -779,6 +801,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() {
@@ -3217,6 +3270,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;
@@ -3281,6 +3383,234 @@
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";
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..76032f8
--- /dev/null
+++ b/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java
@@ -0,0 +1,922 @@
+/*
+ * 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_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.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.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.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;
+
+ 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 mInternalSatelliteAllowResultReceiver;
+ @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);
+ if (s2CellFile != null) {
+ mSatelliteS2CellFile = s2CellFile;
+ }
+ mInternalSatelliteAllowResultReceiver = new ResultReceiver(this) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ handleSatelliteAllowResultFromSatelliteController(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;
+ 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();
+ }
+
+ 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.requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ requestArguments.first, mInternalSatelliteAllowResultReceiver);
+ }
+ }
+
+ 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 handleSatelliteAllowResultFromSatelliteController(
+ int resultCode, Bundle resultData) {
+ logd("handleSatelliteAllowResultFromSatelliteController: resultCode=" + resultCode);
+ synchronized (mLock) {
+ if (resultCode == SATELLITE_RESULT_SUCCESS) {
+ if (resultData.containsKey(KEY_SATELLITE_COMMUNICATION_ALLOWED)) {
+ boolean isSatelliteAllowed = resultData.getBoolean(
+ KEY_SATELLITE_COMMUNICATION_ALLOWED);
+ if (!isSatelliteAllowed) {
+ logd("Satellite is not allowed by modem");
+ sendSatelliteAllowResultToReceivers(resultCode, resultData);
+ } else {
+ checkSatelliteAccessRestrictionForCurrentLocation();
+ }
+ } else {
+ loge("KEY_SATELLITE_COMMUNICATION_ALLOWED does not exist.");
+ sendSatelliteAllowResultToReceivers(resultCode, resultData);
+ }
+ } else if (resultCode == SATELLITE_RESULT_REQUEST_NOT_SUPPORTED) {
+ checkSatelliteAccessRestrictionForCurrentLocation();
+ } 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/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 8dfb787..f3158e6 100644
--- a/src/com/android/phone/settings/RadioInfo.java
+++ b/src/com/android/phone/settings/RadioInfo.java
@@ -1567,8 +1567,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/services/telephony/DisconnectCauseUtil.java b/src/com/android/services/telephony/DisconnectCauseUtil.java
index adb07f9..91ecb93 100644
--- a/src/com/android/services/telephony/DisconnectCauseUtil.java
+++ b/src/com/android/services/telephony/DisconnectCauseUtil.java
@@ -31,7 +31,7 @@
import com.android.internal.telephony.PhoneFactory;
import com.android.phone.ImsUtil;
import com.android.phone.PhoneGlobals;
-import com.android.phone.common.R;
+import com.android.phone.R;
public class DisconnectCauseUtil {
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index ea29b77..efa5278 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -64,6 +64,7 @@
import com.android.internal.telephony.ExponentialBackoff;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.subscription.SubscriptionManagerService;
import com.android.phone.PhoneGlobals;
import com.android.phone.PhoneUtils;
@@ -465,6 +466,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;
@@ -804,6 +814,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.
*
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 86770a1..9fe73e2 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;
@@ -1015,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");
@@ -1304,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.");
@@ -2523,8 +2517,8 @@
}
}
- if (mTelephonyConnectionService.maybeReselectDomain(this,
- mOriginalConnection.getPreciseDisconnectCause(), reasonInfo)) {
+ if (mTelephonyConnectionService.maybeReselectDomain(this, reasonInfo,
+ mShowPreciseFailedCause, mHangupDisconnectCause)) {
clearOriginalConnection();
break;
}
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index cad71c4..8a0382a 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -19,8 +19,8 @@
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.PhoneConstants.PHONE_TYPE_GSM;
import static com.android.internal.telephony.flags.Flags.carrierEnabledSatelliteFlag;
import android.annotation.NonNull;
@@ -71,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;
@@ -88,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;
@@ -640,6 +642,13 @@
}
};
+ private void clearNormalCallDomainSelectionConnection() {
+ if (mDomainSelectionConnection != null) {
+ mDomainSelectionConnection.finishSelection();
+ mDomainSelectionConnection = null;
+ }
+ }
+
/**
* A listener for calls.
*/
@@ -652,17 +661,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;
@@ -749,35 +756,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.");
}
}
});
@@ -1121,6 +1138,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) {
@@ -1145,7 +1163,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)) {
@@ -1389,7 +1407,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);
@@ -2215,23 +2233,11 @@
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()));
@@ -2251,6 +2257,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);
@@ -2280,6 +2305,20 @@
.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);
+ mNormalCallConnection.setTelephonyConnectionDisconnected(mDisconnectCauseFactory
+ .toTelecomDisconnectCause(telephonyDisconnectCause,
+ "Connection is null", phone.getPhoneId()));
+ clearNormalCallDomainSelectionConnection();
+ mNormalCallConnection.close();
+ return;
+ }
+
mNormalCallConnection.setOriginalConnection(connection);
mNormalCallConnection.addTelephonyConnectionListener(mNormalCallConnectionListener);
return;
@@ -2303,10 +2342,7 @@
e.getMessage(), phone.getPhoneId()));
mNormalCallConnection.close();
}
- if (mDomainSelectionConnection != null) {
- mDomainSelectionConnection.finishSelection();
- mDomainSelectionConnection = null;
- }
+ clearNormalCallDomainSelectionConnection();
mNormalCallConnection = null;
}
@@ -2514,15 +2550,19 @@
/**
* 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 (mEmergencyCallDomainSelectionConnection != null) {
@@ -2543,10 +2583,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());
@@ -2554,7 +2591,7 @@
}
}
- return maybeReselectDomainForNormalCall(c, callFailCause, reasonInfo);
+ return maybeReselectDomainForNormalCall(c, reasonInfo, showPreciseCause, overrideCause);
}
private boolean maybeReselectDomainForEmergencyCall(final TelephonyConnection c,
@@ -2695,25 +2732,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);
@@ -2722,10 +2779,7 @@
}
c.removeTelephonyConnectionListener(mTelephonyConnectionListener);
- if (mDomainSelectionConnection != null) {
- mDomainSelectionConnection.finishSelection();
- mDomainSelectionConnection = null;
- }
+ clearNormalCallDomainSelectionConnection();
mNormalCallConnection = null;
Log.d(LOG_TAG, "Reselect call domain not triggered.");
return false;
@@ -3564,6 +3618,11 @@
@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.
@@ -3872,10 +3931,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
@@ -3885,10 +3968,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();
@@ -3913,13 +3997,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);
}
@@ -3929,13 +4016,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()
@@ -3962,6 +4052,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;
@@ -4121,7 +4224,7 @@
TelephonyManagerProxy telephonyManagerProxy) {
Conferenceable c = maybeGetFirstConferenceableFromOtherSubscription(
connections, conferences, outgoingHandle, telephonyManagerProxy);
- if (c != null) {
+ if (c != null && isStateActive(c)) {
onHold(c);
return c;
}
@@ -4171,11 +4274,11 @@
private void handleEmergencyCallStartedForSatelliteSOSMessageRecommender(
@NonNull TelephonyConnection connection, @NonNull Phone phone) {
if (mSatelliteSOSMessageRecommender == null) {
- mSatelliteSOSMessageRecommender = new SatelliteSOSMessageRecommender(
+ mSatelliteSOSMessageRecommender = new SatelliteSOSMessageRecommender(phone.getContext(),
phone.getContext().getMainLooper());
}
connection.addTelephonyConnectionListener(mEmergencyConnectionSatelliteListener);
- mSatelliteSOSMessageRecommender.onEmergencyCallStarted(connection, phone);
+ mSatelliteSOSMessageRecommender.onEmergencyCallStarted(connection);
}
/**
@@ -4194,6 +4297,10 @@
}
ServiceState serviceState = phone.getServiceState();
+ if (serviceState == null) {
+ return false;
+ }
+
if (!serviceState.isUsingNonTerrestrialNetwork()) {
// Device is not connected to satellite
return false;
diff --git a/src/com/android/services/telephony/domainselection/CarrierConfigHelper.java b/src/com/android/services/telephony/domainselection/CarrierConfigHelper.java
new file mode 100644
index 0000000..d39a6b7
--- /dev/null
+++ b/src/com/android/services/telephony/domainselection/CarrierConfigHelper.java
@@ -0,0 +1,207 @@
+/*
+ * 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.domainselection;
+
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.NGRAN;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.SystemProperties;
+import android.preference.PreferenceManager;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.phone.R;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** Helper class to cache carrier configurations. */
+public class CarrierConfigHelper extends Handler {
+ private static final String TAG = "CarrierConfigHelper";
+ private static final boolean DBG = (SystemProperties.getInt("ro.debuggable", 0) == 1);
+
+ @VisibleForTesting
+ public static final String KEY_VONR_EMERGENCY_SUPPORT = "vonr_emergency_support";
+
+ private final Context mContext;
+ private final CarrierConfigManager mConfigManager;
+ private final TelephonyManager mTelephonyManager;
+ private final ArrayMap<Integer, Boolean> mVoNrSupported = new ArrayMap<>();
+
+ private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener =
+ (slotIndex, subId, carrierId, specificCarrierId) -> onCarrierConfigurationChanged(
+ slotIndex, subId, carrierId);
+
+ // For test purpose only
+ private final SharedPreferences mSharedPreferences;
+
+ private List<Integer> mIgnoreNrWhenSimRemoved = null;
+
+ /**
+ * Creates an instance.
+ *
+ * @param context The Context this is associated with.
+ * @param looper The Looper to run the CarrierConfigHelper.
+ */
+ public CarrierConfigHelper(@NonNull Context context, @NonNull Looper looper) {
+ this(context, looper, null);
+ }
+
+ /**
+ * Creates an instance.
+ *
+ * @param context The Context this is associated with.
+ * @param looper The Looper to run the CarrierConfigHelper.
+ * @param sharedPreferences The SharedPreferences instance.
+ */
+ @VisibleForTesting
+ public CarrierConfigHelper(@NonNull Context context, @NonNull Looper looper,
+ @Nullable SharedPreferences sharedPreferences) {
+ super(looper);
+
+ mContext = context;
+ mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ mConfigManager = context.getSystemService(CarrierConfigManager.class);
+ mConfigManager.registerCarrierConfigChangeListener(this::post,
+ mCarrierConfigChangeListener);
+ mSharedPreferences = sharedPreferences;
+
+ readFromSharedPreference();
+ readResourceConfiguration();
+ }
+
+ /**
+ * Returns whether VoNR emergency was supported with the last valid subscription.
+ *
+ * @param slotIndex The SIM slot index.
+ * @return true if VoNR emergency was supported with the last valid subscription.
+ * Otherwise, false.
+ */
+ public boolean isVoNrEmergencySupported(int slotIndex) {
+ return mVoNrSupported.get(Integer.valueOf(slotIndex));
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ default:
+ super.handleMessage(msg);
+ break;
+ }
+ }
+
+ private void readFromSharedPreference() {
+ mVoNrSupported.clear();
+ int modemCount = mTelephonyManager.getActiveModemCount();
+ SharedPreferences sp = (mSharedPreferences != null) ? mSharedPreferences
+ : PreferenceManager.getDefaultSharedPreferences(mContext);
+ for (int i = 0; i < modemCount; i++) {
+ Boolean savedConfig = Boolean.valueOf(
+ sp.getBoolean(KEY_VONR_EMERGENCY_SUPPORT + i, false));
+ mVoNrSupported.put(Integer.valueOf(i), savedConfig);
+ Log.i(TAG, "readFromSharedPreference slot=" + i + ", " + savedConfig);
+ }
+ }
+
+ private void onCarrierConfigurationChanged(int slotIndex, int subId, int carrierId) {
+ Log.i(TAG, "onCarrierConfigurationChanged slotIndex=" + slotIndex
+ + ", subId=" + subId + ", carrierId=" + carrierId);
+
+ if (slotIndex < 0
+ || !SubscriptionManager.isValidSubscriptionId(subId)
+ || mTelephonyManager.getSimState(slotIndex) != TelephonyManager.SIM_STATE_READY) {
+ return;
+ }
+
+ PersistableBundle b = mConfigManager.getConfigForSubId(subId,
+ KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY);
+ if (b.isEmpty()) {
+ Log.e(TAG, "onCarrierConfigurationChanged empty result");
+ return;
+ }
+
+ if (!CarrierConfigManager.isConfigForIdentifiedCarrier(b)) {
+ Log.i(TAG, "onCarrierConfigurationChanged not carrier specific configuration");
+ return;
+ }
+
+ int[] imsRatsConfig = b.getIntArray(
+ KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY);
+ if (imsRatsConfig == null) imsRatsConfig = new int[0];
+ boolean carrierConfig = false;
+ for (int i = 0; i < imsRatsConfig.length; i++) {
+ if (imsRatsConfig[i] == NGRAN) {
+ carrierConfig = true;
+ break;
+ }
+ }
+ if (mIgnoreNrWhenSimRemoved.contains(carrierId)) carrierConfig = false;
+
+ Boolean savedConfig = mVoNrSupported.get(Integer.valueOf(slotIndex));
+ if (carrierConfig == savedConfig) {
+ return;
+ }
+
+ mVoNrSupported.put(Integer.valueOf(slotIndex), Boolean.valueOf(carrierConfig));
+
+ SharedPreferences sp = (mSharedPreferences != null) ? mSharedPreferences
+ : PreferenceManager.getDefaultSharedPreferences(mContext);
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putBoolean(KEY_VONR_EMERGENCY_SUPPORT + slotIndex, carrierConfig);
+ editor.apply();
+
+ Log.i(TAG, "onCarrierConfigurationChanged preference updated slotIndex=" + slotIndex
+ + ", supported=" + carrierConfig);
+ }
+
+ private void readResourceConfiguration() {
+ try {
+ mIgnoreNrWhenSimRemoved = Arrays.stream(mContext.getResources().getIntArray(
+ R.array.config_carriers_ignore_ngran_preference_when_sim_removed))
+ .boxed().collect(Collectors.toList());
+ } catch (Resources.NotFoundException nfe) {
+ Log.e(TAG, "readResourceConfiguration exception=" + nfe);
+ } catch (NullPointerException npe) {
+ Log.e(TAG, "readResourceConfiguration exception=" + npe);
+ }
+ if (mIgnoreNrWhenSimRemoved == null) {
+ mIgnoreNrWhenSimRemoved = new ArrayList<Integer>();
+ }
+ Log.i(TAG, "readResourceConfiguration ignoreNrWhenSimRemoved=" + mIgnoreNrWhenSimRemoved);
+ }
+
+ /** Destroys the instance. */
+ public void destroy() {
+ if (DBG) Log.d(TAG, "destroy");
+ mConfigManager.unregisterCarrierConfigChangeListener(mCarrierConfigChangeListener);
+ }
+}
diff --git a/src/com/android/services/telephony/domainselection/CrossSimRedialingController.java b/src/com/android/services/telephony/domainselection/CrossSimRedialingController.java
index f1bb78c..44904f4 100644
--- a/src/com/android/services/telephony/domainselection/CrossSimRedialingController.java
+++ b/src/com/android/services/telephony/domainselection/CrossSimRedialingController.java
@@ -31,16 +31,19 @@
import android.os.SystemProperties;
import android.telephony.Annotation.PreciseDisconnectCauses;
import android.telephony.CarrierConfigManager;
+import android.telephony.PhoneNumberUtils;
+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 +56,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 +76,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;
}
@@ -242,11 +251,12 @@
continue;
}
- if (mEmergencyNumberHelper.isEmergencyNumber(i, mNumber)) {
- logi("isThereOtherSlot index=" + i + ", found");
+ int subId = SubscriptionManager.getSubscriptionId(i);
+ if (mEmergencyNumberHelper.isEmergencyNumber(subId, mNumber)) {
+ logi("isThereOtherSlot index=" + i + "(" + subId + "), found");
return true;
} else {
- logi("isThereOtherSlot index=" + i + ", not emergency number");
+ logi("isThereOtherSlot index=" + i + "(" + subId + "), not emergency number");
}
}
@@ -278,6 +288,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 00753ee..074fa64 100644
--- a/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
+++ b/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
@@ -53,9 +53,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;
@@ -88,6 +91,7 @@
import android.util.LocalLog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.phone.R;
import java.util.ArrayList;
import java.util.Arrays;
@@ -116,17 +120,7 @@
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;
/**
* Network callback used to determine whether Wi-Fi is connected or not.
@@ -170,6 +164,7 @@
private CancellationSignal mCancelSignal;
+ // Members for carrier configuration
private @RadioAccessNetworkType int[] mImsRatsConfig;
private @RadioAccessNetworkType int[] mCsRatsConfig;
private @RadioAccessNetworkType int[] mImsRoamRatsConfig;
@@ -179,8 +174,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;
@@ -190,6 +183,11 @@
private boolean mRequiresImsRegistration;
private boolean mRequiresVoLteEnabled;
private boolean mLtePreferredAfterNrFailure;
+
+ // Members for states
+ private boolean mIsMonitoringConnectivity;
+ private boolean mWiFiAvailable;
+ private boolean mWasCsfbAfterPsFailure;
private boolean mTryCsWhenPsFails;
private boolean mTryEpsFallback;
private int mModemCount;
@@ -213,12 +211,16 @@
private final PowerManager.WakeLock mPartialWakeLock;
private final CrossSimRedialingController mCrossSimRedialingController;
+ private final CarrierConfigHelper mCarrierConfigHelper;
+ 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 CarrierConfigHelper carrierConfigHelper,
+ @NonNull EmergencyCallbackModeHelper ecbmHelper) {
super(context, slotId, subId, looper, imsStateTracker, destroyListener, TAG);
mImsStateTracker.addBarringInfoListener(this);
@@ -228,6 +230,8 @@
mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mCrossSimRedialingController = csrController;
+ mCarrierConfigHelper = carrierConfigHelper;
+ mEcbmHelper = ecbmHelper;
acquireWakeLock();
}
@@ -366,11 +370,21 @@
// 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()) {
@@ -434,6 +448,7 @@
private void startDomainSelection() {
logi("startDomainSelection modemCount=" + mModemCount);
+ readResourceConfiguration();
updateCarrierConfiguration();
mDomainSelectionRequested = true;
startCrossStackTimer();
@@ -468,6 +483,12 @@
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.
*/
@@ -482,11 +503,7 @@
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 };
- }
+ maybeModifyImsRats();
mCsRatsConfig =
b.getIntArray(KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY);
@@ -556,6 +573,42 @@
}
}
+ /** Adds NGRAN if SIM is absent or locked and the last valid subscription supported NGRAN. */
+ private void maybeModifyImsRats() {
+ if (mCarrierConfigHelper.isVoNrEmergencySupported(getSlotId())
+ && !isSimReady() && mImsRatsConfig.length < 2) {
+ // Default configuration includes only EUTRAN.
+ mImsRatsConfig = new int[] { EUTRAN, NGRAN };
+ mImsRoamRatsConfig = new int[] { EUTRAN, NGRAN };
+ }
+ }
+
+ /**
+ * Caches the resource configuration.
+ */
+ private void readResourceConfiguration() {
+ if (sSimReadyAllowList != null) return;
+ try {
+ sSimReadyAllowList = Arrays.asList(mContext.getResources().getStringArray(
+ R.array.config_countries_require_sim_for_emergency));
+ } catch (Resources.NotFoundException nfe) {
+ loge("readResourceConfiguration exception=" + nfe);
+ } catch (NullPointerException npe) {
+ loge("readResourceConfiguration exception=" + npe);
+ } finally {
+ if (sSimReadyAllowList == null) {
+ sSimReadyAllowList = new ArrayList<String>();
+ }
+ }
+ logi("readResourceConfiguration simReadyCountries=" + sSimReadyAllowList);
+ }
+
+ /** For test purpose only */
+ @VisibleForTesting
+ public void clearResourceConfiguration() {
+ sSimReadyAllowList = null;
+ }
+
private void selectDomain() {
// State updated right after creation.
if (!mDomainSelectionRequested) return;
@@ -581,7 +634,8 @@
return;
}
- if (isWifiPreferred()) {
+ if (isWifiPreferred()
+ || isInEmergencyCallbackModeOnWlan()) {
onWlanSelected();
return;
}
@@ -603,12 +657,17 @@
boolean psInService = isPsInService();
if (!csInService && !psInService) {
+ mCsNetworkType = getSelectableCsNetworkType();
mPsNetworkType = getSelectablePsNetworkType(false);
- logi("selectDomain limited service ps=" + accessNetworkTypeToString(mPsNetworkType));
- if (mPsNetworkType == UNKNOWN) {
- requestScan(true);
- } else {
+ logi("selectDomain limited service ps=" + accessNetworkTypeToString(mPsNetworkType)
+ + ", cs=" + accessNetworkTypeToString(mCsNetworkType));
+ // If NGRAN, request scan to trigger emergency registration.
+ if (mPsNetworkType == EUTRAN) {
onWwanNetworkTypeSelected(mPsNetworkType);
+ } else if (mCsNetworkType != UNKNOWN) {
+ onWwanNetworkTypeSelected(mCsNetworkType);
+ } else {
+ requestScan(true);
}
return;
}
@@ -626,7 +685,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()) {
@@ -637,7 +697,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.
@@ -652,7 +713,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);
@@ -808,6 +870,11 @@
}
}
+ // 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;
}
@@ -1017,13 +1084,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;
}
@@ -1314,7 +1381,7 @@
}
String iso = regResult.getIso();
- if (sAllowOnlyWithSimReady.contains(iso)) {
+ if (sSimReadyAllowList.contains(iso)) {
TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
int simState = tm.getSimState(getSlotId());
if (simState != TelephonyManager.SIM_STATE_READY) {
@@ -1486,10 +1553,21 @@
}
}
+ private boolean isInEmergencyCallbackModeOnWlan() {
+ return mEcbmHelper.isInEmergencyCallbackMode(getSlotId())
+ && mEcbmHelper.getTransportType(getSlotId()) == TRANSPORT_TYPE_WLAN
+ && mEcbmHelper.getDataConnectionState(getSlotId()) == DATA_CONNECTED;
+ }
+
private void selectDomainForTestEmergencyNumber() {
logi("selectDomainForTestEmergencyNumber");
if (isImsRegisteredWithVoiceCapability()) {
- onWwanNetworkTypeSelected(EUTRAN);
+ if (isImsRegisteredOverWifi()
+ || isImsRegisteredOverCrossSim()) {
+ mTransportSelectorCallback.onWlanSelected(mVoWifiOverEmergencyPdn);
+ } else {
+ onWwanNetworkTypeSelected(EUTRAN);
+ }
} else {
onWwanNetworkTypeSelected(UTRAN);
}
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..e42dfe7
--- /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/NormalCallDomainSelector.java b/src/com/android/services/telephony/domainselection/NormalCallDomainSelector.java
index 32c05fa..cd70793 100644
--- a/src/com/android/services/telephony/domainselection/NormalCallDomainSelector.java
+++ b/src/com/android/services/telephony/domainselection/NormalCallDomainSelector.java
@@ -43,6 +43,7 @@
private static final String LOG_TAG = "NCDS";
private boolean mStopDomainSelection = true;
+ private boolean mDestroyed = false;
private ServiceState mServiceState;
private boolean mImsRegStateReceived;
private boolean mMmTelCapabilitiesReceived;
@@ -116,12 +117,16 @@
mImsStateTracker.removeImsStateListener(this);
mSelectionAttributes = null;
mTransportSelectorCallback = null;
+ destroy();
}
@Override
public void destroy() {
- finishSelection();
- super.destroy();
+ logd("destroy");
+ if (!mDestroyed) {
+ mDestroyed = true;
+ super.destroy();
+ }
}
/**
@@ -238,7 +243,8 @@
PersistableBundle config = null;
if (configManager != null) {
- config = configManager.getConfigForSubId(mSelectionAttributes.getSubId());
+ config = configManager.getConfigForSubId(mSelectionAttributes.getSubId(),
+ new String[] {CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL});
}
return (config != null)
@@ -265,7 +271,8 @@
PersistableBundle config = null;
if (configManager != null) {
- config = configManager.getConfigForSubId(mSelectionAttributes.getSubId());
+ config = configManager.getConfigForSubId(mSelectionAttributes.getSubId(),
+ new String[] {CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL});
}
return (config != null)
@@ -297,9 +304,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);
diff --git a/src/com/android/services/telephony/domainselection/OWNERS b/src/com/android/services/telephony/domainselection/OWNERS
new file mode 100644
index 0000000..b9112be
--- /dev/null
+++ b/src/com/android/services/telephony/domainselection/OWNERS
@@ -0,0 +1,8 @@
+# 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
diff --git a/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java b/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
index 3a8fc86..fca5966 100644
--- a/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
+++ b/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
@@ -17,6 +17,7 @@
package com.android.services.telephony.domainselection;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Handler;
@@ -37,6 +38,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 +73,9 @@
@SelectorType int selectorType, boolean isEmergency, @NonNull Looper looper,
@NonNull ImsStateTracker imsStateTracker,
@NonNull DomainSelectorBase.DestroyListener listener,
- @NonNull CrossSimRedialingController crossSimRedialingController);
+ @NonNull CrossSimRedialingController crossSimRedialingController,
+ @NonNull CarrierConfigHelper carrierConfigHelper,
+ @NonNull EmergencyCallbackModeHelper emergencyCallbackModeHelper);
}
private static final class DefaultDomainSelectorFactory implements DomainSelectorFactory {
@@ -80,7 +84,9 @@
@SelectorType int selectorType, boolean isEmergency, @NonNull Looper looper,
@NonNull ImsStateTracker imsStateTracker,
@NonNull DomainSelectorBase.DestroyListener listener,
- @NonNull CrossSimRedialingController crossSimRedialingController) {
+ @NonNull CrossSimRedialingController crossSimRedialingController,
+ @NonNull CarrierConfigHelper carrierConfigHelper,
+ @NonNull EmergencyCallbackModeHelper emergencyCallbackModeHelper) {
DomainSelectorBase selector = null;
logi("create-DomainSelector: slotId=" + slotId + ", subId=" + subId
@@ -91,7 +97,8 @@
case SELECTOR_TYPE_CALLING:
if (isEmergency) {
selector = new EmergencyCallDomainSelector(context, slotId, subId, looper,
- imsStateTracker, listener, crossSimRedialingController);
+ imsStateTracker, listener, crossSimRedialingController,
+ carrierConfigHelper, emergencyCallbackModeHelper);
} else {
selector = new NormalCallDomainSelector(context, slotId, subId, looper,
imsStateTracker, listener);
@@ -187,7 +194,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,18 +202,29 @@
private final DomainSelectorFactory mDomainSelectorFactory;
private Handler mServiceHandler;
private CrossSimRedialingController mCrossSimRedialingController;
+ private CarrierConfigHelper mCarrierConfigHelper;
+ private EmergencyCallbackModeHelper mEmergencyCallbackModeHelper;
- public TelephonyDomainSelectionService(Context context) {
- this(context, ImsStateTracker::new, new DefaultDomainSelectorFactory());
+ /** Default constructor. */
+ public TelephonyDomainSelectionService() {
+ this(ImsStateTracker::new, new DefaultDomainSelectorFactory(), null, null);
}
@VisibleForTesting
- public TelephonyDomainSelectionService(Context context,
+ protected TelephonyDomainSelectionService(
@NonNull ImsStateTrackerFactory imsStateTrackerFactory,
- @NonNull DomainSelectorFactory domainSelectorFactory) {
- mContext = context;
+ @NonNull DomainSelectorFactory domainSelectorFactory,
+ @Nullable CarrierConfigHelper carrierConfigHelper,
+ @Nullable EmergencyCallbackModeHelper ecbmHelper) {
mImsStateTrackerFactory = imsStateTrackerFactory;
mDomainSelectorFactory = domainSelectorFactory;
+ mCarrierConfigHelper = carrierConfigHelper;
+ }
+
+ @Override
+ public void onCreate() {
+ logd("onCreate");
+ mContext = getApplicationContext();
// Create a worker thread for this domain selection service.
getExecutor();
@@ -224,7 +242,13 @@
loge("Adding OnSubscriptionChangedListener failed");
}
- mCrossSimRedialingController = new CrossSimRedialingController(context, getLooper());
+ mCrossSimRedialingController = new CrossSimRedialingController(mContext, getLooper());
+ if (mCarrierConfigHelper == null) {
+ mCarrierConfigHelper = new CarrierConfigHelper(mContext, getLooper());
+ }
+ if (mEmergencyCallbackModeHelper == null) {
+ mEmergencyCallbackModeHelper = new EmergencyCallbackModeHelper(mContext, getLooper());
+ }
logi("TelephonyDomainSelectionService created");
}
@@ -268,6 +292,16 @@
mCrossSimRedialingController = null;
}
+ if (mCarrierConfigHelper != null) {
+ mCarrierConfigHelper.destroy();
+ mCarrierConfigHelper = null;
+ }
+
+ if (mEmergencyCallbackModeHelper != null) {
+ mEmergencyCallbackModeHelper.destroy();
+ mEmergencyCallbackModeHelper = null;
+ }
+
if (mServiceHandler != null) {
mServiceHandler.getLooper().quit();
mServiceHandler = null;
@@ -290,7 +324,7 @@
ImsStateTracker ist = getImsStateTracker(slotId);
DomainSelectorBase selector = mDomainSelectorFactory.create(mContext, slotId, subId,
selectorType, isEmergency, getLooper(), ist, mDestroyListener,
- mCrossSimRedialingController);
+ mCrossSimRedialingController, mCarrierConfigHelper, mEmergencyCallbackModeHelper);
if (selector != null) {
// Ensures that ImsStateTracker is started before selecting the domain if not started
@@ -299,15 +333,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);
+ });
}
/**
@@ -371,6 +411,9 @@
*/
private void handleSubscriptionsChanged() {
SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
+ if (Flags.workProfileApiSplit()) {
+ sm = sm.createForAllUserProfiles();
+ }
List<SubscriptionInfo> subsInfoList =
(sm != null) ? sm.getActiveSubscriptionInfoList() : 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/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/res/layout/activity_SatelliteTestApp.xml b/testapps/TestSatelliteApp/res/layout/activity_SatelliteTestApp.xml
index 90a6f94..0753b82 100644
--- a/testapps/TestSatelliteApp/res/layout/activity_SatelliteTestApp.xml
+++ b/testapps/TestSatelliteApp/res/layout/activity_SatelliteTestApp.xml
@@ -68,7 +68,7 @@
android:id="@+id/TestSatelliteWrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingRight="4dp"
+ android:paddingEnd="4dp"
android:text="@string/TestSatelliteWrapper"/>
</LinearLayout>
</LinearLayout>
diff --git a/testapps/TestSatelliteApp/res/layout/activity_TestSatelliteWrapper.xml b/testapps/TestSatelliteApp/res/layout/activity_TestSatelliteWrapper.xml
index 3365bb9..c136ce7 100644
--- a/testapps/TestSatelliteApp/res/layout/activity_TestSatelliteWrapper.xml
+++ b/testapps/TestSatelliteApp/res/layout/activity_TestSatelliteWrapper.xml
@@ -21,78 +21,84 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
- android:paddingLeft="4dp">
+ 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"/>
<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:orientation="horizontal">
+ <Button
+ android:id="@+id/Back"
+ android:onClick="Back"
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_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/requestNtnSignalStrength"/>
+ android:text="@string/Back"/>
<Button
- android:id="@+id/registerForNtnSignalStrengthChanged"
- android:layout_width="match_parent"
+ 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/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"/>
- <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" />
+ 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>
diff --git a/testapps/TestSatelliteApp/res/values/donottranslate_strings.xml b/testapps/TestSatelliteApp/res/values/donottranslate_strings.xml
index 5ab2475..8ebe5f3 100644
--- a/testapps/TestSatelliteApp/res/values/donottranslate_strings.xml
+++ b/testapps/TestSatelliteApp/res/values/donottranslate_strings.xml
@@ -62,6 +62,8 @@
<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="Back">Back</string>
<string name="ClearLog">Clear Log</string>
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Datagram.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Datagram.java
index 97f676f..015ddcd 100644
--- a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Datagram.java
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Datagram.java
@@ -33,7 +33,7 @@
import android.telephony.satellite.SatelliteDatagram;
import android.telephony.satellite.SatelliteDatagramCallback;
import android.telephony.satellite.SatelliteManager;
-import android.telephony.satellite.SatelliteStateCallback;
+import android.telephony.satellite.SatelliteModemStateCallback;
import android.telephony.satellite.SatelliteTransmissionUpdateCallback;
import android.telephony.satellite.stub.SatelliteResult;
import android.util.Log;
@@ -61,7 +61,7 @@
private SatelliteManager mSatelliteManager;
private SatelliteDatagramCallbackTestApp mDatagramCallback;
- private SatelliteStateCallbackTestApp mStateCallback;
+ private SatelliteModemStateCallbackTestApp mStateCallback;
private SatelliteTransmissionUpdateCallbackTestApp mCallback;
private android.telephony.satellite.stub.SatelliteDatagram mReceivedDatagram;
@@ -75,32 +75,32 @@
super.onCreate(savedInstanceState);
mSatelliteManager = getSystemService(SatelliteManager.class);
mDatagramCallback = new SatelliteDatagramCallbackTestApp();
- mStateCallback = new SatelliteStateCallbackTestApp();
+ mStateCallback = new SatelliteModemStateCallbackTestApp();
mCallback = new SatelliteTransmissionUpdateCallbackTestApp();
mReceivedDatagram = new android.telephony.satellite.stub.SatelliteDatagram();
setContentView(R.layout.activity_Datagram);
findViewById(R.id.startSatelliteTransmissionUpdates)
- .setOnClickListener(this::startSatelliteTransmissionUpdatesApp);
+ .setOnClickListener(this::startTransmissionUpdatesApp);
findViewById(R.id.stopSatelliteTransmissionUpdates)
- .setOnClickListener(this::stopSatelliteTransmissionUpdatesApp);
+ .setOnClickListener(this::stopTransmissionUpdatesApp);
findViewById(R.id.pollPendingSatelliteDatagrams)
- .setOnClickListener(this::pollPendingSatelliteDatagramsApp);
+ .setOnClickListener(this::pollPendingDatagramsApp);
findViewById(R.id.sendSatelliteDatagram)
- .setOnClickListener(this::sendSatelliteDatagramApp);
+ .setOnClickListener(this::sendDatagramApp);
findViewById(R.id.registerForSatelliteDatagram)
- .setOnClickListener(this::registerForSatelliteDatagramApp);
+ .setOnClickListener(this::registerForIncomingDatagramApp);
findViewById(R.id.unregisterForSatelliteDatagram)
- .setOnClickListener(this::unregisterForSatelliteDatagramApp);
+ .setOnClickListener(this::unregisterForIncomingDatagramApp);
findViewById(R.id.showDatagramSendStateTransition)
.setOnClickListener(this::showDatagramSendStateTransitionApp);
findViewById(R.id.showDatagramReceiveStateTransition)
.setOnClickListener(this::showDatagramReceiveStateTransitionApp);
findViewById(R.id.registerForSatelliteModemStateChanged)
- .setOnClickListener(this::registerForSatelliteModemStateChangedApp);
+ .setOnClickListener(this::registerForModemStateChangedApp);
findViewById(R.id.unregisterForSatelliteModemStateChanged)
- .setOnClickListener(this::unregisterForSatelliteModemStateChangedApp);
+ .setOnClickListener(this::unregisterForModemStateChangedApp);
findViewById(R.id.showSatelliteModemStateTransition)
.setOnClickListener(this::showSatelliteModemStateTransitionApp);
@@ -121,7 +121,7 @@
}
}
- protected class SatelliteStateCallbackTestApp implements SatelliteStateCallback {
+ protected class SatelliteModemStateCallbackTestApp implements SatelliteModemStateCallback {
@Override
public void onSatelliteModemStateChanged(int state) {
mModemState = state;
@@ -166,10 +166,10 @@
}
}
- private void startSatelliteTransmissionUpdatesApp(View view) {
+ private void startTransmissionUpdatesApp(View view) {
TextView textView = findViewById(R.id.text_id);
LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
- mSatelliteManager.requestSatelliteEnabled(true, true, Runnable::run, error::offer);
+ mSatelliteManager.requestEnabled(true, true, Runnable::run, error::offer);
TextView showErrorStatusTextView = findViewById(R.id.showErrorStatus);
try {
Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
@@ -186,7 +186,7 @@
return;
}
error.clear();
- mSatelliteManager.startSatelliteTransmissionUpdates(Runnable::run, error::offer, mCallback);
+ mSatelliteManager.startTransmissionUpdates(Runnable::run, error::offer, mCallback);
try {
Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
if (value == null) {
@@ -202,10 +202,10 @@
}
}
- private void stopSatelliteTransmissionUpdatesApp(View view) {
+ private void stopTransmissionUpdatesApp(View view) {
TextView textView = findViewById(R.id.text_id);
LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
- mSatelliteManager.stopSatelliteTransmissionUpdates(mCallback, Runnable::run, error::offer);
+ mSatelliteManager.stopTransmissionUpdates(mCallback, Runnable::run, error::offer);
try {
Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
if (value == null) {
@@ -220,7 +220,7 @@
textView.setText("stopSatelliteTransmissionUpdates exception caught =" + e);
}
}
- private void pollPendingSatelliteDatagramsApp(View view) {
+ private void pollPendingDatagramsApp(View view) {
LinkedBlockingQueue<Integer> resultListener = new LinkedBlockingQueue<>(1);
TextView showErrorStatusTextView = findViewById(R.id.showErrorStatus);
TextView textView = findViewById(R.id.text_id);
@@ -228,7 +228,7 @@
if (SatelliteTestApp.getTestSatelliteService() != null) {
SatelliteTestApp.getTestSatelliteService().sendOnPendingDatagrams();
}
- mSatelliteManager.requestSatelliteEnabled(true, true, Runnable::run, resultListener::offer);
+ mSatelliteManager.requestEnabled(true, true, Runnable::run, resultListener::offer);
try {
Integer value = resultListener.poll(TIMEOUT, TimeUnit.MILLISECONDS);
if (value == null) {
@@ -246,61 +246,61 @@
showErrorStatusTextView.setText("Enable SatelliteService exception caught = " + e);
return;
}
- mSatelliteManager.pollPendingSatelliteDatagrams(Runnable::run, resultListener::offer);
+ 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 pollPendingSatelliteDatagrams with error = "
+ textView.setText("Failed to pollPendingDatagrams with error = "
+ SatelliteErrorUtils.mapError(value));
} else {
- textView.setText("pollPendingSatelliteDatagrams is successful");
+ textView.setText("pollPendingDatagrams is successful");
}
} catch (InterruptedException e) {
- textView.setText("pollPendingSatelliteDatagrams exception caught =" + e);
+ textView.setText("pollPendingDatagrams exception caught =" + e);
}
}
- private void sendSatelliteDatagramApp(View view) {
+ 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.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ 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 sendSatelliteDatagram");
+ textView.setText("Timed out for sendDatagram");
} else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
- textView.setText("Failed to sendSatelliteDatagram with error = "
+ textView.setText("Failed to sendDatagram with error = "
+ SatelliteErrorUtils.mapError(value));
} else {
- textView.setText("sendSatelliteDatagram is successful");
+ textView.setText("sendDatagram is successful");
}
} catch (InterruptedException e) {
- textView.setText("sendSatelliteDatagram exception caught =" + e);
+ textView.setText("sendDatagram exception caught =" + e);
}
}
- private void registerForSatelliteDatagramApp(View view) {
- int result = mSatelliteManager.registerForSatelliteDatagram(Runnable::run,
+ private void registerForIncomingDatagramApp(View view) {
+ int result = mSatelliteManager.registerForIncomingDatagram(Runnable::run,
mDatagramCallback);
TextView textView = findViewById(R.id.text_id);
if (result == 0) {
- textView.setText("registerForSatelliteDatagram is successful");
+ textView.setText("registerForIncomingDatagram is successful");
} else {
- textView.setText("Status for registerForSatelliteDatagram : "
+ textView.setText("Status for registerForIncomingDatagram : "
+ SatelliteErrorUtils.mapError(result));
}
}
- private void unregisterForSatelliteDatagramApp(View view) {
- mSatelliteManager.unregisterForSatelliteDatagram(mDatagramCallback);
+ private void unregisterForIncomingDatagramApp(View view) {
+ mSatelliteManager.unregisterForIncomingDatagram(mDatagramCallback);
TextView textView = findViewById(R.id.text_id);
- textView.setText("unregisterForSatelliteDatagram is successful");
+ textView.setText("unregisterForIncomingDatagram is successful");
}
private void showDatagramSendStateTransitionApp(View view) {
@@ -315,22 +315,22 @@
+ mShowDatagramReceiveStateTransition);
}
- private void registerForSatelliteModemStateChangedApp(View view) {
- int result = mSatelliteManager.registerForSatelliteModemStateChanged(Runnable::run,
+ private void registerForModemStateChangedApp(View view) {
+ int result = mSatelliteManager.registerForModemStateChanged(Runnable::run,
mStateCallback);
TextView textView = findViewById(R.id.text_id);
if (result == 0) {
- textView.setText("registerForSatelliteModemStateChanged is successful");
+ textView.setText("registerForModemStateChanged is successful");
} else {
- textView.setText("Status for registerForSatelliteModemStateChanged : "
+ textView.setText("Status for registerForModemStateChanged : "
+ SatelliteErrorUtils.mapError(result));
}
}
- private void unregisterForSatelliteModemStateChangedApp(View view) {
- mSatelliteManager.unregisterForSatelliteModemStateChanged(mStateCallback);
+ private void unregisterForModemStateChangedApp(View view) {
+ mSatelliteManager.unregisterForModemStateChanged(mStateCallback);
TextView textView = findViewById(R.id.text_id);
- textView.setText("unregisterForSatelliteModemStateChanged is successful");
+ textView.setText("unregisterForModemStateChanged is successful");
}
private void showSatelliteModemStateTransitionApp(View view) {
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/MultipleSendReceive.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/MultipleSendReceive.java
index 3c0b2fd..0e5ab4f 100644
--- a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/MultipleSendReceive.java
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/MultipleSendReceive.java
@@ -66,20 +66,20 @@
SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
mReceivedDatagram, 4);
LinkedBlockingQueue<Integer> resultListener = new LinkedBlockingQueue<>(1);
- mSatelliteManager.requestSatelliteEnabled(true, true, Runnable::run, resultListener::offer);
- mSatelliteManager.pollPendingSatelliteDatagrams(Runnable::run, resultListener::offer);
+ mSatelliteManager.requestEnabled(true, true, Runnable::run, resultListener::offer);
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
mReceivedDatagram, 3);
- mSatelliteManager.pollPendingSatelliteDatagrams(Runnable::run, resultListener::offer);
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
mReceivedDatagram, 2);
- mSatelliteManager.pollPendingSatelliteDatagrams(Runnable::run, resultListener::offer);
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
mReceivedDatagram, 1);
- mSatelliteManager.pollPendingSatelliteDatagrams(Runnable::run, resultListener::offer);
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
mReceivedDatagram, 0);
- mSatelliteManager.pollPendingSatelliteDatagrams(Runnable::run, resultListener::offer);
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
try {
Integer value = resultListener.poll(1000, TimeUnit.MILLISECONDS);
TextView textView = findViewById(R.id.text_id);
@@ -100,15 +100,15 @@
LinkedBlockingQueue<Integer> resultListener = new LinkedBlockingQueue<>(1);
String mText = "This is a test datagram message";
SatelliteDatagram datagram = new SatelliteDatagram(mText.getBytes());
- mSatelliteManager.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
datagram, true, Runnable::run, resultListener::offer);
- mSatelliteManager.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
datagram, true, Runnable::run, resultListener::offer);
- mSatelliteManager.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
datagram, true, Runnable::run, resultListener::offer);
- mSatelliteManager.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
datagram, true, Runnable::run, resultListener::offer);
- mSatelliteManager.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
datagram, true, Runnable::run, resultListener::offer);
try {
Integer value = resultListener.poll(1000, TimeUnit.MILLISECONDS);
@@ -127,27 +127,27 @@
private void multipleSendReceiveSatelliteDatagramApp(View view) {
mSatelliteManager.setDeviceAlignedWithSatellite(true);
LinkedBlockingQueue<Integer> resultListener = new LinkedBlockingQueue<>(1);
- mSatelliteManager.requestSatelliteEnabled(true, true, Runnable::run, resultListener::offer);
+ mSatelliteManager.requestEnabled(true, true, Runnable::run, resultListener::offer);
String mText = "This is a test datagram message";
SatelliteDatagram datagram = new SatelliteDatagram(mText.getBytes());
- mSatelliteManager.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
datagram, true, Runnable::run, resultListener::offer);
SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
mReceivedDatagram, 4);
- mSatelliteManager.pollPendingSatelliteDatagrams(Runnable::run, resultListener::offer);
- mSatelliteManager.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ 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.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
datagram, true, Runnable::run, resultListener::offer);
SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
mReceivedDatagram, 2);
- mSatelliteManager.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
datagram, true, Runnable::run, resultListener::offer);
SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
mReceivedDatagram, 1);
- mSatelliteManager.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
datagram, true, Runnable::run, resultListener::offer);
SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
mReceivedDatagram, 0);
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Provisioning.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Provisioning.java
index 940435e..20c5ef5 100644
--- a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Provisioning.java
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Provisioning.java
@@ -55,15 +55,15 @@
setContentView(R.layout.activity_Provisioning);
findViewById(R.id.provisionSatelliteService)
- .setOnClickListener(this::provisionSatelliteServiceApp);
+ .setOnClickListener(this::provisionServiceApp);
findViewById(R.id.deprovisionSatelliteService)
- .setOnClickListener(this::deprovisionSatelliteServiceApp);
+ .setOnClickListener(this::deprovisionServiceApp);
findViewById(R.id.requestIsSatelliteProvisioned)
- .setOnClickListener(this::requestIsSatelliteProvisionedApp);
+ .setOnClickListener(this::requestIsProvisionedApp);
findViewById(R.id.registerForSatelliteProvisionStateChanged)
- .setOnClickListener(this::registerForSatelliteProvisionStateChangedApp);
+ .setOnClickListener(this::registerForProvisionStateChangedApp);
findViewById(R.id.unregisterForSatelliteProvisionStateChanged)
- .setOnClickListener(this::unregisterForSatelliteProvisionStateChangedApp);
+ .setOnClickListener(this::unregisterForProvisionStateChangedApp);
findViewById(R.id.showCurrentSatelliteProvisionState)
.setOnClickListener(this::showCurrentSatelliteProvisionStateApp);
findViewById(R.id.Back).setOnClickListener(new OnClickListener() {
@@ -84,13 +84,13 @@
}
}
- private void provisionSatelliteServiceApp(View view) {
+ 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.provisionSatelliteService("SATELLITE_TOKEN", testProvisionData,
+ mSatelliteManager.provisionService("SATELLITE_TOKEN", testProvisionData,
cancellationSignal, Runnable::run, error::offer);
try {
Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
@@ -107,10 +107,10 @@
}
}
- private void deprovisionSatelliteServiceApp(View view) {
+ private void deprovisionServiceApp(View view) {
TextView textView = findViewById(R.id.text_id);
LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
- mSatelliteManager.deprovisionSatelliteService("SATELLITE_TOKEN", Runnable::run,
+ mSatelliteManager.deprovisionService("SATELLITE_TOKEN", Runnable::run,
error::offer);
try {
Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
@@ -127,7 +127,7 @@
}
}
- private void requestIsSatelliteProvisionedApp(View view) {
+ private void requestIsProvisionedApp(View view) {
final AtomicReference<Boolean> enabled = new AtomicReference<>();
final AtomicReference<Integer> errorCode = new AtomicReference<>();
OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> mReceiver =
@@ -148,19 +148,19 @@
+ SatelliteErrorUtils.mapError(errorCode.get()));
}
};
- mSatelliteManager.requestIsSatelliteProvisioned(Runnable::run, mReceiver);
+ mSatelliteManager.requestIsProvisioned(Runnable::run, mReceiver);
}
- private void registerForSatelliteProvisionStateChangedApp(View view) {
- int result = mSatelliteManager.registerForSatelliteProvisionStateChanged(Runnable::run,
+ 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 unregisterForSatelliteProvisionStateChangedApp(View view) {
- mSatelliteManager.unregisterForSatelliteProvisionStateChanged(mCallback);
+ private void unregisterForProvisionStateChangedApp(View view) {
+ mSatelliteManager.unregisterForProvisionStateChanged(mCallback);
TextView textView = findViewById(R.id.text_id);
textView.setText("unregisterForSatelliteProvisionStateChanged is successful");
}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteControl.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteControl.java
index 4e339a3..2d01aec 100644
--- a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteControl.java
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteControl.java
@@ -52,16 +52,15 @@
findViewById(R.id.disableSatellite)
.setOnClickListener(this::disableSatelliteApp);
findViewById(R.id.requestIsSatelliteEnabled)
- .setOnClickListener(this::requestIsSatelliteEnabledApp);
+ .setOnClickListener(this::requestIsEnabledApp);
findViewById(R.id.requestIsDemoModeEnabled)
.setOnClickListener(this::requestIsDemoModeEnabledApp);
findViewById(R.id.requestIsSatelliteSupported)
- .setOnClickListener(this::requestIsSatelliteSupportedApp);
+ .setOnClickListener(this::requestIsSupportedApp);
findViewById(R.id.requestSatelliteCapabilities)
- .setOnClickListener(this::requestSatelliteCapabilitiesApp);
+ .setOnClickListener(this::requestCapabilitiesApp);
findViewById(R.id.requestIsSatelliteCommunicationAllowedForCurrentLocation)
- .setOnClickListener(
- this::requestIsSatelliteCommunicationAllowedForCurrentLocationApp);
+ .setOnClickListener(this::requestIsCommunicationAllowedForCurrentLocationApp);
findViewById(R.id.requestTimeForNextSatelliteVisibility)
.setOnClickListener(this::requestTimeForNextSatelliteVisibilityApp);
findViewById(R.id.Back).setOnClickListener(new OnClickListener() {
@@ -74,7 +73,7 @@
private void enableSatelliteApp(View view) {
LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
- mSatelliteManager.requestSatelliteEnabled(true, true, Runnable::run, error::offer);
+ mSatelliteManager.requestEnabled(true, true, Runnable::run, error::offer);
TextView textView = findViewById(R.id.text_id);
try {
Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
@@ -93,7 +92,7 @@
private void disableSatelliteApp(View view) {
LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
- mSatelliteManager.requestSatelliteEnabled(false, true, Runnable::run, error::offer);
+ mSatelliteManager.requestEnabled(false, true, Runnable::run, error::offer);
TextView textView = findViewById(R.id.text_id);
try {
Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
@@ -110,7 +109,7 @@
}
}
- private void requestIsSatelliteEnabledApp(View view) {
+ private void requestIsEnabledApp(View view) {
final AtomicReference<Boolean> enabled = new AtomicReference<>();
final AtomicReference<Integer> errorCode = new AtomicReference<>();
OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> receiver =
@@ -120,9 +119,9 @@
enabled.set(result);
TextView textView = findViewById(R.id.text_id);
if (enabled.get()) {
- textView.setText("requestIsSatelliteEnabled is true");
+ textView.setText("requestIsEnabled is true");
} else {
- textView.setText("Status for requestIsSatelliteEnabled result : "
+ textView.setText("Status for requestIsEnabled result : "
+ enabled.get());
}
}
@@ -131,11 +130,11 @@
public void onError(SatelliteManager.SatelliteException exception) {
errorCode.set(exception.getErrorCode());
TextView textView = findViewById(R.id.text_id);
- textView.setText("Status for requestIsSatelliteEnabled error : "
+ textView.setText("Status for requestIsEnabled error : "
+ SatelliteErrorUtils.mapError(errorCode.get()));
}
};
- mSatelliteManager.requestIsSatelliteEnabled(Runnable::run, receiver);
+ mSatelliteManager.requestIsEnabled(Runnable::run, receiver);
}
private void requestIsDemoModeEnabledApp(View view) {
@@ -166,7 +165,7 @@
mSatelliteManager.requestIsDemoModeEnabled(Runnable::run, receiver);
}
- private void requestIsSatelliteSupportedApp(View view) {
+ private void requestIsSupportedApp(View view) {
final AtomicReference<Boolean> enabled = new AtomicReference<>();
final AtomicReference<Integer> errorCode = new AtomicReference<>();
OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> receiver =
@@ -176,9 +175,9 @@
enabled.set(result);
TextView textView = findViewById(R.id.text_id);
if (enabled.get()) {
- textView.setText("requestIsSatelliteSupported is true");
+ textView.setText("requestIsSupported is true");
} else {
- textView.setText("Status for requestIsSatelliteSupported result : "
+ textView.setText("Status for requestIsSupported result : "
+ enabled.get());
}
}
@@ -187,14 +186,14 @@
public void onError(SatelliteManager.SatelliteException exception) {
errorCode.set(exception.getErrorCode());
TextView textView = findViewById(R.id.text_id);
- textView.setText("Status for requestIsSatelliteSupported error : "
+ textView.setText("Status for requestIsSupported error : "
+ SatelliteErrorUtils.mapError(errorCode.get()));
}
};
- mSatelliteManager.requestIsSatelliteSupported(Runnable::run, receiver);
+ mSatelliteManager.requestIsSupported(Runnable::run, receiver);
}
- private void requestSatelliteCapabilitiesApp(View view) {
+ private void requestCapabilitiesApp(View view) {
final AtomicReference<SatelliteCapabilities> capabilities = new AtomicReference<>();
final AtomicReference<Integer> errorCode = new AtomicReference<>();
OutcomeReceiver<SatelliteCapabilities, SatelliteManager.SatelliteException> receiver =
@@ -203,7 +202,7 @@
public void onResult(SatelliteCapabilities result) {
capabilities.set(result);
TextView textView = findViewById(R.id.text_id);
- textView.setText("Status for requestSatelliteCapabilities result: "
+ textView.setText("Status for requestCapabilities result: "
+ capabilities.get());
}
@@ -211,17 +210,17 @@
public void onError(SatelliteManager.SatelliteException exception) {
errorCode.set(exception.getErrorCode());
TextView textView = findViewById(R.id.text_id);
- textView.setText("Status for requestSatelliteCapabilities error : "
+ textView.setText("Status for requestCapabilities error : "
+ SatelliteErrorUtils.mapError(errorCode.get()));
}
};
- mSatelliteManager.requestSatelliteCapabilities(Runnable::run, receiver);
+ mSatelliteManager.requestCapabilities(Runnable::run, receiver);
}
- private void requestIsSatelliteCommunicationAllowedForCurrentLocationApp(View view) {
+ private void requestIsCommunicationAllowedForCurrentLocationApp(View view) {
final AtomicReference<Boolean> enabled = new AtomicReference<>();
final AtomicReference<Integer> errorCode = new AtomicReference<>();
- String display = "requestIsSatelliteCommunicationAllowedForCurrentLocation";
+ String display = "requestIsCommunicationAllowedForCurrentLocation";
OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> receiver =
new OutcomeReceiver<>() {
@Override
@@ -244,7 +243,7 @@
+ SatelliteErrorUtils.mapError(errorCode.get()));
}
};
- mSatelliteManager.requestIsSatelliteCommunicationAllowedForCurrentLocation(Runnable::run,
+ mSatelliteManager.requestIsCommunicationAllowedForCurrentLocation(Runnable::run,
receiver);
}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SendReceive.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SendReceive.java
index a64aa5d..b5e82b1 100644
--- a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SendReceive.java
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SendReceive.java
@@ -124,7 +124,7 @@
SatelliteDatagram datagram = new SatelliteDatagram(mMessageInput.getBytes());
//Sending Message
- mSatelliteManager.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
datagram, true, Runnable::run, error::offer);
TextView messageStatusTextView = findViewById(R.id.messageStatus);
try {
@@ -139,7 +139,7 @@
+ mEnterMessage.getText().toString());
}
} catch (InterruptedException e) {
- messageStatusTextView.setText("sendSatelliteDatagram exception caught = " + e);
+ messageStatusTextView.setText("sendDatagram exception caught = " + e);
}
}
@@ -148,16 +148,16 @@
byte[] testProvisionData = mMessageOutput.getBytes();
setupForTransferringDatagram(testProvisionData);
- int result = mSatelliteManager.registerForSatelliteDatagram(Runnable::run, mCallback);
+ int result = mSatelliteManager.registerForIncomingDatagram(Runnable::run, mCallback);
TextView showErrorStatusTextView = findViewById(R.id.showErrorStatus);
if (result != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
- showErrorStatusTextView.setText("Status for registerForSatelliteDatagram : "
+ showErrorStatusTextView.setText("Status for registerForIncomingDatagram : "
+ SatelliteErrorUtils.mapError(result));
}
if (SatelliteTestApp.getTestSatelliteService() != null) {
SatelliteTestApp.getTestSatelliteService().sendOnPendingDatagrams();
}
- mSatelliteManager.requestSatelliteEnabled(true, true, Runnable::run, resultListener::offer);
+ mSatelliteManager.requestEnabled(true, true, Runnable::run, resultListener::offer);
try {
Integer value = resultListener.poll(TIMEOUT, TimeUnit.MILLISECONDS);
if (value == null) {
@@ -173,7 +173,7 @@
return;
}
- mSatelliteManager.pollPendingSatelliteDatagrams(Runnable::run, resultListener::offer);
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
try {
Integer value = resultListener.poll(TIMEOUT, TimeUnit.MILLISECONDS);
if (value == null) {
@@ -185,7 +185,7 @@
mMessageStatusTextView.setText("Successfully polled pending messages");
}
} catch (InterruptedException e) {
- mMessageStatusTextView.setText("pollPendingSatelliteDatagrams exception caught = " + e);
+ mMessageStatusTextView.setText("pollPendingDatagrams exception caught = " + e);
}
}
@@ -195,7 +195,7 @@
LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
//Provisioning
- mSatelliteManager.provisionSatelliteService("SATELLITE_TOKEN", provisionData,
+ mSatelliteManager.provisionService("SATELLITE_TOKEN", provisionData,
cancellationSignal, Runnable::run, error::offer);
try {
Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
@@ -233,12 +233,12 @@
+ SatelliteErrorUtils.mapError(errorCode.get()));
}
};
- mSatelliteManager.requestSatelliteCapabilities(Runnable::run, receiver);
+ mSatelliteManager.requestCapabilities(Runnable::run, receiver);
//Satellite Position
SatelliteTransmissionUpdateCallbackTestApp callback =
new SatelliteTransmissionUpdateCallbackTestApp();
- mSatelliteManager.requestSatelliteEnabled(true, true, Runnable::run, error::offer);
+ mSatelliteManager.requestEnabled(true, true, Runnable::run, error::offer);
try {
Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
if (value == null) {
@@ -254,7 +254,7 @@
}
error.clear();
- mSatelliteManager.startSatelliteTransmissionUpdates(Runnable::run, error::offer, callback);
+ mSatelliteManager.startTransmissionUpdates(Runnable::run, error::offer, callback);
// Position update
android.telephony.satellite.stub.PointingInfo pointingInfo =
new android.telephony.satellite.stub.PointingInfo();
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteService.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteService.java
index 929a2e5..9bea30c 100644
--- a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteService.java
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteService.java
@@ -330,14 +330,14 @@
if (mLocalListener != null) {
runWithExecutor(() -> mLocalListener.onPollPendingSatelliteDatagrams());
} else {
- loge("pollPendingSatelliteDatagrams: mLocalListener is null");
+ loge("pollPendingDatagrams: mLocalListener is null");
}
}
@Override
public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isEmergency,
@NonNull IIntegerConsumer errorCallback) {
- logd("sendSatelliteDatagram: mErrorCode=" + mErrorCode);
+ logd("sendDatagram: mErrorCode=" + mErrorCode);
if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
runWithExecutor(() -> errorCallback.accept(mErrorCode));
} else {
@@ -347,7 +347,7 @@
if (mLocalListener != null) {
runWithExecutor(() -> mLocalListener.onSendSatelliteDatagram(datagram, isEmergency));
} else {
- loge("sendSatelliteDatagram: mLocalListener is null");
+ loge("sendDatagram: mLocalListener is null");
}
}
@@ -365,7 +365,7 @@
@Override
public void requestIsSatelliteCommunicationAllowedForCurrentLocation(
@NonNull IIntegerConsumer errorCallback, @NonNull IBooleanConsumer callback) {
- logd("requestIsSatelliteCommunicationAllowedForCurrentLocation: mErrorCode=" + mErrorCode);
+ logd("requestIsCommunicationAllowedForCurrentLocation: mErrorCode=" + mErrorCode);
if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
runWithExecutor(() -> errorCallback.accept(mErrorCode));
return;
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteWrapper.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteWrapper.java
index 13b31f3..e4c2005 100644
--- a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteWrapper.java
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteWrapper.java
@@ -25,6 +25,7 @@
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;
@@ -39,7 +40,6 @@
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
-
/**
* Activity related to SatelliteControl APIs for satellite.
*/
@@ -51,7 +51,9 @@
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
private SatelliteManagerWrapper mSatelliteManagerWrapper;
private NtnSignalStrengthCallback mNtnSignalStrengthCallback = null;
+ private SatelliteCapabilitiesCallbackWrapper mSatelliteCapabilitiesCallback;
private SubscriptionManager mSubscriptionManager;
+
private ListView mLogListView;
@Override
@@ -69,6 +71,10 @@
.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.Back).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
@@ -99,10 +105,17 @@
protected void onDestroy() {
super.onDestroy();
- if (mSatelliteManagerWrapper != null && mNtnSignalStrengthCallback != null) {
- Log.d(TAG, "unregisterForNtnSignalStrengthChanged()");
- mSatelliteManagerWrapper.unregisterForNtnSignalStrengthChanged(
- mNtnSignalStrengthCallback);
+ if (mSatelliteManagerWrapper != null) {
+ if (mNtnSignalStrengthCallback != null) {
+ Log.d(TAG, "unregisterForNtnSignalStrengthChanged()");
+ mSatelliteManagerWrapper.unregisterForNtnSignalStrengthChanged(
+ mNtnSignalStrengthCallback);
+ }
+ if (mSatelliteCapabilitiesCallback != null) {
+ Log.d(TAG, "unregisterForCapabilitiesChanged()");
+ mSatelliteManagerWrapper.unregisterForCapabilitiesChanged(
+ mSatelliteCapabilitiesCallback);
+ }
}
}
@@ -131,7 +144,13 @@
}
};
- mSatelliteManagerWrapper.requestNtnSignalStrength(mExecutor, receiver);
+ try {
+ mSatelliteManagerWrapper.requestNtnSignalStrength(mExecutor, receiver);
+ } catch (SecurityException ex) {
+ String errorMessage = "requestNtnSignalStrength: " + ex.getMessage();
+ Log.d(TAG, errorMessage);
+ addLogMessage(errorMessage);
+ }
}
private void registerForNtnSignalStrengthChanged(View view) {
@@ -141,12 +160,14 @@
Log.d(TAG, "create new NtnSignalStrengthCallback instance.");
mNtnSignalStrengthCallback = new NtnSignalStrengthCallback();
}
- int result = mSatelliteManagerWrapper.registerForNtnSignalStrengthChanged(mExecutor,
- mNtnSignalStrengthCallback);
- if (result != SatelliteManagerWrapper.SATELLITE_RESULT_SUCCESS) {
- String onError = translateResultCodeToString(result);
- Log.d(TAG, onError);
- addLogMessage(onError);
+
+ try {
+ mSatelliteManagerWrapper.registerForNtnSignalStrengthChanged(mExecutor,
+ mNtnSignalStrengthCallback);
+ } catch (Exception ex) {
+ String errorMessage = "registerForNtnSignalStrengthChanged: " + ex.getMessage();
+ Log.d(TAG, errorMessage);
+ addLogMessage(errorMessage);
mNtnSignalStrengthCallback = null;
}
}
@@ -191,6 +212,42 @@
}
}
+ private void registerForCapabilitiesChanged(View view) {
+ addLogMessage("registerForCapabilitiesChanged");
+ Log.d(TAG, "registerForCapabilitiesChanged()");
+ if (mSatelliteCapabilitiesCallback == null) {
+ mSatelliteCapabilitiesCallback =
+ SatelliteCapabilities -> {
+ String message = "Received SatelliteCapabillities : "
+ + SatelliteCapabilities;
+ Log.d(TAG, message);
+ runOnUiThread(() -> addLogMessage(message));
+ };
+ }
+
+ int result = mSatelliteManagerWrapper.registerForCapabilitiesChanged(mExecutor,
+ mSatelliteCapabilitiesCallback);
+ if (result != SatelliteManagerWrapper.SATELLITE_RESULT_SUCCESS) {
+ String onError = translateResultCodeToString(result);
+ Log.d(TAG, onError);
+ addLogMessage(onError);
+ mSatelliteCapabilitiesCallback = null;
+ }
+ }
+
+ private void unregisterForCapabilitiesChanged(View view) {
+ addLogMessage("unregisterForCapabilitiesChanged");
+ Log.d(TAG, "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(
@@ -250,6 +307,8 @@
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;
}
diff --git a/tests/Android.bp b/tests/Android.bp
index d2288d0..6914839 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -50,6 +50,10 @@
"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/phone/CarrierConfigLoaderTest.java b/tests/src/com/android/phone/CarrierConfigLoaderTest.java
index bd2e4f7..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;
@@ -56,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;
@@ -76,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;
@@ -92,6 +103,7 @@
@Mock SubscriptionManagerService mSubscriptionManagerService;
@Mock SharedPreferences mSharedPreferences;
@Mock TelephonyRegistryManager mTelephonyRegistryManager;
+ @Mock FeatureFlags mFeatureFlags;
private TelephonyManager mTelephonyManager;
private CarrierConfigLoader mCarrierConfigLoader;
@@ -118,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();
@@ -141,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.
@@ -412,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);
diff --git a/tests/src/com/android/phone/DiagnosticDataCollectorTest.java b/tests/src/com/android/phone/DiagnosticDataCollectorTest.java
index e0d89bc..2ca04b9 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,9 +95,10 @@
@Test
public void testPersistForTelecomDumpsys() throws IOException, InterruptedException {
+ TelephonyManager.EmergencyCallDiagnosticParams.Builder callDiagnosticBuilder =
+ new TelephonyManager.EmergencyCallDiagnosticParams.Builder();
TelephonyManager.EmergencyCallDiagnosticParams dp =
- new TelephonyManager.EmergencyCallDiagnosticParams();
- dp.setTelecomDumpSysCollection(true);
+ callDiagnosticBuilder.setTelecomDumpSysCollectionEnabled(true).build();
mDiagnosticDataCollector.persistEmergencyDianosticData(mConfig, dp, "test_tag_telecom");
verifyCmdAndDropboxTag(TELECOM_DUMPSYS_COMMAND, "test_tag_telecom", false);
@@ -104,9 +106,10 @@
@Test
public void testPersistForTelephonyDumpsys() throws IOException, InterruptedException {
+ TelephonyManager.EmergencyCallDiagnosticParams.Builder callDiagnosticBuilder =
+ new TelephonyManager.EmergencyCallDiagnosticParams.Builder();
TelephonyManager.EmergencyCallDiagnosticParams dp =
- new TelephonyManager.EmergencyCallDiagnosticParams();
- dp.setTelephonyDumpSysCollection(true);
+ callDiagnosticBuilder.setTelephonyDumpSysCollectionEnabled(true).build();
mDiagnosticDataCollector.persistEmergencyDianosticData(mConfig, dp, "test_tag_telephony");
verifyCmdAndDropboxTag(TELEPHONY_DUMPSYS_COMMAND, "test_tag_telephony", false);
@@ -114,9 +117,11 @@
@Test
public void testPersistForLogcat() throws IOException, InterruptedException {
+ TelephonyManager.EmergencyCallDiagnosticParams.Builder callDiagnosticBuilder =
+ new TelephonyManager.EmergencyCallDiagnosticParams.Builder();
TelephonyManager.EmergencyCallDiagnosticParams dp =
- new TelephonyManager.EmergencyCallDiagnosticParams();
- dp.setLogcatCollection(true, System.currentTimeMillis());
+ callDiagnosticBuilder.setLogcatCollectionStartTimeMillis(
+ SystemClock.elapsedRealtime()).build();
mDiagnosticDataCollector.persistEmergencyDianosticData(mConfig, dp, "test_tag_logcat");
verifyCmdAndDropboxTag(LOGCAT_BINARY, "test_tag_logcat", true);
diff --git a/tests/src/com/android/phone/PhoneInterfaceManagerTest.java b/tests/src/com/android/phone/PhoneInterfaceManagerTest.java
index bea4271..2d338f2 100644
--- a/tests/src/com/android/phone/PhoneInterfaceManagerTest.java
+++ b/tests/src/com/android/phone/PhoneInterfaceManagerTest.java
@@ -20,6 +20,7 @@
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.Mockito.doNothing;
@@ -31,9 +32,13 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+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;
@@ -47,11 +52,17 @@
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.Locale;
/**
@@ -59,9 +70,14 @@
*/
@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;
@@ -69,14 +85,19 @@
Phone mPhone;
@Mock
FeatureFlags mFeatureFlags;
-
+ @Mock
+ PackageManager mPackageManager;
@Mock
private SubscriptionManagerService mSubscriptionManagerService;
+ @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
@@ -87,7 +108,15 @@
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());
}
@Test
@@ -264,6 +293,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.
*/
@@ -286,4 +417,125 @@
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";
+ String error = "";
+ try {
+ mPhoneInterfaceManager.setDataEnabledForReason(1,
+ TelephonyManager.DATA_ENABLED_REASON_USER, true, 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();
+ }
+
+ String appop = "ENABLE_MOBILE_DATA_BY_USER";
+ assertTrue("expected error to contain " + packageName + " but it didn't: " + error,
+ error.contains(packageName));
+ assertTrue("expected error to contain " + appop + " but it didn't: " + error,
+ error.contains(appop));
+ }
+
+ @Test
+ public void notifyEnableDataWithAppOps_enableByCarrier_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_CARRIER, true, 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_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/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..c44b347
--- /dev/null
+++ b/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java
@@ -0,0 +1,658 @@
+/*
+ * 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_ERROR;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+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.doNothing;
+import static org.mockito.Mockito.never;
+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.Bundle;
+import android.os.CancellationSignal;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.telecom.TelecomManager;
+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.SatelliteController;
+
+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.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 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<ResultReceiver> mResultReceiverFromSatelliteControllerCaptor;
+ 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);
+ }
+ }
+ };
+
+ private boolean mQueriedSatelliteAllowed2 = false;
+ private int mQueriedSatelliteAllowedResultCode2 = SATELLITE_RESULT_SUCCESS;
+ private Semaphore mSatelliteAllowedSemaphore2 = new Semaphore(0);
+ private ResultReceiver mSatelliteAllowedReceiver2 = new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ mQueriedSatelliteAllowedResultCode2 = resultCode;
+ if (resultCode == SATELLITE_RESULT_SUCCESS) {
+ if (resultData.containsKey(KEY_SATELLITE_COMMUNICATION_ALLOWED)) {
+ mQueriedSatelliteAllowed2 = resultData.getBoolean(
+ KEY_SATELLITE_COMMUNICATION_ALLOWED);
+ } else {
+ logd("KEY_SATELLITE_COMMUNICATION_ALLOWED does not exist.");
+ mQueriedSatelliteAllowed2 = false;
+ }
+ } else {
+ logd("mSatelliteAllowedReceiver2: resultCode=" + resultCode);
+ mQueriedSatelliteAllowed2 = false;
+ }
+ try {
+ mSatelliteAllowedSemaphore2.release();
+ } catch (Exception ex) {
+ fail("mSatelliteAllowedReceiver2: 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(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);
+ doNothing().when(mMockSatelliteController)
+ .requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ anyInt(), any(ResultReceiver.class));
+
+ 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, but SatelliteController returns error for the query
+ when(mMockFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+ mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
+ SUB_ID, mSatelliteAllowedReceiver);
+ mTestableLooper.processAllMessages();
+ verify(mMockSatelliteController).requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ anyInt(), mResultReceiverFromSatelliteControllerCaptor.capture());
+
+ clearInvocations(mMockSatelliteController);
+ mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
+ SUB_ID, mSatelliteAllowedReceiver2);
+ mTestableLooper.processAllMessages();
+ verify(mMockSatelliteController, never())
+ .requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ anyInt(), any(ResultReceiver.class));
+
+ sendSatelliteAllowResultFromSatelliteController(SATELLITE_RESULT_ERROR, null);
+ assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
+ mSatelliteAllowedSemaphore, 1));
+ assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
+ mSatelliteAllowedSemaphore2, 1));
+ assertEquals(SATELLITE_RESULT_ERROR, mQueriedSatelliteAllowedResultCode);
+ assertEquals(SATELLITE_RESULT_ERROR, mQueriedSatelliteAllowedResultCode2);
+ assertFalse(mQueriedSatelliteAllowed);
+ assertFalse(mQueriedSatelliteAllowed2);
+
+ // SatelliteController returns success result but the result bundle does not have
+ // KEY_SATELLITE_COMMUNICATION_ALLOWED
+ clearAllInvocations();
+ mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
+ SUB_ID, mSatelliteAllowedReceiver);
+ mTestableLooper.processAllMessages();
+ verify(mMockSatelliteController).requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ anyInt(), mResultReceiverFromSatelliteControllerCaptor.capture());
+ sendSatelliteAllowResultFromSatelliteController(SATELLITE_RESULT_SUCCESS, null);
+ assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
+ mSatelliteAllowedSemaphore, 1));
+ assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteAllowedResultCode);
+ assertFalse(mQueriedSatelliteAllowed);
+
+ // SatelliteController returns disallowed result
+ clearAllInvocations();
+ mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
+ SUB_ID, mSatelliteAllowedReceiver);
+ mTestableLooper.processAllMessages();
+ verify(mMockSatelliteController).requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ anyInt(), mResultReceiverFromSatelliteControllerCaptor.capture());
+ sendSatelliteAllowResultFromSatelliteController(SATELLITE_RESULT_SUCCESS, false);
+ assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
+ mSatelliteAllowedSemaphore, 1));
+ assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteAllowedResultCode);
+ assertFalse(mQueriedSatelliteAllowed);
+
+ // SatelliteController returns allowed result. Network country codes are available, but one
+ // country code is not in the allowed list
+ clearAllInvocations();
+ when(mMockCountryDetector.getCurrentNetworkCountryIso()).thenReturn(listOf("US", "IN"));
+ mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
+ SUB_ID, mSatelliteAllowedReceiver);
+ mTestableLooper.processAllMessages();
+ verify(mMockSatelliteController).requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ anyInt(), mResultReceiverFromSatelliteControllerCaptor.capture());
+ sendSatelliteAllowResultFromSatelliteController(SATELLITE_RESULT_SUCCESS, true);
+ assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
+ mSatelliteAllowedSemaphore, 1));
+ assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteAllowedResultCode);
+ assertFalse(mQueriedSatelliteAllowed);
+
+ // SatelliteController returns allowed result. Network country codes are available, and all
+ // country codes are in the allowed list
+ clearAllInvocations();
+ when(mMockCountryDetector.getCurrentNetworkCountryIso()).thenReturn(listOf("US", "CA"));
+ mSatelliteAccessControllerUT.requestIsCommunicationAllowedForCurrentLocation(
+ SUB_ID, mSatelliteAllowedReceiver);
+ mTestableLooper.processAllMessages();
+ verify(mMockSatelliteController).requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ anyInt(), mResultReceiverFromSatelliteControllerCaptor.capture());
+ sendSatelliteAllowResultFromSatelliteController(SATELLITE_RESULT_SUCCESS, true);
+ assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(
+ mSatelliteAllowedSemaphore, 1));
+ assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteAllowedResultCode);
+ assertTrue(mQueriedSatelliteAllowed);
+
+ // SatelliteController returns allowed result. 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();
+ verify(mMockSatelliteController).requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ anyInt(), mResultReceiverFromSatelliteControllerCaptor.capture());
+ sendSatelliteAllowResultFromSatelliteController(SATELLITE_RESULT_SUCCESS, true);
+ 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);
+
+ // SatelliteController returns allowed result. 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();
+ verify(mMockSatelliteController).requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ anyInt(), mResultReceiverFromSatelliteControllerCaptor.capture());
+ sendSatelliteAllowResultFromSatelliteController(SATELLITE_RESULT_SUCCESS, true);
+ 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();
+ verify(mMockSatelliteController).requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ anyInt(), mResultReceiverFromSatelliteControllerCaptor.capture());
+ sendSatelliteAllowResultFromSatelliteController(SATELLITE_RESULT_SUCCESS, true);
+ 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);
+
+ // SatelliteController returns allowed result. 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(mMockSatelliteController).requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ anyInt(), mResultReceiverFromSatelliteControllerCaptor.capture());
+ sendSatelliteAllowResultFromSatelliteController(SATELLITE_RESULT_SUCCESS, true);
+ 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);
+
+ // SatelliteController returns allowed result. 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(mMockSatelliteController).requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ anyInt(), mResultReceiverFromSatelliteControllerCaptor.capture());
+ sendSatelliteAllowResultFromSatelliteController(SATELLITE_RESULT_SUCCESS, true);
+ 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);
+ }
+
+ 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 sendSatelliteAllowResultFromSatelliteController(
+ int resultCode, Boolean satelliteAllowed) {
+ Bundle bundle = null;
+ if (resultCode == SATELLITE_RESULT_SUCCESS) {
+ bundle = new Bundle();
+ if (satelliteAllowed != null) {
+ bundle.putBoolean(KEY_SATELLITE_COMMUNICATION_ALLOWED, satelliteAllowed);
+ }
+ }
+ mResultReceiverFromSatelliteControllerCaptor.getValue().send(resultCode, bundle);
+ mTestableLooper.processAllMessages();
+ }
+
+ private void sendLocationRequestResult(Location location) {
+ mLocationRequestConsumerCaptor.getValue().accept(location);
+ mTestableLooper.processAllMessages();
+ }
+
+ @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/services/telephony/DisconnectCauseUtilTest.java b/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java
index 16a5cdb..e5f7fd3 100644
--- a/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java
+++ b/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java
@@ -41,7 +41,7 @@
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;
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
index 8027969..129b6f4 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
@@ -303,7 +303,7 @@
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))
@@ -319,6 +319,7 @@
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
@@ -1335,7 +1336,7 @@
// This shouldn't happen
fail();
}
- verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), any());
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
}
/**
@@ -1447,7 +1448,7 @@
// This shouldn't happen
fail();
}
- verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), any());
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
}
/**
@@ -1816,7 +1817,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);
@@ -1829,7 +1830,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);
}
@@ -1842,7 +1843,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);
}
@@ -1854,7 +1855,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);
}
@@ -1868,14 +1869,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
@@ -1886,10 +1887,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
@@ -1952,8 +1969,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
@@ -1962,13 +1979,34 @@
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
@@ -2107,7 +2145,7 @@
.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));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
@@ -2137,7 +2175,7 @@
.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));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
@@ -2168,7 +2206,7 @@
verify(mEmergencyStateTracker, times(1))
.startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
- verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
verify(mDomainSelectionResolver, times(0))
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
verify(mEmergencyCallDomainSelectionConnection, times(0))
@@ -2195,7 +2233,8 @@
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);
@@ -2223,7 +2262,8 @@
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);
@@ -2264,7 +2304,7 @@
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);
@@ -2305,7 +2345,7 @@
.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));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
@@ -2346,7 +2386,7 @@
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);
@@ -2362,132 +2402,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());
- doReturn(Arrays.asList(emergencyNumber)).when(mEmergencyNumberTracker).getEmergencyNumbers(
- 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());
- doReturn(Arrays.asList(emergencyNumber)).when(mEmergencyNumberTracker).getEmergencyNumbers(
- 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());
- doReturn(Arrays.asList(emergencyNumber)).when(mEmergencyNumberTracker).getEmergencyNumbers(
- 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)
@@ -2503,46 +2494,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());
- doReturn(Arrays.asList(emergencyNumber)).when(mEmergencyNumberTracker).getEmergencyNumbers(
- 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();
@@ -2557,48 +2529,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());
- doReturn(Arrays.asList(emergencyNumber)).when(mEmergencyNumberTracker).getEmergencyNumbers(
- 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();
@@ -2613,9 +2566,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
@@ -2636,14 +2591,14 @@
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));
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));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
@@ -2677,14 +2632,14 @@
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));
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));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
@@ -2906,7 +2861,8 @@
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
@@ -2943,12 +2899,12 @@
.startEmergencyCall(any(), anyString(), 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));
verify(mEmergencyStateTracker)
.startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
- verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
// dialing is canceled
mTestConnectionService.onLocalHangup(c);
@@ -2985,8 +2941,8 @@
.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());
@@ -3016,7 +2972,7 @@
.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));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
verify(mPhone0).dial(anyString(), any(), any());
@@ -3161,7 +3117,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());
}
@@ -3180,7 +3137,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());
}
@@ -3208,7 +3166,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);
@@ -3233,7 +3191,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);
@@ -3323,6 +3281,52 @@
}
@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);
@@ -3477,6 +3481,50 @@
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 {
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/CarrierConfigHelperTest.java b/tests/src/com/android/services/telephony/domainselection/CarrierConfigHelperTest.java
new file mode 100644
index 0000000..8f51dab
--- /dev/null
+++ b/tests/src/com/android/services/telephony/domainselection/CarrierConfigHelperTest.java
@@ -0,0 +1,276 @@
+/*
+ * 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.domainselection;
+
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.EUTRAN;
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.NGRAN;
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY;
+
+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.anyBoolean;
+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.verify;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+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.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Unit tests for CarrierConfigHelper
+ */
+public class CarrierConfigHelperTest {
+ private static final String TAG = "CarrierConfigHelperTest";
+
+ 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 TEST_SIM_CARRIER_ID = 1911;
+
+ @Mock private SharedPreferences mSharedPreferences;
+ @Mock private SharedPreferences.Editor mEditor;
+ @Mock private Resources mResources;
+
+ private Context mContext;
+ private HandlerThread mHandlerThread;
+ private TestableLooper mLooper;
+ private CarrierConfigHelper mCarrierConfigHelper;
+ private CarrierConfigManager mCarrierConfigManager;
+ private TelephonyManager mTelephonyManager;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext = new TestContext() {
+ @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 Resources getResources() {
+ return mResources;
+ }
+ };
+
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ mHandlerThread = new HandlerThread("CarrierConfigHelperTest");
+ mHandlerThread.start();
+
+ try {
+ mLooper = new TestableLooper(mHandlerThread.getLooper());
+ } catch (Exception e) {
+ logd("Unable to create looper from handler.");
+ }
+
+ doReturn(mEditor).when(mSharedPreferences).edit();
+
+ mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+ doReturn(2).when(mTelephonyManager).getActiveModemCount();
+ doReturn(TelephonyManager.SIM_STATE_READY)
+ .when(mTelephonyManager).getSimState(anyInt());
+
+ doReturn(new int[] { TEST_SIM_CARRIER_ID }).when(mResources).getIntArray(anyInt());
+
+ mCarrierConfigHelper = new CarrierConfigHelper(mContext, mHandlerThread.getLooper(),
+ mSharedPreferences);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mCarrierConfigHelper != null) {
+ mCarrierConfigHelper.destroy();
+ mCarrierConfigHelper = 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());
+ assertFalse(mCarrierConfigHelper.isVoNrEmergencySupported(SLOT_0));
+ }
+
+ @Test
+ public void testCarrierConfigNotApplied() throws Exception {
+ ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> callbackCaptor =
+ ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
+
+ verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
+ callbackCaptor.capture());
+
+ CarrierConfigManager.CarrierConfigChangeListener callback = callbackCaptor.getValue();
+
+ assertNotNull(callback);
+
+ // NR is included but carrier config is not applied.
+ PersistableBundle b = getPersistableBundle(new int[] { EUTRAN, NGRAN }, false);
+ doReturn(b).when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyString());
+ callback.onCarrierConfigChanged(SLOT_0, SUB_1, 0, 0);
+
+ assertFalse(mCarrierConfigHelper.isVoNrEmergencySupported(SLOT_0));
+ }
+
+ @Test
+ public void testCarrierConfigApplied() throws Exception {
+ ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> callbackCaptor =
+ ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
+
+ verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
+ callbackCaptor.capture());
+
+ CarrierConfigManager.CarrierConfigChangeListener callback = callbackCaptor.getValue();
+
+ assertNotNull(callback);
+
+ // NR is included and carrier config is applied.
+ PersistableBundle b = getPersistableBundle(new int[] { EUTRAN, NGRAN }, true);
+ doReturn(b).when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyString());
+ callback.onCarrierConfigChanged(SLOT_0, SUB_1, 0, 0);
+
+ assertTrue(mCarrierConfigHelper.isVoNrEmergencySupported(SLOT_0));
+ assertFalse(mCarrierConfigHelper.isVoNrEmergencySupported(SLOT_1));
+
+ verify(mEditor).putBoolean(eq(CarrierConfigHelper.KEY_VONR_EMERGENCY_SUPPORT + SLOT_0),
+ eq(true));
+
+ // NR is not included and carrier config is applied.
+ b = getPersistableBundle(new int[] { EUTRAN }, true);
+ doReturn(b).when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyString());
+ callback.onCarrierConfigChanged(SLOT_0, SUB_1, 0, 0);
+
+ assertFalse(mCarrierConfigHelper.isVoNrEmergencySupported(SLOT_0));
+
+ verify(mEditor).putBoolean(eq(CarrierConfigHelper.KEY_VONR_EMERGENCY_SUPPORT + SLOT_0),
+ eq(false));
+ }
+
+ @Test
+ public void testCarrierConfigInvalidSubId() throws Exception {
+ ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> callbackCaptor =
+ ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
+
+ verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
+ callbackCaptor.capture());
+
+ CarrierConfigManager.CarrierConfigChangeListener callback = callbackCaptor.getValue();
+
+ assertNotNull(callback);
+
+ // NR is included and carrier config is applied.
+ PersistableBundle b = getPersistableBundle(new int[] { EUTRAN, NGRAN }, true);
+ doReturn(b).when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyString());
+
+ // Invalid subscription
+ callback.onCarrierConfigChanged(SLOT_0, SubscriptionManager.INVALID_SUBSCRIPTION_ID, 0, 0);
+
+ assertFalse(mCarrierConfigHelper.isVoNrEmergencySupported(SLOT_0));
+ }
+
+ @Test
+ public void testRestoreFromSharedPreferences() throws Exception {
+ doReturn(true).when(mSharedPreferences).getBoolean(anyString(), anyBoolean());
+ mCarrierConfigHelper = new CarrierConfigHelper(mContext, mHandlerThread.getLooper(),
+ mSharedPreferences);
+
+ assertTrue(mCarrierConfigHelper.isVoNrEmergencySupported(SLOT_0));
+ }
+
+ @Test
+ public void testCarrierIgnoreNrWhenSimRemoved() throws Exception {
+ ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> callbackCaptor =
+ ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
+
+ verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
+ callbackCaptor.capture());
+
+ CarrierConfigManager.CarrierConfigChangeListener callback = callbackCaptor.getValue();
+
+ assertNotNull(callback);
+
+ // NR is included and carrier config for TEST SIM is applied.
+ PersistableBundle b = getPersistableBundle(new int[] { EUTRAN, NGRAN }, true);
+ doReturn(b).when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyString());
+ callback.onCarrierConfigChanged(SLOT_0, SUB_1, TEST_SIM_CARRIER_ID, 0);
+
+ // NR is ignored.
+ assertFalse(mCarrierConfigHelper.isVoNrEmergencySupported(SLOT_0));
+ assertFalse(mCarrierConfigHelper.isVoNrEmergencySupported(SLOT_1));
+ }
+
+ private static PersistableBundle getPersistableBundle(int[] imsRats, boolean applied) {
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putIntArray(KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY, imsRats);
+ bundle.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, applied);
+ return bundle;
+ }
+
+ private static void logd(String str) {
+ Log.d(TAG, str);
+ }
+}
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/EmergencyCallDomainSelectorTest.java b/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
index 72ef568..6217a92 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;
@@ -52,6 +54,8 @@
import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
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 com.android.services.telephony.domainselection.EmergencyCallDomainSelector.MSG_MAX_CELLULAR_TIMEOUT;
import static com.android.services.telephony.domainselection.EmergencyCallDomainSelector.MSG_NETWORK_SCAN_TIMEOUT;
@@ -68,6 +72,7 @@
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,6 +80,7 @@
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.os.Handler;
@@ -94,9 +100,11 @@
import android.telephony.EmergencyRegResult;
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.emergency.EmergencyNumber;
import android.telephony.ims.ImsManager;
import android.telephony.ims.ImsMmTelManager;
import android.telephony.ims.ProvisioningManager;
@@ -116,7 +124,10 @@
import org.mockito.stubbing.Answer;
import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.function.Consumer;
/**
@@ -127,6 +138,7 @@
private static final int SLOT_0 = 0;
private static final int SLOT_0_SUB_ID = 1;
+ private static final String TEST_EMERGENCY_NUMBER = "911";
@Mock private CarrierConfigManager mCarrierConfigManager;
@Mock private ConnectivityManager mConnectivityManager;
@@ -138,6 +150,9 @@
@Mock private DomainSelectorBase.DestroyListener mDestroyListener;
@Mock private ProvisioningManager mProvisioningManager;
@Mock private CrossSimRedialingController mCsrdCtrl;
+ @Mock private CarrierConfigHelper mCarrierConfigHelper;
+ @Mock private EmergencyCallbackModeHelper mEcbmHelper;
+ @Mock private Resources mResources;
private Context mContext;
@@ -187,6 +202,11 @@
public String getOpPackageName() {
return "";
}
+
+ @Override
+ public Resources getResources() {
+ return mResources;
+ }
};
if (Looper.myLooper() == null) {
@@ -206,6 +226,8 @@
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()))
@@ -252,6 +274,8 @@
}
}).when(mWwanSelectorCallback).onRequestEmergencyNetworkScan(
any(), anyInt(), any(), any());
+
+ when(mResources.getStringArray(anyInt())).thenReturn(null);
}
@After
@@ -278,6 +302,26 @@
}
@Test
+ public void testDestroyed() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+
+ EmergencyRegResult 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 testNoRedundantDomainSelectionFromInitialState() throws Exception {
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
@@ -357,6 +401,7 @@
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(
UNKNOWN, REGISTRATION_STATE_UNKNOWN, 0, false, false, 0, 0, "", "", "jp");
@@ -429,6 +474,74 @@
}
@Test
+ public void testDefaultCombinedImsRegisteredSelectPsThenExtendedServiceRequestFails()
+ throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult 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)
+ .setCsDisconnectCause(SERVICE_OPTION_NOT_AVAILABLE)
+ .setEmergency(true)
+ .setEmergencyRegResult(regResult);
+ attr = builder.build();
+ mDomainSelector.reselectDomain(attr);
+ processAllMessages();
+
+ verifyScanCsPreferred();
+ }
+
+ @Test
+ public void testDefaultCombinedImsRegisteredSelectPsThenNotExtendedServiceRequestFails()
+ throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult 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)
+ .setEmergencyRegResult(regResult);
+ attr = builder.build();
+ mDomainSelector.reselectDomain(attr);
+ processAllMessages();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
public void testDefaultCombinedImsNotRegisteredSelectCs() throws Exception {
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
@@ -446,6 +559,56 @@
}
@Test
+ public void testAirplaneDefaultCombinedImsNotRegisteredSelectPs() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult 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)
+ .setNumber(TEST_EMERGENCY_NUMBER)
+ .setEmergency(true)
+ .setEmergencyRegResult(regResult)
+ .setExitedFromAirplaneMode(true)
+ .build();
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyPsDialed();
+ }
+
+ @Test
+ public void testAirplaneRequiresRegCombinedImsNotRegisteredSelectPs() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putBoolean(KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL, true);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult 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)
+ .setNumber(TEST_EMERGENCY_NUMBER)
+ .setEmergency(true)
+ .setEmergencyRegResult(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,
@@ -1067,6 +1230,7 @@
bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+ mResultConsumer = null;
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
@@ -1091,6 +1255,39 @@
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())).thenReturn(bundle);
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ 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(true);
+
+ verifyScanPsPreferred();
+
+ assertFalse(mDomainSelector.hasMessages(MSG_NETWORK_SCAN_TIMEOUT));
}
@Test
@@ -1244,6 +1441,7 @@
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,
0, false, false, 0, 0, "", "", "jp");
@@ -1259,6 +1457,36 @@
}
@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());
+
+ EmergencyRegResult 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);
@@ -1266,6 +1494,7 @@
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,
0, false, false, 0, 0, "", "", "jp");
@@ -1294,6 +1523,30 @@
}
@Test
+ public void testEutranWithPsDomainOnly() throws Exception {
+ setupForHandleScanResult();
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ DOMAIN_PS, false, false, 0, 0, "", "");
+ mResultConsumer.accept(regResult);
+ processAllMessages();
+
+ verifyPsDialed();
+ }
+
+ @Test
+ public void testUtran() throws Exception {
+ setupForHandleScanResult();
+
+ EmergencyRegResult regResult = getEmergencyRegResult(UTRAN, REGISTRATION_STATE_HOME,
+ DOMAIN_CS, false, false, 0, 0, "", "");
+ mResultConsumer.accept(regResult);
+ processAllMessages();
+
+ verifyCsDialed();
+ }
+
+ @Test
public void testFullService() throws Exception {
PersistableBundle bundle = getDefaultPersistableBundle();
bundle.putInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT, SCAN_TYPE_FULL_SERVICE);
@@ -1606,6 +1859,7 @@
public void testStartCrossStackTimer() throws Exception {
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(false);
+ doReturn(2).when(mTelephonyManager).getActiveModemCount();
EmergencyRegResult regResult = getEmergencyRegResult(
UNKNOWN, REGISTRATION_STATE_UNKNOWN, 0, false, false, 0, 0, "", "");
@@ -1770,6 +2024,20 @@
verify(mTransportSelectorCallback, times(1)).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())).thenReturn(bundle);
+
+ setupForHandleScanResult();
+
+ assertFalse(mDomainSelector.hasMessages(MSG_NETWORK_SCAN_TIMEOUT));
+ assertFalse(mDomainSelector.hasMessages(MSG_MAX_CELLULAR_TIMEOUT));
+ }
@Test
public void testMaxCellularTimeoutScanTimeout() throws Exception {
@@ -1837,6 +2105,9 @@
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();
@@ -1911,6 +2182,331 @@
verify(mTransportSelectorCallback, times(2)).onWlanSelected(anyBoolean());
}
+ @Test
+ public void testSimLockScanPsPreferredWithNr() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ // The last valid subscription supported NR.
+ doReturn(true).when(mCarrierConfigHelper).isVoNrEmergencySupported(eq(SLOT_0));
+ when(mTelephonyManager.getSimState(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_PIN_REQUIRED);
+
+ EmergencyRegResult 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(), any(), any());
+ assertEquals(4, mAccessNetwork.size());
+ assertEquals(EUTRAN, (int) mAccessNetwork.get(0));
+ assertEquals(NGRAN, (int) mAccessNetwork.get(1));
+ assertEquals(UTRAN, (int) mAccessNetwork.get(2));
+ assertEquals(GERAN, (int) mAccessNetwork.get(3));
+ }
+
+ @Test
+ public void testSimLockScanPsPreferredWithNrAtTheEnd() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ when(mTelephonyManager.getSimState(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_PIN_REQUIRED);
+
+ EmergencyRegResult 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(), 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);
+
+ EmergencyRegResult 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(), 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 testInvalidSubscriptionScanPsPreferredWithNr() throws Exception {
+ createSelector(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ unsolBarringInfoChanged(false);
+
+ // The last valid subscription supported NR.
+ doReturn(true).when(mCarrierConfigHelper).isVoNrEmergencySupported(eq(SLOT_0));
+
+ EmergencyRegResult 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(), any(), any());
+ assertEquals(4, mAccessNetwork.size());
+ assertEquals(EUTRAN, (int) mAccessNetwork.get(0));
+ assertEquals(NGRAN, (int) mAccessNetwork.get(1));
+ assertEquals(UTRAN, (int) mAccessNetwork.get(2));
+ assertEquals(GERAN, (int) mAccessNetwork.get(3));
+ }
+
+ @Test
+ public void testDefaultLimitedServiceEutran() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult 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 testDefaultLimitedServiceNgran() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult 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 {
+ EmergencyNumber num = new EmergencyNumber(TEST_EMERGENCY_NUMBER, "us", "",
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE, new ArrayList<String>(),
+ EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+
+ Map<Integer, List<EmergencyNumber>> lists = new HashMap<>();
+ List<EmergencyNumber> list = new ArrayList<>();
+ list.add(num);
+ lists.put(SLOT_0_SUB_ID, list);
+
+ doReturn(lists).when(mTelephonyManager).getEmergencyNumberList();
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult 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();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testTestEmergencyNumberOverPs() throws Exception {
+ EmergencyNumber num = new EmergencyNumber(TEST_EMERGENCY_NUMBER, "us", "",
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE, new ArrayList<String>(),
+ EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+
+ Map<Integer, List<EmergencyNumber>> lists = new HashMap<>();
+ List<EmergencyNumber> list = new ArrayList<>();
+ list.add(num);
+ lists.put(SLOT_0_SUB_ID, list);
+
+ doReturn(lists).when(mTelephonyManager).getEmergencyNumberList();
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult 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();
+
+ bindImsService();
+
+ verifyPsDialed();
+ }
+
+ @Test
+ public void testTestEmergencyNumberOverWifi() throws Exception {
+ EmergencyNumber num = new EmergencyNumber(TEST_EMERGENCY_NUMBER, "us", "",
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE, new ArrayList<String>(),
+ EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+
+ Map<Integer, List<EmergencyNumber>> lists = new HashMap<>();
+ List<EmergencyNumber> list = new ArrayList<>();
+ list.add(num);
+ lists.put(SLOT_0_SUB_ID, list);
+
+ doReturn(lists).when(mTelephonyManager).getEmergencyNumberList();
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult 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();
+
+ bindImsService(true);
+ processAllMessages();
+
+ verify(mTransportSelectorCallback, times(1)).onWlanSelected(anyBoolean());
+ }
+
+ @Test
+ public void testLimitedServiceDialCs() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult 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());
+
+ EmergencyRegResult 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 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());
+
+ EmergencyRegResult 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());
+
+ EmergencyRegResult 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());
+
+ EmergencyRegResult 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());
+ }
+
private void setupForScanListTest(PersistableBundle bundle) throws Exception {
setupForScanListTest(bundle, false);
}
@@ -1984,8 +2580,8 @@
private void createSelector(int subId) throws Exception {
mDomainSelector = new EmergencyCallDomainSelector(
mContext, SLOT_0, subId, mHandlerThread.getLooper(),
- mImsStateTracker, mDestroyListener, mCsrdCtrl);
-
+ mImsStateTracker, mDestroyListener, mCsrdCtrl, mCarrierConfigHelper, mEcbmHelper);
+ mDomainSelector.clearResourceConfiguration();
replaceInstance(DomainSelectorBase.class,
"mWwanSelectorCallback", mDomainSelector, mWwanSelectorCallback);
}
@@ -2157,6 +2753,7 @@
EmergencyRegResult regResult) {
SelectionAttributes.Builder builder =
new SelectionAttributes.Builder(slotId, subId, SELECTOR_TYPE_CALLING)
+ .setNumber(TEST_EMERGENCY_NUMBER)
.setEmergency(true)
.setEmergencyRegResult(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/NormalCallDomainSelectorTest.java b/tests/src/com/android/services/telephony/domainselection/NormalCallDomainSelectorTest.java
index 4dd1f3c..002c7d5 100644
--- a/tests/src/com/android/services/telephony/domainselection/NormalCallDomainSelectorTest.java
+++ b/tests/src/com/android/services/telephony/domainselection/NormalCallDomainSelectorTest.java
@@ -344,7 +344,8 @@
//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);
@@ -387,7 +388,8 @@
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);
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..b9112be
--- /dev/null
+++ b/tests/src/com/android/services/telephony/domainselection/OWNERS
@@ -0,0 +1,8 @@
+# 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
diff --git a/tests/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionServiceTest.java b/tests/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionServiceTest.java
index f340e94..d9c737e 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;
@@ -44,6 +46,7 @@
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,7 +81,9 @@
@SelectorType int selectorType, boolean isEmergency,
@NonNull Looper looper, @NonNull ImsStateTracker imsStateTracker,
@NonNull DomainSelectorBase.DestroyListener listener,
- @NonNull CrossSimRedialingController crossSimRedialingController) {
+ @NonNull CrossSimRedialingController crossSimRedialingController,
+ @NonNull CarrierConfigHelper carrierConfigHelper,
+ @NonNull EmergencyCallbackModeHelper ecbmHelper) {
switch (selectorType) {
case DomainSelectionService.SELECTOR_TYPE_CALLING: // fallthrough
case DomainSelectionService.SELECTOR_TYPE_SMS: // fallthrough
@@ -94,6 +99,26 @@
}
}
};
+ private static class TestTelephonyDomainSelectionService
+ extends TelephonyDomainSelectionService {
+ private final Context mContext;
+
+ TestTelephonyDomainSelectionService(Context context,
+ @NonNull ImsStateTrackerFactory imsStateTrackerFactory,
+ @NonNull DomainSelectorFactory domainSelectorFactory,
+ @Nullable CarrierConfigHelper carrierConfigHelper,
+ @Nullable EmergencyCallbackModeHelper ecbmHelper) {
+ super(imsStateTrackerFactory, domainSelectorFactory, carrierConfigHelper, 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 +132,8 @@
@Mock private TransportSelectorCallback mSelectorCallback1;
@Mock private TransportSelectorCallback mSelectorCallback2;
@Mock private ImsStateTracker mImsStateTracker;
+ @Mock private CarrierConfigHelper mCarrierConfigHelper;
+ @Mock private EmergencyCallbackModeHelper mEcbmHelper;
private final ServiceState mServiceState = new ServiceState();
private final BarringInfo mBarringInfo = new BarringInfo();
@@ -127,12 +154,16 @@
}
mContext = new TestContext();
- mDomainSelectionService = new TelephonyDomainSelectionService(mContext,
- mImsStateTrackerFactory, mDomainSelectorFactory);
+ mDomainSelectionService = new TestTelephonyDomainSelectionService(mContext,
+ mImsStateTrackerFactory, mDomainSelectorFactory, mCarrierConfigHelper, 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 +208,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 +225,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 +242,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 +258,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 +275,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 +288,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 +321,7 @@
.setCallId(CALL_ID)
.setEmergency(true)
.build();
- mServiceHandler.post(() -> {
- mDomainSelectionService.onDomainSelection(attr1, mSelectorCallback1);
- });
+ mDomainSelectionService.onDomainSelection(attr1, mSelectorCallback1);
processAllMessages();
mDomainSelectionService.onDestroy();
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);
+ }
+ }
+}