[automerger skipped] Merge "DO NOT MERGE: Add 028, 116000 normal routed emergency numbers for "ES"." into udc-qpr-dev am: 20864439bc -s ours
am skip reason: contains skip directive
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Telephony/+/25842879
Change-Id: Ic4f3ecb868ce5ec7b989e8d14a15a71bbae815ac
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index dc35c5d..a943299 100644
--- a/Android.bp
+++ b/Android.bp
@@ -41,9 +41,14 @@
"com.android.phone.common-lib",
"guava",
"PlatformProperties",
+ "modules-utils-fastxmlserializer",
"modules-utils-os",
"nist-sip",
- "service-entitlement"
+ "service-entitlement",
+ "telephony_flags_core_java_lib",
+ "android.permission.flags-aconfig-java",
+ "satellite-s2storage-ro",
+ "s2-geometry-library-java",
],
srcs: [
@@ -87,7 +92,7 @@
libs: [
"telephony-common",
"service-entitlement"
- ],
+ ],
}
platform_compat_config {
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8d03ed7..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"
@@ -279,7 +280,7 @@
<activity android:name="GsmUmtsCallForwardOptions"
android:label="@string/labelCF"
android:configChanges="orientation|screenSize|keyboardHidden"
- android:exported="true"
+ android:exported="false"
android:theme="@style/CallSettingsWithoutDividerTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -289,7 +290,7 @@
<activity android:name="CdmaCallForwardOptions"
android:label="@string/labelCF"
android:configChanges="orientation|screenSize|keyboardHidden"
- android:exported="true"
+ android:exported="false"
android:theme="@style/CallSettingsWithoutDividerTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -299,7 +300,7 @@
<activity android:name="GsmUmtsCallBarringOptions"
android:label="@string/labelCallBarring"
android:configChanges="orientation|screenSize|keyboardHidden"
- android:exported="true"
+ android:exported="false"
android:theme="@style/DialerSettingsLight">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -309,7 +310,7 @@
<activity android:name="GsmUmtsAdditionalCallOptions"
android:label="@string/labelGSMMore"
android:configChanges="orientation|screenSize|keyboardHidden"
- android:exported="true"
+ android:exported="false"
android:theme="@style/CallSettingsWithoutDividerTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -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 0fed2f0..53b9401 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,17 +1,5 @@
-breadley@google.com
-fionaxu@google.com
-jackyu@google.com
-rgreenwalt@google.com
-tgunn@google.com
-sarahchin@google.com
-xiaotonj@google.com
-huiwang@google.com
-jayachandranc@google.com
-chinmayd@google.com
-amruthr@google.com
-sasindran@google.com
-tjstuart@google.com
-pmadapurmath@google.com
-grantmenke@google.com
+include platform/frameworks/opt/telephony:/OWNERS
per-file *SimPhonebookProvider* = file:platform/packages/apps/Contacts:/OWNERS
+
+per-file config.xml=hwangoo@google.com,forestchoi@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,radhikaagrawal@google.com
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 3831b6b..04a8efb 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -9,7 +9,8 @@
]
},
{
- "name": "CarrierAppIntegrationTestCases"
+ "name": "CarrierAppIntegrationTestCases",
+ "keywords": ["internal"]
},
{
"name": "CtsSimRestrictedApisTestCases",
diff --git a/assets/google_us_san_sat_s2.dat b/assets/google_us_san_sat_s2.dat
new file mode 100644
index 0000000..60b00df
--- /dev/null
+++ b/assets/google_us_san_sat_s2.dat
Binary files differ
diff --git a/ecc/input/OWNERS b/ecc/input/OWNERS
index 5685875..2ed2f3f 100644
--- a/ecc/input/OWNERS
+++ b/ecc/input/OWNERS
@@ -1,4 +1,5 @@
set noparent
tgunn@google.com
-chinmayd@google.com
\ No newline at end of file
+breadley@google.com
+rgreenwalt@google.com
\ No newline at end of file
diff --git a/ecc/output/OWNERS b/ecc/output/OWNERS
index 5685875..2ed2f3f 100644
--- a/ecc/output/OWNERS
+++ b/ecc/output/OWNERS
@@ -1,4 +1,5 @@
set noparent
tgunn@google.com
-chinmayd@google.com
\ No newline at end of file
+breadley@google.com
+rgreenwalt@google.com
\ No newline at end of file
diff --git a/ecc/output/eccdata b/ecc/output/eccdata
index d472bc9..482ed79 100644
--- a/ecc/output/eccdata
+++ b/ecc/output/eccdata
Binary files differ
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index c451e65..dab1dfb 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Sub-ID van verstekdata-SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Aflaaibandwydte (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Oplaaibandwydte (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Fisieke LTE-kanaalopstelling:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Fisieke kanaalopstellings:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Herlaaikoers van selinligting:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Alle selmetingsinligting:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Datadiens:"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 112441b..95437d5 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"የነባሪ ውሂብ ሲም SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL መተላለፊያ ይዘት (ኪቢ/ሴ)፡"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"የUL መተላለፊያ ይዘት (ኪቢ/ሴ)፡"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"የLTE አካላዊ ሰርጥ ውቅረት:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"የአካላዊ ሰርጥ ውቅረቶች፦"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"የሕዋስ መረጃ እድሳት ፍጥነት፡"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"ሁሉም የሕዋስ መለኪያ መረጃ፡"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"የውሂብ አገልግሎት:"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 986e6b6..54eeb24 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"المعرّف الفرعي لشريحة SIM التلقائية للبيانات:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"معدّل نقل بيانات DL (كيلوبت في الثانية):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"معدّل نقل بيانات UL (كيلوبت في الثانية):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"ضبط قناة LTE Physical:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"إعدادات القنوات:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"معدّل إعادة تحميل المعلومات الخلوية:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"جميع معلومات القياس الخلوية:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"خدمة البيانات:"</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 87ccace..b68f32b 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ডিফ’ল্ট ডেটা ছিমৰ ছাবআইডি:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL বেণ্ডৱিথ (কেবিপিএছ):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL বেণ্ডৱিথ (কেবিপিএছ):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ফিজিকেল চেনেলৰ কনফিগাৰেশ্বন:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ফিজিকেল চেনেল কনফিগাৰেশ্বন:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"চেল তথ্য ৰিফ্ৰেশ্বৰ হাৰ:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"সকলো চেল পৰিমাপৰ তথ্য:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"ডেটা সেৱা:"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index b31c670..38a8f9f 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Defolt data SIM üçün alt Id:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL Buraxılışı (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL Buraxılışı (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE Fiziki Kanal Konfiqurasiyası:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Fiziki Kanal Konfiqurasiyaları:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Mobil məlumatın yenilənmə göstəricisi:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Operatorun bütün mobil ölçü məlumatı:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Data Xidməti:"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 3474c39..69a0f7b 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubID podrazumevanog SIM-a za podatke:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL propusni opseg (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL propusni opseg (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfiguracija LTE fizičkog kanala:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfiguracije fizičkog kanala:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Učestalost osvežavanja informacija o predajniku:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Sve informacije o merenju za predajnik:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Usluga prenosa podataka:"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 33e26a1..0f7f46a 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubID стандартнай SIM-карты для перадачы даных:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Прапускная здольнасць канала спампоўвання (кбіт/с):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Прапускная здольнасць канала запампоўвання (кбіт/с):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Канфігурацыя фізічнага канала LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Канфігурацыі фізічнага канала:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Частата абнаўлення даных сотавай сеткі:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Усе паказчыкі сотавай сеткі:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Сэрвіс перадачы даных:"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index cf06d50..97f086f 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Идентификационен подномер на SIM картата за данни по подразбиране:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Пропускателна способност при изтегляне (кб/сек):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Пропускателна способност при качване (кб/сек):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Конфигурация на физическия канал на LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Конфигурации на физическия канал:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Честота на опресняване на информацията за клетките:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Цялата измервателна информация за клетките:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Услуга за данни:"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index b519003..f1fefbc 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ডিফল্ট ডেটা সিমের SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"ডিএল ব্যান্ডউইথ (কেবিপিএস):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"ইউএল ব্যান্ডউইথ (কেবিপিএস):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ফিজিক্যাল চ্যানেল কনফিগারেশন:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ফিজিক্যাল চ্যানেল কনফিগারেশন:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"সেল তথ্যে রিফ্রেশ রেট:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"সমস্ত সেল পরিমাপ তথ্য:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"ডেটা পরিষেবা:"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 5da6a69..992f1b2 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Pomoćni ID za zadani SIM za prijenos podataka:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL propusnost (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL propusnost (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfiguracija LTE fizičkog kanala:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfiguracije fizičkih kanala:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Interval osvježavanja informacija o ćeliji:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Sve informacije o mjerenju ćelije:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Prijenos podataka:"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index cf1c3a8..6557918 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Subidentificador de la SIM de dades predeterminada:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Amplada de banda de baixada (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Amplada de banda de pujada (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuració del canal físic de LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configuracions del canal físic:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Freqüència d\'actualització de la informació del mòbil:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Tota la informació de mesures del mòbil:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Servei de dades:"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 520c0de..660da5e 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId výchozí datové SIM karty:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Rychlost stahování (kB/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Rychlost nahrávání (kB/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfigurace fyzického kanálu LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfigurace fyzického kanálu:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Interval obnovení informací o mobilní síti:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Všechny údaje o měření mobilní sítě:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Datová služba:"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 56d09f4..801eb12 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Under-id for standard-SIM-kort til data:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Båndbredde til download (kB/sek.):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Båndbredde til upload (kB/sek.):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Fysisk kanalkonfiguration for LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Fysiske kanalkonfigurationer:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Opdateringsfrekvens for celleoplysninger:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Alle oplysninger om cellemåling:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Datatjeneste:"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index db3d447..7378480 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Unter-ID der standardmäßigen Daten-SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL-Bandbreite (kbit/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL-Bandbreite (kbit/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfiguration des physischen LTE-Kanals:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfiguration des physischen Kanals:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Aktualisierungsrate der Mobiltelefoninformationen:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Alle Informationen zu Zellenmesswerten:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Datendienst:"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index e2c6854..875ae61 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId προεπιλεγμένης SIM δεδομένων:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Εύρος ζώνης DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Εύρος ζώνης UL (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Διαμόρφωση φυσικού καναλιού LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Διαμορφώσεις φυσικού καναλιού:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Ρυθμός ανανέωσης στοιχείων κινητής τηλεφωνίας:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Όλα τα στοιχεία μετρήσεων κινητής τηλεφωνίας:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Υπηρεσία δεδομένων:"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 2d81593..9a7861f 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId of default data SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL bandwidth (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL bandwidth (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE physical channel configuration:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Physical channel configurations:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Mobile info refresh rate:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"All mobile measurement info:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Data service:"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index e7a6f1b..81942d0 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId of default data SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL Bandwidth (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL Bandwidth (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE Physical Channel Configuration:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Physical Channel Configurations:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Cell Info Refresh Rate:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"All Cell Measurement Info:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Data Service:"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 2d81593..9a7861f 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId of default data SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL bandwidth (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL bandwidth (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE physical channel configuration:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Physical channel configurations:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Mobile info refresh rate:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"All mobile measurement info:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Data service:"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 2d81593..9a7861f 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId of default data SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL bandwidth (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL bandwidth (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE physical channel configuration:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Physical channel configurations:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Mobile info refresh rate:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"All mobile measurement info:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Data service:"</string>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 319986a..d275cc4 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId of default data SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL Bandwidth (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL Bandwidth (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE Physical Channel Configuration:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Physical Channel Configurations:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Cell Info Refresh Rate:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"All Cell Measurement Info:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Data Service:"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index aa4aab1..2a25ded 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ID secundario de SIM de datos predeterminada:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Ancho de banda de descarga (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Ancho de banda de carga (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuración del canal físico de LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Parámetros de configuración de canales físicos:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Frecuencia de actualización de datos del celular:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Información sobre las dimensiones del celular:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Servicio de datos:"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index ee17505..26d5631 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ID secundario de la SIM de datos predeterminada:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Ancho de banda de bajada (Kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Ancho de banda de subida (Kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuración del canal físico de LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configuraciones del canal físico:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Frecuencia de actualización de la información del teléfono:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Información sobre las dimensiones de los teléfonos:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Servicio de datos:"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index d306e21..60c4d40 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Andmete vaike-SIM-kaardi alam-ID:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL-i ribalaius (kbit/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL-i ribalaius (kbit/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE füüsilise kanali konfiguratsioon:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Füüsilise kanali seadistused:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Kärje teabe värskendamissagedus:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Kõik kärje mõõteandmed:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Andmesideteenus:"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 9e4b8a7..4790caa 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Datu-konexioetarako SIM lehenetsiaren azpiIDa:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Deskargatzeko banda-zabalera (Kb/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Kargen banda-zabalera (Kb/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE kanal fisikoaren konfigurazioa:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Kanal fisikoen konfigurazioa:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Sare mugikorraren informazioa eguneratzeko maiztasuna:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Sare mugikorraren neurketa guztien informazioa:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Datu-zerbitzua:"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 9e5f81ba..76ae668 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId مربوط به سیمکارت داده پیشفرض:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"پهنای باند DL (کیلوبیت بر ثانیه):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"پهنای باند UL (کیلوبیت بر ثانیه):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"پیکربندی کانال فیزیکی LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"پیکربندیهای کانال فیزیکی:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"نرخ بازآوری اطلاعات شبکه همراه:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"همه اطلاعات اندازهگیری شبکه همراه:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"سرویس داده:"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 9cf2479..f24af53 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Oletusarvoisen data-SIM-kortin alitunnus:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL-kaistanleveys (kt/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL-kaistanleveys (kt/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Fyysisen LTE-kanavan kokoonpano:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Fyysisen kanavan määritykset:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Matkapuhelintietojen päivitysaikaväli:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Matkapuhelimen kaikki mittaustiedot:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Datapalvelu:"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 05e4cfc..95e6ae9 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Sous-identifiant de la carte SIM par défaut :"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Bande passante de téléchargement (kb/s) :"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Bande passante de téléversement (kb/s) :"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuration du canal physique LTE :"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configurations des canaux physiques :"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Taux d\'actualisation des données de la cellule :"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Données des mesures de toutes les cellules :"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Service de données :"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 95a882d..8b3f378 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Sous-identifiant SIM par défaut pour les données :"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Bande passante de téléchargement (kbit/s) :"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Bande passante d\'importation (kbit/s) :"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuration de la chaîne physique LTE :"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configurations des canaux physiques :"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Fréquence d\'actualisation des informations mobiles :"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Toutes les informations mobiles liées aux mesures :"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Service de données :"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 8ce562c..5ce9ea3 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Código de identificación secundario da SIM de datos predeterminada:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Largura de banda de descarga (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Largura de banda de carga (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuración de canle física de LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configuración da canle física:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Taxa de actualización da información para móbiles:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Toda a información de medición para móbiles:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Servizo de datos:"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 6b10ddc..99606a0 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ડિફૉલ્ટ ડેટા સિમનું SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL બૅન્ડવિડ્થ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL બૅન્ડવિડ્થ (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ભૌતિક ચૅનલની ગોઠવણી:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ચૅનલનું ભૌતિક કન્ફિગ્યુરેશન:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"સેલ માહિતી રિફ્રેશ થવાનો રેટ:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"તમામ સેલ માપ માહિતી:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"ડેટા સેવા:"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 565ece4..febd572 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"डिफ़ॉल्ट डेटा सिम का सब-आईडी:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"डीएल बैंडविड्थ (केबीपीएस):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"यूएल बैंडविड्थ (केबीपीएस):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"एलटीई की फ़िज़िकल चैनल कॉन्फ़िगरेशन:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"फ़िज़िकल चैनल के कॉन्फ़िगरेशन:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"सेल की जानकारी रीफ़्रेश होने की दर:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"सभी सेल के माप की पूरी जानकारी:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"डेटा सेवा:"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 692c18d..790b6be 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SUBID zadanog SIM-a za podatkovni promet:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL propusnost (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL propusnost (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfiguracija LTE fizičkog kanala:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfiguracije fizičkog kanala:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Stopa osvježavanja informacija u ćeliji:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Sve informacije ćelija o mjerenju:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Podatkovna usluga:"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index a0226d1..54c112b 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Alapértelmezett adatok SIM-alazonosítója:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Letöltési sávszélesség (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Feltöltési sávszélesség (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE fizikai csatorna konfigurációja:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Fizikai csatorna konfigurációi:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Cellainformáció frissítési gyakorisága:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Minden cellamérési információ:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Adatszolgáltatás:"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 8de0ae2..8e68a06 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SIM քարտի հավելյալ ID կանխադրված բջջային ինտերնետի համար՝"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL թողունակությունը (կբ/վ)՝"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL թողունակությունը (կբ/վ)՝"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ֆիզիկական ալիքի կարգավորում՝"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Ֆիզիկական ալիքի կազմաձևեր՝"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Բջիջի տվյալների թարմացման հաճախականությունը՝"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Բոլոր բջիջների չափման տվյալները՝"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Տվյալների ծառայություն՝"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 12110ed..9484f54 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId SIM data default:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Bandwidth DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Bandwidth UL (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfigurasi Saluran Fisik LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfigurasi Saluran Fisik:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Rasio Pembaruan Info Sel"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Semua Info Pengukuran Sel:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Layanan Data:"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 589503f..123519e 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Undirauðkenni sjálfgefins SIM-korts fyrir gögn:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Niðurhalsbandvídd (kb/sek.):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Upphleðslubandvídd (kb/sek.):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE-rásarstilling:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Áþreifanleg stilling stöðva:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Endurnýjunartíðni loftnetaupplýsinga:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Allar mælingarupplýsingar loftneta:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Gagnaþjónusta:"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 68a5335..2b8e870 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ID secondario della SIM dati predefinita:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Larghezza di banda DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Larghezza di banda UL (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configurazione canale fisico LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configurazioni dei canali fisici:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Frequenza di aggiornamento delle informazioni sulle celle:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Informazioni sulla misurazione di tutte le celle:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Servizio dati:"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 16fab20..a51c72d 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -877,7 +877,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"תת-מזהה של כרטיס ה-SIM עם חבילת גלישה המוגדר כברירת מחדל:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"רוחב פס DL (ב-kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"רוחב פס UL (ב-kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"תצורת ערוץ פיזי של LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ההגדרות האישיות של הערוץ הפיזי"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"קצב רענון של מידע סלולרי:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"כל המידע של מדידה סלולרית:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"שירות נתונים:"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 13ed54d..4127de8 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"デフォルトのデータ SIM の SUBID:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL 帯域幅(kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL 帯域幅(kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE の物理チャネル設定:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"物理チャネルの構成:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"セル情報の更新間隔:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"すべてのセルの測定情報:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"データサービス:"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 586858d..f4ca82e 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"მონაცემების ნაგულისხმევი SIM-ის subId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL-არხის გამტარუნარიანობა (კბიტ/წმ):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL-არხის გამტარუნარიანობა (კბიტ/წმ):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ფიზიკური არხის კონფიგურაცია:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"არხის ფიზიკური კონფიგურაცია:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"ფიჭური ინფორმაციის განახლების სიხშირე:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"სრული ინფორმაცია ფიჭური ქსელის შესახებ:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"მობილური ინტერნეტის სერვისი:"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index a10c8f2..72e9c7f 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Әдепкі деректер SIM картасының қосалқы идентификаторы:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL өткізу мүмкіндігі (кбит/сек):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL өткізу мүмкіндігі (кбит/сек):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE физикалық арна конфигурациясы:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Физикалық арна конфигурациялары:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Ұялы желі туралы ақпаратты жаңарту жиілігі:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Барлық ұялы желі өлшемдері туралы ақпарат:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Деректер қызметі:"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index cc8ac7d..d42deb0 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"លេខសម្គាល់រងរបស់ស៊ីមទិន្នន័យលំនាំដើម៖"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"កម្រិតបញ្ជូន DL (kbps) ៖"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"កម្រិតបញ្ជូន UL (kbps) ៖"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"ការកំណត់រចនាសម្ព័ន្ធបណ្ដាញរូបវ័ន្ត LTE ៖"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ការកំណត់រចនាសម្ព័ន្ធបណ្ដាញរូបវន្ត៖"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"អត្រាផ្ទុកឡើងវិញនៃព័ត៌មានទូរសព្ទចល័ត៖"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"ព័ត៌មានវាស់ទូរសព្ទចល័តទាំងអស់៖"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"សេវាកម្មទិន្នន័យ៖"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 3be99a8..011b30b 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ಡೀಫಾಲ್ಟ್ ಡೇಟಾ ಸಿಮ್ನ ವಿಷಯಐಡಿ:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL ಬ್ಯಾಂಡ್ವಿಡ್ತ್ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL ಬ್ಯಾಂಡ್ವಿಡ್ತ್ (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ಭೌತಿಕ ಚಾನೆಲ್ ಕಾನ್ಫಿಗರೇಶನ್:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ಭೌತಿಕ ಚಾನಲ್ ಕಾನ್ಫಿಗರೇಶನ್ಗಳು:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"ಸೆಲ್ ಮಾಹಿತಿ ರಿಫ್ರೆಶ್ ದರ:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"ಎಲ್ಲಾ ಸೆಲ್ ಮಾಪನ ಮಾಹಿತಿ:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"ಡೇಟಾ ಸೇವೆ:"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index affc995..6b403d9 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"기본 데이터 SIM의 subId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL 대역폭(kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL 대역폭(kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE 물리적 채널 구성:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"물리적 채널 구성:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"셀 정보 새로고침 빈도:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"모든 셀 측정 정보:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"데이터 서비스:"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index fd21eac..3fca1ed 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Демейки оператордун SIM картасынын көз салуу идентификатору:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL өткөрүү жөндөмдүүлүгү (кб/сек.):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL өткөрүү жөндөмдүүлүгү (кб/сек.):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE физикалык каналынын конфигурациясы:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Физикалык каналдын конфигурациялары:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Уюлдук маалыматты жаңылоо ылдамдыгы:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Уюлдук чен-өлчөм маалыматтары:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Мобилдик туташуу кызматы:"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 378b6a4..10ddb89 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId ຂອງຊິມອິນເຕີເນັດເລີ່ມຕົ້ນ:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"ແບນວິດ DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"ແບນວິດ UL (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"ການຕັ້ງຄ່າຊ່ອງ LTE ກາຍະພາບ:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ການຕັ້ງຄ່າຊ່ອງທາງກາຍະພາບ:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"ອັດຕາການໂຫຼດຄືນໃໝ່ຂອງຂໍ້ມູນມືຖື:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"ຂໍ້ມູນການວັດແທກມືຖືທັງໝົດ:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"ບໍລິການຂໍ້ມູນ:"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index b765788..9e1a340 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Numatytųjų duomenų SIM kortelės papildomas ID:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL pralaidumas (Kb/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL pralaidumas (Kb/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE fizinio kanalo konfigūracija:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Fizinių kanalų konfigūracijos:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Mobiliojo ryšio informacijos atnaujinimo dažnis:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Visų mobiliųjų ryšių įvertinimo informacija:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Duomenų paslauga:"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 5ea7760..90165b8 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Noklusējuma datu SIM kartes papildu ID:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL joslas platums (kb/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL joslas platums (kb/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE fiziskā kanāla konfigurācija:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Fiziskā kanāla konfigurācijas:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Mobilā tīkla informācijas atsvaidzināšanas biežums:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Visa mobilā tīkla mērījumu informācija:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Datu pakalpojums:"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index b90b9d2..2c68ef1 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SUBID на стандардната SIM за мобилен интернет:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Брзина на пренос при преземање (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Брзина на пренос при прикачување (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Конфигурација на физички канал на LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Конфигурации на физички канали:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Стапка на освежување на информациите за мобилниот:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Сите информации за мерењата на мобилниот:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Услуга за мобилен интернет:"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 1820338..b0785bb 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ഡിഫോൾട്ട് ഡാറ്റാ സിമ്മിന്റെ ഉപഐഡി:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL ബാൻഡ്വിഡ്ത് (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL ബാൻഡ്വിഡ്ത് (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ഫിസിക്കൽ ചാനൽ കോൺഫിഗറേഷൻ:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ഫിസിക്കൽ ചാനൽ കോൺഫിഗറേഷനുകൾ:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"സെൽ വിവരങ്ങൾ പുതുക്കിയെടുക്കൽ നിരക്ക്:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"എല്ലാ സെൽ അളവ് വിവരങ്ങളും:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"ഡാറ്റ സേവനം:"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 08e60d1..712042a 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Өгөгдмөл дата SIM-н SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DLзурвасын өргөн (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL Мессежийн өргөн (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE Сувгийн бодит тохиргоо:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Биет сувгийн тохируулга:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Үүрэн мэдээлэл сэргээх тариф:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Бүх үүрэн хэмжилтийн мэдээлэл:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Дата үйлчилгээ:"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index eda6e6e..76dee50 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"डीफॉल्ट डेटा सिम SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL बँडविड्थ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL बँडविड्थ (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE फिजिकल चॅनल कॉंफिगरेशन:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"प्रत्यक्ष चॅनलची कॉंफिगरेशन:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"सेल माहिती रिफ्रेश रेट:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"सर्व सेल परिमाण माहिती:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"डेटा सर्व्हिस:"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 979ad44..18b47c2 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId SIM data lalai:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Lebar Jalur DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Lebar Jalur UL (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfigurasi Saluran Fizikal LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfigurasi Saluran Fizikal:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Kadar Muat Semula Maklumat Selular:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Semua Maklumat Ukuran Selular:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Perkhidmatan Data:"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 50eb296..b4c7573 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"မူရင်း ဒေတာဆင်းမ်ကဒ်အတွက် Id အခွဲ −"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL မြန်နှုန်း (kbps)−"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL မြန်နှုန်း (kbps)−"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ရုပ်ပိုင်းဆိုင်ရာ ချန်နယ်စီစဉ်သတ်မှတ်မှု−"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ရုပ်ပိုင်းဆိုင်ရာ ချန်နယ်စီစဉ်သတ်မှတ်ချက်များ-"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"ဆဲလ်လူလာ အချက်အလက် ရယူမှုနှုန်း −"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"ဆဲလ်လူလာတိုင်းတာမှု အချက်အလက် အားလုံး −"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"ဒေတာ ဝန်ဆောင်မှု −"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 14b25d9..fb8a4fe 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Under-ID for standard-SIM-kort for data:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Båndbredde for nedlasting (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Båndbredde for opplasting (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfigurering av fysisk LTE-kanal:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfigurasjoner for fysiske kanaler:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Oppdateringsfrekvens for celleinformasjon:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"All informasjon for cellemåling:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Datatjeneste:"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 8ef3956..1b399ab 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"डिफल्ट डेटा SIM को SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL ब्यान्डविथ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL व्यान्डविथ (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE को भौतिक च्यानलको कन्फिगरेसन:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"भौतिक च्यानलका कन्फिगुरेसनहरू:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"सेलसम्बन्धी जानकारीलाई पुनः ताजा गरिने दर:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"सेलको मापनसम्बन्धी सबै जानकारी:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"डेटा सम्बन्धी सेवा:"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 96c99c7..da89764 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId van standaard simkaart voor data:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL-bandbreedte (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL-bandbreedte (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Fysieke LTE-kanaalconfiguratie:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configuraties voor fysieke kanalen:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Vernieuwingsfrequentie van mobiele data:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Alle mobiele meetgegevens:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Gegevensservice:"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index f0f8cfa..fd8e728 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ଡିଫଲ୍ଟ ଡାଟା SIMର SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL ବ୍ୟାଣ୍ଡୱିଡଥ୍ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL ବ୍ୟାଣ୍ଡୱିଡଥ୍ (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ଫିଜିକାଲ୍ ଚ୍ୟାନେଲ୍ କନ୍ଫିଗରେସନ୍:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ଫିଜିକାଲ ଚେନେଲ କନଫିଗରେସନଗୁଡ଼ିକ:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"ସେଲ୍ ସୂଚନା ରିଫ୍ରେସ୍ ଦର:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"ସମସ୍ତ ସେଲ୍ ପରିମାପ ସୂଚନା:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"ଡାଟା ସେବା:"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 33ed1bd..8dd6abd 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ਪੂਰਵ-ਨਿਰਧਾਰਤ ਡਾਟਾ ਸਿਮ ਦਾ SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL ਬੈਂਡਵਿਡਥ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL ਬੈਂਡਵਿਡਥ (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ਭੌਤਿਕ ਚੈਨਲ ਸੰਰੂਪਣ:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ਭੌਤਿਕ ਚੈਨਲ ਸੰਰੂਪਣ:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"ਸੈੱਲ ਦੀ ਜਾਣਕਾਰੀ ਦੀ ਰਿਫ੍ਰੈਸ਼ ਦਰ:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"ਸਾਰੀ ਸੈੱਲ ਮਾਪ ਜਾਣਕਾਰੀ:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"ਡਾਟਾ ਸੇਵਾ:"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 9e4927e..18ff3b1 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Identyfikator domyślnej karty SIM do transmisji danych:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Przepustowość kanału DL (kb/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Przepustowość kanału UL (kb/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfiguracja kanału fizycznego LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfiguracje kanału fizycznego:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Częstotliwość odświeżania informacji o sieci komórkowej:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Wszystkie informacje pomiarowe z sieci komórkowej:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Usługa transmisji danych:"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index cb028c9..10fb31a 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubID do SIM de dados predefinido:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Largura de banda de transferência (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Largura de banda de carregamento (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuração do canal físico LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configurações do canal físico:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Taxa de atualização das informações da célula:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Todas as informações de medição de células:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Serviço de dados:"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 78cda63..02288b9 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Subcódigo do chip de dados padrão:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Largura de banda DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Largura de banda UL (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuração do canal físico de LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configurações do canal físico:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Taxa de atualização das informações do celular:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Todas as informações de medição do celular:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Serviço de dados:"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index e48fcaa..94cf9d4 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId pentru SIM-ul de date prestabilit:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Lățime de bandă de descărcare (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Lățime de bandă de încărcare (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configurarea canalului fizic LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Configurațiile canalului fizic:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Rata de actualizare a informațiilor despre celulă:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Toate informațiile de măsurare despre celulă:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Serviciu de date:"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index dbf6842..ec534c6 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Доп. идентификатор SIM-карты для мобильного Интернета по умолчанию:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Пропускная способность DL-канала (кбит/c):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Пропускная способность UL-канала (кбит/с):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Конфигурация физического канала LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Конфигурации физического канала:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Частота обновления данных о сетях:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Статистика сети:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Сервис для передачи данных:"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 4206329..22f0a1c 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"පෙරනිමි දත්ත SIM පතේ උප හැඳුනුම:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL කලාප පළල (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL කලාප පළල (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE භෞතික නාලිකා වින්යාසය:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"භෞතික නාලිකා වින්යාස කිරීම්:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"සෙල් තොරතුරු නැවුම් කිරීමේ අනුපාතය:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"සියලු සෙල් මිනුම් තොරතුරු:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"දත්ත සේවාව:"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 26e66ca..623f3c0 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Podradený identifikátor predvolenej dátovej SIM karty:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Rýchlosť pripojenia DL (kB/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Rýchlosť pripojenia UL (kB/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfigurácia fyzického kanála LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfigurácie fyzického kanála:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Frekvencia obnovenia informácií o mobilnej sieti:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Všetky informácie o meraní mobilnej siete:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Dátová služba:"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index c92a975..975cdc7 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ID naročnine privzete kartice SIM za prenos podatkov:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Pasovna širina za prenos (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Pasovna širina za nalaganje (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfiguracija fizičnega kanala LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Fizične konfiguracije kanalov:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Frekvenca osveževanja podatkov o celici:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Vsi podatki o meritvah celice:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Podatkovna storitev:"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 3a9307a..0d4d021 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ID-ja dytësore e kartës SIM të parazgjedhur të të dhënave:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Gjerësia e bandës DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Gjerësia e bandës UL (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Konfigurimi i kanalit fizik LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfigurimet e kanalit fizik:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Shpejtësia e rifreskimit të informacioneve të rrjetit celular"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Të gjitha informacionet e matjes së rrjetit celular:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Shërbimi i të dhënave:"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 81073b0..d25009f 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubID подразумеваног SIM-а за податке:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL пропусни опсег (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL пропусни опсег (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Конфигурација LTE физичког канала:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Конфигурације физичког канала:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Учесталост освежавања информација о предајнику:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Све информације о мерењу за предајник:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Услуга преноса података:"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 2b25ed6..19ce90f 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId för standarddata på SIM-kortet:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Bandbredd för nedladdning (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Bandbredd för uppladdning (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Fysisk kanalkonfiguration för LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Konfigurationer för fysiska kanaler:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Frekvens för uppdatering av mastinformation:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Alla information om mastmätning:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Datatjänst:"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index e1b02a0..9e1f6fe 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId ya SIM chaguomsingi ya data:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Kipimo Data cha DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Kipimo Data cha UL (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Mipangilio ya Kituo Halisi cha LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Mipangilio ya Kituo Halisi:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Kasi ya Kuonyesha Upya Maelezo ya Simu:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Maelezo Yote ya Vipimo vya Simu:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Huduma ya Data:"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index c2cf5f4..620d4c0 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"இயல்பான டேட்டா சிம்மின் துணை ஐடி:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL இணைய வேகம் (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL இணைய வேகம் (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE ஃபிசிக்கல் சேனல் உள்ளமைவு:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"சேனல் உள்ளமைவுகள்:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"மொபைல் தகவலின் புதுப்பிப்பு விகிதம்:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"அனைத்து மொபைல் அளவீட்டுத் தகவல்:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"டேட்டா சேவை:"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index f0f50bb..45417a6 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ఆటోమేటిక్ డేటా SIM యొక్క SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL బ్యాండ్విడ్త్ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL బ్యాండ్విడ్త్ (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE భౌతిక ఛానెల్ కాన్ఫిగరేషన్:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"ఫిజికల్ ఛానెల్ కాన్ఫిగరేషన్లు:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"సెల్ సమాచార రిఫ్రెష్ సగటు:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"మొత్తం సెల్ పరిమాణ సమాచారం:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"డేటా సేవ:"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index c9dd1c3..1ada10d 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId ของซิมอินเทอร์เน็ตเริ่มต้น:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"แบนด์วิดท์ดาวน์โหลด (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"แบนด์วิดท์อัปโหลด (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"การกำหนดค่าแชเนลทางกายภาพของ LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"การกำหนดค่าแชแนลทางกายภาพ:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"อัตราการรีเฟรชข้อมูลมือถือ:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"ข้อมูลการวัดเครือข่ายมือถือทั้งหมด:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"บริการข้อมูล:"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 94dec21..81d5bd3 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"SubId ng default na data SIM:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL Bandwidth (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL Bandwidth (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Configuration ng LTE Physical Channel:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Mga Configuration ng Physical Channel:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Rate ng Pag-refresh ng Impormasyon ng Cell:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Impormasyon ng Pagsukat sa Lahat ng Cell:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Serbisyo ng Data:"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 4c176ce..40eb5e7 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Varsayılan veri SIM\'inin alt kimliği:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"İndirme Bant Genişliği (kb/sn.):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Yükleme Bant Genişliği (kb/sn.):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE Fiziksel Kanal Yapılandırması:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Fiziksel Kanal Yapılandırmaları:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Hücre Bilgilerini Yenileme Hızı:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Tüm Hücre Ölçümü Bilgileri:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Veri Hizmeti:"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index d9e8d8c..345d8e7 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Субідентифікатор SIM-карти для даних за умовчанням:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Пропускна спроможність DL (кбіт/с):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Пропускна спроможність UL (кбіт/с):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Конфігурація фізичного каналу LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Конфігурації фізичного каналу:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Частота оновлення даних про мобільний зв\'язок:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Усі дані про показники мобільного зв\'язку:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Мобільний Інтернет:"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 71a2373..520f493 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"ڈیفالٹ ڈیٹا SIM کی SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL بینڈ وڈتھ (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL بینڈ وڈتھ (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE فزيکل چینل کنفیگریشن:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"چینل کی طبعی کنفیگریشنز:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"سیل کی معلومات ریفریش کرنے کی شرح:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"سیل پیمائش کی تمام معلومات:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"ڈیٹا سروس:"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 2adc7ba..19d5563 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Mobil internet uchun birlamchi SIM kartaning qoʻshimcha identifikatori:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Axborot uzatish tezligi (kbit/s):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL kanalining axborot uzatish tezligi (kbit/s):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE jismoniy kanal konfiguratsiyasi:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Tashqi kanal konfiguratsiyalari:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Tarmoq haqidagi axborotning yangilanish darajasi:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Tarmoq statistikasi:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Mobil internet xizmati:"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index a4a0e35..ebaf17f 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"Mã phụ của SIM dữ liệu mặc định:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Băng thông DL (kb/giây):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Băng thông UL (kb/giây):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Cấu hình kênh LTE thực:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Cấu hình của kênh thực tế:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Tốc độ làm mới thông tin mạng di động:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Toàn bộ thông tin về số đo mạng di động:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Dịch vụ dữ liệu:"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 4aebfe8..f439c6b 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"默认数据 SIM 卡的 SubId:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"DL 带宽 (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"UL 带宽 (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE 物理信道配置:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"实体频道配置:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"移动网络信息刷新频率:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"所有移动网络测量信息:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"数据服务:"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index c5eef28..0fadc35 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"預設數據 SIM 卡的子 ID:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"下載頻寬 (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"上載頻寬 (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE 實體渠道設定:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"實體頻道設定:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"發射站資料重新整理頻率:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"所有發射站量度資料:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"數據服務:"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 52a8814..c2aa76c 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"預設資料 SIM 卡的子 ID:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"下行頻寬 (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"上行頻寬 (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"LTE 實體通道設定:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"實體頻道設定:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"行動網路資訊重新整理頻率:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"所有行動網路測量資訊:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"數據服務:"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 51a33ac..452a3c8 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -876,7 +876,7 @@
<string name="radio_info_dds" msgid="1122593144425697126">"I-SubId ye-SIM yedatha yokuzenzakalela:"</string>
<string name="radio_info_dl_kbps" msgid="2382922659525318726">"Umkhawulokudonsa we-DL (kbps):"</string>
<string name="radio_info_ul_kbps" msgid="2102225400904799036">"Umkhawulokudonsa we-UL (kbps):"</string>
- <string name="radio_info_phy_chan_config" msgid="1277949603275436081">"Ukulungiselelwa okuphathekayo kwesiteshi se-LTE:"</string>
+ <string name="radio_info_phy_chan_config" msgid="608045501232211303">"Ukucushwa Kwesiteshi Esiphathekayo:"</string>
<string name="radio_info_cell_info_refresh_rate" msgid="670511448975997340">"Isilinganiso sokuqalisa kabusha solwazi lweseli:"</string>
<string name="radio_info_cellinfo_label" msgid="8199062974670377659">"Ulwazi lwesilinganiso seseli:"</string>
<string name="radio_info_gprs_service_label" msgid="6819204246355412952">"Isevisi yedatha:"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index ba65302..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/res/values/strings.xml b/res/values/strings.xml
index b32b030..61143c9 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2101,7 +2101,7 @@
<!-- Radio Info screen. Label for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="radio_info_ul_kbps">UL Bandwidth (kbps):</string>
<!-- Radio Info screen. Label for a status item. Used for diagnostic info screens, precise translation isn't needed -->
- <string name="radio_info_phy_chan_config">LTE Physical Channel Configuration:</string>
+ <string name="radio_info_phy_chan_config">Physical Channel Configurations:</string>
<!-- Radio Info screen. Label for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="radio_info_cell_info_refresh_rate">Cell Info Refresh Rate:</string>
<!-- Radio Info screen. Label for a status item. Used for diagnostic info screens, precise translation isn't needed -->
diff --git a/src/com/android/phone/CallNotifier.java b/src/com/android/phone/CallNotifier.java
index 73b61b6..daf3aa2 100644
--- a/src/com/android/phone/CallNotifier.java
+++ b/src/com/android/phone/CallNotifier.java
@@ -489,7 +489,7 @@
public void updatePhoneStateListeners(boolean isRefresh, int updateType, int subIdToUpdate) {
List<SubscriptionInfo> subInfos = SubscriptionManagerService.getInstance()
.getActiveSubscriptionInfoList(mApplication.getOpPackageName(),
- mApplication.getAttributionTag());
+ mApplication.getAttributionTag(), true/*isForAllProfile*/);
// Sort sub id list based on slot id, so that CFI/MWI notifications will be updated for
// slot 0 first then slot 1. This is needed to ensure that when CFI or MWI is enabled for
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index fa85f27..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;
@@ -39,6 +42,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.PermissionEnforcer;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
@@ -65,10 +69,11 @@
import com.android.internal.telephony.PhoneConfigurationManager;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.TelephonyPermissions;
+import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.subscription.SubscriptionManagerService;
import com.android.internal.telephony.util.ArrayUtils;
+import com.android.internal.telephony.util.TelephonyUtils;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.telephony.Rlog;
import java.io.File;
import java.io.FileDescriptor;
@@ -78,12 +83,19 @@
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* CarrierConfigLoader binds to privileged carrier apps to fetch carrier config overlays.
@@ -91,6 +103,9 @@
public class CarrierConfigLoader extends ICarrierConfigLoader.Stub {
private static final String LOG_TAG = "CarrierConfigLoader";
+ private static final SimpleDateFormat TIME_FORMAT =
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
+
// Package name for platform carrier config app, bundled with system image.
@NonNull private final String mPlatformCarrierConfigPackage;
@@ -129,7 +144,7 @@
// Broadcast receiver for system events
@NonNull
private final BroadcastReceiver mSystemBroadcastReceiver = new ConfigLoaderBroadcastReceiver();
- @NonNull private final LocalLog mCarrierConfigLoadingLog = new LocalLog(100);
+ @NonNull private final LocalLog mCarrierConfigLoadingLog = new LocalLog(256);
// Number of phone instances (active modem count)
private int mNumPhones;
@@ -228,7 +243,7 @@
@Override
public void handleMessage(@NonNull Message msg) {
final int phoneId = msg.arg1;
- logdWithLocalLog("mHandler: " + eventToString(msg.what) + " phoneId: " + phoneId);
+ logd(eventToString(msg.what) + " phoneId: " + phoneId);
if (!SubscriptionManager.isValidPhoneId(phoneId)
&& msg.what != EVENT_MULTI_SIM_CONFIG_CHANGED) {
return;
@@ -276,19 +291,11 @@
PersistableBundle config = restoreConfigFromXml(
mPlatformCarrierConfigPackage, OVERRIDE_PACKAGE_ADDITION, phoneId);
if (config != null) {
- logd("Loaded persistent override config from XML. package="
- + mPlatformCarrierConfigPackage
- + " phoneId=" + phoneId);
mPersistentOverrideConfigs[phoneId] = config;
}
config = restoreConfigFromXml(mPlatformCarrierConfigPackage, "", phoneId);
if (config != null) {
- logd(
- "Loaded config from XML. package="
- + mPlatformCarrierConfigPackage
- + " phoneId="
- + phoneId);
mConfigFromDefaultApp[phoneId] = config;
Message newMsg = obtainMessage(EVENT_FETCH_DEFAULT_DONE, phoneId, -1);
newMsg.getData().putBoolean("loaded_from_xml", true);
@@ -363,10 +370,10 @@
carrierService.getCarrierConfig(phoneId, carrierId, resultReceiver);
logdWithLocalLog("Fetch config for default app: "
+ mPlatformCarrierConfigPackage
- + " carrierid: " + carrierId.toString());
+ + ", carrierId=" + carrierId.getSpecificCarrierId());
} catch (RemoteException e) {
loge("Failed to get carrier config from default app: " +
- mPlatformCarrierConfigPackage + " err: " + e.toString());
+ mPlatformCarrierConfigPackage + " err: " + e);
unbindIfBound(mContext, conn, phoneId);
break; // So we don't set a timeout.
}
@@ -418,11 +425,6 @@
final PersistableBundle config =
restoreConfigFromXml(carrierPackageName, "", phoneId);
if (config != null) {
- logd(
- "Loaded config from XML. package="
- + carrierPackageName
- + " phoneId="
- + phoneId);
mConfigFromCarrierApp[phoneId] = config;
Message newMsg = obtainMessage(EVENT_FETCH_CARRIER_DONE, phoneId, -1);
newMsg.getData().putBoolean("loaded_from_xml", true);
@@ -503,9 +505,9 @@
carrierService.getCarrierConfig(phoneId, carrierId, resultReceiver);
logdWithLocalLog("Fetch config for carrier app: "
+ getCarrierPackageForPhoneId(phoneId)
- + " carrierid: " + carrierId.toString());
+ + ", carrierId=" + carrierId.getSpecificCarrierId());
} catch (RemoteException e) {
- loge("Failed to get carrier config: " + e.toString());
+ loge("Failed to get carrier config: " + e);
unbindIfBound(mContext, conn, phoneId);
break; // So we don't set a timeout.
}
@@ -577,8 +579,6 @@
restoreNoSimConfigFromXml(mPlatformCarrierConfigPackage);
if (config != null) {
- logd("Loaded no SIM config from XML. package="
- + mPlatformCarrierConfigPackage);
mNoSimConfig = config;
sendMessage(
obtainMessage(
@@ -673,7 +673,7 @@
+ mPlatformCarrierConfigPackage);
} catch (RemoteException e) {
loge("Failed to get no sim carrier config from default app: " +
- mPlatformCarrierConfigPackage + " err: " + e.toString());
+ mPlatformCarrierConfigPackage + " err: " + e);
unbindIfBoundForNoSimConfig(mContext, conn, phoneId);
break; // So we don't set a timeout.
}
@@ -689,12 +689,18 @@
@NonNull private final Handler mHandler;
+ @NonNull private final FeatureFlags mFeatureFlags;
+
+ @NonNull private final PackageManager mPackageManager;
+
/**
* Constructs a CarrierConfigLoader, registers it as a service, and registers a broadcast
* receiver for relevant events.
*/
@VisibleForTesting
- /* package */ CarrierConfigLoader(@NonNull Context context, @NonNull Looper looper) {
+ /* package */ CarrierConfigLoader(@NonNull Context context, @NonNull Looper looper,
+ @NonNull FeatureFlags featureFlags) {
+ super(PermissionEnforcer.fromContext(context));
mContext = context;
mPlatformCarrierConfigPackage =
mContext.getString(R.string.platform_carrier_config_package);
@@ -722,6 +728,8 @@
TelephonyManager.from(context).registerCarrierPrivilegesCallback(phoneId,
new HandlerExecutor(mHandler), mCarrierServiceChangeCallbacks[phoneId]);
}
+ mFeatureFlags = featureFlags;
+ mPackageManager = context.getPackageManager();
logd("CarrierConfigLoader has started");
PhoneConfigurationManager.registerForMultiSimConfigChange(
@@ -736,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);
@@ -1003,7 +1012,7 @@
int phoneId, @Nullable CarrierIdentifier carrierId, @NonNull PersistableBundle config,
boolean isNoSimConfig) {
if (packageName == null) {
- loge("Cannot save config with null packageName");
+ loge("Cannot save config with null packageName. phoneId=" + phoneId);
return;
}
@@ -1013,7 +1022,7 @@
} else {
if (TelephonyManager.getSimStateForSlotIndex(phoneId)
!= TelephonyManager.SIM_STATE_LOADED) {
- loge("Skip save config because SIM records are not loaded.");
+ loge("Skip saving config because SIM records are not loaded. phoneId=" + phoneId);
return;
}
@@ -1021,7 +1030,7 @@
final int cid = carrierId != null ? carrierId.getSpecificCarrierId()
: TelephonyManager.UNKNOWN_CARRIER_ID;
if (iccid == null) {
- loge("Cannot save config with null iccid.");
+ loge("Cannot save config with null iccid. phoneId=" + phoneId);
return;
}
fileName = getFilenameForConfig(packageName, extraString, iccid, cid);
@@ -1038,12 +1047,12 @@
final String version = getPackageVersion(packageName);
if (version == null) {
- loge("Failed to get package version for: " + packageName);
+ loge("Failed to get package version for: " + packageName + ", phoneId=" + phoneId);
return;
}
- logdWithLocalLog(
- "Save config to xml, packagename: " + packageName + " phoneId: " + phoneId);
+ logdWithLocalLog("Save carrier config to cache. phoneId=" + phoneId
+ + ", xml=" + getFilePathForLogging(fileName) + ", version=" + version);
FileOutputStream outFile = null;
try {
@@ -1104,52 +1113,52 @@
} else {
if (TelephonyManager.getSimStateForSlotIndex(phoneId)
!= TelephonyManager.SIM_STATE_LOADED) {
- loge("Skip restore config because SIM records are not loaded.");
+ loge("Skip restore config because SIM records are not loaded. phoneId=" + phoneId);
return null;
}
iccid = getIccIdForPhoneId(phoneId);
final int cid = getSpecificCarrierIdForPhoneId(phoneId);
if (iccid == null) {
- loge("Cannot restore config with null iccid.");
+ loge("Cannot restore config with null iccid. phoneId=" + phoneId);
return null;
}
fileName = getFilenameForConfig(packageName, extraString, iccid, cid);
}
PersistableBundle restoredBundle = null;
- File file = null;
- FileInputStream inFile = null;
- try {
- file = new File(mContext.getFilesDir(),fileName);
- inFile = new FileInputStream(file);
+ File file = new File(mContext.getFilesDir(), fileName);
+ String filePath = file.getPath();
+ String savedVersion = null;
+ try (FileInputStream inFile = new FileInputStream(file)) {
restoredBundle = PersistableBundle.readFromStream(inFile);
- String savedVersion = restoredBundle.getString(KEY_VERSION);
+ savedVersion = restoredBundle.getString(KEY_VERSION);
restoredBundle.remove(KEY_VERSION);
if (!version.equals(savedVersion)) {
- loge("Saved version mismatch: " + version + " vs " + savedVersion);
+ loge("Saved version mismatch: " + version + " vs " + savedVersion
+ + ", phoneId=" + phoneId);
restoredBundle = null;
}
-
- inFile.close();
} catch (FileNotFoundException e) {
// Missing file is normal occurrence that might occur with a new sim or when restoring
// an override file during boot and should not be treated as an error.
- if (file != null) {
- if (isNoSimConfig) {
- logd("File not found: " + file.getPath());
- } else {
- String filePath = file.getPath();
- filePath = getFilePathForLogging(filePath, iccid);
- logd("File not found : " + filePath);
- }
+ if (isNoSimConfig) {
+ logd("File not found: " + file.getPath() + ", phoneId=" + phoneId);
+ } else {
+ logd("File not found : " + getFilePathForLogging(filePath, iccid)
+ + ", phoneId=" + phoneId);
}
} catch (IOException e) {
loge(e.toString());
}
+ if (restoredBundle != null) {
+ logdWithLocalLog("Restored carrier config from cache. phoneId=" + phoneId + ", xml="
+ + getFilePathForLogging(fileName) + ", version=" + savedVersion
+ + ", modified time=" + getFileTime(filePath));
+ }
return restoredBundle;
}
@@ -1159,7 +1168,7 @@
@NonNull
private String getFilePathForLogging(@Nullable String filePath, @Nullable String iccid) {
// If loggable then return with actual file path
- if (Rlog.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ if (TelephonyUtils.IS_DEBUGGABLE) {
return filePath;
}
String path = filePath;
@@ -1203,7 +1212,7 @@
});
if (packageFiles == null || packageFiles.length < 1) return false;
for (File f : packageFiles) {
- logd("Deleting " + getFilePathForLogging(f.getName()));
+ logdWithLocalLog("Deleting " + getFilePathForLogging(f.getName()));
f.delete();
}
return true;
@@ -1326,6 +1335,8 @@
return new PersistableBundle();
}
+ enforceTelephonyFeatureWithException(callingPackage, "getConfigForSubIdWithFeature");
+
int phoneId = SubscriptionManager.getPhoneId(subscriptionId);
PersistableBundle retConfig = CarrierConfigManager.getDefaultConfig();
if (SubscriptionManager.isValidPhoneId(phoneId)) {
@@ -1370,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
@@ -1408,17 +1422,20 @@
return configSubset;
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@Override
public void overrideConfig(int subscriptionId, @Nullable PersistableBundle overrides,
boolean persistent) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_PHONE_STATE, null);
+ overrideConfig_enforcePermission();
int phoneId = SubscriptionManager.getPhoneId(subscriptionId);
if (!SubscriptionManager.isValidPhoneId(phoneId)) {
logd("Ignore invalid phoneId: " + phoneId + " for subId: " + subscriptionId);
throw new IllegalArgumentException(
"Invalid phoneId " + phoneId + " for subId " + subscriptionId);
}
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(), "overrideConfig");
+
// Post to run on handler thread on which all states should be confined.
mHandler.post(() -> {
overrideConfig(mOverrideConfigs, phoneId, overrides);
@@ -1440,6 +1457,8 @@
fileToDelete.delete();
}
}
+ logdWithLocalLog("overrideConfig: subId=" + subscriptionId + ", persistent="
+ + persistent + ", overrides=" + overrides);
updateSubscriptionDatabase(phoneId);
});
}
@@ -1469,6 +1488,12 @@
"Invalid phoneId " + phoneId + " for subId " + subscriptionId);
}
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ "notifyConfigChangedForSubId");
+
+ logdWithLocalLog("Notified carrier config changed. phoneId=" + phoneId
+ + ", subId=" + subscriptionId);
+
// This method should block until deleting has completed, so that an error which prevents us
// from clearing the cache is passed back to the carrier app. With the files successfully
// deleted, this can return and we will eventually bind to the carrier app.
@@ -1478,14 +1503,17 @@
updateConfigForPhoneId(phoneId);
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@Override
public void updateConfigForPhoneId(int phoneId, @NonNull String simState) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_PHONE_STATE, null);
- logdWithLocalLog("Update config for phoneId: " + phoneId + " simState: " + simState);
+ updateConfigForPhoneId_enforcePermission();
+ logdWithLocalLog("Update config for phoneId=" + phoneId + " simState=" + simState);
if (!SubscriptionManager.isValidPhoneId(phoneId)) {
throw new IllegalArgumentException("Invalid phoneId: " + phoneId);
}
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(), "updateConfigForPhoneId");
+
// requires Java 7 for switch on string.
switch (simState) {
case IccCardConstants.INTENT_VALUE_ICC_ABSENT:
@@ -1502,12 +1530,15 @@
}
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@Override
@NonNull
public String getDefaultCarrierServicePackageName() {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ getDefaultCarrierServicePackageName_enforcePermission();
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
"getDefaultCarrierServicePackageName");
+
return mPlatformCarrierConfigPackage;
}
@@ -1573,6 +1604,27 @@
}
/**
+ * Get the file time in readable format.
+ *
+ * @param filePath The full file path.
+ *
+ * @return The time in string format.
+ */
+ @Nullable
+ private String getFileTime(@NonNull String filePath) {
+ String formattedModifiedTime = null;
+ try {
+ // Convert the modified time to a readable format
+ formattedModifiedTime = TIME_FORMAT.format(Files.readAttributes(Paths.get(filePath),
+ BasicFileAttributes.class).lastModifiedTime().toMillis());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return formattedModifiedTime;
+ }
+
+ /**
* If {@code args} contains {@link #DUMP_ARG_REQUESTING_PACKAGE} and a following package name,
* we'll also call {@link IBinder#dump} on the default carrier service (if bound) and the
* specified carrier service (if bound). Typically, this is done for connectivity bug reports
@@ -1613,8 +1665,20 @@
}
printConfig(mNoSimConfig, indentPW, "mNoSimConfig");
- indentPW.println("CarrierConfigLoadingLog=");
+ indentPW.println("mNumPhones=" + mNumPhones);
+ indentPW.println("mPlatformCarrierConfigPackage=" + mPlatformCarrierConfigPackage);
+ indentPW.println("mServiceConnection=[" + Stream.of(mServiceConnection)
+ .map(c -> c != null ? c.pkgName : null)
+ .collect(Collectors.joining(", ")) + "]");
+ indentPW.println("mServiceBoundForNoSimConfig="
+ + Arrays.toString(mServiceBoundForNoSimConfig));
+ indentPW.println("mHasSentConfigChange=" + Arrays.toString(mHasSentConfigChange));
+ indentPW.println("mFromSystemUnlocked=" + Arrays.toString(mFromSystemUnlocked));
+ indentPW.println();
+ indentPW.println("CarrierConfigLoader local log=");
+ indentPW.increaseIndent();
mCarrierConfigLoadingLog.dump(fd, indentPW, args);
+ indentPW.decreaseIndent();
if (requestingPackage != null) {
logd("Including default and requesting package " + requestingPackage
@@ -1626,6 +1690,16 @@
dumpCarrierServiceIfBound(fd, indentPW, "Requesting package", requestingPackage,
true /* considerCarrierPrivileges */);
}
+
+ indentPW.println();
+ indentPW.println("Cached config files:");
+ indentPW.increaseIndent();
+ for (File f : mContext.getFilesDir().listFiles((FilenameFilter) (d, filename)
+ -> filename.startsWith("carrierconfig-"))) {
+ indentPW.println(getFilePathForLogging(f.getName()) + ", modified time="
+ + getFileTime(f.getAbsolutePath()));
+ }
+ indentPW.decreaseIndent();
}
private void printConfig(@NonNull PersistableBundle configApp,
@@ -1775,6 +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 6145870..4f94b58 100644
--- a/src/com/android/phone/CdmaCallOptions.java
+++ b/src/com/android/phone/CdmaCallOptions.java
@@ -16,8 +16,10 @@
package com.android.phone;
+import android.content.Context;
import android.os.Bundle;
import android.os.PersistableBundle;
+import android.os.UserManager;
import android.preference.Preference;
import android.preference.PreferenceScreen;
import android.telephony.CarrierConfigManager;
@@ -30,6 +32,7 @@
import android.view.MenuItem;
import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.flags.Flags;
public class CdmaCallOptions extends TimeConsumingPreferenceActivity {
private static final String LOG_TAG = "CdmaCallOptions";
@@ -78,9 +81,21 @@
buttonVoicePrivacy.setEnabled(false);
}
+ // If mobile network configs are restricted, then hide the mCallForwardingPref and
+ // mCallWaitingPref.
+ UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
+ boolean mobileNetworkConfigsRestricted =
+ userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
+ if (Flags.ensureAccessToCallSettingsIsRestricted() && mobileNetworkConfigsRestricted) {
+ Log.i(LOG_TAG, "Mobile network configs are restricted, hiding CDMA call forwarding "
+ + "and CDMA call waiting options.");
+ }
+
mCallForwardingPref = getPreferenceScreen().findPreference(CALL_FORWARDING_KEY);
if (carrierConfig != null && carrierConfig.getBoolean(
- CarrierConfigManager.KEY_CALL_FORWARDING_VISIBILITY_BOOL)) {
+ CarrierConfigManager.KEY_CALL_FORWARDING_VISIBILITY_BOOL) &&
+ (!mobileNetworkConfigsRestricted ||
+ !Flags.ensureAccessToCallSettingsIsRestricted())) {
mCallForwardingPref.setIntent(
subInfoHelper.getIntent(CdmaCallForwardOptions.class));
} else {
@@ -91,7 +106,9 @@
mCallWaitingPref = (CdmaCallWaitingPreference) getPreferenceScreen()
.findPreference(CALL_WAITING_KEY);
if (carrierConfig == null || !carrierConfig.getBoolean(
- CarrierConfigManager.KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL)) {
+ CarrierConfigManager.KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL) ||
+ (Flags.ensureAccessToCallSettingsIsRestricted() &&
+ mobileNetworkConfigsRestricted)) {
getPreferenceScreen().removePreference(mCallWaitingPref);
mCallWaitingPref = null;
}
diff --git a/src/com/android/phone/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 51d1b66..be5295d 100644
--- a/src/com/android/phone/GsmUmtsCallOptions.java
+++ b/src/com/android/phone/GsmUmtsCallOptions.java
@@ -16,16 +16,20 @@
package com.android.phone;
+import android.content.Context;
import android.os.Bundle;
import android.os.PersistableBundle;
+import android.os.UserManager;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
+import android.util.Log;
import android.view.MenuItem;
import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.flags.Flags;
public class GsmUmtsCallOptions extends PreferenceActivity {
private static final String LOG_TAG = "GsmUmtsCallOptions";
@@ -79,10 +83,23 @@
isAirplaneModeOff = PhoneGlobals.AIRPLANE_ON != airplaneMode;
}
+ // If mobile network configs are restricted, then hide the GsmUmtsCallForwardOptions,
+ // GsmUmtsAdditionalCallOptions, and GsmUmtsCallBarringOptions.
+ UserManager userManager = (UserManager) subInfoHelper.getPhone().getContext()
+ .getSystemService(Context.USER_SERVICE);
+ boolean mobileNetworkConfigsRestricted =
+ userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
+ if (Flags.ensureAccessToCallSettingsIsRestricted() && mobileNetworkConfigsRestricted) {
+ Log.i(LOG_TAG, "Mobile network configs are restricted, hiding GSM call "
+ + "forwarding, additional call settings, and call options.");
+ }
+
Preference callForwardingPref = prefScreen.findPreference(CALL_FORWARDING_KEY);
if (callForwardingPref != null) {
if (b != null && b.getBoolean(
- CarrierConfigManager.KEY_CALL_FORWARDING_VISIBILITY_BOOL)) {
+ CarrierConfigManager.KEY_CALL_FORWARDING_VISIBILITY_BOOL) &&
+ (!Flags.ensureAccessToCallSettingsIsRestricted() ||
+ !mobileNetworkConfigsRestricted)) {
callForwardingPref.setIntent(
subInfoHelper.getIntent(GsmUmtsCallForwardOptions.class));
callForwardingPref.setEnabled(isAirplaneModeOff);
@@ -97,7 +114,9 @@
if (b != null && (b.getBoolean(
CarrierConfigManager.KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL)
|| b.getBoolean(
- CarrierConfigManager.KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL))) {
+ CarrierConfigManager.KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL)) &&
+ (!Flags.ensureAccessToCallSettingsIsRestricted() ||
+ !mobileNetworkConfigsRestricted)) {
additionalGsmSettingsPref.setIntent(
subInfoHelper.getIntent(GsmUmtsAdditionalCallOptions.class));
additionalGsmSettingsPref.setEnabled(isAirplaneModeOff);
@@ -108,7 +127,9 @@
Preference callBarringPref = prefScreen.findPreference(CALL_BARRING_KEY);
if (callBarringPref != null) {
- if (b != null && b.getBoolean(CarrierConfigManager.KEY_CALL_BARRING_VISIBILITY_BOOL)) {
+ if (b != null && b.getBoolean(CarrierConfigManager.KEY_CALL_BARRING_VISIBILITY_BOOL) &&
+ (!Flags.ensureAccessToCallSettingsIsRestricted() ||
+ !mobileNetworkConfigsRestricted)) {
callBarringPref.setIntent(subInfoHelper.getIntent(GsmUmtsCallBarringOptions.class));
callBarringPref.setEnabled(isAirplaneModeOff);
} else {
diff --git a/src/com/android/phone/IccNetworkDepersonalizationPanel.java b/src/com/android/phone/IccNetworkDepersonalizationPanel.java
index a4ec8a4..7099476 100644
--- a/src/com/android/phone/IccNetworkDepersonalizationPanel.java
+++ b/src/com/android/phone/IccNetworkDepersonalizationPanel.java
@@ -112,7 +112,7 @@
}
public static void dialogDismiss(int phoneId) {
- if (phoneId >= sShowingDialog.length) {
+ if (phoneId >= sShowingDialog.length || !SubscriptionManager.isValidPhoneId(phoneId)) {
Log.e(TAG, "[IccNetworkDepersonalizationPanel] - dismiss; invalid phoneId " + phoneId);
return;
}
diff --git a/src/com/android/phone/ImsProvisioningController.java b/src/com/android/phone/ImsProvisioningController.java
index a62980e..b2e34ae 100644
--- a/src/com/android/phone/ImsProvisioningController.java
+++ b/src/com/android/phone/ImsProvisioningController.java
@@ -839,7 +839,7 @@
mSubscriptionManager = mApp.getSystemService(SubscriptionManager.class);
mTelephonyRegistryManager = mApp.getSystemService(TelephonyRegistryManager.class);
mTelephonyRegistryManager.addOnSubscriptionsChangedListener(
- mSubChangedListener, mSubChangedListener.getHandlerExecutor());
+ mSubChangedListener, mHandler::post);
mImsProvisioningLoader = imsProvisioningLoader;
PhoneConfigurationManager.registerForMultiSimConfigChange(mHandler,
diff --git a/src/com/android/phone/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java
index 3f35454..a778f6a 100644
--- a/src/com/android/phone/ImsRcsController.java
+++ b/src/com/android/phone/ImsRcsController.java
@@ -16,7 +16,13 @@
package com.android.phone;
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY_IMS;
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION;
+import static android.telephony.TelephonyManager.ENABLE_FEATURE_MAPPING;
+
import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
@@ -53,6 +59,7 @@
import com.android.internal.telephony.ISipDialogStateCallback;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.TelephonyPermissions;
+import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.ims.ImsResolver;
import com.android.services.telephony.rcs.RcsFeatureController;
import com.android.services.telephony.rcs.SipTransportController;
@@ -74,6 +81,8 @@
private PhoneGlobals mApp;
private TelephonyRcsService mRcsService;
private ImsResolver mImsResolver;
+ private FeatureFlags mFeatureFlags;
+ private PackageManager mPackageManager;
// set by shell cmd phone src set-device-enabled true/false
private Boolean mSingleRegistrationOverride;
@@ -90,10 +99,10 @@
* Initialize the singleton ImsRcsController instance.
* This is only done once, at startup, from PhoneApp.onCreate().
*/
- static ImsRcsController init(PhoneGlobals app) {
+ static ImsRcsController init(PhoneGlobals app, FeatureFlags featureFlags) {
synchronized (ImsRcsController.class) {
if (sInstance == null) {
- sInstance = new ImsRcsController(app);
+ sInstance = new ImsRcsController(app, featureFlags);
} else {
Log.wtf(TAG, "init() called multiple times! sInstance = " + sInstance);
}
@@ -102,9 +111,11 @@
}
/** Private constructor; @see init() */
- private ImsRcsController(PhoneGlobals app) {
+ private ImsRcsController(PhoneGlobals app, FeatureFlags featureFlags) {
Log.i(TAG, "ImsRcsController");
mApp = app;
+ mFeatureFlags = featureFlags;
+ mPackageManager = mApp.getPackageManager();
TelephonyFrameworkInitializer
.getTelephonyServiceManager().getTelephonyImsServiceRegisterer().register(this);
mImsResolver = ImsResolver.getInstance();
@@ -118,6 +129,10 @@
public void registerImsRegistrationCallback(int subId, IImsRegistrationCallback callback) {
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "registerImsRegistrationCallback");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "registerImsRegistrationCallback");
+
final long token = Binder.clearCallingIdentity();
try {
getRcsFeatureController(subId).registerImsRegistrationCallback(subId, callback);
@@ -136,6 +151,10 @@
public void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback callback) {
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "unregisterImsRegistrationCallback");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "unregisterImsRegistrationCallback");
+
final long token = Binder.clearCallingIdentity();
try {
getRcsFeatureController(subId).unregisterImsRegistrationCallback(subId, callback);
@@ -153,6 +172,10 @@
public void getImsRcsRegistrationState(int subId, IIntegerConsumer consumer) {
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getImsRcsRegistrationState");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "getImsRcsRegistrationState");
+
final long token = Binder.clearCallingIdentity();
try {
getRcsFeatureController(subId).getRegistrationState(regState -> {
@@ -175,6 +198,10 @@
public void getImsRcsRegistrationTransportType(int subId, IIntegerConsumer consumer) {
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getImsRcsRegistrationTransportType");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "getImsRcsRegistrationTransportType");
+
final long token = Binder.clearCallingIdentity();
try {
getRcsFeatureController(subId).getRegistrationTech(regTech -> {
@@ -204,6 +231,10 @@
@Override
public void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback) {
enforceReadPrivilegedPermission("registerRcsAvailabilityCallback");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "registerRcsAvailabilityCallback");
+
final long token = Binder.clearCallingIdentity();
try {
getRcsFeatureController(subId).registerRcsAvailabilityCallback(subId, callback);
@@ -224,6 +255,10 @@
@Override
public void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback) {
enforceReadPrivilegedPermission("unregisterRcsAvailabilityCallback");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "unregisterRcsAvailabilityCallback");
+
final long token = Binder.clearCallingIdentity();
try {
getRcsFeatureController(subId).unregisterRcsAvailabilityCallback(subId, callback);
@@ -247,6 +282,10 @@
@RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
@ImsRegistrationImplBase.ImsRegistrationTech int radioTech) {
enforceReadPrivilegedPermission("isCapable");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isCapable");
+
final long token = Binder.clearCallingIdentity();
try {
return getRcsFeatureController(subId).isCapable(capability, radioTech);
@@ -273,6 +312,10 @@
@RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
@ImsRegistrationImplBase.ImsRegistrationTech int radioTech) {
enforceReadPrivilegedPermission("isAvailable");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isAvailable");
+
final long token = Binder.clearCallingIdentity();
try {
return getRcsFeatureController(subId).isAvailable(capability, radioTech);
@@ -290,6 +333,10 @@
List<Uri> contactNumbers, IRcsUceControllerCallback c) {
enforceAccessUserCapabilityExchangePermission("requestCapabilities");
enforceReadContactsPermission("requestCapabilities");
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ FEATURE_TELEPHONY_IMS, "requestCapabilities");
+
final long token = Binder.clearCallingIdentity();
try {
UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
@@ -311,6 +358,10 @@
String callingFeatureId, Uri contactNumber, IRcsUceControllerCallback c) {
enforceAccessUserCapabilityExchangePermission("requestAvailability");
enforceReadContactsPermission("requestAvailability");
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ FEATURE_TELEPHONY_IMS, "requestAvailability");
+
final long token = Binder.clearCallingIdentity();
try {
UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
@@ -330,6 +381,10 @@
@Override
public @PublishState int getUcePublishState(int subId) {
enforceReadPrivilegedPermission("getUcePublishState");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "getUcePublishState");
+
final int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
boolean isSupportPublishingState = false;
@@ -485,6 +540,10 @@
@Override
public void registerUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c) {
enforceReadPrivilegedPermission("registerUcePublishStateCallback");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "registerUcePublishStateCallback");
+
final int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
boolean isSupportPublishingState = false;
@@ -510,6 +569,10 @@
@Override
public void unregisterUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c) {
enforceReadPrivilegedPermission("unregisterUcePublishStateCallback");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "unregisterUcePublishStateCallback");
+
final long token = Binder.clearCallingIdentity();
try {
UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
@@ -534,6 +597,10 @@
+ "isUceSettingEnabled");
return false;
}
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ FEATURE_TELEPHONY_IMS, "isUceSettingEnabled");
+
final long token = Binder.clearCallingIdentity();
try {
return SubscriptionManager.getBooleanSubscriptionProperty(subId,
@@ -546,6 +613,10 @@
@Override
public void setUceSettingEnabled(int subId, boolean isEnabled) {
enforceModifyPermission();
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "setUceSettingEnabled");
+
final long token = Binder.clearCallingIdentity();
try {
SubscriptionManager.setSubscriptionProperty(subId,
@@ -680,6 +751,10 @@
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
}
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION, "registerSipDialogStateCallback");
+
try {
SipTransportController transport = getRcsFeatureController(subId).getFeature(
SipTransportController.class);
@@ -707,6 +782,10 @@
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
}
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION, "unregisterSipDialogStateCallback");
+
try {
SipTransportController transport = getRcsFeatureController(subId).getFeature(
SipTransportController.class);
@@ -897,6 +976,40 @@
PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION);
}
+ /**
+ * Get the current calling package name.
+ * @return the current calling package name
+ */
+ @Nullable
+ private String getCurrentPackageName() {
+ if (mPackageManager == null) return null;
+ String[] callingUids = mPackageManager.getPackagesForUid(Binder.getCallingUid());
+ return (callingUids == null) ? null : callingUids[0];
+ }
+
+ /**
+ * Make sure the device has required telephony feature
+ *
+ * @throws UnsupportedOperationException if the device does not have required telephony feature
+ */
+ private void enforceTelephonyFeatureWithException(@Nullable String callingPackage,
+ @NonNull String telephonyFeature, @NonNull String methodName) {
+ if (callingPackage == null || mPackageManager == null) {
+ return;
+ }
+
+ if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
+ || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
+ Binder.getCallingUserHandle())) {
+ return;
+ }
+
+ if (!mPackageManager.hasSystemFeature(telephonyFeature)) {
+ throw new UnsupportedOperationException(
+ methodName + " is unsupported without " + telephonyFeature);
+ }
+ }
+
void setRcsService(TelephonyRcsService rcsService) {
mRcsService = rcsService;
}
diff --git a/src/com/android/phone/ImsStateCallbackController.java b/src/com/android/phone/ImsStateCallbackController.java
index edad754..019c1ca 100644
--- a/src/com/android/phone/ImsStateCallbackController.java
+++ b/src/com/android/phone/ImsStateCallbackController.java
@@ -739,7 +739,7 @@
updateFeatureControllerSize(numSlots);
mTelephonyRegistryManager.addOnSubscriptionsChangedListener(
- mSubChangedListener, mSubChangedListener.getHandlerExecutor());
+ mSubChangedListener, mHandler::post);
PhoneConfigurationManager.registerForMultiSimConfigChange(mHandler,
EVENT_MSIM_CONFIGURATION_CHANGE, null);
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index e111a2d..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,10 +834,73 @@
* @param subId The subscription ID
*/
void updateNetworkSelection(int serviceState, int subId) {
+ if (!mFeatureFlags.dismissNetworkSelectionNotificationOnSimDisable()) {
+ updateNetworkSelectionForFeatureDisabled(serviceState, subId);
+ return;
+ }
+
+ // for dismissNetworkSelectionNotificationOnSimDisable feature enabled.
int phoneId = SubscriptionManager.getPhoneId(subId);
Phone phone = SubscriptionManager.isValidPhoneId(phoneId) ?
PhoneFactory.getPhone(phoneId) : PhoneFactory.getDefaultPhone();
if (TelephonyCapabilities.supportsNetworkSelection(phone)) {
+ if (SubscriptionManager.isValidSubscriptionId(subId)
+ && mSubscriptionManager.isActiveSubId(subId)) {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+ String selectedNetworkOperatorName =
+ sp.getString(Phone.NETWORK_SELECTION_NAME_KEY + subId, "");
+ // get the shared preference of network_selection.
+ // empty is auto mode, otherwise it is the operator alpha name
+ // in case there is no operator name, check the operator numeric
+ if (TextUtils.isEmpty(selectedNetworkOperatorName)) {
+ selectedNetworkOperatorName =
+ sp.getString(Phone.NETWORK_SELECTION_KEY + subId, "");
+ }
+ boolean isManualSelection;
+ // if restoring manual selection is controlled by framework, then get network
+ // selection from shared preference, otherwise get from real network indicators.
+ boolean restoreSelection = !mContext.getResources().getBoolean(
+ com.android.internal.R.bool.skip_restoring_network_selection);
+ if (restoreSelection) {
+ isManualSelection = !TextUtils.isEmpty(selectedNetworkOperatorName);
+ } else {
+ isManualSelection = phone.getServiceStateTracker().mSS.getIsManualSelection();
+ }
+
+ if (DBG) {
+ log("updateNetworkSelection()..." + "state = " + serviceState + " new network "
+ + (isManualSelection ? selectedNetworkOperatorName : ""));
+ }
+
+ if (isManualSelection) {
+ mSelectedNetworkOperatorName.put(subId, selectedNetworkOperatorName);
+ shouldShowNotification(serviceState, subId);
+ } else {
+ dismissNetworkSelectionNotification(subId);
+ clearUpNetworkSelectionNotificationParam(subId);
+ }
+ } else {
+ if (DBG) {
+ log("updateNetworkSelection()... state = " + serviceState
+ + " not updating network due to invalid subId " + subId);
+ }
+ dismissNetworkSelectionNotificationForInactiveSubId();
+ }
+ }
+ }
+
+ /**
+ * Update notification about no service of user selected operator.
+ * For dismissNetworkSelectionNotificationOnSimDisable feature disabled.
+ *
+ * @param serviceState Phone service state
+ * @param subId The subscription ID
+ */
+ private void updateNetworkSelectionForFeatureDisabled(int serviceState, int subId) {
+ int phoneId = SubscriptionManager.getPhoneId(subId);
+ Phone phone = SubscriptionManager.isValidPhoneId(phoneId)
+ ? PhoneFactory.getPhone(phoneId) : PhoneFactory.getDefaultPhone();
+ if (TelephonyCapabilities.supportsNetworkSelection(phone)) {
if (SubscriptionManager.isValidSubscriptionId(subId)) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
String selectedNetworkOperatorName =
@@ -881,7 +950,10 @@
}
}
- private void dismissNetworkSelectionNotificationForInactiveSubId() {
+ /**
+ * Dismiss the network selection "no service" notification for all inactive subscriptions.
+ */
+ public void dismissNetworkSelectionNotificationForInactiveSubId() {
for (int i = 0; i < mSelectedUnavailableNotify.size(); i++) {
int subId = mSelectedUnavailableNotify.keyAt(i);
if (!mSubscriptionManager.isActiveSubId(subId)) {
@@ -891,15 +963,6 @@
}
}
- /* package */ void postTransientNotification(int notifyId, CharSequence msg) {
- if (mToast != null) {
- mToast.cancel();
- }
-
- mToast = Toast.makeText(mContext, msg, Toast.LENGTH_LONG);
- mToast.show();
- }
-
private void log(String msg) {
Log.d(LOG_TAG, msg);
}
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 2ec9e1b..7fba651 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -72,6 +72,8 @@
import com.android.internal.telephony.data.DataEvaluation.DataDisallowedReason;
import com.android.internal.telephony.domainselection.DomainSelectionResolver;
import com.android.internal.telephony.emergency.EmergencyStateTracker;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.FeatureFlagsImpl;
import com.android.internal.telephony.ims.ImsResolver;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
@@ -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;
@@ -182,6 +182,22 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"ROAMING_NOTIFICATION_REASON_"},
+ value = {
+ ROAMING_NOTIFICATION_REASON_DATA_SETTING_CHANGED,
+ ROAMING_NOTIFICATION_REASON_DATA_ROAMING_SETTING_CHANGED,
+ ROAMING_NOTIFICATION_REASON_CARRIER_CONFIG_CHANGED,
+ ROAMING_NOTIFICATION_REASON_SERVICE_STATE_CHANGED,
+ ROAMING_NOTIFICATION_REASON_DEFAULT_DATA_SUBS_CHANGED})
+ public @interface RoamingNotificationReason {}
+ private static final int ROAMING_NOTIFICATION_REASON_DATA_SETTING_CHANGED = 0;
+ private static final int ROAMING_NOTIFICATION_REASON_DATA_ROAMING_SETTING_CHANGED = 1;
+ private static final int ROAMING_NOTIFICATION_REASON_CARRIER_CONFIG_CHANGED = 2;
+ private static final int ROAMING_NOTIFICATION_REASON_SERVICE_STATE_CHANGED = 3;
+ private static final int ROAMING_NOTIFICATION_REASON_DEFAULT_DATA_SUBS_CHANGED = 4;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"ROAMING_NOTIFICATION_"},
value = {
ROAMING_NOTIFICATION_NO_NOTIFICATION,
@@ -194,8 +210,18 @@
private static final int ROAMING_NOTIFICATION_DISCONNECTED = 2;
@RoamingNotification
+ private int mCurrentRoamingNotification = ROAMING_NOTIFICATION_NO_NOTIFICATION;
+
+ /**
+ * Reasons that have already shown notification to prevent duplicate shows for the same reason.
+ */
+ private ArraySet<String> mShownNotificationReasons = new ArraySet<>();
+
+ // For reorganize_roaming_notification feature disabled.
+ @RoamingNotification
private int mPrevRoamingNotification = ROAMING_NOTIFICATION_NO_NOTIFICATION;
+ // For reorganize_roaming_notification feature disabled.
/** Operator numerics for which we've shown is-roaming notifications. **/
private ArraySet<String> mPrevRoamingOperatorNumerics = new ArraySet<>();
@@ -222,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;
@@ -334,17 +362,20 @@
break;
case EVENT_SIM_STATE_CHANGED:
- // Marks the event where the SIM goes into ready state.
- // Right now, this is only used for the PUK-unlocking
- // process.
EventSimStateChangedBag bag = (EventSimStateChangedBag)msg.obj;
+ // Dismiss the "No services" notification if the SIM is removed.
+ if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(bag.mIccStatus)) {
+ notificationMgr.dismissNetworkSelectionNotificationForInactiveSubId();
+ }
+
+ // Marks the event where the SIM goes into ready state.
+ // Right now, this is only used for the PUK-unlocking process.
if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(bag.mIccStatus)
|| IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(bag.mIccStatus)
|| IccCardConstants.INTENT_VALUE_ICC_NOT_READY.equals(bag.mIccStatus)
|| IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(bag.mIccStatus)) {
- // when the right event is triggered and there
- // are UI objects in the foreground, we close
- // them to display the lock panel.
+ // When the right event is triggered and there are UI objects in the
+ // foreground, we close them to display the lock panel.
if (mPUKEntryActivity != null) {
Log.i(LOG_TAG, "Dismiss puk entry activity");
mPUKEntryActivity.finish();
@@ -364,8 +395,19 @@
//TODO: handle message here;
break;
case EVENT_DATA_ROAMING_SETTINGS_CHANGED:
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ updateDataRoamingStatus(
+ ROAMING_NOTIFICATION_REASON_DATA_ROAMING_SETTING_CHANGED);
+ } else {
+ updateDataRoamingStatusForFeatureDisabled(null);
+ }
+ break;
case EVENT_MOBILE_DATA_SETTINGS_CHANGED:
- updateDataRoamingStatus();
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ updateDataRoamingStatus(ROAMING_NOTIFICATION_REASON_DATA_SETTING_CHANGED);
+ } else {
+ updateDataRoamingStatusForFeatureDisabled(null);
+ }
break;
case EVENT_CARRIER_CONFIG_CHANGED:
int subId = (Integer) msg.obj;
@@ -452,17 +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
- PhoneFactory.makeDefaultPhones(this);
+ 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);
@@ -514,7 +557,7 @@
// Create the SatelliteController singleton, which acts as a backend service for
// {@link android.telephony.satellite.SatelliteManager}.
- SatelliteController.make(this);
+ SatelliteController.make(this, mFeatureFlags);
// Create an instance of CdmaPhoneCallState and initialize it to IDLE
cdmaPhoneCallState = new CdmaPhoneCallState();
@@ -529,11 +572,11 @@
mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
- phoneMgr = PhoneInterfaceManager.init(this);
+ phoneMgr = PhoneInterfaceManager.init(this, mFeatureFlags);
- imsRcsController = ImsRcsController.init(this);
+ imsRcsController = ImsRcsController.init(this, mFeatureFlags);
- configLoader = CarrierConfigLoader.init(this);
+ configLoader = CarrierConfigLoader.init(this, mFeatureFlags);
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS)) {
mImsStateCallbackController =
@@ -576,10 +619,12 @@
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();
+ updateDataRoamingStatus(ROAMING_NOTIFICATION_REASON_DEFAULT_DATA_SUBS_CHANGED);
}
PhoneConfigurationManager.registerForMultiSimConfigChange(
@@ -736,13 +781,14 @@
}
private void handleAirplaneModeChange(boolean isAirplaneNewlyOn) {
+ Log.i(LOG_TAG, "handleAirplaneModeChange: isAirplaneNewlyOn=" + isAirplaneNewlyOn);
int cellState =
Settings.Global.getInt(
getContentResolver(), Settings.Global.CELL_ON, PhoneConstants.CELL_ON_FLAG);
switch (cellState) {
case PhoneConstants.CELL_OFF_FLAG:
- // Airplane mode does not affect the cell radio if user
- // has turned it off.
+ // Airplane mode does not affect the cell radio if user has turned it off.
+ Log.i(LOG_TAG, "Ignore airplane mode change due to cell off.");
break;
case PhoneConstants.CELL_ON_FLAG:
maybeTurnCellOff(isAirplaneNewlyOn);
@@ -782,7 +828,11 @@
/** Clear fields on power off radio **/
private void clearCacheOnRadioOff() {
// Re-show is-roaming notifications after APM mode
- mPrevRoamingOperatorNumerics.clear();
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ mShownNotificationReasons.clear();
+ } else {
+ mPrevRoamingOperatorNumerics.clear();
+ }
}
private void setRadioPowerOn() {
@@ -813,12 +863,16 @@
} else {
Log.i(LOG_TAG, "Ignoring airplane mode: settings prevent cell radio power off");
}
+ } else {
+ Log.i(LOG_TAG, "Ignoring airplane mode: not newly on");
}
}
private void maybeTurnCellOn(boolean isAirplaneNewlyOn) {
if (!isAirplaneNewlyOn) {
setRadioPowerOn();
+ } else {
+ Log.i(LOG_TAG, "Ignoring airplane mode off: radio is already on.");
}
}
@@ -875,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();
+ 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);
@@ -890,7 +948,12 @@
registerSettingsObserver();
Phone phone = getPhone(mDefaultDataSubId);
if (phone != null) {
- updateDataRoamingStatus();
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ updateDataRoamingStatus(
+ ROAMING_NOTIFICATION_REASON_DEFAULT_DATA_SUBS_CHANGED);
+ } else {
+ updateDataRoamingStatusForFeatureDisabled(null);
+ }
}
}
}
@@ -906,7 +969,11 @@
+ mDefaultDataSubId + ", ss roaming=" + serviceState.getDataRoaming());
}
if (subId == mDefaultDataSubId) {
- updateDataRoamingStatus(serviceState.getOperatorNumeric());
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ updateDataRoamingStatus(ROAMING_NOTIFICATION_REASON_SERVICE_STATE_CHANGED);
+ } else {
+ updateDataRoamingStatusForFeatureDisabled(serviceState.getOperatorNumeric());
+ }
}
}
@@ -914,11 +981,149 @@
* When roaming, if mobile data cannot be established due to data roaming not enabled, we need
* to notify the user so they can enable it through settings. Vise versa if the condition
* changes, we need to dismiss the notification.
+ * @param reason to inform which event is called for notification update.
*/
- private void updateDataRoamingStatus() {
- updateDataRoamingStatus(null /*roamingOperatorNumeric*/);
+ private void updateDataRoamingStatus(@RoamingNotificationReason int reason) {
+ if (VDBG) Log.v(LOG_TAG, "updateDataRoamingStatus");
+ Phone phone = getPhone(mDefaultDataSubId);
+ if (phone == null) {
+ Log.w(LOG_TAG, "Can't get phone with sub id = " + mDefaultDataSubId);
+ return;
+ }
+
+ ServiceState serviceState = phone.getServiceState();
+ if (serviceState == null) {
+ Log.e(LOG_TAG, "updateDataRoamingStatus: serviceState is null");
+ return;
+ }
+
+ String roamingNumeric = serviceState.getOperatorNumeric();
+ String roamingNumericReason = "RoamingNumeric=" + roamingNumeric;
+ String callingReason = "CallingReason=" + reason;
+ boolean dataIsNowRoaming = serviceState.getDataRoaming();
+ boolean dataAllowed;
+ boolean notAllowedDueToRoamingOff;
+ List<DataDisallowedReason> reasons = phone.getDataNetworkController()
+ .getInternetDataDisallowedReasons();
+ dataAllowed = reasons.isEmpty();
+ notAllowedDueToRoamingOff = (reasons.size() == 1
+ && reasons.contains(DataDisallowedReason.ROAMING_DISABLED));
+ StringBuilder sb = new StringBuilder("updateDataRoamingStatus");
+ sb.append(" dataAllowed=").append(dataAllowed);
+ sb.append(", reasons=").append(reasons);
+ sb.append(", dataIsNowRoaming=").append(dataIsNowRoaming);
+ sb.append(", ").append(roamingNumericReason);
+ sb.append(", ").append(callingReason);
+ mDataRoamingNotifLog.log(sb.toString());
+ if (VDBG) {
+ Log.v(LOG_TAG, sb.toString());
+ }
+
+ // Determine if a given roaming numeric has never been shown.
+ boolean shownInThisNumeric = false;
+ if (reason == ROAMING_NOTIFICATION_REASON_CARRIER_CONFIG_CHANGED
+ || reason == ROAMING_NOTIFICATION_REASON_SERVICE_STATE_CHANGED) {
+ shownInThisNumeric = mShownNotificationReasons.contains(roamingNumericReason);
+ }
+ // Determine if a notification has never been shown by given calling reason.
+ boolean shownForThisReason = mShownNotificationReasons.contains(callingReason);
+
+ if (!dataAllowed && notAllowedDueToRoamingOff) {
+ if (!shownInThisNumeric && roamingNumeric != null) {
+ mShownNotificationReasons.add(roamingNumericReason);
+ }
+ if (!shownForThisReason
+ && reason == ROAMING_NOTIFICATION_REASON_CARRIER_CONFIG_CHANGED) {
+ mShownNotificationReasons.add(callingReason);
+ }
+ // No need to show it again if we never cancelled it explicitly.
+ if (getCurrentRoamingNotification() == ROAMING_NOTIFICATION_DISCONNECTED) {
+ return;
+ }
+
+ // If the only reason of no data is data roaming disabled, then we notify the user
+ // so the user can turn on data roaming.
+ if (!shownInThisNumeric && !shownForThisReason) {
+ updateDataRoamingNotification(ROAMING_NOTIFICATION_DISCONNECTED);
+ } else {
+ // Don't show roaming notification if we've already shown for this MccMnc
+ Log.d(LOG_TAG, "Skip roaming disconnected notification since already"
+ + " shownInThisNumeric=" + shownInThisNumeric
+ + " shownForThisReason=" + shownForThisReason);
+ // Dismiss notification if the other notification is shown.
+ if (getCurrentRoamingNotification() != ROAMING_NOTIFICATION_NO_NOTIFICATION) {
+ updateDataRoamingNotification(ROAMING_NOTIFICATION_NO_NOTIFICATION);
+ }
+ }
+ } else if (dataAllowed && dataIsNowRoaming) {
+ if (!shownInThisNumeric && roamingNumeric != null) {
+ mShownNotificationReasons.add(roamingNumericReason);
+ }
+ if (!shownForThisReason
+ && reason == ROAMING_NOTIFICATION_REASON_CARRIER_CONFIG_CHANGED) {
+ mShownNotificationReasons.add(callingReason);
+ }
+ boolean shouldShowRoamingNotification = shouldShowRoamingNotification(roamingNumeric);
+ // No need to show it again if we never cancelled it explicitly.
+ if (getCurrentRoamingNotification() == ROAMING_NOTIFICATION_CONNECTED) {
+ return;
+ }
+
+ // Inform users that roaming charges may apply.
+ if (!shownInThisNumeric && !shownForThisReason && shouldShowRoamingNotification) {
+ updateDataRoamingNotification(ROAMING_NOTIFICATION_CONNECTED);
+ } else {
+ // Don't show roaming notification if we've already shown for this MccMnc or
+ // disabled from carrier config.
+ Log.d(LOG_TAG, "Skip roaming connected notification since already"
+ + " shownInThisNumeric:" + shownInThisNumeric
+ + " shownForThisReason:" + shownForThisReason
+ + " shouldShowRoamingNotification:" + shouldShowRoamingNotification);
+ // Dismiss notification if the other notification is shown.
+ if (getCurrentRoamingNotification() != ROAMING_NOTIFICATION_NO_NOTIFICATION) {
+ updateDataRoamingNotification(ROAMING_NOTIFICATION_NO_NOTIFICATION);
+ }
+ }
+ } else if (getCurrentRoamingNotification() != ROAMING_NOTIFICATION_NO_NOTIFICATION) {
+ // Otherwise we either 1) we are not roaming or 2) roaming is off but ROAMING_DISABLED
+ // is not the only data disable reason. In this case we dismiss the notification we
+ // showed earlier.
+ updateDataRoamingNotification(ROAMING_NOTIFICATION_NO_NOTIFICATION);
+ }
}
+ private void updateDataRoamingNotification(@RoamingNotification int roamingNotification) {
+ int event;
+ switch (roamingNotification) {
+ case ROAMING_NOTIFICATION_NO_NOTIFICATION:
+ Log.d(LOG_TAG, "Dismiss roaming notification");
+ mDataRoamingNotifLog.log("Hide roaming.");
+ event = EVENT_DATA_ROAMING_OK;
+ break;
+ case ROAMING_NOTIFICATION_CONNECTED:
+ Log.d(LOG_TAG, "Show roaming connected notification");
+ mDataRoamingNotifLog.log("Show roaming on.");
+ event = EVENT_DATA_ROAMING_CONNECTED;
+ break;
+ case ROAMING_NOTIFICATION_DISCONNECTED:
+ Log.d(LOG_TAG, "Show roaming disconnected notification");
+ mDataRoamingNotifLog.log("Show roaming off.");
+ event = EVENT_DATA_ROAMING_DISCONNECTED;
+ break;
+ default:
+ Log.d(LOG_TAG, "Should never reach here.");
+ mDataRoamingNotifLog.log("Should never reach here.");
+ return;
+ }
+ mCurrentRoamingNotification = roamingNotification;
+ mHandler.obtainMessage(event, mDefaultDataSubId, 0).sendToTarget();
+ }
+
+ private @RoamingNotification int getCurrentRoamingNotification() {
+ return mCurrentRoamingNotification;
+ }
+
+ // For reorganize_roaming_notification feature disabled.
/**
* When roaming, if mobile data cannot be established due to data roaming not enabled, we need
* to notify the user so they can enable it through settings. Vise versa if the condition
@@ -926,8 +1131,9 @@
* @param roamingOperatorNumeric The operator numeric for the current roaming. {@code null} if
* the current roaming operator numeric didn't change.
*/
- private void updateDataRoamingStatus(@Nullable String roamingOperatorNumeric) {
- if (VDBG) Log.v(LOG_TAG, "updateDataRoamingStatus");
+ private void updateDataRoamingStatusForFeatureDisabled(
+ @Nullable String roamingOperatorNumeric) {
+ if (VDBG) Log.v(LOG_TAG, "updateDataRoamingStatusForFeatureDisabled");
Phone phone = getPhone(mDefaultDataSubId);
if (phone == null) {
Log.w(LOG_TAG, "Can't get phone with sub id = " + mDefaultDataSubId);
@@ -1012,8 +1218,9 @@
boolean showRoamingNotification = config.getBoolean(
CarrierConfigManager.KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL);
- if (TextUtils.isEmpty(roamingNumeric)) {
- Log.d(LOG_TAG, "shouldShowRoamingNotification: roamingNumeric=" + roamingNumeric);
+ if (TextUtils.isEmpty(roamingNumeric) || !mFeatureFlags.hideRoamingIcon()) {
+ Log.d(LOG_TAG, "shouldShowRoamingNotification: roamingNumeric=" + roamingNumeric
+ + ", hideRoaming=" + mFeatureFlags.hideRoamingIcon());
return showRoamingNotification;
}
@@ -1133,10 +1340,21 @@
IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
pw.println("------- PhoneGlobals -------");
pw.increaseIndent();
- pw.println("mPrevRoamingNotification=" + mPrevRoamingNotification);
+ pw.println("FeatureFlags:");
+ pw.increaseIndent();
+ pw.println("reorganizeRoamingNotification="
+ + mFeatureFlags.reorganizeRoamingNotification());
+ pw.println("dismissNetworkSelectionNotificationOnSimDisable="
+ + mFeatureFlags.dismissNetworkSelectionNotificationOnSimDisable());
+ pw.decreaseIndent();
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ pw.println("mCurrentRoamingNotification=" + mCurrentRoamingNotification);
+ } else {
+ pw.println("mPrevRoamingNotification=" + mPrevRoamingNotification);
+ }
pw.println("mDefaultDataSubId=" + mDefaultDataSubId);
- pw.println("mDataRoamingNotifLog:");
pw.println("isSmsCapable=" + TelephonyManager.from(this).isSmsCapable());
+ pw.println("mDataRoamingNotifLog:");
pw.increaseIndent();
mDataRoamingNotifLog.dump(fd, pw, args);
pw.decreaseIndent();
@@ -1170,11 +1388,12 @@
e.printStackTrace();
}
pw.decreaseIndent();
- if (mDomainSelectionService != null) {
- mDomainSelectionService.dump(fd, pw, args);
- }
pw.decreaseIndent();
- pw.println("mPrevRoamingOperatorNumerics:" + mPrevRoamingOperatorNumerics);
+ if (mFeatureFlags.reorganizeRoamingNotification()) {
+ pw.println("mShownNotificationReasons=" + mShownNotificationReasons);
+ } else {
+ pw.println("mPrevRoamingOperatorNumerics:" + mPrevRoamingOperatorNumerics);
+ }
pw.println("------- End PhoneGlobals -------");
}
}
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 0107b1c..30621ff 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;
@@ -146,10 +153,14 @@
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.stub.ImsConfigImplBase;
import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.telephony.satellite.INtnSignalStrengthCallback;
+import android.telephony.satellite.ISatelliteCapabilitiesCallback;
import android.telephony.satellite.ISatelliteDatagramCallback;
+import android.telephony.satellite.ISatelliteModemStateCallback;
import android.telephony.satellite.ISatelliteProvisionStateCallback;
-import android.telephony.satellite.ISatelliteStateCallback;
import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
+import android.telephony.satellite.NtnSignalStrength;
+import android.telephony.satellite.NtnSignalStrengthCallback;
import android.telephony.satellite.SatelliteCapabilities;
import android.telephony.satellite.SatelliteDatagram;
import android.telephony.satellite.SatelliteDatagramCallback;
@@ -202,12 +213,14 @@
import com.android.internal.telephony.SmsApplication;
import com.android.internal.telephony.SmsController;
import com.android.internal.telephony.SmsPermissions;
+import com.android.internal.telephony.TelephonyCountryDetector;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.telephony.data.DataUtils;
import com.android.internal.telephony.domainselection.DomainSelectionResolver;
import com.android.internal.telephony.emergency.EmergencyNumberTracker;
import com.android.internal.telephony.euicc.EuiccConnector;
+import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.ims.ImsResolver;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
@@ -234,6 +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;
@@ -296,8 +311,6 @@
private static final int EVENT_GET_ALLOWED_NETWORK_TYPES_BITMASK_DONE = 22;
private static final int CMD_SEND_ENVELOPE = 25;
private static final int EVENT_SEND_ENVELOPE_DONE = 26;
- private static final int CMD_INVOKE_OEM_RIL_REQUEST_RAW = 27;
- private static final int EVENT_INVOKE_OEM_RIL_REQUEST_RAW_DONE = 28;
private static final int CMD_TRANSMIT_APDU_BASIC_CHANNEL = 29;
private static final int EVENT_TRANSMIT_APDU_BASIC_CHANNEL_DONE = 30;
private static final int CMD_EXCHANGE_SIM_IO = 31;
@@ -397,27 +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 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_";
@@ -1154,19 +1173,6 @@
handleNullReturnEvent(msg, "setAllowedNetworkTypesForReason");
break;
- case CMD_INVOKE_OEM_RIL_REQUEST_RAW:
- request = (MainThreadRequest)msg.obj;
- onCompleted = obtainMessage(EVENT_INVOKE_OEM_RIL_REQUEST_RAW_DONE, request);
- defaultPhone.invokeOemRilRequestRaw((byte[]) request.argument, onCompleted);
- break;
-
- case EVENT_INVOKE_OEM_RIL_REQUEST_RAW_DONE:
- ar = (AsyncResult)msg.obj;
- request = (MainThreadRequest)ar.userObj;
- request.result = ar;
- notifyRequester(request);
- break;
-
case CMD_SET_VOICEMAIL_NUMBER:
request = (MainThreadRequest) msg.obj;
onCompleted = obtainMessage(EVENT_SET_VOICEMAIL_NUMBER_DONE, request);
@@ -2226,8 +2232,8 @@
onCompleted = obtainMessage(EVENT_PURCHASE_PREMIUM_CAPABILITY_DONE, request);
PurchasePremiumCapabilityArgument arg =
(PurchasePremiumCapabilityArgument) request.argument;
- SlicePurchaseController.getInstance(request.phone).purchasePremiumCapability(
- arg.capability, onCompleted);
+ SlicePurchaseController.getInstance(request.phone, mFeatureFlags)
+ .purchasePremiumCapability(arg.capability, onCompleted);
break;
}
@@ -2440,10 +2446,10 @@
* Initialize the singleton PhoneInterfaceManager instance.
* This is only done once, at startup, from PhoneApp.onCreate().
*/
- /* package */ static PhoneInterfaceManager init(PhoneGlobals app) {
+ /* package */ static PhoneInterfaceManager init(PhoneGlobals app, FeatureFlags featureFlags) {
synchronized (PhoneInterfaceManager.class) {
if (sInstance == null) {
- sInstance = new PhoneInterfaceManager(app);
+ sInstance = new PhoneInterfaceManager(app, featureFlags);
} else {
Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance);
}
@@ -2452,8 +2458,9 @@
}
/** Private constructor; @see init() */
- private PhoneInterfaceManager(PhoneGlobals app) {
+ private PhoneInterfaceManager(PhoneGlobals app, FeatureFlags featureFlags) {
mApp = app;
+ mFeatureFlags = featureFlags;
mCM = PhoneGlobals.getInstance().mCM;
mImsResolver = ImsResolver.getInstance();
mSatelliteController = SatelliteController.getInstance();
@@ -2465,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
@@ -2559,6 +2573,9 @@
}
public void dial(String number) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "dial");
+
dialForSubscriber(getPreferredVoiceSubscription(), number);
}
@@ -2604,6 +2621,9 @@
return;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "call");
+
final long identity = Binder.clearCallingIdentity();
try {
String url = createTelUrl(number);
@@ -2647,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);
@@ -2661,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);
@@ -2745,6 +2772,9 @@
* If PUK is null, unlock SIM card with PIN
*
* If PUK is not null, unlock SIM card with PUK and set PIN code
+ *
+ * Besides, since it is reused in class level, the thread's looper will be stopped to avoid
+ * its thread leak.
*/
synchronized int[] unlockSim(String puk, String pin) {
@@ -2780,6 +2810,8 @@
if (mResult == PhoneConstants.PIN_RESULT_SUCCESS && pin.length() > 0) {
UiccController.getInstance().getPinStorage().storePin(pin, mPhoneId);
}
+ // This instance is no longer reused, so quit its thread's looper.
+ mHandler.getLooper().quitSafely();
return resultArray;
}
@@ -2861,6 +2893,9 @@
return false;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "isRadioOnWithFeature");
+
final long identity = Binder.clearCallingIdentity();
try {
return isRadioOnForSubscriber(subId);
@@ -2890,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);
@@ -2925,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.
@@ -2946,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++) {
@@ -3027,18 +3072,22 @@
@TelephonyManager.RadioPowerReason int reason) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "requestRadioPowerOffForReason");
+
log("requestRadioPowerOffForReason: subId=" + subId
+ ",reason=" + reason + ",callingPackage=" + getCurrentPackageName());
final long identity = Binder.clearCallingIdentity();
try {
- final Phone phone = getPhoneFromSubIdOrDefault(subId);
- if (phone != null) {
+ boolean result = false;
+ for (Phone phone : PhoneFactory.getPhones()) {
+ result = true;
phone.setRadioPowerForReason(false, reason);
- return true;
- } else {
- loge("requestRadioPowerOffForReason: phone is null");
- return false;
}
+ if (!result) {
+ loge("requestRadioPowerOffForReason: no phone exists");
+ }
+ return result;
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -3056,16 +3105,20 @@
@TelephonyManager.RadioPowerReason int reason) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "clearRadioPowerOffForReason");
+
final long identity = Binder.clearCallingIdentity();
try {
- final Phone phone = getPhoneFromSubIdOrDefault(subId);
- if (phone != null) {
+ boolean result = false;
+ for (Phone phone : PhoneFactory.getPhones()) {
+ result = true;
phone.setRadioPowerForReason(true, reason);
- return true;
- } else {
- loge("clearRadioPowerOffForReason: phone is null");
- return false;
}
+ if (!result) {
+ loge("clearRadioPowerOffForReason: no phone exists");
+ }
+ return result;
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -3082,6 +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 {
@@ -3107,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();
@@ -3128,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();
@@ -3146,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);
@@ -3166,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)) {
@@ -3181,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)) {
@@ -3228,6 +3299,10 @@
+ "targeting API level 31+.");
}
}
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getCallStateForSubscription");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subId);
@@ -3245,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);
@@ -3266,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);
@@ -3303,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 {
@@ -3316,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();
@@ -3386,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);
@@ -3439,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();
@@ -3507,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);
@@ -3545,6 +3640,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_GSM, "getImeiForSlot");
+
final long identity = Binder.clearCallingIdentity();
try {
return phone.getImei();
@@ -3560,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()) {
@@ -3575,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) {
@@ -3609,6 +3714,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CDMA, "getMeidForSlot");
+
final long identity = Binder.clearCallingIdentity();
try {
return phone.getMeid();
@@ -3619,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) {
@@ -3648,6 +3759,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY, "getDeviceSoftwareVersionForSlot");
+
final long identity = Binder.clearCallingIdentity();
try {
return phone.getDeviceSvn();
@@ -3658,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);
@@ -3669,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);
@@ -3680,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);
@@ -3692,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);
@@ -3710,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);
@@ -3830,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);
@@ -3861,6 +3995,10 @@
return -1;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CDMA,
+ "getCdmaEriIconIndexForSubscriber");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -3946,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);
@@ -3968,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);
@@ -3997,6 +4141,9 @@
+ ", configured package: " + authorizedPackage);
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "requestNumberVerification");
+
if (range == null) {
throw new NullPointerException("Range must be non-null");
}
@@ -4011,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();
@@ -4027,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,
@@ -4046,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);
@@ -4068,6 +4224,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getVisualVoicemailPackageName");
+
final long identity = Binder.clearCallingIdentity();
try {
return RemoteVvmTaskManager.getRemotePackage(mApp, subId).getPackageName();
@@ -4081,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(
@@ -4094,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(
@@ -4137,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);
@@ -4150,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);
@@ -4171,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);
@@ -4191,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 {
@@ -4211,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 {
@@ -4254,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();
@@ -4278,6 +4462,9 @@
getDefaultSubscription(), "sendDialerSpecialCode");
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "sendDialerSpecialCode");
+
final long identity = Binder.clearCallingIdentity();
try {
defaultPhone.sendDialerSpecialCode(inputCode);
@@ -4291,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)) {
@@ -4305,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()) {
@@ -4389,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
@@ -4529,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);
@@ -4548,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);
@@ -4611,6 +4884,9 @@
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isAdvancedCallingSettingEnabled");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isAdvancedCallingSettingEnabled");
+
final long token = Binder.clearCallingIdentity();
try {
int slotId = getSlotIndexOrException(subId);
@@ -4627,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);
@@ -4648,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);
@@ -4664,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);
@@ -4685,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);
@@ -4701,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);
@@ -4723,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);
@@ -4746,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);
@@ -4768,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);
@@ -4784,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);
@@ -4801,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);
@@ -4821,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);
@@ -4837,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);
@@ -4853,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);
@@ -4869,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);
@@ -4886,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);
@@ -4907,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);
@@ -5032,6 +5372,9 @@
boolean isProvisioned) {
checkModifyPhoneStatePermission(subId, "setRcsProvisioningStatusForCapability");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "setRcsProvisioningStatusForCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsProvisioningController controller = ImsProvisioningController.getInstance();
@@ -5052,6 +5395,9 @@
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getRcsProvisioningStatusForCapability");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "getRcsProvisioningStatusForCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsProvisioningController controller = ImsProvisioningController.getInstance();
@@ -5072,6 +5418,9 @@
boolean isProvisioned) {
checkModifyPhoneStatePermission(subId, "setImsProvisioningStatusForCapability");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "setImsProvisioningStatusForCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsProvisioningController controller = ImsProvisioningController.getInstance();
@@ -5091,6 +5440,9 @@
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getProvisioningStatusForCapability");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "getImsProvisioningStatusForCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsProvisioningController controller = ImsProvisioningController.getInstance();
@@ -5111,6 +5463,9 @@
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isProvisioningRequiredForCapability");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isProvisioningRequiredForCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsProvisioningController controller = ImsProvisioningController.getInstance();
@@ -5131,6 +5486,9 @@
TelephonyPermissions.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isProvisioningRequiredForCapability");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ FEATURE_TELEPHONY_IMS, "isRcsProvisioningRequiredForCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
ImsProvisioningController controller = ImsProvisioningController.getInstance();
@@ -5154,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.
@@ -5194,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.
@@ -5221,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.
@@ -5260,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.
@@ -5333,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);
@@ -5370,6 +5744,9 @@
}
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getDataNetworkTypeForSubscriber");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -5398,6 +5775,9 @@
}
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getVoiceNetworkTypeForSubscriber");
+
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -5425,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);
@@ -5462,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);
@@ -5543,6 +5929,7 @@
@Override
public IccOpenLogicalChannelResponse iccOpenLogicalChannel(
@NonNull IccLogicalChannelRequest request) {
+
Phone phone = getPhoneFromValidIccLogicalChannelRequest(request,
/*message=*/ "iccOpenLogicalChannel");
@@ -5550,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);
}
@@ -5596,6 +5986,9 @@
@Override
public boolean iccCloseLogicalChannel(@NonNull IccLogicalChannelRequest request) {
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "iccCloseLogicalChannel");
+
Phone phone = getPhoneFromValidIccLogicalChannelRequest(request,
/*message=*/"iccCloseLogicalChannel");
@@ -5643,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="
@@ -5656,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="
@@ -5697,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);
@@ -5710,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="
@@ -5762,6 +6172,9 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "iccExchangeSimIO");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "iccExchangeSimIO");
+
final long identity = Binder.clearCallingIdentity();
try {
if (DBG) {
@@ -5807,6 +6220,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getForbiddenPlmns");
+
final long identity = Binder.clearCallingIdentity();
try {
if (appType != TelephonyManager.APPTYPE_USIM
@@ -5843,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");
@@ -5872,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);
@@ -5977,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);
@@ -6003,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);
@@ -6023,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) {
@@ -6302,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)) {
@@ -6332,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;
@@ -6362,6 +6799,9 @@
.enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "getManualNetworkSelectionPlmn");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getManualNetworkSelectionPlmn");
+
final long identity = Binder.clearCallingIdentity();
try {
if (!isActiveSubscription(subId)) {
@@ -6424,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) {
@@ -6476,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) {
@@ -6509,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);
@@ -6560,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);
@@ -6656,6 +7112,10 @@
}
}
}
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "requestNetworkScan");
+
int callingUid = Binder.getCallingUid();
int callingPid = Binder.getCallingPid();
final long identity = Binder.clearCallingIdentity();
@@ -6731,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");
@@ -6755,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);
@@ -6848,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;
@@ -6893,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 {
@@ -6995,6 +7470,9 @@
enforceReadPrivilegedPermission(functionName);
}
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "isDataEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
int phoneId = SubscriptionManager.getPhoneId(subId);
@@ -7041,6 +7519,8 @@
}
}
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "isDataEnabledForReason");
final long identity = Binder.clearCallingIdentity();
try {
@@ -7069,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());
}
@@ -7076,6 +7559,10 @@
@Override
public int getCarrierPrivilegeStatusForUid(int subId, int uid) {
enforceReadPrivilegedPermission("getCarrierPrivilegeStatusForUid");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getCarrierPrivilegeStatusForUid");
+
return getCarrierPrivilegeStatusForUidWithPermission(subId, uid);
}
@@ -7096,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;
}
@@ -7115,6 +7606,11 @@
@Override
public int checkCarrierPrivilegesForPackageAnyPhone(String pkgName) {
enforceReadPrivilegedPermission("checkCarrierPrivilegesForPackageAnyPhone");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+ "checkCarrierPrivilegesForPackageAnyPhone");
+
return checkCarrierPrivilegesForPackageAnyPhoneWithPermission(pkgName);
}
@@ -7143,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();
@@ -7171,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 {
@@ -7187,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;
@@ -7215,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);
@@ -7238,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);
@@ -7260,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);
@@ -7316,6 +7836,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getLine1NumberForDisplay");
+
final long identity = Binder.clearCallingIdentity();
try {
String iccId = getIccId(subId);
@@ -7443,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(
@@ -7488,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);
@@ -7518,39 +8047,6 @@
}
@Override
- @Deprecated
- public int invokeOemRilRequestRaw(byte[] oemReq, byte[] oemResp) {
- enforceModifyPermission();
-
- int returnValue = 0;
- try {
- AsyncResult result = (AsyncResult) sendRequest(CMD_INVOKE_OEM_RIL_REQUEST_RAW, oemReq);
- if(result.exception == null) {
- if (result.result != null) {
- byte[] responseData = (byte[])(result.result);
- if(responseData.length > oemResp.length) {
- Log.w(LOG_TAG, "Buffer to copy response too small: Response length is " +
- responseData.length + "bytes. Buffer Size is " +
- oemResp.length + "bytes.");
- }
- System.arraycopy(responseData, 0, oemResp, 0, responseData.length);
- returnValue = responseData.length;
- }
- } else {
- CommandException ex = (CommandException) result.exception;
- returnValue = ex.getCommandError().ordinal();
- if(returnValue > 0) returnValue *= -1;
- }
- } catch (RuntimeException e) {
- Log.w(LOG_TAG, "sendOemRilRequestRaw: Runtime Exception");
- returnValue = (CommandException.Error.GENERIC_FAILURE.ordinal());
- if(returnValue > 0) returnValue *= -1;
- }
-
- return returnValue;
- }
-
- @Override
public int getRadioAccessFamily(int phoneId, String callingPackage) {
int raf = RadioAccessFamily.RAF_UNKNOWN;
Phone phone = PhoneFactory.getPhone(phoneId);
@@ -7567,6 +8063,9 @@
throw e;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getRadioAccessFamily");
+
final long identity = Binder.clearCallingIdentity();
try {
raf = ProxyController.getInstance().getRadioAccessFamily(phoneId);
@@ -7587,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)) {
@@ -7673,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(),
@@ -7690,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
@@ -7715,6 +8224,9 @@
return false;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "canChangeDtmfToneLength");
+
final long identity = Binder.clearCallingIdentity();
try {
CarrierConfigManager configManager =
@@ -7733,6 +8245,9 @@
return false;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY, "isWorldPhone");
+
final long identity = Binder.clearCallingIdentity();
try {
CarrierConfigManager configManager =
@@ -7752,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);
@@ -7768,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) {
@@ -7777,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);
@@ -7793,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;
@@ -7882,6 +8410,10 @@
subscriptionId,
"getPhoneAccountHandleForSubscriptionId, " + "subscriptionId: "
+ subscriptionId);
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CALLING, "getPhoneAccountHandleForSubscriptionId");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subscriptionId);
@@ -7949,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;
}
@@ -8021,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");
@@ -8086,7 +8626,7 @@
*/
private List<SubscriptionInfo> getActiveSubscriptionInfoListPrivileged() {
return getSubscriptionManagerService().getActiveSubscriptionInfoList(
- mApp.getOpPackageName(), mApp.getAttributionTag());
+ mApp.getOpPackageName(), mApp.getAttributionTag(), true/*isForAllProfile*/);
}
private ActivityStatsTechSpecificInfo[] mLastModemActivitySpecificInfo = null;
@@ -8103,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();
@@ -8247,6 +8791,9 @@
return null;
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "getServiceStateForSubscriber");
+
boolean hasFinePermission = false;
boolean hasCoarsePermission = false;
if (!renounceFineLocationAccess) {
@@ -8326,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);
@@ -8362,6 +8912,9 @@
"setVoicemailRingtoneUri");
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "setVoicemailRingtoneUri");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = PhoneUtils.getPhoneForPhoneAccountHandle(phoneAccountHandle);
@@ -8383,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);
@@ -8419,6 +8975,9 @@
"setVoicemailVibrationEnabled");
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_CALLING, "setVoicemailVibrationEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = PhoneUtils.getPhoneForPhoneAccountHandle(phoneAccountHandle);
@@ -8495,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();
@@ -8552,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();
@@ -8601,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) {
@@ -8627,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();
@@ -8657,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");
@@ -8779,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();
@@ -8803,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);
@@ -8865,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);
@@ -8935,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());
@@ -8964,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());
@@ -9001,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();
@@ -9018,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);
@@ -9047,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();
@@ -9087,6 +9703,9 @@
mApp, subId, functionName);
}
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "isDataRoamingEnabled");
+
boolean isEnabled = false;
final long identity = Binder.clearCallingIdentity();
try {
@@ -9114,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);
@@ -9131,6 +9753,9 @@
.enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
mApp, subId, "isManualNetworkSelectionAllowed");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS, "isManualNetworkSelectionAllowed");
+
boolean isAllowed = true;
final long identity = Binder.clearCallingIdentity();
try {
@@ -9177,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())) {
@@ -9287,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())) {
@@ -9389,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<>();
@@ -9408,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);
@@ -9418,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();
@@ -9526,10 +10167,6 @@
TelephonyPermissions.enforceShellOnly(
Binder.getCallingUid(), "setCarrierServicePackageOverride");
- // Verify that the callingPackage belongs to the calling UID
- mApp.getSystemService(AppOpsManager.class)
- .checkPackage(Binder.getCallingUid(), callingPackage);
-
final long identity = Binder.clearCallingIdentity();
try {
final Phone phone = getPhone(subId);
@@ -9588,6 +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);
@@ -9601,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);
@@ -9615,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);
@@ -9628,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);
@@ -9644,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<>();
@@ -9669,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()) {
@@ -9761,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);
@@ -9778,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()) {
@@ -9795,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()) {
@@ -9812,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()) {
@@ -9852,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);
@@ -9880,6 +10552,9 @@
throw new SecurityException("Requires READ_PHONE_STATE permission.");
}
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY, "isModemEnabledForSlot");
+
final long identity = Binder.clearCallingIdentity();
try {
try {
@@ -9896,6 +10571,9 @@
public void setMultiSimCarrierRestriction(boolean isMultiSimCarrierRestricted) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_CARRIERLOCK, "setMultiSimCarrierRestriction");
+
final long identity = Binder.clearCallingIdentity();
try {
mTelephonySharedPreferences.edit()
@@ -9915,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();
@@ -9966,6 +10647,10 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, "switchMultiSimConfig");
}
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "switchMultiSimConfig");
+
final long identity = Binder.clearCallingIdentity();
try {
@@ -9983,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;
@@ -10019,6 +10708,11 @@
"doesSwitchMultiSimConfigTriggerReboot")) {
return false;
}
+
+ enforceTelephonyFeatureWithException(callingPackage,
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+ "doesSwitchMultiSimConfigTriggerReboot");
+
final long identity = Binder.clearCallingIdentity();
try {
return mPhoneConfigurationManager.isRebootRequiredForModemConfigChange();
@@ -10039,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 {
@@ -10083,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];
}
/**
@@ -10109,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 {
@@ -10131,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 {
@@ -10148,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);
@@ -10182,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 {
@@ -10200,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);
}
@@ -10247,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 {
@@ -10264,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 {
@@ -10283,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);
@@ -10299,6 +11027,9 @@
boolean enabled) {
enforceModifyPermission();
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_DATA, "setMobileDataPolicyEnabled");
+
final long identity = Binder.clearCallingIdentity();
try {
Phone phone = getPhone(subscriptionId);
@@ -10353,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();
@@ -10371,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 {
@@ -10400,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;
@@ -10432,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;
@@ -10502,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");
@@ -10518,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) {
@@ -10534,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
@@ -10693,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. "
@@ -10923,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();
@@ -10954,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();
@@ -10980,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();
@@ -11005,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();
@@ -11421,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)
@@ -11447,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)
@@ -11513,6 +12313,10 @@
@Override
public PhoneCapability getPhoneCapability() {
enforceReadPrivilegedPermission("getPhoneCapability");
+
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY, "getPhoneCapability");
+
final long identity = Binder.clearCallingIdentity();
try {
return mPhoneConfigurationManager.getCurrentPhoneCapability();
@@ -11531,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);
@@ -11551,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();
@@ -11579,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);
@@ -11586,7 +12400,7 @@
}
final long identity = Binder.clearCallingIdentity();
try {
- return SlicePurchaseController.getInstance(phone)
+ return SlicePurchaseController.getInstance(phone, mFeatureFlags)
.isPremiumCapabilityAvailableForPurchase(capability);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -11621,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 {
@@ -11756,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
@@ -11833,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.
@@ -11901,6 +12740,10 @@
boolean updateIfNeeded) {
enforceInteractAcrossUsersPermission("getDefaultRespondViaMessageApplication");
+ enforceTelephonyFeatureWithException(getCurrentPackageName(),
+ PackageManager.FEATURE_TELEPHONY_MESSAGING,
+ "getDefaultRespondViaMessageApplication");
+
Context context = getPhoneFromSubIdOrDefault(subId).getContext();
UserHandle userHandle = null;
@@ -11969,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}
@@ -11978,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;
@@ -12007,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,
@@ -12053,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();
@@ -12072,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);
@@ -12127,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);
+ }
}
/**
@@ -12276,13 +13190,13 @@
* @param subId The subId of the subscription to register for provision state changed.
* @param callback The callback to handle the satellite provision state changed event.
*
- * @return The {@link SatelliteManager.SatelliteError} result of the operation.
+ * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have the required permission.
*/
@Override
- @SatelliteManager.SatelliteError public int registerForSatelliteProvisionStateChanged(int subId,
- @NonNull ISatelliteProvisionStateCallback callback) {
+ @SatelliteManager.SatelliteResult public int registerForSatelliteProvisionStateChanged(
+ int subId, @NonNull ISatelliteProvisionStateCallback callback) {
enforceSatelliteCommunicationPermission("registerForSatelliteProvisionStateChanged");
return mSatelliteController.registerForSatelliteProvisionStateChanged(subId, callback);
}
@@ -12326,13 +13240,13 @@
* @param subId The subId of the subscription to register for satellite modem state changed.
* @param callback The callback to handle the satellite modem state changed event.
*
- * @return The {@link SatelliteManager.SatelliteError} result of the operation.
+ * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have the required permission.
*/
@Override
- @SatelliteManager.SatelliteError public int registerForSatelliteModemStateChanged(int subId,
- @NonNull ISatelliteStateCallback callback) {
+ @SatelliteManager.SatelliteResult public int registerForSatelliteModemStateChanged(int subId,
+ @NonNull ISatelliteModemStateCallback callback) {
enforceSatelliteCommunicationPermission("registerForSatelliteModemStateChanged");
return mSatelliteController.registerForSatelliteModemStateChanged(subId, callback);
}
@@ -12343,15 +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);
}
/**
@@ -12360,15 +13274,15 @@
* @param subId The subId of the subscription to register for incoming satellite datagrams.
* @param callback The callback to handle incoming datagrams over satellite.
*
- * @return The {@link SatelliteManager.SatelliteError} result of the operation.
+ * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have the required permission.
*/
@Override
- @SatelliteManager.SatelliteError public int registerForSatelliteDatagram(int subId,
+ @SatelliteManager.SatelliteResult public int registerForIncomingDatagram(int subId,
@NonNull ISatelliteDatagramCallback callback) {
- enforceSatelliteCommunicationPermission("registerForSatelliteDatagram");
- return mSatelliteController.registerForSatelliteDatagram(subId, callback);
+ enforceSatelliteCommunicationPermission("registerForIncomingDatagram");
+ return mSatelliteController.registerForIncomingDatagram(subId, callback);
}
/**
@@ -12377,15 +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);
}
/**
@@ -12396,14 +13310,13 @@
* {@link SatelliteDatagramCallback#onSatelliteDatagramReceived(long, SatelliteDatagram, int, Consumer)})}
*
* @param subId The subId of the subscription used for receiving datagrams.
- * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request.
+ * @param callback The callback to get {@link SatelliteManager.SatelliteResult} of the request.
*
* @throws SecurityException if the caller doesn't have required permission.
*/
- @Override
- public void pollPendingSatelliteDatagrams(int subId, IIntegerConsumer callback) {
- enforceSatelliteCommunicationPermission("pollPendingSatelliteDatagrams");
- mSatelliteController.pollPendingSatelliteDatagrams(subId, callback);
+ public void pollPendingDatagrams(int subId, IIntegerConsumer callback) {
+ enforceSatelliteCommunicationPermission("pollPendingDatagrams");
+ mSatelliteController.pollPendingDatagrams(subId, callback);
}
/**
@@ -12420,17 +13333,17 @@
* Datagram will be passed down to modem without any encoding or encryption.
* @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
* full screen mode.
- * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request.
+ * @param callback The callback to get {@link SatelliteManager.SatelliteResult} of the request.
*
* @throws SecurityException if the caller doesn't have required permission.
*/
@Override
- public void sendSatelliteDatagram(int subId, @SatelliteManager.DatagramType int datagramType,
+ public void sendDatagram(int subId, @SatelliteManager.DatagramType int datagramType,
@NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
@NonNull IIntegerConsumer callback) {
- enforceSatelliteCommunicationPermission("sendSatelliteDatagram");
- mSatelliteController.sendSatelliteDatagram(subId, datagramType, datagram,
- needFullScreenPointingUI, callback);
+ enforceSatelliteCommunicationPermission("sendDatagram");
+ mSatelliteController.sendDatagram(subId, datagramType, datagram, needFullScreenPointingUI,
+ callback);
}
/**
@@ -12445,11 +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);
}
@@ -12479,9 +13391,192 @@
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- public void onDeviceAlignedWithSatellite(int subId, @NonNull boolean isAligned) {
+ public void setDeviceAlignedWithSatellite(int subId, @NonNull boolean isAligned) {
enforceSatelliteCommunicationPermission("informDeviceAlignedToSatellite");
- mSatelliteController.onDeviceAlignedWithSatellite(subId, isAligned);
+ mSatelliteController.setDeviceAlignedWithSatellite(subId, isAligned);
+ }
+
+ /**
+ * Add a restriction reason for disallowing carrier supported satellite plmn scan and attach
+ * by modem.
+ *
+ * @param subId The subId of the subscription to request for.
+ * @param reason Reason for disallowing satellite communication for carrier.
+ * @param callback Listener for the {@link SatelliteManager.SatelliteError} result of the
+ * operation.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ */
+ public void addAttachRestrictionForCarrier(int subId,
+ @SatelliteManager.SatelliteCommunicationRestrictionReason int reason,
+ @NonNull IIntegerConsumer callback) {
+ enforceSatelliteCommunicationPermission("addAttachRestrictionForCarrier");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mSatelliteController.addAttachRestrictionForCarrier(subId, reason, callback);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Remove a restriction reason for disallowing carrier supported satellite plmn scan and attach
+ * by modem.
+ *
+ * @param subId The subId of the subscription to request for.
+ * @param reason Reason for disallowing satellite communication.
+ * @param callback Listener for the {@link SatelliteManager.SatelliteError} result of the
+ * operation.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ */
+ public void removeAttachRestrictionForCarrier(int subId,
+ @SatelliteManager.SatelliteCommunicationRestrictionReason int reason,
+ @NonNull IIntegerConsumer callback) {
+ enforceSatelliteCommunicationPermission("removeAttachRestrictionForCarrier");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mSatelliteController.removeAttachRestrictionForCarrier(subId, reason, callback);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Get reasons for disallowing satellite communication, as requested by
+ * {@link #addSatelliteAttachRestrictionForCarrier(int, int, IIntegerConsumer)}.
+ *
+ * @param subId The subId of the subscription to request for.
+ *
+ * @return Integer array of reasons for disallowing satellite communication.
+ *
+ * @throws SecurityException if the caller doesn't have the required permission.
+ */
+ public @NonNull int[] getAttachRestrictionReasonsForCarrier(
+ int subId) {
+ enforceSatelliteCommunicationPermission("getAttachRestrictionReasonsForCarrier");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Set<Integer> reasonSet =
+ mSatelliteController.getAttachRestrictionReasonsForCarrier(subId);
+ return reasonSet.stream().mapToInt(i->i).toArray();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Request to get the signal strength of the satellite connection.
+ *
+ * @param subId The subId of the subscription to request for.
+ * @param result Result receiver to get the error code of the request and the current signal
+ * strength of the satellite connection.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ */
+ @Override
+ public void requestNtnSignalStrength(int subId, @NonNull ResultReceiver result) {
+ enforceSatelliteCommunicationPermission("requestNtnSignalStrength");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mSatelliteController.requestNtnSignalStrength(subId, result);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Registers for NTN signal strength changed from satellite modem. If the registration operation
+ * is not successful, a {@link ServiceSpecificException} that contains
+ * {@link SatelliteManager.SatelliteResult} will be thrown.
+ *
+ * @param subId The subId of the subscription to request for.
+ * @param callback The callback to handle the NTN signal strength changed event. If the
+ * operation is successful, {@link NtnSignalStrengthCallback#onNtnSignalStrengthChanged(
+ * NtnSignalStrength)} will return an instance of {@link NtnSignalStrength} with a value of
+ * {@link NtnSignalStrength.NtnSignalStrengthLevel} when the signal strength of non-terrestrial
+ * network has changed.
+ *
+ * @throws SecurityException If the caller doesn't have the required permission.
+ * @throws ServiceSpecificException If the callback registration operation fails.
+ */
+ @Override
+ public void registerForNtnSignalStrengthChanged(int subId,
+ @NonNull INtnSignalStrengthCallback callback) throws RemoteException {
+ enforceSatelliteCommunicationPermission("registerForNtnSignalStrengthChanged");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mSatelliteController.registerForNtnSignalStrengthChanged(subId, callback);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Unregisters for NTN signal strength changed from satellite modem.
+ * If callback was not registered before, the request will be ignored.
+ *
+ * @param subId The subId of the subscription to unregister for listening NTN signal strength
+ * changed event.
+ * @param callback The callback that was passed to
+ * {@link #registerForNtnSignalStrengthChanged(int, INtnSignalStrengthCallback)}
+ *
+ * @throws SecurityException if the caller doesn't have the required permission.
+ */
+ @Override
+ public void unregisterForNtnSignalStrengthChanged(
+ int subId, @NonNull INtnSignalStrengthCallback callback) {
+ enforceSatelliteCommunicationPermission("unregisterForNtnSignalStrengthChanged");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mSatelliteController.unregisterForNtnSignalStrengthChanged(subId, callback);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Registers for satellite capabilities change event from the satellite service.
+ *
+ * @param subId The subId of the subscription to request for.
+ * @param callback The callback to handle the satellite capabilities changed event.
+ *
+ * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ */
+ @Override
+ @SatelliteManager.SatelliteResult public int registerForCapabilitiesChanged(
+ int subId, @NonNull ISatelliteCapabilitiesCallback callback) {
+ enforceSatelliteCommunicationPermission("registerForCapabilitiesChanged");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mSatelliteController.registerForCapabilitiesChanged(subId, callback);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Unregisters for satellite capabilities change event from the satellite service.
+ * If callback was not registered before, the request will be ignored.
+ *
+ * @param subId The subId of the subscription to unregister for satellite capabilities change.
+ * @param callback The callback that was passed to.
+ * {@link #registerForCapabilitiesChanged(int, ISatelliteCapabilitiesCallback)}.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ */
+ @Override
+ public void unregisterForCapabilitiesChanged(int subId,
+ @NonNull ISatelliteCapabilitiesCallback callback) {
+ enforceSatelliteCommunicationPermission("unregisterForCapabilitiesChanged");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mSatelliteController.unregisterForCapabilitiesChanged(subId, callback);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
/**
@@ -12574,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 setEnableNullCipherNotifications(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:
@@ -12624,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/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java
index 4826d2b..0c8a9c7 100644
--- a/src/com/android/phone/PhoneUtils.java
+++ b/src/com/android/phone/PhoneUtils.java
@@ -703,8 +703,12 @@
}
public static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) {
- return makePstnPhoneAccountHandleWithPrefix(phone, "",
- false, phone.getUserHandle());
+ if (phone == null) {
+ return null;
+ } else {
+ return makePstnPhoneAccountHandleWithPrefix(phone, "",
+ false, phone.getUserHandle());
+ }
}
public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(
diff --git a/src/com/android/phone/RcsProvisioningMonitor.java b/src/com/android/phone/RcsProvisioningMonitor.java
index a948d08..87a2869 100644
--- a/src/com/android/phone/RcsProvisioningMonitor.java
+++ b/src/com/android/phone/RcsProvisioningMonitor.java
@@ -524,7 +524,7 @@
filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
mPhone.registerReceiver(mReceiver, filter);
mTelephonyRegistryManager.addOnSubscriptionsChangedListener(
- mSubChangedListener, mSubChangedListener.getHandlerExecutor());
+ mSubChangedListener, mHandler::post);
mDmaChangedListener.register();
//initialize configs for all active sub
onSubChanged();
diff --git a/src/com/android/phone/SimPhonebookProvider.java b/src/com/android/phone/SimPhonebookProvider.java
index 8952865..3917d83 100644
--- a/src/com/android/phone/SimPhonebookProvider.java
+++ b/src/com/android/phone/SimPhonebookProvider.java
@@ -50,6 +50,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IIccPhoneBook;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.uicc.AdnRecord;
import com.android.internal.telephony.uicc.IccConstants;
@@ -173,17 +174,21 @@
@Override
public boolean onCreate() {
ContentResolver resolver = getContext().getContentResolver();
- return onCreate(getContext().getSystemService(SubscriptionManager.class),
+
+ SubscriptionManager sm = getContext().getSystemService(SubscriptionManager.class);
+ if (sm == null) {
+ return false;
+ } else if (Flags.workProfileApiSplit()) {
+ sm = sm.createForAllUserProfiles();
+ }
+ return onCreate(sm,
SimPhonebookProvider::getIccPhoneBook,
uri -> resolver.notifyChange(uri, null));
}
@TestApi
- boolean onCreate(SubscriptionManager subscriptionManager,
+ boolean onCreate(@NonNull SubscriptionManager subscriptionManager,
Supplier<IIccPhoneBook> iccPhoneBookSupplier, ContentNotifier notifier) {
- if (subscriptionManager == null) {
- return false;
- }
mSubscriptionManager = subscriptionManager;
mIccPhoneBookSupplier = iccPhoneBookSupplier;
mContentNotifier = notifier;
diff --git a/src/com/android/phone/SpecialCharSequenceMgr.java b/src/com/android/phone/SpecialCharSequenceMgr.java
index 3bf0e1a..8fe084b 100644
--- a/src/com/android/phone/SpecialCharSequenceMgr.java
+++ b/src/com/android/phone/SpecialCharSequenceMgr.java
@@ -33,6 +33,7 @@
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.TelephonyCapabilities;
+import com.android.internal.telephony.flags.Flags;
import java.util.ArrayList;
import java.util.List;
@@ -208,6 +209,9 @@
private static int getNextSubIdForState(IccCardConstants.State state, Context context) {
SubscriptionManager subscriptionManager = SubscriptionManager.from(context);
+ if (Flags.workProfileApiSplit()) {
+ subscriptionManager = subscriptionManager.createForAllUserProfiles();
+ }
List<SubscriptionInfo> list = subscriptionManager.getActiveSubscriptionInfoList();
if (list == null) {
// getActiveSubscriptionInfoList was null callers expect an empty list.
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index 498e1ea..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";
@@ -3377,7 +3707,7 @@
// clear-carrier-service-package-override
private int clearCarrierServicePackageOverride() {
PrintWriter errPw = getErrPrintWriter();
- int subId = getDefaultSlot();
+ int subId = SubscriptionManager.getDefaultSubscriptionId();
String opt;
while ((opt = getNextOption()) != null) {
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 59ff11d..f3158e6 100644
--- a/src/com/android/phone/settings/RadioInfo.java
+++ b/src/com/android/phone/settings/RadioInfo.java
@@ -44,6 +44,7 @@
import android.os.Message;
import android.os.PersistableBundle;
import android.os.SystemProperties;
+import android.os.UserManager;
import android.telephony.AccessNetworkConstants;
import android.telephony.CarrierConfigManager;
import android.telephony.CellIdentityCdma;
@@ -494,6 +495,15 @@
return;
}
+ UserManager userManager =
+ (UserManager) getApplicationContext().getSystemService(Context.USER_SERVICE);
+ if (userManager != null
+ && userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
+ Log.w(TAG, "User is restricted from configuring mobile networks.");
+ finish();
+ return;
+ }
+
setContentView(R.layout.radio_info);
log("Started onCreate");
@@ -847,7 +857,9 @@
@Override
protected void onDestroy() {
super.onDestroy();
- mQueuedWork.shutdown();
+ if (mQueuedWork != null) {
+ mQueuedWork.shutdown();
+ }
}
// returns array of string labels for each phone index. The array index is equal to the phone
@@ -1555,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/phone/settings/fdn/FdnSetting.java b/src/com/android/phone/settings/fdn/FdnSetting.java
index 8f46c85..e347dec 100644
--- a/src/com/android/phone/settings/fdn/FdnSetting.java
+++ b/src/com/android/phone/settings/fdn/FdnSetting.java
@@ -19,10 +19,12 @@
import android.app.ActionBar;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
+import android.content.Context;
import android.os.AsyncResult;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.os.UserManager;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
import android.util.Log;
diff --git a/src/com/android/phone/slice/SlicePurchaseController.java b/src/com/android/phone/slice/SlicePurchaseController.java
index 4984b23..9a42e16 100644
--- a/src/com/android/phone/slice/SlicePurchaseController.java
+++ b/src/com/android/phone/slice/SlicePurchaseController.java
@@ -58,6 +58,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.FeatureFlags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -309,6 +310,8 @@
/** The Phone instance used to create the SlicePurchaseController. */
@NonNull private final Phone mPhone;
+ /** Feature flags to control behavior and errors. */
+ @NonNull private final FeatureFlags mFeatureFlags;
/** The set of capabilities that are pending network setup. */
@NonNull private final Set<Integer> mPendingSetupCapabilities = new HashSet<>();
/** The set of throttled capabilities. */
@@ -417,10 +420,11 @@
logd("Slice purchase application unable to show notification for capability: "
+ TelephonyManager.convertPremiumCapabilityToString(capability)
+ " because the user has disabled notifications.");
+ int error = mFeatureFlags.slicingAdditionalErrorCodes()
+ ? TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED
+ : TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED;
SlicePurchaseController.getInstance(phoneId)
- .handlePurchaseResult(capability,
- TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED,
- true);
+ .handlePurchaseResult(capability, error, true);
break;
}
case ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS: {
@@ -449,14 +453,16 @@
* @param phone The Phone to get the SlicePurchaseController for.
* @return The static SlicePurchaseController instance.
*/
- @NonNull public static synchronized SlicePurchaseController getInstance(@NonNull Phone phone) {
+ @NonNull public static synchronized SlicePurchaseController getInstance(@NonNull Phone phone,
+ @NonNull FeatureFlags featureFlags) {
// TODO: Add listeners for multi sim setting changed (maybe carrier config changed too)
// that dismiss notifications and update SlicePurchaseController instance
int phoneId = phone.getPhoneId();
if (sInstances.get(phoneId) == null) {
HandlerThread handlerThread = new HandlerThread("SlicePurchaseController");
handlerThread.start();
- sInstances.put(phoneId, new SlicePurchaseController(phone, handlerThread.getLooper()));
+ sInstances.put(phoneId,
+ new SlicePurchaseController(phone, featureFlags, handlerThread.getLooper()));
}
return sInstances.get(phoneId);
}
@@ -476,18 +482,21 @@
* Create a SlicePurchaseController for the given phone on the given looper.
*
* @param phone The Phone to create the SlicePurchaseController for.
+ * @param featureFlags The FeatureFlags that are supported.
* @param looper The Looper to run the SlicePurchaseController on.
*/
@VisibleForTesting
- public SlicePurchaseController(@NonNull Phone phone, @NonNull Looper looper) {
+ public SlicePurchaseController(@NonNull Phone phone, @NonNull FeatureFlags featureFlags,
+ @NonNull Looper looper) {
super(looper);
mPhone = phone;
+ mFeatureFlags = featureFlags;
// TODO: Create a cached value for slicing config in DataIndication and initialize here
mPhone.mCi.registerForSlicingConfigChanged(this, EVENT_SLICING_CONFIG_CHANGED, null);
mIsSlicingUpsellEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_TELEPHONY, KEY_ENABLE_SLICING_UPSELL, false);
DeviceConfig.addOnPropertiesChangedListener(
- DeviceConfig.NAMESPACE_TELEPHONY, this::post,
+ DeviceConfig.NAMESPACE_TELEPHONY, Runnable::run,
properties -> {
if (TextUtils.equals(DeviceConfig.NAMESPACE_TELEPHONY,
properties.getNamespace())) {
diff --git a/src/com/android/services/telephony/DisconnectCauseUtil.java b/src/com/android/services/telephony/DisconnectCauseUtil.java
index d36f8be..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 {
@@ -72,7 +72,7 @@
public static DisconnectCause toTelecomDisconnectCause(
int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason) {
return toTelecomDisconnectCause(telephonyDisconnectCause, telephonyPreciseDisconnectCause,
- reason, SubscriptionManager.getDefaultVoicePhoneId(), null);
+ reason, SubscriptionManager.getDefaultVoicePhoneId(), null, new FlagsAdapterImpl());
}
/**
@@ -86,7 +86,7 @@
public static DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause,
String reason, int phoneId) {
return toTelecomDisconnectCause(telephonyDisconnectCause, CallFailCause.NOT_VALID,
- reason, phoneId, null);
+ reason, phoneId, null, new FlagsAdapterImpl());
}
/**
@@ -101,9 +101,9 @@
*/
public static DisconnectCause toTelecomDisconnectCause(
int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason,
- int phoneId, ImsReasonInfo imsReasonInfo) {
+ int phoneId, ImsReasonInfo imsReasonInfo, FlagsAdapter featureFlags) {
return toTelecomDisconnectCause(telephonyDisconnectCause, telephonyPreciseDisconnectCause,
- reason, phoneId, imsReasonInfo, getCarrierConfigBundle(phoneId));
+ reason, phoneId, imsReasonInfo, getCarrierConfigBundle(phoneId), featureFlags);
}
/**
@@ -115,16 +115,17 @@
@VisibleForTesting
static DisconnectCause toTelecomDisconnectCause(
int telephonyDisconnectCause, int telephonyPreciseDisconnectCause, String reason,
- int phoneId, ImsReasonInfo imsReasonInfo, PersistableBundle carrierConfig) {
+ int phoneId, ImsReasonInfo imsReasonInfo, PersistableBundle carrierConfig,
+ FlagsAdapter featureFlags) {
Context context = PhoneGlobals.getInstance();
return new DisconnectCause(
toTelecomDisconnectCauseCode(telephonyDisconnectCause, carrierConfig),
toTelecomDisconnectCauseLabel(context, telephonyDisconnectCause,
- telephonyPreciseDisconnectCause, carrierConfig),
+ telephonyPreciseDisconnectCause, carrierConfig, featureFlags),
toTelecomDisconnectCauseDescription(context, telephonyDisconnectCause, phoneId),
toTelecomDisconnectReason(context, telephonyDisconnectCause, reason, phoneId),
- toTelecomDisconnectCauseTone(telephonyDisconnectCause, carrierConfig),
+ toTelecomDisconnectCauseTone(telephonyDisconnectCause, carrierConfig, featureFlags),
telephonyDisconnectCause,
telephonyPreciseDisconnectCause,
imsReasonInfo);
@@ -264,20 +265,29 @@
*/
private static CharSequence toTelecomDisconnectCauseLabel(
Context context, int telephonyDisconnectCause, int telephonyPreciseDisconnectCause,
- PersistableBundle carrierConfig) {
+ PersistableBundle carrierConfig, FlagsAdapter featureFlags) {
CharSequence label;
-
- // special case: some carriers determine what disconnect causes play the BUSY tone.
- // hence, must adjust the disconnectCause LABEL to match the tone.
- if (doesCarrierClassifyDisconnectCauseAsBusyCause(telephonyDisconnectCause,
- carrierConfig)) {
- return context.getResources().getString(R.string.callFailed_userBusy);
+ if (!featureFlags.doNotOverridePreciseLabel()) {
+ // special case: some carriers determine what disconnect causes play the BUSY tone.
+ // hence, must adjust the disconnectCause LABEL to match the tone.
+ if (doesCarrierClassifyDisconnectCauseAsBusyCause(telephonyDisconnectCause,
+ carrierConfig)) {
+ return context.getResources().getString(R.string.callFailed_userBusy);
+ }
}
if (telephonyPreciseDisconnectCause != CallFailCause.NOT_VALID) {
label = getLabelFromPreciseDisconnectCause(context, telephonyPreciseDisconnectCause,
telephonyDisconnectCause);
} else {
+ if (featureFlags.doNotOverridePreciseLabel()) {
+ // special case: some carriers determine what disconnect causes play the BUSY tone.
+ // hence, must adjust the disconnectCause LABEL to match the tone.
+ if (doesCarrierClassifyDisconnectCauseAsBusyCause(telephonyDisconnectCause,
+ carrierConfig)) {
+ return context.getResources().getString(R.string.callFailed_userBusy);
+ }
+ }
label = getLabelFromDisconnectCause(context, telephonyDisconnectCause);
}
return label;
@@ -889,7 +899,7 @@
* Returns the tone to play for the disconnect cause, or UNKNOWN if none should be played.
*/
private static int toTelecomDisconnectCauseTone(int telephonyDisconnectCause,
- PersistableBundle carrierConfig) {
+ PersistableBundle carrierConfig, FlagsAdapter featureFlags) {
// special case: some carriers determine what disconnect causes play the BUSY tone.
if (doesCarrierClassifyDisconnectCauseAsBusyCause(telephonyDisconnectCause,
@@ -898,6 +908,10 @@
}
switch (telephonyDisconnectCause) {
+ case android.telephony.DisconnectCause.BUSY:
+ if (featureFlags.doNotOverridePreciseLabel()) {
+ return ToneGenerator.TONE_SUP_BUSY;
+ }
case android.telephony.DisconnectCause.CONGESTION:
return ToneGenerator.TONE_SUP_CONGESTION;
@@ -932,14 +946,21 @@
/**
* Helper method that examines the carrierConfig KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY
* containing the DisconnectCauses that are classified as DisconnectCause.BUSY
- * @param telephonyDisconnectCause
+ *
* @param carrierConfig object that holds all the carrier specific settings
* @return whether the cause is in the carrier config busy tone array
*/
- private static boolean doesCarrierClassifyDisconnectCauseAsBusyCause(
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static boolean doesCarrierClassifyDisconnectCauseAsBusyCause(
int telephonyDisconnectCause, PersistableBundle carrierConfig) {
+ if (carrierConfig == null) {
+ return false;
+ }
int[] busyToneArray = carrierConfig.getIntArray(
CarrierConfigManager.KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY);
+ if (busyToneArray == null) {
+ return false;
+ }
for (int busyTone : busyToneArray) {
if (busyTone == telephonyDisconnectCause) {
return true;
diff --git a/src/com/android/services/telephony/FlagsAdapter.java b/src/com/android/services/telephony/FlagsAdapter.java
new file mode 100644
index 0000000..fdf00a5
--- /dev/null
+++ b/src/com/android/services/telephony/FlagsAdapter.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.services.telephony;
+
+/**
+ * FlagsAdapter can assist in testing flags that are "Fixed Read Only Flags"
+ * (is_fixed_read_only: true)
+ */
+public interface FlagsAdapter {
+ boolean doNotOverridePreciseLabel();
+}
diff --git a/src/com/android/services/telephony/FlagsAdapterImpl.java b/src/com/android/services/telephony/FlagsAdapterImpl.java
new file mode 100644
index 0000000..c935c59
--- /dev/null
+++ b/src/com/android/services/telephony/FlagsAdapterImpl.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.services.telephony;
+
+import com.android.internal.telephony.flags.Flags;
+
+/**
+ * FlagsAdapterImpl should always be used in production when Telephony is checking a flag status.
+ * To help with testing, it may be necessary to have a different implementation
+ * (e.g. flag is read only).
+ */
+public class FlagsAdapterImpl implements FlagsAdapter {
+ public boolean doNotOverridePreciseLabel() {
+ return Flags.doNotOverridePreciseLabel();
+ }
+}
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 755c85f..7f0c800 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -38,6 +38,7 @@
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.flags.Flags;
import com.android.phone.PhoneUtils;
import com.android.phone.R;
import com.android.telephony.Rlog;
@@ -700,7 +701,11 @@
if (mConferenceHost == null) {
return;
}
- mConferenceHost.performHold();
+ if (Flags.conferenceHoldUnholdChangedToSendMessage()) {
+ mConferenceHost.onHold();
+ } else {
+ mConferenceHost.performHold();
+ }
}
/**
@@ -711,7 +716,11 @@
if (mConferenceHost == null) {
return;
}
- mConferenceHost.performUnhold();
+ if (Flags.conferenceHoldUnholdChangedToSendMessage()) {
+ mConferenceHost.onUnhold();
+ } else {
+ mConferenceHost.performUnhold();
+ }
}
/**
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 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/TelephonyConference.java b/src/com/android/services/telephony/TelephonyConference.java
index 7e4693f..4a70e1c 100644
--- a/src/com/android/services/telephony/TelephonyConference.java
+++ b/src/com/android/services/telephony/TelephonyConference.java
@@ -23,6 +23,7 @@
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.Flags;
import java.util.List;
@@ -128,7 +129,11 @@
public void onHold() {
final TelephonyConnection connection = getFirstConnection();
if (connection != null) {
- connection.performHold();
+ if (Flags.conferenceHoldUnholdChangedToSendMessage()) {
+ connection.onHold();
+ } else {
+ connection.performHold();
+ }
}
}
@@ -139,7 +144,11 @@
public void onUnhold() {
final TelephonyConnection connection = getFirstConnection();
if (connection != null) {
- connection.performUnhold();
+ if (Flags.conferenceHoldUnholdChangedToSendMessage()) {
+ connection.onUnhold();
+ } else {
+ connection.performUnhold();
+ }
}
}
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index e048c0a..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");
@@ -1305,9 +1289,11 @@
}
private boolean answeringDropsFgCalls() {
- Bundle extras = getExtras();
- if (extras != null) {
- return extras.getBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL);
+ if (Flags.callExtraForNonHoldSupportedCarriers()) {
+ Bundle extras = getExtras();
+ if (extras != null) {
+ return extras.getBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL);
+ }
}
return false;
}
@@ -2531,8 +2517,8 @@
}
}
- if (mTelephonyConnectionService.maybeReselectDomain(this,
- mOriginalConnection.getPreciseDisconnectCause(), reasonInfo)) {
+ if (mTelephonyConnectionService.maybeReselectDomain(this, reasonInfo,
+ mShowPreciseFailedCause, mHangupDisconnectCause)) {
clearOriginalConnection();
break;
}
@@ -2574,7 +2560,8 @@
disconnectCause,
preciseDisconnectCause,
mOriginalConnection.getVendorDisconnectCause(),
- getPhone().getPhoneId(), imsReasonInfo));
+ getPhone().getPhoneId(), imsReasonInfo,
+ new FlagsAdapterImpl()));
close();
}
break;
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index f37b8a1..8a0382a 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -16,9 +16,13 @@
package com.android.services.telephony;
+import static android.telephony.CarrierConfigManager.KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL;
import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
import static android.telephony.TelephonyManager.HAL_SERVICE_VOICE;
+import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_GSM;
+import static com.android.internal.telephony.flags.Flags.carrierEnabledSatelliteFlag;
+
import android.annotation.NonNull;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -32,6 +36,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelUuid;
+import android.os.PersistableBundle;
import android.provider.DeviceConfig;
import android.telecom.Conference;
import android.telecom.Conferenceable;
@@ -66,6 +71,7 @@
import com.android.ims.ImsManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallFailCause;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.GsmCdmaPhone;
import com.android.internal.telephony.IccCard;
@@ -83,6 +89,7 @@
import com.android.internal.telephony.emergency.EmergencyStateTracker;
import com.android.internal.telephony.emergency.RadioOnHelper;
import com.android.internal.telephony.emergency.RadioOnStateListener;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneConnection;
@@ -587,6 +594,24 @@
releaseEmergencyCallDomainSelection(false);
}
}
+
+ @Override
+ public void onConnectionPropertiesChanged(Connection connection,
+ int connectionProperties) {
+ if ((connection == null) || (mEmergencyStateTracker == null)) {
+ return;
+ }
+ TelephonyConnection c = (TelephonyConnection) connection;
+ com.android.internal.telephony.Connection origConn = c.getOriginalConnection();
+ if ((origConn == null) || (!origConn.getState().isAlive())) {
+ // ignore if there is no original connection alive
+ Log.i(this, "onConnectionPropertiesChanged without orig connection alive");
+ return;
+ }
+ Log.i(this, "onConnectionPropertiesChanged prop=" + connectionProperties);
+ mEmergencyStateTracker.onEmergencyCallPropertiesChanged(connectionProperties,
+ c.getTelecomCallId());
+ }
};
private final TelephonyConnection.TelephonyConnectionListener
@@ -617,6 +642,13 @@
}
};
+ private void clearNormalCallDomainSelectionConnection() {
+ if (mDomainSelectionConnection != null) {
+ mDomainSelectionConnection.finishSelection();
+ mDomainSelectionConnection = null;
+ }
+ }
+
/**
* A listener for calls.
*/
@@ -629,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;
@@ -726,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.");
}
}
});
@@ -1062,8 +1102,6 @@
final boolean isAirplaneModeOn = mDeviceState.isAirplaneModeOn(this);
- boolean needToTurnOnRadio = (isEmergencyNumber && (!isRadioOn() || isAirplaneModeOn))
- || isRadioPowerDownOnBluetooth();
boolean needToTurnOffSatellite = isSatelliteBlockingCall(isEmergencyNumber);
// Get the right phone object from the account data passed in.
@@ -1071,6 +1109,10 @@
/* Note: when not an emergency, handle can be null for unknown callers */
handle == null ? null : handle.getSchemeSpecificPart());
+ boolean isPhoneWifiCallingEnabled = phone != null && phone.isWifiCallingEnabled();
+ boolean needToTurnOnRadio = (isEmergencyNumber && (!isRadioOn() || isAirplaneModeOn))
+ || (isRadioPowerDownOnBluetooth() && !isPhoneWifiCallingEnabled);
+
if (mDomainSelectionResolver.isDomainSelectionSupported()) {
// Normal routing emergency number shall be handled by normal call domain selctor.
if (isEmergencyNumber && !isNormalRouting(phone, number)) {
@@ -1084,7 +1126,7 @@
if (needToTurnOnRadio || needToTurnOffSatellite) {
final Uri resultHandle = handle;
- final int originalPhoneType = phone.getPhoneType();
+ final int originalPhoneType = (phone == null) ? PHONE_TYPE_GSM : phone.getPhoneType();
final Connection resultConnection = getTelephonyConnection(request, numberToDial,
isEmergencyNumber, resultHandle, phone);
if (mRadioOnHelper == null) {
@@ -1096,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) {
@@ -1120,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)) {
@@ -1364,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);
@@ -2190,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()));
@@ -2226,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);
@@ -2251,11 +2301,24 @@
.setVideoState(videoState)
.setIntentExtras(extras)
.setRttTextStream(mNormalCallConnection.getRttTextStream())
- .setIsWpsCall(NormalCallDomainSelectionConnection
- .isWpsCall(number))
+ .setIsWpsCall(PhoneNumberUtils.isWpsCallNumber(number))
.build(),
mNormalCallConnection::registerForCallEvents);
+ if (connection == null) {
+ Log.d(this, "placeOutgoingConnection, phone.dial returned null");
+
+ // On GSM phones, null connection means that we dialed an MMI code
+ int telephonyDisconnectCause = handleMmiCode(
+ phone, android.telephony.DisconnectCause.OUTGOING_FAILURE);
+ mNormalCallConnection.setTelephonyConnectionDisconnected(mDisconnectCauseFactory
+ .toTelecomDisconnectCause(telephonyDisconnectCause,
+ "Connection is null", phone.getPhoneId()));
+ clearNormalCallDomainSelectionConnection();
+ mNormalCallConnection.close();
+ return;
+ }
+
mNormalCallConnection.setOriginalConnection(connection);
mNormalCallConnection.addTelephonyConnectionListener(mNormalCallConnectionListener);
return;
@@ -2279,10 +2342,7 @@
e.getMessage(), phone.getPhoneId()));
mNormalCallConnection.close();
}
- if (mDomainSelectionConnection != null) {
- mDomainSelectionConnection.finishSelection();
- mDomainSelectionConnection = null;
- }
+ clearNormalCallDomainSelectionConnection();
mNormalCallConnection = null;
}
@@ -2313,7 +2373,7 @@
// Check and select same domain as ongoing call on the same subscription (if exists)
int activeCallDomain = getActiveCallDomain(phone.getSubId());
if (activeCallDomain != NetworkRegistrationInfo.DOMAIN_UNKNOWN
- && !NormalCallDomainSelectionConnection.isWpsCall(number)) {
+ && !PhoneNumberUtils.isWpsCallNumber(number)) {
Log.d(LOG_TAG, "Selecting same domain as ongoing call on same subId");
mNormalCallConnection = connection;
handleOutgoingCallConnectionByCallDomainSelection(
@@ -2490,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) {
@@ -2519,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());
@@ -2530,7 +2591,7 @@
}
}
- return maybeReselectDomainForNormalCall(c, callFailCause, reasonInfo);
+ return maybeReselectDomainForNormalCall(c, reasonInfo, showPreciseCause, overrideCause);
}
private boolean maybeReselectDomainForEmergencyCall(final TelephonyConnection c,
@@ -2575,7 +2636,36 @@
return false;
}
+ private boolean isEmergencyNumberAllowedOnDialedSim(Phone phone, String number) {
+ CarrierConfigManager cfgManager = (CarrierConfigManager)
+ phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (cfgManager != null) {
+ PersistableBundle b = cfgManager.getConfigForSubId(phone.getSubId(),
+ KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL);
+ if (b == null) {
+ b = CarrierConfigManager.getDefaultConfig();
+ }
+ // We need to check only when KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL is true.
+ if (b.getBoolean(KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL, false)
+ && (phone.getEmergencyNumberTracker() != null)) {
+ if (!phone.getEmergencyNumberTracker().isEmergencyNumber(number)) {
+ Log.i(this, "isEmergencyNumberAllowedOnDialedSim false");
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
private boolean isNormalRouting(Phone phone, String number) {
+ // Check isEmergencyNumberAllowedOnDialedSim(): some carriers do not want to handle
+ // dial requests for numbers which are in the emergency number list on another SIM,
+ // but not on their own. Such numbers shall be handled by normal call domain selector.
+ return (isNormalRoutingNumber(phone, number)
+ || !isEmergencyNumberAllowedOnDialedSim(phone, number));
+ }
+
+ private boolean isNormalRoutingNumber(Phone phone, String number) {
if (phone.getEmergencyNumberTracker() != null) {
// Note: There can potentially be multiple instances of EmergencyNumber found; if any of
// them have normal routing, then use normal routing.
@@ -2597,7 +2687,7 @@
public Phone getPhoneForNormalRoutedEmergencyCall(String number) {
return Stream.of(mPhoneFactoryProxy.getPhones())
.filter(p -> p.shouldPreferInServiceSimForNormalRoutedEmergencyCall()
- && isNormalRouting(p, number)
+ && isNormalRoutingNumber(p, number)
&& isAvailableForEmergencyCalls(p,
EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL))
.findFirst().orElse(null);
@@ -2642,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);
@@ -2669,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;
@@ -3511,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.
@@ -3824,17 +3936,20 @@
* false otherwise. Assumes that a TelephonyConference supports HOLD.
*/
private boolean allCallsSupportHold(@NonNull TelephonyConnection incomingConnection) {
- 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;
+ 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;
}
@@ -4159,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);
}
/**
@@ -4173,11 +4288,19 @@
* else {@code false}.
*/
private boolean isCallDisallowedDueToSatellite(Phone phone) {
+ if (!carrierEnabledSatelliteFlag()) {
+ return false;
+ }
+
if (phone == null) {
return false;
}
ServiceState serviceState = phone.getServiceState();
+ if (serviceState == null) {
+ return false;
+ }
+
if (!serviceState.isUsingNonTerrestrialNetwork()) {
// Device is not connected to satellite
return false;
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 3388c97..30b9972 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;
@@ -83,12 +86,12 @@
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsManager;
import android.telephony.ims.ImsMmTelManager;
-import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ProvisioningManager;
import android.text.TextUtils;
import android.util.LocalLog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.phone.R;
import java.util.ArrayList;
import java.util.Arrays;
@@ -117,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.
@@ -171,6 +164,7 @@
private CancellationSignal mCancelSignal;
+ // Members for carrier configuration
private @RadioAccessNetworkType int[] mImsRatsConfig;
private @RadioAccessNetworkType int[] mCsRatsConfig;
private @RadioAccessNetworkType int[] mImsRoamRatsConfig;
@@ -180,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;
@@ -191,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;
@@ -214,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);
@@ -229,6 +230,8 @@
mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mCrossSimRedialingController = csrController;
+ mCarrierConfigHelper = carrierConfigHelper;
+ mEcbmHelper = ecbmHelper;
acquireWakeLock();
}
@@ -367,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()) {
@@ -435,6 +448,7 @@
private void startDomainSelection() {
logi("startDomainSelection modemCount=" + mModemCount);
+ readResourceConfiguration();
updateCarrierConfiguration();
mDomainSelectionRequested = true;
startCrossStackTimer();
@@ -469,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.
*/
@@ -483,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);
@@ -557,29 +573,69 @@
}
}
+ /** 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;
- // Emergency network scan requested has not been completed.
- if (mIsScanRequested) return;
-
- // Domain selection completed, {@link #reselectDomain()} will restart domain selection.
- if (mDomainSelected) return;
-
if (!mBarringInfoReceived || !mImsRegStateReceived || !mMmTelCapabilitiesReceived) {
logi("selectDomain not received"
+ " BarringInfo, IMS registration state, or MMTEL capabilities");
return;
}
+ // The statements below should be executed only once to select domain from initial state.
+ // Next domain selection shall be triggered by reselectDomain().
+ // However, selectDomain() can be called by change of IMS service state and Barring status
+ // at any time. mIsScanRequested and mDomainSelected are not enough since there are cases
+ // when neither mIsScanRequested nor mDomainSelected is set though selectDomain() has been
+ // executed already.
+ // Reset mDomainSelectionRequested to avoid redundant execution of selectDomain().
+ mDomainSelectionRequested = false;
+
if (!allowEmergencyCalls(mSelectionAttributes.getEmergencyRegResult())) {
// Detected the country and found that emergency calls are not allowed with this slot.
terminateSelectionPermanentlyForSlot();
return;
}
- if (isWifiPreferred()) {
+ if (isWifiPreferred()
+ || isInEmergencyCallbackModeOnWlan()) {
onWlanSelected();
return;
}
@@ -601,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;
}
@@ -694,8 +755,7 @@
mCancelSignal = new CancellationSignal();
// In case dialing over Wi-Fi has failed, do not the change the domain preference.
if (!wifiFailed) {
- mLastPreferredNetworks = getNextPreferredNetworks(csPreferred, mTryEpsFallback,
- !startVoWifiTimer);
+ mLastPreferredNetworks = getNextPreferredNetworks(csPreferred, mTryEpsFallback);
}
mTryEpsFallback = false;
@@ -733,13 +793,11 @@
*
* @param csPreferred Indicates whether CS preferred scan is requested.
* @param tryEpsFallback Indicates whether scan requested for EPS fallback.
- * @param lastScanFailed Indicates whether this a scan request due to the failure of last scan
- * request.
* @return The list of preferred network types.
*/
@VisibleForTesting
public @RadioAccessNetworkType List<Integer> getNextPreferredNetworks(boolean csPreferred,
- boolean tryEpsFallback, boolean lastScanFailed) {
+ boolean tryEpsFallback) {
if (mRequiresVoLteEnabled && !isAdvancedCallingSettingEnabled()) {
// Emergency call over IMS is not supported.
logi("getNextPreferredNetworks VoLte setting is not enabled.");
@@ -809,21 +867,9 @@
}
}
- // There can be cases that dialing IMS call failed but the modem doesn't know this
- // situation with some vendor solutions. For example, dialing failure due to the
- // emergency registration failure.
- // Remove the current RAT from the scan list to avoid modem select current PLMN.
- // If the scan fails, the next scan will include this RAT again.
- //
- // TODO (b/278183420) Replace this with a better solution by adding indication
- // of call setup failure to the scan request.
- ImsReasonInfo reasonInfo = mSelectionAttributes.getPsDisconnectCause();
- if (!lastScanFailed && reasonInfo != null
- && reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED) {
- logi("getNextPreferredNetworks remove " + mLastNetworkType);
- if (preferredNetworks.size() > 1) {
- preferredNetworks.remove(Integer.valueOf(mLastNetworkType));
- }
+ // Adds NGRAN at the end of the list if SIM is absent or locked and NGRAN is not included.
+ if (!isSimReady() && !preferredNetworks.contains(NGRAN)) {
+ preferredNetworks.add(NGRAN);
}
return preferredNetworks;
@@ -1035,13 +1081,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;
}
@@ -1332,7 +1378,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) {
@@ -1504,10 +1550,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 f176d90..cd70793 100644
--- a/src/com/android/services/telephony/domainselection/NormalCallDomainSelector.java
+++ b/src/com/android/services/telephony/domainselection/NormalCallDomainSelector.java
@@ -28,13 +28,12 @@
import android.telephony.DisconnectCause;
import android.telephony.DomainSelectionService.SelectionAttributes;
import android.telephony.NetworkRegistrationInfo;
+import android.telephony.PhoneNumberUtils;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TransportSelectorCallback;
import android.telephony.ims.ImsReasonInfo;
-import com.android.internal.telephony.domainselection.NormalCallDomainSelectionConnection;
-
/**
* Implements domain selector for outgoing non-emergency calls.
*/
@@ -44,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;
@@ -117,6 +117,16 @@
mImsStateTracker.removeImsStateListener(this);
mSelectionAttributes = null;
mTransportSelectorCallback = null;
+ destroy();
+ }
+
+ @Override
+ public void destroy() {
+ logd("destroy");
+ if (!mDestroyed) {
+ mDestroyed = true;
+ super.destroy();
+ }
}
/**
@@ -233,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)
@@ -260,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)
@@ -292,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);
@@ -375,9 +387,7 @@
// Handle voice call.
if (mImsStateTracker.isImsVoiceCapable()) {
logd("IMS is voice capable");
- // TODO(b/266175810) Remove this dependency.
- if (NormalCallDomainSelectionConnection
- .isWpsCall(mSelectionAttributes.getNumber())) {
+ if (PhoneNumberUtils.isWpsCallNumber(mSelectionAttributes.getNumber())) {
handleWpsCall();
} else {
notifyPsSelected();
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/GbaTestApp/Android.bp b/testapps/GbaTestApp/Android.bp
index 76e02a0..72f7cc4 100644
--- a/testapps/GbaTestApp/Android.bp
+++ b/testapps/GbaTestApp/Android.bp
@@ -22,7 +22,6 @@
static_libs: [
"androidx.appcompat_appcompat",
"androidx-constraintlayout_constraintlayout",
- "ub-uiautomator",
],
srcs: ["src/**/*.java"],
javacflags: ["-parameters"],
diff --git a/testapps/TestRcsApp/TestApp/Android.bp b/testapps/TestRcsApp/TestApp/Android.bp
index 40254af..ea62925 100644
--- a/testapps/TestRcsApp/TestApp/Android.bp
+++ b/testapps/TestRcsApp/TestApp/Android.bp
@@ -14,7 +14,7 @@
"androidx-constraintlayout_constraintlayout",
"aosp_test_rcs_client_base",
"androidx.appcompat_appcompat",
- "libphonenumber-platform"
+ "libphonenumber-platform",
],
libs: ["org.apache.http.legacy"],
@@ -25,12 +25,15 @@
sdk_version: "system_current",
min_sdk_version: "30",
- required: ["privapp-permissions-com.google.android.sample.rcsclient.xml"]
+ required: ["privapp-permissions-com.google.android.sample.rcsclient.xml"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
prebuilt_etc {
name: "privapp-permissions-com.google.android.sample.rcsclient.xml",
src: "etc/permissions/privapp-permissions-com.google.android.sample.rcsclient.xml",
- sub_dir:"permissions",
+ sub_dir: "permissions",
product_specific: true,
}
diff --git a/testapps/TestRcsApp/TestApp/AndroidManifest.xml b/testapps/TestRcsApp/TestApp/AndroidManifest.xml
index 35a0822..3ec9b69 100644
--- a/testapps/TestRcsApp/TestApp/AndroidManifest.xml
+++ b/testapps/TestRcsApp/TestApp/AndroidManifest.xml
@@ -56,6 +56,7 @@
<activity android:name=".ContactListActivity" />
<activity android:name=".ProvisioningActivity" />
<activity android:name=".FileUploadActivity" />
+ <activity android:name=".carrierLock.CarrieLockModeListActivity" />
<provider
android:name=".util.ChatProvider"
@@ -117,6 +118,10 @@
</intent-filter>
</service>
+ <provider
+ android:name=".carrierLock.CarrierLockProvider"
+ android:authorities="com.sample.lockProvider"
+ android:exported="true" />
</application>
</manifest>
diff --git a/testapps/TestRcsApp/TestApp/lint-baseline.xml b/testapps/TestRcsApp/TestApp/lint-baseline.xml
index 8971388..872a626 100644
--- a/testapps/TestRcsApp/TestApp/lint-baseline.xml
+++ b/testapps/TestRcsApp/TestApp/lint-baseline.xml
@@ -1,26 +1,444 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#bootstrapAuthenticationRequest`"
- errorLine1=" telephonyManager.bootstrapAuthenticationRequest(mUiccType,"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Class requires API level 31 (current min is 30): `android.telephony.ims.stub.DelegateConnectionMessageCallback`"
+ errorLine1=" new DelegateConnectionMessageCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java"
- line="130"
- column="30"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="87"
+ column="17"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.gba.UaSecurityProtocolIdentifier.Builder#build`"
- errorLine1=" UaSecurityProtocolIdentifier spId = builder.build();"
- errorLine2=" ~~~~~">
+ message="Class requires API level 31 (current min is 30): `android.telephony.ims.stub.DelegateConnectionStateCallback`"
+ errorLine1=" new DelegateConnectionStateCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="117"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.FeatureTagState#getFeatureTag`"
+ errorLine1=' stringBuilder.append(featureTagState.getFeatureTag()).append(" ").append('
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="148"
+ column="62"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.FeatureTagState#getState`"
+ errorLine1=" featureTagState.getState());"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="149"
+ column="49"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.DelegateRegistrationState#getRegisteredFeatureTags`"
+ errorLine1=" Set<String> registeredFt = registrationState.getRegisteredFeatureTags();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="151"
+ column="66"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ImsManager#getSipDelegateManager`"
+ errorLine1=" mSipDelegateManager = imsManager.getSipDelegateManager(mDefaultSmsSubId);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="220"
+ column="46"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#createSipDelegate`"
+ errorLine1=" mSipDelegateManager.createSipDelegate(new DelegateRequest(featureTags),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="231"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `new android.telephony.ims.DelegateRequest`"
+ errorLine1=" mSipDelegateManager.createSipDelegate(new DelegateRequest(featureTags),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="231"
+ column="59"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#destroySipDelegate`"
+ errorLine1=" mSipDelegateManager.destroySipDelegate(mSipDelegateConnection,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="247"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#destroySipDelegate`"
+ errorLine1=" mSipDelegateManager.destroySipDelegate(mSipDelegateConnection,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="322"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getVersion`"
+ errorLine1=' + "mVersion=" + config.getVersion()'
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="332"
+ column="40"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getTransportType`"
+ errorLine1=' + ", \n\tmTransportType=" + config.getTransportType()'
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="333"
+ column="52"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getLocalAddress`"
+ errorLine1=' + ", \n\tmLocalIpAddr=" + config.getLocalAddress()'
+ errorLine2=" ~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="334"
+ column="50"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServerAddress`"
+ errorLine1=' + ", \n\tmSipServerAddr=" + config.getSipServerAddress()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="335"
+ column="52"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#isSipCompactFormEnabled`"
+ errorLine1=' + ", \n\tmIsSipCompactFormEnabled=" + config.isSipCompactFormEnabled()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="336"
+ column="62"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#isSipKeepaliveEnabled`"
+ errorLine1=' + ", \n\tmIsSipKeepaliveEnabled=" + config.isSipKeepaliveEnabled()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="337"
+ column="60"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getMaxUdpPayloadSizeBytes`"
+ errorLine1=' + ", \n\tmMaxUdpPayloadSize=" + config.getMaxUdpPayloadSizeBytes()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="338"
+ column="56"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicUserIdentifier`"
+ errorLine1=' + ", \n\tmPublicUserIdentifier=" + config.getPublicUserIdentifier()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="339"
+ column="59"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPrivateUserIdentifier`"
+ errorLine1=' + ", \n\tmPrivateUserIdentifier=" + config.getPrivateUserIdentifier()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="340"
+ column="60"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getHomeDomain`"
+ errorLine1=' + ", \n\tmHomeDomain=" + config.getHomeDomain()'
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="341"
+ column="49"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getImei`"
+ errorLine1=' + ", \n\tmImei=" + config.getImei()'
+ errorLine2=" ~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="342"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicGruuUri`"
+ errorLine1=' + ", \n\tmGruu=" + config.getPublicGruuUri()'
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="343"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAuthenticationHeader`"
+ errorLine1=' + ", \n\tmSipAuthHeader=" + config.getSipAuthenticationHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="344"
+ column="52"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAuthenticationNonce`"
+ errorLine1=' + ", \n\tmSipAuthNonce=" + config.getSipAuthenticationNonce()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="345"
+ column="51"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServiceRouteHeader`"
+ errorLine1=' + ", \n\tmServiceRouteHeader=" + config.getSipServiceRouteHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="346"
+ column="57"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPathHeader`"
+ errorLine1=' + ", \n\tmPathHeader=" + config.getSipPathHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="347"
+ column="49"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipUserAgentHeader`"
+ errorLine1=' + ", \n\tmUserAgentHeader=" + config.getSipUserAgentHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="348"
+ column="54"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipContactUserParameter`"
+ errorLine1=' + ", \n\tmContactUserParam=" + config.getSipContactUserParameter()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="349"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPaniHeader`"
+ errorLine1=' + ", \n\tmPaniHeader=" + config.getSipPaniHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="350"
+ column="49"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPlaniHeader`"
+ errorLine1=' + ", \n\tmPlaniHeader=" + config.getSipPlaniHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="351"
+ column="50"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipCniHeader`"
+ errorLine1=' + ", \n\tmCniHeader=" + config.getSipCniHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="352"
+ column="48"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAssociatedUriHeader`"
+ errorLine1=' + ", \n\tmAssociatedUriHeader=" + config.getSipAssociatedUriHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="353"
+ column="58"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getIpSecConfiguration`"
+ errorLine1=' + ", \n\tmIpSecConfiguration=" + config.getIpSecConfiguration()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="354"
+ column="57"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getNatSocketAddress`"
+ errorLine1=" + ", \n\tmNatConfiguration=" + config.getNatSocketAddress() + '}';"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ line="355"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Class requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager.RcsProvisioningCallback`"
+ errorLine1=" new RcsProvisioningCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
+ line="89"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#setRcsClientConfiguration`"
+ errorLine1=" mProvisioningManager.setRcsClientConfiguration(getDefaultClientConfiguration());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
+ line="220"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#registerRcsProvisioningCallback`"
+ errorLine1=" mProvisioningManager.registerRcsProvisioningCallback(getMainExecutor(), mCallback);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
+ line="221"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `new android.telephony.ims.RcsClientConfiguration`"
+ errorLine1=" return new RcsClientConfiguration("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
+ line="231"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#unregisterRcsProvisioningCallback`"
+ errorLine1=" mProvisioningManager.unregisterRcsProvisioningCallback(mCallback);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
+ line="348"
+ column="34"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `new android.telephony.gba.UaSecurityProtocolIdentifier.Builder`"
+ errorLine1=" new UaSecurityProtocolIdentifier.Builder();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java"
- line="129"
- column="57"/>
+ line="120"
+ column="21"/>
</issue>
<issue
@@ -58,134 +476,68 @@
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.DelegateRegistrationState#getRegisteredFeatureTags`"
- errorLine1=" Set<String> registeredFt = registrationState.getRegisteredFeatureTags();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.gba.UaSecurityProtocolIdentifier.Builder#build`"
+ errorLine1=" UaSecurityProtocolIdentifier spId = builder.build();"
+ errorLine2=" ~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="151"
- column="66"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java"
+ line="129"
+ column="57"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.FeatureTagState#getFeatureTag`"
- errorLine1=' stringBuilder.append(featureTagState.getFeatureTag()).append(" ").append('
- errorLine2=" ~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#bootstrapAuthenticationRequest`"
+ errorLine1=" telephonyManager.bootstrapAuthenticationRequest(mUiccType,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="148"
- column="62"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java"
+ line="130"
+ column="30"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.FeatureTagState#getState`"
- errorLine1=" featureTagState.getState());"
- errorLine2=" ~~~~~~~~">
+ message="Cast to `BootstrapAuthenticationCallback` requires API level 31 (current min is 30)"
+ errorLine1=" new BootstrapAuthenticationCallback() {"
+ errorLine2=" ^">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="149"
- column="49"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java"
+ line="135"
+ column="21"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ImsManager#getSipDelegateManager`"
- errorLine1=" mSipDelegateManager = imsManager.getSipDelegateManager(mDefaultSmsSubId);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyManager.BootstrapAuthenticationCallback`"
+ errorLine1=" new BootstrapAuthenticationCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="220"
- column="46"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java"
+ line="135"
+ column="25"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#isRcsVolteSingleRegistrationCapable`"
- errorLine1=" boolean capable = mProvisioningManager.isRcsVolteSingleRegistrationCapable();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Class requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager.RcsProvisioningCallback`"
+ errorLine1=" new RcsProvisioningCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
- line="204"
- column="60"/>
+ line="80"
+ column="17"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#isRcsVolteSingleRegistrationCapable`"
- errorLine1=" mProvisioningManager.isRcsVolteSingleRegistrationCapable();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `new android.telephony.ims.RcsClientConfiguration`"
+ errorLine1=" return new RcsClientConfiguration("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
- line="166"
- column="34"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#registerRcsProvisioningCallback`"
- errorLine1=" mProvisioningManager.registerRcsProvisioningCallback(mExecutorService,"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
- line="181"
- column="42"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#registerRcsProvisioningCallback`"
- errorLine1=" mProvisioningManager.registerRcsProvisioningCallback(getMainExecutor(), mCallback);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
- line="221"
- column="38"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#setRcsClientConfiguration`"
- errorLine1=" mProvisioningManager.setRcsClientConfiguration(getDefaultClientConfiguration());"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
- line="180"
- column="42"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#setRcsClientConfiguration`"
- errorLine1=" mProvisioningManager.setRcsClientConfiguration(getDefaultClientConfiguration());"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
- line="220"
- column="38"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#unregisterRcsProvisioningCallback`"
- errorLine1=" mProvisioningManager.unregisterRcsProvisioningCallback(mCallback);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
- line="195"
- column="38"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#unregisterRcsProvisioningCallback`"
- errorLine1=" mProvisioningManager.unregisterRcsProvisioningCallback(mCallback);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
- line="348"
- column="34"/>
+ line="106"
+ column="16"/>
</issue>
<issue
@@ -201,122 +553,122 @@
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getContactUri`"
- errorLine1=" b.append(t.getContactUri());"
- errorLine2=" ~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#isRcsVolteSingleRegistrationCapable`"
+ errorLine1=" mProvisioningManager.isRcsVolteSingleRegistrationCapable();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="220"
- column="28"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
+ line="166"
+ column="34"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getServiceCapabilities`"
- errorLine1=" t.getServiceCapabilities();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#setRcsClientConfiguration`"
+ errorLine1=" mProvisioningManager.setRcsClientConfiguration(getDefaultClientConfiguration());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="227"
- column="31"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
+ line="180"
+ column="42"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getServiceCapabilities`"
- errorLine1=" if (t.getServiceCapabilities() != null) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#registerRcsProvisioningCallback`"
+ errorLine1=" mProvisioningManager.registerRcsProvisioningCallback(mExecutorService,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="225"
- column="23"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
+ line="181"
+ column="42"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getServiceId`"
- errorLine1=" b.append(t.getServiceId());"
- errorLine2=" ~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#unregisterRcsProvisioningCallback`"
+ errorLine1=" mProvisioningManager.unregisterRcsProvisioningCallback(mCallback);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="222"
- column="28"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
+ line="195"
+ column="38"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getServiceVersion`"
- errorLine1=" b.append(t.getServiceVersion());"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#isRcsVolteSingleRegistrationCapable`"
+ errorLine1=" boolean capable = mProvisioningManager.isRcsVolteSingleRegistrationCapable();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="224"
- column="28"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
+ line="204"
+ column="60"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#getSupportedDuplexModes`"
- errorLine1=" b.append(servCaps.getSupportedDuplexModes());"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="233"
- column="39"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#getUnsupportedDuplexModes`"
- errorLine1=" b.append(servCaps.getUnsupportedDuplexModes());"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="235"
- column="39"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#isAudioCapable`"
- errorLine1=" b.append(servCaps.isAudioCapable());"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="229"
- column="39"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#isVideoCapable`"
- errorLine1=" b.append(servCaps.isVideoCapable());"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="231"
- column="39"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactUceCapability#getCapabilityMechanism`"
- errorLine1=" if (c.getCapabilityMechanism() == RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="216"
- column="15"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactUceCapability#getCapabilityTuples`"
- errorLine1=" for (RcsContactPresenceTuple t : c.getCapabilityTuples()) {"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsUceAdapter#requestCapabilities`"
+ errorLine1=" mImsRcsManager.getUceAdapter().requestCapabilities(contactList, getMainExecutor(),"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="218"
+ line="95"
+ column="48"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Cast to `CapabilitiesCallback` requires API level 31 (current min is 30)"
+ errorLine1=" new RcsUceAdapter.CapabilitiesCallback() {"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="96"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Class requires API level 31 (current min is 30): `android.telephony.ims.RcsUceAdapter.CapabilitiesCallback`"
+ errorLine1=" new RcsUceAdapter.CapabilitiesCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="96"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsUceAdapter#requestAvailability`"
+ errorLine1=" mImsRcsManager.getUceAdapter().requestAvailability(contactList.get(0),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="135"
+ column="48"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Cast to `CapabilitiesCallback` requires API level 31 (current min is 30)"
+ errorLine1=" getMainExecutor(), new RcsUceAdapter.CapabilitiesCallback() {"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="136"
+ column="44"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Class requires API level 31 (current min is 30): `android.telephony.ims.RcsUceAdapter.CapabilitiesCallback`"
+ errorLine1=" getMainExecutor(), new RcsUceAdapter.CapabilitiesCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="136"
column="48"/>
</issue>
@@ -355,475 +707,123 @@
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsUceAdapter#requestAvailability`"
- errorLine1=" mImsRcsManager.getUceAdapter().requestAvailability(contactList.get(0),"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactUceCapability#getCapabilityMechanism`"
+ errorLine1=" if (c.getCapabilityMechanism() == RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="216"
+ column="15"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactUceCapability#getCapabilityTuples`"
+ errorLine1=" for (RcsContactPresenceTuple t : c.getCapabilityTuples()) {"
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="135"
+ line="218"
column="48"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsUceAdapter#requestCapabilities`"
- errorLine1=" mImsRcsManager.getUceAdapter().requestCapabilities(contactList, getMainExecutor(),"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getContactUri`"
+ errorLine1=" b.append(t.getContactUri());"
+ errorLine2=" ~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="95"
- column="48"/>
+ line="220"
+ column="28"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getHomeDomain`"
- errorLine1=' + ", \n\tmHomeDomain=" + config.getHomeDomain()'
- errorLine2=" ~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getServiceId`"
+ errorLine1=" b.append(t.getServiceId());"
+ errorLine2=" ~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="341"
- column="49"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="222"
+ column="28"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getImei`"
- errorLine1=' + ", \n\tmImei=" + config.getImei()'
- errorLine2=" ~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getServiceVersion`"
+ errorLine1=" b.append(t.getServiceVersion());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="342"
- column="43"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="224"
+ column="28"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getIpSecConfiguration`"
- errorLine1=' + ", \n\tmIpSecConfiguration=" + config.getIpSecConfiguration()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getServiceCapabilities`"
+ errorLine1=" if (t.getServiceCapabilities() != null) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="354"
- column="57"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="225"
+ column="23"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getLocalAddress`"
- errorLine1=' + ", \n\tmLocalIpAddr=" + config.getLocalAddress()'
- errorLine2=" ~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple#getServiceCapabilities`"
+ errorLine1=" t.getServiceCapabilities();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="334"
- column="50"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="227"
+ column="31"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getMaxUdpPayloadSizeBytes`"
- errorLine1=' + ", \n\tmMaxUdpPayloadSize=" + config.getMaxUdpPayloadSizeBytes()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#isAudioCapable`"
+ errorLine1=" b.append(servCaps.isAudioCapable());"
+ errorLine2=" ~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="338"
- column="56"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
+ line="229"
+ column="39"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getNatSocketAddress`"
- errorLine1=" + ", \n\tmNatConfiguration=" + config.getNatSocketAddress() + '}';"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#isVideoCapable`"
+ errorLine1=" b.append(servCaps.isVideoCapable());"
+ errorLine2=" ~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="355"
- column="55"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPrivateUserIdentifier`"
- errorLine1=' + ", \n\tmPrivateUserIdentifier=" + config.getPrivateUserIdentifier()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="340"
- column="60"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicGruuUri`"
- errorLine1=' + ", \n\tmGruu=" + config.getPublicGruuUri()'
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="343"
- column="43"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicUserIdentifier`"
- errorLine1=' + ", \n\tmPublicUserIdentifier=" + config.getPublicUserIdentifier()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="339"
- column="59"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAssociatedUriHeader`"
- errorLine1=' + ", \n\tmAssociatedUriHeader=" + config.getSipAssociatedUriHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="353"
- column="58"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAuthenticationHeader`"
- errorLine1=' + ", \n\tmSipAuthHeader=" + config.getSipAuthenticationHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="344"
- column="52"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAuthenticationNonce`"
- errorLine1=' + ", \n\tmSipAuthNonce=" + config.getSipAuthenticationNonce()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="345"
- column="51"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipCniHeader`"
- errorLine1=' + ", \n\tmCniHeader=" + config.getSipCniHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="352"
- column="48"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipContactUserParameter`"
- errorLine1=' + ", \n\tmContactUserParam=" + config.getSipContactUserParameter()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="349"
- column="55"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPaniHeader`"
- errorLine1=' + ", \n\tmPaniHeader=" + config.getSipPaniHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="350"
- column="49"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPathHeader`"
- errorLine1=' + ", \n\tmPathHeader=" + config.getSipPathHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="347"
- column="49"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPlaniHeader`"
- errorLine1=' + ", \n\tmPlaniHeader=" + config.getSipPlaniHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="351"
- column="50"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServerAddress`"
- errorLine1=' + ", \n\tmSipServerAddr=" + config.getSipServerAddress()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="335"
- column="52"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServiceRouteHeader`"
- errorLine1=' + ", \n\tmServiceRouteHeader=" + config.getSipServiceRouteHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="346"
- column="57"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipUserAgentHeader`"
- errorLine1=' + ", \n\tmUserAgentHeader=" + config.getSipUserAgentHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="348"
- column="54"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getTransportType`"
- errorLine1=' + ", \n\tmTransportType=" + config.getTransportType()'
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="333"
- column="52"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getVersion`"
- errorLine1=' + "mVersion=" + config.getVersion()'
- errorLine2=" ~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="332"
- column="40"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#isSipCompactFormEnabled`"
- errorLine1=' + ", \n\tmIsSipCompactFormEnabled=" + config.isSipCompactFormEnabled()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="336"
- column="62"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#isSipKeepaliveEnabled`"
- errorLine1=' + ", \n\tmIsSipKeepaliveEnabled=" + config.isSipKeepaliveEnabled()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="337"
- column="60"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#createSipDelegate`"
- errorLine1=" mSipDelegateManager.createSipDelegate(new DelegateRequest(featureTags),"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
+ file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
line="231"
- column="41"/>
+ column="39"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#destroySipDelegate`"
- errorLine1=" mSipDelegateManager.destroySipDelegate(mSipDelegateConnection,"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="247"
- column="37"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#destroySipDelegate`"
- errorLine1=" mSipDelegateManager.destroySipDelegate(mSipDelegateConnection,"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="322"
- column="33"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.telephony.gba.UaSecurityProtocolIdentifier.Builder`"
- errorLine1=" new UaSecurityProtocolIdentifier.Builder();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java"
- line="120"
- column="21"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.telephony.ims.DelegateRequest`"
- errorLine1=" mSipDelegateManager.createSipDelegate(new DelegateRequest(featureTags),"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="231"
- column="59"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.telephony.ims.RcsClientConfiguration`"
- errorLine1=" return new RcsClientConfiguration("
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
- line="231"
- column="16"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.telephony.ims.RcsClientConfiguration`"
- errorLine1=" return new RcsClientConfiguration("
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
- line="106"
- column="16"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Cast to `BootstrapAuthenticationCallback` requires API level 31 (current min is 30)"
- errorLine1=" new BootstrapAuthenticationCallback() {"
- errorLine2=" ^">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java"
- line="135"
- column="21"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Cast to `CapabilitiesCallback` requires API level 31 (current min is 30)"
- errorLine1=" getMainExecutor(), new RcsUceAdapter.CapabilitiesCallback() {"
- errorLine2=" ^">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#getSupportedDuplexModes`"
+ errorLine1=" b.append(servCaps.getSupportedDuplexModes());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="136"
- column="44"/>
+ line="233"
+ column="39"/>
</issue>
<issue
id="NewApi"
- message="Cast to `CapabilitiesCallback` requires API level 31 (current min is 30)"
- errorLine1=" new RcsUceAdapter.CapabilitiesCallback() {"
- errorLine2=" ^">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities#getUnsupportedDuplexModes`"
+ errorLine1=" b.append(servCaps.getUnsupportedDuplexModes());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="96"
- column="25"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyManager.BootstrapAuthenticationCallback`"
- errorLine1=" new BootstrapAuthenticationCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/GbaActivity.java"
- line="135"
- column="25"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager.RcsProvisioningCallback`"
- errorLine1=" new RcsProvisioningCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/FileUploadActivity.java"
- line="89"
- column="17"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager.RcsProvisioningCallback`"
- errorLine1=" new RcsProvisioningCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java"
- line="80"
- column="17"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.ims.RcsUceAdapter.CapabilitiesCallback`"
- errorLine1=" getMainExecutor(), new RcsUceAdapter.CapabilitiesCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="136"
- column="48"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.ims.RcsUceAdapter.CapabilitiesCallback`"
- errorLine1=" new RcsUceAdapter.CapabilitiesCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java"
- line="96"
- column="29"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.ims.stub.DelegateConnectionMessageCallback`"
- errorLine1=" new DelegateConnectionMessageCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="87"
- column="17"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.ims.stub.DelegateConnectionStateCallback`"
- errorLine1=" new DelegateConnectionStateCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/DelegateActivity.java"
- line="117"
- column="17"/>
+ line="235"
+ column="39"/>
</issue>
</issues>
\ No newline at end of file
diff --git a/testapps/TestRcsApp/TestApp/res/layout/CarrierLockListLayout.xml b/testapps/TestRcsApp/TestApp/res/layout/CarrierLockListLayout.xml
new file mode 100644
index 0000000..f07c65c
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/res/layout/CarrierLockListLayout.xml
@@ -0,0 +1,77 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".MainActivity">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <Button
+ android:id="@+id/noLockMode"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/no_LockMode"
+ android:textAlignment="center"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/lockToVZW"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/no_LockTo_VZW"
+ android:textAlignment="center"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/lockToATT"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/no_LockTo_ATT"
+ android:textAlignment="center"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/lockToTMO"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/no_LockTo_TMO"
+ android:textAlignment="center"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/lockToKOODOS"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/no_LockTo_KOODOS"
+ android:textAlignment="center"
+ android:textAllCaps="false" />
+
+ <Button
+ android:id="@+id/lockToTELUS"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/no_LockTo_TELUS"
+ android:textAlignment="center"
+ android:textAllCaps="false" />
+ </LinearLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/testapps/TestRcsApp/TestApp/res/layout/activity_main.xml b/testapps/TestRcsApp/TestApp/res/layout/activity_main.xml
index 939feb0..ebf5508 100644
--- a/testapps/TestRcsApp/TestApp/res/layout/activity_main.xml
+++ b/testapps/TestRcsApp/TestApp/res/layout/activity_main.xml
@@ -74,6 +74,14 @@
android:textAlignment="center"
android:textAllCaps="false" />
+ <Button
+ android:id="@+id/setCarrierLockMode"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/setCarrierLockMode"
+ android:textAlignment="center"
+ android:textAllCaps="false"/>
+
<TextView
android:id="@+id/version_info"
android:layout_width="match_parent"
diff --git a/testapps/TestRcsApp/TestApp/res/values/donottranslate_strings.xml b/testapps/TestRcsApp/TestApp/res/values/donottranslate_strings.xml
index f52b70d..b017139 100644
--- a/testapps/TestRcsApp/TestApp/res/values/donottranslate_strings.xml
+++ b/testapps/TestRcsApp/TestApp/res/values/donottranslate_strings.xml
@@ -72,6 +72,8 @@
<string name="browse">Browse</string>
<string name="upload">Upload</string>
<string name="upload_file_gba">Upload File with GBA</string>
+ <string name="setCarrierLockMode">CarrierLock</string>
+
<string name="invalid_parameters">Invalid Parameters</string>
<string name="server">Server:</string>
<string name="file_name">File Name:</string>
@@ -79,6 +81,13 @@
<string name="file_empty">File is empty</string>
<string name="version_info">Version: %s</string>
+ <string name="no_LockMode">NoLock/ UnLocked</string>
+ <string name="no_LockTo_VZW">Lock to Verizon</string>
+ <string name="no_LockTo_ATT">Lock to ATT</string>
+ <string name="no_LockTo_TMO">Lock to TMO</string>
+ <string name="no_LockTo_KOODOS">Lock to KOODO</string>
+ <string name="no_LockTo_TELUS">Lock to TELUS</string>
+
<string-array name="rcs_profile">
<item>UP_1.0</item>
<item>UP_2.3</item>
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/MainActivity.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/MainActivity.java
index 89c5268..5d2db73 100644
--- a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/MainActivity.java
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/MainActivity.java
@@ -29,6 +29,8 @@
import androidx.appcompat.app.AppCompatActivity;
+import com.google.android.sample.rcsclient.carrierLock.CarrieLockModeListActivity;
+
/** An activity to show function list. */
public class MainActivity extends AppCompatActivity {
private static final String TAG = "TestRcsApp.MainActivity";
@@ -39,6 +41,7 @@
private Button mMessageClientButton;
private Button mFileUploadButton;
private TextView mVersionInfo;
+ private Button mCarrierLockModeListBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -56,6 +59,7 @@
mGbaButton = (Button) this.findViewById(R.id.gba);
mFileUploadButton = findViewById(R.id.uploadFile);
mVersionInfo = this.findViewById(R.id.version_info);
+ mCarrierLockModeListBtn = findViewById(R.id.setCarrierLockMode);
mProvisionButton.setOnClickListener(view -> {
Intent intent = new Intent(this, ProvisioningActivity.class);
MainActivity.this.startActivity(intent);
@@ -90,6 +94,11 @@
appVersionName);
mVersionInfo.setText(version);
}
+
+ mCarrierLockModeListBtn.setOnClickListener(view -> {
+ Intent intent = new Intent(this, CarrieLockModeListActivity.class);
+ MainActivity.this.startActivity(intent);
+ });
}
@Override
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/carrierLock/CarrieLockModeListActivity.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/carrierLock/CarrieLockModeListActivity.java
new file mode 100644
index 0000000..6547aeb
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/carrierLock/CarrieLockModeListActivity.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.sample.rcsclient.carrierLock;
+
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.google.android.sample.rcsclient.R;
+
+public class CarrieLockModeListActivity extends AppCompatActivity {
+
+ private final CarrierLockProvider mCarrierLockProvider = new CarrierLockProvider();
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.CarrierLockListLayout);
+
+ Button noLockModeBtn = this.findViewById(R.id.noLockMode);
+ assert noLockModeBtn != null;
+ noLockModeBtn.setOnClickListener(view -> {
+ mCarrierLockProvider.setLockMode(CarrierRestriction.UNLOCKED);
+ Toast.makeText(this, "Lock mode set to UNLOCKED", Toast.LENGTH_LONG).show();
+ });
+
+ Button vzwLockModeBtn = this.findViewById(R.id.lockToVZW);
+ assert vzwLockModeBtn != null;
+ vzwLockModeBtn.setOnClickListener(view -> {
+ mCarrierLockProvider.setLockMode(CarrierRestriction.LOCK_TO_VZW);
+ Toast.makeText(this, "Lock mode set to VZW", Toast.LENGTH_LONG).show();
+ });
+
+ Button attLockModeBtn = this.findViewById(R.id.lockToATT);
+ assert attLockModeBtn != null;
+ attLockModeBtn.setOnClickListener(view -> {
+ mCarrierLockProvider.setLockMode(CarrierRestriction.LOCK_TO_ATT);
+ Toast.makeText(this, "Lock mode set to ATT", Toast.LENGTH_LONG).show();
+ });
+
+ Button tmoLockModeBtn = this.findViewById(R.id.lockToTMO);
+ assert tmoLockModeBtn != null;
+ tmoLockModeBtn.setOnClickListener(view -> {
+ mCarrierLockProvider.setLockMode(CarrierRestriction.LOCK_TO_TMO);
+ Toast.makeText(this, "Lock mode set to TMO", Toast.LENGTH_LONG).show();
+ });
+
+ Button koodoLockModeBtn = this.findViewById(R.id.lockToKOODOS);
+ assert koodoLockModeBtn != null;
+ koodoLockModeBtn.setOnClickListener(view -> {
+ mCarrierLockProvider.setLockMode(CarrierRestriction.LOCK_TO_KOODO);
+ Toast.makeText(this, "Lock mode set to KOODO", Toast.LENGTH_LONG).show();
+ });
+
+ Button telusLockModeBtn = this.findViewById(R.id.lockToTELUS);
+ assert telusLockModeBtn != null;
+ telusLockModeBtn.setOnClickListener(view -> {
+ mCarrierLockProvider.setLockMode(CarrierRestriction.LOCK_TO_TELUS);
+ Toast.makeText(this, "Lock mode set to TELUS", Toast.LENGTH_LONG).show();
+ });
+ }
+}
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/carrierLock/CarrierLockProvider.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/carrierLock/CarrierLockProvider.java
new file mode 100644
index 0000000..8fa3cd6
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/carrierLock/CarrierLockProvider.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.sample.rcsclient.carrierLock;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
+
+public class CarrierLockProvider extends ContentProvider {
+
+ public static final String AUTHORITY = "com.sample.lockProvider";
+ public static final String TAG = "TestCarrierLockProvider";
+
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/carrierLock");
+ // content://com.sample.lockProvider/carrierLock
+
+ private static CarrierRestriction mLockMode = CarrierRestriction.UNLOCKED;
+ private static final ArrayList<Integer> mCarrierIds = new ArrayList<>();
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Bundle call(String method, String args, Bundle extras) {
+ Bundle result = new Bundle();
+ Log.d(TAG, "call query STARTED on method = " + method);
+ switch (method) {
+ case "getCarrierRestrictionStatus":
+ try {
+ if (mLockMode == CarrierRestriction.UNLOCKED) {
+ result.putInt("restriction_status", 0); // Unlocked
+ } else {
+ result.putInt("restriction_status", 2); // Locked/Restricted
+ }
+ mCarrierIds.clear();
+ Log.d(TAG, "Query come : Lock mode set to " + mLockMode);
+ switch (mLockMode) {
+ case UNLOCKED:
+ // Do Nothing
+ break;
+ case LOCK_TO_VZW:
+ mCarrierIds.add(1839);
+ break;
+ case LOCK_TO_ATT:
+ mCarrierIds.add(1187);
+ mCarrierIds.add(10021);
+ mCarrierIds.add(2119);
+ mCarrierIds.add(2120);
+ mCarrierIds.add(1779);
+ mCarrierIds.add(10028);
+ break;
+ case LOCK_TO_TMO:
+ mCarrierIds.add(1);
+ break;
+ case LOCK_TO_KOODO:
+ mCarrierIds.add(2020);
+ break;
+ case LOCK_TO_TELUS:
+ mCarrierIds.add(1404);
+ break;
+ default:
+ // Nothing
+ }
+ StringJoiner joiner = new StringJoiner(", ");
+ if (!mCarrierIds.isEmpty()) {
+ result.putIntegerArrayList("allowed_carrier_ids", mCarrierIds);
+ for (Integer num : mCarrierIds) {
+ joiner.add(num.toString());
+ }
+ result.putString("PrintableCarrierIds", joiner.toString());
+ Log.d(TAG, "Locked to carrierIds = " + joiner.toString());
+ } else {
+ result.putString("allowed_carrier_ids", "");
+ result.putString("PrintableCarrierIds", "");
+ }
+
+ } catch (Exception e) {
+ Log.e(TAG, " call :: query :: exception = " + e.getMessage());
+ }
+ return result;
+
+ case "getList:":
+ String list = String.valueOf(
+ mCarrierIds.size());
+ result.putString("carrierList", list);
+ return result;
+ default:
+ return null;
+ }
+ }
+
+ private void updateLockValue(int lockValue) {
+ Log.d(TAG, "updateLockValue through ADB to = " + lockValue);
+ switch (lockValue) {
+ case 1:
+ mLockMode = CarrierRestriction.LOCK_TO_VZW;
+ break;
+ case 2:
+ mLockMode = CarrierRestriction.LOCK_TO_ATT;
+ break;
+ case 3:
+ mLockMode = CarrierRestriction.LOCK_TO_TMO;
+ break;
+ case 4:
+ mLockMode = CarrierRestriction.LOCK_TO_KOODO;
+ break;
+ case 5:
+ mLockMode = CarrierRestriction.LOCK_TO_TELUS;
+ break;
+ default:
+ mLockMode = CarrierRestriction.UNLOCKED;
+ break;
+ }
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ Log.d(TAG, "CarrierLockProvider Query");
+ return null;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return "vnd.android.cursor.dir/vnd." + AUTHORITY + ".books";
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ Log.d(TAG, "CarrierLockProvider insert START");
+ assert values != null;
+ int newValue = values.getAsInteger("newValue");
+ updateLockValue(newValue);
+ return CONTENT_URI;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ public void setLockMode(CarrierRestriction lockMode) {
+ mLockMode = lockMode;
+ Log.d(TAG, "Setting lockMode to " + mLockMode);
+ }
+}
\ No newline at end of file
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/carrierLock/CarrierRestriction.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/carrierLock/CarrierRestriction.java
new file mode 100644
index 0000000..34f9e7b
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/carrierLock/CarrierRestriction.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.sample.rcsclient.carrierLock;
+
+public enum CarrierRestriction {
+ UNLOCKED,
+ LOCK_TO_VZW,
+ LOCK_TO_ATT,
+ LOCK_TO_TMO,
+ LOCK_TO_KOODO,
+ LOCK_TO_TELUS
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/Android.bp b/testapps/TestRcsApp/aosp_test_rcsclient/Android.bp
index 34b0a12..e1de685 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/Android.bp
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/Android.bp
@@ -26,4 +26,7 @@
sdk_version: "system_current",
min_sdk_version: "30",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/lint-baseline.xml b/testapps/TestRcsApp/aosp_test_rcsclient/lint-baseline.xml
index e0c7c3e..b2110a3 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/lint-baseline.xml
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/lint-baseline.xml
@@ -1,48 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.ConnectivityManager#registerQosCallback`"
- errorLine1=" connectivityManager.registerQosCallback(new QosSocketInfo(network, socket),"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSession.java"
- line="118"
- column="33"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.net.ConnectivityManager#unregisterQosCallback`"
- errorLine1=" connectivityManager.unregisterQosCallback(qosCallback);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSession.java"
- line="181"
- column="29"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#bootstrapAuthenticationRequest`"
- errorLine1=" telephonyManager.bootstrapAuthenticationRequest("
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `new android.telephony.gba.UaSecurityProtocolIdentifier.Builder`"
+ errorLine1=" new UaSecurityProtocolIdentifier.Builder();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java"
- line="97"
- column="26"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.gba.UaSecurityProtocolIdentifier.Builder#build`"
- errorLine1=" UaSecurityProtocolIdentifier spId = builder.build();"
- errorLine2=" ~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java"
- line="79"
- column="53"/>
+ line="55"
+ column="17"/>
</issue>
<issue
@@ -91,673 +58,57 @@
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.DelegateRegistrationState#getRegisteredFeatureTags`"
- errorLine1=" .getRegisteredFeatureTags()"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.gba.UaSecurityProtocolIdentifier.Builder#build`"
+ errorLine1=" UaSecurityProtocolIdentifier spId = builder.build();"
+ errorLine2=" ~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="139"
- column="34"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.DelegateRegistrationState#getRegisteredFeatureTags`"
- errorLine1=" Set<String> registeredFt = registrationState.getRegisteredFeatureTags();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="223"
- column="58"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.FeatureTagState#getFeatureTag`"
- errorLine1=' stringBuilder.append(featureTagState.getFeatureTag()).append(" ").append('
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="220"
- column="54"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.FeatureTagState#getState`"
- errorLine1=" featureTagState.getState());"
- errorLine2=" ~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="221"
- column="41"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ImsManager#getSipDelegateManager`"
- errorLine1=" this.sipDelegateManager = imsManager.getSipDelegateManager(subscriptionId);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="77"
- column="46"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#isRcsVolteSingleRegistrationCapable`"
- errorLine1=" return provisioningManager.isRcsVolteSingleRegistrationCapable();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
- line="166"
- column="36"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#registerRcsProvisioningCallback`"
- errorLine1=" provisioningManager.registerRcsProvisioningCallback(executorService, callback);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
- line="147"
- column="33"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#setRcsClientConfiguration`"
- errorLine1=" provisioningManager.setRcsClientConfiguration(clientConfiguration);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
- line="111"
- column="29"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#triggerRcsReconfiguration`"
- errorLine1=" provisioningManager.triggerRcsReconfiguration();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
- line="176"
- column="29"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#unregisterRcsProvisioningCallback`"
- errorLine1=" provisioningManager.unregisterRcsProvisioningCallback(callback);"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
- line="158"
- column="33"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getHomeDomain`"
- errorLine1=' + ", \n\tmHomeDomain=" + config.getHomeDomain()'
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="246"
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java"
+ line="79"
column="53"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getHomeDomain`"
- errorLine1=" return mConfiguration.getHomeDomain();"
- errorLine2=" ~~~~~~~~~~~~~">
+ message="Cast to `BootstrapAuthenticationCallback` requires API level 31 (current min is 30)"
+ errorLine1=" new TelephonyManager.BootstrapAuthenticationCallback() {"
+ errorLine2=" ^">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="317"
- column="35"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java"
+ line="81"
+ column="17"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getImei`"
- errorLine1=' + ", \n\tmImei=" + config.getImei()'
- errorLine2=" ~~~~~~~">
+ message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyManager.BootstrapAuthenticationCallback`"
+ errorLine1=" new TelephonyManager.BootstrapAuthenticationCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="247"
- column="47"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java"
+ line="81"
+ column="21"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getImei`"
- errorLine1=" return mConfiguration.getImei();"
- errorLine2=" ~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.TelephonyManager#bootstrapAuthenticationRequest`"
+ errorLine1=" telephonyManager.bootstrapAuthenticationRequest("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="357"
- column="35"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java"
+ line="97"
+ column="26"/>
</issue>
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getIpSecConfiguration`"
- errorLine1=' + ", \n\tmIpSecConfiguration=" + config.getIpSecConfiguration()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `new android.telephony.ims.SipMessage`"
+ errorLine1=" return new SipMessage(startLine, headers.toString(), rawContent);"
+ errorLine2=" ~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="259"
- column="61"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getIpSecConfiguration`"
- errorLine1=" SipDelegateConfiguration.IpSecConfiguration c = mConfiguration.getIpSecConfiguration();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="333"
- column="76"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getLocalAddress`"
- errorLine1=' + ", \n\tmLocalAddr=" + config.getLocalAddress()'
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="239"
- column="52"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getLocalAddress`"
- errorLine1=" return mConfiguration.getLocalAddress().getAddress().getHostAddress();"
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="296"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getLocalAddress`"
- errorLine1=" return mConfiguration.getLocalAddress().getPort();"
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="301"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getMaxUdpPayloadSizeBytes`"
- errorLine1=' + ", \n\tmMaxUdpPayloadSize=" + config.getMaxUdpPayloadSizeBytes()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="243"
- column="60"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getMaxUdpPayloadSizeBytes`"
- errorLine1=" ? mConfiguration.getMaxUdpPayloadSizeBytes() : 1500;"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="378"
- column="38"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getMaxUdpPayloadSizeBytes`"
- errorLine1=" return mConfiguration.getMaxUdpPayloadSizeBytes() > 0"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="377"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getNatSocketAddress`"
- errorLine1=" + ", \n\tmNatConfiguration=" + config.getNatSocketAddress() + '}';"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="260"
- column="59"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPrivateUserIdentifier`"
- errorLine1=' + ", \n\tmPrivateUserIdentifier=" + config.getPrivateUserIdentifier()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="245"
- column="64"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicGruuUri`"
- errorLine1=' + ", \n\tmGruu=" + config.getPublicGruuUri()'
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="248"
- column="47"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicUserIdentifier`"
- errorLine1=' + ", \n\tmPublicUserIdentifier=" + config.getPublicUserIdentifier()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="244"
- column="63"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicUserIdentifier`"
- errorLine1=" return mConfiguration.getPublicUserIdentifier();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="312"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAssociatedUriHeader`"
- errorLine1=' + ", \n\tmAssociatedUriHeader=" + config.getSipAssociatedUriHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="258"
- column="62"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAssociatedUriHeader`"
- errorLine1=" String associatedUris = mConfiguration.getSipAssociatedUriHeader();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="322"
- column="52"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAuthenticationHeader`"
- errorLine1=' + ", \n\tmSipAuthHeader=" + config.getSipAuthenticationHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="249"
- column="56"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAuthenticationNonce`"
- errorLine1=' + ", \n\tmSipAuthNonce=" + config.getSipAuthenticationNonce()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="250"
- column="55"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipCniHeader`"
- errorLine1=' + ", \n\tmCniHeader=" + config.getSipCniHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="257"
- column="52"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipContactUserParameter`"
- errorLine1=' + ", \n\tmContactUserParam=" + config.getSipContactUserParameter()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="254"
- column="59"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipContactUserParameter`"
- errorLine1=" return mConfiguration.getSipContactUserParameter();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="352"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPaniHeader`"
- errorLine1=' + ", \n\tmPaniHeader=" + config.getSipPaniHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="255"
- column="53"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPaniHeader`"
- errorLine1=" return mConfiguration.getSipPaniHeader();"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="362"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPathHeader`"
- errorLine1=' + ", \n\tmPathHeader=" + config.getSipPathHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="252"
- column="53"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPlaniHeader`"
- errorLine1=' + ", \n\tmPlaniHeader=" + config.getSipPlaniHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="256"
- column="54"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPlaniHeader`"
- errorLine1=" return mConfiguration.getSipPlaniHeader();"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="367"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServerAddress`"
- errorLine1=' + ", \n\tmSipServerAddr=" + config.getSipServerAddress()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="240"
- column="56"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServerAddress`"
- errorLine1=" return mConfiguration.getSipServerAddress().getAddress().getHostAddress();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="286"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServerAddress`"
- errorLine1=" return mConfiguration.getSipServerAddress().getPort();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="291"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServiceRouteHeader`"
- errorLine1=' + ", \n\tmServiceRouteHeader=" + config.getSipServiceRouteHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="251"
- column="61"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServiceRouteHeader`"
- errorLine1=" mConfiguration.getSipServiceRouteHeader();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="343"
- column="36"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipUserAgentHeader`"
- errorLine1=' + ", \n\tmUserAgentHeader=" + config.getSipUserAgentHeader()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="253"
- column="58"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipUserAgentHeader`"
- errorLine1=" return mConfiguration.getSipUserAgentHeader();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="372"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getTransportType`"
- errorLine1=' + ", \n\tmTransportType=" + config.getTransportType()'
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="238"
- column="56"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getTransportType`"
- errorLine1=" int sipTransport = mConfiguration.getTransportType();"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="306"
- column="47"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getVersion`"
- errorLine1=" + registeredSipConfig.getVersion());"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="127"
- column="63"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getVersion`"
- errorLine1=' + "mVersion=" + config.getVersion()'
- errorLine2=" ~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="237"
- column="44"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getVersion`"
- errorLine1=" return mConfiguration.getVersion();"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="281"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#isSipCompactFormEnabled`"
- errorLine1=' + ", \n\tmIsSipCompactFormEnabled=" + config.isSipCompactFormEnabled()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="241"
- column="66"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#isSipKeepaliveEnabled`"
- errorLine1=' + ", \n\tmIsSipKeepaliveEnabled=" + config.isSipKeepaliveEnabled()'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="242"
- column="64"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration.IpSecConfiguration#getSipSecurityVerifyHeader`"
- errorLine1=" return c.getSipSecurityVerifyHeader();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="337"
- column="22"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConnection#sendMessage`"
- errorLine1=" sipDelegateConnection.sendMessage(MessageConverter.toPlatformMessage(message),"
- errorLine2=" ~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="271"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#createSipDelegate`"
- errorLine1=" controller.sipDelegateManager.createSipDelegate("
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="205"
- column="47"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#destroySipDelegate`"
- errorLine1=" sipDelegateManager.destroySipDelegate(context.sipDelegateConnection,"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="92"
- column="32"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipMessage#getContent`"
- errorLine1=" return new SipMessage(message.getStartLine(), headers, message.getContent());"
- errorLine2=" ~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="395"
- column="76"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipMessage#getHeaderSection`"
- errorLine1=' + message.getHeaderSection().substring(0, 10) + "->"'
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="392"
- column="35"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipMessage#getHeaderSection`"
- errorLine1=" String headers = message.getHeaderSection();"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="387"
- column="38"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipMessage#getStartLine`"
- errorLine1=" return new SipMessage(message.getStartLine(), headers, message.getContent());"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="395"
- column="43"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/MessageConverter.java"
+ line="72"
+ column="16"/>
</issue>
<issue
@@ -784,94 +135,6 @@
<issue
id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.net.QosSocketInfo`"
- errorLine1=" connectivityManager.registerQosCallback(new QosSocketInfo(network, socket),"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSession.java"
- line="118"
- column="53"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.telephony.gba.UaSecurityProtocolIdentifier.Builder`"
- errorLine1=" new UaSecurityProtocolIdentifier.Builder();"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java"
- line="55"
- column="17"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.telephony.ims.DelegateRequest`"
- errorLine1=" DelegateRequest request = new DelegateRequest(imsService.getFeatureTags());"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="203"
- column="39"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.telephony.ims.RcsClientConfiguration`"
- errorLine1=" return new RcsClientConfiguration("
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
- line="74"
- column="16"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.telephony.ims.SipMessage`"
- errorLine1=" return new SipMessage(message.getStartLine(), headers, message.getContent());"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
- line="395"
- column="20"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `new android.telephony.ims.SipMessage`"
- errorLine1=" return new SipMessage(startLine, headers.toString(), rawContent);"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/MessageConverter.java"
- line="72"
- column="16"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Cast to `BootstrapAuthenticationCallback` requires API level 31 (current min is 30)"
- errorLine1=" new TelephonyManager.BootstrapAuthenticationCallback() {"
- errorLine2=" ^">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java"
- line="81"
- column="17"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Cast to `RcsProvisioningCallback` requires API level 31 (current min is 30)"
- errorLine1=" new RcsProvisioningCallback() {"
- errorLine2=" ^">
- <location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
- line="114"
- column="17"/>
- </issue>
-
- <issue
- id="NewApi"
message="Class requires API level 31 (current min is 30): `android.net.QosCallback`"
errorLine1=" private final QosCallback qosCallback = new QosCallback() {"
errorLine2=" ~~~~~~~~~~~">
@@ -883,24 +146,68 @@
<issue
id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.TelephonyManager.BootstrapAuthenticationCallback`"
- errorLine1=" new TelephonyManager.BootstrapAuthenticationCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `new android.net.QosSocketInfo`"
+ errorLine1=" connectivityManager.registerQosCallback(new QosSocketInfo(network, socket),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/filetransfer/requestexecutor/GbaAuthenticationProvider.java"
- line="81"
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSession.java"
+ line="118"
+ column="53"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ImsManager#getSipDelegateManager`"
+ errorLine1=" this.sipDelegateManager = imsManager.getSipDelegateManager(subscriptionId);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="77"
+ column="46"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#destroySipDelegate`"
+ errorLine1=" sipDelegateManager.destroySipDelegate(context.sipDelegateConnection,"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="92"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Class requires API level 31 (current min is 30): `android.telephony.ims.stub.DelegateConnectionStateCallback`"
+ errorLine1=" new DelegateConnectionStateCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="114"
column="21"/>
</issue>
<issue
id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager.RcsProvisioningCallback`"
- errorLine1=" new RcsProvisioningCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getVersion`"
+ errorLine1=" + registeredSipConfig.getVersion());"
+ errorLine2=" ~~~~~~~~~~">
<location
- file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
- line="114"
- column="21"/>
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="127"
+ column="63"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.DelegateRegistrationState#getRegisteredFeatureTags`"
+ errorLine1=" .getRegisteredFeatureTags()"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="139"
+ column="34"/>
</issue>
<issue
@@ -916,13 +223,684 @@
<issue
id="NewApi"
- message="Class requires API level 31 (current min is 30): `android.telephony.ims.stub.DelegateConnectionStateCallback`"
- errorLine1=" new DelegateConnectionStateCallback() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ message="Call requires API level 31 (current min is 30): `new android.telephony.ims.DelegateRequest`"
+ errorLine1=" DelegateRequest request = new DelegateRequest(imsService.getFeatureTags());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="203"
+ column="39"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateManager#createSipDelegate`"
+ errorLine1=" controller.sipDelegateManager.createSipDelegate("
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="205"
+ column="47"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.FeatureTagState#getFeatureTag`"
+ errorLine1=' stringBuilder.append(featureTagState.getFeatureTag()).append(" ").append('
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="220"
+ column="54"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.FeatureTagState#getState`"
+ errorLine1=" featureTagState.getState());"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="221"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.DelegateRegistrationState#getRegisteredFeatureTags`"
+ errorLine1=" Set<String> registeredFt = registrationState.getRegisteredFeatureTags();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="223"
+ column="58"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getVersion`"
+ errorLine1=' + "mVersion=" + config.getVersion()'
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="237"
+ column="44"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getTransportType`"
+ errorLine1=' + ", \n\tmTransportType=" + config.getTransportType()'
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="238"
+ column="56"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getLocalAddress`"
+ errorLine1=' + ", \n\tmLocalAddr=" + config.getLocalAddress()'
+ errorLine2=" ~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="239"
+ column="52"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServerAddress`"
+ errorLine1=' + ", \n\tmSipServerAddr=" + config.getSipServerAddress()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="240"
+ column="56"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#isSipCompactFormEnabled`"
+ errorLine1=' + ", \n\tmIsSipCompactFormEnabled=" + config.isSipCompactFormEnabled()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="241"
+ column="66"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#isSipKeepaliveEnabled`"
+ errorLine1=' + ", \n\tmIsSipKeepaliveEnabled=" + config.isSipKeepaliveEnabled()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="242"
+ column="64"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getMaxUdpPayloadSizeBytes`"
+ errorLine1=' + ", \n\tmMaxUdpPayloadSize=" + config.getMaxUdpPayloadSizeBytes()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="243"
+ column="60"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicUserIdentifier`"
+ errorLine1=' + ", \n\tmPublicUserIdentifier=" + config.getPublicUserIdentifier()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="244"
+ column="63"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPrivateUserIdentifier`"
+ errorLine1=' + ", \n\tmPrivateUserIdentifier=" + config.getPrivateUserIdentifier()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="245"
+ column="64"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getHomeDomain`"
+ errorLine1=' + ", \n\tmHomeDomain=" + config.getHomeDomain()'
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="246"
+ column="53"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getImei`"
+ errorLine1=' + ", \n\tmImei=" + config.getImei()'
+ errorLine2=" ~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="247"
+ column="47"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicGruuUri`"
+ errorLine1=' + ", \n\tmGruu=" + config.getPublicGruuUri()'
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="248"
+ column="47"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAuthenticationHeader`"
+ errorLine1=' + ", \n\tmSipAuthHeader=" + config.getSipAuthenticationHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="249"
+ column="56"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAuthenticationNonce`"
+ errorLine1=' + ", \n\tmSipAuthNonce=" + config.getSipAuthenticationNonce()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="250"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServiceRouteHeader`"
+ errorLine1=' + ", \n\tmServiceRouteHeader=" + config.getSipServiceRouteHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="251"
+ column="61"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPathHeader`"
+ errorLine1=' + ", \n\tmPathHeader=" + config.getSipPathHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="252"
+ column="53"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipUserAgentHeader`"
+ errorLine1=' + ", \n\tmUserAgentHeader=" + config.getSipUserAgentHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="253"
+ column="58"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipContactUserParameter`"
+ errorLine1=' + ", \n\tmContactUserParam=" + config.getSipContactUserParameter()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="254"
+ column="59"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPaniHeader`"
+ errorLine1=' + ", \n\tmPaniHeader=" + config.getSipPaniHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="255"
+ column="53"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPlaniHeader`"
+ errorLine1=' + ", \n\tmPlaniHeader=" + config.getSipPlaniHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="256"
+ column="54"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipCniHeader`"
+ errorLine1=' + ", \n\tmCniHeader=" + config.getSipCniHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="257"
+ column="52"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAssociatedUriHeader`"
+ errorLine1=' + ", \n\tmAssociatedUriHeader=" + config.getSipAssociatedUriHeader()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="258"
+ column="62"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getIpSecConfiguration`"
+ errorLine1=' + ", \n\tmIpSecConfiguration=" + config.getIpSecConfiguration()'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="259"
+ column="61"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getNatSocketAddress`"
+ errorLine1=" + ", \n\tmNatConfiguration=" + config.getNatSocketAddress() + '}';"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="260"
+ column="59"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConnection#sendMessage`"
+ errorLine1=" sipDelegateConnection.sendMessage(MessageConverter.toPlatformMessage(message),"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="271"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getVersion`"
+ errorLine1=" return mConfiguration.getVersion();"
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="281"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServerAddress`"
+ errorLine1=" return mConfiguration.getSipServerAddress().getAddress().getHostAddress();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="286"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServerAddress`"
+ errorLine1=" return mConfiguration.getSipServerAddress().getPort();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="291"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getLocalAddress`"
+ errorLine1=" return mConfiguration.getLocalAddress().getAddress().getHostAddress();"
+ errorLine2=" ~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="296"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getLocalAddress`"
+ errorLine1=" return mConfiguration.getLocalAddress().getPort();"
+ errorLine2=" ~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="301"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getTransportType`"
+ errorLine1=" int sipTransport = mConfiguration.getTransportType();"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="306"
+ column="47"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getPublicUserIdentifier`"
+ errorLine1=" return mConfiguration.getPublicUserIdentifier();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="312"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getHomeDomain`"
+ errorLine1=" return mConfiguration.getHomeDomain();"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="317"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipAssociatedUriHeader`"
+ errorLine1=" String associatedUris = mConfiguration.getSipAssociatedUriHeader();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="322"
+ column="52"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getIpSecConfiguration`"
+ errorLine1=" SipDelegateConfiguration.IpSecConfiguration c = mConfiguration.getIpSecConfiguration();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="333"
+ column="76"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration.IpSecConfiguration#getSipSecurityVerifyHeader`"
+ errorLine1=" return c.getSipSecurityVerifyHeader();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="337"
+ column="22"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipServiceRouteHeader`"
+ errorLine1=" mConfiguration.getSipServiceRouteHeader();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="343"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipContactUserParameter`"
+ errorLine1=" return mConfiguration.getSipContactUserParameter();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="352"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getImei`"
+ errorLine1=" return mConfiguration.getImei();"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="357"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPaniHeader`"
+ errorLine1=" return mConfiguration.getSipPaniHeader();"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="362"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipPlaniHeader`"
+ errorLine1=" return mConfiguration.getSipPlaniHeader();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="367"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getSipUserAgentHeader`"
+ errorLine1=" return mConfiguration.getSipUserAgentHeader();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="372"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getMaxUdpPayloadSizeBytes`"
+ errorLine1=" return mConfiguration.getMaxUdpPayloadSizeBytes() > 0"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="377"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipDelegateConfiguration#getMaxUdpPayloadSizeBytes`"
+ errorLine1=" ? mConfiguration.getMaxUdpPayloadSizeBytes() : 1500;"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="378"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipMessage#getHeaderSection`"
+ errorLine1=" String headers = message.getHeaderSection();"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="387"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipMessage#getHeaderSection`"
+ errorLine1=' + message.getHeaderSection().substring(0, 10) + "->"'
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="392"
+ column="35"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipMessage#getContent`"
+ errorLine1=" return new SipMessage(message.getStartLine(), headers, message.getContent());"
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="395"
+ column="76"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.SipMessage#getStartLine`"
+ errorLine1=" return new SipMessage(message.getStartLine(), headers, message.getContent());"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="395"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `new android.telephony.ims.SipMessage`"
+ errorLine1=" return new SipMessage(message.getStartLine(), headers, message.getContent());"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java"
+ line="395"
+ column="20"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `new android.telephony.ims.RcsClientConfiguration`"
+ errorLine1=" return new RcsClientConfiguration("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
+ line="74"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#setRcsClientConfiguration`"
+ errorLine1=" provisioningManager.setRcsClientConfiguration(clientConfiguration);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
+ line="111"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Cast to `RcsProvisioningCallback` requires API level 31 (current min is 30)"
+ errorLine1=" new RcsProvisioningCallback() {"
+ errorLine2=" ^">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
+ line="114"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Class requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager.RcsProvisioningCallback`"
+ errorLine1=" new RcsProvisioningCallback() {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
line="114"
column="21"/>
</issue>
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#registerRcsProvisioningCallback`"
+ errorLine1=" provisioningManager.registerRcsProvisioningCallback(executorService, callback);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
+ line="147"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#unregisterRcsProvisioningCallback`"
+ errorLine1=" provisioningManager.unregisterRcsProvisioningCallback(callback);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
+ line="158"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#isRcsVolteSingleRegistrationCapable`"
+ errorLine1=" return provisioningManager.isRcsVolteSingleRegistrationCapable();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
+ line="166"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 31 (current min is 30): `android.telephony.ims.ProvisioningManager#triggerRcsReconfiguration`"
+ errorLine1=" provisioningManager.triggerRcsReconfiguration();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/services/Telephony/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/provisioning/StaticConfigProvisioningController.java"
+ line="176"
+ column="29"/>
+ </issue>
+
</issues>
\ No newline at end of file
diff --git a/testapps/TestSatelliteApp/Android.bp b/testapps/TestSatelliteApp/Android.bp
new file mode 100644
index 0000000..78d125d
--- /dev/null
+++ b/testapps/TestSatelliteApp/Android.bp
@@ -0,0 +1,20 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+ name: "SatelliteTestApp",
+ system_ext_specific: true,
+ platform_apis: true,
+ manifest: "AndroidManifest.xml",
+ srcs: [
+ "src/**/*.java",
+ "src/**/I*.aidl",
+ ],
+ static_libs: [
+ "SatelliteClient",
+ ],
+ owner: "google",
+ privileged: true,
+ certificate: "platform",
+}
diff --git a/testapps/TestSatelliteApp/AndroidManifest.xml b/testapps/TestSatelliteApp/AndroidManifest.xml
new file mode 100644
index 0000000..fb30bf3
--- /dev/null
+++ b/testapps/TestSatelliteApp/AndroidManifest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.phone.testapps.satellitetestapp">
+ <uses-permission android:name="android.permission.BIND_SATELLITE_SERVICE"/>
+ <uses-permission android:name="android.permission.SATELLITE_COMMUNICATION"/>
+ <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+ <application android:label="SatelliteTestApp">
+ <activity android:name=".SatelliteTestApp"
+ android:label="SatelliteTestApp"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <service android:name=".TestSatelliteService"
+ android:directBootAware="true"
+ android:persistent="true"
+ android:permission="android.permission.BIND_SATELLITE_SERVICE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.telephony.satellite.SatelliteService"/>
+ </intent-filter>
+ </service>
+
+ <activity android:name=".SatelliteControl" />
+ <activity android:name=".Datagram" />
+ <activity android:name=".Provisioning" />
+ <activity android:name=".MultipleSendReceive" />
+ <activity android:name=".SendReceive" />
+ <activity android:name=".TestSatelliteWrapper" />
+ </application>
+</manifest>
diff --git a/testapps/TestSatelliteApp/res/layout/activity_Datagram.xml b/testapps/TestSatelliteApp/res/layout/activity_Datagram.xml
new file mode 100644
index 0000000..9e53f41
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/layout/activity_Datagram.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:paddingLeft="4dp">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:textColor="@android:color/holo_blue_dark"
+ android:textSize="20dp"
+ android:text="Datagram APIs"/>
+ <Button
+ android:id="@+id/startSatelliteTransmissionUpdates"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/startSatelliteTransmissionUpdates"/>
+ <Button
+ android:id="@+id/stopSatelliteTransmissionUpdates"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/stopSatelliteTransmissionUpdates"/>
+ <Button
+ android:id="@+id/pollPendingSatelliteDatagrams"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/pollPendingSatelliteDatagrams"/>
+ <Button
+ android:id="@+id/sendSatelliteDatagram"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/sendSatelliteDatagram"/>
+ <Button
+ android:id="@+id/registerForSatelliteDatagram"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/registerForSatelliteDatagram"/>
+ <Button
+ android:id="@+id/unregisterForSatelliteDatagram"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/unregisterForSatelliteDatagram"/>
+ <Button
+ android:id="@+id/showDatagramSendStateTransition"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/showDatagramSendStateTransition"/>
+ <Button
+ android:id="@+id/showDatagramReceiveStateTransition"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/showDatagramReceiveStateTransition"/>
+ <Button
+ android:id="@+id/registerForSatelliteModemStateChanged"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/registerForSatelliteModemStateChanged"/>
+ <Button
+ android:id="@+id/unregisterForSatelliteModemStateChanged"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/unregisterForSatelliteModemStateChanged"/>
+ <Button
+ android:id="@+id/showSatelliteModemStateTransition"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/showSatelliteModemStateTransition"/>
+ <Button
+ android:id="@+id/Back"
+ android:onClick="Back"
+ android:textColor="@android:color/holo_blue_dark"
+ android:layout_marginTop="100dp"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/Back"/>
+ <TextView
+ android:id="@+id/text_id"
+ android:layout_width="400dp"
+ android:layout_height="50dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:textSize="15dp" />
+ <TextView
+ android:id="@+id/showErrorStatus"
+ android:layout_width="400dp"
+ android:layout_height="50dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:textSize="15dp" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/testapps/TestSatelliteApp/res/layout/activity_MultipleSendReceive.xml b/testapps/TestSatelliteApp/res/layout/activity_MultipleSendReceive.xml
new file mode 100644
index 0000000..3632ecb
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/layout/activity_MultipleSendReceive.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:paddingLeft="4dp">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:textColor="@android:color/holo_blue_dark"
+ android:textSize="20dp"
+ android:text="Multiple Send and Receive APIs"/>
+ <Button
+ android:id="@+id/multiplePollPendingSatelliteDatagrams"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/multiplePollPendingSatelliteDatagrams"/>
+ <Button
+ android:id="@+id/multipleSendSatelliteDatagram"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/multipleSendSatelliteDatagram"/>
+ <Button
+ android:id="@+id/multipleSendReceiveSatelliteDatagram"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/multipleSendReceiveSatelliteDatagram"/>
+ <Button
+ android:id="@+id/Back"
+ android:onClick="Back"
+ android:textColor="@android:color/holo_blue_dark"
+ android:layout_marginTop="100dp"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/Back"/>
+ <TextView
+ android:id="@+id/text_id"
+ android:layout_width="400dp"
+ android:layout_height="50dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:textSize="15dp" />
+ <TextView
+ android:id="@+id/text_id1"
+ android:layout_width="400dp"
+ android:layout_height="65dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:layout_centerVertical="true"
+ android:textSize="15dp" />
+ <TextView
+ android:id="@+id/text_id2"
+ android:layout_width="400dp"
+ android:layout_height="65dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:layout_centerVertical="true"
+ android:textSize="15dp" />
+ <TextView
+ android:id="@+id/text_id3"
+ android:layout_width="400dp"
+ android:layout_height="65dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:layout_centerVertical="true"
+ android:textSize="15dp" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/testapps/TestSatelliteApp/res/layout/activity_Provisioning.xml b/testapps/TestSatelliteApp/res/layout/activity_Provisioning.xml
new file mode 100644
index 0000000..da5105d
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/layout/activity_Provisioning.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:paddingLeft="4dp">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:textColor="@android:color/holo_blue_dark"
+ android:textSize="20dp"
+ android:text="Provisioning APIs"/>
+ <Button
+ android:id="@+id/provisionSatelliteService"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/provisionSatelliteService"/>
+ <Button
+ android:id="@+id/deprovisionSatelliteService"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/deprovisionSatelliteService"/>
+ <Button
+ android:id="@+id/requestIsSatelliteProvisioned"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/requestIsSatelliteProvisioned"/>
+ <Button
+ android:id="@+id/registerForSatelliteProvisionStateChanged"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/registerForSatelliteProvisionStateChanged"/>
+ <Button
+ android:id="@+id/unregisterForSatelliteProvisionStateChanged"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/unregisterForSatelliteProvisionStateChanged"/>
+ <Button
+ android:id="@+id/showCurrentSatelliteProvisionState"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/showCurrentSatelliteProvisionState"/>
+ <Button
+ android:id="@+id/Back"
+ android:onClick="Back"
+ android:textColor="@android:color/holo_blue_dark"
+ android:layout_marginTop="100dp"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/Back"/>
+ <TextView
+ android:id="@+id/text_id"
+ android:layout_width="300dp"
+ android:layout_height="200dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:layout_centerVertical="true"
+ android:textSize="15dp" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/testapps/TestSatelliteApp/res/layout/activity_SatelliteControl.xml b/testapps/TestSatelliteApp/res/layout/activity_SatelliteControl.xml
new file mode 100644
index 0000000..40e3c69
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/layout/activity_SatelliteControl.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:paddingLeft="4dp">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:textColor="@android:color/holo_blue_dark"
+ android:textSize="20dp"
+ android:text="Satellite Control APIs"/>
+ <Button
+ android:id="@+id/enableSatellite"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/enableSatellite"/>
+ <Button
+ android:id="@+id/disableSatellite"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/disableSatellite"/>
+ <Button
+ android:id="@+id/requestIsSatelliteEnabled"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/requestIsSatelliteEnabled"/>
+ <Button
+ android:id="@+id/requestIsDemoModeEnabled"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/requestIsDemoModeEnabled"/>
+ <Button
+ android:id="@+id/requestIsSatelliteSupported"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/requestIsSatelliteSupported"/>
+ <Button
+ android:id="@+id/requestSatelliteCapabilities"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/requestSatelliteCapabilities"/>
+ <Button
+ android:id="@+id/requestIsSatelliteCommunicationAllowedForCurrentLocation"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/requestIsSatelliteCommunicationAllowedForCurrentLocation"/>
+ <Button
+ android:id="@+id/requestTimeForNextSatelliteVisibility"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/requestTimeForNextSatelliteVisibility"/>
+ <Button
+ android:id="@+id/Back"
+ android:onClick="Back"
+ android:textColor="@android:color/holo_blue_dark"
+ android:layout_marginTop="100dp"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/Back"/>
+ <TextView
+ android:id="@+id/text_id"
+ android:layout_width="300dp"
+ android:layout_height="200dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:layout_centerVertical="true"
+ android:textSize="15dp" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/testapps/TestSatelliteApp/res/layout/activity_SatelliteTestApp.xml b/testapps/TestSatelliteApp/res/layout/activity_SatelliteTestApp.xml
new file mode 100644
index 0000000..0753b82
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/layout/activity_SatelliteTestApp.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingLeft="4dp">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:textColor="@android:color/holo_blue_dark"
+ android:textSize="20dp"
+ android:text="Available Satellite APIs"/>
+ <Button
+ android:id="@+id/SatelliteControl"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/SatelliteControl"/>
+ <Button
+ android:id="@+id/Datagram"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/Datagram"/>
+ <Button
+ android:id="@+id/Provisioning"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/Provisioning"/>
+ <Button
+ android:id="@+id/MultipleSendReceive"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/MultipleSendReceive"/>
+ <Button
+ android:id="@+id/SendReceive"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/SendReceive"/>
+ <Button
+ android:id="@+id/TestSatelliteWrapper"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingEnd="4dp"
+ android:text="@string/TestSatelliteWrapper"/>
+ </LinearLayout>
+</LinearLayout>
diff --git a/testapps/TestSatelliteApp/res/layout/activity_SendReceive.xml b/testapps/TestSatelliteApp/res/layout/activity_SendReceive.xml
new file mode 100644
index 0000000..6490e5d
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/layout/activity_SendReceive.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:paddingLeft="4dp">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:layout_weight="0"
+ android:textColor="@android:color/holo_blue_dark"
+ android:textSize="20dp"
+ android:text="Send and Receive APIs"/>
+ <Button
+ android:id="@+id/sendMessage"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/sendMessage"/>
+ <Button
+ android:id="@+id/receiveMessage"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/receiveMessage"/>
+ <EditText
+ android:id="@+id/enterText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:hint="Enter message to send"
+ android:inputType="text" />
+ <Button
+ android:id="@+id/Back"
+ android:onClick="Back"
+ android:textColor="@android:color/holo_blue_dark"
+ android:layout_marginTop="100dp"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingRight="4dp"
+ android:text="@string/Back"/>
+ <TextView
+ android:id="@+id/showErrorStatus"
+ android:layout_width="400dp"
+ android:layout_height="50dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:textSize="15dp" />
+ <TextView
+ android:id="@+id/devicePosition"
+ android:layout_width="400dp"
+ android:layout_height="65dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:layout_centerVertical="true"
+ android:textSize="15dp" />
+ <TextView
+ android:id="@+id/satellitePosition"
+ android:layout_width="400dp"
+ android:layout_height="65dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:layout_centerVertical="true"
+ android:textSize="15dp" />
+ <TextView
+ android:id="@+id/messageStatus"
+ android:layout_width="400dp"
+ android:layout_height="65dp"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:layout_centerVertical="true"
+ android:textSize="15dp" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/testapps/TestSatelliteApp/res/layout/activity_TestSatelliteWrapper.xml b/testapps/TestSatelliteApp/res/layout/activity_TestSatelliteWrapper.xml
new file mode 100644
index 0000000..c136ce7
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/layout/activity_TestSatelliteWrapper.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center"
+ android: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="horizontal">
+ <Button
+ android:id="@+id/Back"
+ android:onClick="Back"
+ android:textColor="@android:color/holo_blue_dark"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingRight="4dp"
+ android:text="@string/Back"/>
+ <Button
+ android:id="@+id/ClearLog"
+ android:onClick="ClearLog"
+ android:textColor="@android:color/holo_blue_dark"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingRight="4dp"
+ android:text="@string/ClearLog"/>
+ </LinearLayout>
+ <ListView
+ android:id="@+id/logListView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:capitalize="characters"
+ android:textColor="@android:color/holo_blue_light"
+ android:layout_centerVertical="true"
+ android:textSize="8dp" />
+</LinearLayout>
diff --git a/testapps/TestSatelliteApp/res/layout/log_textview.xml b/testapps/TestSatelliteApp/res/layout/log_textview.xml
new file mode 100644
index 0000000..a44641b
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/layout/log_textview.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/log_textview"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textColor="@android:color/holo_blue_light"
+ android:padding="10dp"/>
diff --git a/testapps/TestSatelliteApp/res/values/donottranslate_strings.xml b/testapps/TestSatelliteApp/res/values/donottranslate_strings.xml
new file mode 100644
index 0000000..8ebe5f3
--- /dev/null
+++ b/testapps/TestSatelliteApp/res/values/donottranslate_strings.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<resources>
+ <string name="SatelliteControl">SatelliteControl APIs</string>
+ <string name="Datagram">Datagram APIs</string>
+ <string name="Provisioning">Provisioning APIs</string>
+ <string name="MultipleSendReceive">Test multiple poll and send</string>
+ <string name="SendReceive">Send and Receive datagrams</string>
+
+ <string name="enableSatellite">enableSatellite</string>
+ <string name="disableSatellite">disableSatellite</string>
+ <string name="requestIsSatelliteEnabled">requestIsSatelliteEnabled</string>
+ <string name="requestIsDemoModeEnabled">requestIsDemoModeEnabled</string>
+ <string name="requestIsSatelliteSupported">requestIsSatelliteSupported</string>
+ <string name="requestSatelliteCapabilities">requestSatelliteCapabilities</string>
+ <string name="requestIsSatelliteCommunicationAllowedForCurrentLocation">requestIsSatelliteCommunicationAllowedForCurrentLocation</string>
+ <string name="requestTimeForNextSatelliteVisibility">requestTimeForNextSatelliteVisibility</string>
+
+ <string name="pollPendingSatelliteDatagrams">pollPendingSatelliteDatagrams</string>
+ <string name="sendSatelliteDatagram">sendSatelliteDatagram</string>
+ <string name="registerForSatelliteDatagram">registerForSatelliteDatagram</string>
+ <string name="unregisterForSatelliteDatagram">unregisterForSatelliteDatagram</string>
+ <string name="registerForSatelliteModemStateChanged">registerForSatelliteModemStateChanged</string>
+ <string name="unregisterForSatelliteModemStateChanged">unregisterForSatelliteModemStateChanged</string>
+ <string name="showSatelliteModemStateTransition">showSatelliteModemStateTransition</string>
+ <string name="startSatelliteTransmissionUpdates">startSatelliteTransmissionUpdates</string>
+ <string name="stopSatelliteTransmissionUpdates">stopSatelliteTransmissionUpdates</string>
+ <string name="showDatagramSendStateTransition">showDatagramSendStateTransition</string>
+ <string name="showDatagramReceiveStateTransition">showDatagramReceiveStateTransition</string>
+
+ <string name="provisionSatelliteService">provisionSatelliteService</string>
+ <string name="deprovisionSatelliteService">deprovisionSatelliteService</string>
+ <string name="requestIsSatelliteProvisioned">requestIsSatelliteProvisioned</string>
+ <string name="registerForSatelliteProvisionStateChanged">registerForSatelliteProvisionStateChanged</string>
+ <string name="unregisterForSatelliteProvisionStateChanged">unregisterForSatelliteProvisionStateChanged</string>
+ <string name="showCurrentSatelliteProvisionState">showCurrentSatelliteProvisionState</string>
+
+ <string name="multiplePollPendingSatelliteDatagrams">multiplePollPendingSatelliteDatagrams</string>
+ <string name="multipleSendSatelliteDatagram">multipleSendSatelliteDatagram</string>
+ <string name="multipleSendReceiveSatelliteDatagram">multipleSendReceiveSatelliteDatagram</string>
+
+ <string name="sendMessage">sendMessage</string>
+ <string name="receiveMessage">receiveMessage</string>
+
+ <string name="TestSatelliteWrapper">Test Satellite Wrapper</string>
+ <string name="requestNtnSignalStrength">requestNtnSignalStrength</string>
+ <string name="registerForNtnSignalStrengthChanged">registerForNtnSignalStrengthChanged</string>
+ <string name="unregisterForNtnSignalStrengthChanged">unregisterForNtnSignalStrengthChanged</string>
+ <string name="isOnlyNonTerrestrialNetworkSubscription">isOnlyNonTerrestrialNetworkSubscription</string>
+ <string name="registerForSatelliteCapabilitiesChanged">registerForSatelliteCapabilitiesChanged</string>
+ <string name="unregisterForSatelliteCapabilitiesChanged">unregisterForSatelliteCapabilitiesChanged</string>
+
+ <string name="Back">Back</string>
+ <string name="ClearLog">Clear Log</string>
+</resources>
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Datagram.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Datagram.java
new file mode 100644
index 0000000..015ddcd
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Datagram.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone.testapps.satellitetestapp;
+
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.telephony.satellite.PointingInfo;
+import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.SatelliteDatagramCallback;
+import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.SatelliteModemStateCallback;
+import android.telephony.satellite.SatelliteTransmissionUpdateCallback;
+import android.telephony.satellite.stub.SatelliteResult;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.TextView;
+
+import java.util.LinkedList;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Activity related to Datagram APIs.
+ */
+public class Datagram extends Activity {
+
+ private static final String TAG = "Datagram";
+ private static final int MAX_NUMBER_OF_STORED_STATES = 3;
+ private int mTransferState;
+ private int mModemState;
+ LinkedList<Integer> mModemStateQueue = new LinkedList<>();
+ LinkedList<Integer> mSendQueue = new LinkedList<>();
+ LinkedList<Integer> mReceiveQueue = new LinkedList<>();
+
+ private SatelliteManager mSatelliteManager;
+ private SatelliteDatagramCallbackTestApp mDatagramCallback;
+ private SatelliteModemStateCallbackTestApp mStateCallback;
+ private SatelliteTransmissionUpdateCallbackTestApp mCallback;
+ private android.telephony.satellite.stub.SatelliteDatagram mReceivedDatagram;
+
+ private String mShowSatelliteModemStateTransition;
+ private String mShowDatagramSendStateTransition;
+ private String mShowDatagramReceiveStateTransition;
+ private static final long TIMEOUT = 3000;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSatelliteManager = getSystemService(SatelliteManager.class);
+ mDatagramCallback = new SatelliteDatagramCallbackTestApp();
+ mStateCallback = new SatelliteModemStateCallbackTestApp();
+ mCallback = new SatelliteTransmissionUpdateCallbackTestApp();
+
+ mReceivedDatagram = new android.telephony.satellite.stub.SatelliteDatagram();
+
+ setContentView(R.layout.activity_Datagram);
+ findViewById(R.id.startSatelliteTransmissionUpdates)
+ .setOnClickListener(this::startTransmissionUpdatesApp);
+ findViewById(R.id.stopSatelliteTransmissionUpdates)
+ .setOnClickListener(this::stopTransmissionUpdatesApp);
+ findViewById(R.id.pollPendingSatelliteDatagrams)
+ .setOnClickListener(this::pollPendingDatagramsApp);
+ findViewById(R.id.sendSatelliteDatagram)
+ .setOnClickListener(this::sendDatagramApp);
+ findViewById(R.id.registerForSatelliteDatagram)
+ .setOnClickListener(this::registerForIncomingDatagramApp);
+ findViewById(R.id.unregisterForSatelliteDatagram)
+ .setOnClickListener(this::unregisterForIncomingDatagramApp);
+ findViewById(R.id.showDatagramSendStateTransition)
+ .setOnClickListener(this::showDatagramSendStateTransitionApp);
+ findViewById(R.id.showDatagramReceiveStateTransition)
+ .setOnClickListener(this::showDatagramReceiveStateTransitionApp);
+ findViewById(R.id.registerForSatelliteModemStateChanged)
+ .setOnClickListener(this::registerForModemStateChangedApp);
+ findViewById(R.id.unregisterForSatelliteModemStateChanged)
+ .setOnClickListener(this::unregisterForModemStateChangedApp);
+ findViewById(R.id.showSatelliteModemStateTransition)
+ .setOnClickListener(this::showSatelliteModemStateTransitionApp);
+
+ findViewById(R.id.Back).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(Datagram.this, SatelliteTestApp.class));
+ }
+ });
+ }
+
+ protected class SatelliteDatagramCallbackTestApp implements SatelliteDatagramCallback {
+ @Override
+ public void onSatelliteDatagramReceived(long datagramId, SatelliteDatagram datagram,
+ int pendingCount, Consumer<Void> callback) {
+ Log.d(TAG, "onSatelliteDatagramReceived in TestApp: datagramId =" + datagramId
+ + ", datagram =" + datagram + ", pendingCount=" + pendingCount);
+ }
+ }
+
+ protected class SatelliteModemStateCallbackTestApp implements SatelliteModemStateCallback {
+ @Override
+ public void onSatelliteModemStateChanged(int state) {
+ mModemState = state;
+ mModemStateQueue.addLast(state);
+ if (mModemStateQueue.size() > MAX_NUMBER_OF_STORED_STATES) {
+ mModemStateQueue.removeFirst();
+ }
+ mShowSatelliteModemStateTransition = getSatelliteModemStateTransition(mModemStateQueue);
+ Log.d(TAG, "onSatelliteModemStateChanged in TestApp: state=" + mModemState);
+ }
+ }
+ protected class SatelliteTransmissionUpdateCallbackTestApp implements
+ SatelliteTransmissionUpdateCallback {
+ @Override
+ public void onSatellitePositionChanged(PointingInfo pointingInfo) {
+ Log.d(TAG, "onSatellitePositionChanged in TestApp: pointingInfo =" + pointingInfo);
+ }
+
+ @Override
+ public void onSendDatagramStateChanged(int state, int sendPendingCount, int errorCode) {
+ mTransferState = state;
+ mSendQueue.addLast(state);
+ if (mSendQueue.size() > MAX_NUMBER_OF_STORED_STATES) {
+ mSendQueue.removeFirst();
+ }
+ mShowDatagramSendStateTransition = getTransferStateTransition(mSendQueue);
+ Log.d(TAG, "onSendDatagramStateChanged in TestApp: state =" + mTransferState
+ + ", sendPendingCount =" + sendPendingCount + ", errorCode=" + errorCode);
+ }
+
+ @Override
+ public void onReceiveDatagramStateChanged(
+ int state, int receivePendingCount, int errorCode) {
+ mTransferState = state;
+ mReceiveQueue.addLast(state);
+ if (mReceiveQueue.size() > MAX_NUMBER_OF_STORED_STATES) {
+ mReceiveQueue.removeFirst();
+ }
+ mShowDatagramReceiveStateTransition = getTransferStateTransition(mReceiveQueue);
+ Log.d(TAG, "onReceiveDatagramStateChanged in TestApp: state=" + mTransferState
+ + ", receivePendingCount=" + receivePendingCount + ", errorCode=" + errorCode);
+ }
+ }
+
+ private void startTransmissionUpdatesApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+ mSatelliteManager.requestEnabled(true, true, Runnable::run, error::offer);
+ TextView showErrorStatusTextView = findViewById(R.id.showErrorStatus);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ showErrorStatusTextView.setText("Timed out to enable the satellite");
+ return;
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ showErrorStatusTextView.setText("Failed to enable satellite, error = "
+ + SatelliteErrorUtils.mapError(value));
+ return;
+ }
+ } catch (InterruptedException e) {
+ showErrorStatusTextView.setText("Enable SatelliteService exception caught = " + e);
+ return;
+ }
+ error.clear();
+ mSatelliteManager.startTransmissionUpdates(Runnable::run, error::offer, mCallback);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ textView.setText("Timed out to startSatelliteTransmissionUpdates");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ textView.setText("Failed to startSatelliteTransmissionUpdates with error = "
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ textView.setText("startSatelliteTransmissionUpdates is successful");
+ }
+ } catch (InterruptedException e) {
+ textView.setText("startSatelliteTransmissionUpdates exception caught =" + e);
+ }
+ }
+
+ private void stopTransmissionUpdatesApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+ mSatelliteManager.stopTransmissionUpdates(mCallback, Runnable::run, error::offer);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ textView.setText("Timed out to stopSatelliteTransmissionUpdates");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ textView.setText("Failed to stopSatelliteTransmissionUpdates with error = "
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ textView.setText("stopSatelliteTransmissionUpdates is successful");
+ }
+ } catch (InterruptedException e) {
+ textView.setText("stopSatelliteTransmissionUpdates exception caught =" + e);
+ }
+ }
+ private void pollPendingDatagramsApp(View view) {
+ LinkedBlockingQueue<Integer> resultListener = new LinkedBlockingQueue<>(1);
+ TextView showErrorStatusTextView = findViewById(R.id.showErrorStatus);
+ TextView textView = findViewById(R.id.text_id);
+ mSatelliteManager.setDeviceAlignedWithSatellite(true);
+ if (SatelliteTestApp.getTestSatelliteService() != null) {
+ SatelliteTestApp.getTestSatelliteService().sendOnPendingDatagrams();
+ }
+ mSatelliteManager.requestEnabled(true, true, Runnable::run, resultListener::offer);
+ try {
+ Integer value = resultListener.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ showErrorStatusTextView.setText("Timed out to enable the satellite");
+ return;
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ showErrorStatusTextView.setText("Failed to enable satellite, error = "
+ + SatelliteErrorUtils.mapError(value));
+ return;
+ }
+ resultListener.clear();
+ Log.d(TAG, "Poll to check queue is cleared = "
+ + resultListener.poll(TIMEOUT, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ showErrorStatusTextView.setText("Enable SatelliteService exception caught = " + e);
+ return;
+ }
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
+ try {
+ Integer value = resultListener.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ textView.setText("Timed out for poll message");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ textView.setText("Failed to pollPendingDatagrams with error = "
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ textView.setText("pollPendingDatagrams is successful");
+ }
+ } catch (InterruptedException e) {
+ textView.setText("pollPendingDatagrams exception caught =" + e);
+ }
+ }
+
+ private void sendDatagramApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ mSatelliteManager.setDeviceAlignedWithSatellite(true);
+ LinkedBlockingQueue<Integer> resultListener = new LinkedBlockingQueue<>(1);
+ String mText = "This is a test datagram message";
+ SatelliteDatagram datagram = new SatelliteDatagram(mText.getBytes());
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ try {
+ Integer value = resultListener.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ textView.setText("Timed out for sendDatagram");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ textView.setText("Failed to sendDatagram with error = "
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ textView.setText("sendDatagram is successful");
+ }
+ } catch (InterruptedException e) {
+ textView.setText("sendDatagram exception caught =" + e);
+ }
+ }
+
+ private void registerForIncomingDatagramApp(View view) {
+ int result = mSatelliteManager.registerForIncomingDatagram(Runnable::run,
+ mDatagramCallback);
+ TextView textView = findViewById(R.id.text_id);
+ if (result == 0) {
+ textView.setText("registerForIncomingDatagram is successful");
+ } else {
+ textView.setText("Status for registerForIncomingDatagram : "
+ + SatelliteErrorUtils.mapError(result));
+ }
+ }
+
+ private void unregisterForIncomingDatagramApp(View view) {
+ mSatelliteManager.unregisterForIncomingDatagram(mDatagramCallback);
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("unregisterForIncomingDatagram is successful");
+ }
+
+ private void showDatagramSendStateTransitionApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Last datagram send state transition is : "
+ + mShowDatagramSendStateTransition);
+ }
+
+ private void showDatagramReceiveStateTransitionApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Last datagram receive state transition is : "
+ + mShowDatagramReceiveStateTransition);
+ }
+
+ private void registerForModemStateChangedApp(View view) {
+ int result = mSatelliteManager.registerForModemStateChanged(Runnable::run,
+ mStateCallback);
+ TextView textView = findViewById(R.id.text_id);
+ if (result == 0) {
+ textView.setText("registerForModemStateChanged is successful");
+ } else {
+ textView.setText("Status for registerForModemStateChanged : "
+ + SatelliteErrorUtils.mapError(result));
+ }
+ }
+
+ private void unregisterForModemStateChangedApp(View view) {
+ mSatelliteManager.unregisterForModemStateChanged(mStateCallback);
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("unregisterForModemStateChanged is successful");
+ }
+
+ private void showSatelliteModemStateTransitionApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText(
+ "Last modem transition state is: " + mShowSatelliteModemStateTransition);
+ }
+
+ private String getSatelliteModemStateName(@SatelliteManager.SatelliteModemState int state) {
+ switch (state) {
+ case SatelliteManager.SATELLITE_MODEM_STATE_IDLE:
+ return "IDLE";
+ case SatelliteManager.SATELLITE_MODEM_STATE_LISTENING:
+ return "LISTENING";
+ case SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING:
+ return "DATAGRAM_TRANSFERRING";
+ case SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING:
+ return "DATAGRAM_RETRYING";
+ case SatelliteManager.SATELLITE_MODEM_STATE_OFF:
+ return "OFF";
+ case SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE:
+ return "UNAVAILABLE";
+ default: return "UNKNOWN";
+ }
+ }
+
+ private String getSatelliteModemStateTransition(LinkedList<Integer> states) {
+ StringBuilder sb = new StringBuilder();
+ for (int state : states) {
+ sb.append(getSatelliteModemStateName(state));
+ sb.append("=>");
+ }
+ if (!sb.isEmpty()) {
+ sb.delete(sb.length() - 2, sb.length());
+ }
+ return sb.toString();
+ }
+
+ private String getDatagramTransferStateName(
+ @SatelliteManager.SatelliteDatagramTransferState int state) {
+ switch (state) {
+ case SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE: return "IDLE";
+ case SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING: return "SENDING";
+ case SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS: return "SEND_SUCCESS";
+ case SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED: return "SEND_FAILED";
+ case SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING: return "RECEIVING";
+ case SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS: return "RECEIVE_SUCCESS";
+ case SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE: return "RECEIVE_NONE";
+ case SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED: return "RECEIVE_FAILED";
+ default: return "UNKNOWN";
+ }
+ }
+
+ private String getTransferStateTransition(LinkedList<Integer> states) {
+ StringBuilder sb = new StringBuilder();
+ for (int state : states) {
+ sb.append(getDatagramTransferStateName(state));
+ sb.append("=>");
+ }
+ if (!sb.isEmpty()) {
+ sb.delete(sb.length() - 2, sb.length());
+ }
+ return sb.toString();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ SharedPreferences sh = getSharedPreferences("TestSatelliteSharedPref", MODE_PRIVATE);
+ String modemStateTransition = sh.getString("modem_state",
+ mShowSatelliteModemStateTransition);
+ String datagramSendStateTransition = sh.getString("datagram_send_state",
+ mShowDatagramSendStateTransition);
+ String datagramReceiveStateTransition = sh.getString("datagram_receive_state",
+ mShowDatagramReceiveStateTransition);
+
+ // Setting the fetched data
+ mShowSatelliteModemStateTransition = modemStateTransition;
+ mShowDatagramSendStateTransition = datagramSendStateTransition;
+ mShowDatagramReceiveStateTransition = datagramReceiveStateTransition;
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ SharedPreferences sharedPreferences = getSharedPreferences("TestSatelliteSharedPref",
+ MODE_PRIVATE);
+ SharedPreferences.Editor myEdit = sharedPreferences.edit();
+
+ myEdit.putString("modem_state", mShowSatelliteModemStateTransition);
+ myEdit.putString("datagram_send_state", mShowDatagramSendStateTransition);
+ myEdit.putString("datagram_receive_state", mShowDatagramReceiveStateTransition);
+ myEdit.apply();
+ }
+
+ protected void onDestroy() {
+ super.onDestroy();
+ SharedPreferences sharedPreferences = getSharedPreferences("TestSatelliteSharedPref",
+ MODE_PRIVATE);
+ final SharedPreferences.Editor sharedPrefsEditor = sharedPreferences.edit();
+
+ sharedPrefsEditor.remove("modem_state");
+ sharedPrefsEditor.remove("datagram_send_state");
+ sharedPrefsEditor.remove("datagram_receive_state");
+ sharedPrefsEditor.commit();
+ }
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/ILocalSatelliteListener.aidl b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/ILocalSatelliteListener.aidl
new file mode 100644
index 0000000..2c320c8
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/ILocalSatelliteListener.aidl
@@ -0,0 +1,67 @@
+
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone.testapps.satellitetestapp;
+
+import android.telephony.satellite.stub.SatelliteDatagram;
+
+/**
+ * {@hide}
+ */
+oneway interface ILocalSatelliteListener {
+ /**
+ * Indicates that the remote service - SatelliteModemInterface - has successfully connected to
+ * the TestSatelliteService.
+ */
+ void onRemoteServiceConnected();
+
+ /**
+ * Indicates that TestSatelliteService has just received the request
+ * startSendingSatellitePointingInfo from Telephony.
+ */
+ void onStartSendingSatellitePointingInfo();
+
+ /**
+ * Indicates that TestSatelliteService has just received the request
+ * stopSendingSatellitePointingInfo from Telephony.
+ */
+ void onStopSendingSatellitePointingInfo();
+
+ /**
+ * Indicates that TestSatelliteService has just received the request
+ * pollPendingSatelliteDatagrams from Telephony.
+ */
+ void onPollPendingSatelliteDatagrams();
+
+ /**
+ * Indicates that TestSatelliteService has just received the request
+ * sendSatelliteDatagram from Telephony.
+ */
+ void onSendSatelliteDatagram(in SatelliteDatagram datagram, in boolean isEmergency);
+
+ /**
+ * Indicates that TestSatelliteService has just received the request
+ * requestSatelliteListeningEnabled from Telephony.
+ */
+ void onSatelliteListeningEnabled(in boolean enabled);
+
+ /**
+ * Indicates that TestSatelliteService has just received the request
+ * enableCellularModemWhileSatelliteModeIsOn from Telephony.
+ */
+ void onEnableCellularModemWhileSatelliteModeIsOn(in boolean enable);
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/MultipleSendReceive.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/MultipleSendReceive.java
new file mode 100644
index 0000000..0e5ab4f
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/MultipleSendReceive.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone.testapps.satellitetestapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.SatelliteManager;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.TextView;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity related to Send and Receiving of message APIs for satellite.
+ */
+public class MultipleSendReceive extends Activity {
+
+ private static final String TAG = "MultipleSendReceive";
+
+ private SatelliteManager mSatelliteManager;
+ private android.telephony.satellite.stub.SatelliteDatagram mReceivedDatagram;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSatelliteManager = getSystemService(SatelliteManager.class);
+ mReceivedDatagram = new android.telephony.satellite.stub.SatelliteDatagram();
+
+ setContentView(R.layout.activity_MultipleSendReceive);
+ findViewById(R.id.multiplePollPendingSatelliteDatagrams)
+ .setOnClickListener(this::multiplePollPendingSatelliteDatagramsApp);
+ findViewById(R.id.multipleSendSatelliteDatagram)
+ .setOnClickListener(this::multipleSendSatelliteDatagramApp);
+ findViewById(R.id.multipleSendReceiveSatelliteDatagram)
+ .setOnClickListener(this::multipleSendReceiveSatelliteDatagramApp);
+ findViewById(R.id.Back).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(MultipleSendReceive.this, SatelliteTestApp.class));
+ }
+ });
+ }
+
+ private void multiplePollPendingSatelliteDatagramsApp(View view) {
+ mSatelliteManager.setDeviceAlignedWithSatellite(true);
+ SatelliteTestApp.getTestSatelliteService().sendOnPendingDatagrams();
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 4);
+ LinkedBlockingQueue<Integer> resultListener = new LinkedBlockingQueue<>(1);
+ mSatelliteManager.requestEnabled(true, true, Runnable::run, resultListener::offer);
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 3);
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 2);
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 1);
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 0);
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
+ try {
+ Integer value = resultListener.poll(1000, TimeUnit.MILLISECONDS);
+ TextView textView = findViewById(R.id.text_id);
+ if (value == 0) {
+ textView.setText("multiplePollPendingSatelliteDatagrams is Successful");
+ } else {
+ textView.setText("Status for multiplePollPendingSatelliteDatagrams : "
+ + SatelliteErrorUtils.mapError(value));
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "exception caught =" + e);
+ }
+ }
+
+
+ private void multipleSendSatelliteDatagramApp(View view) {
+ mSatelliteManager.setDeviceAlignedWithSatellite(true);
+ LinkedBlockingQueue<Integer> resultListener = new LinkedBlockingQueue<>(1);
+ String mText = "This is a test datagram message";
+ SatelliteDatagram datagram = new SatelliteDatagram(mText.getBytes());
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ try {
+ Integer value = resultListener.poll(1000, TimeUnit.MILLISECONDS);
+ TextView textView = findViewById(R.id.text_id);
+ if (value == 0) {
+ textView.setText("multipleSendSatelliteDatagram is Successful");
+ } else {
+ textView.setText("Status for multipleSendSatelliteDatagram : "
+ + SatelliteErrorUtils.mapError(value));
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "exception caught =" + e);
+ }
+ }
+
+ private void multipleSendReceiveSatelliteDatagramApp(View view) {
+ mSatelliteManager.setDeviceAlignedWithSatellite(true);
+ LinkedBlockingQueue<Integer> resultListener = new LinkedBlockingQueue<>(1);
+ mSatelliteManager.requestEnabled(true, true, Runnable::run, resultListener::offer);
+ String mText = "This is a test datagram message";
+ SatelliteDatagram datagram = new SatelliteDatagram(mText.getBytes());
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 4);
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 3);
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 2);
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 1);
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, resultListener::offer);
+ SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+ mReceivedDatagram, 0);
+ try {
+ Integer value = resultListener.poll(1000, TimeUnit.MILLISECONDS);
+ TextView textView = findViewById(R.id.text_id);
+ if (value == 0) {
+ textView.setText("multipleSendReceiveSatelliteDatagram is Successful");
+ } else {
+ textView.setText("Status for multipleSendReceiveSatelliteDatagram : "
+ + SatelliteErrorUtils.mapError(value));
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "exception caught =" + e);
+ }
+ }
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Provisioning.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Provisioning.java
new file mode 100644
index 0000000..20c5ef5
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/Provisioning.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone.testapps.satellitetestapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.SatelliteProvisionStateCallback;
+import android.telephony.satellite.stub.SatelliteResult;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.TextView;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Activity related to Provisioning APIs.
+ */
+public class Provisioning extends Activity {
+
+ private static final String TAG = "Provisioning";
+
+ private boolean mProvisioned = false;
+
+ private SatelliteManager mSatelliteManager;
+ private SatelliteProvisionStateCallbackTestApp mCallback;
+ private static final long TIMEOUT = 3000;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSatelliteManager = getSystemService(SatelliteManager.class);
+ mCallback = new SatelliteProvisionStateCallbackTestApp();
+
+ setContentView(R.layout.activity_Provisioning);
+ findViewById(R.id.provisionSatelliteService)
+ .setOnClickListener(this::provisionServiceApp);
+ findViewById(R.id.deprovisionSatelliteService)
+ .setOnClickListener(this::deprovisionServiceApp);
+ findViewById(R.id.requestIsSatelliteProvisioned)
+ .setOnClickListener(this::requestIsProvisionedApp);
+ findViewById(R.id.registerForSatelliteProvisionStateChanged)
+ .setOnClickListener(this::registerForProvisionStateChangedApp);
+ findViewById(R.id.unregisterForSatelliteProvisionStateChanged)
+ .setOnClickListener(this::unregisterForProvisionStateChangedApp);
+ findViewById(R.id.showCurrentSatelliteProvisionState)
+ .setOnClickListener(this::showCurrentSatelliteProvisionStateApp);
+ findViewById(R.id.Back).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(Provisioning.this, SatelliteTestApp.class));
+ }
+ });
+ }
+
+ protected class SatelliteProvisionStateCallbackTestApp implements
+ SatelliteProvisionStateCallback {
+ @Override
+ public void onSatelliteProvisionStateChanged(boolean provisioned) {
+ mProvisioned = provisioned;
+ Log.d(TAG, "onSatelliteProvisionStateChanged in SatelliteTestApp: provisioned="
+ + mProvisioned);
+ }
+ }
+
+ private void provisionServiceApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+ String mText = "This is test provision data.";
+ byte[] testProvisionData = mText.getBytes();
+ mSatelliteManager.provisionService("SATELLITE_TOKEN", testProvisionData,
+ cancellationSignal, Runnable::run, error::offer);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ textView.setText("Timed out to provision the satellite");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ textView.setText("Failed to provision SatelliteService with error = "
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ textView.setText("Successfully provisioned the satellite");
+ }
+ } catch (InterruptedException e) {
+ textView.setText("Provision SatelliteService exception caught =" + e);
+ }
+ }
+
+ private void deprovisionServiceApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+ mSatelliteManager.deprovisionService("SATELLITE_TOKEN", Runnable::run,
+ error::offer);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ textView.setText("Timed out to deprovision the satellite");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ textView.setText("Failed to deprovision SatelliteService with error = "
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ textView.setText("Successfully deprovisioned the satellite");
+ }
+ } catch (InterruptedException e) {
+ textView.setText("Deprovision SatelliteService exception caught =" + e);
+ }
+ }
+
+ private void requestIsProvisionedApp(View view) {
+ final AtomicReference<Boolean> enabled = new AtomicReference<>();
+ final AtomicReference<Integer> errorCode = new AtomicReference<>();
+ OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> mReceiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Boolean result) {
+ enabled.set(result);
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for requestIsSatelliteProvisioned result: "
+ + enabled.get());
+ }
+
+ @Override
+ public void onError(SatelliteManager.SatelliteException exception) {
+ errorCode.set(exception.getErrorCode());
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for requestIsSatelliteProvisioned error : "
+ + SatelliteErrorUtils.mapError(errorCode.get()));
+ }
+ };
+ mSatelliteManager.requestIsProvisioned(Runnable::run, mReceiver);
+ }
+
+ private void registerForProvisionStateChangedApp(View view) {
+ int result = mSatelliteManager.registerForProvisionStateChanged(Runnable::run,
+ mCallback);
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for registerForSatelliteProvisionStateChanged : "
+ + SatelliteErrorUtils.mapError(result));
+ }
+
+ private void unregisterForProvisionStateChangedApp(View view) {
+ mSatelliteManager.unregisterForProvisionStateChanged(mCallback);
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("unregisterForSatelliteProvisionStateChanged is successful");
+ }
+
+ private void showCurrentSatelliteProvisionStateApp(View view) {
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Current Provision State is " + mProvisioned);
+ }
+
+ // Fetch the stored data in onResume()
+ // Because this is what will be called when the app opens again
+ @Override
+ protected void onResume() {
+ super.onResume();
+ // Fetching the stored data from the SharedPreference
+ SharedPreferences sh = getSharedPreferences("MySharedPref", MODE_PRIVATE);
+ boolean isProvisioned = sh.getBoolean("provision_state", mProvisioned);
+
+ // Setting the fetched data
+ mProvisioned = isProvisioned;
+ }
+
+ // Store the data in the SharedPreference in the onPause() method
+ // When the user closes the application onPause() will be called and data will be stored
+ @Override
+ protected void onPause() {
+ super.onPause();
+ // Creating a shared pref object with a file name "MySharedPref" in private mode
+ SharedPreferences sharedPreferences = getSharedPreferences("MySharedPref", MODE_PRIVATE);
+ SharedPreferences.Editor myEdit = sharedPreferences.edit();
+
+ // write all the data entered by the user in SharedPreference and apply
+ myEdit.putBoolean("provision_state", mProvisioned);
+ myEdit.apply();
+ }
+
+ protected void onDestroy() {
+ super.onDestroy();
+ SharedPreferences sharedPreferences = getSharedPreferences("MySharedPref", MODE_PRIVATE);
+
+ final SharedPreferences.Editor sharedPrefsEditor = sharedPreferences.edit();
+ sharedPrefsEditor.remove("provision_state");
+ sharedPrefsEditor.commit();
+ }
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteControl.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteControl.java
new file mode 100644
index 0000000..2d01aec
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteControl.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone.testapps.satellitetestapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.OutcomeReceiver;
+import android.telephony.satellite.SatelliteCapabilities;
+import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.stub.SatelliteResult;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.TextView;
+
+import java.time.Duration;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Activity related to SatelliteControl APIs for satellite.
+ */
+public class SatelliteControl extends Activity {
+
+ private static final long TIMEOUT = 3000;
+
+ private SatelliteManager mSatelliteManager;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSatelliteManager = getSystemService(SatelliteManager.class);
+
+ setContentView(R.layout.activity_SatelliteControl);
+ findViewById(R.id.enableSatellite)
+ .setOnClickListener(this::enableSatelliteApp);
+ findViewById(R.id.disableSatellite)
+ .setOnClickListener(this::disableSatelliteApp);
+ findViewById(R.id.requestIsSatelliteEnabled)
+ .setOnClickListener(this::requestIsEnabledApp);
+ findViewById(R.id.requestIsDemoModeEnabled)
+ .setOnClickListener(this::requestIsDemoModeEnabledApp);
+ findViewById(R.id.requestIsSatelliteSupported)
+ .setOnClickListener(this::requestIsSupportedApp);
+ findViewById(R.id.requestSatelliteCapabilities)
+ .setOnClickListener(this::requestCapabilitiesApp);
+ findViewById(R.id.requestIsSatelliteCommunicationAllowedForCurrentLocation)
+ .setOnClickListener(this::requestIsCommunicationAllowedForCurrentLocationApp);
+ findViewById(R.id.requestTimeForNextSatelliteVisibility)
+ .setOnClickListener(this::requestTimeForNextSatelliteVisibilityApp);
+ findViewById(R.id.Back).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(SatelliteControl.this, SatelliteTestApp.class));
+ }
+ });
+ }
+
+ private void enableSatelliteApp(View view) {
+ LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+ mSatelliteManager.requestEnabled(true, true, Runnable::run, error::offer);
+ TextView textView = findViewById(R.id.text_id);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ textView.setText("Timed out to enable the satellite");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ textView.setText("Failed to enable the satellite, error ="
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ textView.setText("Successfully enabled the satellite");
+ }
+ } catch (InterruptedException e) {
+ textView.setText("Enable SatelliteService exception caught =" + e);
+ }
+ }
+
+ private void disableSatelliteApp(View view) {
+ LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+ mSatelliteManager.requestEnabled(false, true, Runnable::run, error::offer);
+ TextView textView = findViewById(R.id.text_id);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ textView.setText("Timed out to disable the satellite");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ textView.setText("Failed to disable the satellite, error ="
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ textView.setText("Successfully disabled the satellite");
+ }
+ } catch (InterruptedException e) {
+ textView.setText("Disable SatelliteService exception caught =" + e);
+ }
+ }
+
+ private void requestIsEnabledApp(View view) {
+ final AtomicReference<Boolean> enabled = new AtomicReference<>();
+ final AtomicReference<Integer> errorCode = new AtomicReference<>();
+ OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> receiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Boolean result) {
+ enabled.set(result);
+ TextView textView = findViewById(R.id.text_id);
+ if (enabled.get()) {
+ textView.setText("requestIsEnabled is true");
+ } else {
+ textView.setText("Status for requestIsEnabled result : "
+ + enabled.get());
+ }
+ }
+
+ @Override
+ public void onError(SatelliteManager.SatelliteException exception) {
+ errorCode.set(exception.getErrorCode());
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for requestIsEnabled error : "
+ + SatelliteErrorUtils.mapError(errorCode.get()));
+ }
+ };
+ mSatelliteManager.requestIsEnabled(Runnable::run, receiver);
+ }
+
+ private void requestIsDemoModeEnabledApp(View view) {
+ final AtomicReference<Boolean> enabled = new AtomicReference<>();
+ final AtomicReference<Integer> errorCode = new AtomicReference<>();
+ OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> receiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Boolean result) {
+ enabled.set(result);
+ TextView textView = findViewById(R.id.text_id);
+ if (enabled.get()) {
+ textView.setText("requestIsDemoModeEnabled is true");
+ } else {
+ textView.setText("Status for requestIsDemoModeEnabled result : "
+ + enabled.get());
+ }
+ }
+
+ @Override
+ public void onError(SatelliteManager.SatelliteException exception) {
+ errorCode.set(exception.getErrorCode());
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for requestIsDemoModeEnabled error : "
+ + SatelliteErrorUtils.mapError(errorCode.get()));
+ }
+ };
+ mSatelliteManager.requestIsDemoModeEnabled(Runnable::run, receiver);
+ }
+
+ private void requestIsSupportedApp(View view) {
+ final AtomicReference<Boolean> enabled = new AtomicReference<>();
+ final AtomicReference<Integer> errorCode = new AtomicReference<>();
+ OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> receiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Boolean result) {
+ enabled.set(result);
+ TextView textView = findViewById(R.id.text_id);
+ if (enabled.get()) {
+ textView.setText("requestIsSupported is true");
+ } else {
+ textView.setText("Status for requestIsSupported result : "
+ + enabled.get());
+ }
+ }
+
+ @Override
+ public void onError(SatelliteManager.SatelliteException exception) {
+ errorCode.set(exception.getErrorCode());
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for requestIsSupported error : "
+ + SatelliteErrorUtils.mapError(errorCode.get()));
+ }
+ };
+ mSatelliteManager.requestIsSupported(Runnable::run, receiver);
+ }
+
+ private void requestCapabilitiesApp(View view) {
+ final AtomicReference<SatelliteCapabilities> capabilities = new AtomicReference<>();
+ final AtomicReference<Integer> errorCode = new AtomicReference<>();
+ OutcomeReceiver<SatelliteCapabilities, SatelliteManager.SatelliteException> receiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(SatelliteCapabilities result) {
+ capabilities.set(result);
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for requestCapabilities result: "
+ + capabilities.get());
+ }
+
+ @Override
+ public void onError(SatelliteManager.SatelliteException exception) {
+ errorCode.set(exception.getErrorCode());
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for requestCapabilities error : "
+ + SatelliteErrorUtils.mapError(errorCode.get()));
+ }
+ };
+ mSatelliteManager.requestCapabilities(Runnable::run, receiver);
+ }
+
+ private void requestIsCommunicationAllowedForCurrentLocationApp(View view) {
+ final AtomicReference<Boolean> enabled = new AtomicReference<>();
+ final AtomicReference<Integer> errorCode = new AtomicReference<>();
+ String display = "requestIsCommunicationAllowedForCurrentLocation";
+ OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> receiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Boolean result) {
+ enabled.set(result);
+ TextView textView = findViewById(R.id.text_id);
+ if (enabled.get()) {
+ textView.setText(display + "is true");
+ } else {
+ textView.setText("Status for" + display + "result: " + enabled.get());
+ }
+
+ }
+
+ @Override
+ public void onError(SatelliteManager.SatelliteException exception) {
+ errorCode.set(exception.getErrorCode());
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for" + display + "error: "
+ + SatelliteErrorUtils.mapError(errorCode.get()));
+ }
+ };
+ mSatelliteManager.requestIsCommunicationAllowedForCurrentLocation(Runnable::run,
+ receiver);
+ }
+
+ private void requestTimeForNextSatelliteVisibilityApp(View view) {
+ final AtomicReference<Duration> nextVisibilityDuration = new AtomicReference<>();
+ final AtomicReference<Integer> errorCode = new AtomicReference<>();
+ OutcomeReceiver<Duration, SatelliteManager.SatelliteException> receiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Duration result) {
+ nextVisibilityDuration.set(result);
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for requestTimeForNextSatelliteVisibility result : "
+ + result.getSeconds());
+ }
+
+ @Override
+ public void onError(SatelliteManager.SatelliteException exception) {
+ errorCode.set(exception.getErrorCode());
+ TextView textView = findViewById(R.id.text_id);
+ textView.setText("Status for requestTimeForNextSatelliteVisibility error : "
+ + SatelliteErrorUtils.mapError(errorCode.get()));
+ }
+ };
+ mSatelliteManager.requestTimeForNextSatelliteVisibility(Runnable::run, receiver);
+ }
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteErrorUtils.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteErrorUtils.java
new file mode 100644
index 0000000..ffdabdf
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteErrorUtils.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone.testapps.satellitetestapp;
+
+import android.telephony.satellite.stub.SatelliteResult;
+import android.util.Log;
+
+/**
+ * Utils class for satellite error to display as string in SatelliteTestApp UI.
+ */
+public class SatelliteErrorUtils {
+ private static final String TAG = "SatelliteErrorUtils";
+
+ /**
+ * @param error int from the satellite manager.
+ * @return The converted SatelliteError for the testapp, result of the operation.
+ */
+ public static String mapError(int error) {
+ switch (error) {
+ case SatelliteResult.SATELLITE_RESULT_SUCCESS:
+ return "SATELLITE_RESULT_SUCCESS";
+ case SatelliteResult.SATELLITE_RESULT_ERROR:
+ return "SATELLITE_RESULT_ERROR";
+ case SatelliteResult.SATELLITE_RESULT_SERVER_ERROR:
+ return "SATELLITE_RESULT_SERVER_ERROR";
+ case SatelliteResult.SATELLITE_RESULT_SERVICE_ERROR:
+ return "SATELLITE_RESULT_SERVICE_ERROR";
+ case SatelliteResult.SATELLITE_RESULT_MODEM_ERROR:
+ return "SATELLITE_RESULT_MODEM_ERROR";
+ case SatelliteResult.SATELLITE_RESULT_NETWORK_ERROR:
+ return "SATELLITE_RESULT_NETWORK_ERROR";
+ case SatelliteResult.SATELLITE_RESULT_INVALID_MODEM_STATE:
+ return "SATELLITE_RESULT_INVALID_MODEM_STATE";
+ case SatelliteResult.SATELLITE_RESULT_INVALID_ARGUMENTS:
+ return "SATELLITE_RESULT_INVALID_ARGUMENTS";
+ case SatelliteResult.SATELLITE_RESULT_REQUEST_FAILED:
+ return "SATELLITE_RESULT_REQUEST_FAILED";
+ case SatelliteResult.SATELLITE_RESULT_RADIO_NOT_AVAILABLE:
+ return "SATELLITE_RESULT_RADIO_NOT_AVAILABLE";
+ case SatelliteResult.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED:
+ return "SATELLITE_RESULT_REQUEST_NOT_SUPPORTED";
+ case SatelliteResult.SATELLITE_RESULT_NO_RESOURCES:
+ return "SATELLITE_RESULT_NO_RESOURCES";
+ case SatelliteResult.SATELLITE_RESULT_SERVICE_NOT_PROVISIONED:
+ return "SATELLITE_RESULT_SERVICE_NOT_PROVISIONED";
+ case SatelliteResult.SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS:
+ return "SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS";
+ case SatelliteResult.SATELLITE_RESULT_REQUEST_ABORTED:
+ return "SATELLITE_RESULT_REQUEST_ABORTED";
+ case SatelliteResult.SATELLITE_RESULT_ACCESS_BARRED:
+ return "SATELLITE_RESULT_ACCESS_BARRED";
+ case SatelliteResult.SATELLITE_RESULT_NETWORK_TIMEOUT:
+ return "SATELLITE_RESULT_NETWORK_TIMEOUT";
+ case SatelliteResult.SATELLITE_RESULT_NOT_REACHABLE:
+ return "SATELLITE_RESULT_NOT_REACHABLE";
+ case SatelliteResult.SATELLITE_RESULT_NOT_AUTHORIZED:
+ return "SATELLITE_RESULT_NOT_AUTHORIZED";
+ }
+ Log.d(TAG, "Received invalid satellite service error: " + error);
+ return "SATELLITE_RESULT_SERVICE_ERROR";
+ }
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteTestApp.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteTestApp.java
new file mode 100644
index 0000000..ced9a06
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteTestApp.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.testapps.satellitetestapp;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.telephony.satellite.stub.SatelliteDatagram;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * SatelliteTestApp main activity to navigate to other APIs related to satellite.
+ */
+public class SatelliteTestApp extends Activity {
+
+ private static final String TAG = "SatelliteTestApp";
+ public static TestSatelliteService sSatelliteService;
+ private final Object mSendDatagramLock = new Object();
+
+ private TestSatelliteServiceConnection mSatelliteServiceConn;
+ private List<SatelliteDatagram> mSentSatelliteDatagrams = new ArrayList<>();
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (mSatelliteServiceConn == null) {
+ mSatelliteServiceConn = new TestSatelliteServiceConnection();
+ getBaseContext().bindService(new Intent(getBaseContext(),
+ TestSatelliteService.class), mSatelliteServiceConn, Context.BIND_AUTO_CREATE);
+ }
+
+ setContentView(R.layout.activity_SatelliteTestApp);
+ findViewById(R.id.SatelliteControl).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(SatelliteTestApp.this, SatelliteControl.class);
+ startActivity(intent);
+ }
+ });
+ findViewById(R.id.Datagram).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(SatelliteTestApp.this, Datagram.class);
+ startActivity(intent);
+ }
+ });
+ findViewById(R.id.Provisioning).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(SatelliteTestApp.this, Provisioning.class);
+ startActivity(intent);
+ }
+ });
+ findViewById(R.id.MultipleSendReceive).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(SatelliteTestApp.this, MultipleSendReceive.class);
+ startActivity(intent);
+ }
+ });
+ findViewById(R.id.SendReceive).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(SatelliteTestApp.this, SendReceive.class);
+ startActivity(intent);
+ }
+ });
+ findViewById(R.id.TestSatelliteWrapper).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(SatelliteTestApp.this, TestSatelliteWrapper.class);
+ startActivity(intent);
+ }
+ });
+ }
+
+ private final ILocalSatelliteListener mSatelliteListener =
+ new ILocalSatelliteListener.Stub() {
+ @Override
+ public void onRemoteServiceConnected() {
+ Log.d(TAG, "onRemoteServiceConnected");
+ }
+
+ @Override
+ public void onStartSendingSatellitePointingInfo() {
+ Log.d(TAG, "onStartSendingSatellitePointingInfo");
+ }
+
+ @Override
+ public void onStopSendingSatellitePointingInfo() {
+ Log.d(TAG, "onStopSendingSatellitePointingInfo");
+ }
+
+ @Override
+ public void onPollPendingSatelliteDatagrams() {
+ Log.d(TAG, "onPollPendingSatelliteDatagrams");
+ }
+
+ @Override
+ public void onSendSatelliteDatagram(
+ SatelliteDatagram datagram, boolean isEmergency) {
+ Log.d(TAG, "onSendSatelliteDatagram");
+ synchronized (mSendDatagramLock) {
+ mSentSatelliteDatagrams.add(datagram);
+ }
+ }
+
+ @Override
+ public void onSatelliteListeningEnabled(boolean enable) {
+ Log.d(TAG, "onSatelliteListeningEnabled");
+ }
+
+ @Override
+ public void onEnableCellularModemWhileSatelliteModeIsOn(boolean enable) {
+ Log.d(TAG, "onEnableCellularModemWhileSatelliteModeIsOn");
+ }
+ };
+
+ private class TestSatelliteServiceConnection implements ServiceConnection {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.d(TAG, "onServiceConnected in SatelliteTestApp");
+ sSatelliteService = ((TestSatelliteService.LocalBinder) service).getService();
+ sSatelliteService.setLocalSatelliteListener(mSatelliteListener);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.d(TAG, "onServiceDisconnected in SatelliteTestApp");
+ sSatelliteService = null;
+ }
+ }
+
+ public static TestSatelliteService getTestSatelliteService() {
+ return sSatelliteService;
+ }
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SendReceive.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SendReceive.java
new file mode 100644
index 0000000..b5e82b1
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SendReceive.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone.testapps.satellitetestapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+import android.telephony.satellite.PointingInfo;
+import android.telephony.satellite.SatelliteCapabilities;
+import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.SatelliteDatagramCallback;
+import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.SatelliteTransmissionUpdateCallback;
+import android.telephony.satellite.stub.SatelliteResult;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+/**
+ * Activity related to Send and Receiving of message APIs for satellite.
+ */
+public class SendReceive extends Activity {
+
+ private static final String TAG = "SendReceive";
+
+ private SatelliteManager mSatelliteManager;
+ private SendReceive.SatelliteDatagramCallbackTestApp mCallback;
+
+ private PointingInfo mPointingInfo;
+ private String mMessageInput = "";
+ private String mMessageOutput = "";
+ private static final long TIMEOUT = 3000;
+
+ private EditText mEnterMessage;
+ private TextView mMessageStatusTextView;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSatelliteManager = getSystemService(SatelliteManager.class);
+ mCallback = new SendReceive.SatelliteDatagramCallbackTestApp();
+
+ setContentView(R.layout.activity_SendReceive);
+ findViewById(R.id.sendMessage).setOnClickListener(this::sendStatusApp);
+ findViewById(R.id.receiveMessage).setOnClickListener(this::receiveStatusApp);
+ mEnterMessage = (EditText) findViewById(R.id.enterText);
+ mMessageStatusTextView = findViewById(R.id.messageStatus);
+ findViewById(R.id.Back).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(SendReceive.this, SatelliteTestApp.class));
+ }
+ });
+ }
+
+ protected class SatelliteDatagramCallbackTestApp implements SatelliteDatagramCallback {
+ @Override
+ public void onSatelliteDatagramReceived(long datagramId, SatelliteDatagram datagram,
+ int pendingCount, Consumer<Void> callback) {
+ Log.d(TAG, "onSatelliteDatagramReceived in TestApp: datagramId =" + datagramId
+ + ", datagram =" + datagram + ", pendingCount=" + pendingCount);
+ mMessageStatusTextView.setText("Last received satellite message is = "
+ + new String(datagram.getSatelliteDatagram()));
+ }
+ }
+
+ protected class SatelliteTransmissionUpdateCallbackTestApp implements
+ SatelliteTransmissionUpdateCallback {
+ @Override
+ public void onSatellitePositionChanged(PointingInfo pointingInfo) {
+ mPointingInfo = pointingInfo;
+ Log.d(TAG, "onSatellitePositionChanged in TestApp for sendReceive: pointingInfo = "
+ + mPointingInfo);
+ TextView satellitePositionTextView = findViewById(R.id.satellitePosition);
+ satellitePositionTextView.setText("Successfully received the satellite position : "
+ + mPointingInfo);
+ }
+
+ @Override
+ public void onSendDatagramStateChanged(int state, int sendPendingCount, int errorCode) {
+ Log.d(TAG, "onSendDatagramStateChanged in TestApp for sendReceive: state = "
+ + state + ", sendPendingCount =" + sendPendingCount + ", errorCode="
+ + errorCode);
+ }
+
+ @Override
+ public void onReceiveDatagramStateChanged(
+ int state, int receivePendingCount, int errorCode) {
+ Log.d(TAG, "onReceiveDatagramStateChanged in TestApp for sendReceive: state = "
+ + state + ", " + "receivePendingCount=" + receivePendingCount + ", errorCode="
+ + errorCode);
+ }
+ }
+
+ private void sendStatusApp(View view) {
+ LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+ mMessageInput = mEnterMessage.getText().toString();
+ mMessageOutput = mEnterMessage.getText().toString();
+ byte[] testProvisionData = mMessageInput.getBytes();
+ setupForTransferringDatagram(testProvisionData);
+
+ SatelliteDatagram datagram = new SatelliteDatagram(mMessageInput.getBytes());
+ //Sending Message
+ mSatelliteManager.sendDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+ datagram, true, Runnable::run, error::offer);
+ TextView messageStatusTextView = findViewById(R.id.messageStatus);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ messageStatusTextView.setText("Timed out to send the message");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ messageStatusTextView.setText("Failed to send the message, error ="
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ messageStatusTextView.setText("Successfully sent the message = "
+ + mEnterMessage.getText().toString());
+ }
+ } catch (InterruptedException e) {
+ messageStatusTextView.setText("sendDatagram exception caught = " + e);
+ }
+ }
+
+ private void receiveStatusApp(View view) {
+ LinkedBlockingQueue<Integer> resultListener = new LinkedBlockingQueue<>(1);
+ byte[] testProvisionData = mMessageOutput.getBytes();
+ setupForTransferringDatagram(testProvisionData);
+
+ int result = mSatelliteManager.registerForIncomingDatagram(Runnable::run, mCallback);
+ TextView showErrorStatusTextView = findViewById(R.id.showErrorStatus);
+ if (result != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ showErrorStatusTextView.setText("Status for registerForIncomingDatagram : "
+ + SatelliteErrorUtils.mapError(result));
+ }
+ if (SatelliteTestApp.getTestSatelliteService() != null) {
+ SatelliteTestApp.getTestSatelliteService().sendOnPendingDatagrams();
+ }
+ mSatelliteManager.requestEnabled(true, true, Runnable::run, resultListener::offer);
+ try {
+ Integer value = resultListener.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ showErrorStatusTextView.setText("Timed out to enable the satellite");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ showErrorStatusTextView.setText("Failed to enable satellite, error = "
+ + SatelliteErrorUtils.mapError(value));
+ return;
+ }
+ resultListener.clear();
+ } catch (InterruptedException e) {
+ showErrorStatusTextView.setText("Enable SatelliteService exception caught = " + e);
+ return;
+ }
+
+ mSatelliteManager.pollPendingDatagrams(Runnable::run, resultListener::offer);
+ try {
+ Integer value = resultListener.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ mMessageStatusTextView.setText("Timed out to poll pending messages");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ mMessageStatusTextView.setText("Failed to poll pending messages, error = "
+ + SatelliteErrorUtils.mapError(value));
+ } else {
+ mMessageStatusTextView.setText("Successfully polled pending messages");
+ }
+ } catch (InterruptedException e) {
+ mMessageStatusTextView.setText("pollPendingDatagrams exception caught = " + e);
+ }
+ }
+
+ private void setupForTransferringDatagram(byte[] provisionData) {
+ TextView showErrorStatusTextView = findViewById(R.id.showErrorStatus);
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+
+ //Provisioning
+ mSatelliteManager.provisionService("SATELLITE_TOKEN", provisionData,
+ cancellationSignal, Runnable::run, error::offer);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ showErrorStatusTextView.setText("Timed out to provision the satellite");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ showErrorStatusTextView.setText("Failed to provision satellite, error = "
+ + SatelliteErrorUtils.mapError(value));
+ return;
+ }
+ } catch (InterruptedException e) {
+ showErrorStatusTextView.setText("Provision SatelliteService exception caught = " + e);
+ return;
+ }
+ error.clear();
+
+ //Static position of device
+ final AtomicReference<SatelliteCapabilities> capabilities = new AtomicReference<>();
+ final AtomicReference<Integer> errorCode = new AtomicReference<>();
+ OutcomeReceiver<SatelliteCapabilities, SatelliteManager.SatelliteException> receiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(SatelliteCapabilities result) {
+ capabilities.set(result);
+ TextView devicePositionTextView = findViewById(R.id.devicePosition);
+ devicePositionTextView.setText("Successfully receive the device position : "
+ + capabilities.get().getAntennaPositionMap());
+ }
+
+ @Override
+ public void onError(SatelliteManager.SatelliteException exception) {
+ errorCode.set(exception.getErrorCode());
+ TextView devicePositionTextView = findViewById(R.id.devicePosition);
+ devicePositionTextView.setText("Unable to fetch device position error is : "
+ + SatelliteErrorUtils.mapError(errorCode.get()));
+ }
+ };
+ mSatelliteManager.requestCapabilities(Runnable::run, receiver);
+
+ //Satellite Position
+ SatelliteTransmissionUpdateCallbackTestApp callback =
+ new SatelliteTransmissionUpdateCallbackTestApp();
+ mSatelliteManager.requestEnabled(true, true, Runnable::run, error::offer);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ showErrorStatusTextView.setText("Timed out to enable the satellite");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ showErrorStatusTextView.setText("Failed to enable satellite, error = "
+ + SatelliteErrorUtils.mapError(value));
+ return;
+ }
+ } catch (InterruptedException e) {
+ showErrorStatusTextView.setText("Enable SatelliteService exception caught = " + e);
+ return;
+ }
+ error.clear();
+
+ mSatelliteManager.startTransmissionUpdates(Runnable::run, error::offer, callback);
+ // Position update
+ android.telephony.satellite.stub.PointingInfo pointingInfo =
+ new android.telephony.satellite.stub.PointingInfo();
+ pointingInfo.satelliteAzimuth = 50.5f;
+ pointingInfo.satelliteElevation = 20.36f;
+ if (SatelliteTestApp.getTestSatelliteService() != null) {
+ SatelliteTestApp.getTestSatelliteService().sendOnSatellitePositionChanged(pointingInfo);
+ }
+ TextView satellitePositionTextView = findViewById(R.id.satellitePosition);
+ try {
+ Integer value = error.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+ if (value == null) {
+ satellitePositionTextView.setText("Failed to register for satellite transmission"
+ + "updates");
+ } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ satellitePositionTextView.setText("Failed to register for satellite transmission "
+ + "updates, error = " + SatelliteErrorUtils.mapError(value));
+ }
+ } catch (InterruptedException e) {
+ satellitePositionTextView.setText("startSatelliteTransmissionUpdates exception caught ="
+ + e);
+ }
+ //Device is aligned with the satellite for demo mode
+ mSatelliteManager.setDeviceAlignedWithSatellite(true);
+ }
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteService.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteService.java
new file mode 100644
index 0000000..9bea30c
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteService.java
@@ -0,0 +1,528 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone.testapps.satellitetestapp;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.telephony.IBooleanConsumer;
+import android.telephony.IIntegerConsumer;
+import android.telephony.satellite.AntennaDirection;
+import android.telephony.satellite.AntennaPosition;
+import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer;
+import android.telephony.satellite.stub.ISatelliteListener;
+import android.telephony.satellite.stub.NTRadioTechnology;
+import android.telephony.satellite.stub.PointingInfo;
+import android.telephony.satellite.stub.SatelliteCapabilities;
+import android.telephony.satellite.stub.SatelliteDatagram;
+import android.telephony.satellite.stub.SatelliteImplBase;
+import android.telephony.satellite.stub.SatelliteModemState;
+import android.telephony.satellite.stub.SatelliteResult;
+import android.telephony.satellite.stub.SatelliteService;
+import android.util.Log;
+
+import com.android.internal.util.FunctionalUtils;
+import com.android.telephony.Rlog;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Test service for Satellite to verify end to end flow via testapp.
+ */
+public class TestSatelliteService extends SatelliteImplBase {
+ private static final String TAG = "TestSatelliteService";
+
+ // Hardcoded values below
+ private static final int SATELLITE_ALWAYS_VISIBLE = 0;
+ /** SatelliteCapabilities constant indicating that the radio technology is proprietary. */
+ private static final int[] SUPPORTED_RADIO_TECHNOLOGIES =
+ new int[]{NTRadioTechnology.PROPRIETARY};
+ /** SatelliteCapabilities constant indicating that pointing to satellite is required. */
+ private static final boolean POINTING_TO_SATELLITE_REQUIRED = true;
+ /** SatelliteCapabilities constant indicating the maximum number of characters per datagram. */
+ private static final int MAX_BYTES_PER_DATAGRAM = 339;
+ /** SatelliteCapabilities constant keys which are used to fill mAntennaPositionMap. */
+ private static final int[] ANTENNA_POSITION_KEYS = new int[]{
+ SatelliteManager.DISPLAY_MODE_OPENED, SatelliteManager.DISPLAY_MODE_CLOSED};
+ /** SatelliteCapabilities constant values which are used to fill mAntennaPositionMap. */
+ private static final AntennaPosition[] ANTENNA_POSITION_VALUES = new AntennaPosition[] {
+ new AntennaPosition(new AntennaDirection(1, 1, 1),
+ SatelliteManager.DEVICE_HOLD_POSITION_PORTRAIT),
+ new AntennaPosition(new AntennaDirection(2, 2, 2),
+ SatelliteManager.DEVICE_HOLD_POSITION_LANDSCAPE_LEFT)
+ };
+
+ @NonNull
+ private final Map<IBinder, ISatelliteListener> mRemoteListeners = new HashMap<>();
+ @Nullable private ILocalSatelliteListener mLocalListener;
+ private final LocalBinder mBinder = new LocalBinder();
+ @SatelliteResult
+ private int mErrorCode = SatelliteResult.SATELLITE_RESULT_SUCCESS;
+ private final AtomicBoolean mShouldNotifyRemoteServiceConnected =
+ new AtomicBoolean(false);
+
+ // For local access of this Service.
+ class LocalBinder extends Binder {
+ TestSatelliteService getService() {
+ return TestSatelliteService.this;
+ }
+ }
+
+ private boolean mIsCommunicationAllowedInLocation;
+ private boolean mIsEnabled;
+ private boolean mIsProvisioned;
+ private boolean mIsSupported;
+ private int mModemState;
+ private boolean mIsCellularModemEnabledMode;
+
+ /**
+ * Create TestSatelliteService using the Executor specified for methods being called from
+ * the framework.
+ *
+ * @param executor The executor for the framework to use when executing satellite methods.
+ */
+ public TestSatelliteService(@NonNull Executor executor) {
+ super(executor);
+ mIsCommunicationAllowedInLocation = true;
+ mIsEnabled = false;
+ mIsProvisioned = false;
+ mIsSupported = true;
+ mModemState = SatelliteModemState.SATELLITE_MODEM_STATE_OFF;
+ mIsCellularModemEnabledMode = false;
+ }
+
+ /**
+ * Zero-argument constructor to prevent service binding exception.
+ */
+ public TestSatelliteService() {
+ this(Runnable::run);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (SatelliteService.SERVICE_INTERFACE.equals(intent.getAction())) {
+ logd("Remote service bound");
+ return getBinder();
+ }
+ logd("Local service bound");
+ return mBinder;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ logd("onCreate");
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ logd("onDestroy");
+ }
+
+ @Override
+ public void setSatelliteListener(@NonNull ISatelliteListener listener) {
+ logd("setSatelliteListener");
+ mRemoteListeners.put(listener.asBinder(), listener);
+ notifyRemoteServiceConnected();
+ }
+
+ @Override
+ public void requestSatelliteListeningEnabled(boolean enabled, int timeout,
+ @NonNull IIntegerConsumer errorCallback) {
+ logd("requestSatelliteListeningEnabled: mErrorCode=" + mErrorCode);
+
+ if (mLocalListener != null) {
+ runWithExecutor(() -> mLocalListener.onSatelliteListeningEnabled(enabled));
+ } else {
+ loge("requestSatelliteListeningEnabled: mLocalListener is null");
+ }
+
+ if (!verifySatelliteModemState(errorCallback)) {
+ return;
+ }
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+
+ if (enabled) {
+ updateSatelliteModemState(SatelliteModemState.SATELLITE_MODEM_STATE_LISTENING);
+ } else {
+ updateSatelliteModemState(SatelliteModemState.SATELLITE_MODEM_STATE_IDLE);
+ }
+ runWithExecutor(() -> errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+ }
+
+ @Override
+ public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
+ @NonNull IIntegerConsumer errorCallback) {
+ logd("requestSatelliteEnabled: mErrorCode=" + mErrorCode + " enable = " + enableSatellite);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+
+ if (enableSatellite) {
+ enableSatellite(errorCallback);
+ } else {
+ disableSatellite(errorCallback);
+ }
+ }
+
+ private void enableSatellite(@NonNull IIntegerConsumer errorCallback) {
+ mIsEnabled = true;
+ updateSatelliteModemState(SatelliteModemState.SATELLITE_MODEM_STATE_IDLE);
+ runWithExecutor(() -> errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+ }
+
+ private void disableSatellite(@NonNull IIntegerConsumer errorCallback) {
+ mIsEnabled = false;
+ updateSatelliteModemState(SatelliteModemState.SATELLITE_MODEM_STATE_OFF);
+ runWithExecutor(() -> errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+ }
+
+ @Override
+ public void requestIsSatelliteEnabled(@NonNull IIntegerConsumer errorCallback,
+ @NonNull IBooleanConsumer callback) {
+ logd("requestIsSatelliteEnabled: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+ runWithExecutor(() -> callback.accept(mIsEnabled));
+ }
+
+ @Override
+ public void requestIsSatelliteSupported(@NonNull IIntegerConsumer errorCallback,
+ @NonNull IBooleanConsumer callback) {
+ logd("requestIsSatelliteSupported");
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+ runWithExecutor(() -> callback.accept(mIsSupported));
+ }
+
+ @Override
+ public void requestSatelliteCapabilities(@NonNull IIntegerConsumer errorCallback,
+ @NonNull ISatelliteCapabilitiesConsumer callback) {
+ logd("requestSatelliteCapabilities: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+
+ SatelliteCapabilities capabilities = new SatelliteCapabilities();
+ capabilities.supportedRadioTechnologies = SUPPORTED_RADIO_TECHNOLOGIES;
+ capabilities.isPointingRequired = POINTING_TO_SATELLITE_REQUIRED;
+ capabilities.maxBytesPerOutgoingDatagram = MAX_BYTES_PER_DATAGRAM;
+ capabilities.antennaPositionKeys = ANTENNA_POSITION_KEYS;
+ capabilities.antennaPositionValues = ANTENNA_POSITION_VALUES;
+ runWithExecutor(() -> callback.accept(capabilities));
+ }
+
+ @Override
+ public void startSendingSatellitePointingInfo(@NonNull IIntegerConsumer errorCallback) {
+ logd("startSendingSatellitePointingInfo: mErrorCode=" + mErrorCode);
+ if (!verifySatelliteModemState(errorCallback)) {
+ if (mLocalListener != null) {
+ runWithExecutor(() -> mLocalListener.onStartSendingSatellitePointingInfo());
+ } else {
+ loge("startSendingSatellitePointingInfo: mLocalListener is null");
+ }
+ return;
+ }
+
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ } else {
+ runWithExecutor(() -> errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+ }
+
+ if (mLocalListener != null) {
+ runWithExecutor(() -> mLocalListener.onStartSendingSatellitePointingInfo());
+ } else {
+ loge("startSendingSatellitePointingInfo: mLocalListener is null");
+ }
+ }
+
+ @Override
+ public void stopSendingSatellitePointingInfo(@NonNull IIntegerConsumer errorCallback) {
+ logd("stopSendingSatellitePointingInfo: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ } else {
+ runWithExecutor(() -> errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+ }
+
+ if (mLocalListener != null) {
+ runWithExecutor(() -> mLocalListener.onStopSendingSatellitePointingInfo());
+ } else {
+ loge("stopSendingSatellitePointingInfo: mLocalListener is null");
+ }
+ }
+
+ @Override
+ public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData,
+ @NonNull IIntegerConsumer errorCallback) {
+ logd("provisionSatelliteService: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+ runWithExecutor(() -> errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+ updateSatelliteProvisionState(true);
+ }
+
+ @Override
+ public void deprovisionSatelliteService(@NonNull String token,
+ @NonNull IIntegerConsumer errorCallback) {
+ logd("deprovisionSatelliteService: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+ runWithExecutor(() -> errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+ updateSatelliteProvisionState(false);
+ }
+
+ @Override
+ public void requestIsSatelliteProvisioned(@NonNull IIntegerConsumer errorCallback,
+ @NonNull IBooleanConsumer callback) {
+ logd("requestIsSatelliteProvisioned: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+ runWithExecutor(() -> callback.accept(mIsProvisioned));
+ }
+
+ @Override
+ public void pollPendingSatelliteDatagrams(@NonNull IIntegerConsumer errorCallback) {
+ logd("pollPendingSatelliteDatagrams: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ } else {
+ runWithExecutor(() -> errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+ }
+
+ if (mLocalListener != null) {
+ runWithExecutor(() -> mLocalListener.onPollPendingSatelliteDatagrams());
+ } else {
+ loge("pollPendingDatagrams: mLocalListener is null");
+ }
+ }
+
+ @Override
+ public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isEmergency,
+ @NonNull IIntegerConsumer errorCallback) {
+ logd("sendDatagram: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ } else {
+ runWithExecutor(() -> errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS));
+ }
+
+ if (mLocalListener != null) {
+ runWithExecutor(() -> mLocalListener.onSendSatelliteDatagram(datagram, isEmergency));
+ } else {
+ loge("sendDatagram: mLocalListener is null");
+ }
+ }
+
+ @Override
+ public void requestSatelliteModemState(@NonNull IIntegerConsumer errorCallback,
+ @NonNull IIntegerConsumer callback) {
+ logd("requestSatelliteModemState: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+ runWithExecutor(() -> callback.accept(mModemState));
+ }
+
+ @Override
+ public void requestIsSatelliteCommunicationAllowedForCurrentLocation(
+ @NonNull IIntegerConsumer errorCallback, @NonNull IBooleanConsumer callback) {
+ logd("requestIsCommunicationAllowedForCurrentLocation: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+
+ if (mIsCommunicationAllowedInLocation) {
+ runWithExecutor(() -> callback.accept(true));
+ } else {
+ runWithExecutor(() -> callback.accept(false));
+ }
+ }
+
+ @Override
+ public void requestTimeForNextSatelliteVisibility(@NonNull IIntegerConsumer errorCallback,
+ @NonNull IIntegerConsumer callback) {
+ logd("requestTimeForNextSatelliteVisibility: mErrorCode=" + mErrorCode);
+ if (mErrorCode != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+ runWithExecutor(() -> errorCallback.accept(mErrorCode));
+ return;
+ }
+ runWithExecutor(() -> callback.accept(SATELLITE_ALWAYS_VISIBLE));
+ }
+
+ public void setLocalSatelliteListener(@NonNull ILocalSatelliteListener listener) {
+ logd("setLocalSatelliteListener: listener=" + listener);
+ mLocalListener = listener;
+ if (mShouldNotifyRemoteServiceConnected.get()) {
+ notifyRemoteServiceConnected();
+ }
+ }
+
+ public void setErrorCode(@SatelliteResult int errorCode) {
+ logd("setErrorCode: errorCode=" + errorCode);
+ mErrorCode = errorCode;
+ }
+
+ public void setSatelliteSupport(boolean supported) {
+ logd("setSatelliteSupport: supported=" + supported);
+ mIsSupported = supported;
+ }
+
+ public void sendOnSatelliteDatagramReceived(SatelliteDatagram datagram, int pendingCount) {
+ logd("sendOnSatelliteDatagramReceived");
+ mRemoteListeners.values().forEach(listener -> runWithExecutor(() ->
+ listener.onSatelliteDatagramReceived(datagram, pendingCount)));
+ }
+
+ public void sendOnPendingDatagrams() {
+ logd("sendOnPendingDatagrams");
+ mRemoteListeners.values().forEach(listener -> runWithExecutor(() ->
+ listener.onPendingDatagrams()));
+ }
+
+ public void sendOnSatellitePositionChanged(PointingInfo pointingInfo) {
+ logd("sendOnSatellitePositionChanged");
+ mRemoteListeners.values().forEach(listener -> runWithExecutor(() ->
+ listener.onSatellitePositionChanged(pointingInfo)));
+ }
+
+ /**
+ * Helper method to verify that the satellite modem is properly configured to receive
+ * requests.
+ *
+ * @param errorCallback The callback to notify of any errors preventing satellite requests.
+ * @return {@code true} if the satellite modem is configured to receive requests and
+ * {@code false} if it is not.
+ */
+ private boolean verifySatelliteModemState(@NonNull IIntegerConsumer errorCallback) {
+ if (!mIsSupported) {
+ runWithExecutor(() -> errorCallback.accept(
+ SatelliteResult.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED));
+ return false;
+ }
+ if (!mIsProvisioned) {
+ runWithExecutor(() -> errorCallback.accept(
+ SatelliteResult.SATELLITE_RESULT_SERVICE_NOT_PROVISIONED));
+ return false;
+ }
+ if (!mIsEnabled) {
+ runWithExecutor(() -> errorCallback.accept(
+ SatelliteResult.SATELLITE_RESULT_INVALID_MODEM_STATE));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Update the satellite modem state and notify listeners if it changed.
+ *
+ * @param modemState The {@link SatelliteModemState} to update.
+ */
+ private void updateSatelliteModemState(int modemState) {
+ if (modemState == mModemState) {
+ return;
+ }
+ if (mIsCellularModemEnabledMode
+ && modemState == SatelliteModemState.SATELLITE_MODEM_STATE_OFF) {
+ logd("Not updating the Modem state to Off as it is in CellularModemEnabledMode");
+ return;
+ }
+ mRemoteListeners.values().forEach(listener -> runWithExecutor(() ->
+ listener.onSatelliteModemStateChanged(modemState)));
+ mModemState = modemState;
+ }
+
+ /**
+ * Update the satellite provision state and notify listeners if it changed.
+ *
+ * @param isProvisioned {@code true} if the satellite is currently provisioned and
+ * {@code false} if it is not.
+ */
+ private void updateSatelliteProvisionState(boolean isProvisioned) {
+ logd("updateSatelliteProvisionState: isProvisioned=" + isProvisioned
+ + ", mIsProvisioned=" + mIsProvisioned);
+ if (isProvisioned == mIsProvisioned) {
+ return;
+ }
+ mIsProvisioned = isProvisioned;
+ logd("updateSatelliteProvisionState: mRemoteListeners.size=" + mRemoteListeners.size());
+ mRemoteListeners.values().forEach(listener -> runWithExecutor(() ->
+ listener.onSatelliteProvisionStateChanged(mIsProvisioned)));
+ }
+
+ /**
+ * Execute the given runnable using the executor that this service was created with.
+ *
+ * @param r A runnable that can throw an exception.
+ */
+ private void runWithExecutor(@NonNull FunctionalUtils.ThrowingRunnable r) {
+ mExecutor.execute(() -> Binder.withCleanCallingIdentity(r));
+ }
+
+ private void notifyRemoteServiceConnected() {
+ logd("notifyRemoteServiceConnected");
+ if (mLocalListener != null) {
+ runWithExecutor(() -> mLocalListener.onRemoteServiceConnected());
+ mShouldNotifyRemoteServiceConnected.set(false);
+ } else {
+ mShouldNotifyRemoteServiceConnected.set(true);
+ }
+ }
+
+ /**
+ * Log the message to the radio buffer with {@code DEBUG} priority.
+ *
+ * @param log The message to log.
+ */
+ private static void logd(@NonNull String log) {
+ Rlog.d(TAG, log);
+ }
+
+ /**
+ * Log with error attribute
+ *
+ * @param s is string log
+ */
+ protected void loge(@NonNull String s) {
+ Log.e(TAG, s);
+ }
+}
diff --git a/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteWrapper.java b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteWrapper.java
new file mode 100644
index 0000000..e4c2005
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteWrapper.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone.testapps.satellitetestapp;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.OutcomeReceiver;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.satellite.wrapper.NtnSignalStrengthCallbackWrapper;
+import android.telephony.satellite.wrapper.NtnSignalStrengthWrapper;
+import android.telephony.satellite.wrapper.SatelliteCapabilitiesCallbackWrapper;
+import android.telephony.satellite.wrapper.SatelliteManagerWrapper;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+
+/**
+ * Activity related to SatelliteControl APIs for satellite.
+ */
+public class TestSatelliteWrapper extends Activity {
+ private static final String TAG = "TestSatelliteWrapper";
+ ArrayList<String> mLogMessages = new ArrayList<>();
+ ArrayAdapter<String> mAdapter;
+
+ private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+ private SatelliteManagerWrapper mSatelliteManagerWrapper;
+ private NtnSignalStrengthCallback mNtnSignalStrengthCallback = null;
+ private SatelliteCapabilitiesCallbackWrapper mSatelliteCapabilitiesCallback;
+ private SubscriptionManager mSubscriptionManager;
+
+ private ListView mLogListView;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSatelliteManagerWrapper = SatelliteManagerWrapper.getInstance(this);
+ mSubscriptionManager = getSystemService(SubscriptionManager.class);
+
+ setContentView(R.layout.activity_TestSatelliteWrapper);
+ findViewById(R.id.requestNtnSignalStrength)
+ .setOnClickListener(this::requestNtnSignalStrength);
+ findViewById(R.id.registerForNtnSignalStrengthChanged)
+ .setOnClickListener(this::registerForNtnSignalStrengthChanged);
+ findViewById(R.id.unregisterForNtnSignalStrengthChanged)
+ .setOnClickListener(this::unregisterForNtnSignalStrengthChanged);
+ findViewById(R.id.isOnlyNonTerrestrialNetworkSubscription)
+ .setOnClickListener(this::isOnlyNonTerrestrialNetworkSubscription);
+ findViewById(R.id.registerForSatelliteCapabilitiesChanged)
+ .setOnClickListener(this::registerForCapabilitiesChanged);
+ findViewById(R.id.unregisterForSatelliteCapabilitiesChanged)
+ .setOnClickListener(this::unregisterForCapabilitiesChanged);
+ findViewById(R.id.Back).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(TestSatelliteWrapper.this, SatelliteTestApp.class));
+ }
+ });
+ findViewById(R.id.ClearLog).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ clearListView();
+ }
+ });
+
+ mLogListView = findViewById(R.id.logListView);
+ mAdapter = new ArrayAdapter<>(this, R.layout.log_textview, mLogMessages);
+ mLogListView.setAdapter(mAdapter);
+
+ addLogMessage("TestSatelliteWrapper.onCreate()");
+ }
+
+
+ private void clearListView() {
+ mLogMessages.clear();
+ mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ if (mSatelliteManagerWrapper != null) {
+ if (mNtnSignalStrengthCallback != null) {
+ Log.d(TAG, "unregisterForNtnSignalStrengthChanged()");
+ mSatelliteManagerWrapper.unregisterForNtnSignalStrengthChanged(
+ mNtnSignalStrengthCallback);
+ }
+ if (mSatelliteCapabilitiesCallback != null) {
+ Log.d(TAG, "unregisterForCapabilitiesChanged()");
+ mSatelliteManagerWrapper.unregisterForCapabilitiesChanged(
+ mSatelliteCapabilitiesCallback);
+ }
+ }
+ }
+
+ private void requestNtnSignalStrength(View view) {
+ addLogMessage("requestNtnSignalStrength");
+ Log.d(TAG, "requestNtnSignalStrength");
+ OutcomeReceiver<NtnSignalStrengthWrapper,
+ SatelliteManagerWrapper.SatelliteExceptionWrapper> receiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(NtnSignalStrengthWrapper level) {
+ if (level != null) {
+ addLogMessage("requestNtnSignalStrength level is " + level.getLevel());
+ }
+ }
+
+ @Override
+ public void onError(
+ SatelliteManagerWrapper.SatelliteExceptionWrapper exception) {
+ if (exception != null) {
+ String onError = "requestNtnSignalStrength exception: "
+ + translateResultCodeToString(exception.getErrorCode());
+ Log.d(TAG, onError);
+ addLogMessage(onError);
+ }
+ }
+ };
+
+ try {
+ mSatelliteManagerWrapper.requestNtnSignalStrength(mExecutor, receiver);
+ } catch (SecurityException ex) {
+ String errorMessage = "requestNtnSignalStrength: " + ex.getMessage();
+ Log.d(TAG, errorMessage);
+ addLogMessage(errorMessage);
+ }
+ }
+
+ private void registerForNtnSignalStrengthChanged(View view) {
+ addLogMessage("registerForNtnSignalStrengthChanged");
+ Log.d(TAG, "registerForNtnSignalStrengthChanged()");
+ if (mNtnSignalStrengthCallback == null) {
+ Log.d(TAG, "create new NtnSignalStrengthCallback instance.");
+ mNtnSignalStrengthCallback = new NtnSignalStrengthCallback();
+ }
+
+ try {
+ mSatelliteManagerWrapper.registerForNtnSignalStrengthChanged(mExecutor,
+ mNtnSignalStrengthCallback);
+ } catch (Exception ex) {
+ String errorMessage = "registerForNtnSignalStrengthChanged: " + ex.getMessage();
+ Log.d(TAG, errorMessage);
+ addLogMessage(errorMessage);
+ mNtnSignalStrengthCallback = null;
+ }
+ }
+
+ private void unregisterForNtnSignalStrengthChanged(View view) {
+ addLogMessage("unregisterForNtnSignalStrengthChanged");
+ Log.d(TAG, "unregisterForNtnSignalStrengthChanged()");
+ if (mNtnSignalStrengthCallback != null) {
+ mSatelliteManagerWrapper.unregisterForNtnSignalStrengthChanged(
+ mNtnSignalStrengthCallback);
+ mNtnSignalStrengthCallback = null;
+ addLogMessage("mNtnSignalStrengthCallback was unregistered");
+ } else {
+ addLogMessage("mNtnSignalStrengthCallback is null, ignored.");
+ }
+ }
+
+ private void isOnlyNonTerrestrialNetworkSubscription(View view) {
+ addLogMessage("isOnlyNonTerrestrialNetworkSubscription");
+ Log.d(TAG, "isOnlyNonTerrestrialNetworkSubscription()");
+ List<SubscriptionInfo> infoList = mSubscriptionManager.getAvailableSubscriptionInfoList();
+ List<Integer> subIdList = infoList.stream()
+ .map(SubscriptionInfo::getSubscriptionId)
+ .toList();
+
+ Map<Integer, Boolean> resultMap = subIdList.stream().collect(
+ Collectors.toMap(
+ id -> id,
+ id -> {
+ boolean result = mSatelliteManagerWrapper
+ .isOnlyNonTerrestrialNetworkSubscription(id);
+ addLogMessage("SatelliteManagerWrapper"
+ + ".isOnlyNonTerrestrialNetworkSubscription(" + id + ")");
+ return result;
+ }
+ ));
+
+ for (Map.Entry<Integer, Boolean> entry : resultMap.entrySet()) {
+ int subId = entry.getKey();
+ boolean result = entry.getValue();
+ addLogMessage("Subscription ID: " + subId + ", Result: " + result);
+ }
+ }
+
+ private void registerForCapabilitiesChanged(View view) {
+ addLogMessage("registerForCapabilitiesChanged");
+ 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(
+ @NonNull NtnSignalStrengthWrapper ntnSignalStrength) {
+ String message = "Received NTN SignalStrength : " + ntnSignalStrength.getLevel();
+ Log.d(TAG, message);
+ runOnUiThread(() -> addLogMessage(message));
+ }
+ }
+
+ private String translateResultCodeToString(
+ @SatelliteManagerWrapper.SatelliteResult int result) {
+ switch (result) {
+ case SatelliteManagerWrapper.SATELLITE_RESULT_SUCCESS:
+ return "SATELLITE_RESULT_SUCCESS";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_ERROR:
+ return "SATELLITE_RESULT_ERROR";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_SERVER_ERROR:
+ return "SATELLITE_RESULT_SERVER_ERROR";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_SERVICE_ERROR:
+ return "SATELLITE_RESULT_SERVICE_ERROR";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_MODEM_ERROR:
+ return "SATELLITE_RESULT_MODEM_ERROR";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_NETWORK_ERROR:
+ return "SATELLITE_RESULT_NETWORK_ERROR";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_INVALID_TELEPHONY_STATE:
+ return "SATELLITE_RESULT_INVALID_TELEPHONY_STATE";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_INVALID_MODEM_STATE:
+ return "SATELLITE_RESULT_INVALID_MODEM_STATE";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_INVALID_ARGUMENTS:
+ return "SATELLITE_RESULT_INVALID_ARGUMENTS";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_REQUEST_FAILED:
+ return "SATELLITE_RESULT_REQUEST_FAILED";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_RADIO_NOT_AVAILABLE:
+ return "SATELLITE_RESULT_RADIO_NOT_AVAILABLE";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED:
+ return "SATELLITE_RESULT_REQUEST_NOT_SUPPORTED";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_NO_RESOURCES:
+ return "SATELLITE_RESULT_NO_RESOURCES";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_SERVICE_NOT_PROVISIONED:
+ return "SATELLITE_RESULT_SERVICE_NOT_PROVISIONED";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS:
+ return "SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_REQUEST_ABORTED:
+ return "SATELLITE_RESULT_REQUEST_ABORTED";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_ACCESS_BARRED:
+ return "SATELLITE_RESULT_ACCESS_BARRED";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_NETWORK_TIMEOUT:
+ return "SATELLITE_RESULT_NETWORK_TIMEOUT";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_NOT_REACHABLE:
+ return "SATELLITE_RESULT_NOT_REACHABLE";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_NOT_AUTHORIZED:
+ return "SATELLITE_RESULT_NOT_AUTHORIZED";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_NOT_SUPPORTED:
+ return "SATELLITE_RESULT_NOT_SUPPORTED";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_REQUEST_IN_PROGRESS:
+ return "SATELLITE_RESULT_REQUEST_IN_PROGRESS";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_MODEM_BUSY:
+ return "SATELLITE_RESULT_MODEM_BUSY";
+ case SatelliteManagerWrapper.SATELLITE_RESULT_ILLEGAL_STATE:
+ return "SATELLITE_RESULT_ILLEGAL_STATE";
+ default:
+ return "INVALID CODE: " + result;
+ }
+ }
+
+ private void addLogMessage(String message) {
+ mLogMessages.add(message);
+ mAdapter.notifyDataSetChanged();
+ mLogListView.setSelection(mAdapter.getCount() - 1);
+ }
+}
diff --git a/tests/Android.bp b/tests/Android.bp
index 08cac05..6914839 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -38,6 +38,7 @@
instrumentation_for: "TeleService",
static_libs: [
+ "frameworks-base-testutils",
"androidx.test.core",
"androidx.test.espresso.core",
"androidx.test.ext.junit",
@@ -45,9 +46,14 @@
"mockito-target-minus-junit4",
"telephony-common-testing",
"testng",
- "truth-prebuilt",
+ "truth",
"testables",
"platform-compat-test-rules",
+ "flag-junit",
+ "telephony_flags_core_java_lib",
+ "satellite-s2storage-rw",
+ "satellite-s2storage-testutils",
+ "s2-geometry-library-java",
],
test_suites: [
diff --git a/tests/src/com/android/phone/CarrierConfigLoaderTest.java b/tests/src/com/android/phone/CarrierConfigLoaderTest.java
index b6f8ed8..f4197d9 100644
--- a/tests/src/com/android/phone/CarrierConfigLoaderTest.java
+++ b/tests/src/com/android/phone/CarrierConfigLoaderTest.java
@@ -21,6 +21,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -30,6 +31,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.compat.testing.PlatformCompatChangeRule;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
@@ -40,8 +42,10 @@
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.PermissionEnforcer;
import android.os.PersistableBundle;
import android.os.UserHandle;
+import android.os.test.FakePermissionEnforcer;
import android.service.carrier.CarrierIdentifier;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
@@ -54,12 +58,17 @@
import com.android.TelephonyTestBase;
import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.subscription.SubscriptionManagerService;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
@@ -74,10 +83,14 @@
*/
@RunWith(AndroidJUnit4.class)
public class CarrierConfigLoaderTest extends TelephonyTestBase {
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+ private static final String TAG = CarrierConfigLoaderTest.class.getSimpleName();
private static final int DEFAULT_PHONE_ID = 0;
private static final int DEFAULT_SUB_ID = SubscriptionManager.getDefaultSubscriptionId();
private static final String PLATFORM_CARRIER_CONFIG_PACKAGE = "com.android.carrierconfig";
+ private static final String PLATFORM_CARRIER_CONFIG_FEATURE = "com.android.carrierconfig";
private static final long PLATFORM_CARRIER_CONFIG_PACKAGE_VERSION_CODE = 1;
private static final String CARRIER_CONFIG_EXAMPLE_KEY =
CarrierConfigManager.KEY_CARRIER_USSD_METHOD_INT;
@@ -90,6 +103,7 @@
@Mock SubscriptionManagerService mSubscriptionManagerService;
@Mock SharedPreferences mSharedPreferences;
@Mock TelephonyRegistryManager mTelephonyRegistryManager;
+ @Mock FeatureFlags mFeatureFlags;
private TelephonyManager mTelephonyManager;
private CarrierConfigLoader mCarrierConfigLoader;
@@ -97,10 +111,17 @@
private HandlerThread mHandlerThread;
private TestableLooper mTestableLooper;
+ // The AIDL stub will use PermissionEnforcer to check permission from the caller.
+ private FakePermissionEnforcer mFakePermissionEnforcer = new FakePermissionEnforcer();
+
@Before
public void setUp() throws Exception {
super.setUp();
MockitoAnnotations.initMocks(this);
+ doReturn(Context.PERMISSION_ENFORCER_SERVICE).when(mContext).getSystemServiceName(
+ eq(PermissionEnforcer.class));
+ doReturn(mFakePermissionEnforcer).when(mContext).getSystemService(
+ eq(Context.PERMISSION_ENFORCER_SERVICE));
replaceInstance(SubscriptionManagerService.class, "sInstance", null,
mSubscriptionManagerService);
@@ -109,6 +130,8 @@
doReturn(Build.FINGERPRINT).when(mSharedPreferences).getString(eq("build_fingerprint"),
any());
doReturn(mPackageManager).when(mContext).getPackageManager();
+ doReturn(new String[]{TAG}).when(mPackageManager).getPackagesForUid(anyInt());
+
doReturn(mResources).when(mContext).getResources();
doReturn(InstrumentationRegistry.getTargetContext().getFilesDir()).when(
mContext).getFilesDir();
@@ -132,7 +155,8 @@
mHandlerThread.start();
mTestableLooper = new TestableLooper(mHandlerThread.getLooper());
- mCarrierConfigLoader = new CarrierConfigLoader(mContext, mTestableLooper.getLooper());
+ mCarrierConfigLoader = new CarrierConfigLoader(mContext, mTestableLooper.getLooper(),
+ mFeatureFlags);
mHandler = mCarrierConfigLoader.getHandler();
// Clear all configs to have the same starting point.
@@ -142,6 +166,9 @@
@After
public void tearDown() throws Exception {
mContext.revokeAllPermissions();
+ mFakePermissionEnforcer.revoke(android.Manifest.permission.DUMP);
+ mFakePermissionEnforcer.revoke(android.Manifest.permission.MODIFY_PHONE_STATE);
+ mFakePermissionEnforcer.revoke(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
mTestableLooper.destroy();
mHandlerThread.quit();
super.tearDown();
@@ -164,7 +191,7 @@
*/
@Test
public void testUpdateConfigForPhoneId_invalidPhoneId() throws Exception {
- mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ mFakePermissionEnforcer.grant(android.Manifest.permission.MODIFY_PHONE_STATE);
assertThrows(IllegalArgumentException.class,
() -> mCarrierConfigLoader.updateConfigForPhoneId(
@@ -182,7 +209,7 @@
if (!SubscriptionManager.isValidPhoneId(SubscriptionManager.getPhoneId(DEFAULT_SUB_ID))) {
return;
}
- mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ mFakePermissionEnforcer.grant(android.Manifest.permission.MODIFY_PHONE_STATE);
doNothing().when(mContext).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class));
// Prepare a cached config to fetch from xml
@@ -215,7 +242,7 @@
if (!SubscriptionManager.isValidPhoneId(SubscriptionManager.getPhoneId(DEFAULT_SUB_ID))) {
return;
}
- mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ mFakePermissionEnforcer.grant(android.Manifest.permission.MODIFY_PHONE_STATE);
// Prepare to make sure we can save the config into the XML file which used as cache
doReturn(PLATFORM_CARRIER_CONFIG_PACKAGE).when(mTelephonyManager)
@@ -252,7 +279,7 @@
*/
@Test
public void testOverrideConfig_invalidSubId() throws Exception {
- mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ mFakePermissionEnforcer.grant(android.Manifest.permission.MODIFY_PHONE_STATE);
assertThrows(IllegalArgumentException.class, () -> mCarrierConfigLoader.overrideConfig(
SubscriptionManager.INVALID_SUBSCRIPTION_ID, new PersistableBundle(), false));
@@ -267,7 +294,7 @@
if (!SubscriptionManager.isValidPhoneId(SubscriptionManager.getPhoneId(DEFAULT_SUB_ID))) {
return;
}
- mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ mFakePermissionEnforcer.grant(android.Manifest.permission.MODIFY_PHONE_STATE);
mCarrierConfigLoader.overrideConfig(DEFAULT_SUB_ID, null /*overrides*/,
false/*persistent*/);
@@ -288,7 +315,7 @@
if (!SubscriptionManager.isValidPhoneId(SubscriptionManager.getPhoneId(DEFAULT_SUB_ID))) {
return;
}
- mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ mFakePermissionEnforcer.grant(android.Manifest.permission.MODIFY_PHONE_STATE);
PersistableBundle config = getTestConfig();
mCarrierConfigLoader.overrideConfig(DEFAULT_SUB_ID, config /*overrides*/,
@@ -308,7 +335,7 @@
*/
@Test
public void testNotifyConfigChangedForSubId_invalidSubId() throws Exception {
- mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ mFakePermissionEnforcer.grant(STUB_PERMISSION_ENABLE_ALL);
assertThrows(IllegalArgumentException.class,
() -> mCarrierConfigLoader.notifyConfigChangedForSubId(
@@ -346,7 +373,7 @@
*/
@Test
public void testGetDefaultCarrierServicePackageName_withPermission() {
- mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ mFakePermissionEnforcer.grant(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
assertThat(mCarrierConfigLoader.getDefaultCarrierServicePackageName())
.isEqualTo(PLATFORM_CARRIER_CONFIG_PACKAGE);
@@ -400,6 +427,34 @@
assertThat(dumpContent).doesNotContain("Permission Denial:");
}
+ @Test
+ @EnableCompatChanges({TelephonyManager.ENABLE_FEATURE_MAPPING})
+ public void testGetConfigForSubIdWithFeature_withTelephonyFeatureMapping() {
+ doNothing().when(mContext).enforcePermission(
+ eq(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE),
+ anyInt(), anyInt(), anyString());
+
+ doReturn(true).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
+ doReturn(false).when(mPackageManager).hasSystemFeature(
+ eq(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
+ // Not defined required feature, expect UnsupportedOperationException
+ assertThrows(UnsupportedOperationException.class,
+ () -> mCarrierConfigLoader.getConfigForSubIdWithFeature(DEFAULT_SUB_ID,
+ PLATFORM_CARRIER_CONFIG_PACKAGE, PLATFORM_CARRIER_CONFIG_FEATURE));
+
+ doReturn(true).when(mPackageManager).hasSystemFeature(
+ eq(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
+ // Defined required feature, not expect UnsupportedOperationException
+ try {
+ mCarrierConfigLoader.getConfigForSubIdWithFeature(DEFAULT_SUB_ID,
+ PLATFORM_CARRIER_CONFIG_PACKAGE, PLATFORM_CARRIER_CONFIG_FEATURE);
+ } catch (UnsupportedOperationException e) {
+ fail("not expected UnsupportedOperationException");
+ }
+ }
+
private static PersistableBundle getTestConfig() {
PersistableBundle config = new PersistableBundle();
config.putInt(CARRIER_CONFIG_EXAMPLE_KEY, CARRIER_CONFIG_EXAMPLE_VALUE);
@@ -417,7 +472,7 @@
@Test
public void testMultiSimConfigChanged() throws Exception {
replaceInstance(TelephonyManager.class, "sInstance", null, mTelephonyManager);
- mContext.grantPermission(STUB_PERMISSION_ENABLE_ALL);
+ mFakePermissionEnforcer.grant(android.Manifest.permission.MODIFY_PHONE_STATE);
// Changed from 1 to 2.
doReturn(2).when(mTelephonyManager).getActiveModemCount();
diff --git a/tests/src/com/android/phone/DiagnosticDataCollectorTest.java b/tests/src/com/android/phone/DiagnosticDataCollectorTest.java
index e0d89bc..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/NotificationMgrTest.java b/tests/src/com/android/phone/NotificationMgrTest.java
index e009446..98c6a4a 100644
--- a/tests/src/com/android/phone/NotificationMgrTest.java
+++ b/tests/src/com/android/phone/NotificationMgrTest.java
@@ -631,6 +631,7 @@
MOBILE_NETWORK_SELECTION_PACKAGE);
when(mApp.getString(R.string.mobile_network_settings_class)).thenReturn(
MOBILE_NETWORK_SELECTION_CLASS);
+ when(mSubscriptionManager.isActiveSubId(anyInt())).thenReturn(true);
}
private void moveTimeForward(long seconds) {
diff --git a/tests/src/com/android/phone/PhoneInterfaceManagerTest.java b/tests/src/com/android/phone/PhoneInterfaceManagerTest.java
index e702279..df1091f 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;
@@ -44,13 +49,20 @@
import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.subscription.SubscriptionManagerService;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
import java.util.Locale;
/**
@@ -58,33 +70,53 @@
*/
@RunWith(AndroidJUnit4.class)
public class PhoneInterfaceManagerTest extends TelephonyTestBase {
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
private PhoneInterfaceManager mPhoneInterfaceManager;
private SharedPreferences mSharedPreferences;
private IIntegerConsumer mIIntegerConsumer;
+ private static final String sDebugPackageName =
+ PhoneInterfaceManagerTest.class.getPackageName();
@Mock
PhoneGlobals mPhoneGlobals;
@Mock
Phone mPhone;
-
+ @Mock
+ FeatureFlags mFeatureFlags;
+ @Mock
+ PackageManager mPackageManager;
@Mock
private SubscriptionManagerService mSubscriptionManagerService;
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
@UiThreadTest
public void setUp() throws Exception {
super.setUp();
+ doReturn(sDebugPackageName).when(mPhoneGlobals).getOpPackageName();
+
// Note that PhoneInterfaceManager is a singleton. Calling init gives us a handle to the
// global singleton, but the context that is passed in is unused if the phone app is already
// alive on a test devices. You must use the spy to mock behavior. Mocks stemming from the
// passed context will remain unused.
- mPhoneInterfaceManager = spy(PhoneInterfaceManager.init(mPhoneGlobals));
+ mPhoneInterfaceManager = spy(PhoneInterfaceManager.init(mPhoneGlobals, mFeatureFlags));
doReturn(mSubscriptionManagerService).when(mPhoneInterfaceManager)
.getSubscriptionManagerService();
TelephonyManager.setupISubForTest(mSubscriptionManagerService);
mSharedPreferences = mPhoneInterfaceManager.getSharedPreferences();
mSharedPreferences.edit().remove(Phone.PREF_NULL_CIPHER_AND_INTEGRITY_ENABLED).commit();
+ mSharedPreferences.edit().remove(Phone.PREF_NULL_CIPHER_NOTIFICATIONS_ENABLED).commit();
mIIntegerConsumer = mock(IIntegerConsumer.class);
+
+ // In order not to affect the existing implementation, define a telephony features
+ // and disabled enforce_telephony_feature_mapping_for_public_apis feature flag
+ mPhoneInterfaceManager.setFeatureFlags(mFeatureFlags);
+ doReturn(false).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
+ mPhoneInterfaceManager.setPackageManager(mPackageManager);
+ doReturn(true).when(mPackageManager).hasSystemFeature(anyString());
}
@Test
@@ -261,6 +293,108 @@
mPhoneInterfaceManager).getDefaultPhone();
}
+ @Test
+ public void setEnableNullCipherNotifications_allReqsMet_successfullyEnabled() {
+ setModemSupportsNullCipherNotification(true);
+ doNothing().when(mPhoneInterfaceManager).enforceModifyPermission();
+ doReturn(202).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+ assertFalse(mSharedPreferences.contains(Phone.PREF_NULL_CIPHER_NOTIFICATIONS_ENABLED));
+
+ mPhoneInterfaceManager.setEnableNullCipherNotifications(true);
+
+ assertTrue(
+ mSharedPreferences.getBoolean(Phone.PREF_NULL_CIPHER_NOTIFICATIONS_ENABLED, false));
+ }
+
+ @Test
+ public void setEnableNullCipherNotifications_allReqsMet_successfullyDisabled() {
+ setModemSupportsNullCipherNotification(true);
+ doNothing().when(mPhoneInterfaceManager).enforceModifyPermission();
+ doReturn(202).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+ assertFalse(mSharedPreferences.contains(Phone.PREF_NULL_CIPHER_NOTIFICATIONS_ENABLED));
+
+ mPhoneInterfaceManager.setEnableNullCipherNotifications(false);
+
+ assertFalse(
+ mSharedPreferences.getBoolean(Phone.PREF_NULL_CIPHER_NOTIFICATIONS_ENABLED, true));
+ }
+
+ @Test
+ public void setEnableNullCipherNotifications_lackingNecessaryHal_throwsException() {
+ setModemSupportsNullCipherNotification(true);
+ doNothing().when(mPhoneInterfaceManager).enforceModifyPermission();
+ doReturn(102).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+
+ assertThrows(UnsupportedOperationException.class,
+ () -> mPhoneInterfaceManager.setEnableNullCipherNotifications(true));
+ }
+
+ @Test
+ public void setEnableNullCipherNotifications_lackingModemSupport_throwsException() {
+ setModemSupportsNullCipherNotification(false);
+ doNothing().when(mPhoneInterfaceManager).enforceModifyPermission();
+ doReturn(202).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+
+ assertThrows(UnsupportedOperationException.class,
+ () -> mPhoneInterfaceManager.setEnableNullCipherNotifications(true));
+ }
+
+ @Test
+ public void setEnableNullCipherNotifications_lackingPermissions_throwsException() {
+ setModemSupportsNullCipherNotification(true);
+ doReturn(202).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+ doThrow(SecurityException.class).when(mPhoneInterfaceManager).enforceModifyPermission();
+
+ assertThrows(SecurityException.class, () ->
+ mPhoneInterfaceManager.setEnableNullCipherNotifications(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.
*/
@@ -283,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/phone/slice/SlicePurchaseControllerTest.java b/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
index ca67d63..5637c3a 100644
--- a/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
+++ b/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
@@ -62,6 +62,7 @@
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.data.DataSettingsManager;
+import com.android.internal.telephony.flags.FeatureFlags;
import org.junit.Before;
import org.junit.Test;
@@ -92,6 +93,7 @@
private static final long THROTTLE_TIMEOUT = 4000;
@Mock Phone mPhone;
+ @Mock FeatureFlags mFeatureFlags;
@Mock CarrierConfigManager mCarrierConfigManager;
@Mock CommandsInterface mCommandsInterface;
@Mock ServiceState mServiceState;
@@ -153,7 +155,7 @@
// create a spy to mock final PendingIntent methods
SlicePurchaseController slicePurchaseController =
- new SlicePurchaseController(mPhone, mHandler.getLooper());
+ new SlicePurchaseController(mPhone, mFeatureFlags, mHandler.getLooper());
mSlicePurchaseController = spy(slicePurchaseController);
doReturn(null).when(mSlicePurchaseController).createPendingIntent(
anyString(), anyInt(), anyBoolean());
@@ -644,6 +646,7 @@
@Test
public void testPurchasePremiumCapabilityResultNotificationsDisabled() {
+ doReturn(true).when(mFeatureFlags).slicingAdditionalErrorCodes();
sendValidPurchaseRequest();
// broadcast NOTIFICATIONS_DISABLED response from slice purchase application
diff --git a/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java b/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java
index 969622a..e5f7fd3 100644
--- a/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java
+++ b/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java
@@ -22,6 +22,10 @@
import static junit.framework.Assert.assertNotNull;
import static junit.framework.TestCase.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -33,16 +37,16 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.TelephonyTestBase;
+import com.android.internal.telephony.CallFailCause;
import com.android.internal.telephony.GsmCdmaPhone;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
-import com.android.phone.common.R;
+import com.android.phone.R;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.Mockito;
import java.util.Locale;
@@ -60,11 +64,18 @@
@Mock
private GsmCdmaPhone mMockPhone;
+ private final FlagsAdapter mFeatureFlags = new FlagsAdapter(){
+ @Override
+ public boolean doNotOverridePreciseLabel() {
+ return true;
+ }
+ };
+
@Before
public void setUp() throws Exception {
super.setUp();
// objects that call static getInstance()
- mMockPhone = Mockito.mock(GsmCdmaPhone.class);
+ mMockPhone = mock(GsmCdmaPhone.class);
mContext = InstrumentationRegistry.getTargetContext();
// set mocks
setSinglePhone();
@@ -91,7 +102,7 @@
@Test
public void testDefaultDisconnectCauseBehaviorForCauseNotInCarrierBusyToneArray() {
android.telecom.DisconnectCause tcCause = DisconnectCauseUtil.toTelecomDisconnectCause(
- DisconnectCause.ERROR_UNSPECIFIED, EMPTY_STRING, PHONE_ID);
+ DisconnectCause.ERROR_UNSPECIFIED, -1, EMPTY_STRING, PHONE_ID, null, mFeatureFlags);
// CODE
assertEquals(android.telecom.DisconnectCause.ERROR, tcCause.getCode());
// LABEL
@@ -101,29 +112,143 @@
}
/**
- * Simulate a Carrier classifying the DisconnectCause.ERROR_UNSPECIFIED as a
- * DisconnectCause.BUSY. The code, label, and tone should match DisconnectCause.BUSY.
+ * verify that if a precise label is given Telephony, the label is not overridden by Telecom
*/
@Test
- public void testCarrierSetDisconnectCauseInBusyToneArray() {
- int[] carrierBusyArr = {DisconnectCause.BUSY, DisconnectCause.ERROR_UNSPECIFIED};
+ public void testDefaultPhoneConfig_NoPreciseLabelGiven() {
+ android.telecom.DisconnectCause tcCause =
+ DisconnectCauseUtil.toTelecomDisconnectCause(DisconnectCause.BUSY,
+ -1 /* precise label is NOT given */,
+ EMPTY_STRING, PHONE_ID, null /* carrier config is NOT set */,
+ mFeatureFlags);
+ assertBusyCauseWithTargetLabel(R.string.callFailed_userBusy, tcCause);
+ }
+
+ /**
+ * verify that if a precise label is given Telephony, the label is not overridden by Telecom
+ */
+ @Test
+ public void testDefaultPhoneConfig_PreciseLabelProvided() {
+ android.telecom.DisconnectCause tcCause =
+ DisconnectCauseUtil.toTelecomDisconnectCause(DisconnectCause.BUSY,
+ CallFailCause.USER_BUSY /* Telephony defined a precise label */,
+ EMPTY_STRING, PHONE_ID, null /* carrier config is NOT set */,
+ mFeatureFlags);
+ // Note: The precise label should not be overridden even though the carrier defined
+ // the cause to play a busy tone
+ assertBusyCauseWithTargetLabel(R.string.clh_callFailed_user_busy_txt, tcCause);
+ }
+
+ /**
+ * special case: The Carrier has re-defined a disconnect code that should play a busy tone.
+ * Thus, the code, label, and tone should be remapped.
+ * <p>
+ * <p>
+ * Verify that if the disconnect cause is in the carrier busy tone array that the expected
+ * label, tone, and code are returned.
+ */
+ @Test
+ public void testCarrierSetBusyToneArray_NoPreciseLabelGiven() {
+ android.telecom.DisconnectCause tcCause =
+ DisconnectCauseUtil.toTelecomDisconnectCause(
+ DisconnectCause.BUSY, -1 /* precise label is NOT given */,
+ EMPTY_STRING, PHONE_ID, null, getBundleWithBusyToneArray(), mFeatureFlags);
+
+ assertBusyCauseWithTargetLabel(R.string.callFailed_userBusy, tcCause);
+ }
+
+ /**
+ * special case: The Carrier has re-defined a disconnect code that should play a busy tone.
+ * Thus, the code, label, and tone should be remapped.
+ * <p>
+ * <p>
+ * Verify that if the disconnect cause is in the carrier busy tone array and the Telephony
+ * stack has provided a precise label, the label is not overridden.
+ */
+ @Test
+ public void testCarrierSetBusyToneArray_PreciseLabelProvided() {
+ android.telecom.DisconnectCause tcCause =
+ DisconnectCauseUtil.toTelecomDisconnectCause(DisconnectCause.BUSY,
+ CallFailCause.USER_BUSY /* Telephony defined a precise label */,
+ EMPTY_STRING, PHONE_ID, null, getBundleWithBusyToneArray(), mFeatureFlags);
+ // Note: The precise label should not be overridden even though the carrier defined
+ // the cause to play a busy tone
+ assertBusyCauseWithTargetLabel(R.string.clh_callFailed_user_busy_txt, tcCause);
+ }
+
+ /**
+ * Ensure the helper doesCarrierClassifyDisconnectCauseAsBusyCause does not hit a NPE if a
+ * NULL carrier config is passed in.
+ */
+ @Test
+ public void testDoesCarrierClassifyDisconnectCauseAsBusyCause_nullConfig() {
+ assertFalse(DisconnectCauseUtil.doesCarrierClassifyDisconnectCauseAsBusyCause(-1, null));
+ }
+
+ /**
+ * Ensure the helper doesCarrierClassifyDisconnectCauseAsBusyCause does not hit a NPE if an
+ * EMPTY carrier config is passed in.
+ */
+ @Test
+ public void testDoesCarrierClassifyDisconnectCauseAsBusyCause_ConfigDoesNotDefineArray() {
PersistableBundle config = new PersistableBundle();
+ assertFalse(DisconnectCauseUtil.doesCarrierClassifyDisconnectCauseAsBusyCause(-1, config));
+ }
+
+ /**
+ * Ensure the helper doesCarrierClassifyDisconnectCauseAsBusyCause does not hit a NPE if an
+ * EMPTY array is defined for KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY.
+ */
+ @Test
+ public void testDoesCarrierClassifyDisconnectCauseAsBusyCause_ConfigHasEmptyArray() {
+ PersistableBundle config = new PersistableBundle();
+ int[] carrierBusyArr = {}; // NOTE: This is intentionally let empty
config.putIntArray(
CarrierConfigManager.KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
carrierBusyArr);
- android.telecom.DisconnectCause tcCause =
- DisconnectCauseUtil.toTelecomDisconnectCause(
- DisconnectCause.ERROR_UNSPECIFIED, -1,
- EMPTY_STRING, PHONE_ID, null, config);
+ assertFalse(DisconnectCauseUtil.doesCarrierClassifyDisconnectCauseAsBusyCause(-1, config));
+ }
- // CODE
- assertEquals(android.telecom.DisconnectCause.BUSY, tcCause.getCode());
- // LABEL
- safeAssertLabel(R.string.callFailed_userBusy, tcCause);
- // TONE
- assertEquals(TONE_SUP_BUSY, tcCause.getTone());
+ /**
+ * Ensure {@link DisconnectCauseUtil#doesCarrierClassifyDisconnectCauseAsBusyCause} returns
+ * FALSE is the passed in disconnect cause is NOT the busy tone array
+ */
+ @Test
+ public void testDoesCarrierClassifyDisconnectCauseAsBusyCause_ConfigHasBusyToneButNotMatch() {
+ assertFalse(DisconnectCauseUtil.doesCarrierClassifyDisconnectCauseAsBusyCause(-1,
+ getBundleWithBusyToneArray()));
+ }
+
+ /**
+ * Ensure {@link DisconnectCauseUtil#doesCarrierClassifyDisconnectCauseAsBusyCause} returns
+ * TRUE if the disconnect cause is defined in the busy tone array (by the Carrier)
+ */
+ @Test
+ public void testDoesCarrierClassifyDisconnectCauseAsBusyCause_ConfigHasBusyTone() {
+ assertTrue(DisconnectCauseUtil.doesCarrierClassifyDisconnectCauseAsBusyCause(
+ DisconnectCause.BUSY, getBundleWithBusyToneArray()));
+ }
+
+ private void assertBusyCauseWithTargetLabel(Integer targetLabel,
+ android.telecom.DisconnectCause disconnectCause) {
+ // CODE: Describes the cause of a disconnected call
+ assertEquals(android.telecom.DisconnectCause.BUSY, disconnectCause.getCode());
+ // LABEL: This is the label that the user sees
+ safeAssertLabel(targetLabel, disconnectCause);
+ // TONE: This is the DTMF tone being played to the user
+ assertEquals(TONE_SUP_BUSY, disconnectCause.getTone());
+ }
+
+ private PersistableBundle getBundleWithBusyToneArray() {
+ int[] carrierBusyArr = {DisconnectCause.BUSY};
+ PersistableBundle config = new PersistableBundle();
+
+ config.putIntArray(
+ CarrierConfigManager.KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
+ carrierBusyArr);
+ return config;
}
private void setSinglePhone() throws Exception {
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
index 0bfcb5c..129b6f4 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
@@ -16,6 +16,7 @@
package com.android.services.telephony;
+import static android.telecom.Connection.PROPERTY_WIFI;
import static android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE;
import static android.telephony.DisconnectCause.EMERGENCY_TEMP_FAILURE;
import static android.telephony.DisconnectCause.ERROR_UNSPECIFIED;
@@ -59,6 +60,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.telecom.Conference;
import android.telecom.Conferenceable;
import android.telecom.ConnectionRequest;
@@ -102,6 +104,7 @@
import com.android.internal.telephony.emergency.EmergencyStateTracker;
import com.android.internal.telephony.emergency.RadioOnHelper;
import com.android.internal.telephony.emergency.RadioOnStateListener;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.gsm.SuppServiceNotification;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.satellite.SatelliteController;
@@ -109,6 +112,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -132,6 +136,7 @@
@RunWith(AndroidJUnit4.class)
public class TelephonyConnectionServiceTest extends TelephonyTestBase {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String NORMAL_ROUTED_EMERGENCY_NUMBER = "110";
private static final String EMERGENCY_ROUTED_EMERGENCY_NUMBER = "911";
private static final EmergencyNumber MOCK_NORMAL_NUMBER = new EmergencyNumber(
@@ -298,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))
@@ -312,6 +317,9 @@
replaceInstance(TelephonyConnectionService.class,
"mSatelliteController", mTestConnectionService, mSatelliteController);
mBinderStub = (IConnectionService.Stub) mTestConnectionService.onBind(null);
+ mSetFlagsRule.disableFlags(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG);
+ mSetFlagsRule.enableFlags(Flags.FLAG_DO_NOT_OVERRIDE_PRECISE_LABEL);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CALL_EXTRA_FOR_NON_HOLD_SUPPORTED_CARRIERS);
}
@After
@@ -1328,7 +1336,7 @@
// This shouldn't happen
fail();
}
- verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), any());
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
}
/**
@@ -1440,7 +1448,107 @@
// This shouldn't happen
fail();
}
- verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), any());
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
+ }
+
+ /**
+ * Test that the TelephonyConnectionService successfully turns radio on before placing the
+ * call when radio off because bluetooth on and wifi calling is not enabled
+ */
+ @Test
+ @SmallTest
+ public void testCreateOutgoingCall_turnOnRadio_bluetoothOn() {
+ doReturn(true).when(mDeviceState).isRadioPowerDownAllowedOnBluetooth(any());
+ doReturn(PhoneConstants.CELL_ON_FLAG).when(mDeviceState).getCellOnStatus(any());
+ Phone testPhone0 = makeTestPhone(0 /*phoneId*/, ServiceState.STATE_POWER_OFF,
+ false /*isEmergencyOnly*/);
+ Phone testPhone1 = makeTestPhone(1 /*phoneId*/, ServiceState.STATE_POWER_OFF,
+ false /*isEmergencyOnly*/);
+ doReturn(false).when(testPhone0).isRadioOn();
+ doReturn(false).when(testPhone0).isWifiCallingEnabled();
+ doReturn(false).when(testPhone1).isRadioOn();
+ doReturn(false).when(testPhone1).isWifiCallingEnabled();
+ List<Phone> phones = new ArrayList<>(2);
+ phones.add(testPhone0);
+ phones.add(testPhone1);
+ setPhones(phones);
+ setupHandleToPhoneMap(PHONE_ACCOUNT_HANDLE_1, testPhone0);
+ ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
+ .setAccountHandle(PHONE_ACCOUNT_HANDLE_1)
+ .setAddress(TEST_ADDRESS)
+ .build();
+ mConnection = mTestConnectionService.onCreateOutgoingConnection(
+ PHONE_ACCOUNT_HANDLE_1, connectionRequest);
+
+ verify(mRadioOnHelper).triggerRadioOnAndListen(any(), eq(false),
+ eq(testPhone0), eq(false), eq(0));
+ }
+
+ /**
+ * Test that the TelephonyConnectionService successfully turns radio on before placing the
+ * call when phone is null, radio off because bluetooth on and wifi calling is not enabled
+ */
+ @Test
+ @SmallTest
+ public void testCreateOutgoingCall_forWearWatch_whenPhoneIsNull() {
+ doReturn(-1).when(mPhoneUtilsProxy).getSubIdForPhoneAccountHandle(any());
+ doReturn(true).when(mDeviceState).isRadioPowerDownAllowedOnBluetooth(any());
+ doReturn(PhoneConstants.CELL_ON_FLAG).when(mDeviceState).getCellOnStatus(any());
+ Phone testPhone0 = makeTestPhone(0 /*phoneId*/, ServiceState.STATE_POWER_OFF,
+ false /*isEmergencyOnly*/);
+ Phone testPhone1 = makeTestPhone(1 /*phoneId*/, ServiceState.STATE_POWER_OFF,
+ false /*isEmergencyOnly*/);
+ doReturn(false).when(testPhone0).isRadioOn();
+ doReturn(false).when(testPhone0).isWifiCallingEnabled();
+ doReturn(false).when(testPhone1).isRadioOn();
+ doReturn(false).when(testPhone1).isWifiCallingEnabled();
+ List<Phone> phones = new ArrayList<>(2);
+ phones.add(testPhone0);
+ phones.add(testPhone1);
+ setPhones(phones);
+ setupHandleToPhoneMap(PHONE_ACCOUNT_HANDLE_1, testPhone0);
+ ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
+ .setAccountHandle(PHONE_ACCOUNT_HANDLE_1)
+ .setAddress(TEST_ADDRESS)
+ .build();
+ mConnection = mTestConnectionService.onCreateOutgoingConnection(
+ PHONE_ACCOUNT_HANDLE_1, connectionRequest);
+
+ verify(mRadioOnHelper).triggerRadioOnAndListen(any(), eq(false),
+ eq(testPhone0), eq(false), eq(0));
+ }
+
+ /**
+ * Test that the TelephonyConnectionService will not turns radio on before placing the
+ * call when radio off because bluetooth on and wifi calling is enabled
+ */
+ @Test
+ @SmallTest
+ public void testCreateOutgoingCall_notTurnOnRadio_bluetoothOnWifiCallingEnabled() {
+ doReturn(true).when(mDeviceState).isRadioPowerDownAllowedOnBluetooth(any());
+ doReturn(PhoneConstants.CELL_ON_FLAG).when(mDeviceState).getCellOnStatus(any());
+ Phone testPhone0 = makeTestPhone(0 /*phoneId*/, ServiceState.STATE_POWER_OFF,
+ false /*isEmergencyOnly*/);
+ Phone testPhone1 = makeTestPhone(1 /*phoneId*/, ServiceState.STATE_POWER_OFF,
+ false /*isEmergencyOnly*/);
+ doReturn(false).when(testPhone0).isRadioOn();
+ doReturn(true).when(testPhone0).isWifiCallingEnabled();
+ doReturn(false).when(testPhone1).isRadioOn();
+ doReturn(true).when(testPhone1).isWifiCallingEnabled();
+ List<Phone> phones = new ArrayList<>(2);
+ phones.add(testPhone0);
+ phones.add(testPhone1);
+ setPhones(phones);
+ setupHandleToPhoneMap(PHONE_ACCOUNT_HANDLE_1, testPhone0);
+ ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
+ .setAccountHandle(PHONE_ACCOUNT_HANDLE_1)
+ .setAddress(TEST_ADDRESS)
+ .build();
+ mConnection = mTestConnectionService.onCreateOutgoingConnection(
+ PHONE_ACCOUNT_HANDLE_1, connectionRequest);
+
+ verify(mRadioOnHelper, times(0)).triggerRadioOnAndListen(any(),
+ eq(true), eq(testPhone0), eq(false), eq(0));
}
/**
@@ -2037,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);
@@ -2067,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);
@@ -2098,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))
@@ -2125,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);
@@ -2153,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);
@@ -2194,7 +2304,89 @@
verify(mDomainSelectionResolver)
.getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
verify(mNormalCallDomainSelectionConnection).createNormalConnection(any(), any());
- verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), eq(mPhone0));
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
+
+ ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
+
+ verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
+ DialArgs dialArgs = argsCaptor.getValue();
+ assertNotNull("DialArgs param is null", dialArgs);
+ assertNotNull("intentExtras is null", dialArgs.intentExtras);
+ assertTrue(dialArgs.intentExtras.containsKey(PhoneConstants.EXTRA_DIAL_DOMAIN));
+ assertEquals(
+ selectedDomain, dialArgs.intentExtras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN, -1));
+ }
+
+ @Test
+ public void testDomainSelectionDialedSimEmergencyNumberOnlyFalse() throws Exception {
+ setupForCallTest();
+
+ int selectedDomain = DOMAIN_PS;
+
+ EmergencyNumber emergencyNumber = new EmergencyNumber(TEST_EMERGENCY_NUMBER, "", "",
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+ Collections.emptyList(),
+ EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+
+ setupForDialForDomainSelection(mPhone0, selectedDomain, true);
+ doReturn(emergencyNumber).when(mEmergencyNumberTracker).getEmergencyNumber(anyString());
+ doReturn(Arrays.asList(emergencyNumber)).when(mEmergencyNumberTracker).getEmergencyNumbers(
+ anyString());
+ doReturn(false).when(mEmergencyNumberTracker).isEmergencyNumber(anyString());
+ getTestContext().getCarrierConfig(0 /*subId*/).putBoolean(
+ CarrierConfigManager.KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL, false);
+
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
+ TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+
+ 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());
+ verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
+
+ ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
+
+ verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
+ DialArgs dialArgs = argsCaptor.getValue();
+ assertNotNull("DialArgs param is null", dialArgs);
+ assertNotNull("intentExtras is null", dialArgs.intentExtras);
+ assertTrue(dialArgs.intentExtras.containsKey(PhoneConstants.EXTRA_DIAL_DOMAIN));
+ assertEquals(selectedDomain,
+ dialArgs.intentExtras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN, -1));
+ }
+
+ @Test
+ public void testDomainSelectionDialedSimEmergencyNumberOnlyTrue() throws Exception {
+ setupForCallTest();
+ int selectedDomain = DOMAIN_PS;
+
+ EmergencyNumber emergencyNumber = new EmergencyNumber(TEST_EMERGENCY_NUMBER, "", "",
+ EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+ Collections.emptyList(),
+ EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+ EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+
+ setupForDialForDomainSelection(mPhone0, selectedDomain, false);
+ doReturn(true).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(anyString());
+ doReturn(emergencyNumber).when(mEmergencyNumberTracker).getEmergencyNumber(anyString());
+ doReturn(Arrays.asList(emergencyNumber)).when(mEmergencyNumberTracker).getEmergencyNumbers(
+ anyString());
+ doReturn(false).when(mEmergencyNumberTracker).isEmergencyNumber(anyString());
+ getTestContext().getCarrierConfig(0 /*subId*/).putBoolean(
+ CarrierConfigManager.KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL, true);
+
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
+ TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+
+ verify(mDomainSelectionResolver)
+ .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
+ verify(mNormalCallDomainSelectionConnection).createNormalConnection(any(), any());
+ verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
@@ -2210,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)
@@ -2351,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();
@@ -2405,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();
@@ -2461,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
@@ -2484,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);
@@ -2525,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);
@@ -2754,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
@@ -2791,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);
@@ -2833,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());
@@ -2864,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());
@@ -2949,6 +3057,52 @@
}
@Test
+ public void testDomainSelectionListenOriginalConnectionPropertiesChange() throws Exception {
+ setupForCallTest();
+
+ int selectedDomain = DOMAIN_PS;
+
+ setupForDialForDomainSelection(mPhone0, selectedDomain, true);
+
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
+ TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+
+ verify(mDomainSelectionResolver)
+ .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
+ verify(mEmergencyStateTracker)
+ .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+ verify(mEmergencyCallDomainSelectionConnection).createEmergencyConnection(any(), any());
+ verify(mPhone0).dial(anyString(), any(), any());
+
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setTelecomCallId(TELECOM_CALL_ID1);
+ c.setIsImsConnection(true);
+ Connection orgConn = c.getOriginalConnection();
+ doReturn(PhoneConstants.PHONE_TYPE_IMS).when(orgConn).getPhoneType();
+
+ TelephonyConnection.TelephonyConnectionListener connectionListener =
+ mTestConnectionService.getEmergencyConnectionListener();
+
+ doReturn(Call.State.DISCONNECTING).when(orgConn).getState();
+ connectionListener.onConnectionPropertiesChanged(c, PROPERTY_WIFI);
+
+ verify(mEmergencyStateTracker, times(0)).onEmergencyCallPropertiesChanged(
+ anyInt(), anyString());
+
+ doReturn(Call.State.ACTIVE).when(orgConn).getState();
+ connectionListener.onConnectionPropertiesChanged(c, PROPERTY_WIFI);
+
+ verify(mEmergencyStateTracker, times(1)).onEmergencyCallPropertiesChanged(
+ eq(PROPERTY_WIFI), eq(TELECOM_CALL_ID1));
+
+ connectionListener.onConnectionPropertiesChanged(c, 0);
+
+ verify(mEmergencyStateTracker, times(1)).onEmergencyCallPropertiesChanged(
+ eq(0), eq(TELECOM_CALL_ID1));
+ }
+
+ @Test
public void testDomainSelectionTempFailure() throws Exception {
setupForCallTest();
@@ -2963,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());
}
@@ -2982,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());
}
@@ -3010,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);
@@ -3035,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);
@@ -3060,7 +3216,9 @@
}
@Test
- public void testNormalCallUsingNonTerrestrialNetwork() {
+ public void testNormalCallUsingNonTerrestrialNetwork_enableFlag() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG);
+
setupForCallTest();
// Call is not supported while using satellite
NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
@@ -3090,6 +3248,26 @@
}
@Test
+ public void testNormalCallUsingNonTerrestrialNetwork_disableFlag() throws Exception {
+ mSetFlagsRule.disableFlags(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG);
+
+ setupForCallTest();
+ // Flag is disabled, so call is supported while using satellite
+ NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+ .setIsNonTerrestrialNetwork(true)
+ .setAvailableServices(List.of(NetworkRegistrationInfo.SERVICE_TYPE_DATA))
+ .build();
+ ServiceState ss = new ServiceState();
+ ss.addNetworkRegistrationInfo(nri);
+ when(mPhone0.getServiceState()).thenReturn(ss);
+ mConnection = mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1, "1234", TELECOM_CALL_ID1));
+ DisconnectCause disconnectCause = mConnection.getDisconnectCause();
+ assertNotEquals(android.telephony.DisconnectCause.SATELLITE_ENABLED,
+ disconnectCause.getTelephonyDisconnectCause());
+ }
+
+ @Test
public void testIsAvailableForEmergencyCallsNotForCrossSim() {
Phone mockPhone = Mockito.mock(Phone.class);
when(mockPhone.getImsRegistrationTech()).thenReturn(
@@ -3103,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);
@@ -3257,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 9be85ed..119c980 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,12 +100,13 @@
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.ImsReasonInfo;
import android.telephony.ims.ProvisioningManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableLooper;
@@ -117,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;
/**
@@ -128,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;
@@ -139,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;
@@ -188,6 +202,11 @@
public String getOpPackageName() {
return "";
}
+
+ @Override
+ public Resources getResources() {
+ return mResources;
+ }
};
if (Looper.myLooper() == null) {
@@ -207,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()))
@@ -253,6 +274,8 @@
}
}).when(mWwanSelectorCallback).onRequestEmergencyNetworkScan(
any(), anyInt(), any(), any());
+
+ when(mResources.getStringArray(anyInt())).thenReturn(null);
}
@After
@@ -279,6 +302,122 @@
}
@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);
+
+ 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();
+ unsolBarringInfoChanged(false);
+
+ processAllMessages();
+
+ verify(mTransportSelectorCallback, times(1)).onWwanSelected(any());
+ verify(mWwanSelectorCallback, times(1)).onDomainSelected(anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testNoUnexpectedTransportChangeFromInitialState() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ int[] domainPreference = new int[] {
+ CarrierConfigManager.ImsEmergency.DOMAIN_PS_NON_3GPP,
+ CarrierConfigManager.ImsEmergency.DOMAIN_PS_3GPP,
+ CarrierConfigManager.ImsEmergency.DOMAIN_CS
+ };
+ bundle.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY, domainPreference);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ 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();
+
+ bindImsServiceUnregistered();
+ bindImsService(true);
+
+ processAllMessages();
+
+ verify(mTransportSelectorCallback, times(1)).onWwanSelected(any());
+ verify(mTransportSelectorCallback, times(0)).onWlanSelected(anyBoolean());
+ }
+
+ @Test
+ public void testNoRedundantScanRequestFromInitialState() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ 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();
+
+ bindImsService();
+ unsolBarringInfoChanged(false);
+
+ processAllMessages();
+
+ verify(mTransportSelectorCallback, times(1)).onWwanSelected(any());
+ verify(mWwanSelectorCallback, times(1)).onRequestEmergencyNetworkScan(
+ any(), anyInt(), any(), any());
+ }
+
+ @Test
+ public void testNoRedundantTerminationFromInitialState() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+ doReturn(2).when(mTelephonyManager).getActiveModemCount();
+ doReturn(TelephonyManager.SIM_STATE_PIN_REQUIRED)
+ .when(mTelephonyManager).getSimState(anyInt());
+ doReturn(true).when(mCsrdCtrl).isThereOtherSlot();
+ doReturn(new String[] {"jp"}).when(mResources).getStringArray(anyInt());
+
+ EmergencyRegResult regResult = getEmergencyRegResult(
+ UNKNOWN, REGISTRATION_STATE_UNKNOWN, 0, false, false, 0, 0, "", "", "jp");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+ unsolBarringInfoChanged(false);
+
+ verify(mTransportSelectorCallback, times(0)).onWlanSelected(anyBoolean());
+ verify(mTransportSelectorCallback, times(0)).onWwanSelected(any());
+ verify(mTransportSelectorCallback, times(1)).onSelectionTerminated(anyInt());
+ }
+
+ @Test
public void testDefaultCombinedImsRegisteredBarredSelectCs() throws Exception {
createSelector(SLOT_0_SUB_ID);
unsolBarringInfoChanged(true);
@@ -335,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);
@@ -973,6 +1180,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);
@@ -997,6 +1205,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
@@ -1150,6 +1391,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");
@@ -1165,6 +1407,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);
@@ -1172,6 +1444,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");
@@ -1200,6 +1473,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);
@@ -1271,7 +1568,7 @@
setupForScanListTest(bundle);
- verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+ verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false));
}
@Test
@@ -1285,7 +1582,7 @@
setupForScanListTest(bundle);
- verifyPsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+ verifyPsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false));
}
@Test
@@ -1300,7 +1597,7 @@
setupForScanListTest(bundle);
- verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+ verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(false, false));
}
@Test
@@ -1315,7 +1612,7 @@
setupForScanListTest(bundle);
- verifyCsOnlyScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+ verifyCsOnlyScanList(mDomainSelector.getNextPreferredNetworks(false, false));
}
@@ -1330,7 +1627,7 @@
setupForScanListTest(bundle);
- verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+ verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false));
}
@Test
@@ -1344,7 +1641,7 @@
setupForScanListTest(bundle);
- verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+ verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false));
}
@Test
@@ -1360,7 +1657,7 @@
setupForScanListTest(bundle);
- verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+ verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(true, false));
}
@Test
@@ -1375,7 +1672,7 @@
setupForScanListTest(bundle);
- verifyCsOnlyScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+ verifyCsOnlyScanList(mDomainSelector.getNextPreferredNetworks(true, false));
}
@Test
@@ -1392,7 +1689,7 @@
bindImsService();
processAllMessages();
- verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+ verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false));
}
@Test
@@ -1409,7 +1706,7 @@
bindImsService();
processAllMessages();
- verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+ verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false));
}
@Test
@@ -1427,7 +1724,7 @@
bindImsService();
processAllMessages();
- verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+ verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(false, false));
}
@Test
@@ -1444,7 +1741,7 @@
bindImsService();
processAllMessages();
- verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+ verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false));
}
@Test
@@ -1461,7 +1758,7 @@
bindImsService();
processAllMessages();
- verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+ verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false));
}
@Test
@@ -1480,7 +1777,7 @@
bindImsService();
processAllMessages();
- verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+ verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(true, false));
}
@Test
@@ -1496,7 +1793,7 @@
setupForScanListTest(bundle);
- List<Integer> networks = mDomainSelector.getNextPreferredNetworks(false, true, false);
+ List<Integer> networks = mDomainSelector.getNextPreferredNetworks(false, true);
assertFalse(networks.isEmpty());
assertTrue(networks.contains(EUTRAN));
@@ -1512,6 +1809,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, "", "");
@@ -1652,36 +1950,6 @@
}
@Test
- public void testDefaultEpsImsRegisteredSelectPsEmergencyRegFailed() throws Exception {
- createSelector(SLOT_0_SUB_ID);
- unsolBarringInfoChanged(false);
-
- EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
- NetworkRegistrationInfo.DOMAIN_PS,
- true, true, 0, 0, "", "");
- SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
- mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
- processAllMessages();
-
- bindImsService();
-
- verifyPsDialed();
-
- attr = new SelectionAttributes.Builder(SLOT_0, SLOT_0_SUB_ID, SELECTOR_TYPE_CALLING)
- .setEmergency(true)
- .setEmergencyRegResult(regResult)
- .setPsDisconnectCause(
- new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED, 0, null))
- .build();
- mDomainSelector.reselectDomain(attr);
- processAllMessages();
-
- verify(mWwanSelectorCallback, times(1)).onRequestEmergencyNetworkScan(
- any(), anyInt(), any(), any());
- assertFalse(mAccessNetwork.contains(EUTRAN));
- }
-
- @Test
public void testMaxCellularTimeout() throws Exception {
PersistableBundle bundle = getDefaultPersistableBundle();
bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
@@ -1706,6 +1974,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 {
@@ -1773,6 +2055,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();
@@ -1847,6 +2132,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);
}
@@ -1920,8 +2530,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);
}
@@ -2093,6 +2703,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/ImsStateTrackerTest.java b/tests/src/com/android/services/telephony/domainselection/ImsStateTrackerTest.java
index 430adea..6519835 100644
--- a/tests/src/com/android/services/telephony/domainselection/ImsStateTrackerTest.java
+++ b/tests/src/com/android/services/telephony/domainselection/ImsStateTrackerTest.java
@@ -33,6 +33,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.SystemClock;
import android.telephony.AccessNetworkConstants.AccessNetworkType;
import android.telephony.BarringInfo;
import android.telephony.ServiceState;
@@ -74,6 +75,7 @@
private static final int SUB_1 = 1;
private static final int SUB_2 = 2;
private static final long TIMEOUT_MS = 100;
+ private static final long MSG_PROCESS_DELAY_MS = 10;
@Mock private ImsMmTelManager mMmTelManager;
@Mock private ImsMmTelManager mMmTelManager2;
@@ -262,6 +264,9 @@
@Test
@SmallTest
public void testAddAndRemoveServiceStateListener() {
+ mImsStateTracker.getHandler().post(() -> {
+ SystemClock.sleep(MSG_PROCESS_DELAY_MS);
+ });
mImsStateTracker.updateServiceState(mServiceState);
mImsStateTracker.addServiceStateListener(mServiceStateListener);
mImsStateTracker.removeServiceStateListener(mServiceStateListener);
@@ -307,6 +312,9 @@
@Test
@SmallTest
public void testAddAndRemoveBarringInfoListener() {
+ mImsStateTracker.getHandler().post(() -> {
+ SystemClock.sleep(MSG_PROCESS_DELAY_MS);
+ });
mImsStateTracker.updateBarringInfo(mBarringInfo);
mImsStateTracker.addBarringInfoListener(mBarringInfoListener);
mImsStateTracker.removeBarringInfoListener(mBarringInfoListener);
diff --git a/tests/src/com/android/services/telephony/domainselection/NormalCallDomainSelectorTest.java b/tests/src/com/android/services/telephony/domainselection/NormalCallDomainSelectorTest.java
index 4dd1f3c..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);
+ }
+ }
+}