[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: 8543d1baa8 -s ours

am skip reason: subject contains skip directive

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Telephony/+/25647216

Change-Id: I78ff1c19a0e127d15d6d8e0d9e69331823b4cb9c
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index dc35c5d..ffd2292 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,19 @@
     libs: [
         "telephony-common",
         "service-entitlement"
-        ],
+    ],
+}
+
+// Used by satellite unit tests temporarily during the development phase.
+// TODO: Remove this once the satellite code is wired into Telephony code.
+java_library {
+    name: "telephony-satellite",
+    srcs: ["src/com/android/phone/satellite/**/*.java"],
+    libs: [
+        "satellite-s2storage-ro",
+        "s2-geometry-library-java",
+        "telephony-common",
+    ],
 }
 
 platform_compat_config {
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8d03ed7..7e56e8b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -279,7 +279,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 +289,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 +299,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 +309,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 +619,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"
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/ecc/input/OWNERS b/ecc/input/OWNERS
index 5685875..2ed2f3f 100644
--- a/ecc/input/OWNERS
+++ b/ecc/input/OWNERS
@@ -1,4 +1,5 @@
 set noparent
 
 tgunn@google.com
-chinmayd@google.com
\ No newline at end of file
+breadley@google.com
+rgreenwalt@google.com
\ No newline at end of file
diff --git a/ecc/input/eccdata.txt b/ecc/input/eccdata.txt
index 7252480..fe11383 100644
--- a/ecc/input/eccdata.txt
+++ b/ecc/input/eccdata.txt
@@ -906,6 +906,16 @@
       types: TYPE_UNSPECIFIED
       routing: NORMAL
   }
+  eccs {
+      phone_number: "028"
+      types: TYPE_UNSPECIFIED
+      routing: NORMAL
+  }
+  eccs {
+      phone_number: "116000"
+      types: TYPE_UNSPECIFIED
+      routing: NORMAL
+  }
   ecc_fallback: "112"
 }
 countries {
@@ -1372,6 +1382,13 @@
     types: AMBULANCE
     types: FIRE
   }
+  eccs {
+    phone_number: "100"
+    types: POLICE
+    types: AMBULANCE
+    types: FIRE
+    routing: NORMAL
+  }
   ecc_fallback: "112"
 }
 countries {
diff --git a/ecc/output/OWNERS b/ecc/output/OWNERS
index 5685875..2ed2f3f 100644
--- a/ecc/output/OWNERS
+++ b/ecc/output/OWNERS
@@ -1,4 +1,5 @@
 set noparent
 
 tgunn@google.com
-chinmayd@google.com
\ No newline at end of file
+breadley@google.com
+rgreenwalt@google.com
\ No newline at end of file
diff --git a/ecc/output/eccdata b/ecc/output/eccdata
index fc86961..55d8151 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 1d18c0b..58f2670 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 2ff5455..c52cb86 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 704a5cb..85a5a93 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 fb347f7..4f8e559 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 dc25d7c..fe39ede 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 480fe90..0ab1b47 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 7025487..8576e1f 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 d8277c1..5eea570 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 693a0e3..1f86b13 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..dcfa364 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -318,6 +318,25 @@
     <string-array name="thermal_mitigation_allowlisted_packages" translatable="false">
     </string-array>
 
+    <!-- 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>
+
     <!-- 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>
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..3eafb24 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -39,6 +39,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;
@@ -67,8 +68,8 @@
 import com.android.internal.telephony.TelephonyPermissions;
 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 +79,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 +99,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 +140,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 +239,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 +287,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 +366,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 +421,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 +501,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 +575,6 @@
                             restoreNoSimConfigFromXml(mPlatformCarrierConfigPackage);
 
                     if (config != null) {
-                        logd("Loaded no SIM config from XML. package="
-                                + mPlatformCarrierConfigPackage);
                         mNoSimConfig = config;
                         sendMessage(
                                 obtainMessage(
@@ -673,7 +669,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.
                     }
@@ -695,6 +691,7 @@
      */
     @VisibleForTesting
     /* package */ CarrierConfigLoader(@NonNull Context context, @NonNull Looper looper) {
+        super(PermissionEnforcer.fromContext(context));
         mContext = context;
         mPlatformCarrierConfigPackage =
                 mContext.getString(R.string.platform_carrier_config_package);
@@ -1003,7 +1000,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 +1010,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 +1018,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 +1035,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 +1101,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 +1156,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 +1200,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;
@@ -1408,11 +1405,11 @@
         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);
@@ -1440,6 +1437,8 @@
                     fileToDelete.delete();
                 }
             }
+            logdWithLocalLog("overrideConfig: subId=" + subscriptionId + ", persistent="
+                    + persistent + ", overrides=" + overrides);
             updateSubscriptionDatabase(phoneId);
         });
     }
@@ -1469,6 +1468,9 @@
                     "Invalid phoneId " + phoneId + " for subId " + subscriptionId);
         }
 
+        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,11 +1480,11 @@
         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);
         }
@@ -1502,12 +1504,11 @@
         }
     }
 
+    @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");
+        getDefaultCarrierServicePackageName_enforcePermission();
         return mPlatformCarrierConfigPackage;
     }
 
@@ -1573,6 +1574,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 +1635,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 +1660,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,
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/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/ImsStateCallbackController.java b/src/com/android/phone/ImsStateCallbackController.java
index edad754..019c1ca 100644
--- a/src/com/android/phone/ImsStateCallbackController.java
+++ b/src/com/android/phone/ImsStateCallbackController.java
@@ -739,7 +739,7 @@
         updateFeatureControllerSize(numSlots);
 
         mTelephonyRegistryManager.addOnSubscriptionsChangedListener(
-                mSubChangedListener, mSubChangedListener.getHandlerExecutor());
+                mSubChangedListener, mHandler::post);
 
         PhoneConfigurationManager.registerForMultiSimConfigChange(mHandler,
                 EVENT_MSIM_CONFIGURATION_CHANGE, null);
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
index b28bd5c..3cd9a8b 100644
--- a/src/com/android/phone/NotificationMgr.java
+++ b/src/com/android/phone/NotificationMgr.java
@@ -64,6 +64,8 @@
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.TelephonyCapabilities;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.FeatureFlagsImpl;
 import com.android.internal.telephony.util.NotificationChannelController;
 import com.android.phone.settings.VoicemailSettingsActivity;
 
@@ -146,6 +148,9 @@
     // maps each subId to selected network operator name.
     private SparseArray<String> mSelectedNetworkOperatorName = new SparseArray<>();
 
+    // feature flags
+    private final FeatureFlags mFeatureFlags;
+
     private final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
@@ -177,6 +182,7 @@
         mSubscriptionManager = SubscriptionManager.from(mContext);
         mTelecomManager = app.getSystemService(TelecomManager.class);
         mTelephonyManager = (TelephonyManager) app.getSystemService(Context.TELEPHONY_SERVICE);
+        mFeatureFlags = new FeatureFlagsImpl();
     }
 
     /**
@@ -409,7 +415,22 @@
                 }
             }
         } else {
-            cancelAsUser(Integer.toString(subId) /* tag */, VOICEMAIL_NOTIFICATION, UserHandle.ALL);
+            UserHandle subAssociatedUserHandle =
+                    mSubscriptionManager.getSubscriptionUserHandle(subId);
+            List<UserHandle> users = getUsersExcludeDying();
+            for (UserHandle userHandle : users) {
+                boolean isManagedUser = mUserManager.isManagedProfile(userHandle.getIdentifier());
+                if (!hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, userHandle)
+                        && (userHandle.equals(subAssociatedUserHandle)
+                            || (subAssociatedUserHandle == null && !isManagedUser))
+                        && !maybeSendVoicemailNotificationUsingDefaultDialer(phone, 0, null, null,
+                        false, userHandle, isRefresh)) {
+                    cancelAsUser(
+                            Integer.toString(subId) /* tag */,
+                            VOICEMAIL_NOTIFICATION,
+                            userHandle);
+                }
+            }
         }
     }
 
@@ -813,10 +834,73 @@
      * @param subId The subscription ID
      */
     void updateNetworkSelection(int serviceState, int subId) {
+        if (!mFeatureFlags.dismissNetworkSelectionNotificationOnSimDisable()) {
+            updateNetworkSelectionForFeatureDisabled(serviceState, subId);
+            return;
+        }
+
+        // for dismissNetworkSelectionNotificationOnSimDisable feature enabled.
         int phoneId = SubscriptionManager.getPhoneId(subId);
         Phone phone = SubscriptionManager.isValidPhoneId(phoneId) ?
                 PhoneFactory.getPhone(phoneId) : PhoneFactory.getDefaultPhone();
         if (TelephonyCapabilities.supportsNetworkSelection(phone)) {
+            if (SubscriptionManager.isValidSubscriptionId(subId)
+                    && mSubscriptionManager.isActiveSubId(subId)) {
+                SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+                String selectedNetworkOperatorName =
+                        sp.getString(Phone.NETWORK_SELECTION_NAME_KEY + subId, "");
+                // get the shared preference of network_selection.
+                // empty is auto mode, otherwise it is the operator alpha name
+                // in case there is no operator name, check the operator numeric
+                if (TextUtils.isEmpty(selectedNetworkOperatorName)) {
+                    selectedNetworkOperatorName =
+                            sp.getString(Phone.NETWORK_SELECTION_KEY + subId, "");
+                }
+                boolean isManualSelection;
+                // if restoring manual selection is controlled by framework, then get network
+                // selection from shared preference, otherwise get from real network indicators.
+                boolean restoreSelection = !mContext.getResources().getBoolean(
+                        com.android.internal.R.bool.skip_restoring_network_selection);
+                if (restoreSelection) {
+                    isManualSelection = !TextUtils.isEmpty(selectedNetworkOperatorName);
+                } else {
+                    isManualSelection = phone.getServiceStateTracker().mSS.getIsManualSelection();
+                }
+
+                if (DBG) {
+                    log("updateNetworkSelection()..." + "state = " + serviceState + " new network "
+                            + (isManualSelection ? selectedNetworkOperatorName : ""));
+                }
+
+                if (isManualSelection) {
+                    mSelectedNetworkOperatorName.put(subId, selectedNetworkOperatorName);
+                    shouldShowNotification(serviceState, subId);
+                } else {
+                    dismissNetworkSelectionNotification(subId);
+                    clearUpNetworkSelectionNotificationParam(subId);
+                }
+            } else {
+                if (DBG) {
+                    log("updateNetworkSelection()... state = " + serviceState
+                            + " not updating network due to invalid subId " + subId);
+                }
+                dismissNetworkSelectionNotificationForInactiveSubId();
+            }
+        }
+    }
+
+    /**
+     * Update notification about no service of user selected operator.
+     * For dismissNetworkSelectionNotificationOnSimDisable feature disabled.
+     *
+     * @param serviceState Phone service state
+     * @param subId The subscription ID
+     */
+    private void updateNetworkSelectionForFeatureDisabled(int serviceState, int subId) {
+        int phoneId = SubscriptionManager.getPhoneId(subId);
+        Phone phone = SubscriptionManager.isValidPhoneId(phoneId)
+                ? PhoneFactory.getPhone(phoneId) : PhoneFactory.getDefaultPhone();
+        if (TelephonyCapabilities.supportsNetworkSelection(phone)) {
             if (SubscriptionManager.isValidSubscriptionId(subId)) {
                 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
                 String selectedNetworkOperatorName =
@@ -866,7 +950,10 @@
         }
     }
 
-    private void dismissNetworkSelectionNotificationForInactiveSubId() {
+    /**
+     * Dismiss the network selection "no service" notification for all inactive subscriptions.
+     */
+    public void dismissNetworkSelectionNotificationForInactiveSubId() {
         for (int i = 0; i < mSelectedUnavailableNotify.size(); i++) {
             int subId = mSelectedUnavailableNotify.keyAt(i);
             if (!mSubscriptionManager.isActiveSubId(subId)) {
@@ -876,15 +963,6 @@
         }
     }
 
-    /* package */ void postTransientNotification(int notifyId, CharSequence msg) {
-        if (mToast != null) {
-            mToast.cancel();
-        }
-
-        mToast = Toast.makeText(mContext, msg, Toast.LENGTH_LONG);
-        mToast.show();
-    }
-
     private void log(String msg) {
         Log.d(LOG_TAG, msg);
     }
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 7a61dd1..f93b1e5 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -50,6 +50,7 @@
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyLocalConnection;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.LocalLog;
 import android.util.Log;
@@ -71,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;
@@ -181,6 +184,22 @@
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"ROAMING_NOTIFICATION_REASON_"},
+            value = {
+                    ROAMING_NOTIFICATION_REASON_DATA_SETTING_CHANGED,
+                    ROAMING_NOTIFICATION_REASON_DATA_ROAMING_SETTING_CHANGED,
+                    ROAMING_NOTIFICATION_REASON_CARRIER_CONFIG_CHANGED,
+                    ROAMING_NOTIFICATION_REASON_SERVICE_STATE_CHANGED,
+                    ROAMING_NOTIFICATION_REASON_DEFAULT_DATA_SUBS_CHANGED})
+    public @interface RoamingNotificationReason {}
+    private static final int ROAMING_NOTIFICATION_REASON_DATA_SETTING_CHANGED = 0;
+    private static final int ROAMING_NOTIFICATION_REASON_DATA_ROAMING_SETTING_CHANGED = 1;
+    private static final int ROAMING_NOTIFICATION_REASON_CARRIER_CONFIG_CHANGED = 2;
+    private static final int ROAMING_NOTIFICATION_REASON_SERVICE_STATE_CHANGED = 3;
+    private static final int ROAMING_NOTIFICATION_REASON_DEFAULT_DATA_SUBS_CHANGED = 4;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"ROAMING_NOTIFICATION_"},
             value = {
                     ROAMING_NOTIFICATION_NO_NOTIFICATION,
@@ -193,8 +212,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<>();
 
@@ -221,6 +250,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;
@@ -333,17 +364,20 @@
                     break;
 
                 case EVENT_SIM_STATE_CHANGED:
-                    // Marks the event where the SIM goes into ready state.
-                    // Right now, this is only used for the PUK-unlocking
-                    // process.
                     EventSimStateChangedBag bag = (EventSimStateChangedBag)msg.obj;
+                    // Dismiss the "No services" notification if the SIM is removed.
+                    if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(bag.mIccStatus)) {
+                        notificationMgr.dismissNetworkSelectionNotificationForInactiveSubId();
+                    }
+
+                    // Marks the event where the SIM goes into ready state.
+                    // Right now, this is only used for the PUK-unlocking process.
                     if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(bag.mIccStatus)
                             || IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(bag.mIccStatus)
                             || IccCardConstants.INTENT_VALUE_ICC_NOT_READY.equals(bag.mIccStatus)
                             || IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(bag.mIccStatus)) {
-                        // when the right event is triggered and there
-                        // are UI objects in the foreground, we close
-                        // them to display the lock panel.
+                        // When the right event is triggered and there are UI objects in the
+                        // foreground, we close them to display the lock panel.
                         if (mPUKEntryActivity != null) {
                             Log.i(LOG_TAG, "Dismiss puk entry activity");
                             mPUKEntryActivity.finish();
@@ -363,8 +397,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;
@@ -455,7 +500,8 @@
                     getResources().getBoolean(R.bool.config_enable_aosp_domain_selection));
 
             // 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.
@@ -513,7 +559,7 @@
 
             // Create the SatelliteController singleton, which acts as a backend service for
             // {@link android.telephony.satellite.SatelliteManager}.
-            SatelliteController.make(this);
+            SatelliteController.make(this, mFeatureFlags);
 
             // Create an instance of CdmaPhoneCallState and initialize it to IDLE
             cdmaPhoneCallState = new CdmaPhoneCallState();
@@ -528,7 +574,7 @@
 
             mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
 
-            phoneMgr = PhoneInterfaceManager.init(this);
+            phoneMgr = PhoneInterfaceManager.init(this, mFeatureFlags);
 
             imsRcsController = ImsRcsController.init(this);
 
@@ -573,6 +619,15 @@
             intentFilter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
             intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
             registerReceiver(mReceiver, intentFilter);
+            int defaultDataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
+            if (SubscriptionManager.isValidSubscriptionId(defaultDataSubId)) {
+                if (VDBG) {
+                    Log.v(LOG_TAG, "Loaded initial default data sub: " + defaultDataSubId);
+                }
+                mDefaultDataSubId = defaultDataSubId;
+                registerSettingsObserver();
+                updateDataRoamingStatus(ROAMING_NOTIFICATION_REASON_DEFAULT_DATA_SUBS_CHANGED);
+            }
 
             PhoneConfigurationManager.registerForMultiSimConfigChange(
                     mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED, null);
@@ -728,13 +783,14 @@
     }
 
     private void handleAirplaneModeChange(boolean isAirplaneNewlyOn) {
+        Log.i(LOG_TAG, "handleAirplaneModeChange: isAirplaneNewlyOn=" + isAirplaneNewlyOn);
         int cellState =
                 Settings.Global.getInt(
                         getContentResolver(), Settings.Global.CELL_ON, PhoneConstants.CELL_ON_FLAG);
         switch (cellState) {
             case PhoneConstants.CELL_OFF_FLAG:
-                // Airplane mode does not affect the cell radio if user
-                // has turned it off.
+                // Airplane mode does not affect the cell radio if user has turned it off.
+                Log.i(LOG_TAG, "Ignore airplane mode change due to cell off.");
                 break;
             case PhoneConstants.CELL_ON_FLAG:
                 maybeTurnCellOff(isAirplaneNewlyOn);
@@ -774,7 +830,11 @@
     /** Clear fields on power off radio **/
     private void clearCacheOnRadioOff() {
         // Re-show is-roaming notifications after APM mode
-        mPrevRoamingOperatorNumerics.clear();
+        if (mFeatureFlags.reorganizeRoamingNotification()) {
+            mShownNotificationReasons.clear();
+        } else {
+            mPrevRoamingOperatorNumerics.clear();
+        }
     }
 
     private void setRadioPowerOn() {
@@ -805,12 +865,16 @@
             } else {
                 Log.i(LOG_TAG, "Ignoring airplane mode: settings prevent cell radio power off");
             }
+        } else {
+            Log.i(LOG_TAG, "Ignoring airplane mode: not newly on");
         }
     }
 
     private void maybeTurnCellOn(boolean isAirplaneNewlyOn) {
         if (!isAirplaneNewlyOn) {
             setRadioPowerOn();
+        } else {
+            Log.i(LOG_TAG, "Ignoring airplane mode off: radio is already on.");
         }
     }
 
@@ -867,7 +931,11 @@
             } else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
                 // Roaming status could be overridden by carrier config, so we need to update it.
                 if (VDBG) Log.v(LOG_TAG, "carrier config changed.");
-                updateDataRoamingStatus();
+                if (mFeatureFlags.reorganizeRoamingNotification()) {
+                    updateDataRoamingStatus(ROAMING_NOTIFICATION_REASON_CARRIER_CONFIG_CHANGED);
+                } else {
+                    updateDataRoamingStatusForFeatureDisabled(null);
+                }
                 updateLimitedSimFunctionForDualSim();
                 int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
@@ -882,7 +950,12 @@
                 registerSettingsObserver();
                 Phone phone = getPhone(mDefaultDataSubId);
                 if (phone != null) {
-                    updateDataRoamingStatus();
+                    if (mFeatureFlags.reorganizeRoamingNotification()) {
+                        updateDataRoamingStatus(
+                                ROAMING_NOTIFICATION_REASON_DEFAULT_DATA_SUBS_CHANGED);
+                    } else {
+                        updateDataRoamingStatusForFeatureDisabled(null);
+                    }
                 }
             }
         }
@@ -898,7 +971,11 @@
                     + mDefaultDataSubId + ", ss roaming=" + serviceState.getDataRoaming());
         }
         if (subId == mDefaultDataSubId) {
-            updateDataRoamingStatus(serviceState.getOperatorNumeric());
+            if (mFeatureFlags.reorganizeRoamingNotification()) {
+                updateDataRoamingStatus(ROAMING_NOTIFICATION_REASON_SERVICE_STATE_CHANGED);
+            } else {
+                updateDataRoamingStatusForFeatureDisabled(serviceState.getOperatorNumeric());
+            }
         }
     }
 
@@ -906,11 +983,144 @@
      * 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();
+        String roamingNumeric = serviceState.getOperatorNumeric();
+        String roamingNumericReason = "RoamingNumeric=" + roamingNumeric;
+        String callingReason = "CallingReason=" + reason;
+        boolean dataIsNowRoaming = serviceState.getDataRoaming();
+        boolean dataAllowed;
+        boolean notAllowedDueToRoamingOff;
+        List<DataDisallowedReason> reasons = phone.getDataNetworkController()
+                .getInternetDataDisallowedReasons();
+        dataAllowed = reasons.isEmpty();
+        notAllowedDueToRoamingOff = (reasons.size() == 1
+                && reasons.contains(DataDisallowedReason.ROAMING_DISABLED));
+        StringBuilder sb = new StringBuilder("updateDataRoamingStatus");
+        sb.append(" dataAllowed=").append(dataAllowed);
+        sb.append(", reasons=").append(reasons);
+        sb.append(", dataIsNowRoaming=").append(dataIsNowRoaming);
+        sb.append(", ").append(roamingNumericReason);
+        sb.append(", ").append(callingReason);
+        mDataRoamingNotifLog.log(sb.toString());
+        if (VDBG) {
+            Log.v(LOG_TAG, sb.toString());
+        }
+
+        // Determine if a given roaming numeric has never been shown.
+        boolean shownInThisNumeric = false;
+        if (reason == ROAMING_NOTIFICATION_REASON_CARRIER_CONFIG_CHANGED
+                || reason == ROAMING_NOTIFICATION_REASON_SERVICE_STATE_CHANGED) {
+            shownInThisNumeric = mShownNotificationReasons.contains(roamingNumericReason);
+        }
+        // Determine if a notification has never been shown by given calling reason.
+        boolean shownForThisReason = mShownNotificationReasons.contains(callingReason);
+
+        if (!dataAllowed && notAllowedDueToRoamingOff) {
+            if (!shownInThisNumeric && roamingNumeric != null) {
+                mShownNotificationReasons.add(roamingNumericReason);
+            }
+            if (!shownForThisReason
+                    && reason == ROAMING_NOTIFICATION_REASON_CARRIER_CONFIG_CHANGED) {
+                mShownNotificationReasons.add(callingReason);
+            }
+            // No need to show it again if we never cancelled it explicitly.
+            if (getCurrentRoamingNotification() == ROAMING_NOTIFICATION_DISCONNECTED) {
+                return;
+            }
+
+            // If the only reason of no data is data roaming disabled, then we notify the user
+            // so the user can turn on data roaming.
+            if (!shownInThisNumeric && !shownForThisReason) {
+                updateDataRoamingNotification(ROAMING_NOTIFICATION_DISCONNECTED);
+            } else {
+                // Don't show roaming notification if we've already shown for this MccMnc
+                Log.d(LOG_TAG, "Skip roaming disconnected notification since already"
+                        + " shownInThisNumeric=" + shownInThisNumeric
+                        + " shownForThisReason=" + shownForThisReason);
+                // Dismiss notification if the other notification is shown.
+                if (getCurrentRoamingNotification() != ROAMING_NOTIFICATION_NO_NOTIFICATION) {
+                    updateDataRoamingNotification(ROAMING_NOTIFICATION_NO_NOTIFICATION);
+                }
+            }
+        } else if (dataAllowed && dataIsNowRoaming) {
+            if (!shownInThisNumeric && roamingNumeric != null) {
+                mShownNotificationReasons.add(roamingNumericReason);
+            }
+            if (!shownForThisReason
+                    && reason == ROAMING_NOTIFICATION_REASON_CARRIER_CONFIG_CHANGED) {
+                mShownNotificationReasons.add(callingReason);
+            }
+            boolean shouldShowRoamingNotification = shouldShowRoamingNotification(roamingNumeric);
+            // No need to show it again if we never cancelled it explicitly.
+            if (getCurrentRoamingNotification() == ROAMING_NOTIFICATION_CONNECTED) {
+                return;
+            }
+
+            // Inform users that roaming charges may apply.
+            if (!shownInThisNumeric && !shownForThisReason && shouldShowRoamingNotification) {
+                updateDataRoamingNotification(ROAMING_NOTIFICATION_CONNECTED);
+            } else {
+                // Don't show roaming notification if we've already shown for this MccMnc or
+                // disabled from carrier config.
+                Log.d(LOG_TAG, "Skip roaming connected notification since already"
+                        + " shownInThisNumeric:" + shownInThisNumeric
+                        + " shownForThisReason:" + shownForThisReason
+                        + " shouldShowRoamingNotification:" + shouldShowRoamingNotification);
+                // Dismiss notification if the other notification is shown.
+                if (getCurrentRoamingNotification() != ROAMING_NOTIFICATION_NO_NOTIFICATION) {
+                    updateDataRoamingNotification(ROAMING_NOTIFICATION_NO_NOTIFICATION);
+                }
+            }
+        } else if (getCurrentRoamingNotification() != ROAMING_NOTIFICATION_NO_NOTIFICATION) {
+            // Otherwise we either 1) we are not roaming or 2) roaming is off but ROAMING_DISABLED
+            // is not the only data disable reason. In this case we dismiss the notification we
+            // showed earlier.
+            updateDataRoamingNotification(ROAMING_NOTIFICATION_NO_NOTIFICATION);
+        }
     }
 
+    private void updateDataRoamingNotification(@RoamingNotification int roamingNotification) {
+        int event;
+        switch (roamingNotification) {
+            case ROAMING_NOTIFICATION_NO_NOTIFICATION:
+                Log.d(LOG_TAG, "Dismiss roaming notification");
+                mDataRoamingNotifLog.log("Hide roaming.");
+                event = EVENT_DATA_ROAMING_OK;
+                break;
+            case ROAMING_NOTIFICATION_CONNECTED:
+                Log.d(LOG_TAG, "Show roaming connected notification");
+                mDataRoamingNotifLog.log("Show roaming on.");
+                event = EVENT_DATA_ROAMING_CONNECTED;
+                break;
+            case ROAMING_NOTIFICATION_DISCONNECTED:
+                Log.d(LOG_TAG, "Show roaming disconnected notification");
+                mDataRoamingNotifLog.log("Show roaming off.");
+                event = EVENT_DATA_ROAMING_DISCONNECTED;
+                break;
+            default:
+                Log.d(LOG_TAG, "Should never reach here.");
+                mDataRoamingNotifLog.log("Should never reach here.");
+                return;
+        }
+        mCurrentRoamingNotification = roamingNotification;
+        mHandler.obtainMessage(event, mDefaultDataSubId, 0).sendToTarget();
+    }
+
+    private @RoamingNotification int getCurrentRoamingNotification() {
+        return mCurrentRoamingNotification;
+    }
+
+    // For reorganize_roaming_notification feature disabled.
     /**
      * When roaming, if mobile data cannot be established due to data roaming not enabled, we need
      * to notify the user so they can enable it through settings. Vise versa if the condition
@@ -918,8 +1128,9 @@
      * @param roamingOperatorNumeric The operator numeric for the current roaming. {@code null} if
      *                               the current roaming operator numeric didn't change.
      */
-    private void updateDataRoamingStatus(@Nullable String roamingOperatorNumeric) {
-        if (VDBG) Log.v(LOG_TAG, "updateDataRoamingStatus");
+    private void updateDataRoamingStatusForFeatureDisabled(
+            @Nullable String roamingOperatorNumeric) {
+        if (VDBG) Log.v(LOG_TAG, "updateDataRoamingStatusForFeatureDisabled");
         Phone phone = getPhone(mDefaultDataSubId);
         if (phone == null) {
             Log.w(LOG_TAG, "Can't get phone with sub id = " + mDefaultDataSubId);
@@ -959,10 +1170,10 @@
             msg.arg1 = mDefaultDataSubId;
             msg.sendToTarget();
         } else if (dataAllowed && dataIsNowRoaming(mDefaultDataSubId)) {
-            boolean isShowRoamingNotificationEnabled = getCarrierConfigForSubId(mDefaultDataSubId)
-                    .getBoolean(CarrierConfigManager
-                            .KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL);
-            if (!isShowRoamingNotificationEnabled) return;
+            if (!shouldShowRoamingNotification(roamingOperatorNumeric)) {
+                Log.d(LOG_TAG, "Skip showing roaming connected notification.");
+                return;
+            }
             // Don't show roaming notification if we've already shown for this MccMnc
             if (roamingOperatorNumeric != null
                     && !mPrevRoamingOperatorNumerics.add(roamingOperatorNumeric)) {
@@ -999,6 +1210,46 @@
         return getPhone(subId).getServiceState().getDataRoaming();
     }
 
+    private boolean shouldShowRoamingNotification(String roamingNumeric) {
+        PersistableBundle config = getCarrierConfigForSubId(mDefaultDataSubId);
+        boolean showRoamingNotification = config.getBoolean(
+                CarrierConfigManager.KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL);
+
+        if (TextUtils.isEmpty(roamingNumeric) || !mFeatureFlags.hideRoamingIcon()) {
+            Log.d(LOG_TAG, "shouldShowRoamingNotification: roamingNumeric=" + roamingNumeric
+                    + ", hideRoaming=" + mFeatureFlags.hideRoamingIcon());
+            return showRoamingNotification;
+        }
+
+        String[] includedMccMncs = config.getStringArray(CarrierConfigManager
+                .KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_INCLUDED_MCC_MNCS_STRING_ARRAY);
+        if (includedMccMncs != null) {
+            for (String mccMnc : includedMccMncs) {
+                if (roamingNumeric.equals(mccMnc)) {
+                    Log.d(LOG_TAG, "shouldShowRoamingNotification: show for MCC/MNC " + mccMnc);
+                    return showRoamingNotification;
+                }
+            }
+        }
+
+        String[] excludedMccs = config.getStringArray(CarrierConfigManager
+                .KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY);
+        String roamingMcc = roamingNumeric.length() < 3 ? "" : roamingNumeric.substring(0, 3);
+        if (excludedMccs != null && !TextUtils.isEmpty(roamingMcc)) {
+            for (String mcc : excludedMccs) {
+                if (roamingMcc.equals(mcc)) {
+                    Log.d(LOG_TAG, "shouldShowRoamingNotification: ignore for MCC " + mcc);
+                    return false;
+                }
+            }
+        }
+
+        if (showRoamingNotification) {
+            Log.d(LOG_TAG, "shouldShowRoamingNotification: show for numeric " + roamingNumeric);
+        }
+        return showRoamingNotification;
+    }
+
     private void updateLimitedSimFunctionForDualSim() {
         if (DBG) Log.d(LOG_TAG, "updateLimitedSimFunctionForDualSim");
         // check conditions to display limited SIM function notification under dual SIM
@@ -1086,10 +1337,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();
@@ -1127,7 +1389,11 @@
             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..8f3281c 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -17,6 +17,7 @@
 package com.android.phone;
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.permission.flags.Flags.opEnableMobileDataByUser;
 import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK;
 import static android.telephony.TelephonyManager.HAL_SERVICE_RADIO;
 
@@ -146,10 +147,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.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;
@@ -208,6 +213,7 @@
 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;
@@ -296,8 +302,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,12 +401,15 @@
 
     // 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;
 
     /** The singleton instance. */
     private static PhoneInterfaceManager sInstance;
     private static List<String> sThermalMitigationAllowlistedPackages = new ArrayList<>();
 
     private final PhoneGlobals mApp;
+    private final FeatureFlags mFeatureFlags;
     private final CallManager mCM;
     private final ImsResolver mImsResolver;
 
@@ -1154,19 +1161,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 +2220,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 +2434,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 +2446,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();
@@ -2745,6 +2740,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 +2778,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;
         }
@@ -3031,14 +3031,15 @@
                 + ",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);
         }
@@ -3058,14 +3059,15 @@
 
         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);
         }
@@ -4389,6 +4391,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
@@ -7518,39 +7590,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);
@@ -8086,7 +8125,7 @@
      */
     private List<SubscriptionInfo> getActiveSubscriptionInfoListPrivileged() {
         return getSubscriptionManagerService().getActiveSubscriptionInfoList(
-                mApp.getOpPackageName(), mApp.getAttributionTag());
+                mApp.getOpPackageName(), mApp.getAttributionTag(), true/*isForAllProfile*/);
     }
 
     private ActivityStatsTechSpecificInfo[] mLastModemActivitySpecificInfo = null;
@@ -8865,6 +8904,12 @@
             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);
+        }
+
         final long identity = Binder.clearCallingIdentity();
         try {
             Phone phone = getPhone(subId);
@@ -9526,10 +9571,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);
@@ -11586,7 +11627,7 @@
         }
         final long identity = Binder.clearCallingIdentity();
         try {
-            return SlicePurchaseController.getInstance(phone)
+            return SlicePurchaseController.getInstance(phone, mFeatureFlags)
                     .isPremiumCapabilityAvailableForPurchase(capability);
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -11969,6 +12010,18 @@
         }
     }
 
+    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");
+        }
+    }
+
     /**
      * Get the SIM state for the slot index.
      * For Remote-SIMs, this method returns {@link IccCardConstants.State#UNKNOWN}
@@ -12276,13 +12329,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,12 +12379,12 @@
      * @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,
+    @SatelliteManager.SatelliteResult public int registerForSatelliteModemStateChanged(int subId,
             @NonNull ISatelliteStateCallback callback) {
         enforceSatelliteCommunicationPermission("registerForSatelliteModemStateChanged");
         return mSatelliteController.registerForSatelliteModemStateChanged(subId, callback);
@@ -12360,12 +12413,12 @@
      * @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 registerForSatelliteDatagram(int subId,
             @NonNull ISatelliteDatagramCallback callback) {
         enforceSatelliteCommunicationPermission("registerForSatelliteDatagram");
         return mSatelliteController.registerForSatelliteDatagram(subId, callback);
@@ -12396,7 +12449,7 @@
      * {@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.
      */
@@ -12420,7 +12473,7 @@
      *                 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.
      */
@@ -12479,9 +12532,193 @@
      */
     @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 addSatelliteAttachRestrictionForCarrier(int subId,
+            @SatelliteManager.SatelliteCommunicationRestrictionReason int reason,
+            @NonNull IIntegerConsumer callback) {
+        enforceSatelliteCommunicationPermission("addSatelliteAttachRestrictionForCarrier");
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mSatelliteController.addSatelliteAttachRestrictionForCarrier(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 removeSatelliteAttachRestrictionForCarrier(int subId,
+            @SatelliteManager.SatelliteCommunicationRestrictionReason int reason,
+            @NonNull IIntegerConsumer callback) {
+        enforceSatelliteCommunicationPermission("removeSatelliteAttachRestrictionForCarrier");
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mSatelliteController.removeSatelliteAttachRestrictionForCarrier(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[] getSatelliteAttachRestrictionReasonsForCarrier(
+            int subId) {
+        enforceSatelliteCommunicationPermission("getSatelliteAttachRestrictionReasonsForCarrier");
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            Set<Integer> reasonSet =
+                    mSatelliteController.getSatelliteAttachRestrictionReasonsForCarrier(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 registerForSatelliteCapabilitiesChanged(
+            int subId, @NonNull ISatelliteCapabilitiesCallback callback) {
+        enforceSatelliteCommunicationPermission("registerForSatelliteCapabilitiesChanged");
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return mSatelliteController.registerForSatelliteCapabilitiesChanged(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 #registerForSatelliteCapabilitiesChanged(int, ISatelliteCapabilitiesCallback)}.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     */
+    @Override
+    public void unregisterForSatelliteCapabilitiesChanged(
+            int subId, @NonNull ISatelliteCapabilitiesCallback callback) {
+        enforceSatelliteCommunicationPermission("unregisterForSatelliteCapabilitiesChanged");
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mSatelliteController.unregisterForSatelliteCapabilitiesChanged(subId, callback);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     /**
@@ -12574,6 +12811,96 @@
     }
 
     /**
+     * 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 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();
+    }
+
+    /**
      * 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:
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..5986a7c 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -189,6 +189,10 @@
             "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_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 +384,10 @@
                 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();
             default: {
                 return handleDefaultCommands(cmd);
             }
@@ -779,6 +787,14 @@
         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.");
     }
 
     private void onHelpImei() {
@@ -3217,6 +3233,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 +3346,54 @@
         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 handleCarrierRestrictionStatusCommand() {
         try {
             String MOCK_MODEM_SERVICE_NAME = "android.telephony.mockmodem.MockModemService";
@@ -3377,7 +3490,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..62fbd18
--- /dev/null
+++ b/src/com/android/phone/satellite/accesscontrol/S2RangeSatelliteOnDeviceAccessController.java
@@ -0,0 +1,156 @@
+/*
+ * 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);
+    }
+
+    @Override
+    public LocationToken createLocationTokenForLatLng(double latDegrees, double lngDegrees) {
+        return new LocationTokenImpl(getS2CellId(latDegrees, lngDegrees).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());
+    }
+
+    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 S2CellId getS2CellId(double latDegrees, double lngDegrees) {
+        // 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(mS2Level);
+    }
+
+    @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..7f9c1aa
--- /dev/null
+++ b/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java
@@ -0,0 +1,116 @@
+/*
+ * 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.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ResultReceiver;
+import android.telephony.Rlog;
+import android.telephony.satellite.SatelliteManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlags;
+
+/**
+ * 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";
+
+    private static final int CMD_IS_SATELLITE_COMMUNICATION_ALLOWED = 1;
+
+    /** Feature flags to control behavior and errors. */
+    @NonNull private final FeatureFlags mFeatureFlags;
+    @Nullable private final SatelliteOnDeviceAccessController mSatelliteOnDeviceAccessController;
+
+    /**
+     * Create a SatelliteAccessController instance.
+     *
+     * @param featureFlags The FeatureFlags that are supported.
+     * @param looper The Looper to run the SatelliteAccessController on.
+     * @param satelliteOnDeviceAccessController The location-based satellite restriction lookup.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public SatelliteAccessController(@NonNull FeatureFlags featureFlags, @NonNull Looper looper,
+            @Nullable SatelliteOnDeviceAccessController satelliteOnDeviceAccessController) {
+        super(looper);
+        mFeatureFlags = featureFlags;
+        mSatelliteOnDeviceAccessController = satelliteOnDeviceAccessController;
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case CMD_IS_SATELLITE_COMMUNICATION_ALLOWED:
+                handleRequestIsSatelliteCommunicationAllowedForCurrentLocation(
+                        (ResultReceiver) msg.obj);
+                break;
+            default:
+                logw("SatelliteAccessControllerHandler: unexpected message code: " + msg.what);
+                break;
+        }
+    }
+
+    /**
+     * Request to get whether satellite communication is allowed for the current location.
+     *
+     * @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 requestIsSatelliteCommunicationAllowedForCurrentLocation(
+            @NonNull ResultReceiver result) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("oemEnabledSatelliteFlag is disabled");
+            result.send(SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, null);
+            return;
+        }
+        sendRequestAsync(CMD_IS_SATELLITE_COMMUNICATION_ALLOWED, result);
+    }
+
+    private void handleRequestIsSatelliteCommunicationAllowedForCurrentLocation(
+            @NonNull ResultReceiver result) {
+        // To be implemented
+    }
+
+    /**
+     * 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..9292f33
--- /dev/null
+++ b/src/com/android/phone/satellite/accesscontrol/SatelliteOnDeviceAccessController.java
@@ -0,0 +1,80 @@
+/*
+ * 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.
+     *
+     * @throws IOException in the unlikely event of errors when reading the underlying file
+     */
+    public abstract LocationToken createLocationTokenForLatLng(double latDegrees, double lngDegrees)
+            throws IOException;
+
+    /**
+     * 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;
+
+    /**
+     * 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/settings/PhoneAccountSettingsFragment.java b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
index 7cc9235..976afd4 100644
--- a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
+++ b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
@@ -24,6 +24,7 @@
 import android.util.Log;
 
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.Flags;
 import com.android.phone.PhoneUtils;
 import com.android.phone.R;
 import com.android.phone.SubscriptionInfoHelper;
@@ -94,6 +95,9 @@
         mTelecomManager = getActivity().getSystemService(TelecomManager.class);
         mTelephonyManager = TelephonyManager.from(getActivity());
         mSubscriptionManager = SubscriptionManager.from(getActivity());
+        if (Flags.workProfileApiSplit()) {
+            mSubscriptionManager = mSubscriptionManager.createForAllUserProfiles();
+        }
     }
 
     @Override
diff --git a/src/com/android/phone/settings/RadioInfo.java b/src/com/android/phone/settings/RadioInfo.java
index 124badf..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;
@@ -207,6 +208,7 @@
     private static final int EVENT_QUERY_SMSC_DONE = 1005;
     private static final int EVENT_UPDATE_SMSC_DONE = 1006;
     private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED = 1007;
+    private static final int EVENT_UPDATE_NR_STATS = 1008;
 
     private static final int MENU_ITEM_VIEW_ADN            = 1;
     private static final int MENU_ITEM_VIEW_FDN            = 2;
@@ -379,7 +381,14 @@
             updateNetworkType();
             updateRawRegistrationState(serviceState);
             updateImsProvisionedState();
-            updateNrStats(serviceState);
+
+            // Since update NR stats includes a ril message to get slicing information, it runs
+            // as blocking during the timeout period of 1 second. if ServiceStateChanged event
+            // fires consecutively, RadioInfo can run for more than 10 seconds. This can cause ANR.
+            // Therefore, send event only when there is no same event being processed.
+            if (!mHandler.hasMessages(EVENT_UPDATE_NR_STATS)) {
+                mHandler.obtainMessage(EVENT_UPDATE_NR_STATS).sendToTarget();
+            }
         }
 
         @Override
@@ -465,6 +474,10 @@
                     }
                     updatePhysicalChannelConfiguration((List<PhysicalChannelConfig>) ar.result);
                     break;
+                case EVENT_UPDATE_NR_STATS:
+                    log("got EVENT_UPDATE_NR_STATS");
+                    updateNrStats();
+                    break;
                 default:
                     super.handleMessage(msg);
                     break;
@@ -482,6 +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");
@@ -690,7 +712,7 @@
         updateProperties();
         updateDnsCheckState();
         updateNetworkType();
-        updateNrStats(null);
+        updateNrStats();
 
         updateCellInfo(mCellInfoResult);
         updateSubscriptionIds();
@@ -835,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
@@ -1235,15 +1259,12 @@
                     AccessNetworkConstants.TRANSPORT_TYPE_WLAN));
     }
 
-    private void updateNrStats(ServiceState serviceState) {
+    private void updateNrStats() {
         if ((mTelephonyManager.getSupportedRadioAccessFamily()
                 & TelephonyManager.NETWORK_TYPE_BITMASK_NR) == 0) {
             return;
         }
-        ServiceState ss = serviceState;
-        if (ss == null && mPhone != null) {
-            ss = mPhone.getServiceState();
-        }
+        ServiceState ss = (mPhone == null) ? null : mPhone.getServiceState();
         if (ss != null) {
             NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
                     NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
@@ -1257,6 +1278,13 @@
             }
             mNrState.setText(NetworkRegistrationInfo.nrStateToString(ss.getNrState()));
             mNrFrequency.setText(ServiceState.frequencyRangeToString(ss.getNrFrequencyRange()));
+        } else {
+            Log.e(TAG, "Clear Nr stats by null service state");
+            mEndcAvailable.setText("");
+            mDcnrRestricted.setText("");
+            mNrAvailable.setText("");
+            mNrState.setText("");
+            mNrFrequency.setText("");
         }
 
         CompletableFuture<NetworkSlicingConfig> resultFuture = new CompletableFuture<>();
@@ -1539,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/PremiumNetworkEntitlementApi.java b/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java
index d5ef816..3bfe1a4 100644
--- a/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java
+++ b/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java
@@ -46,6 +46,7 @@
     private static final String PROVISION_STATUS_KEY = "ProvStatus";
     private static final String SERVICE_FLOW_URL_KEY = "ServiceFlow_URL";
     private static final String SERVICE_FLOW_USERDATA_KEY = "ServiceFlow_UserData";
+    private static final String SERVICE_FLOW_CONTENTS_TYPE_KEY = "ServiceFlow_ContentsType";
     private static final String DEFAULT_EAP_AKA_RESPONSE = "Default EAP AKA response";
     /**
      * UUID to report an anomaly if an unexpected error is received during entitlement check.
@@ -120,13 +121,11 @@
         }
         try {
             JSONObject jsonAuthResponse = new JSONObject(response);
-            String entitlementStatus = null;
-            String provisionStatus = null;
             if (jsonAuthResponse.has(ServiceEntitlement.APP_DATA_PLAN_BOOST)) {
                 JSONObject jsonToken = jsonAuthResponse.getJSONObject(
                         ServiceEntitlement.APP_DATA_PLAN_BOOST);
                 if (jsonToken.has(ENTITLEMENT_STATUS_KEY)) {
-                    entitlementStatus = jsonToken.getString(ENTITLEMENT_STATUS_KEY);
+                    String entitlementStatus = jsonToken.getString(ENTITLEMENT_STATUS_KEY);
                     if (entitlementStatus == null) {
                         return null;
                     }
@@ -134,7 +133,7 @@
                             Integer.parseInt(entitlementStatus);
                 }
                 if (jsonToken.has(PROVISION_STATUS_KEY)) {
-                    provisionStatus = jsonToken.getString(PROVISION_STATUS_KEY);
+                    String provisionStatus = jsonToken.getString(PROVISION_STATUS_KEY);
                     if (provisionStatus != null) {
                         premiumNetworkEntitlementResponse.mProvisionStatus =
                                 Integer.parseInt(provisionStatus);
@@ -148,6 +147,10 @@
                     premiumNetworkEntitlementResponse.mServiceFlowUserData =
                             jsonToken.getString(SERVICE_FLOW_USERDATA_KEY);
                 }
+                if (jsonToken.has(SERVICE_FLOW_CONTENTS_TYPE_KEY)) {
+                    premiumNetworkEntitlementResponse.mServiceFlowContentsType =
+                            jsonToken.getString(SERVICE_FLOW_CONTENTS_TYPE_KEY);
+                }
             } else {
                 Log.e(TAG, "queryEntitlementStatus failed with no app");
             }
diff --git a/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java b/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java
index ba44581..9126244 100644
--- a/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java
+++ b/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java
@@ -25,19 +25,19 @@
  *
  * The relationship between entitlement status (left column) and provision status (top row)
  * is defined in the table below:
- * +--------------+-----------------+-------------------+-------------------+---------------+
- * |              | Not Provisioned |    Provisioned    |   Not Available   |  In Progress  |
- * +--------------+-----------------+-------------------+-------------------+---------------+
- * |   Disabled   |   Check failed  |    Check failed   |    Check failed   |  Check failed |
- * +--------------+-----------------+-------------------+-------------------+---------------+
- * |    Enabled   |  Carrier error  |  Display webview  |  Display webview  | Carrier error |
- * +--------------+-----------------+-------------------+-------------------+---------------+
- * | Incompatible |   Check failed  |    Check failed   |    Check failed   |  Check failed |
- * +--------------+-----------------+-------------------+-------------------+---------------+
- * | Provisioning |  Carrier error  |   Carrier error   |    In Progress    |  In Progress  |
- * +--------------+-----------------+-------------------+-------------------+---------------+
- * |   Included   |  Carrier error  | Already purchased | Already purchased | Carrier error |
- * +--------------+-----------------+-------------------+-------------------+---------------+
+ * +--------------+-----------------+-------------------+-------------------+-----------------+
+ * |              | Not Provisioned |    Provisioned    |   Not Available   |   In Progress   |
+ * +--------------+-----------------+-------------------+-------------------+-----------------+
+ * |   Disabled   |   Check failed  |    Check failed   |    Check failed   |   Check failed  |
+ * +--------------+-----------------+-------------------+-------------------+-----------------+
+ * |    Enabled   | Display webview | Already purchased | Already purchased |   In progress   |
+ * +--------------+-----------------+-------------------+-------------------+-----------------+
+ * | Incompatible |   Check failed  |    Check failed   |    Check failed   |   Check failed  |
+ * +--------------+-----------------+-------------------+-------------------+-----------------+
+ * | Provisioning |  Carrier error  |   Carrier error   |    In progress    |   In progress   |
+ * +--------------+-----------------+-------------------+-------------------+-----------------+
+ * |   Included   |  Carrier error  | Already purchased | Already purchased |  Carrier error  |
+ * +--------------+-----------------+-------------------+-------------------+-----------------+
  */
 public class PremiumNetworkEntitlementResponse {
     public static final int PREMIUM_NETWORK_ENTITLEMENT_STATUS_DISABLED = 0;
@@ -72,13 +72,19 @@
     @PremiumNetworkProvisionStatus public int mProvisionStatus;
     @NonNull public String mServiceFlowURL;
     @NonNull public String mServiceFlowUserData;
+    @NonNull public String mServiceFlowContentsType;
 
     /**
-     * @return {@code true} if the premium network is provisioned and {@code false} otherwise.
+     * @return {@code true} if the premium network is already purchased and {@code false} otherwise.
      */
-    public boolean isProvisioned() {
-        return !isInvalidResponse()
-                && mEntitlementStatus == PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCLUDED;
+    public boolean isAlreadyPurchased() {
+        switch (mEntitlementStatus) {
+            case PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED:
+            case PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCLUDED:
+                return mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_PROVISIONED
+                        || mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_NOT_AVAILABLE;
+        }
+        return false;
     }
 
     /**
@@ -86,8 +92,14 @@
      *         {@code false} otherwise.
      */
     public boolean isProvisioningInProgress() {
-        return !isInvalidResponse()
-                && mEntitlementStatus == PREMIUM_NETWORK_ENTITLEMENT_STATUS_PROVISIONING;
+        switch (mEntitlementStatus) {
+            case PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED:
+                return mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_IN_PROGRESS;
+            case PREMIUM_NETWORK_ENTITLEMENT_STATUS_PROVISIONING:
+                return mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_IN_PROGRESS
+                        || mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_NOT_AVAILABLE;
+        }
+        return false;
     }
 
     /**
@@ -108,7 +120,6 @@
      */
     public boolean isInvalidResponse() {
         switch (mEntitlementStatus) {
-            case PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED:
             case PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCLUDED:
                 return mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_NOT_PROVISIONED
                         || mProvisionStatus == PREMIUM_NETWORK_PROVISION_STATUS_IN_PROGRESS;
@@ -123,6 +134,7 @@
     @NonNull public String toString() {
         return "PremiumNetworkEntitlementResponse{mEntitlementStatus=" + mEntitlementStatus
                 + ", mProvisionStatus=" + mProvisionStatus + ", mServiceFlowURL=" + mServiceFlowURL
-                + ", mServiceFlowUserData" + mServiceFlowUserData + "}";
+                + ", mServiceFlowUserData=" + mServiceFlowUserData + ", mServiceFlowContentsType="
+                + mServiceFlowContentsType + "}";
     }
 }
diff --git a/src/com/android/phone/slice/SlicePurchaseController.java b/src/com/android/phone/slice/SlicePurchaseController.java
index b1abe56..9a42e16 100644
--- a/src/com/android/phone/slice/SlicePurchaseController.java
+++ b/src/com/android/phone/slice/SlicePurchaseController.java
@@ -48,6 +48,9 @@
 import android.telephony.TelephonyManager;
 import android.telephony.data.NetworkSliceInfo;
 import android.telephony.data.NetworkSlicingConfig;
+import android.telephony.data.RouteSelectionDescriptor;
+import android.telephony.data.TrafficDescriptor;
+import android.telephony.data.UrspRule;
 import android.text.TextUtils;
 import android.util.Log;
 import android.webkit.URLUtil;
@@ -55,6 +58,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -92,12 +96,15 @@
     public static final int FAILURE_CODE_UNKNOWN = 0;
     /** Performance boost purchase failed because the carrier URL is unavailable. */
     public static final int FAILURE_CODE_CARRIER_URL_UNAVAILABLE = 1;
-    /** Performance boost purchase failed because the server is unreachable. */
-    public static final int FAILURE_CODE_SERVER_UNREACHABLE = 2;
     /** Performance boost purchase failed because user authentication failed. */
-    public static final int FAILURE_CODE_AUTHENTICATION_FAILED = 3;
+    public static final int FAILURE_CODE_AUTHENTICATION_FAILED = 2;
     /** Performance boost purchase failed because the payment failed. */
-    public static final int FAILURE_CODE_PAYMENT_FAILED = 4;
+    public static final int FAILURE_CODE_PAYMENT_FAILED = 3;
+    /**
+     * Performance boost purchase failed because the content type was specified but
+     * user data does not exist.
+     */
+    public static final int FAILURE_CODE_NO_USER_DATA = 4;
 
     /**
      * Failure codes that the carrier website can return when a premium capability purchase fails.
@@ -106,9 +113,9 @@
     @IntDef(prefix = { "FAILURE_CODE_" }, value = {
             FAILURE_CODE_UNKNOWN,
             FAILURE_CODE_CARRIER_URL_UNAVAILABLE,
-            FAILURE_CODE_SERVER_UNREACHABLE,
             FAILURE_CODE_AUTHENTICATION_FAILED,
-            FAILURE_CODE_PAYMENT_FAILED})
+            FAILURE_CODE_PAYMENT_FAILED,
+            FAILURE_CODE_NO_USER_DATA})
     public @interface FailureCode {}
 
     /** Value for an invalid premium capability. */
@@ -177,6 +184,12 @@
     private static final String ACTION_SLICE_PURCHASE_APP_RESPONSE_NOT_DEFAULT_DATA_SUBSCRIPTION =
             "com.android.phone.slice.action."
                     + "SLICE_PURCHASE_APP_RESPONSE_NOT_DEFAULT_DATA_SUBSCRIPTION";
+    /**
+     * Action indicating the performance boost notification was not shown because the user
+     * disabled notifications for the application or channel.
+     */
+    private static final String ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED =
+            "com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED";
     /** Action indicating the purchase request was successful. */
     private static final String ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS =
             "com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_SUCCESS";
@@ -209,6 +222,8 @@
     public static final String EXTRA_CARRIER = "com.android.phone.slice.extra.CARRIER";
     /** Extra for the user data received from the entitlement service to send to the webapp. */
     public static final String EXTRA_USER_DATA = "com.android.phone.slice.extra.USER_DATA";
+    /** Extra for the contents type received from the entitlement service to send to the webapp. */
+    public static final String EXTRA_CONTENTS_TYPE = "com.android.phone.slice.extra.CONTENTS_TYPE";
     /**
      * Extra for the canceled PendingIntent that the slice purchase application can send as a
      * response if the performance boost notification or WebView was canceled by the user.
@@ -242,6 +257,14 @@
     public static final String EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION =
             "com.android.phone.slice.extra.INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION";
     /**
+     * Extra for the notifications disabled PendingIntent that the slice purchase application can
+     * send as a response if the premium capability purchase request failed because the user
+     * disabled notifications for the application or channel.
+     * Sends {@link #ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED}.
+     */
+    public static final String EXTRA_INTENT_NOTIFICATIONS_DISABLED =
+            "com.android.phone.slice.extra.INTENT_NOTIFICATIONS_DISABLED";
+    /**
      * Extra for the success PendingIntent that the slice purchase application can send as a
      * response if the premium capability purchase request was successful.
      * Sends {@link #ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS}.
@@ -287,6 +310,8 @@
 
     /** The Phone instance used to create the SlicePurchaseController. */
     @NonNull private final Phone mPhone;
+    /** Feature flags to control behavior and errors. */
+    @NonNull private final FeatureFlags mFeatureFlags;
     /** The set of capabilities that are pending network setup. */
     @NonNull private final Set<Integer> mPendingSetupCapabilities = new HashSet<>();
     /** The set of throttled capabilities. */
@@ -320,7 +345,7 @@
         /**
          * Create a SlicePurchaseControllerBroadcastReceiver for the given capability
          *
-         * @param capability The requested capability to listen to response for.
+         * @param capability The requested premium capability to listen to response for.
          */
         SlicePurchaseControllerBroadcastReceiver(
                 @TelephonyManager.PremiumCapability int capability) {
@@ -391,6 +416,17 @@
                             false);
                     break;
                 }
+                case ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED: {
+                    logd("Slice purchase application unable to show notification for capability: "
+                            + TelephonyManager.convertPremiumCapabilityToString(capability)
+                            + " because the user has disabled notifications.");
+                    int error = mFeatureFlags.slicingAdditionalErrorCodes()
+                            ? TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED
+                            : TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED;
+                    SlicePurchaseController.getInstance(phoneId)
+                            .handlePurchaseResult(capability, error, true);
+                    break;
+                }
                 case ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS: {
                     long duration = intent.getLongExtra(EXTRA_PURCHASE_DURATION, 0);
                     SlicePurchaseController.getInstance(phoneId).onCarrierSuccess(
@@ -417,14 +453,16 @@
      * @param phone The Phone to get the SlicePurchaseController for.
      * @return The static SlicePurchaseController instance.
      */
-    @NonNull public static synchronized SlicePurchaseController getInstance(@NonNull Phone phone) {
+    @NonNull public static synchronized SlicePurchaseController getInstance(@NonNull Phone phone,
+            @NonNull FeatureFlags featureFlags) {
         // TODO: Add listeners for multi sim setting changed (maybe carrier config changed too)
         //  that dismiss notifications and update SlicePurchaseController instance
         int phoneId = phone.getPhoneId();
         if (sInstances.get(phoneId) == null) {
             HandlerThread handlerThread = new HandlerThread("SlicePurchaseController");
             handlerThread.start();
-            sInstances.put(phoneId, new SlicePurchaseController(phone, handlerThread.getLooper()));
+            sInstances.put(phoneId,
+                    new SlicePurchaseController(phone, featureFlags, handlerThread.getLooper()));
         }
         return sInstances.get(phoneId);
     }
@@ -444,18 +482,21 @@
      * Create a SlicePurchaseController for the given phone on the given looper.
      *
      * @param phone The Phone to create the SlicePurchaseController for.
+     * @param featureFlags The FeatureFlags that are supported.
      * @param looper The Looper to run the SlicePurchaseController on.
      */
     @VisibleForTesting
-    public SlicePurchaseController(@NonNull Phone phone, @NonNull Looper looper) {
+    public SlicePurchaseController(@NonNull Phone phone, @NonNull FeatureFlags featureFlags,
+            @NonNull Looper looper) {
         super(looper);
         mPhone = phone;
+        mFeatureFlags = featureFlags;
         // TODO: Create a cached value for slicing config in DataIndication and initialize here
         mPhone.mCi.registerForSlicingConfigChanged(this, EVENT_SLICING_CONFIG_CHANGED, null);
         mIsSlicingUpsellEnabled = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_TELEPHONY, KEY_ENABLE_SLICING_UPSELL, false);
         DeviceConfig.addOnPropertiesChangedListener(
-                DeviceConfig.NAMESPACE_TELEPHONY, this::post,
+                DeviceConfig.NAMESPACE_TELEPHONY, Runnable::run,
                 properties -> {
                     if (TextUtils.equals(DeviceConfig.NAMESPACE_TELEPHONY,
                             properties.getNamespace())) {
@@ -475,6 +516,17 @@
         mLocalDate = localDate;
     }
 
+    /**
+     * Set the NetworkSlicingConfig to use for determining whether the premium capability was
+     * successfully set up on the carrier network.
+     *
+     * @param slicingConfig The LocalDate instance to use.
+     */
+    @VisibleForTesting
+    public void setSlicingConfig(@NonNull NetworkSlicingConfig slicingConfig) {
+        mSlicingConfig = slicingConfig;
+    }
+
     @Override
     public void handleMessage(@NonNull Message msg) {
         switch (msg.what) {
@@ -488,7 +540,8 @@
             case EVENT_SLICING_CONFIG_CHANGED: {
                 AsyncResult ar = (AsyncResult) msg.obj;
                 NetworkSlicingConfig config = (NetworkSlicingConfig) ar.result;
-                logd("EVENT_SLICING_CONFIG_CHANGED: from " + mSlicingConfig + " to " + config);
+                logd("EVENT_SLICING_CONFIG_CHANGED: previous= " + mSlicingConfig);
+                logd("EVENT_SLICING_CONFIG_CHANGED: current= " + config);
                 mSlicingConfig = config;
                 onSlicingConfigChanged();
                 break;
@@ -690,6 +743,17 @@
 
     private void onStartSlicePurchaseApplication(
             @TelephonyManager.PremiumCapability int capability) {
+        updateNotificationCounts();
+        if (mMonthlyCount >= getCarrierConfigs().getInt(
+                CarrierConfigManager.KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT)
+                || mDailyCount >= getCarrierConfigs().getInt(
+                CarrierConfigManager.KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT)) {
+            logd("Reached maximum number of performance boost notifications.");
+            handlePurchaseResult(capability,
+                    TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED, false);
+            return;
+        }
+
         final PremiumNetworkEntitlementApi premiumNetworkEntitlementApi =
                 getPremiumNetworkEntitlementApi();
         PremiumNetworkEntitlementResponse premiumNetworkEntitlementResponse =
@@ -711,8 +775,8 @@
             return;
         }
 
-        if (premiumNetworkEntitlementResponse.isProvisioned()) {
-            logd("Entitlement Check: Already provisioned.");
+        if (premiumNetworkEntitlementResponse.isAlreadyPurchased()) {
+            logd("Entitlement Check: Already purchased/provisioned.");
             handlePurchaseResult(capability,
                     PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED, true);
             return;
@@ -725,7 +789,6 @@
             return;
         }
 
-        String userData = premiumNetworkEntitlementResponse.mServiceFlowUserData;
         String purchaseUrl = getPurchaseUrl(premiumNetworkEntitlementResponse);
         String carrier = getSimOperator();
         if (TextUtils.isEmpty(purchaseUrl) || TextUtils.isEmpty(carrier)) {
@@ -734,17 +797,6 @@
             return;
         }
 
-        updateNotificationCounts();
-        if (mMonthlyCount >= getCarrierConfigs().getInt(
-                CarrierConfigManager.KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT)
-                || mDailyCount >= getCarrierConfigs().getInt(
-                CarrierConfigManager.KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT)) {
-            logd("Reached maximum number of performance boost notifications.");
-            handlePurchaseResult(capability,
-                    TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED, false);
-            return;
-        }
-
         // Start timeout for purchase completion.
         long timeout = getCarrierConfigs().getLong(CarrierConfigManager
                 .KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG);
@@ -761,9 +813,9 @@
         intent.putExtra(EXTRA_PREMIUM_CAPABILITY, capability);
         intent.putExtra(EXTRA_PURCHASE_URL, purchaseUrl);
         intent.putExtra(EXTRA_CARRIER, carrier);
-        if (!TextUtils.isEmpty(userData)) {
-            intent.putExtra(EXTRA_USER_DATA, userData);
-        }
+        intent.putExtra(EXTRA_USER_DATA, premiumNetworkEntitlementResponse.mServiceFlowUserData);
+        intent.putExtra(EXTRA_CONTENTS_TYPE,
+                premiumNetworkEntitlementResponse.mServiceFlowContentsType);
         intent.putExtra(EXTRA_INTENT_CANCELED, createPendingIntent(
                 ACTION_SLICE_PURCHASE_APP_RESPONSE_CANCELED, capability, false));
         intent.putExtra(EXTRA_INTENT_CARRIER_ERROR, createPendingIntent(
@@ -773,6 +825,8 @@
         intent.putExtra(EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION, createPendingIntent(
                 ACTION_SLICE_PURCHASE_APP_RESPONSE_NOT_DEFAULT_DATA_SUBSCRIPTION, capability,
                 false));
+        intent.putExtra(EXTRA_INTENT_NOTIFICATIONS_DISABLED, createPendingIntent(
+                ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED, capability, false));
         intent.putExtra(EXTRA_INTENT_SUCCESS, createPendingIntent(
                 ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS, capability, true));
         intent.putExtra(EXTRA_INTENT_NOTIFICATION_SHOWN, createPendingIntent(
@@ -788,6 +842,7 @@
         filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_CARRIER_ERROR);
         filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_REQUEST_FAILED);
         filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_NOT_DEFAULT_DATA_SUBSCRIPTION);
+        filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED);
         filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS);
         filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN);
         mPhone.getContext().registerReceiver(
@@ -977,7 +1032,8 @@
 
     private long getThrottleDuration(@TelephonyManager.PurchasePremiumCapabilityResult int result) {
         if (result == TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
-                || result == TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT) {
+                || result == TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
+                || result == TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED) {
             return getCarrierConfigs().getLong(CarrierConfigManager
                     .KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG);
         }
@@ -1037,26 +1093,71 @@
         return mPhone.getSubId() == SubscriptionManager.getDefaultDataSubscriptionId();
     }
 
-    private boolean isSlicingConfigActive(@TelephonyManager.PremiumCapability int capability) {
+    /**
+     * Check whether the current network slicing configuration indicates that the given premium
+     * capability is active and set up on the carrier network.
+     * @param capability The premium capability to check for.
+     * @return {@code true} if the slicing config indicates the capability is active and
+     * {@code false} otherwise.
+     */
+    @VisibleForTesting
+    public boolean isSlicingConfigActive(@TelephonyManager.PremiumCapability int capability) {
         if (mSlicingConfig == null) {
             return false;
         }
-        int capabilityServiceType = getSliceServiceType(capability);
-        for (NetworkSliceInfo sliceInfo : mSlicingConfig.getSliceInfo()) {
-            if (sliceInfo.getSliceServiceType() == capabilityServiceType
-                    && sliceInfo.getStatus() == NetworkSliceInfo.SLICE_STATUS_ALLOWED) {
-                return true;
+        for (UrspRule urspRule : mSlicingConfig.getUrspRules()) {
+            for (TrafficDescriptor trafficDescriptor : urspRule.getTrafficDescriptors()) {
+                TrafficDescriptor.OsAppId osAppId =
+                        new TrafficDescriptor.OsAppId(trafficDescriptor.getOsAppId());
+                if (osAppId.getAppId().equals(getAppId(capability))) {
+                    for (RouteSelectionDescriptor rsd : urspRule.getRouteSelectionDescriptor()) {
+                        for (NetworkSliceInfo sliceInfo : rsd.getSliceInfo()) {
+                            if (sliceInfo.getStatus() == NetworkSliceInfo.SLICE_STATUS_ALLOWED
+                                    && getSliceServiceTypes(capability).contains(
+                                            sliceInfo.getSliceServiceType())) {
+                                return true;
+                            }
+                        }
+                    }
+                }
             }
         }
         return false;
     }
 
-    @NetworkSliceInfo.SliceServiceType private int getSliceServiceType(
-            @TelephonyManager.PremiumCapability int capability) {
+    /**
+     * Get the application ID associated with the given premium capability.
+     * The app ID is a field in {@link TrafficDescriptor} that helps match URSP rules to determine
+     * whether the premium capability was successfully set up on the carrier network.
+     * @param capability The premium capability to get the app ID for.
+     * @return The application ID associated with the premium capability.
+     */
+    @VisibleForTesting
+    @NonNull public static String getAppId(@TelephonyManager.PremiumCapability int capability) {
         if (capability == TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY) {
-            return NetworkSliceInfo.SLICE_SERVICE_TYPE_URLLC;
+            return "PRIORITIZE_LATENCY";
         }
-        return NetworkSliceInfo.SLICE_SERVICE_TYPE_NONE;
+        return "";
+    }
+
+    /**
+     * Get the slice service types associated with the given premium capability.
+     * The slice service type is a field in {@link NetworkSliceInfo} that helps to match determine
+     * whether the premium capability was successfully set up on the carrier network.
+     * @param capability The premium capability to get the associated slice service types for.
+     * @return A set of slice service types associated with the premium capability.
+     */
+    @VisibleForTesting
+    @NonNull @NetworkSliceInfo.SliceServiceType public static Set<Integer> getSliceServiceTypes(
+            @TelephonyManager.PremiumCapability int capability) {
+        Set<Integer> sliceServiceTypes = new HashSet<>();
+        if (capability == TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY) {
+            sliceServiceTypes.add(NetworkSliceInfo.SLICE_SERVICE_TYPE_EMBB);
+            sliceServiceTypes.add(NetworkSliceInfo.SLICE_SERVICE_TYPE_URLLC);
+        } else {
+            sliceServiceTypes.add(NetworkSliceInfo.SLICE_SERVICE_TYPE_NONE);
+        }
+        return sliceServiceTypes;
     }
 
     private boolean isNetworkAvailable() {
@@ -1094,9 +1195,9 @@
         switch (failureCode) {
             case FAILURE_CODE_UNKNOWN: return "UNKNOWN";
             case FAILURE_CODE_CARRIER_URL_UNAVAILABLE: return "CARRIER_URL_UNAVAILABLE";
-            case FAILURE_CODE_SERVER_UNREACHABLE: return "SERVER_UNREACHABLE";
             case FAILURE_CODE_AUTHENTICATION_FAILED: return "AUTHENTICATION_FAILED";
             case FAILURE_CODE_PAYMENT_FAILED: return "PAYMENT_FAILED";
+            case FAILURE_CODE_NO_USER_DATA: return "NO_USER_DATA";
             default:
                 return "UNKNOWN(" + failureCode + ")";
         }
diff --git a/src/com/android/services/telephony/DisconnectCauseUtil.java b/src/com/android/services/telephony/DisconnectCauseUtil.java
index d36f8be..adb07f9 100644
--- a/src/com/android/services/telephony/DisconnectCauseUtil.java
+++ b/src/com/android/services/telephony/DisconnectCauseUtil.java
@@ -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 2b69b82..ea29b77 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -16,6 +16,7 @@
 
 package com.android.services.telephony;
 
+import android.app.ActivityManager;
 import android.app.PropertyInvalidatedCache;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -1191,7 +1192,7 @@
     private int mSubscriptionListenerState = LISTENER_STATE_UNREGISTERED;
     private int mServiceState = ServiceState.STATE_POWER_OFF;
     private int mActiveDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-    private boolean mIsPrimaryUser = true;
+    private boolean mIsPrimaryUser = UserHandle.of(ActivityManager.getCurrentUser()).isSystem();
     private ExponentialBackoff mRegisterSubscriptionListenerBackoff;
     private final HandlerThread mHandlerThread = new HandlerThread("TelecomAccountRegistry");
 
diff --git a/src/com/android/services/telephony/TelephonyConference.java b/src/com/android/services/telephony/TelephonyConference.java
index 7e4693f..4a70e1c 100644
--- a/src/com/android/services/telephony/TelephonyConference.java
+++ b/src/com/android/services/telephony/TelephonyConference.java
@@ -23,6 +23,7 @@
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.Flags;
 
 import java.util.List;
 
@@ -128,7 +129,11 @@
     public void onHold() {
         final TelephonyConnection connection = getFirstConnection();
         if (connection != null) {
-            connection.performHold();
+            if (Flags.conferenceHoldUnholdChangedToSendMessage()) {
+                connection.onHold();
+            } else {
+                connection.performHold();
+            }
         }
     }
 
@@ -139,7 +144,11 @@
     public void onUnhold() {
         final TelephonyConnection connection = getFirstConnection();
         if (connection != null) {
-            connection.performUnhold();
+            if (Flags.conferenceHoldUnholdChangedToSendMessage()) {
+                connection.onUnhold();
+            } else {
+                connection.performUnhold();
+            }
         }
     }
 
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 6d136b0..f96e6bd 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -149,6 +149,8 @@
     private static final int MSG_DTMF_DONE = 22;
     private static final int MSG_MEDIA_ATTRIBUTES_CHANGED = 23;
     private static final int MSG_ON_RTT_INITIATED = 24;
+    private static final int MSG_HOLD = 25;
+    private static final int MSG_UNHOLD = 26;
 
     private static final String JAPAN_COUNTRY_CODE_WITH_PLUS_SIGN = "+81";
     private static final String JAPAN_ISO_COUNTRY_CODE = "JP";
@@ -344,6 +346,12 @@
                     }
                     sendRttInitiationSuccess();
                     break;
+                case MSG_HOLD:
+                    performHold();
+                    break;
+                case MSG_UNHOLD:
+                    performUnhold();
+                    break;
             }
         }
     };
@@ -1049,12 +1057,12 @@
 
     @Override
     public void onHold() {
-        performHold();
+        mHandler.obtainMessage(MSG_HOLD).sendToTarget();
     }
 
     @Override
     public void onUnhold() {
-        performUnhold();
+        mHandler.obtainMessage(MSG_UNHOLD).sendToTarget();
     }
 
     @Override
@@ -2515,8 +2523,8 @@
                             }
                         }
 
-                        if (mTelephonyConnectionService.maybeReselectDomain(this,
-                                  mOriginalConnection.getPreciseDisconnectCause(), reasonInfo)) {
+                        if (mTelephonyConnectionService.maybeReselectDomain(this, reasonInfo,
+                                mShowPreciseFailedCause, mHangupDisconnectCause)) {
                             clearOriginalConnection();
                             break;
                         }
@@ -2558,7 +2566,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 bf7ce00..8ab2723 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;
@@ -114,6 +120,7 @@
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 import java.util.regex.Pattern;
+import java.util.stream.Stream;
 
 import javax.annotation.Nullable;
 
@@ -586,6 +593,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
@@ -725,35 +750,45 @@
                 public void onSelectionTerminated(@DisconnectCauses int cause) {
                     mDomainSelectionMainExecutor.execute(new Runnable() {
                         int mCause = cause;
+
                         @Override
                         public void run() {
                             Log.v(this, "Call domain selection terminated.");
                             if (mDomainSelectionConnection != null) {
-                                mDomainSelectionConnection = null;
-                            }
-                            if (mNormalCallConnection != null) {
-                                // TODO: To support ShowPreciseFailedCause, TelephonyConnection
-                                //  .getShowPreciseFailedCause API should be added.
+                                if (mNormalCallConnection != null) {
 
-                                // If cause is NOT_VALID then, it's a redial cancellation and
-                                // use cause code from original connection.
-                                com.android.internal.telephony.Connection connection =
-                                        mNormalCallConnection.getOriginalConnection();
-                                if (connection != null) {
+                                    NormalCallDomainSelectionConnection ncdsConn =
+                                            (NormalCallDomainSelectionConnection)
+                                                    mDomainSelectionConnection;
+
+                                    // If cause is NOT_VALID then, it's a redial cancellation
                                     if (mCause == android.telephony.DisconnectCause.NOT_VALID) {
-                                        mCause = connection.getDisconnectCause();
+                                        mCause = ncdsConn.getDisconnectCause();
                                     }
 
-                                    String reason = connection.getVendorDisconnectCause();
-                                    int phoneId = mNormalCallConnection.getPhone().getPhoneId();
+                                    Log.d(this, "Call connection closed. PreciseCause: "
+                                            + ncdsConn.getPreciseDisconnectCause()
+                                            + " DisconnectCause: " + ncdsConn.getDisconnectCause()
+                                            + " Reason: " + ncdsConn.getReasonMessage());
+
                                     mNormalCallConnection.setTelephonyConnectionDisconnected(
-                                            mDisconnectCauseFactory.toTelecomDisconnectCause(
-                                                    mCause, reason, phoneId));
-                                    Log.d(this, "Call connection closed. Cause: " + mCause
-                                            + " Reason: " + reason);
+                                            DisconnectCauseUtil.toTelecomDisconnectCause(mCause,
+                                                    ncdsConn.getPreciseDisconnectCause(),
+                                                    ncdsConn.getReasonMessage(),
+                                                    ncdsConn.getPhoneId(),
+                                                    ncdsConn.getImsReasonInfo(),
+                                                    new FlagsAdapterImpl()));
+
+                                    mNormalCallConnection.close();
+                                    mNormalCallConnection = null;
+                                } else {
+                                    Log.v(this, "NormalCallConnection is null.");
                                 }
-                                mNormalCallConnection.close();
-                                mNormalCallConnection = null;
+
+                                mDomainSelectionConnection = null;
+
+                            } else {
+                                Log.v(this, "DomainSelectionConnection is null.");
                             }
                         }
                     });
@@ -1061,8 +1096,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.
@@ -1070,6 +1103,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)) {
@@ -1083,7 +1120,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) {
@@ -1171,7 +1208,8 @@
             }
 
             if (!isEmergencyNumber) {
-                if (mSatelliteController.isSatelliteEnabled()) {
+                if (mSatelliteController.isSatelliteEnabled()
+                        || isCallDisallowedDueToSatellite(phone)) {
                     Log.d(this, "onCreateOutgoingConnection, cannot make call in satellite mode.");
                     return Connection.createFailedConnection(
                             mDisconnectCauseFactory.toTelecomDisconnectCause(
@@ -1362,7 +1400,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);
@@ -2249,8 +2287,7 @@
                                         .setVideoState(videoState)
                                         .setIntentExtras(extras)
                                         .setRttTextStream(mNormalCallConnection.getRttTextStream())
-                                        .setIsWpsCall(NormalCallDomainSelectionConnection
-                                                .isWpsCall(number))
+                                        .setIsWpsCall(PhoneNumberUtils.isWpsCallNumber(number))
                                         .build(),
                                 mNormalCallConnection::registerForCallEvents);
 
@@ -2311,7 +2348,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(
@@ -2488,15 +2525,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) {
@@ -2528,7 +2569,7 @@
             }
         }
 
-        return maybeReselectDomainForNormalCall(c, callFailCause, reasonInfo);
+        return maybeReselectDomainForNormalCall(c, reasonInfo, showPreciseCause, overrideCause);
     }
 
     private boolean maybeReselectDomainForEmergencyCall(final TelephonyConnection c,
@@ -2573,17 +2614,63 @@
         return false;
     }
 
-    private boolean isNormalRouting(Phone phone, String number) {
-        if (phone.getEmergencyNumberTracker() != null) {
-            EmergencyNumber num = phone.getEmergencyNumberTracker().getEmergencyNumber(number);
-            if (num != null) {
-                return num.getEmergencyCallRouting()
-                        == EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL;
+    private boolean isEmergencyNumberAllowedOnDialedSim(Phone phone, String number) {
+        CarrierConfigManager cfgManager = (CarrierConfigManager)
+                phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (cfgManager != null) {
+            PersistableBundle b = cfgManager.getConfigForSubId(phone.getSubId(),
+                    KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL);
+            if (b == null) {
+                b = CarrierConfigManager.getDefaultConfig();
+            }
+            // We need to check only when KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL is true.
+            if (b.getBoolean(KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL, false)
+                      && (phone.getEmergencyNumberTracker() != null)) {
+                if (!phone.getEmergencyNumberTracker().isEmergencyNumber(number)) {
+                    Log.i(this, "isEmergencyNumberAllowedOnDialedSim false");
+                    return false;
+                }
             }
         }
+        return true;
+    }
+
+    private boolean isNormalRouting(Phone phone, String number) {
+        // Check isEmergencyNumberAllowedOnDialedSim(): some carriers do not want to handle
+        // dial requests for numbers which are in the emergency number list on another SIM,
+        // but not on their own. Such numbers shall be handled by normal call domain selector.
+        return (isNormalRoutingNumber(phone, number)
+                || !isEmergencyNumberAllowedOnDialedSim(phone, number));
+    }
+
+    private boolean isNormalRoutingNumber(Phone phone, String number) {
+        if (phone.getEmergencyNumberTracker() != null) {
+            // Note: There can potentially be multiple instances of EmergencyNumber found; if any of
+            // them have normal routing, then use normal routing.
+            List<EmergencyNumber> nums = phone.getEmergencyNumberTracker().getEmergencyNumbers(
+                    number);
+            return nums.stream().anyMatch(n ->
+                    n.getEmergencyCallRouting() == EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+        }
         return false;
     }
 
+    /**
+     * Determines the phone to use for a normal routed emergency call.
+     * @param number The emergency number.
+     * @return The {@link Phone} to place the normal routed emergency call on, or {@code null} if
+     * none was found.
+     */
+    @VisibleForTesting
+    public Phone getPhoneForNormalRoutedEmergencyCall(String number) {
+        return Stream.of(mPhoneFactoryProxy.getPhones())
+                .filter(p -> p.shouldPreferInServiceSimForNormalRoutedEmergencyCall()
+                        && isNormalRoutingNumber(p, number)
+                        && isAvailableForEmergencyCalls(p,
+                                EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL))
+                .findFirst().orElse(null);
+    }
+
     private boolean isVoiceInService(Phone phone, boolean imsVoiceCapable) {
         // Dialing normal call is available.
         if (phone.isWifiCallingEnabled()) {
@@ -2623,25 +2710,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);
@@ -3031,15 +3138,34 @@
         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
             int phoneId = mSubscriptionManagerProxy.getPhoneId(subId);
             chosenPhone = mPhoneFactoryProxy.getPhone(phoneId);
+            Log.i(this, "getPhoneForAccount: handle=%s, subId=%s", accountHandle,
+                    (chosenPhone == null ? "null" : chosenPhone.getSubId()));
         }
-        // If this is an emergency call and the phone we originally planned to make this call
+
+        // If this isn't an emergency call, just use the chosen phone (or null if none was found).
+        if (!isEmergency) {
+            return chosenPhone;
+        }
+
+        // Check if this call should be treated as a normal routed emergency call; we'll return null
+        // if this is not a normal routed emergency call.
+        Phone normalRoutingPhone = getPhoneForNormalRoutedEmergencyCall(emergencyNumberAddress);
+        if (normalRoutingPhone != null) {
+            Log.i(this, "getPhoneForAccount: normal routed emergency number,"
+                            + "using phoneId=%d/subId=%d", normalRoutingPhone.getPhoneId(),
+                    normalRoutingPhone.getSubId());
+            return normalRoutingPhone;
+        }
+
+        // Default emergency call phone selection logic:
+        // This is an emergency call and the phone we originally planned to make this call
         // with is not in service or was invalid, try to find one that is in service, using the
         // default as a last chance backup.
-        if (isEmergency && (chosenPhone == null || !isAvailableForEmergencyCalls(chosenPhone))) {
+        if (chosenPhone == null || !isAvailableForEmergencyCalls(chosenPhone)) {
             Log.d(this, "getPhoneForAccount: phone for phone acct handle %s is out of service "
                     + "or invalid for emergency call.", accountHandle);
             chosenPhone = getPhoneForEmergencyCall(emergencyNumberAddress);
-            Log.d(this, "getPhoneForAccount: using subId: " +
+            Log.i(this, "getPhoneForAccount: emergency call - using subId: %s",
                     (chosenPhone == null ? "null" : chosenPhone.getSubId()));
         }
         return chosenPhone;
@@ -3459,10 +3585,20 @@
         }
     }
 
-    /**
-     * Returns true if the state of the Phone is IN_SERVICE or available for emergency calling only.
-     */
     private boolean isAvailableForEmergencyCalls(Phone phone) {
+        return isAvailableForEmergencyCalls(phone,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+    }
+
+    /**
+     * Determines if the phone is available for an emergency call given the specified routing.
+     *
+     * @param phone the phone to check the service availability for
+     * @param routing the emergency call routing for this call
+     */
+    @VisibleForTesting
+    public boolean isAvailableForEmergencyCalls(Phone phone,
+            @EmergencyNumber.EmergencyCallRouting int routing) {
         if (phone.getImsRegistrationTech() == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
             // When a Phone is registered to Cross-SIM calling, there must always be a Phone on the
             // other sub which is registered to cellular, so that must be selected.
@@ -3470,8 +3606,17 @@
                     + phone + " as it is registered to CROSS_SIM");
             return false;
         }
-        return ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState() ||
-                phone.getServiceState().isEmergencyOnly();
+
+        // In service phones are always appropriate for emergency calls.
+        if (ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState()) {
+            return true;
+        }
+
+        // If the call routing is unknown or is using emergency routing, an emergency only attach is
+        // sufficient for placing the emergency call.  Normal routed emergency calls cannot be
+        // placed on an emergency-only phone.
+        return (routing != EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL
+                && phone.getServiceState().isEmergencyOnly());
     }
 
     /**
@@ -3852,6 +3997,19 @@
                 });
     }
 
+    static boolean isStateActive(Conferenceable conferenceable) {
+        if (conferenceable instanceof Connection) {
+            Connection connection = (Connection) conferenceable;
+            return connection.getState() == Connection.STATE_ACTIVE;
+        } else if (conferenceable instanceof Conference) {
+            Conference conference = (Conference) conferenceable;
+            return conference.getState() == Connection.STATE_ACTIVE;
+        } else {
+            throw new IllegalArgumentException(
+                    "isStateActive(): Unexpected conferenceable! " + conferenceable);
+        }
+    }
+
     static void onHold(Conferenceable conferenceable) {
         if (conferenceable instanceof Connection) {
             Connection connection = (Connection) conferenceable;
@@ -4011,7 +4169,7 @@
             TelephonyManagerProxy telephonyManagerProxy) {
         Conferenceable c = maybeGetFirstConferenceableFromOtherSubscription(
                 connections, conferences, outgoingHandle, telephonyManagerProxy);
-        if (c != null) {
+        if (c != null && isStateActive(c)) {
             onHold(c);
             return c;
         }
@@ -4061,10 +4219,44 @@
     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);
+    }
+
+    /**
+     * Check whether making a call is disallowed while using satellite
+     * @param phone phone object whose supported services needs to be checked
+     * @return {@code true} if network does not support calls while using satellite
+     * else {@code false}.
+     */
+    private boolean isCallDisallowedDueToSatellite(Phone phone) {
+        if (!carrierEnabledSatelliteFlag()) {
+            return false;
+        }
+
+        if (phone == null) {
+            return false;
+        }
+
+        ServiceState serviceState = phone.getServiceState();
+        if (!serviceState.isUsingNonTerrestrialNetwork()) {
+            // Device is not connected to satellite
+            return false;
+        }
+
+        for (NetworkRegistrationInfo nri : serviceState.getNetworkRegistrationInfoList()) {
+            if (nri.isNonTerrestrialNetwork()
+                    && nri.getAvailableServices().contains(
+                    NetworkRegistrationInfo.SERVICE_TYPE_VOICE)) {
+                // Call is supported while using satellite
+                return false;
+            }
+        }
+
+        // Call is disallowed while using satellite
+        return true;
     }
 }
diff --git a/src/com/android/services/telephony/domainselection/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..3d6a4d1 100644
--- a/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
+++ b/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
@@ -53,9 +53,11 @@
 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 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 +85,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 +119,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 +163,7 @@
 
     private CancellationSignal mCancelSignal;
 
+    // Members for carrier configuration
     private @RadioAccessNetworkType int[] mImsRatsConfig;
     private @RadioAccessNetworkType int[] mCsRatsConfig;
     private @RadioAccessNetworkType int[] mImsRoamRatsConfig;
@@ -180,8 +173,6 @@
     private List<String> mCdmaPreferredNumbers;
     private boolean mPreferImsWhenCallsOnCs;
     private int mVoWifiRequiresCondition;
-    private boolean mIsMonitoringConnectivity;
-    private boolean mWiFiAvailable;
     private int mScanTimeout;
     private int mMaxCellularTimeout;
     private int mMaxNumOfVoWifiTries;
@@ -191,6 +182,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 +210,14 @@
 
     private final PowerManager.WakeLock mPartialWakeLock;
     private final CrossSimRedialingController mCrossSimRedialingController;
+    private final CarrierConfigHelper mCarrierConfigHelper;
 
     /** 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) {
         super(context, slotId, subId, looper, imsStateTracker, destroyListener, TAG);
 
         mImsStateTracker.addBarringInfoListener(this);
@@ -229,6 +227,7 @@
         mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
 
         mCrossSimRedialingController = csrController;
+        mCarrierConfigHelper = carrierConfigHelper;
         acquireWakeLock();
     }
 
@@ -367,11 +366,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 +444,7 @@
 
     private void startDomainSelection() {
         logi("startDomainSelection modemCount=" + mModemCount);
+        readResourceConfiguration();
         updateCarrierConfiguration();
         mDomainSelectionRequested = true;
         startCrossStackTimer();
@@ -469,6 +479,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 +499,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,22 +569,61 @@
         }
     }
 
+    /** 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();
@@ -694,8 +745,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 +783,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 +857,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 +1071,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 +1368,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) {
diff --git a/src/com/android/services/telephony/domainselection/NormalCallDomainSelector.java b/src/com/android/services/telephony/domainselection/NormalCallDomainSelector.java
index f176d90..f85dabe 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.
  */
@@ -119,6 +118,12 @@
         mTransportSelectorCallback = null;
     }
 
+    @Override
+    public void destroy() {
+        finishSelection();
+        super.destroy();
+    }
+
     /**
      * Cancel an ongoing selection operation. It is up to the DomainSelectionService
      * to clean up all ongoing operations with the framework.
@@ -233,7 +238,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 +266,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 +299,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 +382,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..66894f7 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,8 @@
                 @SelectorType int selectorType, boolean isEmergency, @NonNull Looper looper,
                 @NonNull ImsStateTracker imsStateTracker,
                 @NonNull DomainSelectorBase.DestroyListener listener,
-                @NonNull CrossSimRedialingController crossSimRedialingController);
+                @NonNull CrossSimRedialingController crossSimRedialingController,
+                @NonNull CarrierConfigHelper carrierConfigHelper);
     }
 
     private static final class DefaultDomainSelectorFactory implements DomainSelectorFactory {
@@ -80,7 +83,8 @@
                 @SelectorType int selectorType, boolean isEmergency, @NonNull Looper looper,
                 @NonNull ImsStateTracker imsStateTracker,
                 @NonNull DomainSelectorBase.DestroyListener listener,
-                @NonNull CrossSimRedialingController crossSimRedialingController) {
+                @NonNull CrossSimRedialingController crossSimRedialingController,
+                @NonNull CarrierConfigHelper carrierConfigHelper) {
             DomainSelectorBase selector = null;
 
             logi("create-DomainSelector: slotId=" + slotId + ", subId=" + subId
@@ -91,7 +95,8 @@
                 case SELECTOR_TYPE_CALLING:
                     if (isEmergency) {
                         selector = new EmergencyCallDomainSelector(context, slotId, subId, looper,
-                                imsStateTracker, listener, crossSimRedialingController);
+                                imsStateTracker, listener, crossSimRedialingController,
+                                carrierConfigHelper);
                     } else {
                         selector = new NormalCallDomainSelector(context, slotId, subId, looper,
                                 imsStateTracker, listener);
@@ -195,15 +200,17 @@
     private final DomainSelectorFactory mDomainSelectorFactory;
     private Handler mServiceHandler;
     private CrossSimRedialingController mCrossSimRedialingController;
+    private CarrierConfigHelper mCarrierConfigHelper;
 
     public TelephonyDomainSelectionService(Context context) {
-        this(context, ImsStateTracker::new, new DefaultDomainSelectorFactory());
+        this(context, ImsStateTracker::new, new DefaultDomainSelectorFactory(), null);
     }
 
     @VisibleForTesting
     public TelephonyDomainSelectionService(Context context,
             @NonNull ImsStateTrackerFactory imsStateTrackerFactory,
-            @NonNull DomainSelectorFactory domainSelectorFactory) {
+            @NonNull DomainSelectorFactory domainSelectorFactory,
+            @Nullable CarrierConfigHelper carrierConfigHelper) {
         mContext = context;
         mImsStateTrackerFactory = imsStateTrackerFactory;
         mDomainSelectorFactory = domainSelectorFactory;
@@ -225,6 +232,8 @@
         }
 
         mCrossSimRedialingController = new CrossSimRedialingController(context, getLooper());
+        mCarrierConfigHelper = (carrierConfigHelper != null)
+                ? carrierConfigHelper : new CarrierConfigHelper(context, getLooper());
 
         logi("TelephonyDomainSelectionService created");
     }
@@ -268,6 +277,11 @@
             mCrossSimRedialingController = null;
         }
 
+        if (mCarrierConfigHelper != null) {
+            mCarrierConfigHelper.destroy();
+            mCarrierConfigHelper = null;
+        }
+
         if (mServiceHandler != null) {
             mServiceHandler.getLooper().quit();
             mServiceHandler = null;
@@ -290,7 +304,7 @@
         ImsStateTracker ist = getImsStateTracker(slotId);
         DomainSelectorBase selector = mDomainSelectorFactory.create(mContext, slotId, subId,
                 selectorType, isEmergency, getLooper(), ist, mDestroyListener,
-                mCrossSimRedialingController);
+                mCrossSimRedialingController, mCarrierConfigHelper);
 
         if (selector != null) {
             // Ensures that ImsStateTracker is started before selecting the domain if not started
@@ -371,6 +385,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/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/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..97f676f
--- /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.SatelliteStateCallback;
+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 SatelliteStateCallbackTestApp 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 SatelliteStateCallbackTestApp();
+        mCallback = new SatelliteTransmissionUpdateCallbackTestApp();
+
+        mReceivedDatagram = new android.telephony.satellite.stub.SatelliteDatagram();
+
+        setContentView(R.layout.activity_Datagram);
+        findViewById(R.id.startSatelliteTransmissionUpdates)
+                .setOnClickListener(this::startSatelliteTransmissionUpdatesApp);
+        findViewById(R.id.stopSatelliteTransmissionUpdates)
+                .setOnClickListener(this::stopSatelliteTransmissionUpdatesApp);
+        findViewById(R.id.pollPendingSatelliteDatagrams)
+                .setOnClickListener(this::pollPendingSatelliteDatagramsApp);
+        findViewById(R.id.sendSatelliteDatagram)
+                .setOnClickListener(this::sendSatelliteDatagramApp);
+        findViewById(R.id.registerForSatelliteDatagram)
+                .setOnClickListener(this::registerForSatelliteDatagramApp);
+        findViewById(R.id.unregisterForSatelliteDatagram)
+                .setOnClickListener(this::unregisterForSatelliteDatagramApp);
+        findViewById(R.id.showDatagramSendStateTransition)
+                .setOnClickListener(this::showDatagramSendStateTransitionApp);
+        findViewById(R.id.showDatagramReceiveStateTransition)
+                .setOnClickListener(this::showDatagramReceiveStateTransitionApp);
+        findViewById(R.id.registerForSatelliteModemStateChanged)
+                .setOnClickListener(this::registerForSatelliteModemStateChangedApp);
+        findViewById(R.id.unregisterForSatelliteModemStateChanged)
+                .setOnClickListener(this::unregisterForSatelliteModemStateChangedApp);
+        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 SatelliteStateCallbackTestApp implements SatelliteStateCallback {
+        @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 startSatelliteTransmissionUpdatesApp(View view) {
+        TextView textView = findViewById(R.id.text_id);
+        LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+        mSatelliteManager.requestSatelliteEnabled(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.startSatelliteTransmissionUpdates(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 stopSatelliteTransmissionUpdatesApp(View view) {
+        TextView textView = findViewById(R.id.text_id);
+        LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+        mSatelliteManager.stopSatelliteTransmissionUpdates(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 pollPendingSatelliteDatagramsApp(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.requestSatelliteEnabled(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.pollPendingSatelliteDatagrams(Runnable::run, resultListener::offer);
+        try {
+            Integer value = resultListener.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+            if (value == null) {
+                textView.setText("Timed out for poll message");
+            } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+                textView.setText("Failed to pollPendingSatelliteDatagrams with error = "
+                        + SatelliteErrorUtils.mapError(value));
+            } else {
+                textView.setText("pollPendingSatelliteDatagrams is successful");
+            }
+        } catch (InterruptedException e) {
+            textView.setText("pollPendingSatelliteDatagrams exception caught =" + e);
+        }
+    }
+
+    private void sendSatelliteDatagramApp(View view) {
+        TextView textView = findViewById(R.id.text_id);
+        mSatelliteManager.setDeviceAlignedWithSatellite(true);
+        LinkedBlockingQueue<Integer> resultListener = new LinkedBlockingQueue<>(1);
+        String mText = "This is a test datagram message";
+        SatelliteDatagram datagram = new SatelliteDatagram(mText.getBytes());
+        mSatelliteManager.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+                datagram, true, Runnable::run, resultListener::offer);
+        try {
+            Integer value = resultListener.poll(TIMEOUT, TimeUnit.MILLISECONDS);
+            if (value == null) {
+                textView.setText("Timed out for sendSatelliteDatagram");
+            } else if (value != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+                textView.setText("Failed to sendSatelliteDatagram with error = "
+                        + SatelliteErrorUtils.mapError(value));
+            } else {
+                textView.setText("sendSatelliteDatagram is successful");
+            }
+        } catch (InterruptedException e) {
+            textView.setText("sendSatelliteDatagram exception caught =" + e);
+        }
+    }
+
+    private void registerForSatelliteDatagramApp(View view) {
+        int result = mSatelliteManager.registerForSatelliteDatagram(Runnable::run,
+                mDatagramCallback);
+        TextView textView = findViewById(R.id.text_id);
+        if (result == 0) {
+            textView.setText("registerForSatelliteDatagram is successful");
+        } else {
+            textView.setText("Status for registerForSatelliteDatagram : "
+                    + SatelliteErrorUtils.mapError(result));
+        }
+    }
+
+    private void unregisterForSatelliteDatagramApp(View view) {
+        mSatelliteManager.unregisterForSatelliteDatagram(mDatagramCallback);
+        TextView textView = findViewById(R.id.text_id);
+        textView.setText("unregisterForSatelliteDatagram 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 registerForSatelliteModemStateChangedApp(View view) {
+        int result = mSatelliteManager.registerForSatelliteModemStateChanged(Runnable::run,
+                mStateCallback);
+        TextView textView = findViewById(R.id.text_id);
+        if (result == 0) {
+            textView.setText("registerForSatelliteModemStateChanged is successful");
+        } else {
+            textView.setText("Status for registerForSatelliteModemStateChanged : "
+                    + SatelliteErrorUtils.mapError(result));
+        }
+    }
+
+    private void unregisterForSatelliteModemStateChangedApp(View view) {
+        mSatelliteManager.unregisterForSatelliteModemStateChanged(mStateCallback);
+        TextView textView = findViewById(R.id.text_id);
+        textView.setText("unregisterForSatelliteModemStateChanged 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..3c0b2fd
--- /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.requestSatelliteEnabled(true, true, Runnable::run, resultListener::offer);
+        mSatelliteManager.pollPendingSatelliteDatagrams(Runnable::run, resultListener::offer);
+        SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+                mReceivedDatagram, 3);
+        mSatelliteManager.pollPendingSatelliteDatagrams(Runnable::run, resultListener::offer);
+        SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+                mReceivedDatagram, 2);
+        mSatelliteManager.pollPendingSatelliteDatagrams(Runnable::run, resultListener::offer);
+        SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+                mReceivedDatagram, 1);
+        mSatelliteManager.pollPendingSatelliteDatagrams(Runnable::run, resultListener::offer);
+        SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+                mReceivedDatagram, 0);
+        mSatelliteManager.pollPendingSatelliteDatagrams(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.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+                datagram, true, Runnable::run, resultListener::offer);
+        mSatelliteManager.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+                datagram, true, Runnable::run, resultListener::offer);
+        mSatelliteManager.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+                datagram, true, Runnable::run, resultListener::offer);
+        mSatelliteManager.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+                datagram, true, Runnable::run, resultListener::offer);
+        mSatelliteManager.sendSatelliteDatagram(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.requestSatelliteEnabled(true, true, Runnable::run, resultListener::offer);
+        String mText = "This is a test datagram message";
+        SatelliteDatagram datagram = new SatelliteDatagram(mText.getBytes());
+        mSatelliteManager.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+                datagram, true, Runnable::run, resultListener::offer);
+        SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+                mReceivedDatagram, 4);
+        mSatelliteManager.pollPendingSatelliteDatagrams(Runnable::run, resultListener::offer);
+        mSatelliteManager.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+                datagram, true, Runnable::run, resultListener::offer);
+        SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+                mReceivedDatagram, 3);
+        mSatelliteManager.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+                datagram, true, Runnable::run, resultListener::offer);
+        SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+                mReceivedDatagram, 2);
+        mSatelliteManager.sendSatelliteDatagram(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+                datagram, true, Runnable::run, resultListener::offer);
+        SatelliteTestApp.getTestSatelliteService().sendOnSatelliteDatagramReceived(
+                mReceivedDatagram, 1);
+        mSatelliteManager.sendSatelliteDatagram(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..940435e
--- /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::provisionSatelliteServiceApp);
+        findViewById(R.id.deprovisionSatelliteService)
+                .setOnClickListener(this::deprovisionSatelliteServiceApp);
+        findViewById(R.id.requestIsSatelliteProvisioned)
+                .setOnClickListener(this::requestIsSatelliteProvisionedApp);
+        findViewById(R.id.registerForSatelliteProvisionStateChanged)
+                .setOnClickListener(this::registerForSatelliteProvisionStateChangedApp);
+        findViewById(R.id.unregisterForSatelliteProvisionStateChanged)
+                .setOnClickListener(this::unregisterForSatelliteProvisionStateChangedApp);
+        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 provisionSatelliteServiceApp(View view) {
+        TextView textView = findViewById(R.id.text_id);
+        CancellationSignal cancellationSignal = new CancellationSignal();
+        LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+        String mText = "This is test provision data.";
+        byte[] testProvisionData = mText.getBytes();
+        mSatelliteManager.provisionSatelliteService("SATELLITE_TOKEN", testProvisionData,
+                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 deprovisionSatelliteServiceApp(View view) {
+        TextView textView = findViewById(R.id.text_id);
+        LinkedBlockingQueue<Integer> error = new LinkedBlockingQueue<>(1);
+        mSatelliteManager.deprovisionSatelliteService("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 requestIsSatelliteProvisionedApp(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.requestIsSatelliteProvisioned(Runnable::run, mReceiver);
+    }
+
+    private void registerForSatelliteProvisionStateChangedApp(View view) {
+        int result = mSatelliteManager.registerForSatelliteProvisionStateChanged(Runnable::run,
+                mCallback);
+        TextView textView = findViewById(R.id.text_id);
+        textView.setText("Status for registerForSatelliteProvisionStateChanged : "
+                + SatelliteErrorUtils.mapError(result));
+    }
+
+    private void unregisterForSatelliteProvisionStateChangedApp(View view) {
+        mSatelliteManager.unregisterForSatelliteProvisionStateChanged(mCallback);
+        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..4e339a3
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/SatelliteControl.java
@@ -0,0 +1,274 @@
+/*
+ * 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::requestIsSatelliteEnabledApp);
+        findViewById(R.id.requestIsDemoModeEnabled)
+                .setOnClickListener(this::requestIsDemoModeEnabledApp);
+        findViewById(R.id.requestIsSatelliteSupported)
+                .setOnClickListener(this::requestIsSatelliteSupportedApp);
+        findViewById(R.id.requestSatelliteCapabilities)
+                .setOnClickListener(this::requestSatelliteCapabilitiesApp);
+        findViewById(R.id.requestIsSatelliteCommunicationAllowedForCurrentLocation)
+                .setOnClickListener(
+                this::requestIsSatelliteCommunicationAllowedForCurrentLocationApp);
+        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.requestSatelliteEnabled(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.requestSatelliteEnabled(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 requestIsSatelliteEnabledApp(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("requestIsSatelliteEnabled is true");
+                } else {
+                    textView.setText("Status for requestIsSatelliteEnabled 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 requestIsSatelliteEnabled error : "
+                        + SatelliteErrorUtils.mapError(errorCode.get()));
+            }
+        };
+        mSatelliteManager.requestIsSatelliteEnabled(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 requestIsSatelliteSupportedApp(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("requestIsSatelliteSupported is true");
+                } else {
+                    textView.setText("Status for requestIsSatelliteSupported 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 requestIsSatelliteSupported error : "
+                        + SatelliteErrorUtils.mapError(errorCode.get()));
+            }
+        };
+        mSatelliteManager.requestIsSatelliteSupported(Runnable::run, receiver);
+    }
+
+    private void requestSatelliteCapabilitiesApp(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 requestSatelliteCapabilities 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 requestSatelliteCapabilities error : "
+                        + SatelliteErrorUtils.mapError(errorCode.get()));
+            }
+        };
+        mSatelliteManager.requestSatelliteCapabilities(Runnable::run, receiver);
+    }
+
+    private void requestIsSatelliteCommunicationAllowedForCurrentLocationApp(View view) {
+        final AtomicReference<Boolean> enabled = new AtomicReference<>();
+        final AtomicReference<Integer> errorCode = new AtomicReference<>();
+        String display = "requestIsSatelliteCommunicationAllowedForCurrentLocation";
+        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.requestIsSatelliteCommunicationAllowedForCurrentLocation(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..a64aa5d
--- /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.sendSatelliteDatagram(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("sendSatelliteDatagram exception caught = " + e);
+        }
+    }
+
+    private void receiveStatusApp(View view) {
+        LinkedBlockingQueue<Integer> resultListener = new LinkedBlockingQueue<>(1);
+        byte[] testProvisionData = mMessageOutput.getBytes();
+        setupForTransferringDatagram(testProvisionData);
+
+        int result = mSatelliteManager.registerForSatelliteDatagram(Runnable::run, mCallback);
+        TextView showErrorStatusTextView = findViewById(R.id.showErrorStatus);
+        if (result != SatelliteResult.SATELLITE_RESULT_SUCCESS) {
+            showErrorStatusTextView.setText("Status for registerForSatelliteDatagram : "
+                    + SatelliteErrorUtils.mapError(result));
+        }
+        if (SatelliteTestApp.getTestSatelliteService() != null) {
+            SatelliteTestApp.getTestSatelliteService().sendOnPendingDatagrams();
+        }
+        mSatelliteManager.requestSatelliteEnabled(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.pollPendingSatelliteDatagrams(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("pollPendingSatelliteDatagrams 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.provisionSatelliteService("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.requestSatelliteCapabilities(Runnable::run, receiver);
+
+        //Satellite Position
+        SatelliteTransmissionUpdateCallbackTestApp callback =
+                new SatelliteTransmissionUpdateCallbackTestApp();
+        mSatelliteManager.requestSatelliteEnabled(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.startSatelliteTransmissionUpdates(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..929a2e5
--- /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("pollPendingSatelliteDatagrams: mLocalListener is null");
+        }
+    }
+
+    @Override
+    public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isEmergency,
+            @NonNull IIntegerConsumer errorCallback) {
+        logd("sendSatelliteDatagram: 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("sendSatelliteDatagram: 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("requestIsSatelliteCommunicationAllowedForCurrentLocation: 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..792c984
--- /dev/null
+++ b/testapps/TestSatelliteApp/src/com/android/phone/testapps/satellitetestapp/TestSatelliteWrapper.java
@@ -0,0 +1,330 @@
+/*
+ * 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.SatelliteManager;
+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::registerForSatelliteCapabilitiesChanged);
+        findViewById(R.id.unregisterForSatelliteCapabilitiesChanged)
+                .setOnClickListener(this::unregisterForSatelliteCapabilitiesChanged);
+        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, "unregisterForSatelliteCapabilitiesChanged()");
+                mSatelliteManagerWrapper.unregisterForSatelliteCapabilitiesChanged(
+                        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 | IllegalStateException 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;
+            if (ex instanceof SatelliteManager.SatelliteException) {
+                errorMessage =
+                        "registerForNtnSignalStrengthChanged: " + translateResultCodeToString(
+                                ((SatelliteManager.SatelliteException) ex).getErrorCode());
+            } else {
+                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 registerForSatelliteCapabilitiesChanged(View view) {
+        addLogMessage("registerForSatelliteCapabilitiesChanged");
+        Log.d(TAG, "registerForSatelliteCapabilitiesChanged()");
+        if (mSatelliteCapabilitiesCallback == null) {
+            mSatelliteCapabilitiesCallback =
+                    SatelliteCapabilities -> {
+                        String message = "Received SatelliteCapabillities : "
+                                + SatelliteCapabilities;
+                        Log.d(TAG, message);
+                        runOnUiThread(() -> addLogMessage(message));
+                    };
+        }
+
+        int result = mSatelliteManagerWrapper.registerForSatelliteCapabilitiesChanged(mExecutor,
+                mSatelliteCapabilitiesCallback);
+        if (result != SatelliteManagerWrapper.SATELLITE_RESULT_SUCCESS) {
+            String onError = translateResultCodeToString(result);
+            Log.d(TAG, onError);
+            addLogMessage(onError);
+            mSatelliteCapabilitiesCallback = null;
+        }
+    }
+
+    private void unregisterForSatelliteCapabilitiesChanged(View view) {
+        addLogMessage("unregisterForSatelliteCapabilitiesChanged");
+        Log.d(TAG, "unregisterForSatelliteCapabilitiesChanged()");
+        if (mSatelliteCapabilitiesCallback != null) {
+            mSatelliteManagerWrapper.unregisterForSatelliteCapabilitiesChanged(
+                    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";
+            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..3015f76 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",
+        "satellite-s2storage-rw",
+        "satellite-s2storage-testutils",
+        "s2-geometry-library-java",
+        "telephony-satellite",
     ],
 
     test_suites: [
diff --git a/tests/src/com/android/phone/CarrierConfigLoaderTest.java b/tests/src/com/android/phone/CarrierConfigLoaderTest.java
index b6f8ed8..bd2e4f7 100644
--- a/tests/src/com/android/phone/CarrierConfigLoaderTest.java
+++ b/tests/src/com/android/phone/CarrierConfigLoaderTest.java
@@ -40,8 +40,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;
@@ -97,10 +99,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);
 
@@ -142,6 +151,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 +176,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 +194,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 +227,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 +264,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 +279,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 +300,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 +320,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 +358,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);
@@ -417,7 +429,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/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..150703d 100644
--- a/tests/src/com/android/phone/PhoneInterfaceManagerTest.java
+++ b/tests/src/com/android/phone/PhoneInterfaceManagerTest.java
@@ -34,6 +34,8 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 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,9 +46,11 @@
 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 org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -66,10 +70,14 @@
     PhoneGlobals mPhoneGlobals;
     @Mock
     Phone mPhone;
+    @Mock
+    FeatureFlags mFeatureFlags;
 
     @Mock
     private SubscriptionManagerService mSubscriptionManagerService;
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Before
     @UiThreadTest
     public void setUp() throws Exception {
@@ -78,7 +86,7 @@
         // 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);
@@ -283,4 +291,72 @@
         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, "");
+    }
 }
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..84c233a
--- /dev/null
+++ b/tests/src/com/android/phone/satellite/accesscontrol/S2RangeSatelliteOnDeviceAccessControllerTest.java
@@ -0,0 +1,194 @@
+/*
+ * 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);
+
+            // 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 =
+                    accessController.createLocationTokenForLatLng(
+                            s2LatLng.latDegrees(), s2LatLng.lngDegrees());
+            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 = accessController.createLocationTokenForLatLng(
+                                s2LatLng.latDegrees(), s2LatLng.lngDegrees());
+                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 = accessController.createLocationTokenForLatLng(
+                    s2LatLng.latDegrees(), s2LatLng.lngDegrees());
+            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 = accessController.createLocationTokenForLatLng(
+                        s2LatLng.latDegrees(), s2LatLng.lngDegrees());
+                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 = accessController.createLocationTokenForLatLng(
+                    s2LatLng.latDegrees(), s2LatLng.lngDegrees());
+            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 = accessController.createLocationTokenForLatLng(
+                    s2LatLng.latDegrees(), s2LatLng.lngDegrees());
+            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 = accessController.createLocationTokenForLatLng(
+                        s2LatLng.latDegrees(), s2LatLng.lngDegrees());
+                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 = accessController.createLocationTokenForLatLng(
+                    s2LatLng.latDegrees(), s2LatLng.lngDegrees());
+            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/slice/SlicePurchaseControllerTest.java b/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
index b2a4a9f..5637c3a 100644
--- a/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
+++ b/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
@@ -34,6 +34,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -50,6 +51,9 @@
 import android.telephony.TelephonyManager;
 import android.telephony.data.NetworkSliceInfo;
 import android.telephony.data.NetworkSlicingConfig;
+import android.telephony.data.RouteSelectionDescriptor;
+import android.telephony.data.TrafficDescriptor;
+import android.telephony.data.UrspRule;
 import android.testing.TestableLooper;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -58,6 +62,7 @@
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.data.DataSettingsManager;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -66,7 +71,9 @@
 import org.mockito.Mockito;
 
 import java.time.LocalDate;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 
 @RunWith(AndroidJUnit4.class)
@@ -86,6 +93,7 @@
     private static final long THROTTLE_TIMEOUT = 4000;
 
     @Mock Phone mPhone;
+    @Mock FeatureFlags mFeatureFlags;
     @Mock CarrierConfigManager mCarrierConfigManager;
     @Mock CommandsInterface mCommandsInterface;
     @Mock ServiceState mServiceState;
@@ -147,7 +155,7 @@
 
         // create a spy to mock final PendingIntent methods
         SlicePurchaseController slicePurchaseController =
-                new SlicePurchaseController(mPhone, mHandler.getLooper());
+                new SlicePurchaseController(mPhone, mFeatureFlags, mHandler.getLooper());
         mSlicePurchaseController = spy(slicePurchaseController);
         doReturn(null).when(mSlicePurchaseController).createPendingIntent(
                 anyString(), anyInt(), anyBoolean());
@@ -578,7 +586,7 @@
         intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
                 TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
         intent.putExtra(SlicePurchaseController.EXTRA_FAILURE_CODE,
-                SlicePurchaseController.FAILURE_CODE_SERVER_UNREACHABLE);
+                SlicePurchaseController.FAILURE_CODE_CARRIER_URL_UNAVAILABLE);
         mContext.getBroadcastReceiver().onReceive(mContext, intent);
         mTestableLooper.processAllMessages();
         assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR, mResult);
@@ -619,6 +627,7 @@
     public void testPurchasePremiumCapabilityResultNotDefaultDataSubscriptionResponse() {
         sendValidPurchaseRequest();
 
+        // broadcast NOT_DEFAULT_DATA_SUBSCRIPTION response from slice purchase application
         Intent intent = new Intent();
         intent.setAction("com.android.phone.slice.action."
                 + "SLICE_PURCHASE_APP_RESPONSE_NOT_DEFAULT_DATA_SUBSCRIPTION");
@@ -636,6 +645,35 @@
     }
 
     @Test
+    public void testPurchasePremiumCapabilityResultNotificationsDisabled() {
+        doReturn(true).when(mFeatureFlags).slicingAdditionalErrorCodes();
+        sendValidPurchaseRequest();
+
+        // broadcast NOTIFICATIONS_DISABLED response from slice purchase application
+        Intent intent = new Intent();
+        intent.setAction("com.android.phone.slice.action."
+                + "SLICE_PURCHASE_APP_RESPONSE_NOTIFICATIONS_DISABLED");
+        intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
+        intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+                TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+        mContext.getBroadcastReceiver().onReceive(mContext, intent);
+        mTestableLooper.processAllMessages();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED, mResult);
+
+        // retry to verify throttled
+        mSlicePurchaseController.purchasePremiumCapability(
+                TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, mHandler.obtainMessage());
+        mTestableLooper.processAllMessages();
+        assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED, mResult);
+
+        // retry to verify unthrottled
+        mTestableLooper.moveTimeForward(THROTTLE_TIMEOUT);
+        mTestableLooper.processAllMessages();
+
+        testPurchasePremiumCapabilityResultSuccess();
+    }
+
+    @Test
     public void testPurchasePremiumCapabilityResultNotificationThrottled() {
         mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR, MONTH, DATE));
         mSlicePurchaseController.updateNotificationCounts();
@@ -670,6 +708,69 @@
         assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED, mResult);
     }
 
+    @Test
+    public void testIsSlicingConfigActive_emptyUrspRules() {
+        int capability = TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY;
+        NetworkSliceInfo sliceInfo = createNetworkSliceInfo(
+                getRandomSliceServiceType(capability), true);
+        NetworkSlicingConfig slicingConfig = new NetworkSlicingConfig(
+                Collections.emptyList(), Collections.singletonList(sliceInfo));
+        mSlicePurchaseController.setSlicingConfig(slicingConfig);
+
+        assertFalse(mSlicePurchaseController.isSlicingConfigActive(capability));
+    }
+
+    @Test
+    public void testIsSlicingConfigActive_noMatchingTrafficDescriptor() {
+        int capability = TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY;
+        NetworkSliceInfo sliceInfo = createNetworkSliceInfo(
+                getRandomSliceServiceType(capability), true);
+        TrafficDescriptor trafficDescriptor = createTrafficDescriptor("ENTERPRISE");
+        RouteSelectionDescriptor routeSelectionDescriptor = createRouteSelectionDescriptor(
+                Collections.singletonList(sliceInfo));
+        NetworkSlicingConfig slicingConfig = createNetworkSlicingConfig(
+                Collections.singletonList(sliceInfo),
+                Collections.singletonList(trafficDescriptor),
+                Collections.singletonList(routeSelectionDescriptor));
+        mSlicePurchaseController.setSlicingConfig(slicingConfig);
+
+        assertFalse(mSlicePurchaseController.isSlicingConfigActive(capability));
+    }
+
+    @Test
+    public void testIsSlicingConfigActive_multipleElements() {
+        int capability = TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY;
+        NetworkSliceInfo sliceInfo1 = createNetworkSliceInfo(
+                getRandomSliceServiceType(SlicePurchaseController.PREMIUM_CAPABILITY_INVALID),
+                false);
+        NetworkSliceInfo sliceInfo2 = createNetworkSliceInfo(
+                getRandomSliceServiceType(capability), true);
+        List<NetworkSliceInfo> sliceInfos = new ArrayList<>();
+        sliceInfos.add(sliceInfo1);
+        sliceInfos.add(sliceInfo2);
+
+        TrafficDescriptor trafficDescriptor1 = createTrafficDescriptor("ENTERPRISE");
+        TrafficDescriptor trafficDescriptor2 = createTrafficDescriptor(
+                SlicePurchaseController.getAppId(capability));
+        List<TrafficDescriptor> trafficDescriptors = new ArrayList<>();
+        trafficDescriptors.add(trafficDescriptor1);
+        trafficDescriptors.add(trafficDescriptor2);
+
+        RouteSelectionDescriptor routeSelectionDescriptor1 = createRouteSelectionDescriptor(
+                Collections.emptyList());
+        RouteSelectionDescriptor routeSelectionDescriptor2 = createRouteSelectionDescriptor(
+                sliceInfos);
+        List<RouteSelectionDescriptor> routeSelectionDescriptors = new ArrayList<>();
+        routeSelectionDescriptors.add(routeSelectionDescriptor1);
+        routeSelectionDescriptors.add(routeSelectionDescriptor2);
+
+        NetworkSlicingConfig slicingConfig = createNetworkSlicingConfig(
+                sliceInfos, trafficDescriptors, routeSelectionDescriptors);
+        mSlicePurchaseController.setSlicingConfig(slicingConfig);
+
+        assertTrue(mSlicePurchaseController.isSlicingConfigActive(capability));
+    }
+
     private void completeSuccessfulPurchase() {
         sendValidPurchaseRequest();
 
@@ -726,7 +827,7 @@
         mEntitlementResponse.mEntitlementStatus =
                 PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED;
         mEntitlementResponse.mProvisionStatus =
-                PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_PROVISION_STATUS_PROVISIONED;
+                PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_PROVISION_STATUS_NOT_PROVISIONED;
 
         // send purchase request
         mSlicePurchaseController.purchasePremiumCapability(
@@ -744,18 +845,61 @@
     }
 
     private void sendNetworkSlicingConfig(int capability, boolean configActive) {
-        int sliceServiceType = capability == TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY
-                ? NetworkSliceInfo.SLICE_SERVICE_TYPE_URLLC
-                : NetworkSliceInfo.SLICE_SERVICE_TYPE_NONE;
-        NetworkSliceInfo sliceInfo = new NetworkSliceInfo.Builder()
-                .setStatus(configActive ? NetworkSliceInfo.SLICE_STATUS_ALLOWED
-                        : NetworkSliceInfo.SLICE_STATUS_UNKNOWN)
-                .setSliceServiceType(sliceServiceType)
-                .build();
-        NetworkSlicingConfig slicingConfig = new NetworkSlicingConfig(Collections.emptyList(),
+        NetworkSliceInfo sliceInfo = createNetworkSliceInfo(
+                getRandomSliceServiceType(capability), configActive);
+        TrafficDescriptor trafficDescriptor = createTrafficDescriptor(
+                SlicePurchaseController.getAppId(capability));
+        RouteSelectionDescriptor routeSelectionDescriptor = createRouteSelectionDescriptor(
                 Collections.singletonList(sliceInfo));
+        NetworkSlicingConfig slicingConfig = createNetworkSlicingConfig(
+                Collections.singletonList(sliceInfo),
+                Collections.singletonList(trafficDescriptor),
+                Collections.singletonList(routeSelectionDescriptor));
         mSlicePurchaseController.obtainMessage(2 /* EVENT_SLICING_CONFIG_CHANGED */,
                 new AsyncResult(null, slicingConfig, null)).sendToTarget();
         mTestableLooper.processAllMessages();
     }
+
+    @NetworkSliceInfo.SliceServiceType private int getRandomSliceServiceType(
+            @TelephonyManager.PremiumCapability int capability) {
+        for (int sliceServiceType : SlicePurchaseController.getSliceServiceTypes(capability)) {
+            // Get a random valid sst from the set
+            return sliceServiceType;
+        }
+        return NetworkSliceInfo.SLICE_SERVICE_TYPE_NONE;
+    }
+
+    @NonNull private NetworkSliceInfo createNetworkSliceInfo(
+            @NetworkSliceInfo.SliceServiceType int sliceServiceType, boolean active) {
+        return new NetworkSliceInfo.Builder()
+                .setStatus(active ? NetworkSliceInfo.SLICE_STATUS_ALLOWED
+                        : NetworkSliceInfo.SLICE_STATUS_UNKNOWN)
+                .setSliceServiceType(sliceServiceType)
+                .build();
+    }
+
+    @NonNull private TrafficDescriptor createTrafficDescriptor(@NonNull String appId) {
+        TrafficDescriptor.OsAppId osAppId = new TrafficDescriptor.OsAppId(
+                TrafficDescriptor.OsAppId.ANDROID_OS_ID, appId);
+        return new TrafficDescriptor.Builder()
+                .setOsAppId(osAppId.getBytes())
+                .build();
+    }
+
+    @NonNull private RouteSelectionDescriptor createRouteSelectionDescriptor(
+            @NonNull List<NetworkSliceInfo> sliceInfos) {
+        return new RouteSelectionDescriptor(
+                RouteSelectionDescriptor.MIN_ROUTE_PRECEDENCE,
+                RouteSelectionDescriptor.SESSION_TYPE_IPV4,
+                RouteSelectionDescriptor.ROUTE_SSC_MODE_1,
+                sliceInfos, Collections.emptyList());
+    }
+
+    @NonNull private NetworkSlicingConfig createNetworkSlicingConfig(
+            @NonNull List<NetworkSliceInfo> sliceInfos,
+            @NonNull List<TrafficDescriptor> trafficDescriptors,
+            @NonNull List<RouteSelectionDescriptor> routeSelectionDescriptors) {
+        UrspRule urspRule = new UrspRule(0, trafficDescriptors, routeSelectionDescriptors);
+        return new NetworkSlicingConfig(Collections.singletonList(urspRule), sliceInfos);
+    }
 }
diff --git a/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java b/tests/src/com/android/services/telephony/DisconnectCauseUtilTest.java
index 969622a..16a5cdb 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,6 +37,7 @@
 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;
@@ -42,7 +47,6 @@
 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 4f9b879..f0a5220 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.services.telephony;
 
+import static android.telecom.Connection.PROPERTY_WIFI;
 import static android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE;
 import static android.telephony.DisconnectCause.EMERGENCY_TEMP_FAILURE;
 import static android.telephony.DisconnectCause.ERROR_UNSPECIFIED;
@@ -32,9 +33,11 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
 
+import static org.junit.Assert.assertNotEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -57,10 +60,12 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.telecom.Conference;
 import android.telecom.Conferenceable;
 import android.telecom.ConnectionRequest;
 import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.telephony.AccessNetworkConstants;
@@ -74,7 +79,9 @@
 import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArrayMap;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -97,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;
@@ -104,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;
@@ -116,8 +125,10 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * Unit tests for TelephonyConnectionService.
@@ -125,6 +136,35 @@
 
 @RunWith(AndroidJUnit4.class)
 public class TelephonyConnectionServiceTest extends TelephonyTestBase {
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+    private static final String NORMAL_ROUTED_EMERGENCY_NUMBER = "110";
+    private static final String EMERGENCY_ROUTED_EMERGENCY_NUMBER = "911";
+    private static final EmergencyNumber MOCK_NORMAL_NUMBER = new EmergencyNumber(
+            NORMAL_ROUTED_EMERGENCY_NUMBER,
+            "US" /* country */,
+            null /* mcc */,
+            EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+            Collections.emptyList() /* categories */,
+            EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+            EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+    private static final EmergencyNumber MOCK_NORMAL_NUMBER_WITH_UNKNOWN_ROUTING =
+            new EmergencyNumber(
+                    NORMAL_ROUTED_EMERGENCY_NUMBER,
+                    "US" /* country */,
+                    "455" /* mcc */,
+                    EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                    Collections.emptyList() /* categories */,
+                    EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+                    EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+    private static final EmergencyNumber MOCK_EMERGENCY_NUMBER = new EmergencyNumber(
+            EMERGENCY_ROUTED_EMERGENCY_NUMBER,
+            "US" /* country */,
+            null /* mcc */,
+            EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+            Collections.emptyList() /* categories */,
+            EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+            EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+
     /**
      * Unlike {@link TestTelephonyConnection}, a bare minimal {@link TelephonyConnection} impl
      * that does not try to configure anything.
@@ -174,8 +214,8 @@
 
     private static final ComponentName TEST_COMPONENT_NAME = new ComponentName(
             "com.android.phone.tests", TelephonyConnectionServiceTest.class.getName());
-    private static final String TEST_ACCOUNT_ID1 = "id1";
-    private static final String TEST_ACCOUNT_ID2 = "id2";
+    private static final String TEST_ACCOUNT_ID1 = "0"; // subid 0
+    private static final String TEST_ACCOUNT_ID2 = "1"; // subid 1
     private static final PhoneAccountHandle PHONE_ACCOUNT_HANDLE_1 = new PhoneAccountHandle(
             TEST_COMPONENT_NAME, TEST_ACCOUNT_ID1);
     private static final PhoneAccountHandle PHONE_ACCOUNT_HANDLE_2 = new PhoneAccountHandle(
@@ -263,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))
@@ -277,6 +317,8 @@
         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);
     }
 
     @After
@@ -1293,7 +1335,83 @@
             // This shouldn't happen
             fail();
         }
-        verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any(), any());
+        verify(mSatelliteSOSMessageRecommender).onEmergencyCallStarted(any());
+    }
+
+    /**
+     * Test that the TelephonyConnectionService successfully dials an outgoing normal routed
+     * emergency call on an in-service sim.
+     */
+    @Test
+    @SmallTest
+    public void testCreateOutgoingConnectionForNormalRoutedEmergencyCall()
+            throws CallStateException {
+        // A whole load of annoying mocks to set up to test this scenario.
+        // We'll purposely try to start the call on the limited service phone.
+        ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
+                .setAccountHandle(PHONE_ACCOUNT_HANDLE_1)
+                .setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, NORMAL_ROUTED_EMERGENCY_NUMBER,
+                        null))
+                .build();
+
+        // First phone is in limited service.
+        Phone testPhone0 = makeTestPhone(0 /*phoneId*/, ServiceState.STATE_EMERGENCY_ONLY,
+                true /*isEmergencyOnly*/);
+        doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_LTE).when(testPhone0)
+                .getImsRegistrationTech();
+        doReturn(0).when(testPhone0).getSubId();
+        setupMockEmergencyNumbers(testPhone0, List.of(MOCK_NORMAL_NUMBER,
+                MOCK_NORMAL_NUMBER_WITH_UNKNOWN_ROUTING, MOCK_EMERGENCY_NUMBER));
+
+        // Second phone is in full service; this is ultimately the one we want to pick.
+        Phone testPhone1 = makeTestPhone(1 /*phoneId*/, ServiceState.STATE_IN_SERVICE,
+                false /*isEmergencyOnly*/);
+        doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_LTE).when(testPhone1)
+                .getImsRegistrationTech();
+        doReturn(1).when(testPhone1).getSubId();
+        setupMockEmergencyNumbers(testPhone1, List.of(MOCK_NORMAL_NUMBER,
+                MOCK_NORMAL_NUMBER_WITH_UNKNOWN_ROUTING, MOCK_EMERGENCY_NUMBER));
+
+        // Make sure both phones are going to prefer in service for normal routed ecalls.
+        doReturn(true).when(testPhone0).shouldPreferInServiceSimForNormalRoutedEmergencyCall();
+        doReturn(true).when(testPhone1).shouldPreferInServiceSimForNormalRoutedEmergencyCall();
+
+        // A whole load of other stuff that needs to be setup for this to work.
+        doReturn(GSM_PHONE).when(testPhone0).getPhoneType();
+        doReturn(GSM_PHONE).when(testPhone1).getPhoneType();
+        List<Phone> phones = new ArrayList<>(2);
+        doReturn(true).when(testPhone0).isRadioOn();
+        doReturn(true).when(testPhone1).isRadioOn();
+        phones.add(testPhone0);
+        phones.add(testPhone1);
+        setPhones(phones);
+        doReturn(0).when(mPhoneUtilsProxy).getSubIdForPhoneAccountHandle(
+                eq(PHONE_ACCOUNT_HANDLE_1));
+        doReturn(1).when(mPhoneUtilsProxy).getSubIdForPhoneAccountHandle(
+                eq(PHONE_ACCOUNT_HANDLE_2));
+        setupHandleToPhoneMap(PHONE_ACCOUNT_HANDLE_1, testPhone0);
+        setupHandleToPhoneMap(PHONE_ACCOUNT_HANDLE_2, testPhone1);
+        setupDeviceConfig(testPhone0, testPhone1, 0);
+        doReturn(true).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(
+                eq(NORMAL_ROUTED_EMERGENCY_NUMBER));
+        HashMap<Integer, List<EmergencyNumber>> emergencyNumbers = new HashMap<>(1);
+        List<EmergencyNumber> numbers = new ArrayList<>();
+        numbers.add(MOCK_EMERGENCY_NUMBER);
+        numbers.add(MOCK_NORMAL_NUMBER);
+        numbers.add(MOCK_NORMAL_NUMBER_WITH_UNKNOWN_ROUTING);
+        emergencyNumbers.put(0 /*subId*/, numbers);
+        doReturn(emergencyNumbers).when(mTelephonyManagerProxy).getCurrentEmergencyNumberList();
+        doReturn(2).when(mTelephonyManagerProxy).getPhoneCount();
+
+        // All of that for... this.
+        mConnection = mTestConnectionService.onCreateOutgoingConnection(
+                PHONE_ACCOUNT_HANDLE_1, connectionRequest);
+        assertNotNull("test connection was not set up correctly.", mConnection);
+
+        // Lets make sure we DID try to place the call on phone 1, which is the in service phone.
+        verify(testPhone1).dial(anyString(), any(DialArgs.class), any(Consumer.class));
+        // And make sure we DID NOT try to place the call on phone 0, which is in limited service.
+        verify(testPhone0, never()).dial(anyString(), any(DialArgs.class), any(Consumer.class));
     }
 
     /**
@@ -1329,7 +1447,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));
     }
 
     /**
@@ -1734,8 +1952,8 @@
     }
 
     /**
-     * For DSDA devices, placing an outgoing call on a 2nd sub will hold the existing connection on
-     * the first sub.
+     * For DSDA devices, placing an outgoing call on a 2nd sub will hold the existing ACTIVE
+     * connection on the first sub.
      */
     @Test
     @SmallTest
@@ -1744,13 +1962,34 @@
 
         ArrayList<android.telecom.Connection> tcs = new ArrayList<>();
         SimpleTelephonyConnection tc1 = createTestConnection(SUB1_HANDLE, 0, false);
+        tc1.setTelephonyConnectionActive();
         tcs.add(tc1);
+
         Conferenceable c = TelephonyConnectionService.maybeHoldCallsOnOtherSubs(
                 tcs, new ArrayList<>(), SUB2_HANDLE, mTelephonyManagerProxy);
         assertTrue(c.equals(tc1));
         assertTrue(tc1.wasHeld);
     }
 
+    /**
+     * For DSDA devices, if the existing connection was already held, placing an outgoing call on a
+     * 2nd sub will not attempt to hold the existing connection on the first sub.
+     */
+    @Test
+    @SmallTest
+    public void testNoHold_ifExistingConnectionAlreadyHeld_ForVirtualDsdaDevice() {
+        when(mTelephonyManagerProxy.isConcurrentCallsPossible()).thenReturn(true);
+
+        ArrayList<android.telecom.Connection> tcs = new ArrayList<>();
+        SimpleTelephonyConnection tc1 = createTestConnection(SUB1_HANDLE, 0, false);
+        tc1.setTelephonyConnectionOnHold();
+        tcs.add(tc1);
+
+        Conferenceable c = TelephonyConnectionService.maybeHoldCallsOnOtherSubs(
+                tcs, new ArrayList<>(), SUB2_HANDLE, mTelephonyManagerProxy);
+        assertNull(c);
+    }
+
     // For 'Virtual DSDA' devices, if there is an existing call on sub1, an outgoing call on sub2
     // will place the sub1 call on hold.
     @Test
@@ -1889,7 +2128,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);
@@ -1919,7 +2158,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);
@@ -1950,7 +2189,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))
@@ -1977,7 +2216,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);
@@ -2005,7 +2245,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);
@@ -2036,6 +2277,8 @@
         setupForDialForDomainSelection(mPhone0, selectedDomain, false);
         doReturn(true).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(anyString());
         doReturn(emergencyNumber).when(mEmergencyNumberTracker).getEmergencyNumber(anyString());
+        doReturn(Arrays.asList(emergencyNumber)).when(mEmergencyNumberTracker).getEmergencyNumbers(
+                anyString());
 
         mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
                 createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
@@ -2044,7 +2287,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);
 
@@ -2078,6 +2403,8 @@
 
         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);
 
@@ -2125,6 +2452,8 @@
 
         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);
 
@@ -2166,6 +2495,8 @@
 
         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);
 
@@ -2219,6 +2550,8 @@
 
         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);
 
@@ -2273,6 +2606,8 @@
 
         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);
 
@@ -2324,14 +2659,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);
@@ -2365,14 +2700,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);
@@ -2594,7 +2929,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
@@ -2631,12 +2967,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);
@@ -2673,8 +3009,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());
 
@@ -2704,7 +3040,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());
 
@@ -2789,6 +3125,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();
 
@@ -2803,7 +3185,8 @@
         doReturn(new CompletableFuture()).when(mEmergencyCallDomainSelectionConnection)
                 .reselectDomain(any());
 
-        assertTrue(mTestConnectionService.maybeReselectDomain(c, preciseDisconnectCause, null));
+        assertTrue(mTestConnectionService.maybeReselectDomain(c, null, true,
+                android.telephony.DisconnectCause.NOT_VALID));
         verify(mEmergencyCallDomainSelectionConnection).reselectDomain(any());
     }
 
@@ -2822,7 +3205,8 @@
         doReturn(new CompletableFuture()).when(mEmergencyCallDomainSelectionConnection)
                 .reselectDomain(any());
 
-        assertTrue(mTestConnectionService.maybeReselectDomain(c, preciseDisconnectCause, null));
+        assertTrue(mTestConnectionService.maybeReselectDomain(c, null, true,
+                android.telephony.DisconnectCause.NOT_VALID));
         verify(mEmergencyCallDomainSelectionConnection).reselectDomain(any());
     }
 
@@ -2850,7 +3234,7 @@
         verify(mDomainSelectionResolver)
                 .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
         verify(mNormalCallDomainSelectionConnection).createNormalConnection(any(), any());
-        verify(mSatelliteSOSMessageRecommender, never()).onEmergencyCallStarted(any(), any());
+        verify(mSatelliteSOSMessageRecommender, never()).onEmergencyCallStarted(any());
 
         ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
 
@@ -2875,7 +3259,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);
 
@@ -2899,6 +3283,205 @@
                 disconnectCause.getTelephonyDisconnectCause());
     }
 
+    @Test
+    public void testNormalCallUsingNonTerrestrialNetwork_enableFlag() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG);
+
+        setupForCallTest();
+        // Call is not supported while using satellite
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setIsNonTerrestrialNetwork(true)
+                .setAvailableServices(List.of(NetworkRegistrationInfo.SERVICE_TYPE_DATA))
+                .build();
+        ServiceState ss = new ServiceState();
+        ss.addNetworkRegistrationInfo(nri);
+        when(mPhone0.getServiceState()).thenReturn(ss);
+        mConnection = mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+                createConnectionRequest(PHONE_ACCOUNT_HANDLE_1, "1234", TELECOM_CALL_ID1));
+        DisconnectCause disconnectCause = mConnection.getDisconnectCause();
+        assertEquals(android.telephony.DisconnectCause.SATELLITE_ENABLED,
+                disconnectCause.getTelephonyDisconnectCause());
+
+        // Call is supported while using satellite
+        nri = new NetworkRegistrationInfo.Builder()
+                .setIsNonTerrestrialNetwork(true)
+                .setAvailableServices(List.of(NetworkRegistrationInfo.SERVICE_TYPE_VOICE))
+                .build();
+        ss.addNetworkRegistrationInfo(nri);
+        mConnection = mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+                createConnectionRequest(PHONE_ACCOUNT_HANDLE_1, "1234", "TC@2"));
+        disconnectCause = mConnection.getDisconnectCause();
+        assertNotEquals(android.telephony.DisconnectCause.SATELLITE_ENABLED,
+                disconnectCause.getTelephonyDisconnectCause());
+    }
+
+    @Test
+    public void testNormalCallUsingNonTerrestrialNetwork_disableFlag() throws Exception {
+        mSetFlagsRule.disableFlags(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG);
+
+        setupForCallTest();
+        // Flag is disabled, so call is supported while using satellite
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setIsNonTerrestrialNetwork(true)
+                .setAvailableServices(List.of(NetworkRegistrationInfo.SERVICE_TYPE_DATA))
+                .build();
+        ServiceState ss = new ServiceState();
+        ss.addNetworkRegistrationInfo(nri);
+        when(mPhone0.getServiceState()).thenReturn(ss);
+        mConnection = mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+                createConnectionRequest(PHONE_ACCOUNT_HANDLE_1, "1234", TELECOM_CALL_ID1));
+        DisconnectCause disconnectCause = mConnection.getDisconnectCause();
+        assertNotEquals(android.telephony.DisconnectCause.SATELLITE_ENABLED,
+                disconnectCause.getTelephonyDisconnectCause());
+    }
+
+    @Test
+    public void testIsAvailableForEmergencyCallsNotForCrossSim() {
+        Phone mockPhone = Mockito.mock(Phone.class);
+        when(mockPhone.getImsRegistrationTech()).thenReturn(
+                ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM);
+        assertFalse(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY));
+        assertFalse(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL));
+        assertFalse(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN));
+    }
+
+    @Test
+    public void testIsAvailableForEmergencyCallsForEmergencyRoutingInEmergencyOnly() {
+        ServiceState mockService = Mockito.mock(ServiceState.class);
+        when(mockService.isEmergencyOnly()).thenReturn(true);
+        when(mockService.getState()).thenReturn(ServiceState.STATE_EMERGENCY_ONLY);
+
+        Phone mockPhone = Mockito.mock(Phone.class);
+        when(mockPhone.getImsRegistrationTech()).thenReturn(
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+        when(mockPhone.getServiceState()).thenReturn(mockService);
+
+        assertTrue(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY));
+        assertFalse(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL));
+        assertTrue(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN));
+    }
+
+    @Test
+    public void testIsAvailableForEmergencyCallsForEmergencyRoutingInService() {
+        ServiceState mockService = Mockito.mock(ServiceState.class);
+        when(mockService.isEmergencyOnly()).thenReturn(false);
+        when(mockService.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+
+        Phone mockPhone = Mockito.mock(Phone.class);
+        when(mockPhone.getImsRegistrationTech()).thenReturn(
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+        when(mockPhone.getServiceState()).thenReturn(mockService);
+
+        assertTrue(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY));
+        assertTrue(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL));
+        assertTrue(mTestConnectionService.isAvailableForEmergencyCalls(mockPhone,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN));
+    }
+
+    /**
+     * Verify that is the carrier config indicates that the carrier does not prefer to use an in
+     * service sim for a normal routed emergency call that we'll get no result.
+     */
+    @Test
+    public void testGetPhoneForNormalRoutedEmergencyCallWhenCarrierDoesntSupport() {
+        ServiceState mockService = Mockito.mock(ServiceState.class);
+        when(mockService.isEmergencyOnly()).thenReturn(false);
+        when(mockService.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+
+        Phone mockPhone = Mockito.mock(Phone.class);
+        when(mockPhone.shouldPreferInServiceSimForNormalRoutedEmergencyCall()).thenReturn(
+                false);
+        setupMockEmergencyNumbers(mockPhone, List.of(MOCK_NORMAL_NUMBER));
+        when(mPhoneFactoryProxy.getPhones()).thenReturn(new Phone[] {mockPhone});
+
+        assertNull(mTestConnectionService.getPhoneForNormalRoutedEmergencyCall(
+                NORMAL_ROUTED_EMERGENCY_NUMBER));
+    }
+
+    /**
+     * Verify that is the carrier config indicates that the carrier prefers to use an in service sim
+     * for a normal routed emergency call that we'll get the in service sim that supports it.
+     */
+    @Test
+    public void testGetPhoneForNormalRoutedEmergencyCallWhenCarrierDoesSupport() {
+        ServiceState mockService = Mockito.mock(ServiceState.class);
+        when(mockService.isEmergencyOnly()).thenReturn(false);
+        when(mockService.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+
+        Phone mockPhone = Mockito.mock(Phone.class);
+        when(mockPhone.shouldPreferInServiceSimForNormalRoutedEmergencyCall()).thenReturn(
+                true);
+        when(mockPhone.getServiceState()).thenReturn(mockService);
+        setupMockEmergencyNumbers(mockPhone, List.of(MOCK_NORMAL_NUMBER));
+        when(mPhoneFactoryProxy.getPhones()).thenReturn(new Phone[] {mockPhone});
+
+        assertEquals(mockPhone,
+                mTestConnectionService.getPhoneForNormalRoutedEmergencyCall(
+                        NORMAL_ROUTED_EMERGENCY_NUMBER));
+    }
+
+    /**
+     * Verify where there are two sims, one in limited service, and another in full service, if the
+     * carrier prefers to use an in-service sim, we choose the in-service sim.
+     */
+    @Test
+    public void testGetPhoneForNormalRoutedEmergencyCallWhenCarrierDoesSupportMultiSim() {
+        ServiceState mockInService = Mockito.mock(ServiceState.class);
+        when(mockInService.isEmergencyOnly()).thenReturn(false);
+        when(mockInService.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+        ServiceState mockLimitedService = Mockito.mock(ServiceState.class);
+        when(mockLimitedService.isEmergencyOnly()).thenReturn(true);
+        when(mockLimitedService.getState()).thenReturn(ServiceState.STATE_EMERGENCY_ONLY);
+
+        Phone mockInservicePhone = Mockito.mock(Phone.class);
+        when(mockInservicePhone.shouldPreferInServiceSimForNormalRoutedEmergencyCall()).thenReturn(
+                true);
+        when(mockInservicePhone.getServiceState()).thenReturn(mockInService);
+        setupMockEmergencyNumbers(mockInservicePhone, List.of(MOCK_NORMAL_NUMBER));
+
+        Phone mockLimitedServicePhone = Mockito.mock(Phone.class);
+        when(mockLimitedServicePhone.shouldPreferInServiceSimForNormalRoutedEmergencyCall())
+                .thenReturn(true);
+        when(mockLimitedServicePhone.getServiceState()).thenReturn(mockLimitedService);
+        setupMockEmergencyNumbers(mockLimitedServicePhone, List.of(MOCK_NORMAL_NUMBER));
+
+        when(mPhoneFactoryProxy.getPhones()).thenReturn(new Phone[] {mockLimitedServicePhone,
+                mockInservicePhone});
+
+        assertEquals(mockInservicePhone,
+                mTestConnectionService.getPhoneForNormalRoutedEmergencyCall(
+                        NORMAL_ROUTED_EMERGENCY_NUMBER));
+    }
+
+    private void setupMockEmergencyNumbers(Phone mockPhone, List<EmergencyNumber> numbers) {
+        EmergencyNumberTracker emergencyNumberTracker = Mockito.mock(EmergencyNumberTracker.class);
+        // Yuck.  There should really be a fake emergency number class which makes it easy to inject
+        // the numbers for testing.
+        ArrayMap<String, List<EmergencyNumber>> numbersMap = new ArrayMap<>();
+        for (EmergencyNumber number : numbers) {
+            when(emergencyNumberTracker.getEmergencyNumber(eq(number.getNumber())))
+                    .thenReturn(number);
+            if (!numbersMap.containsKey(number.getNumber())) {
+                numbersMap.put(number.getNumber(), new ArrayList<>());
+            }
+            numbersMap.get(number.getNumber()).add(number);
+        }
+        // Double yuck.
+        for (Map.Entry<String, List<EmergencyNumber>> entry : numbersMap.entrySet()) {
+            when(emergencyNumberTracker.getEmergencyNumbers(eq(entry.getKey()))).thenReturn(
+                    entry.getValue());
+        }
+        when(mockPhone.getEmergencyNumberTracker()).thenReturn(emergencyNumberTracker);
+    }
+
     private void setupForDialForDomainSelection(Phone mockPhone, int domain, boolean isEmergency) {
         if (isEmergency) {
             doReturn(mEmergencyCallDomainSelectionConnection).when(mDomainSelectionResolver)
@@ -3107,10 +3690,14 @@
     }
 
     private void setupHandleToPhoneMap(PhoneAccountHandle handle, Phone phone) {
-        // use subId 0
-        when(mPhoneUtilsProxy.getSubIdForPhoneAccountHandle(eq(handle))).thenReturn(0);
-        when(mSubscriptionManagerProxy.getPhoneId(eq(0))).thenReturn(0);
-        when(mPhoneFactoryProxy.getPhone(eq(0))).thenReturn(phone);
+        // The specified handle has an id which is subID
+        doReturn(Integer.parseInt(handle.getId())).when(mPhoneUtilsProxy)
+                .getSubIdForPhoneAccountHandle(eq(handle));
+        // The specified sub id in the passed handle will correspond to the phone's phone id.
+        doReturn(phone.getPhoneId()).when(mSubscriptionManagerProxy)
+                .getPhoneId(eq(Integer.parseInt(handle.getId())));
+        int phoneId = phone.getPhoneId();
+        doReturn(phone).when(mPhoneFactoryProxy).getPhone(eq(phoneId));
     }
 
     private AsyncResult getSuppServiceNotification(int notificationType, int code) {
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
index bf9fa01..c659d5e 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
@@ -14,6 +14,7 @@
 
 import static org.junit.Assert.assertNotEquals;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
@@ -280,7 +281,7 @@
         c.updateState();
 
         verify(mTelephonyConnectionService)
-                .maybeReselectDomain(any(), anyInt(), any());
+                .maybeReselectDomain(any(), any(), anyBoolean(), anyInt());
     }
 
     @Test
@@ -291,7 +292,7 @@
                 .getState();
         c.setTelephonyConnectionService(mTelephonyConnectionService);
         doReturn(false).when(mTelephonyConnectionService)
-                .maybeReselectDomain(any(), anyInt(), any());
+                .maybeReselectDomain(any(), any(), anyBoolean(), anyInt());
         c.updateState();
 
         assertEquals(STATE_DISCONNECTED, c.getState());
@@ -306,7 +307,7 @@
                 .getState();
         c.setTelephonyConnectionService(mTelephonyConnectionService);
         doReturn(true).when(mTelephonyConnectionService)
-                .maybeReselectDomain(any(), anyInt(), any());
+                .maybeReselectDomain(any(), any(), anyBoolean(), anyInt());
         c.resetOriginalConnectionCleared();
         c.updateState();
 
@@ -327,7 +328,7 @@
                 .when(mImsPhoneConnection).getEmergencyNumberInfo();
         c.setTelephonyConnectionService(mTelephonyConnectionService);
         doReturn(true).when(mTelephonyConnectionService)
-                .maybeReselectDomain(any(), anyInt(), any());
+                .maybeReselectDomain(any(), any(), anyBoolean(), anyInt());
         c.updateState();
 
         Integer serviceCategory = c.getEmergencyServiceCategory();
@@ -351,7 +352,7 @@
                 .when(mImsPhoneConnection).getEmergencyNumberInfo();
         c.setTelephonyConnectionService(mTelephonyConnectionService);
         doReturn(true).when(mTelephonyConnectionService)
-                .maybeReselectDomain(any(), anyInt(), any());
+                .maybeReselectDomain(any(), any(), anyBoolean(), anyInt());
         c.updateState();
 
         Integer serviceCategory = c.getEmergencyServiceCategory();
diff --git a/tests/src/com/android/services/telephony/domainselection/CarrierConfigHelperTest.java b/tests/src/com/android/services/telephony/domainselection/CarrierConfigHelperTest.java
new file mode 100644
index 0000000..5d4fe17
--- /dev/null
+++ b/tests/src/com/android/services/telephony/domainselection/CarrierConfigHelperTest.java
@@ -0,0 +1,277 @@
+/*
+ * 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 static org.mockito.Mockito.when;
+
+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 Context mContext;
+    @Mock private SharedPreferences mSharedPreferences;
+    @Mock private SharedPreferences.Editor mEditor;
+    @Mock private Resources mResources;
+
+    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..eab40f9 100644
--- a/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
+++ b/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
@@ -52,6 +52,7 @@
 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 com.android.services.telephony.domainselection.EmergencyCallDomainSelector.MSG_MAX_CELLULAR_TIMEOUT;
 import static com.android.services.telephony.domainselection.EmergencyCallDomainSelector.MSG_NETWORK_SCAN_TIMEOUT;
@@ -75,6 +76,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 +96,12 @@
 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.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;
@@ -139,6 +141,8 @@
     @Mock private DomainSelectorBase.DestroyListener mDestroyListener;
     @Mock private ProvisioningManager mProvisioningManager;
     @Mock private CrossSimRedialingController mCsrdCtrl;
+    @Mock private CarrierConfigHelper mCarrierConfigHelper;
+    @Mock private Resources mResources;
 
     private Context mContext;
 
@@ -188,6 +192,11 @@
             public String getOpPackageName() {
                 return "";
             }
+
+            @Override
+            public Resources getResources() {
+                return mResources;
+            }
         };
 
         if (Looper.myLooper() == null) {
@@ -207,6 +216,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 +264,8 @@
             }
         }).when(mWwanSelectorCallback).onRequestEmergencyNetworkScan(
                 any(), anyInt(), any(), any());
+
+        when(mResources.getStringArray(anyInt())).thenReturn(null);
     }
 
     @After
@@ -279,6 +292,102 @@
     }
 
     @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 +444,41 @@
     }
 
     @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 testDefaultCombinedImsNotRegisteredSelectCs() throws Exception {
         createSelector(SLOT_0_SUB_ID);
         unsolBarringInfoChanged(false);
@@ -1000,6 +1144,31 @@
     }
 
     @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
     public void testVoWifiSosPdnRequiresSettingEnabled() throws Exception {
         PersistableBundle bundle = getDefaultPersistableBundle();
         bundle.putBoolean(KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL, true);
@@ -1150,6 +1319,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");
@@ -1172,6 +1342,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");
@@ -1271,7 +1442,7 @@
 
         setupForScanListTest(bundle);
 
-        verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+        verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false));
     }
 
     @Test
@@ -1285,7 +1456,7 @@
 
         setupForScanListTest(bundle);
 
-        verifyPsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+        verifyPsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false));
     }
 
     @Test
@@ -1300,7 +1471,7 @@
 
         setupForScanListTest(bundle);
 
-        verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+        verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(false, false));
     }
 
     @Test
@@ -1315,7 +1486,7 @@
 
         setupForScanListTest(bundle);
 
-        verifyCsOnlyScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+        verifyCsOnlyScanList(mDomainSelector.getNextPreferredNetworks(false, false));
 
     }
 
@@ -1330,7 +1501,7 @@
 
         setupForScanListTest(bundle);
 
-        verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+        verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false));
     }
 
     @Test
@@ -1344,7 +1515,7 @@
 
         setupForScanListTest(bundle);
 
-        verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+        verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false));
     }
 
     @Test
@@ -1360,7 +1531,7 @@
 
         setupForScanListTest(bundle);
 
-        verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+        verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(true, false));
     }
 
     @Test
@@ -1375,7 +1546,7 @@
 
         setupForScanListTest(bundle);
 
-        verifyCsOnlyScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+        verifyCsOnlyScanList(mDomainSelector.getNextPreferredNetworks(true, false));
     }
 
     @Test
@@ -1392,7 +1563,7 @@
         bindImsService();
         processAllMessages();
 
-        verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+        verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false));
     }
 
     @Test
@@ -1409,7 +1580,7 @@
         bindImsService();
         processAllMessages();
 
-        verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+        verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(false, false));
     }
 
     @Test
@@ -1427,7 +1598,7 @@
         bindImsService();
         processAllMessages();
 
-        verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(false, false, false));
+        verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(false, false));
     }
 
     @Test
@@ -1444,7 +1615,7 @@
         bindImsService();
         processAllMessages();
 
-        verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+        verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false));
     }
 
     @Test
@@ -1461,7 +1632,7 @@
         bindImsService();
         processAllMessages();
 
-        verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+        verifyCsPreferredScanList(mDomainSelector.getNextPreferredNetworks(true, false));
     }
 
     @Test
@@ -1480,7 +1651,7 @@
         bindImsService();
         processAllMessages();
 
-        verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(true, false, false));
+        verifyPsOnlyScanList(mDomainSelector.getNextPreferredNetworks(true, false));
     }
 
     @Test
@@ -1496,7 +1667,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 +1683,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 +1824,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 +1848,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 {
@@ -1847,6 +2003,111 @@
         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));
+    }
+
     private void setupForScanListTest(PersistableBundle bundle) throws Exception {
         setupForScanListTest(bundle, false);
     }
@@ -1920,8 +2181,8 @@
     private void createSelector(int subId) throws Exception {
         mDomainSelector = new EmergencyCallDomainSelector(
                 mContext, SLOT_0, subId, mHandlerThread.getLooper(),
-                mImsStateTracker, mDestroyListener, mCsrdCtrl);
-
+                mImsStateTracker, mDestroyListener, mCsrdCtrl, mCarrierConfigHelper);
+        mDomainSelector.clearResourceConfiguration();
         replaceInstance(DomainSelectorBase.class,
                 "mWwanSelectorCallback", mDomainSelector, mWwanSelectorCallback);
     }
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..f4d2732 100644
--- a/tests/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionServiceTest.java
@@ -20,6 +20,7 @@
 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;
@@ -44,6 +45,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 +80,8 @@
                         @SelectorType int selectorType, boolean isEmergency,
                         @NonNull Looper looper, @NonNull ImsStateTracker imsStateTracker,
                         @NonNull DomainSelectorBase.DestroyListener listener,
-                        @NonNull CrossSimRedialingController crossSimRedialingController) {
+                        @NonNull CrossSimRedialingController crossSimRedialingController,
+                        @NonNull CarrierConfigHelper carrierConfigHelper) {
                     switch (selectorType) {
                         case DomainSelectionService.SELECTOR_TYPE_CALLING: // fallthrough
                         case DomainSelectionService.SELECTOR_TYPE_SMS: // fallthrough
@@ -107,6 +110,7 @@
     @Mock private TransportSelectorCallback mSelectorCallback1;
     @Mock private TransportSelectorCallback mSelectorCallback2;
     @Mock private ImsStateTracker mImsStateTracker;
+    @Mock private CarrierConfigHelper mCarrierConfigHelper;
 
     private final ServiceState mServiceState = new ServiceState();
     private final BarringInfo mBarringInfo = new BarringInfo();
@@ -128,11 +132,14 @@
 
         mContext = new TestContext();
         mDomainSelectionService = new TelephonyDomainSelectionService(mContext,
-                mImsStateTrackerFactory, mDomainSelectorFactory);
+                mImsStateTrackerFactory, mDomainSelectorFactory, mCarrierConfigHelper);
         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(
diff --git a/utils/satellite/README.md b/utils/satellite/README.md
new file mode 100644
index 0000000..e219823
--- /dev/null
+++ b/utils/satellite/README.md
@@ -0,0 +1,62 @@
+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
+ `S2RangeFileBasedSatelliteLocationLookup`.
+
+`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 `signed-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_createtestsats2file /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.
\ 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 &gt;= 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..4b8a026
--- /dev/null
+++ b/utils/satellite/s2storage/src/testutils/java/com/android/telephony/sats2range/testutils/TestUtils.java
@@ -0,0 +1,144 @@
+/*
+ * 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(String.valueOf(fileFormat.createCellId(0b100_11111111, suffix)));
+            }
+
+            // Range 2
+            for (int suffix = 2001; suffix < 3000; suffix++) {
+                printer.println(String.valueOf(fileFormat.createCellId(0b100_11111111, suffix)));
+            }
+
+            // Range 3
+            for (int suffix = 1000; suffix < 2000; suffix++) {
+                printer.println(String.valueOf(fileFormat.createCellId(0b101_11111111, suffix)));
+            }
+            printer.print(String.valueOf(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(String.valueOf(fileFormat.createCellId(0b100_11111111, 100)));
+
+            // Invalid line
+            printer.print("Invalid line");
+
+            // Another valid line
+            printer.println(String.valueOf(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..9aacdd9
--- /dev/null
+++ b/utils/satellite/tools/Android.bp
@@ -0,0 +1,71 @@
+// 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 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 &lt;file name&gt;
+     */
+    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..b701a7b
--- /dev/null
+++ b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/SatS2FileCreator.java
@@ -0,0 +1,246 @@
+/*
+ * 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.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Scanner;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/** 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 a signed-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
+        List<S2CellId> sortedS2CellIds = s2Cells.stream()
+                .map(x -> new S2CellId(x))
+                .collect(Collectors.toList());
+        // IDs of S2CellId are converted to unsigned long numbers, which will be then used to
+        // compare S2CellId.
+        Collections.sort(sortedS2CellIds);
+
+        // 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
+     *                  a 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.hasNextLong()) {
+                s2Cells.add(scanner.nextLong());
+            }
+            if (scanner.hasNextLine()) {
+                throw new IllegalStateException("Input s2 cell file has invalid format, "
+                        + "current line=" + scanner.nextLine());
+            }
+        }
+        return s2Cells;
+    }
+
+    /**
+     * 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/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);
+        }
+    }
+}