Merge "Move onRttInitiated callback onto TelephonyConnection handler."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index bf6872e..32733cc 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -199,6 +199,7 @@
android:label="@string/emergencyDialerIconLabel"
android:theme="@style/EmergencyDialerTheme"
android:screenOrientation="portrait"
+ android:showWhenLocked="true"
android:exported="true"
android:resizeableActivity="false">
<intent-filter>
@@ -515,7 +516,6 @@
<activity android:name="com.android.phone.settings.VoicemailSettingsActivity"
android:label="@string/voicemail"
android:configChanges="orientation|screenSize|keyboardHidden|screenLayout"
- android:screenOrientation="portrait"
android:exported="true"
android:theme="@style/CallSettingsWithoutDividerTheme">
<intent-filter >
diff --git a/ecc/input/eccdata.txt b/ecc/input/eccdata.txt
index f3f088b..e1c7306 100644
--- a/ecc/input/eccdata.txt
+++ b/ecc/input/eccdata.txt
@@ -215,6 +215,19 @@
types: POLICE
types: AMBULANCE
types: FIRE
+ routing: EMERGENCY
+ }
+ eccs {
+ phone_number: "100"
+ types: POLICE
+ types: AMBULANCE
+ types: FIRE
+ routing: EMERGENCY
+ }
+ eccs {
+ phone_number: "101"
+ types: TYPE_UNSPECIFIED
+ routing: NORMAL
}
ecc_fallback: "112"
}
@@ -482,6 +495,7 @@
eccs {
phone_number: "133"
types: POLICE
+ routing: EMERGENCY
}
eccs {
phone_number: "131"
@@ -627,6 +641,7 @@
types: POLICE
types: AMBULANCE
types: FIRE
+ routing: EMERGENCY
}
ecc_fallback: "112"
}
@@ -677,6 +692,7 @@
types: POLICE
types: AMBULANCE
types: FIRE
+ routing: EMERGENCY
}
ecc_fallback: "112"
}
@@ -739,6 +755,7 @@
types: POLICE
types: AMBULANCE
types: FIRE
+ routing: EMERGENCY
}
ecc_fallback: "112"
}
@@ -848,6 +865,11 @@
types: TYPE_UNSPECIFIED
routing: NORMAL
}
+ eccs {
+ phone_number: "116111"
+ types: TYPE_UNSPECIFIED
+ routing: NORMAL
+ }
ecc_fallback: "112"
}
countries {
@@ -1259,11 +1281,18 @@
eccs {
phone_number: "110"
types: POLICE
+ routing: EMERGENCY
+ }
+ eccs {
+ phone_number: "118"
+ types: MARINE_GUARD
+ routing: EMERGENCY
}
eccs {
phone_number: "119"
types: AMBULANCE
types: FIRE
+ routing: EMERGENCY
}
ecc_fallback: "112"
}
@@ -1365,11 +1394,42 @@
eccs {
phone_number: "112"
types: POLICE
+ routing: EMERGENCY
}
eccs {
phone_number: "119"
- types: AMBULANCE
types: FIRE
+ routing: EMERGENCY
+ }
+ eccs {
+ phone_number: "122"
+ types: MARINE_GUARD
+ routing: EMERGENCY
+ }
+ eccs {
+ phone_number: "111"
+ types: TYPE_UNSPECIFIED
+ routing: EMERGENCY
+ }
+ eccs {
+ phone_number: "113"
+ types: TYPE_UNSPECIFIED
+ routing: EMERGENCY
+ }
+ eccs {
+ phone_number: "117"
+ types: TYPE_UNSPECIFIED
+ routing: EMERGENCY
+ }
+ eccs {
+ phone_number: "118"
+ types: TYPE_UNSPECIFIED
+ routing: EMERGENCY
+ }
+ eccs {
+ phone_number: "125"
+ types: TYPE_UNSPECIFIED
+ routing: EMERGENCY
}
ecc_fallback: "112"
}
@@ -1504,6 +1564,7 @@
types: POLICE
types: AMBULANCE
types: FIRE
+ routing: EMERGENCY
}
ecc_fallback: "112"
}
@@ -1793,6 +1854,7 @@
types: POLICE
types: AMBULANCE
types: FIRE
+ routing: EMERGENCY
}
ecc_fallback: "911"
}
@@ -1908,8 +1970,17 @@
eccs {
phone_number: "112"
types: POLICE
+ routing: EMERGENCY
+ }
+ eccs {
+ phone_number: "113"
types: AMBULANCE
+ routing: EMERGENCY
+ }
+ eccs {
+ phone_number: "110"
types: FIRE
+ routing: EMERGENCY
}
ecc_fallback: "112"
}
@@ -1962,6 +2033,21 @@
types: POLICE
types: AMBULANCE
types: FIRE
+ routing: EMERGENCY
+ }
+ eccs {
+ phone_number: "112"
+ types: POLICE
+ types: AMBULANCE
+ types: FIRE
+ routing: EMERGENCY
+ }
+ eccs {
+ phone_number: "911"
+ types: POLICE
+ types: AMBULANCE
+ types: FIRE
+ routing: EMERGENCY
}
ecc_fallback: "112"
}
@@ -1993,6 +2079,7 @@
eccs {
phone_number: "105"
types: POLICE
+ routing: EMERGENCY
}
eccs {
phone_number: "110"
@@ -2121,6 +2208,14 @@
types: POLICE
types: AMBULANCE
types: FIRE
+ routing: EMERGENCY
+ }
+ eccs {
+ phone_number: "911"
+ types: POLICE
+ types: AMBULANCE
+ types: FIRE
+ routing: EMERGENCY
}
ecc_fallback: "112"
}
@@ -2281,6 +2376,7 @@
types: POLICE
types: AMBULANCE
types: FIRE
+ routing: EMERGENCY
}
ecc_fallback: "112"
}
@@ -2701,6 +2797,7 @@
types: POLICE
types: AMBULANCE
types: FIRE
+ routing: EMERGENCY
}
ecc_fallback: "911"
}
diff --git a/ecc/output/OWNERS b/ecc/output/OWNERS
index d9ecbb7..5685875 100644
--- a/ecc/output/OWNERS
+++ b/ecc/output/OWNERS
@@ -1,5 +1,4 @@
set noparent
-djkrause@google.com
-satk@google.com
-somakala@google.com
\ No newline at end of file
+tgunn@google.com
+chinmayd@google.com
\ No newline at end of file
diff --git a/ecc/output/eccdata b/ecc/output/eccdata
index 4a3a2c8..8ab89d6 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 955929e..7a251d8 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Gekoppel"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Opgeskort"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Onbekend"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Primêr"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"grepe"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 5137085..dcd184e 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"ተገናኝቷል"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"ታግዷል"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"ያልታወቀ"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"ቀዳሚ"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"ባይቶች"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 24e634f..db93b63 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"متّصل بالإنترنت"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"معلّق"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"غير معروف"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"أساسي"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"الحزم"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"بايت"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"بالديسيبل"</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index aab6884..125539f 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"সংযোগ কৰা হ’ল"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"নিলম্বন কৰা হৈছে"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"অজ্ঞাত"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"প্ৰাথমিক"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"পিকেটিএছ"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"বাইটসমূহ"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"ডিবিএম"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 479738f..a1e694b 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Qoşuldu"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Dayandırılıb"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Naməlum"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"İlkin"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkt"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bayt"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 9815d95..0f72816 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Povezano"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Suspendovano"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Nepoznato"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Primarno"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pak."</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bajt(ov)a"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 188e778..c3f1678 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Падключана"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Прыпынена"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Невядома"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Асноўны"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"пак."</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"Б"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"дБм"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 9426c5c..168d749 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Установена е връзка"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Спрени"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Неизвестно"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Основна"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"пакета"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"байта"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 4054d88..93685f0 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"কানেক্ট করা হয়েছে"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"সাসপেন্ড করা হয়েছে"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"অজানা"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"প্রাথমিক"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"বাইট"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index c0bb458..7f0c408 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Povezano"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Obustavljeno"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Nepoznato"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Primarno"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"paketi"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bajtova"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 6b18654..be5be33 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Amb connexió"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"En suspensió"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Desconegut"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Principal"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"paquets"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bytes"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index fc63e25..b0cde9a 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Připojeno"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Pozastaveno"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Neznámé"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Primární"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pakety"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"B"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index c41d548..d9505a5 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -577,7 +577,7 @@
<string name="dialerKeyboardHintText" msgid="1115266533703764049">"Brug tastatur til at ringe op"</string>
<string name="onscreenHoldText" msgid="4025348842151665191">"Hold"</string>
<string name="onscreenEndCallText" msgid="6138725377654842757">"Afslut"</string>
- <string name="onscreenShowDialpadText" msgid="658465753816164079">"Nummerblok"</string>
+ <string name="onscreenShowDialpadText" msgid="658465753816164079">"Nummertaster"</string>
<string name="onscreenMuteText" msgid="5470306116733843621">"Lyd fra"</string>
<string name="onscreenAddCallText" msgid="9075675082903611677">"Tilføj opkald"</string>
<string name="onscreenMergeCallsText" msgid="3692389519611225407">"Slå opkald sammen"</string>
@@ -657,8 +657,8 @@
<string name="selectContact" msgid="1527612842599767382">"vælg kontakt"</string>
<string name="not_voice_capable" msgid="2819996734252084253">"Taleopkald understøttes ikke"</string>
<string name="description_dial_button" msgid="8614631902795087259">"ring op"</string>
- <string name="description_dialpad_button" msgid="7395114120463883623">"vis numerisk tastatur"</string>
- <string name="pane_title_emergency_dialpad" msgid="3627372514638694401">"Numerisk tastatur til nødopkald"</string>
+ <string name="description_dialpad_button" msgid="7395114120463883623">"vis nummertastatur"</string>
+ <string name="pane_title_emergency_dialpad" msgid="3627372514638694401">"Nummertastatur til nødopkald"</string>
<string name="voicemail_visual_voicemail_switch_title" msgid="6610414098912832120">"Visuel telefonsvarer"</string>
<string name="voicemail_set_pin_dialog_title" msgid="7005128605986960003">"Angiv pinkode"</string>
<string name="voicemail_change_pin_dialog_title" msgid="4633077715231764435">"Skift pinkode"</string>
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Tilsluttet"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Suspenderet"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Ukendt"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Primær"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pk."</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bytes"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 2594f47..45875d1 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Verbunden"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Gesperrt"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Unbekannt"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Primär"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"Pakete"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"Bytes"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 6af4940..8400f09 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Σε σύνδεση"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Σε αναστολή"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Άγνωστο"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Κύρια"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"byte"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index dc357ae..8451cc7 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Connected"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Suspended"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Unknown"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Primary"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bytes"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index e605117..67cb90d 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Connected"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Suspended"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Unknown"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Primary"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bytes"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index dc357ae..8451cc7 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Connected"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Suspended"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Unknown"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Primary"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bytes"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index dc357ae..8451cc7 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Connected"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Suspended"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Unknown"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Primary"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bytes"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 2284c44..8a26155 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Connected"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Suspended"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Unknown"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Primary"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bytes"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 2ba08e9..97f7985 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Conectado"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Suspendido"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Desconocido"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Principal"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"paquetes"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bytes"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index b447036..b5e18fb 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Conectado"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Suspendido"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Desconocido"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Principal"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pqts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bytes"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index c458160..84566b5 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Ühendatud"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Peatatud"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Teadmata"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Peamine"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"baiti"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 92df0e7..cbe88e0 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Konektatuta"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Behin-behinean itxitakoak"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Ezezaguna"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Nagusia"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"byte"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 6743e96..5d6a5ce 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"متصل"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"تعلیقشده"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"نامشخص"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"اصلی"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"بایت"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index b835a0c..a4c9836 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Yhdistetty"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Jäädytetty"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Tuntematon"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Ensisijainen"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pakettia"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"tavua"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index a2e9063..0483dd6 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Connecté"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Suspendu"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Inconnu"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Principal"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"paquets"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"octets"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 55fc28e..e3662ec 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Connecté"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Suspendu"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Inconnu"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Principal"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"paquets"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"octets"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index ba68d12..9890857 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -665,7 +665,7 @@
<string name="preference_category_ringtone" msgid="8787281191375434976">"Ton de chamada e vibración"</string>
<string name="pstn_connection_service_label" msgid="9200102709997537069">"Tarxetas SIM integradas"</string>
<string name="enable_video_calling_title" msgid="7246600931634161830">"Activar videochamadas"</string>
- <string name="enable_video_calling_dialog_msg" msgid="7141478720386203540">"Para activar as videochamadas, necesitas activar o Modo LTE 4G mellorado na configuración do sistema."</string>
+ <string name="enable_video_calling_dialog_msg" msgid="7141478720386203540">"Para activar as videochamadas, necesitas activar o modo LTE 4G mellorado na configuración do sistema."</string>
<string name="enable_video_calling_dialog_settings" msgid="8697890611305307110">"Configuración de rede"</string>
<string name="enable_video_calling_dialog_close" msgid="4298929725917045270">"Pechar"</string>
<string name="sim_label_emergency_calls" msgid="9078241989421522310">"Chamadas de emerxencia"</string>
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Conectada"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Suspendido"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Descoñecido"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Principal"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bytes"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 1cc4dd1..bafa321 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"કનેક્ટેડ"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"સસ્પેન્ડ કરી"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"અજાણ"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"પ્રાથમિક"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"બાઇટ"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index d9de3dd..d2be9dd 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"कनेक्ट किया गया"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"थोड़ी देर के लिए रोक लगी है"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"अज्ञात"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"प्राइमरी"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"बाइट"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index d5349a0..c7d0607 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Povezano"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Obustavljeno"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Nepoznato"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Primarno"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bajtovi"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 63ae0b9..872abe1 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Csatlakoztatva"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Felfüggesztve"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Ismeretlen"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Elsődleges"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"csomag"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bájt"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 4b9a9a9..26b2648 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Միացված է"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Անջատված է"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Անհայտ"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Հիմնական"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"բայթ"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index d58999b..4551592 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -48,7 +48,7 @@
<string name="no_vm_number_msg" msgid="5165161462411372504">"Tidak ada nomor kotak pesan tersimpan pada kartu SIM."</string>
<string name="add_vm_number_str" msgid="7368168964435881637">"Tambahkan nomor"</string>
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"Setelan Pesan Suara hanya dapat diubah oleh Pengguna Utama."</string>
- <string name="puk_unlocked" msgid="4627340655215746511">"Kartu SIM Anda tidak lagi dicekal. Ponsel Anda sedang dibuka kuncinya..."</string>
+ <string name="puk_unlocked" msgid="4627340655215746511">"Kartu SIM Anda tidak lagi diblokir. Ponsel Anda sedang dibuka kuncinya..."</string>
<string name="label_ndp" msgid="7617392683877410341">"PIN pembuka kunci jaringan SIM"</string>
<string name="label_phoneid" msgid="8775611434123577808">"SIM dikunci untuk operator"</string>
<string name="sim_ndp_unlock_text" msgid="7737338355451978338">"Buka kunci"</string>
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Terhubung"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Ditangguhkan"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Tidak diketahui"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Primer"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"byte"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index a475a9e..0ccd715 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Tengt"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Lokað tímabundið"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Óþekkt"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Aðal"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pk."</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bæti"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 2eaf7ee..be494db 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Connesso"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Sospeso"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Sconosciuto"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Principale"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkt"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"byte"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 154c8a0..c8f246e 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"מקושר"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"בהשעיה"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"לא ידוע"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"ראשי"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"בייטים"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 4d9f50d..0679d84 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"接続済み"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"停止中"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"不明"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"メイン"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"バイト"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 3f4f5a1..f5d6045 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"დაკავშირებულია"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"შეჩერებულია"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"უცნობი"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"ძირითადი"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"პკტ."</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"ბაიტი"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 048b5a0..0d13867 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -452,7 +452,7 @@
<string name="sum_fdn_change_pin" msgid="3510994280557335727">"ТТН қол жетімділігі үшін PIN кодты өзгерту"</string>
<string name="sum_fdn_manage_list" msgid="3311397063233992907">"Телефон нөмірлері тізімін басқару"</string>
<string name="voice_privacy" msgid="7346935172372181951">"Дауыс құпиялығы"</string>
- <string name="voice_privacy_summary" msgid="3556460926168473346">"Жетілдірілген құпиялылық режимін қосу"</string>
+ <string name="voice_privacy_summary" msgid="3556460926168473346">"Жетілдірілген құпиялық режимін қосу"</string>
<string name="tty_mode_option_title" msgid="3843817710032641703">"Tелетайп режимі"</string>
<string name="tty_mode_option_summary" msgid="4770510287236494371">"Телетайп режиміне реттеу"</string>
<string name="auto_retry_mode_title" msgid="2985801935424422340">"Әрекетті автоматты қайталау"</string>
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Жалғанған"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Уақытша тоқтатылған"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Белгісіз"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Негізгі"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"байт"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 40ebda9..38177be 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"បានភ្ជាប់"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"បានផ្អាក"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"មិនស្គាល់"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"ចម្បង"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"បៃ"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 97ce9fc..8b7b574 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"ಅಮಾನತುಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"ಅಪರಿಚಿತ"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"ಪ್ರಾಥಮಿಕ"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"ಬೈಟ್ಗಳು"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 7e6fe78..2e64555 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"연결됨"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"정지됨"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"알 수 없음"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"기본"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"패킷"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"바이트"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index d966d45..9443c12 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Туташты"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Убактылуу токтотулду"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Белгисиз"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Башкы"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"байттар"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index bd7e6ab..9dbd694 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"ເຊື່ອມຕໍ່ແລ້ວ"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"ລະງັບໄວ້ແລ້ວ"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"ບໍ່ຮູ້ຈັກ"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"ຫຼັກ"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"ໄບຕ໌"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 4784b8a..ef02da0 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Prisijungta"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Laikinai sustabdyta"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Nežinoma"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Pagrindinis"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pakuot."</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"B"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index a61f412..890dc79 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Savienojums izveidots"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Darbība apturēta"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Nezināms"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Primārais"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"baiti"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 16e5f8d..70a9d6e 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Поврзан"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Суспендиран"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Непознат"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Примарен"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"бајти"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index a7fa922..3d01a16 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"കണക്റ്റ് ചെയ്തു"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"താൽക്കാലികമായി റദ്ദാക്കി"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"അജ്ഞാതം"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"പ്രാഥമികം"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"ബൈറ്റുകൾ"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index b04b00d..bdf8df1 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Холбогдсон"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Түр хаасан"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Тодорхойгүй"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Үндсэн"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"байт"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index a1c4bf0..719297a 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -424,7 +424,7 @@
</string-array>
<string name="cdma_subscription_title" msgid="3449527179325589434">"CDMA सदस्यता"</string>
<string name="cdma_subscription_summary" msgid="5681152534466169001">"RUIM/सिम आणि NV मध्ये बदला"</string>
- <string name="cdma_subscription_dialogtitle" msgid="8872086335839723980">"सदस्यता"</string>
+ <string name="cdma_subscription_dialogtitle" msgid="8872086335839723980">"सदस्यत्व"</string>
<string-array name="cdma_subscription_choices">
<item msgid="7989486897370727698">"RUIM/सिम"</item>
<item msgid="5445342771222849381">"NV"</item>
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"कनेक्ट केले"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"निलंबित केले"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"अज्ञात"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"प्राथमिक"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"बाइट"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 8bacab4..2240b6f 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Disambungkan"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Digantung"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Tidak diketahui"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Utama"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"bgksn"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bait"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 9dbce74..6114e94 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"ချိတ်ဆက်ထားသည်"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"ဆိုင်းငံ့ထားသည်"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"မသိရသေးပါ"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"အဓိက"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"ဘိုက်"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index a099240..2957043 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Tilkoblet"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Sperret midlertidig"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Ukjent"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Primær"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pakker"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"byte"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index d2ba6c3..f21bb2a 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"जडान गरियो"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"निलम्बित"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"अज्ञात"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"प्राथमिक"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"बाइटहरू"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index e7fa4d0..3820e52 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Verbonden"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Opgeschort"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Onbekend"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Primair"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pakketten"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bytes"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 89aa190..5db4c61 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"ସଂଯୋଗ କରାଯାଇଛି"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"ନିଲମ୍ବନ କରାଯାଇଛି"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"ଅଜଣା"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"ପ୍ରାଥମିକ"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"ପ୍ୟାକେଟ୍"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"ବାଇଟ୍ସ"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 40bf12a..8a44ab8 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"ਕਨੈਕਟ ਹੈ"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"ਮੁਅੱਤਲ ਕੀਤਾ ਗਿਆ"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"ਅਗਿਆਤ"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"ਪ੍ਰਾਇਮਰੀ"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"ਬਾਈਟਾਂ"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index d9774d9..cd9e7a6 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Połączono"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Zawieszone"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Brak informacji"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Główny"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"– pakiety"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"B"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index c8ce62f..4e19fc0 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Ligado"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Suspenso"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Desconhecido"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Principal"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bytes"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 6cbc051..38109a8 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Conectado"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Suspenso"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Desconhecido"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Principal"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bytes"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 90f918e..df11b3f 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Conectat"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Suspendat"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Necunoscut"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Principal"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pachete"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"byți"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 9df5817..88059d8 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Подключено"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Заблокировано"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Неизвестно"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Основной"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"байт"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 97b09d7..f04a45f 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"සම්බන්ධිතයි"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"අත්හිටුවා ඇත"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"නොදන්නා"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"මූලික"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"බයිට"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index b084876..faf16d4 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Pripojené"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Pozastavené"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Neznáme"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Primárne"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pakety"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"B"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index ff14a87..71e47d8 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Povezano"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Onemogočeno"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Neznano"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Glavno"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"B"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 6866372..ce593ed 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -660,7 +660,7 @@
<string name="description_dialpad_button" msgid="7395114120463883623">"shfaq bllokun e formimit të numrit"</string>
<string name="pane_title_emergency_dialpad" msgid="3627372514638694401">"Blloku i formimit të numrit të urgjencës"</string>
<string name="voicemail_visual_voicemail_switch_title" msgid="6610414098912832120">"Posta zanore vizuale"</string>
- <string name="voicemail_set_pin_dialog_title" msgid="7005128605986960003">"Konfiguro kodin PIN"</string>
+ <string name="voicemail_set_pin_dialog_title" msgid="7005128605986960003">"Cakto kodin PIN"</string>
<string name="voicemail_change_pin_dialog_title" msgid="4633077715231764435">"Ndrysho kodin PIN"</string>
<string name="preference_category_ringtone" msgid="8787281191375434976">"Me zile dhe me dridhje"</string>
<string name="pstn_connection_service_label" msgid="9200102709997537069">"Kartat e integruara SIM"</string>
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Lidhur"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Pezulluar"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"E panjohur"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Kryesore"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"paketa"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bajte"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 5a409f1..34892f3 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Повезано"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Суспендовано"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Непознато"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Примарно"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"пак."</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"бајт(ов)а"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index f64d1bc..cf5ba2a 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Ansluten"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Tillfälligt avstängt"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Okänt"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Primär"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"paket"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bytes"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 849691e..285f910 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Imeunganishwa"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Imesimamishwa"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Haijulikani"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Msingi"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"baiti"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index c782c0f..4919c99 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"இணைக்கப்பட்டுள்ளது"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"இடைநீக்கப்பட்டது"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"தெரியாதது"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"முதன்மை"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"பைட்டுகள்"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 9aa0800..80e077a 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -30,7 +30,7 @@
<string name="mmiStarted" msgid="9212975136944568623">"MMI కోడ్ ప్రారంభించబడింది"</string>
<string name="ussdRunning" msgid="1163586813106772717">"USSD కోడ్ అమలు చేయబడుతోంది…"</string>
<string name="mmiCancelled" msgid="5339191899200678272">"MMI కోడ్ రద్దు చేయబడింది"</string>
- <string name="cancel" msgid="8984206397635155197">"రద్దు చేయి"</string>
+ <string name="cancel" msgid="8984206397635155197">"రద్దు చేయండి"</string>
<string name="enter_input" msgid="6193628663039958990">"USSD మెసేజ్ తప్పనిసరిగా <xliff:g id="MIN_LEN">%1$d</xliff:g> మరియు <xliff:g id="MAX_LEN">%2$d</xliff:g> అక్షరాల మధ్య ఉండాలి. దయచేసి మళ్లీ ప్రయత్నించండి."</string>
<string name="manageConferenceLabel" msgid="8415044818156353233">"కాన్ఫరెన్స్ కాల్ను నిర్వహించండి"</string>
<string name="ok" msgid="7818974223666140165">"సరే"</string>
@@ -46,7 +46,7 @@
<string name="wild_prompt_str" msgid="5858910969703305375">"దీనితో వైల్డ్ అక్షరాన్ని భర్తీ చేయండి"</string>
<string name="no_vm_number" msgid="6623853880546176930">"వాయిస్ మెయిల్ నంబర్ లేదు"</string>
<string name="no_vm_number_msg" msgid="5165161462411372504">"సిమ్ కార్డులో వాయిస్ మెయిల్ నంబర్ ఏదీ నిల్వ చేయబడలేదు."</string>
- <string name="add_vm_number_str" msgid="7368168964435881637">"నంబర్ను జోడించు"</string>
+ <string name="add_vm_number_str" msgid="7368168964435881637">"నంబర్ను జోడించండి"</string>
<string name="voice_number_setting_primary_user_only" msgid="3394706575741912843">"వాయిస్ మెయిల్ సెట్టింగ్లను ప్రాథమిక వినియోగదారు మాత్రమే ఎడిట్ చేయగలరు."</string>
<string name="puk_unlocked" msgid="4627340655215746511">"మీ SIM కార్డు అన్బ్లాక్ చేయబడింది. మీ ఫోన్ అన్లాక్ చేయబడుతోంది…"</string>
<string name="label_ndp" msgid="7617392683877410341">"SIM నెట్వర్క్ అన్లాక్ పిన్"</string>
@@ -71,7 +71,7 @@
<string name="phone_accounts_settings_header" msgid="6296501692964706536">"సెట్టింగ్లు"</string>
<string name="phone_accounts_choose_accounts" msgid="4748805293314824974">"ఖాతాలను ఎంచుకోండి"</string>
<string name="phone_accounts_selection_header" msgid="2945830843104108440">"ఫోన్ ఖాతాలు"</string>
- <string name="phone_accounts_add_sip_account" msgid="1437634802033309305">"SIP ఖాతాను జోడించు"</string>
+ <string name="phone_accounts_add_sip_account" msgid="1437634802033309305">"SIP ఖాతాను జోడించండి"</string>
<string name="phone_accounts_configure_account_settings" msgid="6622119715253196586">"ఖాతా సెట్టింగ్లను కాన్ఫిగర్ చేయి"</string>
<string name="phone_accounts_all_calling_accounts" msgid="1609600743500618823">"అన్ని కాలింగ్ ఖాతాలు"</string>
<string name="phone_accounts_all_calling_accounts_summary" msgid="2214134955430107240">"ఏ ఖాతాల నుండి కాల్స్ చేయవచ్చనే దాన్ని ఎంచుకోండి"</string>
@@ -81,7 +81,7 @@
<string name="voicemail_settings_with_label" msgid="4228431668214894138">"వాయిస్ మెయిల్ (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="voicemail_abbreviated" msgid="7746778673131551185">"VM:"</string>
<string name="make_and_receive_calls" msgid="4868913166494621109">"కాల్స్ను చేయండి & స్వీకరించండి"</string>
- <string name="smart_forwarding_settings_menu" msgid="8850429887958938540">"స్మార్ట్ ఫార్వర్డ్ను ప్రారంభించు"</string>
+ <string name="smart_forwarding_settings_menu" msgid="8850429887958938540">"స్మార్ట్ ఫార్వర్డ్ను ప్రారంభించండి"</string>
<string name="smart_forwarding_settings_menu_summary" msgid="5096947726032885325">"ఒక నంబర్ను చేరుకోలేకపోయినప్పుడు, మీ ఇతర నంబర్లకు కాల్స్ను ఎల్లప్పుడూ ఫార్వర్డ్ చేయబడతాయి."</string>
<string name="voicemail_notifications_preference_title" msgid="7829238858063382977">"నోటిఫికేషన్లు"</string>
<string name="cell_broadcast_settings" msgid="8135324242541809924">"అత్యవసర ప్రసారాలు"</string>
@@ -96,7 +96,7 @@
<string name="sum_loading_settings" msgid="434063780286688775">"సెట్టింగ్లను లోడ్ చేస్తోంది…"</string>
<string name="sum_hide_caller_id" msgid="131100328602371933">"అవుట్గోయింగ్ కాల్స్లో నంబర్ దాచబడుతుంది"</string>
<string name="sum_show_caller_id" msgid="3571854755324664591">"అవుట్గోయింగ్ కాల్స్లో నంబర్ ప్రదర్శించబడుతుంది"</string>
- <string name="sum_default_caller_id" msgid="1767070797135682959">"అవుట్గోయింగ్ కాల్స్లో నా నంబర్ను ప్రదర్శించడానికి ఆటోమేటిక్ ఆపరేటర్ సెట్టింగ్లను ఉపయోగించు"</string>
+ <string name="sum_default_caller_id" msgid="1767070797135682959">"అవుట్గోయింగ్ కాల్స్లో నా నంబర్ను ప్రదర్శించడానికి ఆటోమేటిక్ ఆపరేటర్ సెట్టింగ్లను ఉపయోగించండి"</string>
<string name="labelCW" msgid="8449327023861428622">"కాల్ నిరీక్షణ"</string>
<string name="sum_cw_enabled" msgid="3977308526187139996">"కాల్ సమయంలో, ఇన్కమింగ్ కాల్స్ల గురించి నాకు తెలియజేయి"</string>
<string name="sum_cw_disabled" msgid="3658094589461768637">"కాల్ సమయంలో, ఇన్కమింగ్ కాల్స్ల గురించి నాకు తెలియజేయి"</string>
@@ -104,7 +104,7 @@
<string name="call_forwarding_settings_with_label" msgid="2345432813399564272">"కాల్ ఫార్వార్డింగ్ సెట్టింగ్లు (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
<string name="labelCF" msgid="3578719437928476078">"కాల్ ఫార్వార్డింగ్"</string>
<string name="labelCFU" msgid="8870170873036279706">"ఎల్లప్పుడూ ఫార్వర్డ్ చేయి"</string>
- <string name="messageCFU" msgid="1361806450979589744">"ఎల్లప్పుడూ ఈ నంబర్ను ఉపయోగించు"</string>
+ <string name="messageCFU" msgid="1361806450979589744">"ఎల్లప్పుడూ ఈ నంబర్ను ఉపయోగించండి"</string>
<string name="sum_cfu_enabled_indicator" msgid="9030139213402432776">"అన్ని కాల్స్ను ఫార్వర్డ్ చేస్తోంది"</string>
<string name="sum_cfu_enabled" msgid="5806923046528144526">"అన్ని కాల్స్ను <xliff:g id="PHONENUMBER">{0}</xliff:g>కి ఫార్వర్డ్ చేస్తోంది"</string>
<string name="sum_cfu_enabled_no_number" msgid="7287752761743377930">"నంబర్ అందుబాటులో లేదు"</string>
@@ -128,7 +128,7 @@
<string name="cdma_call_waiting" msgid="4565070960879673216">"కాల్ వెయిటింగ్ను ఆన్ చేయాలా?"</string>
<string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"మీరు కాల్స్లో ఉండగా, ఇన్కమింగ్ కాల్స్ల గురించి మీకు తెలియజేయబడుతుంది"</string>
<string name="enable_cdma_cw" msgid="811047045863422232">"ఆన్ చేయి"</string>
- <string name="disable_cdma_cw" msgid="7119290446496301734">"రద్దు చేయి"</string>
+ <string name="disable_cdma_cw" msgid="7119290446496301734">"రద్దు చేయండి"</string>
<string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"IMSలో CDMA కాల్ వెయిటింగ్ ఆన్లో ఉంది"</string>
<string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"IMSలో CDMA కాల్ వెయిటింగ్ ఆఫ్లో ఉంది"</string>
<string name="updating_title" msgid="6130548922615719689">"కాల్ సెట్టింగ్లు"</string>
@@ -273,7 +273,7 @@
<string name="network_operator_category" msgid="4992217193732304680">"నెట్వర్క్"</string>
<string name="enhanced_4g_lte_mode_title" msgid="4213420368777080540">"మెరుగుపరిచిన 4G LTE మోడ్"</string>
<!-- no translation found for enhanced_4g_lte_mode_title_variant:0 (7240155150166394308) -->
- <string name="enhanced_4g_lte_mode_summary" msgid="7725708511804143638">"వాయిస్, ఇతర కమ్యూనికే. మెరుగుపరచడానికి LTE సేవలను ఉపయోగించు (సిఫార్సు చేయబడింది)"</string>
+ <string name="enhanced_4g_lte_mode_summary" msgid="7725708511804143638">"వాయిస్, ఇతర కమ్యూనికే. మెరుగుపరచడానికి LTE సేవలను ఉపయోగించండి (సిఫార్సు చేయబడింది)"</string>
<string name="enhanced_4g_lte_mode_summary_o2" msgid="2521108446409016542">"వాయిస్, ఇతర కమ్యూనికేషన్లను మెరుగుపరచడానికి 4G సేవలను ఉపయోగించండి (సిఫార్సు చేయబడింది)"</string>
<!-- no translation found for enhanced_4g_lte_mode_sumary_variant:0 (2943982616649705147) -->
<!-- no translation found for enhanced_4g_lte_mode_sumary_variant:1 (5262249464504131443) -->
@@ -301,7 +301,7 @@
<string name="data_usage_template" msgid="6287906680674061783">"<xliff:g id="ID_2">%2$s</xliff:g> మధ్య కాలంలో <xliff:g id="ID_1">%1$s</xliff:g> మొబైల్ డేటా ఉపయోగించబడింది"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"అధునాతన సెట్టింగ్లు"</string>
<string name="carrier_settings_euicc" msgid="1190237227261337749">"క్యారియర్"</string>
- <string name="keywords_carrier_settings_euicc" msgid="8540160967922063745">"క్యారియర్, ఇసిమ్, సిమ్, ఇయుక్, క్యారియర్లను మార్చు, క్యారియర్ను జోడించు"</string>
+ <string name="keywords_carrier_settings_euicc" msgid="8540160967922063745">"క్యారియర్, ఇసిమ్, సిమ్, ఇయుక్, క్యారియర్లను మార్చు, క్యారియర్ను జోడించండి"</string>
<string name="carrier_settings_euicc_summary" msgid="2027941166597330117">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g> — <xliff:g id="PHONE_NUMBER">%2$s</xliff:g>"</string>
<string name="mobile_data_settings_title" msgid="7228249980933944101">"మొబైల్ డేటా"</string>
<string name="mobile_data_settings_summary" msgid="5012570152029118471">"మొబైల్ నెట్వర్క్ను ఉపయోగించి డేటాను యాక్సెస్ చేయండి"</string>
@@ -443,11 +443,11 @@
<string name="fdn_activation" msgid="2178637004710435895">"FDN సక్రియం"</string>
<string name="fdn_enabled" msgid="7017355494808056447">"ఫిక్సెడ్ డయలింగ్ నంబర్లు ప్రారంభించబడ్డాయి"</string>
<string name="fdn_disabled" msgid="6696468878037736600">"ఫిక్సెడ్ డయలింగ్ నంబర్లు నిలిపివేయబడ్డాయి"</string>
- <string name="enable_fdn" msgid="4830555730418033723">"FDNని ప్రారంభించు"</string>
+ <string name="enable_fdn" msgid="4830555730418033723">"FDNని ప్రారంభించండి"</string>
<string name="disable_fdn" msgid="3918794950264647541">"FDNని నిలిపివేయి"</string>
<string name="change_pin2" msgid="3110844547237754871">"పిన్2ని మార్చు"</string>
<string name="enable_fdn_ok" msgid="5080925177369329827">"FDNని నిలిపివేయి"</string>
- <string name="disable_fdn_ok" msgid="3745475926874838676">"FDNని ప్రారంభించు"</string>
+ <string name="disable_fdn_ok" msgid="3745475926874838676">"FDNని ప్రారంభించండి"</string>
<string name="sum_fdn" msgid="6152246141642323582">"ఫిక్స్డ్ డయలింగ్ నంబర్లను నిర్వహించండి"</string>
<string name="sum_fdn_change_pin" msgid="3510994280557335727">"FDN యాక్సెస్ కోసం పిన్ను మార్చండి"</string>
<string name="sum_fdn_manage_list" msgid="3311397063233992907">"ఫోన్ నంబర్ లిస్ట్ను నిర్వహించండి"</string>
@@ -465,7 +465,7 @@
<string name="get_pin2" msgid="4221654606863196332">"PIN2ని టైప్ చేయండి"</string>
<string name="name" msgid="1347432469852527784">"పేరు"</string>
<string name="number" msgid="1564053487748491000">"నంబర్"</string>
- <string name="save" msgid="983805790346099749">"సేవ్ చేయి"</string>
+ <string name="save" msgid="983805790346099749">"సేవ్ చేయండి"</string>
<string name="add_fdn_contact" msgid="1169713422306640887">"ఫిక్స్డ్ డయలింగ్ నంబర్ను జోడించండి"</string>
<string name="adding_fdn_contact" msgid="3112531600824361259">"ఫిక్స్డ్ డయలింగ్ నంబర్ను జోడిస్తోంది..."</string>
<string name="fdn_contact_added" msgid="2840016151693394596">"ఫిక్స్డ్ డయలింగ్ నంబర్ జోడించబడింది."</string>
@@ -579,7 +579,7 @@
<string name="onscreenEndCallText" msgid="6138725377654842757">"ముగించు"</string>
<string name="onscreenShowDialpadText" msgid="658465753816164079">"డయల్ప్యాడ్"</string>
<string name="onscreenMuteText" msgid="5470306116733843621">"మ్యూట్ చేయి"</string>
- <string name="onscreenAddCallText" msgid="9075675082903611677">"కాల్ను జోడించు"</string>
+ <string name="onscreenAddCallText" msgid="9075675082903611677">"కాల్ను జోడించండి"</string>
<string name="onscreenMergeCallsText" msgid="3692389519611225407">"కాల్స్ను విలీనం చేయి"</string>
<string name="onscreenSwapCallsText" msgid="2682542150803377991">"స్వాప్ చేయి"</string>
<string name="onscreenManageCallsText" msgid="1162047856081836469">"కాల్స్ను మేనేజ్ చేయండి"</string>
@@ -685,7 +685,7 @@
<string name="callFailed_wfc_service_not_available_in_this_location" msgid="3624536608369524988">"ఈ లొకేషన్లో Wi-Fi కాలింగ్ అందుబాటులో లేదు."</string>
<string name="change_pin_title" msgid="3564254326626797321">"వాయిస్ మెయిల్ PINని మార్చండి"</string>
<string name="change_pin_continue_label" msgid="5177011752453506371">"కొనసాగించండి"</string>
- <string name="change_pin_cancel_label" msgid="2301711566758827936">"రద్దు చేయి"</string>
+ <string name="change_pin_cancel_label" msgid="2301711566758827936">"రద్దు చేయండి"</string>
<string name="change_pin_ok_label" msgid="6861082678817785330">"సరే"</string>
<string name="change_pin_enter_old_pin_header" msgid="853151335217594829">"మీ పాత PINని నిర్ధారించండి"</string>
<string name="change_pin_enter_old_pin_hint" msgid="8801292976275169367">"కొనసాగించడానికి మీ వాయిస్ మెయిల్ PINని నమోదు చేయండి."</string>
@@ -707,8 +707,8 @@
<string name="mobile_data_activate_footer" msgid="7895874069807204548">"మీరు మీ క్యారియర్ <xliff:g id="PROVIDER_NAME">%s</xliff:g> ద్వారా మొబైల్ డేటా లేదా రోమింగ్ ప్లాన్ను జోడించవచ్చు."</string>
<string name="mobile_data_activate_diag_title" msgid="5401741936224757312">"డేటాను జోడించాలా?"</string>
<string name="mobile_data_activate_diag_message" msgid="3527260988020415441">"మీరు <xliff:g id="PROVIDER_NAME">%s</xliff:g> ద్వారా డేటాను జోడించాల్సి ఉండవచ్చు"</string>
- <string name="mobile_data_activate_button" msgid="1139792516354374612">"డేటాను జోడించు"</string>
- <string name="mobile_data_activate_cancel_button" msgid="3530174817572005860">"రద్దు చేయి"</string>
+ <string name="mobile_data_activate_button" msgid="1139792516354374612">"డేటాను జోడించండి"</string>
+ <string name="mobile_data_activate_cancel_button" msgid="3530174817572005860">"రద్దు చేయండి"</string>
<string name="clh_card_title_call_ended_txt" msgid="5977978317527299698">"కాల్ ముగిసింది"</string>
<string name="clh_callFailed_powerOff_txt" msgid="8279934912560765361">"విమానం మోడ్ ఆన్లో ఉంది"</string>
<string name="clh_callFailed_simError_txt" msgid="5128538525762326413">"SIM కార్డ్ని యాక్సెస్ చేయడం సాధ్యపడలేదు"</string>
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"కనెక్ట్ చేయబడింది"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"తాత్కాలికంగా నిలిపివేయబడింది"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"తెలియదు"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"ప్రాథమిక"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"బైట్లు"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
@@ -900,7 +901,7 @@
<string name="ping_test_label" msgid="448617502935719694">"పింగ్ పరీక్షను అమలు చేయండి"</string>
<string name="radio_info_smsc_label" msgid="3749927072726033763">"SMSC:"</string>
<string name="radio_info_smsc_update_label" msgid="5141996256097115753">"అప్డేట్ చేయండి"</string>
- <string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"రిఫ్రెష్ చేయి"</string>
+ <string name="radio_info_smsc_refresh_label" msgid="8409923721451604560">"రిఫ్రెష్ చేయండి"</string>
<string name="radio_info_toggle_dns_check_label" msgid="1394078554927787350">"DNS తనిఖీని టోగుల్ చేయండి"</string>
<string name="oem_radio_info_label" msgid="2914167475119997456">"OEM-నిర్దిష్ట సమాచారం/సెట్టింగ్లు"</string>
<string name="radio_info_endc_available" msgid="2983767110681230019">"EN-DC అందుబాటులో ఉన్న (NSA):"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index d8640f8..0f990bf 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"เชื่อมต่อแล้ว"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"ถูกระงับ"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"ไม่รู้จัก"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"หลัก"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"ไบต์"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 08df78b..eae12df 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -511,7 +511,7 @@
<string name="invalidPuk2" msgid="713729511903849544">"Maglagay ng PUK2 na 8 numero."</string>
<string name="pin2_changed" msgid="5710551850481287821">"Na-update na ang PIN2"</string>
<string name="label_puk2_code" msgid="2852217004288085562">"Ilagay ang PUK2 code"</string>
- <string name="fdn_enable_puk2_requested" msgid="5793652792131588041">"Mali ang password. Naka-block na ngayon ang PIN2. Upang subukang muli, baguhin ang PIN 2."</string>
+ <string name="fdn_enable_puk2_requested" msgid="5793652792131588041">"Mali ang password. Naka-block na ngayon ang PIN2. Upang subukan ulit, baguhin ang PIN 2."</string>
<string name="puk2_requested" msgid="6992374450720307514">"Mali ang password. Naka-lock na ngayon ang SIM. Ilagay ang PUK2."</string>
<string name="puk2_blocked" msgid="3131139031779319911">"Permanenteng na-block ang PUK2."</string>
<string name="pin2_attempts" msgid="5625178102026453023">\n"Mayroon ka na lang <xliff:g id="NUMBER">%d</xliff:g> (na) natitirang pagsubok."</string>
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Konektado"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Sinuspinde"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Hindi alam"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Pangunahin"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bytes"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 66ddb7f..1afe088 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Bağlı"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Askıya alındı"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Bilinmiyor"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Birincil"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkt"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bayt"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 16322ba..c2626c7 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Підключено"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Заблоковано"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Невідомо"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Основний"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"пак."</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"байт"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"дБм"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 1b0fbbf..0250039 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"منسلک"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"معطل کر دیا گیا"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"نامعلوم"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"بنیادی"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"پیکٹس"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"بائٹس"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index f8f663f..1577a55 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Ulangan"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Bloklangan"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Noma’lum"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Asosiy"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"bayt"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 186448a..caafd7a 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Đã kết nối"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Bị tạm ngưng"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Không xác định"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Chính"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"byte"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index acee999..58d0344 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"已连接"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"已暂停"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"未知"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"主要"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"字节"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 7923cbb..208d7d3 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"已連線"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"已停用"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"不明"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"主要"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkt"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"位元組"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index c43183c..7bcd733 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"已連線"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"待命"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"不明"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"主要"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"pkts"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"位元組"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index d111a5e..1fb675f 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -862,6 +862,7 @@
<string name="radioInfo_data_connected" msgid="7637335645634239508">"Ixhunyiwe"</string>
<string name="radioInfo_data_suspended" msgid="8695262782642002785">"Kumiswe okwesikhashana"</string>
<string name="radioInfo_unknown" msgid="5401423738500672850">"Akwaziwa"</string>
+ <string name="radioInfo_imei_primary" msgid="5948747378637224400">"Okuyinhloko"</string>
<string name="radioInfo_display_packets" msgid="6794302192441084157">"amaphakethe"</string>
<string name="radioInfo_display_bytes" msgid="7701006329222413797">"amabhaythi"</string>
<string name="radioInfo_display_dbm" msgid="7491944975710865703">"dBm"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index 19252c0..ba65302 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -317,4 +317,8 @@
<!-- The package names which can request thermal mitigation. -->
<string-array name="thermal_mitigation_allowlisted_packages" translatable="false">
</string-array>
+
+ <!-- Flag specifying whether the AOSP domain selection is enabled or
+ the device should fallback to the modem based domain selection architecture. -->
+ <bool name="config_enable_aosp_domain_selection">false</bool>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 44151c5..79119a3 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2070,6 +2070,8 @@
<!-- Used for diagnostic info screens, precise translation isn't needed -->
<string name="radioInfo_unknown">Unknown</string>
+ <!-- Used for diagnostic info screens, precise translation isn't needed -->
+ <string name="radioInfo_imei_primary">Primary</string>
<!-- Phone Info screen. Units shown after a value. Used for diagnostic info screens, precise translation isn't needed -->
<string name="radioInfo_display_packets">pkts</string>
<!-- Phone Info screen. Units shown after a value. 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 7f61f78..14db930 100644
--- a/src/com/android/phone/CallNotifier.java
+++ b/src/com/android/phone/CallNotifier.java
@@ -40,10 +40,12 @@
import com.android.internal.telephony.CallManager;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.SubscriptionController;
import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaDisplayInfoRec;
import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaSignalInfoRec;
import com.android.internal.telephony.cdma.SignalToneUtil;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
import java.util.ArrayList;
import java.util.Collections;
@@ -487,9 +489,16 @@
}
public void updatePhoneStateListeners(boolean isRefresh, int updateType, int subIdToUpdate) {
- List<SubscriptionInfo> subInfos = SubscriptionController.getInstance()
- .getActiveSubscriptionInfoList(mApplication.getOpPackageName(),
- mApplication.getAttributionTag());
+ List<SubscriptionInfo> subInfos;
+ if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
+ subInfos = SubscriptionManagerService.getInstance()
+ .getActiveSubscriptionInfoList(mApplication.getOpPackageName(),
+ mApplication.getAttributionTag());
+ } else {
+ subInfos = SubscriptionController.getInstance()
+ .getActiveSubscriptionInfoList(mApplication.getOpPackageName(),
+ mApplication.getAttributionTag());
+ }
// 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
@@ -498,8 +507,8 @@
List<Integer> subIdList = new ArrayList<Integer>(mTelephonyCallback.keySet());
Collections.sort(subIdList, new Comparator<Integer>() {
public int compare(Integer sub1, Integer sub2) {
- int slotId1 = SubscriptionController.getInstance().getSlotIndex(sub1);
- int slotId2 = SubscriptionController.getInstance().getSlotIndex(sub2);
+ int slotId1 = SubscriptionManager.getSlotIndex(sub1);
+ int slotId2 = SubscriptionManager.getSlotIndex(sub2);
return slotId1 > slotId2 ? 0 : -1;
}
});
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index 5d9928e..ede0015 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -52,6 +52,7 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.TelephonyManager;
+import android.telephony.TelephonyRegistryManager;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.LocalLog;
@@ -80,6 +81,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -194,6 +196,14 @@
// requested the dump.
private static final String DUMP_ARG_REQUESTING_PACKAGE = "--requesting-package";
+ // Configs that should always be included when clients calls getConfig[ForSubId] with specified
+ // keys (even configs are not explicitly specified). Those configs have special purpose for the
+ // carrier config APIs to work correctly.
+ private static final String[] CONFIG_SUBSET_METADATA_KEYS = new String[] {
+ CarrierConfigManager.KEY_CARRIER_CONFIG_VERSION_STRING,
+ CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL
+ };
+
// Handler to process various events.
//
// For each phoneId, the event sequence should be:
@@ -799,35 +809,41 @@
}
private void broadcastConfigChangedIntent(int phoneId, boolean addSubIdExtra) {
+ int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ int carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+ int specificCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+
Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
Intent.FLAG_RECEIVER_FOREGROUND);
if (addSubIdExtra) {
- int simApplicationState = TelephonyManager.SIM_STATE_UNKNOWN;
- int[] subIds = SubscriptionManager.getSubId(phoneId);
- if (!ArrayUtils.isEmpty(subIds)) {
- TelephonyManager telMgr = TelephonyManager.from(mContext)
- .createForSubscriptionId(subIds[0]);
- simApplicationState = telMgr.getSimApplicationState();
- }
- logd("Broadcast CARRIER_CONFIG_CHANGED for phone " + phoneId
- + " simApplicationState " + simApplicationState);
+ int simApplicationState = getSimApplicationStateForPhone(phoneId);
// Include subId/carrier id extra only if SIM records are loaded
if (simApplicationState != TelephonyManager.SIM_STATE_UNKNOWN
&& simApplicationState != TelephonyManager.SIM_STATE_NOT_READY) {
+ subId = SubscriptionManager.getSubscriptionId(phoneId);
+ carrierId = getCarrierIdForPhoneId(phoneId);
+ specificCarrierId = getSpecificCarrierIdForPhoneId(phoneId);
+ intent.putExtra(TelephonyManager.EXTRA_SPECIFIC_CARRIER_ID, specificCarrierId);
SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId);
- intent.putExtra(TelephonyManager.EXTRA_SPECIFIC_CARRIER_ID,
- getSpecificCarrierIdForPhoneId(phoneId));
- intent.putExtra(TelephonyManager.EXTRA_CARRIER_ID, getCarrierIdForPhoneId(phoneId));
+ intent.putExtra(TelephonyManager.EXTRA_CARRIER_ID, carrierId);
}
}
intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, phoneId);
intent.putExtra(CarrierConfigManager.EXTRA_REBROADCAST_ON_UNLOCK,
mFromSystemUnlocked[phoneId]);
+
+ TelephonyRegistryManager trm = mContext.getSystemService(TelephonyRegistryManager.class);
+ // Unlike broadcast, we wouldn't notify registrants on carrier config change when device is
+ // unlocked. Only real carrier config change will send the notification to registrants.
+ if (trm != null && !mFromSystemUnlocked[phoneId]) {
+ trm.notifyCarrierConfigChanged(phoneId, subId, carrierId, specificCarrierId);
+ }
+
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
- int[] subIds = SubscriptionManager.getSubId(phoneId);
- if (subIds != null && subIds.length > 0) {
- logd("Broadcast CARRIER_CONFIG_CHANGED for phone " + phoneId + ", subId=" + subIds[0]);
+
+ if (SubscriptionManager.isValidSubscriptionId(subId)) {
+ logd("Broadcast CARRIER_CONFIG_CHANGED for phone " + phoneId + ", subId=" + subId);
} else {
logd("Broadcast CARRIER_CONFIG_CHANGED for phone " + phoneId);
}
@@ -835,6 +851,17 @@
mFromSystemUnlocked[phoneId] = false;
}
+ private int getSimApplicationStateForPhone(int phoneId) {
+ int simApplicationState = TelephonyManager.SIM_STATE_UNKNOWN;
+ int subId = SubscriptionManager.getSubscriptionId(phoneId);
+ if (SubscriptionManager.isValidSubscriptionId(subId)) {
+ TelephonyManager telMgr = TelephonyManager.from(mContext)
+ .createForSubscriptionId(subId);
+ simApplicationState = telMgr.getSimApplicationState();
+ }
+ return simApplicationState;
+ }
+
/** Binds to the default or carrier config app. */
private boolean bindToConfigPackage(@NonNull String pkgName, int phoneId, int eventId) {
logdWithLocalLog("Binding to " + pkgName + " for phone " + phoneId);
@@ -978,7 +1005,7 @@
if (isNoSimConfig) {
fileName = getFilenameForNoSimConfig(packageName);
} else {
- if (SubscriptionManager.getSimStateForSlotIndex(phoneId)
+ if (TelephonyManager.getSimStateForSlotIndex(phoneId)
!= TelephonyManager.SIM_STATE_LOADED) {
loge("Skip save config because SIM records are not loaded.");
return;
@@ -1069,7 +1096,7 @@
if (isNoSimConfig) {
fileName = getFilenameForNoSimConfig(packageName);
} else {
- if (SubscriptionManager.getSimStateForSlotIndex(phoneId)
+ if (TelephonyManager.getSimStateForSlotIndex(phoneId)
!= TelephonyManager.SIM_STATE_LOADED) {
loge("Skip restore config because SIM records are not loaded.");
return null;
@@ -1329,6 +1356,53 @@
}
@Override
+ @NonNull
+ public PersistableBundle getConfigSubsetForSubIdWithFeature(int subscriptionId,
+ @NonNull String callingPackage, @Nullable String callingFeatureId,
+ @NonNull String[] keys) {
+ Objects.requireNonNull(callingPackage, "Calling package must be non-null");
+ Objects.requireNonNull(keys, "Config keys must be non-null");
+ enforceCallerIsSystemOrRequestingPackage(callingPackage);
+
+ // Permission check is performed inside and an empty bundle will return on failure.
+ // No SecurityException thrown here since most clients expect to retrieve the overridden
+ // value if present or use default one if not
+ PersistableBundle allConfigs = getConfigForSubIdWithFeature(subscriptionId, callingPackage,
+ callingFeatureId);
+ if (allConfigs.isEmpty()) {
+ return allConfigs;
+ }
+ for (String key : keys) {
+ Objects.requireNonNull(key, "Config key must be non-null");
+ }
+
+ PersistableBundle configSubset = new PersistableBundle(
+ keys.length + CONFIG_SUBSET_METADATA_KEYS.length);
+ for (String carrierConfigKey : keys) {
+ Object value = allConfigs.get(carrierConfigKey);
+ if (value == null) {
+ // Filter out keys without values.
+ // In history, many AOSP or OEMs/carriers private configs didn't provide default
+ // values. We have to continue supporting them for now. See b/261776046 for details.
+ continue;
+ }
+ // Config value itself could be PersistableBundle which requires different API to put
+ if (value instanceof PersistableBundle) {
+ configSubset.putPersistableBundle(carrierConfigKey, (PersistableBundle) value);
+ } else {
+ configSubset.putObject(carrierConfigKey, value);
+ }
+ }
+
+ // Configs in CONFIG_SUBSET_ALWAYS_INCLUDED_KEYS should always be included
+ for (String generalKey : CONFIG_SUBSET_METADATA_KEYS) {
+ configSubset.putObject(generalKey, allConfigs.get(generalKey));
+ }
+
+ return configSubset;
+ }
+
+ @Override
public void overrideConfig(int subscriptionId, @Nullable PersistableBundle overrides,
boolean persistent) {
mContext.enforceCallingOrSelfPermission(
@@ -1686,12 +1760,12 @@
}
private boolean hasCarrierPrivileges(@NonNull String pkgName, int phoneId) {
- int[] subIds = SubscriptionManager.getSubId(phoneId);
- if (ArrayUtils.isEmpty(subIds)) {
+ int subId = SubscriptionManager.getSubscriptionId(phoneId);
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
return false;
}
- return TelephonyManager.from(mContext).createForSubscriptionId(
- subIds[0]).checkCarrierPrivilegesForPackage(pkgName)
+ return TelephonyManager.from(mContext).createForSubscriptionId(subId)
+ .checkCarrierPrivilegesForPackage(pkgName)
== TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
}
diff --git a/src/com/android/phone/EmergencyDialer.java b/src/com/android/phone/EmergencyDialer.java
index 9b7a43e..5fe8708 100644
--- a/src/com/android/phone/EmergencyDialer.java
+++ b/src/com/android/phone/EmergencyDialer.java
@@ -259,8 +259,6 @@
mEntryType = getIntent().getIntExtra(EXTRA_ENTRY_TYPE, ENTRY_TYPE_UNKNOWN);
Log.d(LOG_TAG, "Launched from " + entryTypeToString(mEntryType));
- // Allow this activity to be displayed in front of the keyguard / lockscreen.
- setShowWhenLocked(true);
// Allow turning screen on
setTurnScreenOn(true);
diff --git a/src/com/android/phone/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java
index d4a0f1e..3f35454 100644
--- a/src/com/android/phone/ImsRcsController.java
+++ b/src/com/android/phone/ImsRcsController.java
@@ -50,6 +50,7 @@
import com.android.ims.ImsManager;
import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.telephony.IIntegerConsumer;
+import com.android.internal.telephony.ISipDialogStateCallback;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.telephony.ims.ImsResolver;
@@ -667,6 +668,60 @@
}
/**
+ * Register a state of Sip Dialog callback
+ */
+ @Override
+ public void registerSipDialogStateCallback(int subId, ISipDialogStateCallback cb) {
+ enforceReadPrivilegedPermission("registerSipDialogStateCallback");
+ if (cb == null) {
+ throw new IllegalArgumentException("SipDialogStateCallback is null");
+ }
+ final long identity = Binder.clearCallingIdentity();
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
+ }
+ try {
+ SipTransportController transport = getRcsFeatureController(subId).getFeature(
+ SipTransportController.class);
+ if (transport == null) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
+ "This transport does not support the registerSipDialogStateCallback"
+ + " of SIP delegates");
+ }
+ transport.addCallbackForSipDialogState(subId, cb);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Unregister a state of Sip Dialog callback
+ */
+ @Override
+ public void unregisterSipDialogStateCallback(int subId, ISipDialogStateCallback cb) {
+ enforceReadPrivilegedPermission("unregisterSipDialogStateCallback");
+ if (cb == null) {
+ throw new IllegalArgumentException("SipDialogStateCallback is null");
+ }
+ final long identity = Binder.clearCallingIdentity();
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ throw new IllegalArgumentException("Invalid Subscription ID: " + subId);
+ }
+ try {
+ SipTransportController transport = getRcsFeatureController(subId).getFeature(
+ SipTransportController.class);
+ if (transport == null) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
+ "This transport does not support the unregisterSipDialogStateCallback"
+ + " of SIP delegates");
+ }
+ transport.removeCallbackForSipDialogState(subId, cb);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
* Registers for updates to the RcsFeature connection through the IImsServiceFeatureCallback
* callback.
*/
diff --git a/src/com/android/phone/ImsUtil.java b/src/com/android/phone/ImsUtil.java
index ba4ad38..d90c256 100644
--- a/src/com/android/phone/ImsUtil.java
+++ b/src/com/android/phone/ImsUtil.java
@@ -154,12 +154,7 @@
}
private static int getSubId(int phoneId) {
- final int[] subIds = SubscriptionManager.getSubId(phoneId);
- int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- if (subIds != null && subIds.length >= 1) {
- subId = subIds[0];
- }
- return subId;
+ return SubscriptionManager.getSubscriptionId(phoneId);
}
private static boolean getLastKnownRoamingState(int phoneId) {
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index abbd816..78a734a 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -17,6 +17,7 @@
package com.android.phone;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.app.Activity;
import android.app.KeyguardManager;
import android.app.ProgressDialog;
@@ -49,6 +50,7 @@
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyLocalConnection;
import android.telephony.TelephonyManager;
+import android.util.ArraySet;
import android.util.LocalLog;
import android.util.Log;
import android.widget.Toast;
@@ -67,6 +69,8 @@
import com.android.internal.telephony.TelephonyComponentFactory;
import com.android.internal.telephony.TelephonyIntents;
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.ims.ImsResolver;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
@@ -75,6 +79,7 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.phone.settings.SettingsConstants;
import com.android.phone.vvm.CarrierVvmPackageInstalledReceiver;
+import com.android.services.telephony.domainselection.TelephonyDomainSelectionService;
import com.android.services.telephony.rcs.TelephonyRcsService;
import java.io.FileDescriptor;
@@ -157,6 +162,7 @@
public ImsStateCallbackController mImsStateCallbackController;
public ImsProvisioningController mImsProvisioningController;
CarrierConfigLoader configLoader;
+ TelephonyDomainSelectionService mDomainSelectionService;
private Phone phoneInEcm;
@@ -188,6 +194,9 @@
@RoamingNotification
private int mPrevRoamingNotification = ROAMING_NOTIFICATION_NO_NOTIFICATION;
+ /** Operator numerics for which we've shown is-roaming notifications. **/
+ private ArraySet<String> mPrevRoamingOperatorNumerics = new ArraySet<>();
+
private WakeState mWakeState = WakeState.SLEEP;
private PowerManager mPowerManager;
@@ -375,7 +384,7 @@
.unregisterTelephonyCallback(callback);
callback = new PhoneAppCallback(subId);
tm.createForSubscriptionId(subId).registerTelephonyCallback(
- TelephonyManager.INCLUDE_LOCATION_DATA_NONE, mHandler::post,
+ TelephonyManager.INCLUDE_LOCATION_DATA_COARSE, mHandler::post,
callback);
mTelephonyCallbacks[phone.getPhoneId()] = callback;
}
@@ -437,9 +446,27 @@
// Inject telephony component factory if configured using other jars.
XmlResourceParser parser = getResources().getXml(R.xml.telephony_injection);
TelephonyComponentFactory.getInstance().injectTheComponentFactory(parser);
+
+ // Create DomainSelectionResolver always, but it MUST be initialized only when
+ // the device supports AOSP domain selection architecture and
+ // has new IRadio that supports its related HAL APIs.
+ DomainSelectionResolver.make(this,
+ getResources().getBoolean(R.bool.config_enable_aosp_domain_selection));
+
// Initialize the telephony framework
PhoneFactory.makeDefaultPhones(this);
+ // Initialize the DomainSelectionResolver after creating the Phone instance
+ // to check the Radio HAL version.
+ if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+ mDomainSelectionService = new TelephonyDomainSelectionService(this);
+ DomainSelectionResolver.getInstance().initialize(mDomainSelectionService);
+ // Initialize EmergencyStateTracker if domain selection is supported
+ boolean isSuplDdsSwitchRequiredForEmergencyCall = getResources()
+ .getBoolean(R.bool.config_gnss_supl_requires_default_data_for_emergency);
+ EmergencyStateTracker.make(this, isSuplDdsSwitchRequiredForEmergencyCall);
+ }
+
// Only bring up ImsResolver if the device supports having an IMS stack.
if (getPackageManager().hasSystemFeature(
PackageManager.FEATURE_TELEPHONY_IMS)) {
@@ -736,6 +763,13 @@
Settings.Global.putInt(getContentResolver(), Settings.Global.ENABLE_CELLULAR_ON_BOOT, 0);
TelephonyProperties.airplane_mode_on(true); // true means int value 1
PhoneUtils.setRadioPower(false);
+ clearCacheOnRadioOff();
+ }
+
+ /** Clear fields on power off radio **/
+ private void clearCacheOnRadioOff() {
+ // Re-show is-roaming notifications after APM mode
+ mPrevRoamingOperatorNumerics.clear();
}
private void setRadioPowerOn() {
@@ -859,26 +893,27 @@
+ mDefaultDataSubId + ", ss roaming=" + serviceState.getDataRoaming());
}
if (subId == mDefaultDataSubId) {
- updateDataRoamingStatus();
+ updateDataRoamingStatus(serviceState.getOperatorNumeric());
}
}
/**
- * @return whether or not we should show a notification when connecting to data roaming if the
- * user has data roaming enabled
- */
- private boolean shouldShowDataConnectedRoaming(int subId) {
- PersistableBundle config = getCarrierConfigForSubId(subId);
- return config.getBoolean(CarrierConfigManager
- .KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL);
- }
-
- /**
* 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.
*/
private void updateDataRoamingStatus() {
+ updateDataRoamingStatus(null /*roamingOperatorNumeric*/);
+ }
+
+ /**
+ * When roaming, if mobile data cannot be established due to data roaming not enabled, we need
+ * to notify the user so they can enable it through settings. Vise versa if the condition
+ * changes, we need to dismiss the notification.
+ * @param roamingOperatorNumeric The operator numeric for the current roaming. {@code null} if
+ * the current roaming operator numeric didn't change.
+ */
+ private void updateDataRoamingStatus(@Nullable String roamingOperatorNumeric) {
if (VDBG) Log.v(LOG_TAG, "updateDataRoamingStatus");
Phone phone = getPhone(mDefaultDataSubId);
if (phone == null) {
@@ -893,10 +928,21 @@
dataAllowed = reasons.isEmpty();
notAllowedDueToRoamingOff = (reasons.size() == 1
&& reasons.contains(DataDisallowedReason.ROAMING_DISABLED));
- mDataRoamingNotifLog.log("dataAllowed=" + dataAllowed + ", reasons=" + reasons);
- if (VDBG) Log.v(LOG_TAG, "dataAllowed=" + dataAllowed + ", reasons=" + reasons);
+ mDataRoamingNotifLog.log("dataAllowed=" + dataAllowed + ", reasons=" + reasons
+ + ", roamingOperatorNumeric=" + roamingOperatorNumeric);
+ if (VDBG) {
+ Log.v(LOG_TAG, "dataAllowed=" + dataAllowed + ", reasons=" + reasons
+ + ", roamingOperatorNumeric=" + roamingOperatorNumeric);
+ }
if (!dataAllowed && notAllowedDueToRoamingOff) {
+ // Don't show roaming notification if we've already shown for this MccMnc
+ if (roamingOperatorNumeric != null
+ && !mPrevRoamingOperatorNumerics.add(roamingOperatorNumeric)) {
+ Log.d(LOG_TAG, "Skip roaming disconnected notification since already shown in "
+ + "MccMnc " + roamingOperatorNumeric);
+ return;
+ }
// No need to show it again if we never cancelled it explicitly.
if (mPrevRoamingNotification == ROAMING_NOTIFICATION_DISCONNECTED) return;
// If the only reason of no data is data roaming disabled, then we notify the user
@@ -907,8 +953,18 @@
Message msg = mHandler.obtainMessage(EVENT_DATA_ROAMING_DISCONNECTED);
msg.arg1 = mDefaultDataSubId;
msg.sendToTarget();
- } else if (dataAllowed && dataIsNowRoaming(mDefaultDataSubId)
- && shouldShowDataConnectedRoaming(mDefaultDataSubId)) {
+ } else if (dataAllowed && dataIsNowRoaming(mDefaultDataSubId)) {
+ boolean isShowRoamingNotificationEnabled = getCarrierConfigForSubId(mDefaultDataSubId)
+ .getBoolean(CarrierConfigManager
+ .KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL);
+ if (!isShowRoamingNotificationEnabled) return;
+ // Don't show roaming notification if we've already shown for this MccMnc
+ if (roamingOperatorNumeric != null
+ && !mPrevRoamingOperatorNumerics.add(roamingOperatorNumeric)) {
+ Log.d(LOG_TAG, "Skip roaming connected notification since already shown in "
+ + "MccMnc " + roamingOperatorNumeric);
+ return;
+ }
// No need to show it again if we never cancelled it explicitly, or carrier config
// indicates this is not needed.
if (mPrevRoamingNotification == ROAMING_NOTIFICATION_CONNECTED) return;
@@ -1052,7 +1108,21 @@
} catch (Exception e) {
e.printStackTrace();
}
+ pw.println("DomainSelectionResolver:");
+ pw.increaseIndent();
+ try {
+ if (DomainSelectionResolver.getInstance() != null) {
+ DomainSelectionResolver.getInstance().dump(fd, pw, args);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
pw.decreaseIndent();
+ if (mDomainSelectionService != null) {
+ mDomainSelectionService.dump(fd, pw, args);
+ }
+ pw.decreaseIndent();
+ 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 c50e99a..c4449d8 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.telephony.TelephonyManager.HAL_SERVICE_NETWORK;
import static android.telephony.TelephonyManager.HAL_SERVICE_RADIO;
import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_CDMA;
@@ -83,6 +84,7 @@
import android.telephony.CallForwardingInfo;
import android.telephony.CarrierConfigManager;
import android.telephony.CarrierRestrictionRules;
+import android.telephony.CellBroadcastIdRange;
import android.telephony.CellIdentity;
import android.telephony.CellIdentityCdma;
import android.telephony.CellIdentityGsm;
@@ -111,6 +113,7 @@
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.TelephonyHistogram;
import android.telephony.TelephonyManager;
+import android.telephony.TelephonyManager.SimState;
import android.telephony.TelephonyScanManager;
import android.telephony.ThermalMitigationRequest;
import android.telephony.UiccCardInfo;
@@ -167,6 +170,7 @@
import com.android.internal.telephony.INumberVerificationCallback;
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.IccLogicalChannelRequest;
import com.android.internal.telephony.LocaleTracker;
import com.android.internal.telephony.NetworkScanRequestTracker;
@@ -181,12 +185,14 @@
import com.android.internal.telephony.RILConstants;
import com.android.internal.telephony.RadioInterfaceCapabilityController;
import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.SmsApplication;
import com.android.internal.telephony.SmsController;
import com.android.internal.telephony.SmsPermissions;
import com.android.internal.telephony.SubscriptionController;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.telephony.data.DataUtils;
+import com.android.internal.telephony.domainselection.DomainSelectionResolver;
import com.android.internal.telephony.emergency.EmergencyNumberTracker;
import com.android.internal.telephony.euicc.EuiccConnector;
import com.android.internal.telephony.ims.ImsResolver;
@@ -194,6 +200,8 @@
import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
import com.android.internal.telephony.metrics.RcsStats;
import com.android.internal.telephony.metrics.TelephonyMetrics;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
import com.android.internal.telephony.uicc.IccIoResult;
import com.android.internal.telephony.uicc.IccUtils;
@@ -205,6 +213,7 @@
import com.android.internal.telephony.uicc.UiccProfile;
import com.android.internal.telephony.uicc.UiccSlot;
import com.android.internal.telephony.util.LocaleUtils;
+import com.android.internal.telephony.util.TelephonyUtils;
import com.android.internal.telephony.util.VoicemailNotificationSettingsUtil;
import com.android.internal.util.FunctionalUtils;
import com.android.internal.util.HexDump;
@@ -371,6 +380,9 @@
private static final int SELECT_P2 = 0;
private static final int SELECT_P3 = 0x10;
+ // Toggling null cipher and integrity support was added in IRadioNetwork 2.1
+ private static final int MIN_NULL_CIPHER_AND_INTEGRITY_VERSION = 201;
+
/** The singleton instance. */
private static PhoneInterfaceManager sInstance;
private static List<String> sThermalMitigationAllowlistedPackages = new ArrayList<>();
@@ -1437,6 +1449,7 @@
request = (MainThreadRequest) ar.userObj;
ResultReceiver result = (ResultReceiver) request.argument;
int error = 0;
+ ModemActivityInfo ret = null;
if (mLastModemActivityInfo == null) {
mLastModemActivitySpecificInfo = new ActivityStatsTechSpecificInfo[1];
mLastModemActivitySpecificInfo[0] =
@@ -1455,12 +1468,14 @@
if (isModemActivityInfoValid(info)) {
mergeModemActivityInfo(info);
}
- mLastModemActivityInfo =
- new ModemActivityInfo(
- mLastModemActivityInfo.getTimestampMillis(),
- mLastModemActivityInfo.getSleepTimeMillis(),
- mLastModemActivityInfo.getIdleTimeMillis(),
- mLastModemActivitySpecificInfo);
+ // This is needed to decouple ret from mLastModemActivityInfo
+ // We don't want to return mLastModemActivityInfo which is updated
+ // inside mergeModemActivityInfo()
+ ret = new ModemActivityInfo(
+ mLastModemActivityInfo.getTimestampMillis(),
+ mLastModemActivityInfo.getSleepTimeMillis(),
+ mLastModemActivityInfo.getIdleTimeMillis(),
+ deepCopyModemActivitySpecificInfo(mLastModemActivitySpecificInfo));
} else {
if (ar.result == null) {
@@ -1478,10 +1493,10 @@
}
}
Bundle bundle = new Bundle();
- if (mLastModemActivityInfo != null) {
+ if (ret != null) {
bundle.putParcelable(
TelephonyManager.MODEM_ACTIVITY_RESULT_KEY,
- mLastModemActivityInfo);
+ ret);
} else {
bundle.putInt(TelephonyManager.EXCEPTION_RESULT_KEY, error);
}
@@ -2409,6 +2424,11 @@
publish();
}
+ @VisibleForTesting
+ public SharedPreferences getSharedPreferences() {
+ return mTelephonySharedPreferences;
+ }
+
private Phone getDefaultPhone() {
Phone thePhone = getPhone(getDefaultSubscription());
return (thePhone != null) ? thePhone : PhoneFactory.getDefaultPhone();
@@ -2445,7 +2465,7 @@
// returns phone associated with the subId.
private Phone getPhone(int subId) {
- return PhoneFactory.getPhone(mSubscriptionController.getPhoneId(subId));
+ return PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId));
}
private void sendEraseModemConfig(@NonNull Phone phone) {
@@ -3006,7 +3026,7 @@
final long identity = Binder.clearCallingIdentity();
try {
- int subId = mSubscriptionController.getDefaultDataSubId();
+ int subId = SubscriptionManager.getDefaultDataSubscriptionId();
final Phone phone = getPhone(subId);
if (phone != null) {
phone.getDataSettingsManager().setDataEnabled(
@@ -3027,7 +3047,7 @@
final long identity = Binder.clearCallingIdentity();
try {
- int subId = mSubscriptionController.getDefaultDataSubId();
+ int subId = SubscriptionManager.getDefaultDataSubscriptionId();
final Phone phone = getPhone(subId);
if (phone != null) {
phone.getDataSettingsManager().setDataEnabled(
@@ -3138,7 +3158,7 @@
@Override
public int getDataState() {
- return getDataStateForSubId(mSubscriptionController.getDefaultDataSubId());
+ return getDataStateForSubId(SubscriptionManager.getDefaultDataSubscriptionId());
}
@Override
@@ -3159,7 +3179,7 @@
@Override
public @DataActivityType int getDataActivity() {
- return getDataActivityForSubId(mSubscriptionController.getDefaultDataSubId());
+ return getDataActivityForSubId(SubscriptionManager.getDefaultDataSubscriptionId());
}
@Override
@@ -3205,7 +3225,7 @@
final long identity = Binder.clearCallingIdentity();
try {
if (DBG_LOC) log("getCellLocation: is active user");
- int subId = mSubscriptionController.getDefaultDataSubId();
+ int subId = SubscriptionManager.getDefaultDataSubscriptionId();
return (CellIdentity) sendRequest(CMD_GET_CELL_LOCATION, workSource, subId);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -3222,7 +3242,7 @@
// Get default phone in this case.
phoneId = SubscriptionManager.DEFAULT_PHONE_INDEX;
}
- final int subId = mSubscriptionController.getSubId(phoneId);
+ final int subId = SubscriptionManager.getSubscriptionId(phoneId);
Phone phone = PhoneFactory.getPhone(phoneId);
if (phone == null) return "";
ServiceStateTracker sst = phone.getServiceStateTracker();
@@ -3452,6 +3472,26 @@
}
@Override
+ public String getPrimaryImei(String callingPackage, String callingFeatureId) {
+ enforceCallingPackage(callingPackage, Binder.getCallingUid(), "getPrimaryImei");
+ if (!checkCallingOrSelfReadDeviceIdentifiersForAnySub(mApp, callingPackage,
+ callingFeatureId, "getPrimaryImei")) {
+ throw new SecurityException("Caller does not have permission");
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ for (Phone phone : PhoneFactory.getPhones()) {
+ if (phone.getImeiType() == Phone.IMEI_TYPE_PRIMARY) {
+ return phone.getImei();
+ }
+ }
+ throw new UnsupportedOperationException("Operation not supported");
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public String getTypeAllocationCodeForSlot(int slotIndex) {
Phone phone = PhoneFactory.getPhone(slotIndex);
String tac = null;
@@ -3625,10 +3665,21 @@
*
* @throws SecurityException if the caller does not have the required permission
*/
- private void enforceModifyPermission() {
+ @VisibleForTesting
+ public void enforceModifyPermission() {
mApp.enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null);
}
+ /**
+ * Make sure the caller has the MODIFY_PHONE_STATE permission.
+ *
+ * @throws SecurityException if the caller does not have the required permission
+ */
+ @VisibleForTesting
+ public void enforceReadPermission() {
+ mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE, null);
+ }
+
private void enforceActiveEmergencySessionPermission() {
mApp.enforceCallingOrSelfPermission(
android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION, null);
@@ -5169,7 +5220,7 @@
*/
@Override
public int getDataNetworkType(String callingPackage, String callingFeatureId) {
- return getDataNetworkTypeForSubscriber(mSubscriptionController.getDefaultDataSubId(),
+ return getDataNetworkTypeForSubscriber(SubscriptionManager.getDefaultDataSubscriptionId(),
callingPackage, callingFeatureId);
}
@@ -5234,7 +5285,7 @@
*/
public boolean hasIccCard() {
// FIXME Make changes to pass defaultSimId of type int
- return hasIccCardUsingSlotIndex(mSubscriptionController.getSlotIndex(
+ return hasIccCardUsingSlotIndex(SubscriptionManager.getSlotIndex(
getDefaultSubscription()));
}
@@ -5299,18 +5350,22 @@
* Returns Default subId, 0 in the case of single standby.
*/
private int getDefaultSubscription() {
- return mSubscriptionController.getDefaultSubId();
+ return SubscriptionManager.getDefaultSubscriptionId();
}
private int getSlotForDefaultSubscription() {
- return mSubscriptionController.getPhoneId(getDefaultSubscription());
+ return SubscriptionManager.getPhoneId(getDefaultSubscription());
}
private int getPreferredVoiceSubscription() {
- return mSubscriptionController.getDefaultVoiceSubId();
+ return SubscriptionManager.getDefaultVoiceSubscriptionId();
}
private boolean isActiveSubscription(int subId) {
+ if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
+ return SubscriptionManagerService.getInstance().isActiveSubId(subId,
+ mApp.getOpPackageName(), mApp.getFeatureId());
+ }
return mSubscriptionController.isActiveSubId(subId);
}
@@ -5976,11 +6031,9 @@
*/
public boolean setBoundImsServiceOverride(int slotIndex, boolean isCarrierService,
int[] featureTypes, String packageName) {
- int[] subIds = SubscriptionManager.getSubId(slotIndex);
TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(), "setBoundImsServiceOverride");
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp,
- (subIds != null ? subIds[0] : SubscriptionManager.INVALID_SUBSCRIPTION_ID),
- "setBoundImsServiceOverride");
+ SubscriptionManager.getSubscriptionId(slotIndex), "setBoundImsServiceOverride");
final long identity = Binder.clearCallingIdentity();
try {
@@ -6010,12 +6063,10 @@
*/
@Override
public boolean clearCarrierImsServiceOverride(int slotIndex) {
- int[] subIds = SubscriptionManager.getSubId(slotIndex);
TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
"clearCarrierImsServiceOverride");
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp,
- (subIds != null ? subIds[0] : SubscriptionManager.INVALID_SUBSCRIPTION_ID),
- "clearCarrierImsServiceOverride");
+ SubscriptionManager.getSubscriptionId(slotIndex), "clearCarrierImsServiceOverride");
final long identity = Binder.clearCallingIdentity();
try {
@@ -6040,11 +6091,9 @@
*/
public String getBoundImsServicePackage(int slotId, boolean isCarrierImsService,
@ImsFeature.FeatureType int featureType) {
- int[] subIds = SubscriptionManager.getSubId(slotId);
TelephonyPermissions
- .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
- mApp, (subIds != null ? subIds[0] : SubscriptionManager.INVALID_SUBSCRIPTION_ID),
- "getBoundImsServicePackage");
+ .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(mApp,
+ SubscriptionManager.getSubscriptionId(slotId), "getBoundImsServicePackage");
final long identity = Binder.clearCallingIdentity();
try {
@@ -6155,11 +6204,11 @@
TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
mApp, subId, "setNetworkSelectionModeManual");
+ final long identity = Binder.clearCallingIdentity();
if (!isActiveSubscription(subId)) {
return false;
}
- final long identity = Binder.clearCallingIdentity();
try {
ManualNetworkSelectionArgument arg = new ManualNetworkSelectionArgument(operatorInfo,
persistSelection);
@@ -6770,7 +6819,7 @@
final long identity = Binder.clearCallingIdentity();
try {
- int phoneId = mSubscriptionController.getPhoneId(subId);
+ int phoneId = SubscriptionManager.getPhoneId(subId);
if (DBG) log("isUserDataEnabled: subId=" + subId + " phoneId=" + phoneId);
Phone phone = PhoneFactory.getPhone(phoneId);
if (phone != null) {
@@ -6817,7 +6866,7 @@
final long identity = Binder.clearCallingIdentity();
try {
- int phoneId = mSubscriptionController.getPhoneId(subId);
+ int phoneId = SubscriptionManager.getPhoneId(subId);
Phone phone = PhoneFactory.getPhone(phoneId);
if (phone != null) {
boolean retVal = phone.getDataSettingsManager().isDataEnabled();
@@ -6864,7 +6913,7 @@
final long identity = Binder.clearCallingIdentity();
try {
- int phoneId = mSubscriptionController.getPhoneId(subId);
+ int phoneId = SubscriptionManager.getPhoneId(subId);
if (DBG) {
log("isDataEnabledForReason: subId=" + subId + " phoneId=" + phoneId
+ " reason=" + reason);
@@ -7276,9 +7325,16 @@
return null;
}
- final SubscriptionInfo info = SubscriptionController.getInstance()
- .getSubscriptionInfo(subId);
- final ParcelUuid groupUuid = info.getGroupUuid();
+ ParcelUuid groupUuid;
+ if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
+ final SubscriptionInfo info = SubscriptionManagerService.getInstance()
+ .getSubscriptionInfo(subId);
+ groupUuid = info.getGroupUuid();
+ } else {
+ final SubscriptionInfo info = mSubscriptionController
+ .getSubscriptionInfo(subId);
+ groupUuid = info.getGroupUuid();
+ }
// If it doesn't belong to any group, return just subscriberId of itself.
if (groupUuid == null) {
return new String[]{subscriberId};
@@ -7286,9 +7342,16 @@
// Get all subscriberIds from the group.
final List<String> mergedSubscriberIds = new ArrayList<>();
- final List<SubscriptionInfo> groupInfos = SubscriptionController.getInstance()
- .getSubscriptionsInGroup(groupUuid, mApp.getOpPackageName(),
- mApp.getAttributionTag());
+ List<SubscriptionInfo> groupInfos;
+ if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
+ groupInfos = SubscriptionManagerService.getInstance()
+ .getSubscriptionsInGroup(groupUuid, mApp.getOpPackageName(),
+ mApp.getAttributionTag());
+ } else {
+ groupInfos = mSubscriptionController
+ .getSubscriptionsInGroup(groupUuid, mApp.getOpPackageName(),
+ mApp.getAttributionTag());
+ }
for (SubscriptionInfo subInfo : groupInfos) {
subscriberId = telephonyManager.getSubscriberId(subInfo.getSubscriptionId());
if (subscriberId != null) {
@@ -7848,11 +7911,23 @@
}
final long identity = Binder.clearCallingIdentity();
try {
- final SubscriptionInfo info = mSubscriptionController.getActiveSubscriptionInfo(subId,
- phone.getContext().getOpPackageName(), phone.getContext().getAttributionTag());
- if (info == null) {
- log("getSimLocaleForSubscriber, inactive subId: " + subId);
- return null;
+ SubscriptionInfo info;
+ if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
+ info = SubscriptionManagerService.getInstance().getActiveSubscriptionInfo(subId,
+ phone.getContext().getOpPackageName(),
+ phone.getContext().getAttributionTag());
+ if (info == null) {
+ log("getSimLocaleForSubscriber, inactive subId: " + subId);
+ return null;
+ }
+ } else {
+ info = mSubscriptionController.getActiveSubscriptionInfo(subId,
+ phone.getContext().getOpPackageName(),
+ phone.getContext().getAttributionTag());
+ if (info == null) {
+ log("getSimLocaleForSubscriber, inactive subId: " + subId);
+ return null;
+ }
}
// Try and fetch the locale from the carrier properties or from the SIM language
// preferences (EF-PL and EF-LI)...
@@ -7900,15 +7975,14 @@
return inputLocale.toLanguageTag();
}
- private List<SubscriptionInfo> getAllSubscriptionInfoList() {
- return mSubscriptionController.getAllSubInfoList(mApp.getOpPackageName(),
- mApp.getAttributionTag());
- }
-
/**
* NOTE: this method assumes permission checks are done and caller identity has been cleared.
*/
private List<SubscriptionInfo> getActiveSubscriptionInfoListPrivileged() {
+ if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
+ return SubscriptionManagerService.getInstance().getActiveSubscriptionInfoList(
+ mApp.getOpPackageName(), mApp.getAttributionTag());
+ }
return mSubscriptionController.getActiveSubscriptionInfoList(mApp.getOpPackageName(),
mApp.getAttributionTag());
}
@@ -7991,7 +8065,7 @@
*/
private void mergeModemActivityInfo(ModemActivityInfo info) {
List<ActivityStatsTechSpecificInfo> merged = new ArrayList<>();
- ActivityStatsTechSpecificInfo mDeltaSpecificInfo;
+ ActivityStatsTechSpecificInfo deltaSpecificInfo;
boolean matched;
for (int i = 0; i < info.getSpecificInfoLength(); i++) {
matched = false;
@@ -8016,13 +8090,13 @@
}
if (!matched) {
- mDeltaSpecificInfo =
+ deltaSpecificInfo =
new ActivityStatsTechSpecificInfo(
rat,
freq,
info.getTransmitTimeMillis(rat, freq),
(int) info.getReceiveTimeMillis(rat, freq));
- merged.addAll(Arrays.asList(mDeltaSpecificInfo));
+ merged.addAll(Arrays.asList(deltaSpecificInfo));
}
}
merged.addAll(Arrays.asList(mLastModemActivitySpecificInfo));
@@ -8037,6 +8111,26 @@
mLastModemActivityInfo.setIdleTimeMillis(
info.getIdleTimeMillis()
+ mLastModemActivityInfo.getIdleTimeMillis());
+
+ mLastModemActivityInfo =
+ new ModemActivityInfo(
+ mLastModemActivityInfo.getTimestampMillis(),
+ mLastModemActivityInfo.getSleepTimeMillis(),
+ mLastModemActivityInfo.getIdleTimeMillis(),
+ mLastModemActivitySpecificInfo);
+ }
+
+ private ActivityStatsTechSpecificInfo[] deepCopyModemActivitySpecificInfo(
+ ActivityStatsTechSpecificInfo[] info) {
+ int infoSize = info.length;
+ ActivityStatsTechSpecificInfo[] ret = new ActivityStatsTechSpecificInfo[infoSize];
+ for (int i = 0; i < infoSize; i++) {
+ ret[i] = new ActivityStatsTechSpecificInfo(
+ info[i].getRat(), info[i].getFrequencyRange(),
+ info[i].getTransmitTimeMillis(),
+ (int) info[i].getReceiveTimeMillis());
+ }
+ return ret;
}
/**
@@ -8100,10 +8194,21 @@
.contains(callingPackage);
try {
// isActiveSubId requires READ_PHONE_STATE, which we already check for above
- if (!mSubscriptionController.isActiveSubId(subId, callingPackage, callingFeatureId)) {
- Rlog.d(LOG_TAG,
- "getServiceStateForSubscriber returning null for inactive subId=" + subId);
- return null;
+ if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
+ SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
+ .getSubscriptionInfoInternal(subId);
+ if (subInfo == null || !subInfo.isActive()) {
+ Rlog.d(LOG_TAG, "getServiceStateForSubscriber returning null for inactive "
+ + "subId=" + subId);
+ return null;
+ }
+ } else {
+ if (!mSubscriptionController.isActiveSubId(subId, callingPackage,
+ callingFeatureId)) {
+ Rlog.d(LOG_TAG, "getServiceStateForSubscriber returning null for inactive "
+ + "subId=" + subId);
+ return null;
+ }
}
ServiceState ss = phone.getServiceState();
@@ -8254,6 +8359,16 @@
}
/**
+ * Make sure either called from same process as self (phone) or IPC caller has interact across
+ * users permission.
+ *
+ * @throws SecurityException if the caller does not have the required permission
+ */
+ private void enforceInteractAcrossUsersPermission(String message) {
+ mApp.enforceCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS, message);
+ }
+
+ /**
* Make sure called from the package in charge of visual voicemail.
*
* @throws SecurityException if the caller is not the visual voicemail package.
@@ -9242,7 +9357,7 @@
*/
private int getDefaultNetworkType(int subId) {
List<Integer> list = TelephonyProperties.default_network();
- int phoneId = mSubscriptionController.getPhoneId(subId);
+ int phoneId = SubscriptionManager.getPhoneId(subId);
if (phoneId >= 0 && phoneId < list.size() && list.get(phoneId) != null) {
return list.get(phoneId);
}
@@ -11188,9 +11303,8 @@
}
for (SignalThresholdInfo info : request.getSignalThresholdInfos()) {
- // Only system caller can set mHysteresisMs/mHysteresisDb/mIsEnabled.
+ // Only system caller can set mHysteresisMs/mIsEnabled.
if (info.getHysteresisMs() != SignalThresholdInfo.HYSTERESIS_MS_DISABLED
- || info.getHysteresisDb() != SignalThresholdInfo.HYSTERESIS_DB_DISABLED
|| info.isEnabled()) {
throw new IllegalArgumentException(
"Only system can set hide fields in SignalThresholdInfo");
@@ -11288,6 +11402,10 @@
}
Phone phone = getPhone(subId);
+ if (phone == null) {
+ loge("isPremiumCapabilityAvailableForPurchase: phone is null, subId=" + subId);
+ return false;
+ }
final long identity = Binder.clearCallingIdentity();
try {
return SlicePurchaseController.getInstance(phone)
@@ -11320,6 +11438,21 @@
}
Phone phone = getPhone(subId);
+ if (phone == null) {
+ try {
+ int result = TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED;
+ callback.accept(result);
+ loge("purchasePremiumCapability: phone is null, subId=" + subId);
+ } catch (RemoteException e) {
+ String logStr = "Purchase premium capability "
+ + TelephonyManager.convertPremiumCapabilityToString(capability)
+ + " failed due to RemoteException handling null phone: " + e;
+ if (DBG) log(logStr);
+ AnomalyReporter.reportAnomaly(
+ UUID.fromString(PURCHASE_PREMIUM_CAPABILITY_ERROR_UUID), logStr);
+ }
+ return;
+ }
String appName;
try {
appName = mApp.getPackageManager().getApplicationLabel(mApp.getPackageManager()
@@ -11536,5 +11669,201 @@
}
}
+ /**
+ * Get the component name of the default app to direct respond-via-message intent for the
+ * user associated with this subscription, update the cache if there is no respond-via-message
+ * application currently configured for this user.
+ * @return component name of the app and class to direct Respond Via Message intent to, or
+ * {@code null} if the functionality is not supported.
+ * @hide
+ */
+ @Override
+ public @Nullable ComponentName getDefaultRespondViaMessageApplication(int subId,
+ boolean updateIfNeeded) {
+ enforceInteractAcrossUsersPermission("getDefaultRespondViaMessageApplication");
-}
+ Context context = getPhone(subId).getContext();
+ UserHandle userHandle = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ userHandle = TelephonyUtils.getSubscriptionUserHandle(context, subId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return SmsApplication.getDefaultRespondViaMessageApplicationAsUser(context,
+ updateIfNeeded, userHandle);
+ }
+
+ /**
+ * Set whether the device is able to connect with null ciphering or integrity
+ * algorithms. This is a global setting and will apply to all active subscriptions
+ * and all new subscriptions after this.
+ *
+ * @param enabled when true, null cipher and integrity algorithms are allowed.
+ * @hide
+ */
+ @Override
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void setNullCipherAndIntegrityEnabled(boolean enabled) {
+ enforceModifyPermission();
+ checkForNullCipherAndIntegritySupport();
+
+ // Persist the state of our preference. Each GsmCdmaPhone instance is responsible
+ // for listening to these preference changes and applying them immediately.
+ SharedPreferences.Editor editor = mTelephonySharedPreferences.edit();
+ editor.putBoolean(Phone.PREF_NULL_CIPHER_AND_INTEGRITY_ENABLED, enabled);
+ editor.apply();
+
+ for (Phone phone: PhoneFactory.getPhones()) {
+ phone.handleNullCipherEnabledChange();
+ }
+ }
+
+
+ /**
+ * Get whether the device is able to connect with null ciphering or integrity
+ * algorithms. Note that this retrieves the phone-global preference and not
+ * the state of the radio.
+ *
+ * @throws SecurityException if {@link permission#MODIFY_PHONE_STATE} is not satisfied
+ * @throws UnsupportedOperationException if the device does not support the minimum HAL
+ * version for this feature.
+ * @hide
+ */
+ @Override
+ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ public boolean isNullCipherAndIntegrityPreferenceEnabled() {
+ enforceReadPermission();
+ checkForNullCipherAndIntegritySupport();
+ return getDefaultPhone().getNullCipherAndIntegrityEnabledPreference();
+ }
+
+ private void checkForNullCipherAndIntegritySupport() {
+ if (getHalVersion(HAL_SERVICE_NETWORK) < MIN_NULL_CIPHER_AND_INTEGRITY_VERSION) {
+ throw new UnsupportedOperationException(
+ "Null cipher and integrity operations require HAL 2.1 or above");
+ }
+ }
+
+ /**
+ * Get the SIM state for the slot index.
+ * For Remote-SIMs, this method returns {@link IccCardConstants.State#UNKNOWN}
+ *
+ * @return SIM state as the ordinal of {@link IccCardConstants.State}
+ */
+ @Override
+ @SimState
+ public int getSimStateForSlotIndex(int slotIndex) {
+ IccCardConstants.State simState;
+ if (slotIndex < 0) {
+ simState = IccCardConstants.State.UNKNOWN;
+ } else {
+ Phone phone = null;
+ try {
+ phone = PhoneFactory.getPhone(slotIndex);
+ } catch (IllegalStateException e) {
+ // ignore
+ }
+ if (phone == null) {
+ simState = IccCardConstants.State.UNKNOWN;
+ } else {
+ IccCard icc = phone.getIccCard();
+ if (icc == null) {
+ simState = IccCardConstants.State.UNKNOWN;
+ } else {
+ simState = icc.getState();
+ }
+ }
+ }
+ return simState.ordinal();
+ }
+
+ /**
+ * Get current cell broadcast ranges.
+ */
+ @Override
+ @RequiresPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS)
+ public List<CellBroadcastIdRange> getCellBroadcastIdRanges(int subId) {
+ mApp.enforceCallingPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS,
+ "getCellBroadcastIdRanges");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return getPhone(subId).getCellBroadcastIdRanges();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Set reception of cell broadcast messages with the list of the given ranges
+ *
+ * @param ranges the list of {@link CellBroadcastIdRange} to be enabled
+ */
+ @Override
+ @RequiresPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS)
+ public void setCellBroadcastIdRanges(int subId, @NonNull List<CellBroadcastIdRange> ranges,
+ @Nullable IIntegerConsumer callback) {
+ mApp.enforceCallingPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS,
+ "setCellBroadcastIdRanges");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Phone phone = getPhoneFromSubId(subId);
+ if (DBG) {
+ log("setCellBroadcastIdRanges for subId :" + subId + ", phone:" + phone);
+ }
+ phone.setCellBroadcastIdRanges(ranges, result -> {
+ if (callback != null) {
+ try {
+ callback.accept(result);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "setCellBroadcastIdRanges: callback not available.");
+ }
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Returns whether the device supports the domain selection service.
+ *
+ * @return {@code true} if the device supports the domain selection service.
+ */
+ @Override
+ public boolean isDomainSelectionSupported() {
+ mApp.enforceCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ "isDomainSelectionSupported");
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return DomainSelectionResolver.getInstance().isDomainSelectionSupported();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * 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:
+ * <ul>
+ * <li>return true : if the calling package has the appop permission {@link
+ * Manifest.permission#USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER} in the manifest </>
+ * <li>return true : if any one subscription has the READ_PRIVILEGED_PHONE_STATE
+ * permission, the calling package passes a DevicePolicyManager Device Owner / Profile
+ * Owner device identifier access check, or the calling package has carrier privileges</>
+ * <li>throw SecurityException: if the caller does not meet any of the requirements.
+ * </ul>
+ */
+ private static boolean checkCallingOrSelfReadDeviceIdentifiersForAnySub(Context context,
+ String callingPackage, @Nullable String callingFeatureId, String message) {
+ for (Phone phone : PhoneFactory.getPhones()) {
+ if (TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(context,
+ phone.getSubId(), callingPackage, callingFeatureId, message)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index 0cf120a..fdaf1bb 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -1099,7 +1099,7 @@
private int handleBarringSendCommand() {
PrintWriter errPw = getErrPrintWriter();
int slotId = getDefaultSlot();
- int subId = SubscriptionManager.getSubId(slotId)[0];
+ int subId = SubscriptionManager.getSubscriptionId(slotId);
@BarringInfo.BarringServiceInfo.BarringType int barringType =
BarringInfo.BarringServiceInfo.BARRING_TYPE_UNCONDITIONAL;
boolean isConditionallyBarred = false;
@@ -1111,7 +1111,7 @@
case "-s": {
try {
slotId = Integer.parseInt(getNextArgRequired());
- subId = SubscriptionManager.getSubId(slotId)[0];
+ subId = SubscriptionManager.getSubscriptionId(slotId);
} catch (NumberFormatException e) {
errPw.println("barring send requires an integer as a SLOT_ID.");
return -1;
@@ -2162,8 +2162,7 @@
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
}
- int[] subIds = SubscriptionManager.getSubId(slotId);
- return subIds[0];
+ return SubscriptionManager.getSubscriptionId(slotId);
}
private int handleGbaSetServiceCommand() {
diff --git a/src/com/android/phone/settings/AccessibilitySettingsFragment.java b/src/com/android/phone/settings/AccessibilitySettingsFragment.java
index 475d878..4c29e65 100644
--- a/src/com/android/phone/settings/AccessibilitySettingsFragment.java
+++ b/src/com/android/phone/settings/AccessibilitySettingsFragment.java
@@ -40,6 +40,7 @@
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.SubscriptionController;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
import com.android.phone.PhoneGlobals;
import com.android.phone.R;
@@ -183,10 +184,16 @@
// Update RTT config with IMS Manager if the always-on carrier config isn't set to true.
CarrierConfigManager configManager = (CarrierConfigManager) mContext.getSystemService(
Context.CARRIER_CONFIG_SERVICE);
- for (int subId : SubscriptionController.getInstance().getActiveSubIdList(true)) {
+ int[] activeSubIds;
+ if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
+ activeSubIds = SubscriptionManagerService.getInstance().getActiveSubIdList(true);
+ } else {
+ activeSubIds = SubscriptionController.getInstance().getActiveSubIdList(true);
+ }
+ for (int subId : activeSubIds) {
if (!configManager.getConfigForSubId(subId).getBoolean(
CarrierConfigManager.KEY_IGNORE_RTT_MODE_SETTING_BOOL, false)) {
- int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
+ int phoneId = SubscriptionManager.getPhoneId(subId);
ImsManager imsManager = ImsManager.getInstance(getContext(), phoneId);
imsManager.setRttEnabled(mButtonRtt.isChecked());
}
@@ -264,6 +271,14 @@
private boolean shouldShowRttSetting() {
// Go through all the subs -- if we want to display the RTT setting for any of them, do
// display it.
+ if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
+ for (int subId : SubscriptionManagerService.getInstance().getActiveSubIdList(true)) {
+ if (PhoneGlobals.getInstance().phoneMgr.isRttSupported(subId)) {
+ return true;
+ }
+ }
+ return false;
+ }
for (int subId : SubscriptionController.getInstance().getActiveSubIdList(true)) {
if (PhoneGlobals.getInstance().phoneMgr.isRttSupported(subId)) {
return true;
diff --git a/src/com/android/phone/settings/RadioInfo.java b/src/com/android/phone/settings/RadioInfo.java
index 29f2d73..574440a 100644
--- a/src/com/android/phone/settings/RadioInfo.java
+++ b/src/com/android/phone/settings/RadioInfo.java
@@ -29,6 +29,7 @@
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.Typeface;
+import android.hardware.radio.modem.ImeiInfo;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -1245,11 +1246,16 @@
Resources r = getResources();
s = mPhone.getDeviceId();
- if (s == null) s = r.getString(R.string.radioInfo_unknown);
+ if (s == null) {
+ s = r.getString(R.string.radioInfo_unknown);
+ } else if (mPhone.getImeiType() == ImeiInfo.ImeiType.PRIMARY) {
+ s = s + " (" + r.getString(R.string.radioInfo_imei_primary) + ")";
+ }
mDeviceId.setText(s);
s = mPhone.getSubscriberId();
if (s == null) s = r.getString(R.string.radioInfo_unknown);
+
mSubscriberId.setText(s);
SubscriptionManager subMgr = getSystemService(SubscriptionManager.class);
@@ -1902,14 +1908,8 @@
return;
}
// getSubId says it takes a slotIndex, but it actually takes a phone index
- int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- int[] subIds = SubscriptionManager.getSubId(phoneIndex);
- if (subIds != null && subIds.length > 0) {
- subId = subIds[0];
- }
mSelectedPhoneIndex = phoneIndex;
-
- updatePhoneIndex(phoneIndex, subId);
+ updatePhoneIndex(phoneIndex, SubscriptionManager.getSubscriptionId(phoneIndex));
}
}
diff --git a/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java b/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java
index 3fa64df..2546023 100644
--- a/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java
+++ b/src/com/android/phone/slice/PremiumNetworkEntitlementApi.java
@@ -43,7 +43,7 @@
public class PremiumNetworkEntitlementApi {
private static final String TAG = "PremiumNwEntitlementApi";
private static final String ENTITLEMENT_STATUS_KEY = "EntitlementStatus";
- private static final String PROVISION_STATUS_KEY = "ProvisionStatus";
+ private static final String PROVISION_STATUS_KEY = "ProvStatus";
private static final String SERVICE_FLOW_URL_KEY = "ServiceFlow_URL";
private static final String PROVISION_TIME_LEFT_KEY = "ProvisionTimeLeft";
private static final String DEFAULT_EAP_AKA_RESPONSE = "Default EAP AKA response";
@@ -145,16 +145,11 @@
if (jsonToken.has(PROVISION_TIME_LEFT_KEY)) {
provisionTimeLeft = jsonToken.getString(PROVISION_TIME_LEFT_KEY);
if (provisionTimeLeft != null) {
- premiumNetworkEntitlementResponse.mEntitlementStatus =
+ premiumNetworkEntitlementResponse.mProvisionTimeLeft =
Integer.parseInt(provisionTimeLeft);
}
}
if (jsonToken.has(SERVICE_FLOW_URL_KEY)) {
- provisionStatus = jsonToken.getString(SERVICE_FLOW_URL_KEY);
- if (provisionStatus != null) {
- premiumNetworkEntitlementResponse.mProvisionStatus =
- Integer.parseInt(provisionStatus);
- }
premiumNetworkEntitlementResponse.mServiceFlowURL =
jsonToken.getString(SERVICE_FLOW_URL_KEY);
}
diff --git a/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java b/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java
index d852a69..4e63e35 100644
--- a/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java
+++ b/src/com/android/phone/slice/PremiumNetworkEntitlementResponse.java
@@ -56,6 +56,7 @@
@PremiumNetworkEntitlementStatus public int mEntitlementStatus;
@PremiumNetworkProvisionStatus public int mProvisionStatus;
+ public int mProvisionTimeLeft;
@NonNull public String mServiceFlowURL;
/**
diff --git a/src/com/android/phone/slice/SlicePurchaseController.java b/src/com/android/phone/slice/SlicePurchaseController.java
index ead6b8c..3864119 100644
--- a/src/com/android/phone/slice/SlicePurchaseController.java
+++ b/src/com/android/phone/slice/SlicePurchaseController.java
@@ -31,6 +31,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.os.AsyncResult;
import android.os.Handler;
@@ -38,6 +39,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
+import android.provider.DeviceConfig;
import android.telephony.AnomalyReporter;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
@@ -57,6 +59,9 @@
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -83,13 +88,13 @@
/** Unknown failure code. */
public static final int FAILURE_CODE_UNKNOWN = 0;
- /** Network boost purchase failed because the carrier URL is unavailable. */
+ /** Performance boost purchase failed because the carrier URL is unavailable. */
public static final int FAILURE_CODE_CARRIER_URL_UNAVAILABLE = 1;
- /** Network boost purchase failed because the server is unreachable. */
+ /** Performance boost purchase failed because the server is unreachable. */
public static final int FAILURE_CODE_SERVER_UNREACHABLE = 2;
- /** Network boost purchase failed because user authentication failed. */
+ /** Performance boost purchase failed because user authentication failed. */
public static final int FAILURE_CODE_AUTHENTICATION_FAILED = 3;
- /** Network boost purchase failed because the payment failed. */
+ /** Performance boost purchase failed because the payment failed. */
public static final int FAILURE_CODE_PAYMENT_FAILED = 4;
/**
@@ -115,8 +120,8 @@
private static final int EVENT_PURCHASE_UNTHROTTLED = 1;
/** Slicing config changed. */
private static final int EVENT_SLICING_CONFIG_CHANGED = 2;
- /** Display booster notification. */
- private static final int EVENT_DISPLAY_BOOSTER_NOTIFICATION = 3;
+ /** Start slice purchase application. */
+ private static final int EVENT_START_SLICE_PURCHASE_APP = 3;
/**
* Premium capability was not purchased within the timeout specified by
* {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG}.
@@ -127,6 +132,8 @@
* {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG}.
*/
private static final int EVENT_SETUP_TIMEOUT = 5;
+ /** Device config changed. */
+ private static final int EVENT_DEVICE_CONFIG_CHANGED = 6;
/** UUID to report an anomaly when a premium capability is throttled twice in a row. */
private static final String UUID_CAPABILITY_THROTTLED_TWICE =
@@ -144,14 +151,15 @@
private static final String UUID_NETWORK_SETUP_FAILED = "12eeffbf-08f8-40ed-9a00-d344199552fc";
/**
- * Action to start the slice purchase application and display the network boost notification.
+ * Action to start the slice purchase application and display the
+ * performance boost notification.
*/
public static final String ACTION_START_SLICE_PURCHASE_APP =
"com.android.phone.slice.action.START_SLICE_PURCHASE_APP";
/** Action indicating the premium capability purchase was not completed in time. */
public static final String ACTION_SLICE_PURCHASE_APP_RESPONSE_TIMEOUT =
"com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_TIMEOUT";
- /** Action indicating the network boost notification or WebView was canceled. */
+ /** Action indicating the performance boost notification or WebView was canceled. */
private static final String ACTION_SLICE_PURCHASE_APP_RESPONSE_CANCELED =
"com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_CANCELED";
/** Action indicating a carrier error prevented premium capability purchase. */
@@ -170,6 +178,11 @@
/** 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";
+ /**
+ * Action indicating the slice purchase application showed the performance boost notification.
+ */
+ private static final String ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN =
+ "com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN";
/** Extra for the phone index to send to the slice purchase application. */
public static final String EXTRA_PHONE_ID = "com.android.phone.slice.extra.PHONE_ID";
@@ -180,6 +193,8 @@
*/
public static final String EXTRA_PREMIUM_CAPABILITY =
"com.android.phone.slice.extra.PREMIUM_CAPABILITY";
+ /** Extra for the carrier URL to display to the user to allow premium capability purchase. */
+ public static final String EXTRA_PURCHASE_URL = "com.android.phone.slice.extra.PURCHASE_URL";
/** Extra for the duration of the purchased premium capability. */
public static final String EXTRA_PURCHASE_DURATION =
"com.android.phone.slice.extra.PURCHASE_DURATION";
@@ -196,7 +211,7 @@
"com.android.phone.slice.extra.REQUESTING_APP_NAME";
/**
* Extra for the canceled PendingIntent that the slice purchase application can send as a
- * response if the network boost notification or WebView was canceled by the user.
+ * response if the performance boost notification or WebView was canceled by the user.
* Sends {@link #ACTION_SLICE_PURCHASE_APP_RESPONSE_CANCELED}.
*/
public static final String EXTRA_INTENT_CANCELED =
@@ -235,12 +250,37 @@
*/
public static final String EXTRA_INTENT_SUCCESS =
"com.android.phone.slice.extra.INTENT_SUCCESS";
+ /**
+ * Extra for the PendingIntent that the slice purchase application can send to indicate
+ * that it displayed the performance boost notification to the user.
+ * Sends {@link #ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN}.
+ */
+ public static final String EXTRA_INTENT_NOTIFICATION_SHOWN =
+ "com.android.phone.slice.extra.NOTIFICATION_SHOWN";
- /** Component name to send an explicit broadcast to SlicePurchaseBroadcastReceiver. */
+ /** Component name for the SlicePurchaseBroadcastReceiver. */
private static final ComponentName SLICE_PURCHASE_APP_COMPONENT_NAME =
ComponentName.unflattenFromString(
"com.android.carrierdefaultapp/.SlicePurchaseBroadcastReceiver");
+ /** Shared preference name for performance boost notification preferences. */
+ private static final String PERFORMANCE_BOOST_NOTIFICATION_PREFERENCES =
+ "performance_boost_notification_preferences";
+ /** Shared preference key for daily count of performance boost notifications. */
+ private static final String KEY_DAILY_NOTIFICATION_COUNT = "daily_notification_count";
+ /** Shared preference key for monthly count of performance boost notifications. */
+ private static final String KEY_MONTHLY_NOTIFICATION_COUNT = "monthly_notification_count";
+ /** DeviceConfig key for whether the slicing upsell feature is enabled. */
+ private static final String KEY_ENABLE_SLICING_UPSELL = "enable_slicing_upsell";
+ /**
+ * Shared preference key for the date the daily or monthly counts of performance boost
+ * notifications were last reset.
+ * A String with ISO-8601 format {@code YYYY-MM-DD}, from {@link LocalDate#toString}.
+ * For example, if the count was last updated on December 25, 2020, this would be `2020-12-25`.
+ */
+ private static final String KEY_NOTIFICATION_COUNT_LAST_RESET_DATE =
+ "notification_count_last_reset_date";
+
/** Map of phone ID -> SlicePurchaseController instances. */
@NonNull private static final Map<Integer, SlicePurchaseController> sInstances =
new HashMap<>();
@@ -261,21 +301,39 @@
mSlicePurchaseControllerBroadcastReceivers = new HashMap<>();
/** The current network slicing configuration. */
@Nullable private NetworkSlicingConfig mSlicingConfig;
- /** Premium network entitlement query API */
+ /** Premium network entitlement query API. */
@NonNull private final PremiumNetworkEntitlementApi mPremiumNetworkEntitlementApi;
+ /** LocalDate to use when resetting notification counts. {@code null} except when testing. */
+ @Nullable private LocalDate mLocalDate;
+ /** The number of times the performance boost notification has been shown today. */
+ private int mDailyCount;
+ /** The number of times the performance boost notification has been shown this month. */
+ private int mMonthlyCount;
+ /** {@code true} if the slicing upsell feature is enabled and {@code false} otherwise. */
+ private boolean mIsSlicingUpsellEnabled;
/**
* BroadcastReceiver to receive responses from the slice purchase application.
*/
- @VisibleForTesting
- public class SlicePurchaseControllerBroadcastReceiver extends BroadcastReceiver {
+ private class SlicePurchaseControllerBroadcastReceiver extends BroadcastReceiver {
@TelephonyManager.PremiumCapability private final int mCapability;
+ /**
+ * Create a SlicePurchaseControllerBroadcastReceiver for the given capability
+ *
+ * @param capability The requested capability to listen to response for.
+ */
SlicePurchaseControllerBroadcastReceiver(
@TelephonyManager.PremiumCapability int capability) {
mCapability = capability;
}
+ /**
+ * Process responses from the slice purchase application.
+ *
+ * @param context The Context in which the receiver is running.
+ * @param intent The Intent being received.
+ */
@Override
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
String action = intent.getAction();
@@ -340,6 +398,10 @@
capability, duration);
break;
}
+ case ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN: {
+ SlicePurchaseController.getInstance(phoneId).onNotificationShown();
+ break;
+ }
default:
reportAnomaly(UUID_UNKNOWN_ACTION, "SlicePurchaseControllerBroadcastReceiver("
+ TelephonyManager.convertPremiumCapabilityToString(mCapability)
@@ -381,6 +443,7 @@
/**
* Create a SlicePurchaseController for the given phone on the given looper.
+ *
* @param phone The Phone to create the SlicePurchaseController for.
* @param looper The Looper to run the SlicePurchaseController on.
*/
@@ -390,8 +453,29 @@
mPhone = phone;
// TODO: Create a cached value for slicing config in DataIndication and initialize here
mPhone.mCi.registerForSlicingConfigChanged(this, EVENT_SLICING_CONFIG_CHANGED, null);
- mPremiumNetworkEntitlementApi = new PremiumNetworkEntitlementApi(mPhone,
- getCarrierConfigs());
+ mPremiumNetworkEntitlementApi =
+ new PremiumNetworkEntitlementApi(mPhone, getCarrierConfigs());
+ mIsSlicingUpsellEnabled = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_TELEPHONY, KEY_ENABLE_SLICING_UPSELL, false);
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_TELEPHONY, this::post,
+ properties -> {
+ if (TextUtils.equals(DeviceConfig.NAMESPACE_TELEPHONY,
+ properties.getNamespace())) {
+ sendEmptyMessage(EVENT_DEVICE_CONFIG_CHANGED);
+ }
+ });
+ updateNotificationCounts();
+ }
+
+ /**
+ * Set the LocalDate to use for resetting daily and monthly notification counts.
+ *
+ * @param localDate The LocalDate instance to use.
+ */
+ @VisibleForTesting
+ public void setLocalDate(@NonNull LocalDate localDate) {
+ mLocalDate = localDate;
}
@Override
@@ -412,12 +496,12 @@
onSlicingConfigChanged();
break;
}
- case EVENT_DISPLAY_BOOSTER_NOTIFICATION: {
+ case EVENT_START_SLICE_PURCHASE_APP: {
int capability = msg.arg1;
String appName = (String) msg.obj;
- logd("EVENT_DISPLAY_BOOSTER_NOTIFICATION: " + appName + " requests capability "
+ logd("EVENT_START_SLICE_PURCHASE_APP: " + appName + " requests capability "
+ TelephonyManager.convertPremiumCapabilityToString(capability));
- onDisplayBoosterNotification(capability, appName);
+ onStartSlicePurchaseApplication(capability, appName);
break;
}
case EVENT_PURCHASE_TIMEOUT: {
@@ -433,6 +517,15 @@
+ TelephonyManager.convertPremiumCapabilityToString(capability));
onSetupTimeout(capability);
break;
+ case EVENT_DEVICE_CONFIG_CHANGED:
+ boolean isSlicingUpsellEnabled = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_TELEPHONY, KEY_ENABLE_SLICING_UPSELL, false);
+ if (isSlicingUpsellEnabled != mIsSlicingUpsellEnabled) {
+ logd("EVENT_DEVICE_CONFIG_CHANGED: from " + mIsSlicingUpsellEnabled + " to "
+ + isSlicingUpsellEnabled);
+ mIsSlicingUpsellEnabled = isSlicingUpsellEnabled;
+ }
+ break;
default:
loge("Unknown event: " + msg.obj);
}
@@ -531,10 +624,10 @@
return;
}
- // All state checks passed. Mark purchase pending and display the booster notification to
- // prompt user purchase. Process through the handler since this method is synchronized.
+ // All state checks passed. Mark purchase pending and start the slice purchase application.
+ // Process through the handler since this method is synchronized.
mPendingPurchaseCapabilities.put(capability, onComplete);
- sendMessage(obtainMessage(EVENT_DISPLAY_BOOSTER_NOTIFICATION, capability, 0 /* unused */,
+ sendMessage(obtainMessage(EVENT_START_SLICE_PURCHASE_APP, capability, 0 /* unused */,
appName));
}
@@ -594,7 +687,7 @@
}
}
- private void onDisplayBoosterNotification(@TelephonyManager.PremiumCapability int capability,
+ private void onStartSlicePurchaseApplication(@TelephonyManager.PremiumCapability int capability,
@NonNull String appName) {
PremiumNetworkEntitlementResponse premiumNetworkEntitlementResponse =
mPremiumNetworkEntitlementApi.checkEntitlementStatus(capability);
@@ -627,6 +720,24 @@
return;
}
+ String purchaseUrl = getPurchaseUrl(premiumNetworkEntitlementResponse);
+ if (TextUtils.isEmpty(purchaseUrl)) {
+ handlePurchaseResult(capability,
+ PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_DISABLED, false);
+ 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);
@@ -641,6 +752,7 @@
intent.putExtra(EXTRA_PHONE_ID, mPhone.getPhoneId());
intent.putExtra(EXTRA_SUB_ID, mPhone.getSubId());
intent.putExtra(EXTRA_PREMIUM_CAPABILITY, capability);
+ intent.putExtra(EXTRA_PURCHASE_URL, purchaseUrl);
intent.putExtra(EXTRA_REQUESTING_APP_NAME, appName);
intent.putExtra(EXTRA_INTENT_CANCELED, createPendingIntent(
ACTION_SLICE_PURCHASE_APP_RESPONSE_CANCELED, capability, false));
@@ -653,6 +765,8 @@
false));
intent.putExtra(EXTRA_INTENT_SUCCESS, createPendingIntent(
ACTION_SLICE_PURCHASE_APP_RESPONSE_SUCCESS, capability, true));
+ intent.putExtra(EXTRA_INTENT_NOTIFICATION_SHOWN, createPendingIntent(
+ ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN, capability, false));
logd("Broadcasting start intent to SlicePurchaseBroadcastReceiver.");
mPhone.getContext().sendBroadcast(intent);
@@ -665,11 +779,32 @@
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_SUCCESS);
+ filter.addAction(ACTION_SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN);
mPhone.getContext().registerReceiver(
mSlicePurchaseControllerBroadcastReceivers.get(capability), filter);
}
/**
+ * Get a valid purchase URL from either entitlement response or carrier configs, if one exists.
+ *
+ * @param entitlementResponse The entitlement response to get the purchase URL from.
+ * @return A valid purchase URL or an empty string if one doesn't exist.
+ */
+ @VisibleForTesting
+ @NonNull public String getPurchaseUrl(
+ @NonNull PremiumNetworkEntitlementResponse entitlementResponse) {
+ String purchaseUrl = entitlementResponse.mServiceFlowURL;
+ if (!isUrlValid(purchaseUrl)) {
+ purchaseUrl = getCarrierConfigs().getString(
+ CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
+ if (!isUrlValid(purchaseUrl)) {
+ purchaseUrl = "";
+ }
+ }
+ return purchaseUrl;
+ }
+
+ /**
* Create the PendingIntent to allow the slice purchase application to send back responses.
*
* @param action The action that will be sent for this PendingIntent
@@ -742,6 +877,73 @@
}
}
+ private void onNotificationShown() {
+ SharedPreferences sp = mPhone.getContext().getSharedPreferences(
+ PERFORMANCE_BOOST_NOTIFICATION_PREFERENCES, 0);
+ mDailyCount = sp.getInt((KEY_DAILY_NOTIFICATION_COUNT + mPhone.getPhoneId()), 0) + 1;
+ mMonthlyCount = sp.getInt((KEY_MONTHLY_NOTIFICATION_COUNT + mPhone.getPhoneId()), 0) + 1;
+ logd("Performance boost notification was shown " + mDailyCount + " times today and "
+ + mMonthlyCount + " times this month.");
+
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putInt((KEY_DAILY_NOTIFICATION_COUNT + mPhone.getPhoneId()), mDailyCount);
+ editor.putInt((KEY_MONTHLY_NOTIFICATION_COUNT + mPhone.getPhoneId()), mMonthlyCount);
+ editor.apply();
+
+ // Don't call updateNotificationCounts here because it will be called whenever a new
+ // purchase request comes in or when SlicePurchaseController is initialized.
+ }
+
+ /**
+ * Update the current daily and monthly performance boost notification counts.
+ * If it has been at least a day since the last daily reset or at least a month since the last
+ * monthly reset, reset the current daily or monthly notification counts.
+ */
+ @VisibleForTesting
+ public void updateNotificationCounts() {
+ SharedPreferences sp = mPhone.getContext().getSharedPreferences(
+ PERFORMANCE_BOOST_NOTIFICATION_PREFERENCES, 0);
+ mDailyCount = sp.getInt((KEY_DAILY_NOTIFICATION_COUNT + mPhone.getPhoneId()), 0);
+ mMonthlyCount = sp.getInt((KEY_MONTHLY_NOTIFICATION_COUNT + mPhone.getPhoneId()), 0);
+
+ if (mLocalDate == null) {
+ // Standardize to UTC to prevent default time zone dependency
+ mLocalDate = LocalDate.now(ZoneId.of("UTC"));
+ }
+ LocalDate lastLocalDate = LocalDate.of(1, 1, 1);
+ String lastLocalDateString = sp.getString(
+ (KEY_NOTIFICATION_COUNT_LAST_RESET_DATE + mPhone.getPhoneId()), "");
+ if (!TextUtils.isEmpty(lastLocalDateString)) {
+ try {
+ lastLocalDate = LocalDate.parse(lastLocalDateString);
+ } catch (DateTimeParseException e) {
+ loge("Error parsing LocalDate from SharedPreferences: " + e);
+ }
+ }
+ logd("updateNotificationCounts: mDailyCount=" + mDailyCount + ", mMonthlyCount="
+ + mMonthlyCount + ", mLocalDate=" + mLocalDate + ", lastLocalDate="
+ + lastLocalDate);
+
+ boolean resetMonthly = lastLocalDate.getYear() != mLocalDate.getYear()
+ || lastLocalDate.getMonthValue() != mLocalDate.getMonthValue();
+ boolean resetDaily = resetMonthly
+ || lastLocalDate.getDayOfMonth() != mLocalDate.getDayOfMonth();
+ if (resetDaily) {
+ logd("Resetting daily" + (resetMonthly ? " and monthly" : "") + " notification count.");
+ SharedPreferences.Editor editor = sp.edit();
+ if (resetMonthly) {
+ mMonthlyCount = 0;
+ editor.putInt((KEY_MONTHLY_NOTIFICATION_COUNT + mPhone.getPhoneId()),
+ mMonthlyCount);
+ }
+ mDailyCount = 0;
+ editor.putInt((KEY_DAILY_NOTIFICATION_COUNT + mPhone.getPhoneId()), mDailyCount);
+ editor.putString((KEY_NOTIFICATION_COUNT_LAST_RESET_DATE + mPhone.getPhoneId()),
+ mLocalDate.toString());
+ editor.apply();
+ }
+ }
+
@Nullable private PersistableBundle getCarrierConfigs() {
return mPhone.getContext().getSystemService(CarrierConfigManager.class)
.getConfigForSubId(mPhone.getSubId());
@@ -763,11 +965,6 @@
private boolean isPremiumCapabilitySupportedByCarrier(
@TelephonyManager.PremiumCapability int capability) {
- String url = getCarrierConfigs().getString(
- CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
- if (!isUrlValid(url)) {
- return false;
- }
int[] supportedCapabilities = getCarrierConfigs().getIntArray(
CarrierConfigManager.KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY);
if (supportedCapabilities == null) {
@@ -797,10 +994,11 @@
}
private boolean arePremiumCapabilitiesSupportedByDevice() {
- // TODO: Add more checks?
- // Maybe device resource overlay to enable/disable in addition to carrier configs
- return (mPhone.getCachedAllowedNetworkTypesBitmask()
- & TelephonyManager.NETWORK_TYPE_BITMASK_NR) != 0;
+ if ((mPhone.getCachedAllowedNetworkTypesBitmask()
+ & TelephonyManager.NETWORK_TYPE_BITMASK_NR) == 0) {
+ return false;
+ }
+ return mIsSlicingUpsellEnabled;
}
private boolean isDefaultDataSub() {
@@ -813,7 +1011,6 @@
}
int capabilityServiceType = getSliceServiceType(capability);
for (NetworkSliceInfo sliceInfo : mSlicingConfig.getSliceInfo()) {
- // TODO: check if TrafficDescriptor has realtime capability slice
if (sliceInfo.getSliceServiceType() == capabilityServiceType
&& sliceInfo.getStatus() == NetworkSliceInfo.SLICE_STATUS_ALLOWED) {
return true;
@@ -824,7 +1021,9 @@
@NetworkSliceInfo.SliceServiceType private int getSliceServiceType(
@TelephonyManager.PremiumCapability int capability) {
- // TODO: Implement properly -- potentially need to add new slice service types?
+ if (capability == TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY) {
+ return NetworkSliceInfo.SLICE_SERVICE_TYPE_URLLC;
+ }
return NetworkSliceInfo.SLICE_SERVICE_TYPE_NONE;
}
diff --git a/src/com/android/services/telephony/PstnIncomingCallNotifier.java b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
index f78a9b9..d58c211 100644
--- a/src/com/android/services/telephony/PstnIncomingCallNotifier.java
+++ b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
@@ -393,8 +393,9 @@
*/
private PhoneAccountHandle findCorrectPhoneAccountHandle() {
TelecomAccountRegistry telecomAccountRegistry = TelecomAccountRegistry.getInstance(null);
- // Check to see if a the SIM PhoneAccountHandle Exists for the Call.
- PhoneAccountHandle handle = PhoneUtils.makePstnPhoneAccountHandle(mPhone);
+ // Check to see if a SIM PhoneAccountHandle Exists for the Call.
+ PhoneAccountHandle handle = telecomAccountRegistry.getPhoneAccountHandleForSubId(
+ mPhone.getSubId());
if (telecomAccountRegistry.hasAccountEntryForPhoneAccount(handle)) {
return handle;
}
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 10f1f86..d92b136 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -64,6 +64,7 @@
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.SubscriptionController;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
import com.android.phone.PhoneGlobals;
import com.android.phone.PhoneUtils;
import com.android.phone.R;
@@ -547,18 +548,37 @@
return false;
}
- SubscriptionController controller = SubscriptionController.getInstance();
- if (controller == null) {
- Log.d(this, "isEmergencyPreferredAccount: SubscriptionController not available.");
- return false;
- }
- // Only set an emergency preference on devices with multiple active subscriptions
- // (include opportunistic subscriptions) in this check.
- // API says never null, but this can return null in testing.
- int[] activeSubIds = controller.getActiveSubIdList(false);
- if (activeSubIds == null || activeSubIds.length <= 1) {
- Log.d(this, "isEmergencyPreferredAccount: one or less active subscriptions.");
- return false;
+ if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
+ if (SubscriptionManagerService.getInstance() == null) {
+ Log.d(this,
+ "isEmergencyPreferredAccount: SubscriptionManagerService not "
+ + "available.");
+ return false;
+ }
+ // Only set an emergency preference on devices with multiple active subscriptions
+ // (include opportunistic subscriptions) in this check.
+ // API says never null, but this can return null in testing.
+ int[] activeSubIds = SubscriptionManagerService.getInstance()
+ .getActiveSubIdList(false);
+ if (activeSubIds == null || activeSubIds.length <= 1) {
+ Log.d(this, "isEmergencyPreferredAccount: one or less active subscriptions.");
+ return false;
+ }
+ } else {
+ SubscriptionController controller = SubscriptionController.getInstance();
+ if (controller == null) {
+ Log.d(this,
+ "isEmergencyPreferredAccount: SubscriptionController not available.");
+ return false;
+ }
+ // Only set an emergency preference on devices with multiple active subscriptions
+ // (include opportunistic subscriptions) in this check.
+ // API says never null, but this can return null in testing.
+ int[] activeSubIds = controller.getActiveSubIdList(false);
+ if (activeSubIds == null || activeSubIds.length <= 1) {
+ Log.d(this, "isEmergencyPreferredAccount: one or less active subscriptions.");
+ return false;
+ }
}
// Check to see if this PhoneAccount is associated with the default Data subscription.
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
@@ -566,10 +586,21 @@
+ "valid.");
return false;
}
- int userDefaultData = controller.getDefaultDataSubId();
+ int userDefaultData = SubscriptionManager.getDefaultDataSubscriptionId();
boolean isActiveDataValid = SubscriptionManager.isValidSubscriptionId(activeDataSubId);
- boolean isActiveDataOpportunistic = isActiveDataValid
- && controller.isOpportunistic(activeDataSubId);
+
+ boolean isActiveDataOpportunistic;
+ if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
+ SubscriptionInfo subInfo;
+ subInfo = SubscriptionManagerService.getInstance()
+ .getSubscriptionInfo(activeDataSubId);
+ isActiveDataOpportunistic = isActiveDataValid && subInfo != null
+ && subInfo.isOpportunistic();
+ } else {
+ isActiveDataOpportunistic = isActiveDataValid
+ && SubscriptionController.getInstance().isOpportunistic(activeDataSubId);
+ }
+
// compare the activeDataSubId to the subId specified only if it is valid and not an
// opportunistic subscription (only supports data). If not, use the current default
// defined by the user.
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 31c589e..1cf35a5 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -48,11 +48,13 @@
import android.telephony.ServiceState.RilRadioTechnology;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsCallProfile;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ImsStreamMediaProfile;
import android.telephony.ims.RtpHeaderExtension;
import android.telephony.ims.RtpHeaderExtensionType;
+import android.telephony.ims.feature.MmTelFeature;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Pair;
@@ -809,6 +811,13 @@
Log.i(this, "onReceivedDtmfDigit: digit=%c", digit);
mDtmfTransport.onDtmfReceived(digit);
}
+
+ @Override
+ public void onAudioModeIsVoipChanged(int imsAudioHandler) {
+ boolean isVoip = imsAudioHandler == MmTelFeature.AUDIO_HANDLER_ANDROID;
+ Log.i(this, "onAudioModeIsVoipChanged isVoip =" + isVoip);
+ setAudioModeIsVoip(isVoip);
+ }
};
private TelephonyConnectionService mTelephonyConnectionService;
@@ -822,6 +831,7 @@
private RttTextStream mRttTextStream = null;
private boolean mWasImsConnection;
+ private boolean mWasCrossSim;
/**
* Tracks the multiparty state of the ImsCall so that changes in the bit state can be detected.
@@ -933,6 +943,8 @@
private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap(
new ConcurrentHashMap<TelephonyConnectionListener, Boolean>(8, 0.9f, 1));
+ private Integer mEmergencyServiceCategory = null;
+
protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection,
String callId, @android.telecom.Call.Details.CallDirection int callDirection) {
setCallDirection(callDirection);
@@ -2166,7 +2178,7 @@
mPhoneForEvents = null;
}
- @VisibleForTesting
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
public void hangup(int telephonyDisconnectCode) {
if (mOriginalConnection != null) {
mHangupDisconnectCause = telephonyDisconnectCode;
@@ -2192,6 +2204,7 @@
Log.e(this, e, "Call to Connection.hangup failed with exception");
}
} else {
+ mTelephonyConnectionService.onLocalHangup(this);
if (getState() == STATE_DISCONNECTED) {
Log.i(this, "hangup called on an already disconnected call!");
close();
@@ -2369,6 +2382,16 @@
ImsCallProfile.EXTRA_CONFERENCE_AVAIL)) {
updateConnectionCapabilities();
}
+ // If extras contain or contained Cross Sim information,
+ // then ensure connection properties are updated and propagated to Telecom.
+ // Also, update the status hints in the case the call has
+ // has moved from cross sim call back to wifi
+ mWasCrossSim |= mOriginalConnectionExtras.containsKey(
+ ImsCallProfile.EXTRA_IS_CROSS_SIM_CALL);
+ if (mWasCrossSim) {
+ updateStatusHints();
+ updateConnectionProperties();
+ }
} else {
Log.d(this, "Extras update not required");
}
@@ -2452,6 +2475,29 @@
setTelephonyConnectionRinging();
break;
case DISCONNECTED:
+ if (mTelephonyConnectionService != null) {
+ ImsReasonInfo reasonInfo = null;
+ if (isImsConnection()) {
+ ImsPhoneConnection imsPhoneConnection =
+ (ImsPhoneConnection) mOriginalConnection;
+ reasonInfo = imsPhoneConnection.getImsReasonInfo();
+ if (reasonInfo != null && reasonInfo.getCode()
+ == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL) {
+ EmergencyNumber emergencyNumber =
+ imsPhoneConnection.getEmergencyNumberInfo();
+ if (emergencyNumber != null) {
+ mEmergencyServiceCategory =
+ emergencyNumber.getEmergencyServiceCategoryBitmask();
+ }
+ }
+ }
+
+ if (mTelephonyConnectionService.maybeReselectDomain(this,
+ mOriginalConnection.getPreciseDisconnectCause(), reasonInfo)) {
+ break;
+ }
+ }
+
if (shouldTreatAsEmergencyCall()
&& (cause
== android.telephony.DisconnectCause.EMERGENCY_TEMP_FAILURE
@@ -3839,4 +3885,21 @@
public List<TelephonyConnectionListener> getTelephonyConnectionListeners() {
return new ArrayList<>(mTelephonyListeners);
}
+
+ /**
+ * @return An {@link Integer} instance of the emergency service category.
+ */
+ public @Nullable Integer getEmergencyServiceCategory() {
+ return mEmergencyServiceCategory;
+ }
+
+ /**
+ * Sets the emergency service category.
+ *
+ * @param eccCategory The emergency service category.
+ */
+ @VisibleForTesting
+ public void setEmergencyServiceCategory(int eccCategory) {
+ mEmergencyServiceCategory = eccCategory;
+ }
}
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index ef77483..a5be6c3 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -16,6 +16,7 @@
package com.android.services.telephony;
+import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
import static android.telephony.TelephonyManager.HAL_SERVICE_VOICE;
import android.annotation.NonNull;
@@ -40,17 +41,24 @@
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
+import android.telephony.Annotation.DisconnectCauses;
import android.telephony.CarrierConfigManager;
+import android.telephony.DomainSelectionService;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.EmergencyRegResult;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneNumberUtils;
import android.telephony.RadioAccessFamily;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.ImsReasonInfo;
import android.text.TextUtils;
import android.util.Pair;
import android.view.WindowManager;
+import com.android.ims.ImsManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
@@ -64,9 +72,17 @@
import com.android.internal.telephony.SubscriptionController;
import com.android.internal.telephony.d2d.Communicator;
import com.android.internal.telephony.data.PhoneSwitcher;
+import com.android.internal.telephony.domainselection.DomainSelectionConnection;
+import com.android.internal.telephony.domainselection.DomainSelectionResolver;
+import com.android.internal.telephony.domainselection.EmergencyCallDomainSelectionConnection;
+import com.android.internal.telephony.domainselection.NormalCallDomainSelectionConnection;
+import com.android.internal.telephony.emergency.EmergencyStateTracker;
import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneConnection;
+import com.android.internal.telephony.imsphone.ImsPhoneMmiCode;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
import com.android.phone.FrameworksUtils;
import com.android.phone.MMIDialogActivity;
import com.android.phone.PhoneUtils;
@@ -87,6 +103,7 @@
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.regex.Pattern;
@@ -177,6 +194,15 @@
@VisibleForTesting
public Pair<WeakReference<TelephonyConnection>, Queue<Phone>> mEmergencyRetryCache;
private DeviceState mDeviceState = new DeviceState();
+ private EmergencyStateTracker mEmergencyStateTracker;
+ private DomainSelectionResolver mDomainSelectionResolver;
+ private EmergencyCallDomainSelectionConnection mEmergencyCallDomainSelectionConnection;
+ private TelephonyConnection mEmergencyConnection;
+ private String mEmergencyCallId = null;
+ private Executor mDomainSelectionMainExecutor;
+ private ImsManager mImsManager = null;
+ private DomainSelectionConnection mDomainSelectionConnection;
+ private TelephonyConnection mNormalCallConnection;
/**
* Keeps track of the status of a SIM slot.
@@ -231,7 +257,7 @@
@Override
public int getSimStateForSlotIdx(int slotId) {
- return SubscriptionManager.getSimStateForSlotIndex(slotId);
+ return TelephonyManager.getSimStateForSlotIndex(slotId);
}
@Override
@@ -502,6 +528,101 @@
}
/**
+ * A listener for emergency calls.
+ */
+ private final TelephonyConnection.TelephonyConnectionListener mEmergencyConnectionListener =
+ new TelephonyConnection.TelephonyConnectionListener() {
+ @Override
+ public void onStateChanged(Connection connection, @Connection.ConnectionState int state) {
+ if (connection != null) {
+ TelephonyConnection c = (TelephonyConnection) connection;
+ Log.i(this, "onStateChanged callId=" + c.getTelecomCallId() + ", state=" + state);
+ if (c.getState() == Connection.STATE_ACTIVE) {
+ mEmergencyStateTracker.onEmergencyCallStateChanged(
+ c.getOriginalConnection().getState(), c.getTelecomCallId());
+ c.removeTelephonyConnectionListener(mEmergencyConnectionListener);
+ releaseEmergencyCallDomainSelection(false);
+ }
+ }
+ }
+ };
+
+ /**
+ * A listener for calls.
+ */
+ private final TelephonyConnection.TelephonyConnectionListener mNormalCallConnectionListener =
+ new TelephonyConnection.TelephonyConnectionListener() {
+ @Override
+ public void onStateChanged(
+ Connection connection, @Connection.ConnectionState int state) {
+ TelephonyConnection c = (TelephonyConnection) connection;
+ if (c != null) {
+ if (c.getState() == Connection.STATE_ACTIVE) {
+ Log.d(LOG_TAG, "Call State->ACTIVE."
+ + "Clearing DomainSelectionConnection");
+ c.removeTelephonyConnectionListener(mNormalCallConnectionListener);
+ mDomainSelectionConnection.finishSelection();
+ mDomainSelectionConnection = null;
+ }
+ }
+ }
+ };
+
+ private final DomainSelectionConnection.DomainSelectionConnectionCallback
+ mEmergencyDomainSelectionConnectionCallback =
+ new DomainSelectionConnection.DomainSelectionConnectionCallback() {
+ @Override
+ public void onSelectionTerminated(@DisconnectCauses int cause) {
+ if (mEmergencyCallDomainSelectionConnection != null) {
+ Log.i(this, "onSelectionTerminated cause=" + cause);
+ mEmergencyCallDomainSelectionConnection = null;
+ if (mEmergencyConnection != null) {
+ mEmergencyConnection.hangup(android.telephony.DisconnectCause.OUT_OF_NETWORK);
+ mEmergencyConnection = null;
+ }
+ }
+ }
+ };
+
+ private final DomainSelectionConnection.DomainSelectionConnectionCallback
+ mCallDomainSelectionConnectionCallback =
+ new DomainSelectionConnection.DomainSelectionConnectionCallback() {
+ @Override
+ public void onSelectionTerminated(@DisconnectCauses int cause) {
+ 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 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) {
+ if (cause == android.telephony.DisconnectCause.NOT_VALID) {
+ cause = connection.getDisconnectCause();
+ }
+
+ String reason = connection.getVendorDisconnectCause();
+
+ mNormalCallConnection.setTelephonyConnectionDisconnected(
+ mDisconnectCauseFactory.toTelecomDisconnectCause(
+ cause, reason));
+ Log.d(this, "Call connection closed. Cause: " + cause
+ + " Reason: " + reason);
+ }
+ mNormalCallConnection.close();
+ mNormalCallConnection = null;
+ }
+
+ }
+ };
+
+ /**
* A listener to actionable events specific to the TelephonyConnection.
*/
private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
@@ -540,6 +661,8 @@
TelecomAccountRegistry.getInstance(this).setTelephonyConnectionService(this);
mHoldTracker = new HoldTracker();
mIsTtyEnabled = mDeviceState.isTtyModeEnabled(this);
+ mDomainSelectionMainExecutor = getApplicationContext().getMainExecutor();
+ mDomainSelectionResolver = DomainSelectionResolver.getInstance();
IntentFilter intentFilter = new IntentFilter(
TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED);
@@ -807,6 +930,16 @@
/* Note: when not an emergency, handle can be null for unknown callers */
handle == null ? null : handle.getSchemeSpecificPart());
+ if (mDomainSelectionResolver.isDomainSelectionSupported()) {
+ if (isEmergencyNumber) {
+ final Connection resultConnection =
+ placeEmergencyConnection(phone,
+ request, numberToDial, isTestEmergencyNumber,
+ handle, needToTurnOnRadio);
+ if (resultConnection != null) return resultConnection;
+ }
+ }
+
if (needToTurnOnRadio) {
final Uri resultHandle = handle;
final int originalPhoneType = phone.getPhoneType();
@@ -842,6 +975,19 @@
return (phone.getState() == PhoneConstants.State.OFFHOOK)
|| phone.getServiceStateTracker().isRadioOn();
} else {
+ if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
+ SubscriptionInfoInternal subInfo = SubscriptionManagerService
+ .getInstance().getSubscriptionInfoInternal(phone.getSubId());
+ // Wait until we are in service and ready to make calls. This can happen
+ // when we power down the radio on bluetooth to save power on watches or
+ // if it is a test emergency number and we have to wait for the device
+ // to move IN_SERVICE before the call can take place over normal
+ // routing.
+ return (phone.getState() == PhoneConstants.State.OFFHOOK)
+ // Do not wait for voice in service on opportunistic SIMs.
+ || (subInfo != null && subInfo.isOpportunistic())
+ || serviceState == ServiceState.STATE_IN_SERVICE;
+ }
// Wait until we are in service and ready to make calls. This can happen
// when we power down the radio on bluetooth to save power on watches or if
// it is a test emergency number and we have to wait for the device to move
@@ -1002,9 +1148,16 @@
// Notify Telecom of the new Connection type.
// TODO: Switch out the underlying connection instead of creating a new
// one and causing UI Jank.
- boolean noActiveSimCard = SubscriptionController.getInstance()
- .getActiveSubInfoCount(phone.getContext().getOpPackageName(),
- phone.getContext().getAttributionTag()) == 0;
+ boolean noActiveSimCard;
+ if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
+ noActiveSimCard = SubscriptionManagerService.getInstance()
+ .getActiveSubInfoCount(phone.getContext().getOpPackageName(),
+ phone.getContext().getAttributionTag()) == 0;
+ } else {
+ noActiveSimCard = SubscriptionController.getInstance()
+ .getActiveSubInfoCount(phone.getContext().getOpPackageName(),
+ phone.getContext().getAttributionTag()) == 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);
@@ -1693,6 +1846,10 @@
}
updatePhoneAccount(c, newPhoneToUse);
}
+ if (mDomainSelectionResolver.isDomainSelectionSupported()) {
+ onEmergencyRedial(c, newPhoneToUse);
+ return;
+ }
placeOutgoingConnection(c, newPhoneToUse, videoState, connExtras);
} else {
// We have run out of Phones to use. Disconnect the call and destroy the connection.
@@ -1743,13 +1900,12 @@
}
});
}
-
final com.android.internal.telephony.Connection originalConnection;
try {
if (phone != null) {
- EmergencyNumber emergencyNumber =
- phone.getEmergencyNumberTracker().getEmergencyNumber(number);
- if (emergencyNumber != null) {
+ boolean isEmergency = mTelephonyManagerProxy.isCurrentEmergencyNumber(number);
+ Log.i(this, "placeOutgoingConnection isEmergency=" + isEmergency);
+ if (isEmergency) {
if (!getAllConnections().isEmpty()) {
if (!shouldHoldForEmergencyCall(phone)) {
// If we do not support holding ongoing calls for an outgoing
@@ -1785,12 +1941,15 @@
}
}
}
+ } else if (handleOutgoingCallConnection(number, connection,
+ phone, videoState)) {
+ return;
}
originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder()
- .setVideoState(videoState)
- .setIntentExtras(extras)
- .setRttTextStream(connection.getRttTextStream())
- .build(),
+ .setVideoState(videoState)
+ .setIntentExtras(extras)
+ .setRttTextStream(connection.getRttTextStream())
+ .build(),
// We need to wait until the phone has been chosen in GsmCdmaPhone to
// register for the associated TelephonyConnection call event listeners.
connection::registerForCallEvents);
@@ -1803,12 +1962,11 @@
handleCallStateException(e, connection, phone);
return;
}
-
if (originalConnection == null) {
int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
// On GSM phones, null connection means that we dialed an MMI code
if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM ||
- phone.isUtEnabled()) {
+ phone.isUtEnabled()) {
Log.d(this, "dialed MMI code");
int subId = phone.getSubId();
Log.d(this, "subId: "+subId);
@@ -1841,6 +1999,540 @@
}
}
+ private void handleOutgoingCallConnectionByCallDomainSelection(
+ int domain, Phone phone, String number, int videoState) {
+ Log.d(this, "Call Domain Selected : " + domain);
+ try {
+ Bundle extras = mNormalCallConnection.getExtras();
+ if (extras == null) {
+ extras = new Bundle();
+ }
+ extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, domain);
+
+ if (phone != null) {
+ Log.v(LOG_TAG, "Call dialing. Domain: " + domain);
+ com.android.internal.telephony.Connection connection =
+ phone.dial(number, new ImsPhone.ImsDialArgs.Builder()
+ .setVideoState(videoState)
+ .setIntentExtras(extras)
+ .setRttTextStream(mNormalCallConnection.getRttTextStream())
+ .build(),
+ mNormalCallConnection::registerForCallEvents);
+
+ mNormalCallConnection.setOriginalConnection(connection);
+ mNormalCallConnection.addTelephonyConnectionListener(mNormalCallConnectionListener);
+ return;
+ } else {
+ Log.w(this, "placeOutgoingCallConnection. Dialing failed. Phone is null");
+ mNormalCallConnection.setTelephonyConnectionDisconnected(
+ mDisconnectCauseFactory.toTelecomDisconnectCause(
+ android.telephony.DisconnectCause.OUTGOING_FAILURE,
+ "Phone is null", phone.getPhoneId()));
+ mNormalCallConnection.close();
+ }
+ } catch (CallStateException e) {
+ Log.e(this, e, "Call placeOutgoingCallConnection, phone.dial exception: " + e);
+ mNormalCallConnection.unregisterForCallEvents();
+ handleCallStateException(e, mNormalCallConnection, phone);
+ } catch (Exception e) {
+ Log.e(this, e, "Call exception in placeOutgoingCallConnection:" + e);
+ mNormalCallConnection.unregisterForCallEvents();
+ mNormalCallConnection.setTelephonyConnectionDisconnected(DisconnectCauseUtil
+ .toTelecomDisconnectCause(android.telephony.DisconnectCause.OUTGOING_FAILURE,
+ e.getMessage(), phone.getPhoneId()));
+ mNormalCallConnection.close();
+ }
+ mDomainSelectionConnection.finishSelection();
+ mDomainSelectionConnection = null;
+ mNormalCallConnection = null;
+ }
+
+ private boolean handleOutgoingCallConnection(
+ String number, TelephonyConnection connection, Phone phone, int videoState) {
+
+ if (!mDomainSelectionResolver.isDomainSelectionSupported()) {
+ return false;
+ }
+
+ String dialPart = PhoneNumberUtils.extractNetworkPortionAlt(
+ PhoneNumberUtils.stripSeparators(number));
+ boolean isMmiCode = (dialPart.startsWith("*") || dialPart.startsWith("#"))
+ && dialPart.endsWith("#");
+ boolean isSuppServiceCode = ImsPhoneMmiCode.isSuppServiceCodes(dialPart, phone);
+
+ // If the number is both an MMI code and a supplementary service code,
+ // it shall be treated as UT. In this case, domain selection is not performed.
+ if (isMmiCode && isSuppServiceCode) {
+ return false;
+ }
+
+ mDomainSelectionConnection = mDomainSelectionResolver
+ .getDomainSelectionConnection(phone, SELECTOR_TYPE_CALLING, false);
+ if (mDomainSelectionConnection == null) {
+ return false;
+ }
+ Log.d(LOG_TAG, "Call Connection created");
+ SelectionAttributes selectionAttributes =
+ new SelectionAttributes.Builder(phone.getPhoneId(), phone.getSubId(),
+ SELECTOR_TYPE_CALLING)
+ .setEmergency(false)
+ .setVideoCall(VideoProfile.isVideo(videoState))
+ .build();
+
+ NormalCallDomainSelectionConnection normalCallDomainSelectionConnection =
+ (NormalCallDomainSelectionConnection) mDomainSelectionConnection;
+ CompletableFuture<Integer> future = normalCallDomainSelectionConnection
+ .createNormalConnection(selectionAttributes,
+ mCallDomainSelectionConnectionCallback);
+ Log.d(LOG_TAG, "Call Domain selection triggered.");
+
+ mNormalCallConnection = connection;
+ future.thenAcceptAsync((domain) -> handleOutgoingCallConnectionByCallDomainSelection(
+ domain, phone, number, videoState), mDomainSelectionMainExecutor);
+ return true;
+ }
+
+ @SuppressWarnings("FutureReturnValueIgnored")
+ private Connection placeEmergencyConnection(
+ final Phone phone, final ConnectionRequest request,
+ final String numberToDial, final boolean isTestEmergencyNumber,
+ final Uri handle, final boolean needToTurnOnRadio) {
+
+ final Connection resultConnection =
+ getTelephonyConnection(request, numberToDial, true, handle, phone);
+
+ if (resultConnection instanceof TelephonyConnection) {
+ Log.i(this, "placeEmergencyConnection");
+
+ mIsEmergencyCallPending = true;
+ ((TelephonyConnection) resultConnection).addTelephonyConnectionListener(
+ mEmergencyConnectionListener);
+
+ if (mEmergencyStateTracker == null) {
+ mEmergencyStateTracker = EmergencyStateTracker.getInstance();
+ }
+
+ mEmergencyCallId = resultConnection.getTelecomCallId();
+ CompletableFuture<Integer> future = mEmergencyStateTracker.startEmergencyCall(
+ phone, mEmergencyCallId, isTestEmergencyNumber);
+ future.thenAccept((result) -> {
+ Log.d(this, "startEmergencyCall-complete result=" + result);
+ if (result == android.telephony.DisconnectCause.NOT_DISCONNECTED) {
+ createEmergencyConnection(phone, (TelephonyConnection) resultConnection,
+ numberToDial, request, needToTurnOnRadio,
+ mEmergencyStateTracker.getEmergencyRegResult());
+ } else {
+ mEmergencyConnection = null;
+ String reason = "Couldn't setup emergency call";
+ if (result == android.telephony.DisconnectCause.POWER_OFF) {
+ reason = "Failed to turn on radio.";
+ }
+ ((TelephonyConnection) resultConnection).setTelephonyConnectionDisconnected(
+ mDisconnectCauseFactory.toTelecomDisconnectCause(result, reason));
+ ((TelephonyConnection) resultConnection).close();
+ mIsEmergencyCallPending = false;
+ }
+ });
+ mEmergencyConnection = (TelephonyConnection) resultConnection;
+ return resultConnection;
+ }
+ Log.i(this, "placeEmergencyConnection returns null");
+ return null;
+ }
+
+ @SuppressWarnings("FutureReturnValueIgnored")
+ private void createEmergencyConnection(final Phone phone,
+ final TelephonyConnection resultConnection, final String number,
+ final ConnectionRequest request, boolean needToTurnOnRadio,
+ final EmergencyRegResult regResult) {
+ Log.i(this, "createEmergencyConnection");
+
+ if (phone.getImsPhone() == null) {
+ // Dialing emergency calls over IMS is not available without ImsPhone instance.
+ Log.w(this, "createEmergencyConnection no ImsPhone");
+ dialCsEmergencyCall(phone, resultConnection, request);
+ return;
+ }
+
+ ImsManager imsManager = mImsManager;
+ if (imsManager == null) {
+ // mImsManager is not null only while unit test.
+ imsManager = ImsManager.getInstance(phone.getContext(), phone.getPhoneId());
+ }
+ if (!imsManager.isNonTtyOrTtyOnVolteEnabled()) {
+ Log.w(this, "createEmergencyConnection - TTY on VoLTE is not supported.");
+ dialCsEmergencyCall(phone, resultConnection, request);
+ return;
+ }
+
+ DomainSelectionConnection selectConnection =
+ mDomainSelectionResolver.getDomainSelectionConnection(
+ phone, SELECTOR_TYPE_CALLING, true);
+
+ if (selectConnection == null) {
+ // While the domain selection service is enabled, the valid
+ // {@link DomainSelectionConnection} is not available.
+ // This can happen when the domain selection service is not available.
+ Log.w(this, "createEmergencyConnection - no selectionConnection");
+ dialCsEmergencyCall(phone, resultConnection, request);
+ return;
+ }
+
+ mEmergencyCallDomainSelectionConnection =
+ (EmergencyCallDomainSelectionConnection) selectConnection;
+
+ DomainSelectionService.SelectionAttributes attr =
+ EmergencyCallDomainSelectionConnection.getSelectionAttributes(
+ phone.getPhoneId(), phone.getSubId(), needToTurnOnRadio,
+ request.getTelecomCallId(), number, 0, null, regResult);
+
+ CompletableFuture<Integer> future =
+ mEmergencyCallDomainSelectionConnection.createEmergencyConnection(
+ attr, mEmergencyDomainSelectionConnectionCallback);
+ future.thenAcceptAsync((result) -> {
+ Log.d(this, "createEmergencyConnection-complete result=" + result);
+ Bundle extras = request.getExtras();
+ extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, result);
+ placeOutgoingConnection(request, resultConnection, phone);
+ mIsEmergencyCallPending = false;
+ }, mDomainSelectionMainExecutor);
+ }
+
+ private void dialCsEmergencyCall(final Phone phone,
+ final TelephonyConnection resultConnection, final ConnectionRequest request) {
+ Log.d(this, "dialCsEmergencyCall");
+ Bundle extras = request.getExtras();
+ extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, NetworkRegistrationInfo.DOMAIN_CS);
+ mDomainSelectionMainExecutor.execute(
+ () -> placeOutgoingConnection(request, resultConnection, phone));
+ }
+
+ private void releaseEmergencyCallDomainSelection(boolean cancel) {
+ if (mEmergencyCallDomainSelectionConnection != null) {
+ if (cancel) mEmergencyCallDomainSelectionConnection.cancelSelection();
+ else mEmergencyCallDomainSelectionConnection.finishSelection();
+ mEmergencyCallDomainSelectionConnection = null;
+ }
+ mIsEmergencyCallPending = false;
+ mEmergencyConnection = null;
+ }
+
+ /**
+ * Determine whether reselection of domain is required or not.
+ * @param c the {@link Connection} instance.
+ * @param callFailCause the reason why CS call is disconnected. Allowed values are defined in
+ * {@link com.android.internal.telephony.CallFailCause}.
+ * @param reasonInfo the reason why PS call is disconnected.
+ * @return {@code true} if reselection of domain is required.
+ */
+ public boolean maybeReselectDomain(final TelephonyConnection c,
+ int callFailCause, ImsReasonInfo reasonInfo) {
+ if (!mDomainSelectionResolver.isDomainSelectionSupported()) return false;
+
+ Log.i(this, "maybeReselectDomain csCause=" + callFailCause + ", psCause=" + reasonInfo);
+ if (TextUtils.equals(mEmergencyCallId, c.getTelecomCallId())) {
+ if (mEmergencyCallDomainSelectionConnection != null) {
+ return maybeReselectDomainForEmergencyCall(c, callFailCause, reasonInfo);
+ }
+ Log.i(this, "maybeReselectDomain endCall()");
+ mEmergencyStateTracker.endCall(c.getTelecomCallId());
+ mEmergencyCallId = null;
+ return false;
+ }
+
+ if (reasonInfo != null
+ && reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL) {
+ onEmergencyRedial(c, c.getPhone());
+ return true;
+ }
+
+ return maybeReselectDomainForNormalCall(c, callFailCause, reasonInfo);
+ }
+
+ private boolean maybeReselectDomainForEmergencyCall(final TelephonyConnection c,
+ int callFailCause, ImsReasonInfo reasonInfo) {
+ Log.i(this, "maybeReselectDomainForEmergencyCall "
+ + "csCause=" + callFailCause + ", psCause=" + reasonInfo);
+
+ // EMERGENCY_TEMP_FAILURE and EMERGENCY_PERM_FAILURE shall be handled after
+ // reselecting new {@link Phone} in {@link #retryOutgoingOriginalConnection()}.
+ if (c.getOriginalConnection() != null
+ && c.getOriginalConnection().getDisconnectCause()
+ != android.telephony.DisconnectCause.EMERGENCY_TEMP_FAILURE
+ && c.getOriginalConnection().getDisconnectCause()
+ != android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE
+ && c.getOriginalConnection().getDisconnectCause()
+ != android.telephony.DisconnectCause.LOCAL
+ && c.getOriginalConnection().getDisconnectCause()
+ != android.telephony.DisconnectCause.POWER_OFF) {
+
+ DomainSelectionService.SelectionAttributes attr =
+ EmergencyCallDomainSelectionConnection.getSelectionAttributes(
+ c.getPhone().getPhoneId(), c.getPhone().getSubId(), false,
+ c.getTelecomCallId(), c.getAddress().getSchemeSpecificPart(),
+ callFailCause, reasonInfo, null);
+
+ CompletableFuture<Integer> future =
+ mEmergencyCallDomainSelectionConnection.reselectDomain(attr);
+ if (future != null) {
+ future.thenAcceptAsync((result) -> {
+ Log.d(this, "reselectDomain-complete");
+ onEmergencyRedialOnDomain(c, c.getPhone(), result);
+ }, mDomainSelectionMainExecutor);
+ return true;
+ }
+ }
+
+ Log.i(this, "maybeReselectDomainForEmergencyCall endCall()");
+ c.removeTelephonyConnectionListener(mEmergencyConnectionListener);
+ mEmergencyStateTracker.endCall(c.getTelecomCallId());
+ releaseEmergencyCallDomainSelection(true);
+
+ return false;
+ }
+
+ private boolean maybeReselectDomainForNormalCall(
+ final TelephonyConnection c, int callFailCause, ImsReasonInfo reasonInfo) {
+
+ Log.i(LOG_TAG, "maybeReselectDomainForNormalCall " + "csCause:" + callFailCause
+ + ", psCause:" + reasonInfo);
+
+ if (mDomainSelectionConnection != null && c.getOriginalConnection() != null) {
+ Phone phone = c.getPhone();
+ final String number = c.getAddress().getSchemeSpecificPart();
+ int videoState = c.getOriginalConnection().getVideoState();
+ SelectionAttributes selectionAttributes = NormalCallDomainSelectionConnection
+ .getSelectionAttributes(phone.getPhoneId(), phone.getSubId(),
+ c.getTelecomCallId(), number, VideoProfile.isVideo(videoState),
+ callFailCause, reasonInfo);
+
+ Log.d(LOG_TAG, "Reselecting the domain for call");
+ CompletableFuture<Integer> future = mDomainSelectionConnection
+ .reselectDomain(selectionAttributes);
+ if (future != null) {
+ future.thenAcceptAsync((result) -> {
+ onNormalCallRedial(c, result, videoState);
+ }, mDomainSelectionMainExecutor);
+ return true;
+ }
+ }
+
+ c.removeTelephonyConnectionListener(mTelephonyConnectionListener);
+ if (mDomainSelectionConnection != null) {
+ mDomainSelectionConnection.finishSelection();
+ mDomainSelectionConnection = null;
+ }
+ mNormalCallConnection = null;
+ Log.d(LOG_TAG, "Reselecting the domain for call failed");
+ return false;
+ }
+
+ private void onEmergencyRedialOnDomain(TelephonyConnection connection,
+ final Phone phone, @NetworkRegistrationInfo.Domain int domain) {
+ Log.i(this, "onEmergencyRedialOnDomain phoneId=" + phone.getPhoneId()
+ + ", domain=" + DomainSelectionService.getDomainName(domain));
+
+ String number = connection.getAddress().getSchemeSpecificPart();
+
+ // Indicates undetectable emergency number with DialArgs
+ boolean isEmergency = false;
+ int eccCategory = EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED;
+ if (connection.getEmergencyServiceCategory() != null) {
+ isEmergency = true;
+ eccCategory = connection.getEmergencyServiceCategory();
+ Log.i(this, "onEmergencyRedialOnDomain eccCategory=" + eccCategory);
+ }
+
+ Bundle extras = new Bundle();
+ extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, domain);
+
+ com.android.internal.telephony.Connection originalConnection =
+ connection.getOriginalConnection();
+ try {
+ if (phone != null) {
+ originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder()
+ .setVideoState(VideoProfile.STATE_AUDIO_ONLY)
+ .setIntentExtras(extras)
+ .setRttTextStream(connection.getRttTextStream())
+ .setIsEmergency(isEmergency)
+ .setEccCategory(eccCategory)
+ .build(),
+ connection::registerForCallEvents);
+ }
+ } catch (CallStateException e) {
+ Log.e(this, e, "onEmergencyRedialOnDomain, exception: " + e);
+ }
+ if (originalConnection == null) {
+ Log.d(this, "onEmergencyRedialOnDomain, phone.dial returned null");
+ connection.setDisconnected(
+ mDisconnectCauseFactory.toTelecomDisconnectCause(
+ android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
+ "unknown error"));
+ } else {
+ connection.setOriginalConnection(originalConnection);
+ }
+ }
+
+ @SuppressWarnings("FutureReturnValueIgnored")
+ private void onEmergencyRedial(final TelephonyConnection c, final Phone phone) {
+ Log.i(this, "onEmergencyRedial phoneId=" + phone.getPhoneId());
+
+ final String number = c.getAddress().getSchemeSpecificPart();
+ final boolean isTestEmergencyNumber = isEmergencyNumberTestNumber(number);
+
+ mIsEmergencyCallPending = true;
+ c.addTelephonyConnectionListener(mEmergencyConnectionListener);
+
+ if (mEmergencyStateTracker == null) {
+ mEmergencyStateTracker = EmergencyStateTracker.getInstance();
+ }
+
+ mEmergencyCallId = c.getTelecomCallId();
+ CompletableFuture<Integer> future = mEmergencyStateTracker.startEmergencyCall(
+ phone, mEmergencyCallId, isTestEmergencyNumber);
+ future.thenAccept((result) -> {
+ Log.d(this, "onEmergencyRedial-complete result=" + result);
+ if (result == android.telephony.DisconnectCause.NOT_DISCONNECTED) {
+
+ DomainSelectionConnection selectConnection =
+ mDomainSelectionResolver.getDomainSelectionConnection(
+ phone, SELECTOR_TYPE_CALLING, true);
+
+ if (selectConnection == null) {
+ Log.w(this, "onEmergencyRedial no selectionConnection, dial CS emergency call");
+ mIsEmergencyCallPending = false;
+ mDomainSelectionMainExecutor.execute(
+ () -> recreateEmergencyConnection(c, phone,
+ NetworkRegistrationInfo.DOMAIN_CS));
+ return;
+ }
+
+ mEmergencyCallDomainSelectionConnection =
+ (EmergencyCallDomainSelectionConnection) selectConnection;
+
+ mEmergencyConnection = c;
+
+ DomainSelectionService.SelectionAttributes attr =
+ EmergencyCallDomainSelectionConnection.getSelectionAttributes(
+ phone.getPhoneId(),
+ phone.getSubId(), false,
+ c.getTelecomCallId(),
+ c.getAddress().getSchemeSpecificPart(),
+ 0, null, mEmergencyStateTracker.getEmergencyRegResult());
+
+ CompletableFuture<Integer> domainFuture =
+ mEmergencyCallDomainSelectionConnection.createEmergencyConnection(
+ attr, mEmergencyDomainSelectionConnectionCallback);
+ domainFuture.thenAcceptAsync((domain) -> {
+ Log.d(this, "onEmergencyRedial-createEmergencyConnection-complete domain="
+ + domain);
+ recreateEmergencyConnection(c, phone, domain);
+ mIsEmergencyCallPending = false;
+ }, mDomainSelectionMainExecutor);
+ } else {
+ c.setTelephonyConnectionDisconnected(
+ mDisconnectCauseFactory.toTelecomDisconnectCause(result, "unknown error"));
+ c.close();
+ mIsEmergencyCallPending = false;
+ }
+ });
+ }
+
+ private void recreateEmergencyConnection(final TelephonyConnection connection,
+ final Phone phone, final @NetworkRegistrationInfo.Domain int result) {
+ Log.d(this, "recreateEmergencyConnection result=" + result);
+ if (!getAllConnections().isEmpty()) {
+ if (!shouldHoldForEmergencyCall(phone)) {
+ // If we do not support holding ongoing calls for an outgoing
+ // emergency call, disconnect the ongoing calls.
+ for (Connection c : getAllConnections()) {
+ if (!c.equals(connection)
+ && c.getState() != Connection.STATE_DISCONNECTED
+ && c instanceof TelephonyConnection) {
+ ((TelephonyConnection) c).hangup(
+ android.telephony.DisconnectCause
+ .OUTGOING_EMERGENCY_CALL_PLACED);
+ }
+ }
+ for (Conference c : getAllConferences()) {
+ if (c.getState() != Connection.STATE_DISCONNECTED) {
+ c.onDisconnect();
+ }
+ }
+ } else if (!isVideoCallHoldAllowed(phone)) {
+ // If we do not support holding ongoing video call for an outgoing
+ // emergency call, disconnect the ongoing video call.
+ for (Connection c : getAllConnections()) {
+ if (!c.equals(connection)
+ && c.getState() == Connection.STATE_ACTIVE
+ && VideoProfile.isVideo(c.getVideoState())
+ && c instanceof TelephonyConnection) {
+ ((TelephonyConnection) c).hangup(
+ android.telephony.DisconnectCause
+ .OUTGOING_EMERGENCY_CALL_PLACED);
+ break;
+ }
+ }
+ }
+ }
+ onEmergencyRedialOnDomain(connection, phone, result);
+ }
+
+ private void onNormalCallRedial(TelephonyConnection connection,
+ @NetworkRegistrationInfo.Domain int domain, int videocallState) {
+
+ Log.v(LOG_TAG, "Redialing the call in domain:"
+ + DomainSelectionService.getDomainName(domain));
+
+ String number = connection.getAddress().getSchemeSpecificPart();
+ Phone phone = connection.getPhone();
+
+ Bundle extras = new Bundle();
+ extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, domain);
+
+ com.android.internal.telephony.Connection originalConnection =
+ connection.getOriginalConnection();
+ if (originalConnection instanceof ImsPhoneConnection) {
+ if (((ImsPhoneConnection) originalConnection).isRttEnabledForCall()) {
+ extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, true);
+ }
+ }
+
+ try {
+ if (phone != null) {
+ Log.d(LOG_TAG, "Redialing Call.");
+ originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder()
+ .setVideoState(videocallState)
+ .setIntentExtras(extras)
+ .setRttTextStream(connection.getRttTextStream())
+ .setIsEmergency(false)
+ .build(),
+ connection::registerForCallEvents);
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, e, "Call redial exception: " + e);
+ }
+ if (originalConnection == null) {
+ Log.e(LOG_TAG, new Exception("Phone is null"),
+ "Call redial failure due to phone.dial returned null");
+ connection.setDisconnected(mDisconnectCauseFactory.toTelecomDisconnectCause(
+ android.telephony.DisconnectCause.OUTGOING_FAILURE, "connection is null"));
+ connection.close();
+ } else {
+ connection.setOriginalConnection(originalConnection);
+ }
+ }
+
+ protected void onLocalHangup(TelephonyConnection c) {
+ if (TextUtils.equals(mEmergencyCallId, c.getTelecomCallId())) {
+ Log.i(this, "onLocalHangup " + mEmergencyCallId);
+ c.removeTelephonyConnectionListener(mEmergencyConnectionListener);
+ mEmergencyStateTracker.endCall(c.getTelecomCallId());
+ releaseEmergencyCallDomainSelection(true);
+ mEmergencyCallId = null;
+ }
+ }
+
private boolean isVideoCallHoldAllowed(Phone phone) {
CarrierConfigManager cfgManager = (CarrierConfigManager)
phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
diff --git a/src/com/android/services/telephony/domainselection/DomainSelectorBase.java b/src/com/android/services/telephony/domainselection/DomainSelectorBase.java
new file mode 100644
index 0000000..1a7c992
--- /dev/null
+++ b/src/com/android/services/telephony/domainselection/DomainSelectorBase.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 android.annotation.NonNull;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.DomainSelector;
+import android.telephony.TransportSelectorCallback;
+import android.telephony.WwanSelectorCallback;
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
+import android.util.Log;
+
+import com.android.internal.annotations.Keep;
+
+import java.io.PrintWriter;
+
+/**
+ * An abstract base class to implement domain selector for a specific use case.
+ */
+@Keep
+public abstract class DomainSelectorBase extends Handler implements DomainSelector {
+ /**
+ * A listener used to inform the DomainSelectorService that this DomainSelector has been
+ * destroyed.
+ */
+ public interface DestroyListener {
+ /**
+ * Called when the specified domain selector is being destroyed.
+ * This MUST be called when this domain selector is no longer available after
+ * {@link DomainSelector#finishSelection} called.
+ */
+ void onDomainSelectorDestroyed(DomainSelectorBase selector);
+ }
+
+ // Persistent Logging
+ protected final LocalLog mEventLog = new LocalLog(30);
+ protected final Context mContext;
+ protected final ImsStateTracker mImsStateTracker;
+ protected SelectionAttributes mSelectionAttributes;
+ protected TransportSelectorCallback mTransportSelectorCallback;
+ protected WwanSelectorCallback mWwanSelectorCallback;
+ private final int mSlotId;
+ private final int mSubId;
+ private final DestroyListener mDestroyListener;
+ private final String mLogTag;
+
+ public DomainSelectorBase(Context context, int slotId, int subId, @NonNull Looper looper,
+ @NonNull ImsStateTracker imsStateTracker, @NonNull DestroyListener destroyListener,
+ String logTag) {
+ super(looper);
+ mContext = context;
+ mImsStateTracker = imsStateTracker;
+ mSlotId = slotId;
+ mSubId = subId;
+ mDestroyListener = destroyListener;
+ mLogTag = logTag;
+ }
+
+ /**
+ * Selects a domain for the specified attributes and callback.
+ *
+ * @param attr The attributes required to determine the domain.
+ * @param callback The callback called when the transport selection is completed.
+ */
+ public abstract void selectDomain(SelectionAttributes attr, TransportSelectorCallback callback);
+
+ /**
+ * Destroys this domain selector.
+ */
+ protected void destroy() {
+ removeCallbacksAndMessages(null);
+ notifyDomainSelectorDestroyed();
+ }
+
+ /**
+ * Notifies the application that this domain selector is being destroyed.
+ */
+ protected void notifyDomainSelectorDestroyed() {
+ if (mDestroyListener != null) {
+ mDestroyListener.onDomainSelectorDestroyed(this);
+ }
+ }
+
+ /**
+ * Returns the slot index for this domain selector.
+ */
+ protected int getSlotId() {
+ return mSlotId;
+ }
+
+ /**
+ * Returns the subscription index for this domain selector.
+ */
+ protected int getSubId() {
+ return mSubId;
+ }
+
+ /**
+ * Dumps this instance into a readable format for dumpsys usage.
+ */
+ protected void dump(@NonNull PrintWriter pw) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.println(mLogTag + ":");
+ ipw.increaseIndent();
+ ipw.println("SlotId: " + getSlotId());
+ ipw.println("SubId: " + getSubId());
+ mEventLog.dump(ipw);
+ ipw.decreaseIndent();
+ }
+
+ protected void logd(String s) {
+ Log.d(mLogTag, "[" + getSlotId() + "|" + getSubId() + "] " + s);
+ }
+
+ protected void logi(String s) {
+ Log.i(mLogTag, "[" + getSlotId() + "|" + getSubId() + "] " + s);
+ mEventLog.log("[" + getSlotId() + "|" + getSubId() + "] " + s);
+ }
+
+ protected void loge(String s) {
+ Log.e(mLogTag, "[" + getSlotId() + "|" + getSubId() + "] " + s);
+ mEventLog.log("[" + getSlotId() + "|" + getSubId() + "] " + s);
+ }
+}
diff --git a/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java b/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
new file mode 100644
index 0000000..9aaf6da
--- /dev/null
+++ b/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelector.java
@@ -0,0 +1,1047 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.CDMA2000;
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.EUTRAN;
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.NGRAN;
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.UNKNOWN;
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.UTRAN;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.BarringInfo.BARRING_SERVICE_TYPE_EMERGENCY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.DOMAIN_CS;
+import static android.telephony.CarrierConfigManager.ImsEmergency.DOMAIN_PS_3GPP;
+import static android.telephony.CarrierConfigManager.ImsEmergency.DOMAIN_PS_NON_3GPP;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_SCAN_TIMER_SEC_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.PowerManager;
+import android.os.SystemProperties;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.AccessNetworkConstants.TransportType;
+import android.telephony.BarringInfo;
+import android.telephony.CarrierConfigManager;
+import android.telephony.DomainSelectionService;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.EmergencyRegResult;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.TransportSelectorCallback;
+import android.telephony.ims.ImsManager;
+import android.telephony.ims.ImsMmTelManager;
+import android.text.TextUtils;
+import android.util.LocalLog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.IntFunction;
+
+/**
+ * Selects the domain for emergency calling.
+ */
+public class EmergencyCallDomainSelector extends DomainSelectorBase
+ implements ImsStateTracker.BarringInfoListener, ImsStateTracker.ImsStateListener {
+ private static final String TAG = "DomainSelector-EmergencyCall";
+ private static final boolean DBG = (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ private static final int LOG_SIZE = 50;
+
+ private static final int MSG_START_DOMAIN_SELECTION = 11;
+ @VisibleForTesting
+ public static final int MSG_NETWORK_SCAN_TIMEOUT = 12;
+ private static final int MSG_NETWORK_SCAN_RESULT = 13;
+
+ private static final int NOT_SUPPORTED = -1;
+
+ private static final LocalLog sLocalLog = new LocalLog(LOG_SIZE);
+
+ private boolean mIsEmergencyBarred;
+ private boolean mImsRegistered;
+ private boolean mIsVoiceCapable;
+ private boolean mBarringInfoReceived;
+ private boolean mImsRegStateReceived;
+ private boolean mMmTelCapabilitiesReceived;
+ private int mVoWifiTrialCount = 0;
+
+ private @RadioAccessNetworkType int mCsNetworkType = UNKNOWN;
+ private @RadioAccessNetworkType int mPsNetworkType = UNKNOWN;
+ private @RadioAccessNetworkType int mLastNetworkType = UNKNOWN;
+ private @TransportType int mLastTransportType = TRANSPORT_TYPE_INVALID;
+ private @DomainSelectionService.EmergencyScanType int mScanType;
+ private @RadioAccessNetworkType List<Integer> mLastPreferredNetworks;
+
+ private CancellationSignal mCancelSignal;
+
+ private @RadioAccessNetworkType int[] mImsRatsConfig;
+ private @RadioAccessNetworkType int[] mCsRatsConfig;
+ private @RadioAccessNetworkType int[] mImsRoamRatsConfig;
+ private @RadioAccessNetworkType int[] mCsRoamRatsConfig;
+ private @CarrierConfigManager.ImsEmergency.EmergencyDomain int[] mDomainPreference;
+ private @CarrierConfigManager.ImsEmergency.EmergencyDomain int[] mDomainPreferenceRoam;
+ private List<String> mCdmaPreferredNumbers;
+ private boolean mPreferImsWhenCallsOnCs;
+ private int mScanTimeout;
+ private int mMaxNumOfVoWifiTries;
+ private @CarrierConfigManager.ImsEmergency.EmergencyScanType int mPreferredNetworkScanType;
+ private int mCallSetupTimerOnCurrentRatSec;
+ private boolean mRequiresImsRegistration;
+ private boolean mRequiresVoLteEnabled;
+ private boolean mLtePreferredAfterNrFailure;
+ private boolean mTryCsWhenPsFails;
+
+ /** Indicates whether this instance is deactivated. */
+ private boolean mDestroyed = false;
+ /** Indicates whether emergency network scan is requested. */
+ private boolean mIsScanRequested = false;
+ /** Indicates whether selected domain has been notified. */
+ private boolean mDomainSelected = false;
+ /**
+ * Indicates whether {@link #selectDomain(SelectionAttributes, TransportSelectionCallback)}
+ * is called or not.
+ */
+ private boolean mDomainSelectionRequested = false;
+
+ private final PowerManager.WakeLock mPartialWakeLock;
+
+ /** Constructor. */
+ public EmergencyCallDomainSelector(Context context, int slotId, int subId,
+ @NonNull Looper looper, @NonNull ImsStateTracker imsStateTracker,
+ @NonNull DestroyListener destroyListener) {
+ super(context, slotId, subId, looper, imsStateTracker, destroyListener, TAG);
+
+ mImsStateTracker.addBarringInfoListener(this);
+ mImsStateTracker.addImsStateListener(this);
+
+ PowerManager pm = context.getSystemService(PowerManager.class);
+ mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+
+ acquireWakeLock();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (mDestroyed) return;
+
+ switch(msg.what) {
+ case MSG_START_DOMAIN_SELECTION:
+ startDomainSelection();
+ break;
+
+ case MSG_NETWORK_SCAN_TIMEOUT:
+ handleNetworkScanTimeout();
+ break;
+
+ case MSG_NETWORK_SCAN_RESULT:
+ handleScanResult((EmergencyRegResult) msg.obj);
+ break;
+
+ default:
+ super.handleMessage(msg);
+ break;
+ }
+ }
+
+ /**
+ * Handles the scan result.
+ *
+ * @param result The scan result.
+ */
+ private void handleScanResult(EmergencyRegResult result) {
+ logi("handleScanResult result=" + result);
+
+ if (result.getAccessNetwork() == UNKNOWN) {
+ if ((mPreferredNetworkScanType == SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE)
+ || (mScanType == DomainSelectionService.SCAN_TYPE_FULL_SERVICE)) {
+ mScanType = DomainSelectionService.SCAN_TYPE_LIMITED_SERVICE;
+ mWwanSelectorCallback.onRequestEmergencyNetworkScan(
+ mLastPreferredNetworks, mScanType, mCancelSignal,
+ (regResult) -> {
+ logi("requestScan-onComplete");
+ sendMessage(obtainMessage(MSG_NETWORK_SCAN_RESULT, regResult));
+ });
+ } else {
+ // Continuous scan, do not start a new timer.
+ requestScan(false);
+ }
+ return;
+ }
+
+ removeMessages(MSG_NETWORK_SCAN_TIMEOUT);
+ onWwanNetworkTypeSelected(result.getAccessNetwork());
+ mIsScanRequested = false;
+ }
+
+ @Override
+ public void cancelSelection() {
+ logi("cancelSelection");
+ finishSelection();
+ }
+
+ @Override
+ public void reselectDomain(SelectionAttributes attr) {
+ logi("reselectDomain tryCsWhenPsFails=" + mTryCsWhenPsFails + ", attr=" + attr);
+ mSelectionAttributes = attr;
+ if (mTryCsWhenPsFails) {
+ mTryCsWhenPsFails = false;
+ mCsNetworkType = getSelectableCsNetworkType();
+ logi("reselectDomain tryCs=" + accessNetworkTypeToString(mCsNetworkType));
+ if (mCsNetworkType != UNKNOWN) {
+ onWwanNetworkTypeSelected(mCsNetworkType);
+ return;
+ }
+ } else if (getImsNetworkTypeConfiguration().isEmpty()
+ || (mRequiresVoLteEnabled && !isAdvancedCallingSettingEnabled())) {
+ // Emergency call over IMS is not supported.
+ mCsNetworkType = UTRAN;
+ onWwanNetworkTypeSelected(mCsNetworkType);
+ return;
+ }
+
+ if (mLastTransportType == TRANSPORT_TYPE_WLAN) {
+ // Dialing over Wi-Fi failed. Try scanning cellular networks.
+ onWwanSelected(() -> {
+ requestScan(true, false, true);
+ mDomainSelected = false;
+ });
+ return;
+ }
+
+ requestScan(true);
+ mDomainSelected = false;
+ }
+
+ @Override
+ public void finishSelection() {
+ logi("finishSelection");
+ destroy();
+ }
+
+ @Override
+ public void onBarringInfoUpdated(BarringInfo barringInfo) {
+ if (mDestroyed) return;
+
+ mBarringInfoReceived = true;
+ BarringInfo.BarringServiceInfo serviceInfo =
+ barringInfo.getBarringServiceInfo(BARRING_SERVICE_TYPE_EMERGENCY);
+ mIsEmergencyBarred = serviceInfo.isBarred();
+ logi("onBarringInfoUpdated emergencyBarred=" + mIsEmergencyBarred
+ + ", serviceInfo=" + serviceInfo);
+ selectDomain();
+ }
+
+ @Override
+ public void selectDomain(SelectionAttributes attr, TransportSelectorCallback cb) {
+ logi("selectDomain attr=" + attr);
+ mTransportSelectorCallback = cb;
+ mSelectionAttributes = attr;
+
+ sendEmptyMessage(MSG_START_DOMAIN_SELECTION);
+ }
+
+ private void startDomainSelection() {
+ logi("startDomainSelection");
+ updateCarrierConfiguration();
+ mDomainSelectionRequested = true;
+ if (SubscriptionManager.isValidSubscriptionId(getSubId())) {
+ selectDomain();
+ } else {
+ logi("startDomainSelection invalid subId");
+ onImsRegistrationStateChanged();
+ onImsMmTelCapabilitiesChanged();
+ }
+ }
+
+ @Override
+ public void onImsMmTelFeatureAvailableChanged() {
+ // DOMAIN_CS shall be selected when ImsService is not available.
+ // TODO(b/258289015) Recover the temporary failure in ImsService connection.
+ }
+
+ @Override
+ public void onImsRegistrationStateChanged() {
+ mImsRegStateReceived = true;
+ mImsRegistered = mImsStateTracker.isImsRegistered();
+ logi("onImsRegistrationStateChanged " + mImsRegistered);
+ selectDomain();
+ }
+
+ @Override
+ public void onImsMmTelCapabilitiesChanged() {
+ mMmTelCapabilitiesReceived = true;
+ mIsVoiceCapable = mImsStateTracker.isImsVoiceCapable();
+ logi("onImsMmTelCapabilitiesChanged " + mIsVoiceCapable);
+ selectDomain();
+ }
+
+ /**
+ * Caches the configuration.
+ */
+ private void updateCarrierConfiguration() {
+ CarrierConfigManager configMgr = mContext.getSystemService(CarrierConfigManager.class);
+ PersistableBundle b = configMgr.getConfigForSubId(getSubId());
+ if (b == null) {
+ b = CarrierConfigManager.getDefaultConfig();
+ }
+
+ mImsRatsConfig =
+ b.getIntArray(KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY);
+ mImsRoamRatsConfig = b.getIntArray(
+ KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY);
+ if (!SubscriptionManager.isValidSubscriptionId(getSubId())) {
+ // Default configuration includes only EUTRAN . In case of no SIM, add NGRAN.
+ mImsRatsConfig = new int[] { EUTRAN, NGRAN };
+ mImsRoamRatsConfig = new int[] { EUTRAN, NGRAN };
+ }
+
+ mCsRatsConfig =
+ b.getIntArray(KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY);
+ mCsRoamRatsConfig = b.getIntArray(
+ KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY);
+ mDomainPreference = b.getIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY);
+ mDomainPreferenceRoam = b.getIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY);
+ mPreferImsWhenCallsOnCs = b.getBoolean(
+ KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL);
+ mScanTimeout = b.getInt(KEY_EMERGENCY_SCAN_TIMER_SEC_INT);
+ mMaxNumOfVoWifiTries = b.getInt(KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT);
+ mPreferredNetworkScanType = b.getInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT);
+ mCallSetupTimerOnCurrentRatSec = b.getInt(
+ KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT);
+ mRequiresImsRegistration = b.getBoolean(KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL);
+ mRequiresVoLteEnabled = b.getBoolean(KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL);
+ mLtePreferredAfterNrFailure = b.getBoolean(
+ KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL);
+ String[] numbers = b.getStringArray(KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY);
+
+ if (mImsRatsConfig == null) mImsRatsConfig = new int[0];
+ if (mCsRatsConfig == null) mCsRatsConfig = new int[0];
+ if (mImsRoamRatsConfig == null) mImsRoamRatsConfig = new int[0];
+ if (mCsRoamRatsConfig == null) mCsRoamRatsConfig = new int[0];
+ if (mDomainPreference == null) mDomainPreference = new int[0];
+ if (mDomainPreferenceRoam == null) mDomainPreferenceRoam = new int[0];
+ if (numbers == null) numbers = new String[0];
+
+ logi("updateCarrierConfiguration "
+ + "imsRats=" + arrayToString(mImsRatsConfig,
+ EmergencyCallDomainSelector::accessNetworkTypeToString)
+ + ", csRats=" + arrayToString(mCsRatsConfig,
+ EmergencyCallDomainSelector::accessNetworkTypeToString)
+ + ", imsRoamRats=" + arrayToString(mImsRoamRatsConfig,
+ EmergencyCallDomainSelector::accessNetworkTypeToString)
+ + ", csRoamRats=" + arrayToString(mCsRoamRatsConfig,
+ EmergencyCallDomainSelector::accessNetworkTypeToString)
+ + ", domainPref=" + arrayToString(mDomainPreference,
+ EmergencyCallDomainSelector::domainPreferenceToString)
+ + ", domainPrefRoam=" + arrayToString(mDomainPreferenceRoam,
+ EmergencyCallDomainSelector::domainPreferenceToString)
+ + ", preferImsOnCs=" + mPreferImsWhenCallsOnCs
+ + ", scanTimeout=" + mScanTimeout
+ + ", maxNumOfVoWifiTries=" + mMaxNumOfVoWifiTries
+ + ", preferredScanType=" + carrierConfigNetworkScanTypeToString(
+ mPreferredNetworkScanType)
+ + ", callSetupTimer=" + mCallSetupTimerOnCurrentRatSec
+ + ", requiresImsReg=" + mRequiresImsRegistration
+ + ", requiresVoLteEnabled=" + mRequiresVoLteEnabled
+ + ", ltePreferredAfterNr=" + mLtePreferredAfterNrFailure
+ + ", cdmaPreferredNumbers=" + arrayToString(numbers));
+
+ mCdmaPreferredNumbers = Arrays.asList(numbers);
+
+ if ((mPreferredNetworkScanType == CarrierConfigManager.ImsEmergency.SCAN_TYPE_FULL_SERVICE)
+ || (mPreferredNetworkScanType
+ == SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE)) {
+ mScanType = DomainSelectionService.SCAN_TYPE_FULL_SERVICE;
+ } else {
+ mScanType = DomainSelectionService.SCAN_TYPE_NO_PREFERENCE;
+ }
+ }
+
+ private void selectDomain() {
+ // State updated right after creation.
+ if (!mDomainSelectionRequested) return;
+
+ // Emergency network scan requested has not been completed.
+ if (mIsScanRequested) return;
+
+ // Domain selection completed, {@link #reselectDomain()} will restart domain selection.
+ if (mDomainSelected) return;
+
+ if (!mBarringInfoReceived || !mImsRegStateReceived || !mMmTelCapabilitiesReceived) {
+ logi("selectDomain not received"
+ + " BarringInfo, IMS registration state, or MMTEL capabilities");
+ return;
+ }
+
+ if (isWifiPreferred()) {
+ onWlanSelected();
+ return;
+ }
+
+ onWwanSelected(this::selectDomainInternal);
+ }
+
+ private void selectDomainInternal() {
+ if (getImsNetworkTypeConfiguration().isEmpty()
+ || (mRequiresVoLteEnabled && !isAdvancedCallingSettingEnabled())) {
+ // Emergency call over IMS is not supported.
+ mCsNetworkType = UTRAN;
+ onWwanNetworkTypeSelected(mCsNetworkType);
+ return;
+ }
+
+ boolean csInService = isCsInService();
+ boolean psInService = isPsInService();
+
+ if (!csInService && !psInService) {
+ mPsNetworkType = getSelectablePsNetworkType(false);
+ logi("selectDomain limited service ps=" + accessNetworkTypeToString(mPsNetworkType));
+ if (mPsNetworkType == UNKNOWN) {
+ requestScan(true);
+ } else {
+ onWwanNetworkTypeSelected(mPsNetworkType);
+ }
+ return;
+ }
+
+ // Domain selection per 3GPP TS 23.167 Table H.1.
+ // PS is preferred in case selection between CS and PS is implementation option.
+ mCsNetworkType = UNKNOWN;
+ mPsNetworkType = UNKNOWN;
+ if (csInService) mCsNetworkType = getSelectableCsNetworkType();
+ if (psInService) mPsNetworkType = getSelectablePsNetworkType(true);
+
+ boolean csAvailable = mCsNetworkType != UNKNOWN;
+ boolean psAvailable = mPsNetworkType != UNKNOWN;
+
+ logi("selectDomain CS={" + csInService + ", " + accessNetworkTypeToString(mCsNetworkType)
+ + "}, PS={" + psInService + ", " + accessNetworkTypeToString(mPsNetworkType) + "}");
+ if (csAvailable && psAvailable) {
+ if (mPreferImsWhenCallsOnCs || isImsRegisteredWithVoiceCapability()) {
+ mTryCsWhenPsFails = true;
+ onWwanNetworkTypeSelected(mPsNetworkType);
+ } else if (isDeactivatedSim()) {
+ // Deactivated SIM but PS is in service and supports emergency calls.
+ onWwanNetworkTypeSelected(mPsNetworkType);
+ } else {
+ onWwanNetworkTypeSelected(mCsNetworkType);
+ }
+ } else if (psAvailable) {
+ if (!mRequiresImsRegistration || isImsRegisteredWithVoiceCapability()) {
+ onWwanNetworkTypeSelected(mPsNetworkType);
+ } else if (isDeactivatedSim()) {
+ // Deactivated SIM but PS is in service and supports emergency calls.
+ onWwanNetworkTypeSelected(mPsNetworkType);
+ } else {
+ // Carrier configuration requires IMS registration for emergency services over PS,
+ // but not registered. Try CS emergency call.
+ requestScan(true, true);
+ }
+ } else if (csAvailable) {
+ onWwanNetworkTypeSelected(mCsNetworkType);
+ } else {
+ // PS is in service but not supports emergency calls.
+ if (mRequiresImsRegistration && !isImsRegisteredWithVoiceCapability()) {
+ // Carrier configuration requires IMS registration for emergency services over PS,
+ // but not registered. Try CS emergency call.
+ requestScan(true, true);
+ } else {
+ requestScan(true);
+ }
+ }
+ }
+
+ /**
+ * Requests network scan.
+ *
+ * @param startVoWifiTimer Indicates whether a VoWifi timer will be started.
+ */
+ private void requestScan(boolean startVoWifiTimer) {
+ requestScan(startVoWifiTimer, false);
+ }
+
+ /**
+ * Requests network scan.
+ *
+ * @param startVoWifiTimer Indicates whether a VoWifi timer will be started.
+ * @param csPreferred Indicates whether CS preferred scan is requested.
+ */
+ private void requestScan(boolean startVoWifiTimer, boolean csPreferred) {
+ requestScan(startVoWifiTimer, csPreferred, false);
+ }
+
+ /**
+ * Requests network scan.
+ *
+ * @param startVoWifiTimer Indicates whether a VoWifi timer will be started.
+ * @param csPreferred Indicates whether CS preferred scan is requested.
+ * @param wifiFailed Indicates dialing over Wi-Fi has failed.
+ */
+ private void requestScan(boolean startVoWifiTimer, boolean csPreferred, boolean wifiFailed) {
+ logi("requestScan timer=" + startVoWifiTimer + ", csPreferred=" + csPreferred
+ + ", wifiFailed=" + wifiFailed);
+
+ mCancelSignal = new CancellationSignal();
+ // In case dialing over Wi-Fi has failed, do not the change the domain preference.
+ if (!wifiFailed) mLastPreferredNetworks = getNextPreferredNetworks(csPreferred);
+
+ if (isInRoaming()
+ && (mPreferredNetworkScanType == DomainSelectionService.SCAN_TYPE_FULL_SERVICE)) {
+ // FULL_SERVICE only preference is available only when not in roaming.
+ mScanType = DomainSelectionService.SCAN_TYPE_NO_PREFERENCE;
+ }
+
+ mIsScanRequested = true;
+ mWwanSelectorCallback.onRequestEmergencyNetworkScan(
+ mLastPreferredNetworks, mScanType, mCancelSignal,
+ (result) -> {
+ logi("requestScan-onComplete");
+ sendMessage(obtainMessage(MSG_NETWORK_SCAN_RESULT, result));
+ });
+
+ if (startVoWifiTimer && SubscriptionManager.isValidSubscriptionId(getSubId())) {
+ if (isEmcOverWifiSupported()
+ && mScanTimeout > 0 && mVoWifiTrialCount < mMaxNumOfVoWifiTries) {
+ logi("requestScan start scan timer");
+ // remove any pending timers.
+ removeMessages(MSG_NETWORK_SCAN_TIMEOUT);
+ sendEmptyMessageDelayed(MSG_NETWORK_SCAN_TIMEOUT, mScanTimeout);
+ }
+ }
+ }
+
+ /**
+ * Gets the list of preferred network type for the new scan request.
+ *
+ * @param csPreferred Indicates whether CS preferred scan is requested.
+ * @return The list of preferred network types.
+ */
+ private @RadioAccessNetworkType List<Integer> getNextPreferredNetworks(boolean csPreferred) {
+ List<Integer> preferredNetworks = new ArrayList<>();
+
+ List<Integer> domains = getDomainPreference();
+ int psPriority = domains.indexOf(DOMAIN_PS_3GPP);
+ int csPriority = domains.indexOf(DOMAIN_CS);
+ logi("getNextPreferredNetworks psPriority=" + psPriority + ", csPriority=" + csPriority
+ + ", csPreferred=" + csPreferred
+ + ", lastNetworkType=" + accessNetworkTypeToString(mLastNetworkType));
+
+ if (!csPreferred && mLastNetworkType == UNKNOWN) {
+ // Generate the list per the domain preference.
+
+ if (psPriority == NOT_SUPPORTED && csPriority == NOT_SUPPORTED) {
+ // should not reach here.
+ } else if (psPriority == NOT_SUPPORTED && csPriority > NOT_SUPPORTED) {
+ // CS networks only.
+ preferredNetworks = generatePreferredNetworks(getCsNetworkTypeConfiguration());
+ } else if (psPriority > NOT_SUPPORTED && csPriority == NOT_SUPPORTED) {
+ // PS networks only.
+ preferredNetworks = generatePreferredNetworks(getImsNetworkTypeConfiguration());
+ } else if (psPriority < csPriority) {
+ // PS preferred.
+ preferredNetworks = generatePreferredNetworks(getImsNetworkTypeConfiguration(),
+ getCsNetworkTypeConfiguration());
+ } else {
+ // CS preferred.
+ generatePreferredNetworks(getCsNetworkTypeConfiguration(),
+ getImsNetworkTypeConfiguration());
+ }
+ } else if (csPreferred || mLastNetworkType == EUTRAN || mLastNetworkType == NGRAN) {
+ if (!csPreferred && mLastNetworkType == NGRAN && mLtePreferredAfterNrFailure) {
+ // LTE is preferred after dialing over NR failed.
+ List<Integer> imsRats = getImsNetworkTypeConfiguration();
+ imsRats.remove(new Integer(NGRAN));
+ preferredNetworks = generatePreferredNetworks(imsRats,
+ getCsNetworkTypeConfiguration());
+ } else if (csPriority > NOT_SUPPORTED) {
+ // PS tried, generate the list with CS preferred.
+ preferredNetworks = generatePreferredNetworks(getCsNetworkTypeConfiguration(),
+ getImsNetworkTypeConfiguration());
+ } else {
+ // CS not suppored.
+ generatePreferredNetworks(getImsNetworkTypeConfiguration());
+ }
+ } else {
+ // CS tried, generate the list with PS preferred.
+ if (psPriority > NOT_SUPPORTED) {
+ preferredNetworks = generatePreferredNetworks(getImsNetworkTypeConfiguration(),
+ getCsNetworkTypeConfiguration());
+ } else {
+ // PS not suppored.
+ preferredNetworks = generatePreferredNetworks(getCsNetworkTypeConfiguration());
+ }
+ }
+
+ return preferredNetworks;
+ }
+
+ private @RadioAccessNetworkType List<Integer> generatePreferredNetworks(List<Integer>...lists) {
+ List<Integer> preferredNetworks = new ArrayList<>();
+ for (List<Integer> list : lists) {
+ preferredNetworks.addAll(list);
+ }
+
+ return preferredNetworks;
+ }
+
+ private void handleNetworkScanTimeout() {
+ if (isImsRegisteredWithVoiceCapability()
+ && isImsRegisteredOverWifi()) {
+ if (mCancelSignal != null) {
+ mCancelSignal.cancel();
+ mCancelSignal = null;
+ }
+ onWlanSelected();
+ }
+ }
+
+ /**
+ * Determines whether CS is in service.
+ *
+ * @return {@code true} if CS is in service.
+ */
+ private boolean isCsInService() {
+ EmergencyRegResult regResult = mSelectionAttributes.getEmergencyRegResult();
+ if (regResult == null) return false;
+
+ int regState = regResult.getRegState();
+ int domain = regResult.getDomain();
+
+ if ((regState == REGISTRATION_STATE_HOME || regState == REGISTRATION_STATE_ROAMING)
+ && ((domain & NetworkRegistrationInfo.DOMAIN_CS) > 0)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines the network type of the circuit-switched(CS) network.
+ *
+ * @return The network type of the CS network.
+ */
+ private @RadioAccessNetworkType int getSelectableCsNetworkType() {
+ EmergencyRegResult regResult = mSelectionAttributes.getEmergencyRegResult();
+ logi("getSelectableCsNetworkType regResult=" + regResult);
+ if (regResult == null) return UNKNOWN;
+
+ int accessNetwork = regResult.getAccessNetwork();
+
+ List<Integer> ratList = getCsNetworkTypeConfiguration();
+ if (ratList.contains(accessNetwork)) {
+ return accessNetwork;
+ }
+
+ if ((regResult.getAccessNetwork() == EUTRAN)
+ && ((regResult.getDomain() & NetworkRegistrationInfo.DOMAIN_CS) > 0)) {
+ return UTRAN;
+ }
+
+ return UNKNOWN;
+ }
+
+ /**
+ * Determines whether PS is in service.
+ *
+ * @return {@code true} if PS is in service.
+ */
+ private boolean isPsInService() {
+ EmergencyRegResult regResult = mSelectionAttributes.getEmergencyRegResult();
+ if (regResult == null) return false;
+
+ int regState = regResult.getRegState();
+ int domain = regResult.getDomain();
+
+ if ((regState == REGISTRATION_STATE_HOME || regState == REGISTRATION_STATE_ROAMING)
+ && ((domain & NetworkRegistrationInfo.DOMAIN_PS) > 0)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines the network type supporting emergency services over packet-switched(PS) network.
+ *
+ * @param inService Indicates whether PS is IN_SERVICE state.
+ * @return The network type if the network supports emergency services over PS network.
+ */
+ private @RadioAccessNetworkType int getSelectablePsNetworkType(boolean inService) {
+ EmergencyRegResult regResult = mSelectionAttributes.getEmergencyRegResult();
+ logi("getSelectablePsNetworkType regResult=" + regResult);
+ if (regResult == null) return UNKNOWN;
+
+ int accessNetwork = regResult.getAccessNetwork();
+ List<Integer> ratList = getImsNetworkTypeConfiguration();
+ if (ratList.contains(accessNetwork)) {
+ if (mIsEmergencyBarred) {
+ logi("sgetSelectablePsNetworkType barred");
+ return UNKNOWN;
+ }
+ if (accessNetwork == NGRAN) {
+ return (regResult.getNwProvidedEmc() > 0 && regResult.isVopsSupported())
+ ? NGRAN : UNKNOWN;
+ } else if (accessNetwork == EUTRAN) {
+ return (regResult.isEmcBearerSupported()
+ && (regResult.isVopsSupported() || !inService))
+ ? EUTRAN : UNKNOWN;
+ }
+ }
+
+ return UNKNOWN;
+ }
+
+ /**
+ * Determines whether the SIM is a deactivated one.
+ *
+ * @return {@code true} if the SIM is a deactivated one.
+ */
+ private boolean isDeactivatedSim() {
+ if (SubscriptionManager.isValidSubscriptionId(getSubId())) {
+ TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+ tm = tm.createForSubscriptionId(getSubId());
+ int state = tm.getDataActivationState();
+ logi("isDeactivatedSim state=" + state);
+ return (state == TelephonyManager.SIM_ACTIVATION_STATE_DEACTIVATED);
+ }
+ return false;
+ }
+
+ /**
+ * Determines whether emergency call over Wi-Fi is allowed.
+ *
+ * @return {@code true} if emergency call over Wi-Fi allowed.
+ */
+ private boolean isEmcOverWifiSupported() {
+ if (SubscriptionManager.isValidSubscriptionId(getSubId())) {
+ List<Integer> domains = getDomainPreference();
+ return domains.contains(DOMAIN_PS_NON_3GPP);
+ }
+ return false;
+ }
+
+ /**
+ * Determines whether Wi-Fi is preferred when IMS registered over Wi-Fi.
+ *
+ * @return {@code true} if Wi-Fi is preferred when IMS registered over Wi-Fi.
+ */
+ private boolean isWifiPreferred() {
+ if (SubscriptionManager.isValidSubscriptionId(getSubId())) {
+ List<Integer> domains = getDomainPreference();
+ int priority = domains.indexOf(DOMAIN_PS_NON_3GPP);
+ logi("isWifiPreferred priority=" + priority);
+
+ if ((priority == 0)
+ && isImsRegisteredWithVoiceCapability()
+ && isImsRegisteredOverWifi()) {
+ logi("isWifiPreferred try emergency call over Wi-Fi");
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isAdvancedCallingSettingEnabled() {
+ try {
+ if (SubscriptionManager.isValidSubscriptionId(getSubId())) {
+ ImsManager imsMngr = mContext.getSystemService(ImsManager.class);
+ ImsMmTelManager mmTelManager = imsMngr.getImsMmTelManager(getSubId());
+ boolean result = mmTelManager.isAdvancedCallingSettingEnabled();
+ logi("isAdvancedCallingSettingEnabled " + result);
+ return result;
+ }
+ } catch (Exception e) {
+ logi("isAdvancedCallingSettingEnabled e=" + e);
+ }
+ return true;
+ }
+
+ private @NonNull List<Integer> getImsNetworkTypeConfiguration() {
+ int[] rats = mImsRatsConfig;
+ if (isInRoaming()) rats = mImsRoamRatsConfig;
+
+ List<Integer> ratList = new ArrayList<Integer>();
+ for (int i = 0; i < rats.length; i++) {
+ ratList.add(rats[i]);
+ }
+ return ratList;
+ }
+
+ private @NonNull List<Integer> getCsNetworkTypeConfiguration() {
+ int[] rats = mCsRatsConfig;
+ if (isInRoaming()) rats = mCsRoamRatsConfig;
+
+ List<Integer> ratList = new ArrayList<Integer>();
+ for (int i = 0; i < rats.length; i++) {
+ ratList.add(rats[i]);
+ }
+
+ if (!mCdmaPreferredNumbers.isEmpty()) {
+ if (mCdmaPreferredNumbers.contains(mSelectionAttributes.getNumber())) {
+ // The number will be dialed over CDMA.
+ ratList.clear();
+ ratList.add(new Integer(CDMA2000));
+ } else {
+ // The number will be dialed over UTRAN or GERAN.
+ ratList.remove(new Integer(CDMA2000));
+ }
+ }
+
+ return ratList;
+ }
+
+ private @NonNull List<Integer> getDomainPreference() {
+ int[] domains = mDomainPreference;
+ if (isInRoaming()) domains = mDomainPreferenceRoam;
+
+ List<Integer> domainList = new ArrayList<Integer>();
+ for (int i = 0; i < domains.length; i++) {
+ domainList.add(domains[i]);
+ }
+ return domainList;
+ }
+
+ private boolean isInRoaming() {
+ if (!SubscriptionManager.isValidSubscriptionId(getSubId())) return false;
+
+ TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+ tm = tm.createForSubscriptionId(getSubId());
+ String netIso = tm.getNetworkCountryIso();
+
+ EmergencyRegResult regResult = mSelectionAttributes.getEmergencyRegResult();
+ if (regResult != null) {
+ if (regResult.getRegState() == REGISTRATION_STATE_HOME) return false;
+ if (regResult.getRegState() == REGISTRATION_STATE_ROAMING) return true;
+
+ String iso = regResult.getIso();
+ if (!TextUtils.isEmpty(iso)) netIso = iso;
+ }
+
+ String simIso = tm.getSimCountryIso();
+ logi("isInRoaming simIso=" + simIso + ", netIso=" + netIso);
+
+ if (TextUtils.isEmpty(simIso)) return false;
+ if (TextUtils.isEmpty(netIso)) return false;
+
+ return !(TextUtils.equals(simIso, netIso));
+ }
+
+ /**
+ * Determines whether IMS is registered over Wi-Fi.
+ *
+ * @return {@code true} if IMS is registered over Wi-Fi.
+ */
+ private boolean isImsRegisteredOverWifi() {
+ boolean ret = false;
+ if (SubscriptionManager.isValidSubscriptionId(getSubId())) {
+ ret = mImsStateTracker.isImsRegisteredOverWlan();
+ }
+
+ logi("isImsRegisteredOverWifi " + ret);
+ return ret;
+ }
+
+ /**
+ * Determines whether IMS is registered with voice capability.
+ *
+ * @return {@code true} if IMS is registered with voice capability.
+ */
+ private boolean isImsRegisteredWithVoiceCapability() {
+ boolean ret = mImsRegistered && mIsVoiceCapable;
+
+ logi("isImsRegisteredWithVoiceCapability " + ret);
+ return ret;
+ }
+
+ private void onWlanSelected() {
+ logi("onWlanSelected");
+ mLastTransportType = TRANSPORT_TYPE_WLAN;
+ mVoWifiTrialCount++;
+ mTransportSelectorCallback.onWlanSelected();
+ mWwanSelectorCallback = null;
+ }
+
+ private void onWwanSelected(Runnable runnable) {
+ logi("onWwanSelected");
+ if (mLastTransportType == TRANSPORT_TYPE_WWAN
+ && mWwanSelectorCallback != null) {
+ logi("onWwanSelected already notified");
+ runnable.run();
+ return;
+ }
+
+ mLastTransportType = TRANSPORT_TYPE_WWAN;
+ mTransportSelectorCallback.onWwanSelected((callback) -> {
+ mWwanSelectorCallback = callback;
+ runnable.run();
+ });
+ }
+
+ private void onWwanNetworkTypeSelected(@RadioAccessNetworkType int accessNetworkType) {
+ logi("onWwanNetworkTypeSelected " + accessNetworkTypeToString(accessNetworkType));
+ if (mWwanSelectorCallback == null) {
+ logi("onWwanNetworkTypeSelected callback is null");
+ return;
+ }
+
+ mDomainSelected = true;
+ mLastNetworkType = accessNetworkType;
+ int domain = NetworkRegistrationInfo.DOMAIN_CS;
+ if (accessNetworkType == EUTRAN || accessNetworkType == NGRAN) {
+ domain = NetworkRegistrationInfo.DOMAIN_PS;
+ }
+ mWwanSelectorCallback.onDomainSelected(domain);
+ }
+
+ private static String arrayToString(int[] intArray, IntFunction<String> func) {
+ int length = intArray.length;
+ StringBuilder sb = new StringBuilder("{");
+ if (length > 0) {
+ int i = 0;
+ sb.append(func.apply(intArray[i++]));
+ while (i < length) {
+ sb.append(", ").append(func.apply(intArray[i++]));
+ }
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ private static String arrayToString(String[] stringArray) {
+ StringBuilder sb;
+ int length = stringArray.length;
+ sb = new StringBuilder("{");
+ if (length > 0) {
+ int i = 0;
+ sb.append(stringArray[i++]);
+ while (i < length) {
+ sb.append(", ").append(stringArray[i++]);
+ }
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ private static String domainPreferenceToString(
+ @CarrierConfigManager.ImsEmergency.EmergencyDomain int domain) {
+ switch (domain) {
+ case DOMAIN_CS: return "CS";
+ case DOMAIN_PS_3GPP: return "PS_3GPP";
+ case DOMAIN_PS_NON_3GPP: return "PS_NON_3GPP";
+ default: return "UNKNOWN";
+ }
+ }
+
+ private static String carrierConfigNetworkScanTypeToString(
+ @CarrierConfigManager.ImsEmergency.EmergencyScanType int scanType) {
+ switch (scanType) {
+ case CarrierConfigManager.ImsEmergency.SCAN_TYPE_NO_PREFERENCE: return "NO_PREF";
+ case CarrierConfigManager.ImsEmergency.SCAN_TYPE_FULL_SERVICE: return "FULL";
+ case SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE: return "FULL_N_LIMITED";
+ default: return "UNKNOWN";
+ }
+ }
+
+ private static String accessNetworkTypeToString(
+ @RadioAccessNetworkType int accessNetworkType) {
+ switch (accessNetworkType) {
+ case AccessNetworkType.UNKNOWN: return "UNKNOWN";
+ case AccessNetworkType.GERAN: return "GERAN";
+ case AccessNetworkType.UTRAN: return "UTRAN";
+ case AccessNetworkType.EUTRAN: return "EUTRAN";
+ case AccessNetworkType.CDMA2000: return "CDMA2000";
+ case AccessNetworkType.IWLAN: return "IWLAN";
+ case AccessNetworkType.NGRAN: return "NGRAN";
+ default: return Integer.toString(accessNetworkType);
+ }
+ }
+
+ /**
+ * Destroys the instance.
+ */
+ @VisibleForTesting
+ public void destroy() {
+ if (DBG) logd("destroy");
+
+ releaseWakeLock();
+
+ mDestroyed = true;
+ mImsStateTracker.removeBarringInfoListener(this);
+ mImsStateTracker.removeImsStateListener(this);
+
+ super.destroy();
+ }
+
+ private void acquireWakeLock() {
+ if (mPartialWakeLock != null) {
+ synchronized (mPartialWakeLock) {
+ logi("acquireWakeLock");
+ mPartialWakeLock.acquire();
+ }
+ }
+ }
+
+ private void releaseWakeLock() {
+ if (mPartialWakeLock != null) {
+ synchronized (mPartialWakeLock) {
+ if (mPartialWakeLock.isHeld()) {
+ logi("releaseWakeLock");
+ mPartialWakeLock.release();
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void logi(String msg) {
+ super.logi(msg);
+ sLocalLog.log(msg);
+ }
+
+ @Override
+ protected void loge(String msg) {
+ super.loge(msg);
+ sLocalLog.log(msg);
+ }
+}
diff --git a/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelector.java b/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelector.java
new file mode 100644
index 0000000..cd588e1
--- /dev/null
+++ b/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelector.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 android.annotation.NonNull;
+import android.content.Context;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.BarringInfo;
+import android.telephony.CarrierConfigManager;
+import android.telephony.DataSpecificRegistrationInfo;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.telephony.VopsSupportInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Implements an emergency SMS domain selector for sending an emergency SMS.
+ */
+public class EmergencySmsDomainSelector extends SmsDomainSelector implements
+ ImsStateTracker.BarringInfoListener, ImsStateTracker.ServiceStateListener {
+ /**
+ * Stores the configuration value of
+ * {@link CarrierConfigManager#KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL}.
+ * This value is always updated whenever the domain selection is requested.
+ */
+ private Boolean mEmergencySmsOverImsSupportedByConfig;
+ private ServiceState mServiceState;
+ private boolean mServiceStateReceived;
+ private BarringInfo mBarringInfo;
+ private boolean mBarringInfoReceived;
+
+ public EmergencySmsDomainSelector(Context context, int slotId, int subId,
+ @NonNull Looper looper, @NonNull ImsStateTracker imsStateTracker,
+ @NonNull DestroyListener listener) {
+ super(context, slotId, subId, looper, imsStateTracker, listener,
+ "DomainSelector-EmergencySMS");
+
+ mImsStateTracker.addServiceStateListener(this);
+ mImsStateTracker.addBarringInfoListener(this);
+ }
+
+ @Override
+ public void destroy() {
+ if (mDestroyed) {
+ return;
+ }
+ mImsStateTracker.removeServiceStateListener(this);
+ mImsStateTracker.removeBarringInfoListener(this);
+ super.destroy();
+ }
+
+ @Override
+ public void finishSelection() {
+ super.finishSelection();
+ mServiceStateReceived = false;
+ mServiceState = null;
+ mBarringInfoReceived = false;
+ mBarringInfo = null;
+ mEmergencySmsOverImsSupportedByConfig = null;
+ }
+
+ @Override
+ public void onBarringInfoUpdated(BarringInfo barringInfo) {
+ mBarringInfoReceived = true;
+ mBarringInfo = barringInfo;
+ sendMessageForDomainSelection();
+ }
+
+ @Override
+ public void onServiceStateUpdated(ServiceState serviceState) {
+ mServiceStateReceived = true;
+ mServiceState = serviceState;
+ sendMessageForDomainSelection();
+ }
+
+ /**
+ * Checks whether the domain selector is ready to select the domain or not.
+ * The emergency SMS requires to be updated for the {@link ServiceState} and
+ * {@link BarringInfo} to confirm that the cellular network supports to send emergency SMS
+ * messages over IMS.
+ */
+ @VisibleForTesting
+ public boolean isDomainSelectionReady() {
+ return mServiceStateReceived && mBarringInfoReceived;
+ }
+
+ @Override
+ protected boolean isSmsOverImsAvailable() {
+ if (super.isSmsOverImsAvailable()) {
+ /**
+ * Even though IMS is successfully registered, the cellular domain should be
+ * available for the emergency SMS according to the carrier's requirement
+ * when {@link CarrierConfigManager#KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL} is set
+ * to true.
+ */
+ if (isEmergencySmsOverImsSupportedByConfig()) {
+ /**
+ * Emergency SMS should be supported via emergency PDN.
+ * If this condition is false, then need to fallback to CS network
+ * because the current PS network does not allow the emergency service.
+ */
+ return isNetworkAvailableForImsEmergencySms();
+ }
+
+ // Emergency SMS is supported via IMS PDN.
+ return true;
+ }
+
+ return isImsEmergencySmsAvailable();
+ }
+
+ @Override
+ protected void selectDomain() {
+ if (!isDomainSelectionRequested()) {
+ logi("Domain selection is not requested!");
+ return;
+ }
+
+ if (!isDomainSelectionReady()) {
+ logd("Wait for the readiness of the domain selection!");
+ return;
+ }
+
+ logi("selectDomain: " + mImsStateTracker.imsStateToString());
+
+ if (isSmsOverImsAvailable()) {
+ if (mImsStateTracker.isImsRegisteredOverWlan()) {
+ if (!isEmergencySmsOverImsSupportedByConfig()) {
+ notifyWlanSelected();
+ return;
+ }
+
+ /**
+ * When {@link CarrierConfigManager#KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL}
+ * is set to true, the emergency SMS supports on the LTE network using the
+ * emergency PDN. So, considering EUTRAN only at this point.
+ */
+ logi("DomainSelected: WLAN >> WWAN");
+ }
+ notifyWwanSelected(NetworkRegistrationInfo.DOMAIN_PS);
+ } else {
+ notifyWwanSelected(NetworkRegistrationInfo.DOMAIN_CS);
+ }
+ }
+
+ /**
+ * Checks if the emergency SMS messages over IMS is available according to the carrier
+ * configuration and the current network states.
+ */
+ private boolean isImsEmergencySmsAvailable() {
+ boolean emergencySmsOverImsSupportedByConfig = isEmergencySmsOverImsSupportedByConfig();
+ boolean networkAvailable = isNetworkAvailableForImsEmergencySms();
+
+ logi("isImsEmergencySmsAvailable: "
+ + "emergencySmsOverIms=" + emergencySmsOverImsSupportedByConfig
+ + ", mmTelFeatureAvailable=" + mImsStateTracker.isMmTelFeatureAvailable()
+ + ", networkAvailable=" + networkAvailable);
+
+ return emergencySmsOverImsSupportedByConfig
+ && mImsStateTracker.isMmTelFeatureAvailable()
+ && networkAvailable;
+ }
+
+ /**
+ * Checks if sending emergency SMS messages over IMS is supported when in LTE/limited LTE
+ * (Emergency only) service mode from the carrier configuration.
+ */
+ private boolean isEmergencySmsOverImsSupportedByConfig() {
+ if (mEmergencySmsOverImsSupportedByConfig == null) {
+ CarrierConfigManager ccm = mContext.getSystemService(CarrierConfigManager.class);
+
+ if (ccm == null) {
+ loge("CarrierConfigManager is null");
+ return false;
+ }
+
+ PersistableBundle b = ccm.getConfigForSubId(getSubId());
+
+ if (b == null) {
+ loge("PersistableBundle is null");
+ return false;
+ }
+
+ mEmergencySmsOverImsSupportedByConfig = b.getBoolean(
+ CarrierConfigManager.KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL);
+ }
+
+ return mEmergencySmsOverImsSupportedByConfig;
+ }
+
+ /**
+ * Checks if the emergency service is available in the LTE service mode.
+ */
+ private boolean isLteEmergencyAvailableInService() {
+ if (mServiceState == null) {
+ return false;
+ }
+
+ final NetworkRegistrationInfo regInfo = mServiceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+ if (regInfo != null
+ && regInfo.getAccessNetworkTechnology() == TelephonyManager.NETWORK_TYPE_LTE
+ && regInfo.isRegistered()) {
+ return isEmergencyServiceSupported(regInfo) && isEmergencyServiceAllowed();
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the emergency service is available in the limited LTE service(Emergency only) mode.
+ */
+ private boolean isLteEmergencyAvailableInLimitedService() {
+ if (mServiceState == null) {
+ return false;
+ }
+
+ final NetworkRegistrationInfo regInfo = mServiceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+ if (regInfo != null
+ && regInfo.getAccessNetworkTechnology() == TelephonyManager.NETWORK_TYPE_LTE
+ && regInfo.isEmergencyEnabled()) {
+ return isEmergencyServiceSupported(regInfo) && isEmergencyServiceAllowed();
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the network is available for the IMS emergency SMS.
+ */
+ private boolean isNetworkAvailableForImsEmergencySms() {
+ return isLteEmergencyAvailableInService()
+ || isLteEmergencyAvailableInLimitedService();
+ }
+
+ /**
+ * Checks if the emergency service is supported by the network.
+ *
+ * This checks if "Emergency bearer services indicator (EMC-BS)" field (bits) set to
+ * the "Emergency bearer services in S1 mode supported".
+ *
+ * @return {@code true} if the emergency service is supported by the network,
+ * {@code false} otherwise.
+ */
+ private boolean isEmergencyServiceSupported(@NonNull NetworkRegistrationInfo regInfo) {
+ final DataSpecificRegistrationInfo dsRegInfo = regInfo.getDataSpecificInfo();
+ if (dsRegInfo != null) {
+ final VopsSupportInfo vopsSupportInfo = dsRegInfo.getVopsSupportInfo();
+ return vopsSupportInfo != null
+ && vopsSupportInfo.isEmergencyServiceSupported();
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the emergency service is allowed (not barred) by the network.
+ *
+ * This checks if SystemInformationBlockType2 includes the ac-BarringInfo and
+ * with the ac-BarringForEmergency set to FALSE or
+ * if the SystemInformationBlockType2 does not include the ac-BarringInfo.
+ *
+ * @return {@code true} if the emergency service is allowed by the network,
+ * {@code false} otherwise.
+ */
+ private boolean isEmergencyServiceAllowed() {
+ if (mBarringInfo == null) {
+ return true;
+ }
+ final BarringInfo.BarringServiceInfo bsi =
+ mBarringInfo.getBarringServiceInfo(BarringInfo.BARRING_SERVICE_TYPE_EMERGENCY);
+ return !bsi.isBarred();
+ }
+}
diff --git a/src/com/android/services/telephony/domainselection/ImsStateTracker.java b/src/com/android/services/telephony/domainselection/ImsStateTracker.java
new file mode 100644
index 0000000..e1d0d31
--- /dev/null
+++ b/src/com/android/services/telephony/domainselection/ImsStateTracker.java
@@ -0,0 +1,855 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 android.annotation.NonNull;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.BarringInfo;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsManager;
+import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
+import android.telephony.ims.ImsStateCallback;
+import android.telephony.ims.ImsStateCallback.DisconnectedReason;
+import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
+import android.util.Log;
+
+import com.android.internal.annotations.Keep;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A class for tracking the IMS related information like IMS registration state, MMTEL capabilities.
+ * And, it also tracks the {@link ServiceState} and {@link BarringInfo} to identify the current
+ * network state to which the device is attached.
+ */
+@Keep
+public class ImsStateTracker {
+ /**
+ * A listener used to be notified of the {@link ServiceState} change.
+ */
+ public interface ServiceStateListener {
+ /**
+ * Called when the {@link ServiceState} is updated.
+ */
+ void onServiceStateUpdated(ServiceState serviceState);
+ }
+
+ /**
+ * A listener used to be notified of the {@link BarringInfo} change.
+ */
+ public interface BarringInfoListener {
+ /**
+ * Called when the {@link BarringInfo} is updated.
+ */
+ void onBarringInfoUpdated(BarringInfo barringInfo);
+ }
+
+ /**
+ * A listener used to be notified of the change for MMTEL connection state, IMS registration
+ * state, and MMTEL capabilities.
+ */
+ public interface ImsStateListener {
+ /**
+ * Called when MMTEL feature connection state is changed.
+ */
+ void onImsMmTelFeatureAvailableChanged();
+
+ /**
+ * Called when IMS registration state is changed.
+ */
+ void onImsRegistrationStateChanged();
+
+ /**
+ * Called when MMTEL capability is changed - IMS is registered
+ * and the service is currently available over IMS.
+ */
+ void onImsMmTelCapabilitiesChanged();
+ }
+
+ private static final String TAG = ImsStateTracker.class.getSimpleName();
+ /**
+ * When MMTEL feature connection is unavailable temporarily,
+ * the IMS state will be set to unavailable after waiting for this time.
+ */
+ @VisibleForTesting
+ protected static final long MMTEL_FEATURE_AVAILABLE_WAIT_TIME_MILLIS = 1000; // 1 seconds
+
+ // Persistent Logging
+ private final LocalLog mEventLog = new LocalLog(30);
+ private final Context mContext;
+ private final int mSlotId;
+ private final Handler mHandler;
+ private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+ /** For tracking the ServiceState and its related listeners. */
+ private ServiceState mServiceState;
+ private final Set<ServiceStateListener> mServiceStateListeners = new ArraySet<>(2);
+
+ /** For tracking the BarringInfo and its related listeners. */
+ private BarringInfo mBarringInfo;
+ private final Set<BarringInfoListener> mBarringInfoListeners = new ArraySet<>(2);
+
+ /** For tracking IMS states and callbacks. */
+ private final Set<ImsStateListener> mImsStateListeners = new ArraySet<>(5);
+ private ImsMmTelManager mMmTelManager;
+ private ImsStateCallback mImsStateCallback;
+ private RegistrationManager.RegistrationCallback mImsRegistrationCallback;
+ private ImsMmTelManager.CapabilityCallback mMmTelCapabilityCallback;
+ /** The availability of MmTelFeature. */
+ private Boolean mMmTelFeatureAvailable;
+ /** The IMS registration state and the network type that performed IMS registration. */
+ private Boolean mImsRegistered;
+ private @RadioAccessNetworkType int mImsAccessNetworkType = AccessNetworkType.UNKNOWN;
+ /** The MMTEL capabilities - Voice, Video, SMS, and Ut. */
+ private MmTelCapabilities mMmTelCapabilities;
+ private final Runnable mMmTelFeatureUnavailableRunnable = new Runnable() {
+ @Override
+ public void run() {
+ setImsStateAsUnavailable();
+ notifyImsMmTelFeatureAvailableChanged();
+ }
+ };
+
+ public ImsStateTracker(@NonNull Context context, int slotId, @NonNull Looper looper) {
+ mContext = context;
+ mSlotId = slotId;
+ mHandler = new Handler(looper);
+ }
+
+ /**
+ * Destroys this tracker.
+ */
+ public void destroy() {
+ stopListeningForImsState();
+ mHandler.removeCallbacksAndMessages(null);
+ }
+
+ /**
+ * Returns the slot index for this tracker.
+ */
+ public int getSlotId() {
+ return mSlotId;
+ }
+
+ /**
+ * Returns the current subscription index for this tracker.
+ */
+ public int getSubId() {
+ return mSubId;
+ }
+
+ /**
+ * Returns the Handler instance of this tracker.
+ */
+ @VisibleForTesting
+ public @NonNull Handler getHandler() {
+ return mHandler;
+ }
+
+ /**
+ * Starts monitoring the IMS states with the specified subscription.
+ * This method will be called whenever the subscription index for this tracker is changed.
+ * If the subscription index for this tracker is same as previously set, it will be ignored.
+ *
+ * @param subId The subscription index to be started.
+ */
+ public void start(int subId) {
+ if (mSubId == subId) {
+ if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
+ setImsStateAsUnavailable();
+ return;
+ } else if (mImsStateCallback != null) {
+ // If start() is called with the same subscription index and the ImsStateCallback
+ // was already registered, we don't need to unregister and register this callback
+ // again. So, this request should be ignored if the subscription index is same.
+ logd("start: ignored for same subscription(" + mSubId + ")");
+ return;
+ }
+ } else {
+ logi("start: subscription changed from " + mSubId + " to " + subId);
+ mSubId = subId;
+ }
+
+ stopListeningForImsState();
+ startListeningForImsState();
+ }
+
+ /**
+ * Updates the service state of the network to which the device is currently attached.
+ * This method should be run on the same thread as the Handler.
+ *
+ * @param serviceState The {@link ServiceState} to be updated.
+ */
+ public void updateServiceState(ServiceState serviceState) {
+ mServiceState = serviceState;
+
+ for (ServiceStateListener listener : mServiceStateListeners) {
+ listener.onServiceStateUpdated(serviceState);
+ }
+ }
+
+ /**
+ * Adds a listener to be notified of the {@link ServiceState} change.
+ * The newly added listener is notified if the current {@link ServiceState} is present.
+ *
+ * @param listener The listener to be added.
+ */
+ public void addServiceStateListener(@NonNull ServiceStateListener listener) {
+ mServiceStateListeners.add(listener);
+
+ final ServiceState serviceState = mServiceState;
+ if (serviceState != null) {
+ mHandler.post(() -> notifyServiceStateUpdated(listener, serviceState));
+ }
+ }
+
+ /**
+ * Removes a listener to be notified of the {@link ServiceState} change.
+ *
+ * @param listener The listener to be removed.
+ */
+ public void removeServiceStateListener(@NonNull ServiceStateListener listener) {
+ mServiceStateListeners.remove(listener);
+ }
+
+ /**
+ * Notifies the specified listener of a change to {@link ServiceState}.
+ *
+ * @param listener The listener to be notified.
+ * @param serviceState The {@link ServiceState} to be reported.
+ */
+ private void notifyServiceStateUpdated(ServiceStateListener listener,
+ ServiceState serviceState) {
+ if (!mServiceStateListeners.contains(listener)) {
+ return;
+ }
+ listener.onServiceStateUpdated(serviceState);
+ }
+
+ /**
+ * Updates the barring information received from the network to which the device is currently
+ * attached.
+ * This method should be run on the same thread as the Handler.
+ *
+ * @param barringInfo The {@link BarringInfo} to be updated.
+ */
+ public void updateBarringInfo(BarringInfo barringInfo) {
+ mBarringInfo = barringInfo;
+
+ for (BarringInfoListener listener : mBarringInfoListeners) {
+ listener.onBarringInfoUpdated(barringInfo);
+ }
+ }
+
+ /**
+ * Adds a listener to be notified of the {@link BarringInfo} change.
+ * The newly added listener is notified if the current {@link BarringInfo} is present.
+ *
+ * @param listener The listener to be added.
+ */
+ public void addBarringInfoListener(@NonNull BarringInfoListener listener) {
+ mBarringInfoListeners.add(listener);
+
+ final BarringInfo barringInfo = mBarringInfo;
+ if (barringInfo != null) {
+ mHandler.post(() -> notifyBarringInfoUpdated(listener, barringInfo));
+ }
+ }
+
+ /**
+ * Removes a listener to be notified of the {@link BarringInfo} change.
+ *
+ * @param listener The listener to be removed.
+ */
+ public void removeBarringInfoListener(@NonNull BarringInfoListener listener) {
+ mBarringInfoListeners.remove(listener);
+ }
+
+ /**
+ * Notifies the specified listener of a change to {@link BarringInfo}.
+ *
+ * @param listener The listener to be notified.
+ * @param barringInfo The {@link BarringInfo} to be reported.
+ */
+ private void notifyBarringInfoUpdated(BarringInfoListener listener, BarringInfo barringInfo) {
+ if (!mBarringInfoListeners.contains(listener)) {
+ return;
+ }
+ listener.onBarringInfoUpdated(barringInfo);
+ }
+
+ /**
+ * Adds a listener to be notified of the IMS state change.
+ * If each state was already received from the IMS service, the newly added listener
+ * is notified once.
+ *
+ * @param listener The listener to be added.
+ */
+ public void addImsStateListener(@NonNull ImsStateListener listener) {
+ mImsStateListeners.add(listener);
+ mHandler.post(() -> notifyImsStateChangeIfValid(listener));
+ }
+
+ /**
+ * Removes a listener to be notified of the IMS state change.
+ *
+ * @param listener The listener to be removed.
+ */
+ public void removeImsStateListener(@NonNull ImsStateListener listener) {
+ mImsStateListeners.remove(listener);
+ }
+
+ /**
+ * Returns {@code true} if all IMS states are ready, {@code false} otherwise.
+ */
+ @VisibleForTesting
+ public boolean isImsStateReady() {
+ return mMmTelFeatureAvailable != null
+ && mImsRegistered != null
+ && mMmTelCapabilities != null;
+ }
+
+ /**
+ * Returns {@code true} if MMTEL feature connection is available, {@code false} otherwise.
+ */
+ public boolean isMmTelFeatureAvailable() {
+ return mMmTelFeatureAvailable != null && mMmTelFeatureAvailable;
+ }
+
+ /**
+ * Returns {@code true} if IMS is registered, {@code false} otherwise.
+ */
+ public boolean isImsRegistered() {
+ return mImsRegistered != null && mImsRegistered;
+ }
+
+ /**
+ * Returns {@code true} if IMS is registered over Wi-Fi (IWLAN), {@code false} otherwise.
+ */
+ public boolean isImsRegisteredOverWlan() {
+ return mImsAccessNetworkType == AccessNetworkType.IWLAN;
+ }
+
+ /**
+ * Returns {@code true} if IMS voice call is capable, {@code false} otherwise.
+ */
+ public boolean isImsVoiceCapable() {
+ return mMmTelCapabilities != null
+ && mMmTelCapabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_VOICE);
+ }
+
+ /**
+ * Returns {@code true} if IMS video call is capable, {@code false} otherwise.
+ */
+ public boolean isImsVideoCapable() {
+ return mMmTelCapabilities != null
+ && mMmTelCapabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_VIDEO);
+ }
+
+ /**
+ * Returns {@code true} if IMS SMS is capable, {@code false} otherwise.
+ */
+ public boolean isImsSmsCapable() {
+ return mMmTelCapabilities != null
+ && mMmTelCapabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_SMS);
+ }
+
+ /**
+ * Returns {@code true} if IMS UT is capable, {@code false} otherwise.
+ */
+ public boolean isImsUtCapable() {
+ return mMmTelCapabilities != null
+ && mMmTelCapabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_UT);
+ }
+
+ /**
+ * Returns the access network type to which IMS is registered.
+ */
+ public @RadioAccessNetworkType int getImsAccessNetworkType() {
+ return mImsAccessNetworkType;
+ }
+
+ /**
+ * Sets the IMS states to the initial values.
+ */
+ private void initImsState() {
+ mMmTelFeatureAvailable = null;
+ mImsRegistered = null;
+ mImsAccessNetworkType = AccessNetworkType.UNKNOWN;
+ mMmTelCapabilities = null;
+ }
+
+ /**
+ * Sets the IMS states to unavailable to notify the readiness of the IMS state
+ * when the subscription is not valid.
+ */
+ private void setImsStateAsUnavailable() {
+ logd("setImsStateAsUnavailable");
+ setMmTelFeatureAvailable(false);
+ setImsRegistered(false);
+ setImsAccessNetworkType(AccessNetworkType.UNKNOWN);
+ setMmTelCapabilities(new MmTelCapabilities());
+ }
+
+ private void setMmTelFeatureAvailable(boolean available) {
+ if (!Objects.equals(mMmTelFeatureAvailable, Boolean.valueOf(available))) {
+ logi("setMmTelFeatureAvailable: " + mMmTelFeatureAvailable + " >> " + available);
+ mMmTelFeatureAvailable = Boolean.valueOf(available);
+ }
+ }
+
+ private void setImsRegistered(boolean registered) {
+ if (!Objects.equals(mImsRegistered, Boolean.valueOf(registered))) {
+ logi("setImsRegistered: " + mImsRegistered + " >> " + registered);
+ mImsRegistered = Boolean.valueOf(registered);
+ }
+ }
+
+ private void setImsAccessNetworkType(int accessNetworkType) {
+ if (mImsAccessNetworkType != accessNetworkType) {
+ logi("setImsAccessNetworkType: " + accessNetworkTypeToString(mImsAccessNetworkType)
+ + " >> " + accessNetworkTypeToString(accessNetworkType));
+ mImsAccessNetworkType = accessNetworkType;
+ }
+ }
+
+ private void setMmTelCapabilities(@NonNull MmTelCapabilities capabilities) {
+ if (!Objects.equals(mMmTelCapabilities, capabilities)) {
+ logi("MMTEL capabilities: " + mMmTelCapabilities + " >> " + capabilities);
+ mMmTelCapabilities = capabilities;
+ }
+ }
+
+ /**
+ * Notifies the specified listener of the current IMS state if it's valid.
+ *
+ * @param listener The {@link ImsStateListener} to be notified.
+ */
+ private void notifyImsStateChangeIfValid(@NonNull ImsStateListener listener) {
+ if (!mImsStateListeners.contains(listener)) {
+ return;
+ }
+
+ if (mMmTelFeatureAvailable != null) {
+ listener.onImsMmTelFeatureAvailableChanged();
+ }
+
+ if (mImsRegistered != null) {
+ listener.onImsRegistrationStateChanged();
+ }
+
+ if (mMmTelCapabilities != null) {
+ listener.onImsMmTelCapabilitiesChanged();
+ }
+ }
+
+ /**
+ * Notifies the application that MMTEL feature connection state is changed.
+ */
+ private void notifyImsMmTelFeatureAvailableChanged() {
+ for (ImsStateListener l : mImsStateListeners) {
+ l.onImsMmTelFeatureAvailableChanged();
+ }
+ }
+
+ /**
+ * Notifies the application that IMS registration state is changed.
+ */
+ private void notifyImsRegistrationStateChanged() {
+ logi("ImsState: " + imsStateToString());
+ for (ImsStateListener l : mImsStateListeners) {
+ l.onImsRegistrationStateChanged();
+ }
+ }
+
+ /**
+ * Notifies the application that MMTEL capabilities is changed.
+ */
+ private void notifyImsMmTelCapabilitiesChanged() {
+ logi("ImsState: " + imsStateToString());
+ for (ImsStateListener l : mImsStateListeners) {
+ l.onImsMmTelCapabilitiesChanged();
+ }
+ }
+
+ /**
+ * Called when MMTEL feature connection state is available.
+ */
+ private void onMmTelFeatureAvailable() {
+ logd("onMmTelFeatureAvailable");
+ mHandler.removeCallbacks(mMmTelFeatureUnavailableRunnable);
+ setMmTelFeatureAvailable(true);
+ registerImsRegistrationCallback();
+ registerMmTelCapabilityCallback();
+ notifyImsMmTelFeatureAvailableChanged();
+ }
+
+ /**
+ * Called when MMTEL feature connection state is unavailable.
+ */
+ private void onMmTelFeatureUnavailable(@DisconnectedReason int reason) {
+ logd("onMmTelFeatureUnavailable: reason=" + disconnectedCauseToString(reason));
+
+ if (reason == ImsStateCallback.REASON_UNKNOWN_TEMPORARY_ERROR
+ || reason == ImsStateCallback.REASON_IMS_SERVICE_NOT_READY) {
+ // Wait for onAvailable for some times and
+ // if it's not available, the IMS state will be set to unavailable.
+ initImsState();
+ setMmTelFeatureAvailable(false);
+ mHandler.postDelayed(mMmTelFeatureUnavailableRunnable,
+ MMTEL_FEATURE_AVAILABLE_WAIT_TIME_MILLIS);
+ } else if (reason == ImsStateCallback.REASON_UNKNOWN_PERMANENT_ERROR
+ || reason == ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED) {
+ // Permanently blocked for this subscription.
+ setImsStateAsUnavailable();
+ notifyImsMmTelFeatureAvailableChanged();
+ } else if (reason == ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED) {
+ // Wait for onAvailable for some times and
+ // if it's not available, the IMS state will be set to unavailable.
+ initImsState();
+ setMmTelFeatureAvailable(false);
+ unregisterImsRegistrationCallback();
+ unregisterMmTelCapabilityCallback();
+ mHandler.postDelayed(mMmTelFeatureUnavailableRunnable,
+ MMTEL_FEATURE_AVAILABLE_WAIT_TIME_MILLIS);
+ } else if (reason == ImsStateCallback.REASON_SUBSCRIPTION_INACTIVE) {
+ // The {@link TelephonyDomainSelectionService} will call ImsStateTracker#start
+ // when the subscription changes to register new callbacks.
+ setImsStateAsUnavailable();
+ unregisterImsRegistrationCallback();
+ unregisterMmTelCapabilityCallback();
+ notifyImsMmTelFeatureAvailableChanged();
+ } else {
+ logw("onMmTelFeatureUnavailable: unexpected reason=" + reason);
+ }
+ }
+
+ /**
+ * Called when IMS is registered to the IMS network.
+ */
+ private void onImsRegistered(@NonNull ImsRegistrationAttributes attributes) {
+ logd("onImsRegistered: " + attributes);
+
+ setImsRegistered(true);
+ setImsAccessNetworkType(
+ imsRegTechToAccessNetworkType(attributes.getRegistrationTechnology()));
+ notifyImsRegistrationStateChanged();
+ }
+
+ /**
+ * Called when IMS is unregistered from the IMS network.
+ */
+ private void onImsUnregistered(@NonNull ImsReasonInfo info) {
+ logd("onImsUnregistered: " + info);
+ setImsRegistered(false);
+ setImsAccessNetworkType(AccessNetworkType.UNKNOWN);
+ setMmTelCapabilities(new MmTelCapabilities());
+ notifyImsRegistrationStateChanged();
+ }
+
+ /**
+ * Called when MMTEL capability is changed - IMS is registered
+ * and the service is currently available over IMS.
+ */
+ private void onMmTelCapabilitiesChanged(@NonNull MmTelCapabilities capabilities) {
+ logd("onMmTelCapabilitiesChanged: " + capabilities);
+ setMmTelCapabilities(capabilities);
+ notifyImsMmTelCapabilitiesChanged();
+ }
+
+ /**
+ * Starts listening to monitor the IMS states -
+ * connection state, IMS registration state, and MMTEL capabilities.
+ */
+ private void startListeningForImsState() {
+ if (!SubscriptionManager.isValidSubscriptionId(getSubId())) {
+ setImsStateAsUnavailable();
+ return;
+ }
+
+ ImsManager imsMngr = mContext.getSystemService(ImsManager.class);
+ mMmTelManager = imsMngr.getImsMmTelManager(getSubId());
+ initImsState();
+ registerImsStateCallback();
+ }
+
+ /**
+ * Stops listening to monitor the IMS states -
+ * connection state, IMS registration state, and MMTEL capabilities.
+ */
+ private void stopListeningForImsState() {
+ mHandler.removeCallbacks(mMmTelFeatureUnavailableRunnable);
+
+ if (mMmTelManager != null) {
+ unregisterMmTelCapabilityCallback();
+ unregisterImsRegistrationCallback();
+ unregisterImsStateCallback();
+ mMmTelManager = null;
+ }
+ }
+
+ private void registerImsStateCallback() {
+ if (mImsStateCallback != null) {
+ loge("ImsStateCallback is already registered for sub-" + getSubId());
+ return;
+ }
+ /**
+ * Listens to the IMS connection state change.
+ */
+ mImsStateCallback = new ImsStateCallback() {
+ @Override
+ public void onUnavailable(@DisconnectedReason int reason) {
+ onMmTelFeatureUnavailable(reason);
+ }
+
+ @Override
+ public void onAvailable() {
+ onMmTelFeatureAvailable();
+ }
+
+ @Override
+ public void onError() {
+ // This case will not be happened because this domain selection service
+ // is running on the Telephony service.
+ }
+ };
+
+ try {
+ mMmTelManager.registerImsStateCallback(mHandler::post, mImsStateCallback);
+ } catch (ImsException e) {
+ loge("Exception when registering ImsStateCallback: " + e);
+ mImsStateCallback = null;
+ }
+ }
+
+ private void unregisterImsStateCallback() {
+ if (mImsStateCallback != null) {
+ try {
+ mMmTelManager.unregisterImsStateCallback(mImsStateCallback);
+ } catch (Exception ignored) {
+ // Ignore the runtime exception while unregistering callback.
+ logd("Exception when unregistering ImsStateCallback: " + ignored);
+ }
+ mImsStateCallback = null;
+ }
+ }
+
+ private void registerImsRegistrationCallback() {
+ if (mImsRegistrationCallback != null) {
+ logd("RegistrationCallback is already registered for sub-" + getSubId());
+ return;
+ }
+ /**
+ * Listens to the IMS registration state change.
+ */
+ mImsRegistrationCallback = new RegistrationManager.RegistrationCallback() {
+ @Override
+ public void onRegistered(@NonNull ImsRegistrationAttributes attributes) {
+ onImsRegistered(attributes);
+ }
+
+ @Override
+ public void onUnregistered(@NonNull ImsReasonInfo info) {
+ onImsUnregistered(info);
+ }
+ };
+
+ try {
+ mMmTelManager.registerImsRegistrationCallback(mHandler::post, mImsRegistrationCallback);
+ } catch (ImsException e) {
+ loge("Exception when registering RegistrationCallback: " + e);
+ mImsRegistrationCallback = null;
+ }
+ }
+
+ private void unregisterImsRegistrationCallback() {
+ if (mImsRegistrationCallback != null) {
+ try {
+ mMmTelManager.unregisterImsRegistrationCallback(mImsRegistrationCallback);
+ } catch (Exception ignored) {
+ // Ignore the runtime exception while unregistering callback.
+ logd("Exception when unregistering RegistrationCallback: " + ignored);
+ }
+ mImsRegistrationCallback = null;
+ }
+ }
+
+ private void registerMmTelCapabilityCallback() {
+ if (mMmTelCapabilityCallback != null) {
+ logd("CapabilityCallback is already registered for sub-" + getSubId());
+ return;
+ }
+ /**
+ * Listens to the MmTel feature capabilities change.
+ */
+ mMmTelCapabilityCallback = new ImsMmTelManager.CapabilityCallback() {
+ @Override
+ public void onCapabilitiesStatusChanged(@NonNull MmTelCapabilities capabilities) {
+ onMmTelCapabilitiesChanged(capabilities);
+ }
+ };
+
+ try {
+ mMmTelManager.registerMmTelCapabilityCallback(mHandler::post, mMmTelCapabilityCallback);
+ } catch (ImsException e) {
+ loge("Exception when registering CapabilityCallback: " + e);
+ mMmTelCapabilityCallback = null;
+ }
+ }
+
+ private void unregisterMmTelCapabilityCallback() {
+ if (mMmTelCapabilityCallback != null) {
+ try {
+ mMmTelManager.unregisterMmTelCapabilityCallback(mMmTelCapabilityCallback);
+ } catch (Exception ignored) {
+ // Ignore the runtime exception while unregistering callback.
+ logd("Exception when unregistering CapabilityCallback: " + ignored);
+ }
+ mMmTelCapabilityCallback = null;
+ }
+ }
+
+ /** Returns a string representation of IMS states. */
+ public String imsStateToString() {
+ StringBuilder sb = new StringBuilder("{ ");
+ sb.append("MMTEL: featureAvailable=").append(booleanToString(mMmTelFeatureAvailable));
+ sb.append(", registered=").append(booleanToString(mImsRegistered));
+ sb.append(", accessNetworkType=").append(accessNetworkTypeToString(mImsAccessNetworkType));
+ sb.append(", capabilities=").append(mmTelCapabilitiesToString(mMmTelCapabilities));
+ sb.append(" }");
+ return sb.toString();
+ }
+
+ protected static String accessNetworkTypeToString(
+ @RadioAccessNetworkType int accessNetworkType) {
+ switch (accessNetworkType) {
+ case AccessNetworkType.UNKNOWN: return "UNKNOWN";
+ case AccessNetworkType.GERAN: return "GERAN";
+ case AccessNetworkType.UTRAN: return "UTRAN";
+ case AccessNetworkType.EUTRAN: return "EUTRAN";
+ case AccessNetworkType.CDMA2000: return "CDMA2000";
+ case AccessNetworkType.IWLAN: return "IWLAN";
+ case AccessNetworkType.NGRAN: return "NGRAN";
+ default: return Integer.toString(accessNetworkType);
+ }
+ }
+
+ /** Converts the IMS registration technology to the access network type. */
+ private static @RadioAccessNetworkType int imsRegTechToAccessNetworkType(
+ @ImsRegistrationImplBase.ImsRegistrationTech int imsRegTech) {
+ switch (imsRegTech) {
+ case ImsRegistrationImplBase.REGISTRATION_TECH_LTE:
+ return AccessNetworkType.EUTRAN;
+ case ImsRegistrationImplBase.REGISTRATION_TECH_NR:
+ return AccessNetworkType.NGRAN;
+ case ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN:
+ return AccessNetworkType.IWLAN;
+ default:
+ return AccessNetworkType.UNKNOWN;
+ }
+ }
+
+ private static String booleanToString(Boolean b) {
+ return b == null ? "null" : b.toString();
+ }
+
+ private static String mmTelCapabilitiesToString(MmTelCapabilities c) {
+ if (c == null) {
+ return "null";
+ }
+ StringBuilder sb = new StringBuilder("[");
+ sb.append("voice=").append(c.isCapable(MmTelCapabilities.CAPABILITY_TYPE_VOICE));
+ sb.append(", video=").append(c.isCapable(MmTelCapabilities.CAPABILITY_TYPE_VIDEO));
+ sb.append(", ut=").append(c.isCapable(MmTelCapabilities.CAPABILITY_TYPE_UT));
+ sb.append(", sms=").append(c.isCapable(MmTelCapabilities.CAPABILITY_TYPE_SMS));
+ sb.append("]");
+ return sb.toString();
+ }
+
+ private static String disconnectedCauseToString(@DisconnectedReason int reason) {
+ switch (reason) {
+ case ImsStateCallback.REASON_UNKNOWN_TEMPORARY_ERROR:
+ return "UNKNOWN_TEMPORARY_ERROR";
+ case ImsStateCallback.REASON_UNKNOWN_PERMANENT_ERROR:
+ return "UNKNOWN_PERMANENT_ERROR";
+ case ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED:
+ return "IMS_SERVICE_DISCONNECTED";
+ case ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED:
+ return "NO_IMS_SERVICE_CONFIGURED";
+ case ImsStateCallback.REASON_SUBSCRIPTION_INACTIVE:
+ return "SUBSCRIPTION_INACTIVE";
+ case ImsStateCallback.REASON_IMS_SERVICE_NOT_READY:
+ return "IMS_SERVICE_NOT_READY";
+ default:
+ return Integer.toString(reason);
+ }
+ }
+
+ /**
+ * Dumps this instance into a readable format for dumpsys usage.
+ */
+ public void dump(@NonNull PrintWriter pw) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.println("ImsStateTracker:");
+ ipw.increaseIndent();
+ ipw.println("SlotId: " + getSlotId());
+ ipw.println("SubId: " + getSubId());
+ ipw.println("ServiceState: " + mServiceState);
+ ipw.println("BarringInfo: " + mBarringInfo);
+ ipw.println("ImsState: " + imsStateToString());
+ ipw.println("Event Log:");
+ ipw.increaseIndent();
+ mEventLog.dump(ipw);
+ ipw.decreaseIndent();
+ ipw.decreaseIndent();
+ }
+
+ private void logd(String s) {
+ Log.d(TAG, "[" + getSlotId() + "|" + getSubId() + "] " + s);
+ }
+
+ private void logi(String s) {
+ Log.i(TAG, "[" + getSlotId() + "|" + getSubId() + "] " + s);
+ mEventLog.log("[" + getSlotId() + "|" + getSubId() + "] " + s);
+ }
+
+ private void loge(String s) {
+ Log.e(TAG, "[" + getSlotId() + "|" + getSubId() + "] " + s);
+ mEventLog.log("[" + getSlotId() + "|" + getSubId() + "] " + s);
+ }
+
+ private void logw(String s) {
+ Log.w(TAG, "[" + getSlotId() + "|" + getSubId() + "] " + s);
+ mEventLog.log("[" + getSlotId() + "|" + getSubId() + "] " + s);
+ }
+}
diff --git a/src/com/android/services/telephony/domainselection/NormalCallDomainSelector.java b/src/com/android/services/telephony/domainselection/NormalCallDomainSelector.java
new file mode 100644
index 0000000..82057d3
--- /dev/null
+++ b/src/com/android/services/telephony/domainselection/NormalCallDomainSelector.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.DomainSelectionService.SELECTOR_TYPE_CALLING;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Looper;
+import android.telephony.Annotation.DisconnectCauses;
+import android.telephony.DisconnectCause;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TransportSelectorCallback;
+import android.telephony.ims.ImsReasonInfo;
+
+/**
+ * Implements domain selector for outgoing non-emergency calls.
+ */
+public class NormalCallDomainSelector extends DomainSelectorBase implements
+ ImsStateTracker.ImsStateListener, ImsStateTracker.ServiceStateListener {
+
+ private static final String LOG_TAG = "NCDS";
+
+ private boolean mStopDomainSelection = true;
+ private ServiceState mServiceState;
+ private boolean mImsRegStateReceived;
+ private boolean mMmTelCapabilitiesReceived;
+ private boolean mReselectDomain;
+
+ public NormalCallDomainSelector(Context context, int slotId, int subId, @NonNull Looper looper,
+ @NonNull ImsStateTracker imsStateTracker,
+ @NonNull DestroyListener destroyListener) {
+ super(context, slotId, subId, looper, imsStateTracker, destroyListener, LOG_TAG);
+
+ if (SubscriptionManager.isValidSubscriptionId(subId)) {
+ logd("Subscribing to state callbacks. Subid:" + subId);
+ mImsStateTracker.addServiceStateListener(this);
+ mImsStateTracker.addImsStateListener(this);
+ } else {
+ loge("Invalid Subscription. Subid:" + subId);
+ }
+ }
+
+ @Override
+ public void selectDomain(SelectionAttributes attributes, TransportSelectorCallback callback) {
+ mSelectionAttributes = attributes;
+ mTransportSelectorCallback = callback;
+ mStopDomainSelection = false;
+
+ if (callback == null) {
+ loge("Invalid params: TransportSelectorCallback is null");
+ return;
+ }
+
+ if (attributes == null) {
+ loge("Invalid params: SelectionAttributes are null");
+ notifySelectionTerminated(DisconnectCause.OUTGOING_FAILURE);
+ return;
+ }
+
+ int subId = attributes.getSubId();
+ boolean validSubscriptionId = SubscriptionManager.isValidSubscriptionId(subId);
+ if (attributes.getSelectorType() != SELECTOR_TYPE_CALLING || attributes.isEmergency()
+ || !validSubscriptionId) {
+ loge("Domain Selection stopped. SelectorType:" + attributes.getSelectorType()
+ + ", isEmergency:" + attributes.isEmergency()
+ + ", ValidSubscriptionId:" + validSubscriptionId);
+
+ notifySelectionTerminated(DisconnectCause.OUTGOING_FAILURE);
+ return;
+ }
+
+ if (subId == getSubId()) {
+ logd("NormalCallDomainSelection triggered. Sub-id:" + subId);
+ post(() -> selectDomain());
+ } else {
+ loge("Subscription-ids doesn't match. This instance is associated with sub-id:"
+ + getSubId() + ", requested sub-id:" + subId);
+ // TODO: Throw anamoly here. This condition should never occur.
+ }
+ }
+
+ @Override
+ public void reselectDomain(SelectionAttributes attributes) {
+ logd("reselectDomain called");
+ mReselectDomain = true;
+ selectDomain(attributes, mTransportSelectorCallback);
+ }
+
+ @Override
+ public synchronized void finishSelection() {
+ logd("finishSelection");
+ mStopDomainSelection = true;
+ mImsStateTracker.removeServiceStateListener(this);
+ mImsStateTracker.removeImsStateListener(this);
+ mSelectionAttributes = null;
+ mTransportSelectorCallback = null;
+ }
+
+ /**
+ * Cancel an ongoing selection operation. It is up to the DomainSelectionService
+ * to clean up all ongoing operations with the framework.
+ */
+ @Override
+ public void cancelSelection() {
+ logd("cancelSelection");
+ mStopDomainSelection = true;
+ mReselectDomain = false;
+ if (mTransportSelectorCallback != null) {
+ mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_CANCELED);
+ }
+ finishSelection();
+ }
+
+ @Override
+ public void onImsRegistrationStateChanged() {
+ logd("onImsRegistrationStateChanged");
+ mImsRegStateReceived = true;
+ selectDomain();
+ }
+
+ @Override
+ public void onImsMmTelCapabilitiesChanged() {
+ logd("onImsMmTelCapabilitiesChanged");
+ mMmTelCapabilitiesReceived = true;
+ selectDomain();
+ }
+
+ @Override
+ public void onImsMmTelFeatureAvailableChanged() {
+ logd("onImsMmTelFeatureAvailableChanged");
+ selectDomain();
+ }
+
+ @Override
+ public void onServiceStateUpdated(ServiceState serviceState) {
+ logd("onServiceStateUpdated");
+ mServiceState = serviceState;
+ selectDomain();
+ }
+
+ private void notifyPsSelected() {
+ logd("notifyPsSelected");
+ mStopDomainSelection = true;
+ if (mImsStateTracker.isImsRegisteredOverWlan()) {
+ logd("WLAN selected");
+ mTransportSelectorCallback.onWlanSelected();
+ } else {
+ if (mWwanSelectorCallback == null) {
+ mTransportSelectorCallback.onWwanSelected((callback) -> {
+ mWwanSelectorCallback = callback;
+ notifyPsSelectedInternal();
+ });
+ } else {
+ notifyPsSelectedInternal();
+ }
+ }
+ }
+
+ private void notifyPsSelectedInternal() {
+ if (mWwanSelectorCallback != null) {
+ logd("notifyPsSelected - onWwanSelected");
+ mWwanSelectorCallback.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS);
+ } else {
+ loge("wwanSelectorCallback is null");
+ mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_FAILURE);
+ }
+ }
+
+ private void notifyCsSelected() {
+ logd("notifyCsSelected");
+ mStopDomainSelection = true;
+ if (mWwanSelectorCallback == null) {
+ mTransportSelectorCallback.onWwanSelected((callback) -> {
+ mWwanSelectorCallback = callback;
+ notifyCsSelectedInternal();
+ });
+ } else {
+ notifyCsSelectedInternal();
+ }
+ }
+
+ private void notifyCsSelectedInternal() {
+ if (mWwanSelectorCallback != null) {
+ logd("wwanSelectorCallback -> onDomainSelected(DOMAIN_CS)");
+ mWwanSelectorCallback.onDomainSelected(NetworkRegistrationInfo.DOMAIN_CS);
+ } else {
+ loge("wwanSelectorCallback is null");
+ mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_FAILURE);
+ }
+ }
+
+ private void notifySelectionTerminated(@DisconnectCauses int cause) {
+ mStopDomainSelection = true;
+ if (mTransportSelectorCallback != null) {
+ mTransportSelectorCallback.onSelectionTerminated(cause);
+ finishSelection();
+ }
+ }
+
+ private synchronized void selectDomain() {
+ if (mStopDomainSelection || mSelectionAttributes == null
+ || mTransportSelectorCallback == null) {
+ logd("Domain Selection is stopped.");
+ return;
+ }
+
+ if (mServiceState == null) {
+ logd("Waiting for ServiceState callback.");
+ return;
+ } else if (mServiceState.getState() == ServiceState.STATE_OUT_OF_SERVICE
+ || mServiceState.getState() == ServiceState.STATE_POWER_OFF
+ || mServiceState.getState() == ServiceState.STATE_EMERGENCY_ONLY) {
+ loge("Cannot place call in current ServiceState: " + mServiceState.getState());
+ notifySelectionTerminated(DisconnectCause.OUT_OF_SERVICE);
+ return;
+ }
+
+ // Check if this is a re-dial scenario
+ // IMS -> CS
+ ImsReasonInfo imsReasonInfo = mSelectionAttributes.getPsDisconnectCause();
+ if (mReselectDomain && imsReasonInfo != null) {
+ logd("PsDisconnectCause:" + imsReasonInfo.mCode);
+ mReselectDomain = false;
+ if (imsReasonInfo.mCode == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED) {
+ logd("Redialing over CS");
+ notifyCsSelected();
+ return;
+ } else {
+ logd("Redialing cancelled.");
+ // Not a valid redial
+ notifySelectionTerminated(DisconnectCause.NOT_VALID);
+ return;
+ }
+ }
+
+ // CS -> IMS
+ // TODO: @PreciseDisconnectCauses doesn't contain cause code related to redial on IMS.
+ if (mReselectDomain /*mSelectionAttributes.getCsDisconnectCause() == IMS_REDIAL_CODE*/) {
+ logd("Redialing cancelled.");
+ // Not a valid redial
+ notifySelectionTerminated(DisconnectCause.NOT_VALID);
+ return;
+ }
+
+ if (mImsStateTracker.isMmTelFeatureAvailable()) {
+
+ if (!mImsRegStateReceived || !mMmTelCapabilitiesReceived) {
+ loge("Waiting for ImsState and MmTelCapabilities callbacks");
+ return;
+ }
+
+ if (!mImsStateTracker.isImsRegistered()) {
+ logd("IMS is NOT registered");
+ notifyCsSelected();
+ return;
+ }
+
+ if (mSelectionAttributes.isVideoCall()) {
+ logd("It's a video call");
+ if (mImsStateTracker.isImsVideoCapable()) {
+ logd("IMS is video capable");
+ notifyPsSelected();
+ } else {
+ logd("IMS is not video capable. Ending the call");
+ notifySelectionTerminated(DisconnectCause.OUTGOING_FAILURE);
+ }
+ } else if (mImsStateTracker.isImsVoiceCapable()) {
+ logd("IMS is voice capable");
+ // Voice call over PS
+ notifyPsSelected();
+ } else {
+ logd("IMS is not voice capable");
+ // Voice call CS fallback
+ notifyCsSelected();
+ }
+ } else {
+ logd("IMS is not registered or unavailable");
+ notifyCsSelected();
+ }
+ }
+}
diff --git a/src/com/android/services/telephony/domainselection/SmsDomainSelector.java b/src/com/android/services/telephony/domainselection/SmsDomainSelector.java
new file mode 100644
index 0000000..e7c9ac5
--- /dev/null
+++ b/src/com/android/services/telephony/domainselection/SmsDomainSelector.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 android.annotation.NonNull;
+import android.content.Context;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.DisconnectCause;
+import android.telephony.DomainSelectionService;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.TransportSelectorCallback;
+
+/**
+ * Implements SMS domain selector for sending MO SMS.
+ */
+public class SmsDomainSelector extends DomainSelectorBase implements
+ ImsStateTracker.ImsStateListener {
+ protected static final int EVENT_SELECT_DOMAIN = 101;
+
+ protected boolean mDestroyed = false;
+ private boolean mDomainSelectionRequested = false;
+
+ public SmsDomainSelector(Context context, int slotId, int subId, @NonNull Looper looper,
+ @NonNull ImsStateTracker imsStateTracker, @NonNull DestroyListener listener) {
+ this(context, slotId, subId, looper, imsStateTracker, listener, "DomainSelector-SMS");
+ }
+
+ protected SmsDomainSelector(Context context, int slotId, int subId, @NonNull Looper looper,
+ @NonNull ImsStateTracker imsStateTracker, @NonNull DestroyListener listener,
+ String logTag) {
+ super(context, slotId, subId, looper, imsStateTracker, listener, logTag);
+ }
+
+ @Override
+ public void destroy() {
+ if (mDestroyed) {
+ return;
+ }
+ logd("destroy");
+ mDestroyed = true;
+ mImsStateTracker.removeImsStateListener(this);
+ super.destroy();
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ switch (msg.what) {
+ case EVENT_SELECT_DOMAIN:
+ selectDomain();
+ break;
+ default:
+ super.handleMessage(msg);
+ break;
+ }
+ }
+
+ @Override
+ public void cancelSelection() {
+ logi("cancelSelection");
+ finishSelection();
+ }
+
+ @Override
+ public void reselectDomain(@NonNull SelectionAttributes attr) {
+ if (isDomainSelectionRequested()) {
+ // The domain selection is already requested,
+ // so we don't need to request it again before completing the previous task.
+ logi("Domain selection is already running.");
+ return;
+ }
+
+ logi("reselectDomain");
+ mSelectionAttributes = attr;
+ setDomainSelectionRequested(true);
+ obtainMessage(EVENT_SELECT_DOMAIN).sendToTarget();
+ }
+
+ @Override
+ public void finishSelection() {
+ logi("finishSelection");
+ setDomainSelectionRequested(false);
+ mSelectionAttributes = null;
+ mTransportSelectorCallback = null;
+ mWwanSelectorCallback = null;
+ destroy();
+ }
+
+ @Override
+ public void selectDomain(SelectionAttributes attr, TransportSelectorCallback callback) {
+ if (isDomainSelectionRequested()) {
+ // The domain selection is already requested,
+ // so we don't need to request it again before completing the previous task.
+ logi("Domain selection is already running.");
+ return;
+ }
+ mSelectionAttributes = attr;
+ mTransportSelectorCallback = callback;
+ setDomainSelectionRequested(true);
+ mImsStateTracker.addImsStateListener(this);
+ obtainMessage(EVENT_SELECT_DOMAIN).sendToTarget();
+ }
+
+ @Override
+ public void onImsMmTelFeatureAvailableChanged() {
+ sendMessageForDomainSelection();
+ }
+
+ @Override
+ public void onImsRegistrationStateChanged() {
+ sendMessageForDomainSelection();
+ }
+
+ @Override
+ public void onImsMmTelCapabilitiesChanged() {
+ sendMessageForDomainSelection();
+ }
+
+ protected boolean isSmsOverImsAvailable() {
+ return mImsStateTracker.isImsSmsCapable()
+ && mImsStateTracker.isImsRegistered()
+ && mImsStateTracker.isMmTelFeatureAvailable();
+ }
+
+ protected void selectDomain() {
+ if (!isDomainSelectionRequested()) {
+ logi("Domain selection is not requested!");
+ return;
+ }
+
+ logi("selectDomain: " + mImsStateTracker.imsStateToString());
+
+ if (isSmsOverImsAvailable()) {
+ if (mImsStateTracker.isImsRegisteredOverWlan()) {
+ notifyWlanSelected();
+ return;
+ }
+ notifyWwanSelected(NetworkRegistrationInfo.DOMAIN_PS);
+ } else {
+ notifyWwanSelected(NetworkRegistrationInfo.DOMAIN_CS);
+ }
+ }
+
+ protected void sendMessageForDomainSelection() {
+ // If the event is already queued to this handler,
+ // it will be removed first to avoid the duplicate operation.
+ removeMessages(EVENT_SELECT_DOMAIN);
+ // Since the IMS state may have already been posted,
+ // proceed with the domain selection after processing all pending messages.
+ obtainMessage(EVENT_SELECT_DOMAIN).sendToTarget();
+ }
+
+ protected boolean isDomainSelectionRequested() {
+ return mDomainSelectionRequested;
+ }
+
+ protected void setDomainSelectionRequested(boolean requested) {
+ if (mDomainSelectionRequested != requested) {
+ logd("DomainSelectionRequested: " + mDomainSelectionRequested + " >> " + requested);
+ mDomainSelectionRequested = requested;
+ }
+ }
+
+ protected void notifyWlanSelected() {
+ logi("DomainSelected: WLAN");
+ mTransportSelectorCallback.onWlanSelected();
+ setDomainSelectionRequested(false);
+ }
+
+ protected void notifyWwanSelected(@NetworkRegistrationInfo.Domain int domain) {
+ if (mWwanSelectorCallback == null) {
+ mTransportSelectorCallback.onWwanSelected((callback) -> {
+ mWwanSelectorCallback = callback;
+ notifyWwanSelectedInternal(domain);
+ });
+ } else {
+ notifyWwanSelectedInternal(domain);
+ }
+
+ setDomainSelectionRequested(false);
+ }
+
+ protected void notifyWwanSelectedInternal(@NetworkRegistrationInfo.Domain int domain) {
+ logi("DomainSelected: WWAN/" + DomainSelectionService.getDomainName(domain));
+
+ if (mWwanSelectorCallback != null) {
+ mWwanSelectorCallback.onDomainSelected(domain);
+ } else {
+ mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.LOCAL);
+ }
+ }
+}
diff --git a/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java b/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
new file mode 100644
index 0000000..13db06b
--- /dev/null
+++ b/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionService.java
@@ -0,0 +1,516 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.telephony.BarringInfo;
+import android.telephony.DisconnectCause;
+import android.telephony.DomainSelectionService;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.TelephonyManager;
+import android.telephony.TransportSelectorCallback;
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Implements the telephony domain selection for various telephony features.
+ */
+public class TelephonyDomainSelectionService extends DomainSelectionService {
+ /**
+ * Testing interface for injecting mock ImsStateTracker.
+ */
+ @VisibleForTesting
+ public interface ImsStateTrackerFactory {
+ /**
+ * @return The {@link ImsStateTracker} created for the specified slot.
+ */
+ ImsStateTracker create(Context context, int slotId, @NonNull Looper looper);
+ }
+
+ /**
+ * Testing interface for injecting mock DomainSelector.
+ */
+ @VisibleForTesting
+ public interface DomainSelectorFactory {
+ /**
+ * @return The {@link DomainSelectorBase} created using the specified arguments.
+ */
+ DomainSelectorBase create(Context context, int slotId, int subId,
+ @SelectorType int selectorType, boolean isEmergency, @NonNull Looper looper,
+ @NonNull ImsStateTracker imsStateTracker,
+ @NonNull DomainSelectorBase.DestroyListener listener);
+ }
+
+ private static final class DefaultDomainSelectorFactory implements DomainSelectorFactory {
+ @Override
+ public DomainSelectorBase create(Context context, int slotId, int subId,
+ @SelectorType int selectorType, boolean isEmergency, @NonNull Looper looper,
+ @NonNull ImsStateTracker imsStateTracker,
+ @NonNull DomainSelectorBase.DestroyListener listener) {
+ DomainSelectorBase selector = null;
+
+ logi("create-DomainSelector: slotId=" + slotId + ", subId=" + subId
+ + ", selectorType=" + selectorTypeToString(selectorType)
+ + ", emergency=" + isEmergency);
+
+ switch (selectorType) {
+ case SELECTOR_TYPE_CALLING:
+ if (isEmergency) {
+ selector = new EmergencyCallDomainSelector(context, slotId, subId, looper,
+ imsStateTracker, listener);
+ } else {
+ selector = new NormalCallDomainSelector(context, slotId, subId, looper,
+ imsStateTracker, listener);
+ }
+ break;
+ case SELECTOR_TYPE_SMS:
+ if (isEmergency) {
+ selector = new EmergencySmsDomainSelector(context, slotId, subId, looper,
+ imsStateTracker, listener);
+ } else {
+ selector = new SmsDomainSelector(context, slotId, subId, looper,
+ imsStateTracker, listener);
+ }
+ break;
+ default:
+ // Not reachable.
+ break;
+ }
+
+ return selector;
+ }
+ };
+
+ /**
+ * A container class to manage the domain selector per a slot and selector type.
+ * If the domain selector is not null and reusable, the same domain selector will be used
+ * for the specific slot.
+ */
+ private static final class DomainSelectorContainer {
+ private final int mSlotId;
+ private final @SelectorType int mSelectorType;
+ private final boolean mIsEmergency;
+ private final @NonNull DomainSelectorBase mSelector;
+
+ DomainSelectorContainer(int slotId, @SelectorType int selectorType, boolean isEmergency,
+ @NonNull DomainSelectorBase selector) {
+ mSlotId = slotId;
+ mSelectorType = selectorType;
+ mIsEmergency = isEmergency;
+ mSelector = selector;
+ }
+
+ public int getSlotId() {
+ return mSlotId;
+ }
+
+ public @SelectorType int getSelectorType() {
+ return mSelectorType;
+ }
+
+ public DomainSelectorBase getDomainSelector() {
+ return mSelector;
+ }
+
+ public boolean isEmergency() {
+ return mIsEmergency;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("{ ")
+ .append("slotId=").append(mSlotId)
+ .append(", selectorType=").append(selectorTypeToString(mSelectorType))
+ .append(", isEmergency=").append(mIsEmergency)
+ .append(", selector=").append(mSelector)
+ .append(" }").toString();
+ }
+ }
+
+ private final DomainSelectorBase.DestroyListener mDestroyListener =
+ new DomainSelectorBase.DestroyListener() {
+ @Override
+ public void onDomainSelectorDestroyed(DomainSelectorBase selector) {
+ logd("DomainSelector destroyed: " + selector);
+ removeDomainSelector(selector);
+ }
+ };
+
+ /**
+ * A class to listen for the subscription change for starting {@link ImsStateTracker}
+ * to monitor the IMS states.
+ */
+ private final OnSubscriptionsChangedListener mSubscriptionsChangedListener =
+ new OnSubscriptionsChangedListener() {
+ @Override
+ public void onSubscriptionsChanged() {
+ handleSubscriptionsChanged();
+ }
+ };
+
+ private static final String TAG = TelephonyDomainSelectionService.class.getSimpleName();
+
+ // Persistent Logging
+ private static final LocalLog sEventLog = new LocalLog(20);
+ private final Context mContext;
+ // Map of slotId -> ImsStateTracker
+ private final SparseArray<ImsStateTracker> mImsStateTrackers = new SparseArray<>(2);
+ private final List<DomainSelectorContainer> mDomainSelectorContainers = new ArrayList<>();
+ private final ImsStateTrackerFactory mImsStateTrackerFactory;
+ private final DomainSelectorFactory mDomainSelectorFactory;
+ private Handler mServiceHandler;
+
+ public TelephonyDomainSelectionService(Context context) {
+ this(context, ImsStateTracker::new, new DefaultDomainSelectorFactory());
+ }
+
+ @VisibleForTesting
+ public TelephonyDomainSelectionService(Context context,
+ @NonNull ImsStateTrackerFactory imsStateTrackerFactory,
+ @NonNull DomainSelectorFactory domainSelectorFactory) {
+ mContext = context;
+ mImsStateTrackerFactory = imsStateTrackerFactory;
+ mDomainSelectorFactory = domainSelectorFactory;
+
+ // Create a worker thread for this domain selection service.
+ getExecutor();
+
+ TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+ int activeModemCount = (tm != null) ? tm.getActiveModemCount() : 1;
+ for (int i = 0; i < activeModemCount; ++i) {
+ mImsStateTrackers.put(i, mImsStateTrackerFactory.create(mContext, i, getLooper()));
+ }
+
+ SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
+ if (sm != null) {
+ sm.addOnSubscriptionsChangedListener(getExecutor(), mSubscriptionsChangedListener);
+ } else {
+ loge("Adding OnSubscriptionChangedListener failed");
+ }
+
+ logi("TelephonyDomainSelectionService created");
+ }
+
+ @Override
+ public void onDestroy() {
+ logd("onDestroy");
+
+ List<DomainSelectorContainer> domainSelectorContainers;
+
+ synchronized (mDomainSelectorContainers) {
+ domainSelectorContainers = new ArrayList<>(mDomainSelectorContainers);
+ mDomainSelectorContainers.clear();
+ }
+
+ for (DomainSelectorContainer dsc : domainSelectorContainers) {
+ DomainSelectorBase selector = dsc.getDomainSelector();
+ if (selector != null) {
+ selector.destroy();
+ }
+ }
+ domainSelectorContainers.clear();
+
+ synchronized (mImsStateTrackers) {
+ for (int i = 0; i < mImsStateTrackers.size(); ++i) {
+ ImsStateTracker ist = mImsStateTrackers.get(i);
+ if (ist != null) {
+ ist.destroy();
+ }
+ }
+ mImsStateTrackers.clear();
+ }
+
+ SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
+ if (sm != null) {
+ sm.removeOnSubscriptionsChangedListener(mSubscriptionsChangedListener);
+ }
+
+ if (mServiceHandler != null) {
+ mServiceHandler.getLooper().quit();
+ mServiceHandler = null;
+ }
+ }
+
+ /**
+ * Selects a domain for the given attributes and callback.
+ *
+ * @param attr required to determine the domain.
+ * @param callback the callback instance being registered.
+ */
+ @Override
+ public void onDomainSelection(@NonNull SelectionAttributes attr,
+ @NonNull TransportSelectorCallback callback) {
+ final int slotId = attr.getSlotId();
+ final int subId = attr.getSubId();
+ final int selectorType = attr.getSelectorType();
+ final boolean isEmergency = attr.isEmergency();
+ ImsStateTracker ist = getImsStateTracker(slotId);
+ DomainSelectorBase selector = mDomainSelectorFactory.create(mContext, slotId, subId,
+ selectorType, isEmergency, getLooper(), ist, mDestroyListener);
+
+ if (selector != null) {
+ // Ensures that ImsStateTracker is started before selecting the domain if not started
+ // for the specified subscription index.
+ ist.start(subId);
+ addDomainSelector(slotId, selectorType, isEmergency, selector);
+ } else {
+ loge("No proper domain selector: " + selectorTypeToString(selectorType));
+ callback.onSelectionTerminated(DisconnectCause.ERROR_UNSPECIFIED);
+ return;
+ }
+
+ // Notify the caller that the domain selector is created.
+ callback.onCreated(selector);
+
+ // Performs the domain selection.
+ selector.selectDomain(attr, callback);
+ }
+
+ /**
+ * Called when the {@link ServiceState} needs to be updated for the specified slot and
+ * subcription index.
+ *
+ * @param slotId for which the service state changed.
+ * @param subId The current subscription for a specified slot.
+ * @param serviceState The {@link ServiceState} to be updated.
+ */
+ @Override
+ public void onServiceStateUpdated(int slotId, int subId, @NonNull ServiceState serviceState) {
+ ImsStateTracker ist = getImsStateTracker(slotId);
+ if (ist != null) {
+ ist.updateServiceState(serviceState);
+ }
+ }
+
+ /**
+ * Called when the {@link BarringInfo} needs to be updated for the specified slot and
+ * subscription index.
+ *
+ * @param slotId The slot the BarringInfo is updated for.
+ * @param subId The current subscription for a specified slot.
+ * @param barringInfo The {@link BarringInfo} to be updated.
+ */
+ @Override
+ public void onBarringInfoUpdated(int slotId, int subId, @NonNull BarringInfo barringInfo) {
+ ImsStateTracker ist = getImsStateTracker(slotId);
+ if (ist != null) {
+ ist.updateBarringInfo(barringInfo);
+ }
+ }
+
+ /**
+ * Returns an Executor used to execute methods called remotely by the framework.
+ */
+ @SuppressLint("OnNameExpected")
+ @Override
+ public @NonNull Executor getExecutor() {
+ if (mServiceHandler == null) {
+ HandlerThread handlerThread = new HandlerThread(TAG);
+ handlerThread.start();
+ mServiceHandler = new Handler(handlerThread.getLooper());
+ }
+
+ return mServiceHandler::post;
+ }
+
+ /**
+ * Returns a Looper instance.
+ */
+ @VisibleForTesting
+ public Looper getLooper() {
+ getExecutor();
+ return mServiceHandler.getLooper();
+ }
+
+ /**
+ * Handles the subscriptions change.
+ */
+ private void handleSubscriptionsChanged() {
+ SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
+ List<SubscriptionInfo> subsInfoList =
+ (sm != null) ? sm.getActiveSubscriptionInfoList() : null;
+
+ if (subsInfoList == null || subsInfoList.isEmpty()) {
+ logd("handleSubscriptionsChanged: No valid SubscriptionInfo");
+ return;
+ }
+
+ for (int i = 0; i < subsInfoList.size(); ++i) {
+ SubscriptionInfo subsInfo = subsInfoList.get(i);
+ int slotId = subsInfo.getSimSlotIndex();
+
+ if (slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+ logd("handleSubscriptionsChanged: slotId=" + slotId);
+ ImsStateTracker ist = getImsStateTracker(slotId);
+ ist.start(subsInfo.getSubscriptionId());
+ }
+ }
+ }
+
+ /**
+ * Adds the {@link DomainSelectorBase} to the list of domain selector container.
+ */
+ private void addDomainSelector(int slotId, @SelectorType int selectorType,
+ boolean isEmergency, @NonNull DomainSelectorBase selector) {
+ synchronized (mDomainSelectorContainers) {
+ // If the domain selector already exists, remove the previous one first.
+ for (int i = 0; i < mDomainSelectorContainers.size(); ++i) {
+ DomainSelectorContainer dsc = mDomainSelectorContainers.get(i);
+
+ if (dsc.getSlotId() == slotId
+ && dsc.getSelectorType() == selectorType
+ && dsc.isEmergency() == isEmergency) {
+ mDomainSelectorContainers.remove(i);
+ DomainSelectorBase oldSelector = dsc.getDomainSelector();
+ if (oldSelector != null) {
+ logw("DomainSelector destroyed by new domain selection request: " + dsc);
+ oldSelector.destroy();
+ }
+ break;
+ }
+ }
+
+ DomainSelectorContainer dsc =
+ new DomainSelectorContainer(slotId, selectorType, isEmergency, selector);
+ mDomainSelectorContainers.add(dsc);
+
+ logi("DomainSelector added: " + dsc + ", count=" + mDomainSelectorContainers.size());
+ }
+ }
+
+ /**
+ * Removes the domain selector container that matches with the specified
+ * {@link DomainSelectorBase}.
+ */
+ private void removeDomainSelector(@NonNull DomainSelectorBase selector) {
+ synchronized (mDomainSelectorContainers) {
+ for (int i = 0; i < mDomainSelectorContainers.size(); ++i) {
+ DomainSelectorContainer dsc = mDomainSelectorContainers.get(i);
+
+ if (dsc.getDomainSelector() == selector) {
+ mDomainSelectorContainers.remove(i);
+ logi("DomainSelector removed: " + dsc
+ + ", count=" + mDomainSelectorContainers.size());
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the {@link ImsStateTracker} instance for the specified slot.
+ * If the {@link ImsStateTracker} does not exist for the slot, it creates new instance
+ * and returns.
+ */
+ private ImsStateTracker getImsStateTracker(int slotId) {
+ synchronized (mImsStateTrackers) {
+ ImsStateTracker ist = mImsStateTrackers.get(slotId);
+
+ if (ist == null) {
+ ist = mImsStateTrackerFactory.create(mContext, slotId, getLooper());
+ mImsStateTrackers.put(slotId, ist);
+ }
+
+ return ist;
+ }
+ }
+
+ private static String selectorTypeToString(@SelectorType int selectorType) {
+ switch (selectorType) {
+ case SELECTOR_TYPE_CALLING: return "CALLING";
+ case SELECTOR_TYPE_SMS: return "SMS";
+ case SELECTOR_TYPE_UT: return "UT";
+ default: return Integer.toString(selectorType);
+ }
+ }
+
+ private static void logd(String s) {
+ Log.d(TAG, s);
+ }
+
+ private static void logi(String s) {
+ Log.i(TAG, s);
+ sEventLog.log(s);
+ }
+
+ private static void loge(String s) {
+ Log.e(TAG, s);
+ sEventLog.log(s);
+ }
+
+ private static void logw(String s) {
+ Log.w(TAG, s);
+ sEventLog.log(s);
+ }
+
+ /**
+ * Dumps this instance into a readable format for dumpsys usage.
+ */
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.println("TelephonyDomainSelectionService:");
+ ipw.increaseIndent();
+ ipw.println("ImsStateTrackers:");
+ synchronized (mImsStateTrackers) {
+ for (int i = 0; i < mImsStateTrackers.size(); ++i) {
+ ImsStateTracker ist = mImsStateTrackers.valueAt(i);
+ ist.dump(ipw);
+ }
+ }
+ ipw.decreaseIndent();
+ ipw.increaseIndent();
+ synchronized (mDomainSelectorContainers) {
+ for (int i = 0; i < mDomainSelectorContainers.size(); ++i) {
+ DomainSelectorContainer dsc = mDomainSelectorContainers.get(i);
+ ipw.println("DomainSelector: " + dsc.toString());
+ ipw.increaseIndent();
+ DomainSelectorBase selector = dsc.getDomainSelector();
+ if (selector != null) {
+ selector.dump(ipw);
+ }
+ ipw.decreaseIndent();
+ }
+ }
+ ipw.decreaseIndent();
+ ipw.increaseIndent();
+ ipw.println("Event Log:");
+ ipw.increaseIndent();
+ sEventLog.dump(ipw);
+ ipw.decreaseIndent();
+ ipw.decreaseIndent();
+ ipw.println("________________________________");
+ }
+}
diff --git a/src/com/android/services/telephony/rcs/MessageTransportWrapper.java b/src/com/android/services/telephony/rcs/MessageTransportWrapper.java
index 45f7d95..d992883 100644
--- a/src/com/android/services/telephony/rcs/MessageTransportWrapper.java
+++ b/src/com/android/services/telephony/rcs/MessageTransportWrapper.java
@@ -481,6 +481,16 @@
}
}
+ /**
+ * This is a listener to handle SipDialog state of delegate
+ * @param listener {@link SipDialogsStateListener}
+ * @param isNeedNotify It indicates whether the current dialogs state should be notified.
+ */
+ public void setSipDialogsListener(SipDialogsStateListener listener,
+ boolean isNeedNotify) {
+ mSipSessionTracker.setSipDialogsListener(listener, isNeedNotify);
+ }
+
private void logi(String log) {
Log.i(SipTransportController.LOG_TAG, TAG + "[" + mSubId + "] " + log);
mLocalLog.log("[I] " + log);
diff --git a/src/com/android/services/telephony/rcs/RcsFeatureController.java b/src/com/android/services/telephony/rcs/RcsFeatureController.java
index 48c84b1..a2f435c 100644
--- a/src/com/android/services/telephony/rcs/RcsFeatureController.java
+++ b/src/com/android/services/telephony/rcs/RcsFeatureController.java
@@ -17,11 +17,13 @@
package com.android.services.telephony.rcs;
import android.annotation.AnyThread;
+import android.annotation.NonNull;
import android.content.Context;
import android.net.Uri;
import android.telephony.SubscriptionManager;
import android.telephony.ims.ImsException;
import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.IImsRegistrationCallback;
import android.telephony.ims.stub.ImsRegistrationImplBase;
@@ -166,7 +168,7 @@
private ImsRegistrationCallbackHelper.ImsRegistrationUpdate mRcsRegistrationUpdate = new
ImsRegistrationCallbackHelper.ImsRegistrationUpdate() {
@Override
- public void handleImsRegistered(int imsRadioTech) {
+ public void handleImsRegistered(@NonNull ImsRegistrationAttributes attributes) {
}
@Override
@@ -174,7 +176,8 @@
}
@Override
- public void handleImsUnregistered(ImsReasonInfo imsReasonInfo) {
+ public void handleImsUnregistered(ImsReasonInfo imsReasonInfo,
+ int suggestedAction, int imsRadioTech) {
}
@Override
diff --git a/src/com/android/services/telephony/rcs/SipDelegateController.java b/src/com/android/services/telephony/rcs/SipDelegateController.java
index 860a6d9..f7fc359 100644
--- a/src/com/android/services/telephony/rcs/SipDelegateController.java
+++ b/src/com/android/services/telephony/rcs/SipDelegateController.java
@@ -391,6 +391,16 @@
}
/**
+ * This is a listener to handle SipDialog state of delegate
+ * @param listener {@link SipDialogsStateListener}
+ * @param isNeedNotify It indicates whether the current dialogs state should be notified.
+ */
+ public void setSipDialogsListener(SipDialogsStateListener listener,
+ boolean isNeedNotify) {
+ mMessageTransportWrapper.setSipDialogsListener(listener, isNeedNotify);
+ }
+
+ /**
* Write the current state of this controller in String format using the PrintWriter provided
* for dumpsys.
*/
diff --git a/src/com/android/services/telephony/rcs/SipDialogsStateListener.java b/src/com/android/services/telephony/rcs/SipDialogsStateListener.java
new file mode 100644
index 0000000..bbd3bd1
--- /dev/null
+++ b/src/com/android/services/telephony/rcs/SipDialogsStateListener.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.services.telephony.rcs;
+
+import android.telephony.ims.SipDialogState;
+
+import java.util.List;
+
+/**
+ * The listener interface for notifying the state of sip dialogs to SipDialogsStateHandle.
+ * refer to {@link SipTransportController}
+ */
+public interface SipDialogsStateListener {
+ /**
+ * To map dialog state information of available delegates
+ * @param key This is an ID of SipSessionTracker for distinguishing whose delegate is
+ * during dialog mapping.
+ * @param dialogStates This is dialog state information of delegate
+ */
+ void reMappingSipDelegateState(String key, List<SipDialogState> dialogStates);
+
+ /**
+ * Notify SipDialogState information with
+ * {@link com.android.internal.telephony.ISipDialogStateCallback}
+ */
+ void notifySipDialogState();
+}
diff --git a/src/com/android/services/telephony/rcs/SipSessionTracker.java b/src/com/android/services/telephony/rcs/SipSessionTracker.java
index 68e3065..2d48a9f 100644
--- a/src/com/android/services/telephony/rcs/SipSessionTracker.java
+++ b/src/com/android/services/telephony/rcs/SipSessionTracker.java
@@ -16,6 +16,7 @@
package com.android.services.telephony.rcs;
+import android.telephony.ims.SipDialogState;
import android.telephony.ims.SipMessage;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -33,6 +34,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Set;
+import java.util.UUID;
import java.util.stream.Collectors;
/**
@@ -69,10 +71,13 @@
private final RcsStats mRcsStats;
int mSubId;
+ private SipDialogsStateListener mSipDialogsListener;
+ private String mDelegateKey;
public SipSessionTracker(int subId, RcsStats rcsStats) {
mSubId = subId;
mRcsStats = rcsStats;
+ mDelegateKey = String.valueOf(UUID.randomUUID());
}
/**
@@ -152,6 +157,7 @@
logi("Dialog closed: " + d);
}
mTrackedDialogs.removeAll(dialogsToCleanup);
+ notifySipDialogState();
}
/**
@@ -213,6 +219,7 @@
}
mTrackedDialogs.clear();
mPendingAck.clear();
+ notifySipDialogState();
}
/**
@@ -308,6 +315,7 @@
d.close();
logi("Dialog closed: " + d);
}
+ notifySipDialogState();
};
}
@@ -365,16 +373,47 @@
if (statusCode >= 300) {
mRcsStats.onSipTransportSessionClosed(mSubId, m.getCallIdParameter(), statusCode, true);
d.close();
+ notifySipDialogState();
return;
}
if (toTag == null) logw("updateSipDialogState: No to tag for message: " + m);
if (statusCode >= 200) {
mRcsStats.confirmedSipTransportSession(m.getCallIdParameter(), statusCode);
d.confirm(toTag);
+ notifySipDialogState();
return;
}
// 1XX responses still require updates to dialogs.
d.earlyResponse(toTag);
+ notifySipDialogState();
+ }
+
+ /**
+ * This is a listener to handle SipDialog state of delegate
+ * @param listener {@link SipDialogsStateListener}
+ * @param isNeedNotify It indicates whether the current dialogs state should be notified.
+ */
+ public void setSipDialogsListener(SipDialogsStateListener listener,
+ boolean isNeedNotify) {
+ mSipDialogsListener = listener;
+ if (listener == null) {
+ return;
+ }
+ if (isNeedNotify) {
+ notifySipDialogState();
+ }
+ }
+
+ private void notifySipDialogState() {
+ if (mSipDialogsListener == null) {
+ return;
+ }
+ List<SipDialogState> dialogStates = new ArrayList<>();
+ for (SipDialog d : mTrackedDialogs) {
+ SipDialogState dialog = new SipDialogState.Builder(d.getState()).build();
+ dialogStates.add(dialog);
+ }
+ mSipDialogsListener.reMappingSipDelegateState(mDelegateKey, dialogStates);
}
private void logi(String log) {
diff --git a/src/com/android/services/telephony/rcs/SipTransportController.java b/src/com/android/services/telephony/rcs/SipTransportController.java
index 1fc1349..2f090d5 100644
--- a/src/com/android/services/telephony/rcs/SipTransportController.java
+++ b/src/com/android/services/telephony/rcs/SipTransportController.java
@@ -29,6 +29,7 @@
import android.telephony.ims.ImsException;
import android.telephony.ims.ImsService;
import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.SipDialogState;
import android.telephony.ims.aidl.IImsRegistration;
import android.telephony.ims.aidl.ISipDelegate;
import android.telephony.ims.aidl.ISipDelegateConnectionStateCallback;
@@ -46,6 +47,8 @@
import com.android.ims.RcsFeatureManager;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.ISipDialogStateCallback;
+import com.android.internal.telephony.util.RemoteCallbackListExt;
import com.android.internal.util.IndentingPrintWriter;
import com.android.phone.RcsProvisioningMonitor;
@@ -54,9 +57,11 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
@@ -239,6 +244,65 @@
}
/**
+ * This is to handle with dialogs of all available delegates that have dialogs.
+ */
+ private final class SipDialogsStateHandle implements SipDialogsStateListener {
+
+ private Executor mExecutor = Runnable::run;
+ Map<String, List<SipDialogState>> mMapDialogState = new HashMap<>();
+
+ /**
+ * This will be called using the {@link SipDialogsStateListener}
+ * @param key This is the ID of the SipSessionTracker for handling the dialogs of
+ * each created delegates.
+ * @param dialogStates This is a list of dialog states tracked in SipSessionTracker.
+ */
+ @Override
+ public void reMappingSipDelegateState(String key,
+ List<SipDialogState> dialogStates) {
+ mExecutor.execute(()->processReMappingSipDelegateState(key, dialogStates));
+ }
+
+ /**
+ * Notify SipDialogState information with
+ * {@link com.android.internal.telephony.ISipDialogStateCallback}
+ */
+ @Override
+ public void notifySipDialogState() {
+ mExecutor.execute(()->processNotifySipDialogState());
+ }
+
+ private void processReMappingSipDelegateState(String key,
+ List<SipDialogState> dialogStates) {
+ if (dialogStates.isEmpty()) {
+ mMapDialogState.remove(key);
+ } else {
+ mMapDialogState.put(key, dialogStates);
+ }
+ notifySipDialogState();
+ }
+
+ private void processNotifySipDialogState() {
+ List<SipDialogState> finalDialogStates = new ArrayList<>();
+ for (List<SipDialogState> d : mMapDialogState.values()) {
+ finalDialogStates.addAll(d);
+ }
+
+ if (mSipDialogStateCallbacks.getRegisteredCallbackCount() == 0) {
+ return;
+ }
+ mSipDialogStateCallbacks.broadcastAction((c) -> {
+ try {
+ c.onActiveSipDialogsChanged(finalDialogStates);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG,
+ "onActiveSipDialogsChanged() - Skipping callback." + e);
+ }
+ });
+ }
+ }
+
+ /**
* Allow the ability for tests to easily mock out the SipDelegateController for testing.
*/
@VisibleForTesting
@@ -266,6 +330,12 @@
private final List<SipDelegateController> mDelegatePendingCreate = new ArrayList<>();
// SipDelegateControllers that are pending to be destroyed.
private final List<DestroyRequest> mDelegatePendingDestroy = new ArrayList<>();
+ // SipDialogStateCallback that are adding to use callback.
+ private final RemoteCallbackListExt<ISipDialogStateCallback> mSipDialogStateCallbacks =
+ new RemoteCallbackListExt<>();
+ // To listen the state information if the dialog status is changed from the SipSessionTracker.
+ private final SipDialogsStateListener mSipDialogsListener = new SipDialogsStateHandle();
+
// Cache of Binders to remote IMS applications for tracking their potential death
private final TrackedAppBinders mActiveAppBinders = new TrackedAppBinders();
@@ -458,6 +528,10 @@
logi("createSipDelegateInternal: request= " + request + ", packageName= " + packageName
+ ", controller created: " + c);
addPendingCreateAndEvaluate(c);
+ // If SipDialogStateCallback is registered, listener will be set.
+ if (mSipDialogStateCallbacks.getRegisteredCallbackCount() > 0) {
+ c.setSipDialogsListener(mSipDialogsListener, false);
+ }
}
private void destroySipDelegateInternal(int subId, ISipDelegate connection, int reason) {
@@ -1057,6 +1131,56 @@
}
}
+ /**
+ * Adds a callback that gets called when SipDialog status has changed.
+ * @param subId The subId associated with the request.
+ * @param cb A {@link android.telephony.ims.SipDialogStateCallback} that will notify the caller
+ * when Dialog status has changed.
+ */
+ public void addCallbackForSipDialogState(int subId, ISipDialogStateCallback cb) {
+ if (subId != mSubId) {
+ logw("addCallbackForSipDialogState the subId is not supported");
+ return;
+ }
+ // callback register and no delegate : register this callback / notify (empty state)
+ // callback register and delegates : register this callback / release listener / notify
+ mSipDialogStateCallbacks.register(cb);
+ if (!mDelegatePriorityQueue.isEmpty()) {
+ for (SipDelegateController dc : mDelegatePriorityQueue) {
+ dc.setSipDialogsListener(mSipDialogsListener, true);
+ }
+ } else {
+ mSipDialogsListener.notifySipDialogState();
+ }
+ }
+
+ /**
+ * Unregister previously registered callback
+ * @param subId The subId associated with the request.
+ * @param cb A {@link android.telephony.ims.SipDialogStateCallback} that will be unregistering.
+ */
+ public void removeCallbackForSipDialogState(int subId, ISipDialogStateCallback cb) {
+ if (subId != mSubId) {
+ logw("addCallbackForSipDialogState the subId is not supported");
+ return;
+ }
+ if (cb == null) {
+ throw new IllegalArgumentException("callback is null");
+ }
+
+ // remove callback register and no delegate : only unregister this callback
+ // remove callback register and delegates :
+ // unregister this callback and setListener(null)
+ mSipDialogStateCallbacks.unregister(cb);
+ if (mSipDialogStateCallbacks.getRegisteredCallbackCount() == 0) {
+ if (!mDelegatePriorityQueue.isEmpty()) {
+ for (SipDelegateController dc : mDelegatePriorityQueue) {
+ dc.setSipDialogsListener(null, false);
+ }
+ }
+ }
+ }
+
@Override
public void dump(PrintWriter printWriter) {
IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
diff --git a/src/com/android/services/telephony/rcs/TransportSipMessageValidator.java b/src/com/android/services/telephony/rcs/TransportSipMessageValidator.java
index 3b1b26d..b15992e 100644
--- a/src/com/android/services/telephony/rcs/TransportSipMessageValidator.java
+++ b/src/com/android/services/telephony/rcs/TransportSipMessageValidator.java
@@ -577,6 +577,16 @@
}
}
+ /**
+ * This is a listener to handle SipDialog state of delegate
+ * @param listener {@link SipDialogsStateListener}
+ * @param isNeedNotify It indicates whether the current dialogs state should be notified.
+ */
+ public void setSipDialogsListener(SipDialogsStateListener listener,
+ boolean isNeedNotify) {
+ mSipSessionTracker.setSipDialogsListener(listener, isNeedNotify);
+ }
+
private void logi(String log) {
Log.i(SipTransportController.LOG_TAG, LOG_TAG + "[" + mSubId + "] " + log);
mLocalLog.log("[I] " + log);
diff --git a/testapps/TestRcsApp/OWNERS b/testapps/TestRcsApp/OWNERS
index 1d0d52b..4261109 100644
--- a/testapps/TestRcsApp/OWNERS
+++ b/testapps/TestRcsApp/OWNERS
@@ -1,3 +1,5 @@
-allenwtsu@google.com
-calvinpan@google.com
-jamescflin@google.com
+breadley@google.com
+joonhunshin@google.com
+donaldahn@google.com
+hhshin@google.com
+schie@google.com
diff --git a/testapps/TestServerApp/.gitignore b/testapps/TestServerApp/.gitignore
deleted file mode 100644
index aa724b7..0000000
--- a/testapps/TestServerApp/.gitignore
+++ /dev/null
@@ -1,15 +0,0 @@
-*.iml
-.gradle
-/local.properties
-/.idea/caches
-/.idea/libraries
-/.idea/modules.xml
-/.idea/workspace.xml
-/.idea/navEditor.xml
-/.idea/assetWizardSettings.xml
-.DS_Store
-/build
-/captures
-.externalNativeBuild
-.cxx
-local.properties
diff --git a/testapps/TestServerApp/.idea/.gitignore b/testapps/TestServerApp/.idea/.gitignore
deleted file mode 100644
index 26d3352..0000000
--- a/testapps/TestServerApp/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/testapps/TestServerApp/.idea/compiler.xml b/testapps/TestServerApp/.idea/compiler.xml
deleted file mode 100644
index fb7f4a8..0000000
--- a/testapps/TestServerApp/.idea/compiler.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="CompilerConfiguration">
- <bytecodeTargetLevel target="11" />
- </component>
-</project>
\ No newline at end of file
diff --git a/testapps/TestServerApp/.idea/gradle.xml b/testapps/TestServerApp/.idea/gradle.xml
deleted file mode 100644
index a2d7c21..0000000
--- a/testapps/TestServerApp/.idea/gradle.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="GradleMigrationSettings" migrationVersion="1" />
- <component name="GradleSettings">
- <option name="linkedExternalProjectsSettings">
- <GradleProjectSettings>
- <option name="testRunner" value="GRADLE" />
- <option name="distributionType" value="DEFAULT_WRAPPED" />
- <option name="externalProjectPath" value="$PROJECT_DIR$" />
- <option name="modules">
- <set>
- <option value="$PROJECT_DIR$" />
- <option value="$PROJECT_DIR$/app" />
- </set>
- </option>
- </GradleProjectSettings>
- </option>
- </component>
-</project>
\ No newline at end of file
diff --git a/testapps/TestServerApp/.idea/misc.xml b/testapps/TestServerApp/.idea/misc.xml
deleted file mode 100644
index 7c85865..0000000
--- a/testapps/TestServerApp/.idea/misc.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="ExternalStorageConfigurationManager" enabled="true" />
- <component name="NullableNotNullManager">
- <option name="myDefaultNullable" value="androidx.annotation.Nullable" />
- <option name="myDefaultNotNull" value="androidx.annotation.NonNull" />
- <option name="myNullables">
- <value>
- <list size="15">
- <item index="0" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
- <item index="1" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
- <item index="2" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
- <item index="3" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" />
- <item index="4" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
- <item index="5" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
- <item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
- <item index="7" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
- <item index="8" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
- <item index="9" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
- <item index="10" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
- <item index="11" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
- <item index="12" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
- <item index="13" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
- <item index="14" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
- </list>
- </value>
- </option>
- <option name="myNotNulls">
- <value>
- <list size="14">
- <item index="0" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
- <item index="1" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
- <item index="2" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
- <item index="3" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
- <item index="4" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
- <item index="5" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
- <item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
- <item index="7" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
- <item index="8" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
- <item index="9" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
- <item index="10" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
- <item index="11" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
- <item index="12" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
- <item index="13" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
- </list>
- </value>
- </option>
- </component>
- <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="JDK" project-jdk-type="JavaSDK">
- <output url="file://$PROJECT_DIR$/build/classes" />
- </component>
- <component name="ProjectType">
- <option name="id" value="Android" />
- </component>
-</project>
\ No newline at end of file
diff --git a/testapps/TestServerApp/.idea/vcs.xml b/testapps/TestServerApp/.idea/vcs.xml
deleted file mode 100644
index 47fe944..0000000
--- a/testapps/TestServerApp/.idea/vcs.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
- <component name="IssueNavigationConfiguration">
- <option name="links">
- <list>
- <IssueNavigationLink>
- <option name="issueRegexp" value="\bb/(\d+)(#\w+)?\b" />
- <option name="linkRegexp" value="https://buganizer.corp.google.com/issues/$1$2" />
- </IssueNavigationLink>
- <IssueNavigationLink>
- <option name="issueRegexp" value="\b(?:BUG=|FIXED=)(\d+)\b" />
- <option name="linkRegexp" value="https://buganizer.corp.google.com/issues/$1" />
- </IssueNavigationLink>
- <IssueNavigationLink>
- <option name="issueRegexp" value="\b(?:cl/|cr/|OCL=|DIFFBASE=|ROLLBACK_OF=)(\d+)\b" />
- <option name="linkRegexp" value="https://critique.corp.google.com/$1" />
- </IssueNavigationLink>
- <IssueNavigationLink>
- <option name="issueRegexp" value="\bomg/(\d+)\b" />
- <option name="linkRegexp" value="https://omg.corp.google.com/$1" />
- </IssueNavigationLink>
- <IssueNavigationLink>
- <option name="issueRegexp" value="\b(?:go/|goto/)([^,.<>()"\s]+(?:[.,][^,.<>()"\s]+)*)" />
- <option name="linkRegexp" value="https://goto.google.com/$1" />
- </IssueNavigationLink>
- <IssueNavigationLink>
- <option name="issueRegexp" value="\bcs/([^\s]+[\w$])" />
- <option name="linkRegexp" value="https://cs.corp.google.com/search/?q=$1" />
- </IssueNavigationLink>
- <IssueNavigationLink>
- <option name="issueRegexp" value="(LINT\.IfChange)|(LINT\.ThenChange)" />
- <option name="linkRegexp" value="https://goto.google.com/ifthisthenthatlint" />
- </IssueNavigationLink>
- </list>
- </option>
- </component>
-</project>
\ No newline at end of file
diff --git a/testapps/TestServerApp/app/.gitignore b/testapps/TestServerApp/app/.gitignore
deleted file mode 100644
index 42afabf..0000000
--- a/testapps/TestServerApp/app/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
\ No newline at end of file
diff --git a/testapps/TestServerApp/app/build.gradle b/testapps/TestServerApp/app/build.gradle
deleted file mode 100644
index 64a8ed9..0000000
--- a/testapps/TestServerApp/app/build.gradle
+++ /dev/null
@@ -1,40 +0,0 @@
-plugins {
- id 'com.android.application'
-}
-
-android {
- compileSdkPreview "android-Tiramisu"
-
- defaultConfig {
- applicationId "com.google.android.testserverapp"
- minSdkPreview "Tiramisu"
- targetSdkPreview "Tiramisu"
- versionCode 1
- versionName "1.0"
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
-
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-}
-
-dependencies {
-
- implementation 'androidx.appcompat:appcompat:1.4.1'
- implementation 'com.google.android.material:material:1.5.0'
- implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
- implementation 'androidx.legacy:legacy-support-v4:1.0.0'
- testImplementation 'junit:junit:4.+'
- androidTestImplementation 'androidx.test.ext:junit:1.1.3'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
- implementation 'com.squareup.okhttp3:okhttp:3.10.0'
-}
\ No newline at end of file
diff --git a/testapps/TestServerApp/app/proguard-rules.pro b/testapps/TestServerApp/app/proguard-rules.pro
deleted file mode 100644
index 481bb43..0000000
--- a/testapps/TestServerApp/app/proguard-rules.pro
+++ /dev/null
@@ -1,21 +0,0 @@
-# Add project specific ProGuard rules here.
-# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/testapps/TestServerApp/app/src/androidTest/java/com/google/android/testserverapp/ExampleInstrumentedTest.java b/testapps/TestServerApp/app/src/androidTest/java/com/google/android/testserverapp/ExampleInstrumentedTest.java
deleted file mode 100644
index 555bec9..0000000
--- a/testapps/TestServerApp/app/src/androidTest/java/com/google/android/testserverapp/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.google.android.testserverapp;
-
-import android.content.Context;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
-
- @Test
- public void useAppContext() {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
- assertEquals("com.google.android.testserverapp", appContext.getPackageName());
- }
-}
\ No newline at end of file
diff --git a/testapps/TestServerApp/app/src/main/Android.bp b/testapps/TestServerApp/app/src/main/Android.bp
deleted file mode 100644
index 1605962..0000000
--- a/testapps/TestServerApp/app/src/main/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-package {
- // See: http://go/android-license-faq
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_import {
- name: "sun-http-server",
- jars: ["libs/http-2.2.1.jar", "libs/sun-common-server.jar"],
-}
-
-android_app {
- name: "TestServerApp",
- srcs: [
- "java/com/google/android/testserverapp/*.java",
- ],
- static_libs: [
- "androidx-constraintlayout_constraintlayout",
- "androidx.appcompat_appcompat",
- "sun-http-server",
- ],
- libs: ["org.apache.http.legacy"],
- certificate: "platform",
- privileged: true,
- product_specific: true,
- sdk_version: "system_current",
- min_sdk_version: "30",
- optimize: {
- proguard_flags_files: ["proguard.flags"],
- },
-}
diff --git a/testapps/TestServerApp/app/src/main/AndroidManifest.xml b/testapps/TestServerApp/app/src/main/AndroidManifest.xml
deleted file mode 100644
index 3c91a82..0000000
--- a/testapps/TestServerApp/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.google.android.testserverapp">
-
- <uses-permission android:name="android.permission.INTERNET"/>
-
- <application
- android:allowBackup="true"
- android:dataExtractionRules="@xml/data_extraction_rules"
- android:fullBackupContent="@xml/backup_rules"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/Theme.AppCompat"
- android:versionCode="34">
- <activity
- android:name=".MainActivity"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
-
- <meta-data
- android:name="android.app.lib_name"
- android:value="" />
- </activity>
- </application>
-
-</manifest>
\ No newline at end of file
diff --git a/testapps/TestServerApp/app/src/main/java/com/google/android/testserverapp/MainActivity.java b/testapps/TestServerApp/app/src/main/java/com/google/android/testserverapp/MainActivity.java
deleted file mode 100644
index b0d49ef..0000000
--- a/testapps/TestServerApp/app/src/main/java/com/google/android/testserverapp/MainActivity.java
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.testserverapp;
-
-import android.os.Bundle;
-import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.TextView;
-import androidx.appcompat.app.AppCompatActivity;
-import com.sun.net.httpserver.Headers;
-import com.sun.net.httpserver.HttpExchange;
-import com.sun.net.httpserver.HttpHandler;
-import com.sun.net.httpserver.HttpServer;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.net.InetSocketAddress;
-import java.util.List;
-import java.util.Map.Entry;
-import java.util.concurrent.Executors;
-
-public class MainActivity extends AppCompatActivity {
- private static final String TAG = "TestServerApp";
- private static final int SERVER_PORT = 5555;
-
- private HttpServer mHttpServer = null;
- private boolean mIsServerUp = false;
- private int mEntitlementStatus = 1;
- private int mProvisionStatus = 1;
- private int mResponseCount = 0;
-
- private Button mServerButton;
- private TextView mServerStatusTextView, mClientRequestTextView;
- private AdapterView mEntitlementStatusSpinner, mProvisionStatusSpinner;
-
- private HttpHandler mHttpHandler = new HttpHandler() {
- @Override
- public void handle(HttpExchange httpExchange) throws IOException {
- String method = httpExchange.getRequestMethod();
- switch (method) {
- case "GET":
- case "POST":
- updateClientRequestTextView("Client Request: received a request from client");
- Log.d(TAG, "Client Request: received a request from client, requestHeaders = "
- + httpHeadersToString(httpExchange.getRequestHeaders()));
-
- sendResponseToClient(httpExchange, getTS43Response(), 200);
- break;
- default:
- Log.d(TAG, "Request method = " + method);
- }
- }
- };
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
- mServerStatusTextView = findViewById(R.id.serverStatusTextView);
- mClientRequestTextView = findViewById(R.id.clientRequestTextView);
- mServerButton = findViewById(R.id.serverButton);
- mServerButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- if (mIsServerUp) {
- stopServer();
- mIsServerUp = false;
- } else {
- startServer(SERVER_PORT);
- mIsServerUp = true;
- }
- }
- });
-
- mEntitlementStatusSpinner = findViewById(R.id.entitlementStatusSpinner);
- ArrayAdapter<CharSequence> entitlementArrayAdapter = ArrayAdapter.createFromResource(this,
- R.array.entitlement_status, android.R.layout.simple_spinner_item);
- entitlementArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_item);
- mEntitlementStatusSpinner.setAdapter(entitlementArrayAdapter);
- mEntitlementStatusSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- updateEntitlementStatus(parent.getItemAtPosition(position).toString());
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> parent) {}
- });
-
- mProvisionStatusSpinner = findViewById(R.id.provisionStatusSpinner);
- ArrayAdapter<CharSequence> provisionArrayAdapter = ArrayAdapter.createFromResource(this,
- R.array.provision_status, android.R.layout.simple_spinner_item);
- entitlementArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_item);
- mProvisionStatusSpinner.setAdapter(provisionArrayAdapter);
- mProvisionStatusSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- updateProvisionStatus(parent.getItemAtPosition(position).toString());
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> parent) {}
- });
- }
-
- private void startServer(int port) {
- try {
- mHttpServer = HttpServer.create(new InetSocketAddress(port), 0);
- mHttpServer.setExecutor(Executors.newCachedThreadPool());
-
- mHttpServer.createContext("/", mHttpHandler);
- mHttpServer.createContext("/index", mHttpHandler);
-
- mHttpServer.start();
-
- mServerStatusTextView.setText(R.string.server_running);
- mServerButton.setText(R.string.stop_server);
- } catch (IOException e) {
- Log.d(TAG, "Exception in startServer, e = " + e);
- }
- }
-
- private void stopServer() {
- if (mHttpServer != null) {
- mHttpServer.stop(0);
-
- mServerStatusTextView.setText(R.string.server_down);
- mServerButton.setText(R.string.start_server);
- }
- }
-
- private void sendResponseToClient(HttpExchange httpExchange, String message, int responseCode) {
- try {
- httpExchange.sendResponseHeaders(responseCode, message.length());
- OutputStream os = httpExchange.getResponseBody();
- os.write(message.getBytes());
- os.close();
-
- Log.d(TAG, "Sent a response to client, message = " + message);
- updateClientRequestTextView("Client Request: Sent " + ++mResponseCount
- + " responses to the clients");
- } catch (IOException e) {
- Log.d(TAG, "Exception in sendResponseToClient, e = " + e);
- updateClientRequestTextView("Client Request: Exception in sendResponseToClient!!!");
- }
- }
-
- private String httpHeadersToString(Headers headers) {
- StringBuilder sb = new StringBuilder();
- for (Entry<String, List<String>> entry : headers.entrySet()) {
- sb.append("{" + entry.getKey() + ":");
- for (String str : entry.getValue()) {
- sb.append(str + ",");
- }
- sb.append("}");
- }
- return sb.toString();
- }
-
- private String getTS43Response() {
- return "{"
- + " \"Vers\":{"
- + " \"version\": \"1\","
- + " \"validity\": \"1728000\""
- + " },"
- + " \"Token\":{"
- + " \"token\": \"kZYfCEpSsMr88KZVmab5UsZVzl+nWSsX\""
- + " },"
- + " \"ap2012\":{"
- + " \"EntitlementStatus\": " + mEntitlementStatus + ","
- + " \"ServiceFlow_URL\": \"file:///android_asset/slice_purchase_test.html\","
- + " \"ServiceFlow_UserData\": \"PostData=U6%2FbQ%2BEP&amp;l=en_US\","
- + " \"ProvStatus\": "+ mProvisionStatus + ","
- + " \"ProvTimeLeft\": 0"
- + " },"
- + " \"eap-relay-packet\":\"EapAkaChallengeRequest\""
- + "}";
- }
-
- private void updateClientRequestTextView(String status) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mClientRequestTextView.setText(status);
- }
- });
- }
-
- private void updateEntitlementStatus(String status) {
- switch (status) {
- case "Disabled":
- mEntitlementStatus = 0;
- break;
- case "Enabled":
- mEntitlementStatus = 1;
- break;
- case "Incompatible":
- mEntitlementStatus = 2;
- break;
- case "Provisioning":
- mEntitlementStatus = 3;
- break;
- case "Included":
- mEntitlementStatus = 4;
- break;
- }
- mClientRequestTextView.setText("Entitlement Status is set to \"" + status + "\"");
- }
-
- private void updateProvisionStatus(String status) {
- switch (status) {
- case "Not Provisioned":
- mProvisionStatus = 0;
- break;
- case "Provisioned":
- mProvisionStatus = 1;
- break;
- case "Not Required":
- mProvisionStatus = 2;
- break;
- case "In Progress":
- mProvisionStatus = 3;
- break;
- }
- mClientRequestTextView.setText("Provision Status is set to \"" + status + "\"");
- }
-}
diff --git a/testapps/TestServerApp/app/src/main/libs/LICENSE b/testapps/TestServerApp/app/src/main/libs/LICENSE
deleted file mode 100644
index 3d33284..0000000
--- a/testapps/TestServerApp/app/src/main/libs/LICENSE
+++ /dev/null
@@ -1,201 +0,0 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
\ No newline at end of file
diff --git a/testapps/TestServerApp/app/src/main/libs/http-2.2.1.jar b/testapps/TestServerApp/app/src/main/libs/http-2.2.1.jar
deleted file mode 100644
index 6e2b44e..0000000
--- a/testapps/TestServerApp/app/src/main/libs/http-2.2.1.jar
+++ /dev/null
Binary files differ
diff --git a/testapps/TestServerApp/app/src/main/libs/sun-common-server.jar b/testapps/TestServerApp/app/src/main/libs/sun-common-server.jar
deleted file mode 100644
index ca7127f..0000000
--- a/testapps/TestServerApp/app/src/main/libs/sun-common-server.jar
+++ /dev/null
Binary files differ
diff --git a/testapps/TestServerApp/app/src/main/proguard.flags b/testapps/TestServerApp/app/src/main/proguard.flags
deleted file mode 100644
index 4eefde6..0000000
--- a/testapps/TestServerApp/app/src/main/proguard.flags
+++ /dev/null
@@ -1,4 +0,0 @@
--dontobfuscate
--dontoptimize
-
--keep class com.google.android.testserverapp.*
diff --git a/testapps/TestServerApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/testapps/TestServerApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
deleted file mode 100644
index 966abaf..0000000
--- a/testapps/TestServerApp/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt"
- android:width="108dp"
- android:height="108dp"
- android:viewportHeight="108"
- android:viewportWidth="108">
- <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
- <aapt:attr name="android:fillColor">
- <gradient
- android:endX="85.84757"
- android:endY="92.4963"
- android:startX="42.9492"
- android:startY="49.59793"
- android:type="linear">
- <item
- android:color="#44000000"
- android:offset="0.0" />
- <item
- android:color="#00000000"
- android:offset="1.0" />
- </gradient>
- </aapt:attr>
- </path>
- <path
- android:fillColor="#FFFFFF"
- android:fillType="nonZero"
- android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
- android:strokeColor="#00000000"
- android:strokeWidth="1" />
-</vector>
\ No newline at end of file
diff --git a/testapps/TestServerApp/app/src/main/res/drawable/ic_launcher_background.xml b/testapps/TestServerApp/app/src/main/res/drawable/ic_launcher_background.xml
deleted file mode 100644
index 61bb79e..0000000
--- a/testapps/TestServerApp/app/src/main/res/drawable/ic_launcher_background.xml
+++ /dev/null
@@ -1,170 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="108dp"
- android:height="108dp"
- android:viewportHeight="108"
- android:viewportWidth="108">
- <path
- android:fillColor="#3DDC84"
- android:pathData="M0,0h108v108h-108z" />
- <path
- android:fillColor="#00000000"
- android:pathData="M9,0L9,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M19,0L19,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M29,0L29,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M39,0L39,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M49,0L49,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M59,0L59,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M69,0L69,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M79,0L79,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M89,0L89,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M99,0L99,108"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,9L108,9"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,19L108,19"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,29L108,29"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,39L108,39"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,49L108,49"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,59L108,59"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,69L108,69"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,79L108,79"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,89L108,89"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M0,99L108,99"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M19,29L89,29"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M19,39L89,39"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M19,49L89,49"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M19,59L89,59"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M19,69L89,69"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M19,79L89,79"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M29,19L29,89"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M39,19L39,89"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M49,19L49,89"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M59,19L59,89"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M69,19L69,89"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
- <path
- android:fillColor="#00000000"
- android:pathData="M79,19L79,89"
- android:strokeColor="#33FFFFFF"
- android:strokeWidth="0.8" />
-</vector>
diff --git a/testapps/TestServerApp/app/src/main/res/layout/activity_main.xml b/testapps/TestServerApp/app/src/main/res/layout/activity_main.xml
deleted file mode 100644
index a4ca45a..0000000
--- a/testapps/TestServerApp/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
-
- android:layout_height="match_parent"
- tools:context=".MainActivity">
-
- <Button
- android:id="@+id/serverButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center|center_horizontal"
- android:text="@string/start_server"
- tools:layout_editor_absoluteX="124dp"
- tools:layout_editor_absoluteY="55dp" />
- <TextView
- android:id="@+id/serverStatusTextView"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:gravity="center"
- android:text="@string/server_down"
- android:textColor="#4CAF50"
- android:textSize="20sp"
- app:layout_constraintTop_toBottomOf="@id/serverButton"
- tools:layout_editor_absoluteX="0dp" />
- <TextView
- android:id="@+id/entitlementStatus"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="20dp"
- android:gravity="left"
- android:text="Entitlement Status:"
- android:textColor="#4CAF50"
- android:textSize="20sp"
- app:layout_constraintTop_toBottomOf="@id/serverStatusTextView"
- tools:layout_editor_absoluteX="0dp" />
- <Spinner
- android:id="@+id/entitlementStatusSpinner"
- android:layout_width="232dp"
- android:layout_height="wrap_content"
- android:layout_marginTop="15dp"
- android:gravity="left"
- android:textColor="#4CAF50"
- android:textSize="20sp"
- app:layout_constraintTop_toBottomOf="@id/entitlementStatus"
- tools:layout_editor_absoluteX="-195dp" />
- <TextView
- android:id="@+id/provisionStatus"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="20dp"
- android:gravity="left"
- android:text="Provision Status:"
- android:textColor="#4CAF50"
- android:textSize="20sp"
- app:layout_constraintTop_toBottomOf="@id/entitlementStatusSpinner"
- tools:layout_editor_absoluteX="0dp" />
- <Spinner
- android:id="@+id/provisionStatusSpinner"
- android:layout_width="233dp"
- android:layout_height="wrap_content"
- android:layout_marginTop="15dp"
- android:gravity="center"
- android:textColor="#4CAF50"
- android:textSize="20sp"
- app:layout_constraintTop_toBottomOf="@id/provisionStatus"
- tools:layout_editor_absoluteX="-195dp" />
- <TextView
- android:id="@+id/clientRequestTextView"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="20dp"
- android:gravity="center"
- android:text="Client Request:"
- android:textColor="#4CAF50"
- android:textSize="20sp"
- app:layout_constraintTop_toBottomOf="@id/provisionStatusSpinner"
- tools:layout_editor_absoluteX="0dp" />
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/testapps/TestServerApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/testapps/TestServerApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
deleted file mode 100644
index 03eed25..0000000
--- a/testapps/TestServerApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
- <background android:drawable="@drawable/ic_launcher_background" />
- <foreground android:drawable="@drawable/ic_launcher_foreground" />
-</adaptive-icon>
\ No newline at end of file
diff --git a/testapps/TestServerApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/testapps/TestServerApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
deleted file mode 100644
index 03eed25..0000000
--- a/testapps/TestServerApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
- <background android:drawable="@drawable/ic_launcher_background" />
- <foreground android:drawable="@drawable/ic_launcher_foreground" />
-</adaptive-icon>
\ No newline at end of file
diff --git a/testapps/TestServerApp/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/testapps/TestServerApp/app/src/main/res/mipmap-hdpi/ic_launcher.webp
deleted file mode 100644
index c209e78..0000000
--- a/testapps/TestServerApp/app/src/main/res/mipmap-hdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/testapps/TestServerApp/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/testapps/TestServerApp/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
deleted file mode 100644
index b2dfe3d..0000000
--- a/testapps/TestServerApp/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
+++ /dev/null
Binary files differ
diff --git a/testapps/TestServerApp/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/testapps/TestServerApp/app/src/main/res/mipmap-mdpi/ic_launcher.webp
deleted file mode 100644
index 4f0f1d6..0000000
--- a/testapps/TestServerApp/app/src/main/res/mipmap-mdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/testapps/TestServerApp/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/testapps/TestServerApp/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
deleted file mode 100644
index 62b611d..0000000
--- a/testapps/TestServerApp/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
+++ /dev/null
Binary files differ
diff --git a/testapps/TestServerApp/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/testapps/TestServerApp/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
deleted file mode 100644
index 948a307..0000000
--- a/testapps/TestServerApp/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/testapps/TestServerApp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/testapps/TestServerApp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
deleted file mode 100644
index 1b9a695..0000000
--- a/testapps/TestServerApp/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
+++ /dev/null
Binary files differ
diff --git a/testapps/TestServerApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/testapps/TestServerApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
deleted file mode 100644
index 28d4b77..0000000
--- a/testapps/TestServerApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/testapps/TestServerApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/testapps/TestServerApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 9287f50..0000000
--- a/testapps/TestServerApp/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
+++ /dev/null
Binary files differ
diff --git a/testapps/TestServerApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/testapps/TestServerApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
deleted file mode 100644
index aa7d642..0000000
--- a/testapps/TestServerApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/testapps/TestServerApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/testapps/TestServerApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 9126ae3..0000000
--- a/testapps/TestServerApp/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
+++ /dev/null
Binary files differ
diff --git a/testapps/TestServerApp/app/src/main/res/values-af/strings.xml b/testapps/TestServerApp/app/src/main/res/values-af/strings.xml
deleted file mode 100644
index d9a3978..0000000
--- a/testapps/TestServerApp/app/src/main/res/values-af/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2894617184221823208">"Toetsbediener-app"</string>
- <string name="action_settings" msgid="1335152369747372374">"Instellings"</string>
- <string name="server_running" msgid="2780193626090379172">"Bediener werk tans …"</string>
- <string name="stop_server" msgid="6192029827529013598">"Stop bediener"</string>
- <string name="server_down" msgid="1030249207496490556">"Bediener is af"</string>
- <string name="start_server" msgid="3878573341408591975">"Begin bediener"</string>
- <string-array name="entitlement_status">
- <item msgid="5560300387618996934">"Gedeaktiveer"</item>
- <item msgid="3193389681837907872">"Geaktiveer"</item>
- <item msgid="3124590179479393815">"Onversoenbaar"</item>
- <item msgid="1606753456265236910">"Stel tans op"</item>
- <item msgid="3930807209231347454">"Ingesluit"</item>
- </string-array>
- <string-array name="provision_status">
- <item msgid="3486273747926710021">"Nie opgestel nie"</item>
- <item msgid="7598231293776486217">"Opgestel"</item>
- <item msgid="3720547957514534185">"Nie vereis nie"</item>
- <item msgid="1264673582354896949">"Tans besig"</item>
- </string-array>
-</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-am/strings.xml b/testapps/TestServerApp/app/src/main/res/values-am/strings.xml
new file mode 100644
index 0000000..8ec544c
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-am/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"ቅንብሮች"</string>
+ <string name="server_running" msgid="2780193626090379172">"አገልጋይ እያሄደ ነው..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"አገልጋይን አቁም"</string>
+ <string name="server_down" msgid="1030249207496490556">"አገልጋይ አይሰራም"</string>
+ <string name="start_server" msgid="3878573341408591975">"አገልጋይን አስጀምር"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"ተሰናክሏል"</item>
+ <item msgid="3193389681837907872">"ነቅቷል"</item>
+ <item msgid="3124590179479393815">"ተኳሃኝ አይደለም"</item>
+ <item msgid="1606753456265236910">"በማቅረብ ላይ"</item>
+ <item msgid="3930807209231347454">"ተካትቷል"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"አልቀረበም"</item>
+ <item msgid="7598231293776486217">"ቀርቧል"</item>
+ <item msgid="3720547957514534185">"አያስፈልግም"</item>
+ <item msgid="1264673582354896949">"በሂደት ላይ"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-ar/strings.xml b/testapps/TestServerApp/app/src/main/res/values-ar/strings.xml
new file mode 100644
index 0000000..c901917
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-ar/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"الإعدادات"</string>
+ <string name="server_running" msgid="2780193626090379172">"الخادم قيد التشغيل…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"إيقاف الخادم"</string>
+ <string name="server_down" msgid="1030249207496490556">"الخادم معطّل"</string>
+ <string name="start_server" msgid="3878573341408591975">"بدء الخادم"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"غير مفعَّلة"</item>
+ <item msgid="3193389681837907872">"مفعّلة"</item>
+ <item msgid="3124590179479393815">"غير متوافق"</item>
+ <item msgid="1606753456265236910">"جارٍ توفير المتطلبات اللازمة"</item>
+ <item msgid="3930807209231347454">"متاحة"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"لم يتم توفير المتطلبات اللازمة"</item>
+ <item msgid="7598231293776486217">"توفير المتطلبات اللازمة"</item>
+ <item msgid="3720547957514534185">"غير مطلوب"</item>
+ <item msgid="1264673582354896949">"قيد التقدم"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-as/strings.xml b/testapps/TestServerApp/app/src/main/res/values-as/strings.xml
new file mode 100644
index 0000000..46ee915
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-as/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"ছেটিং"</string>
+ <string name="server_running" msgid="2780193626090379172">"ছাৰ্ভাৰটো চলি আছে..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"ছাৰ্ভাৰ বন্ধ কৰক"</string>
+ <string name="server_down" msgid="1030249207496490556">"ছাৰ্ভাৰটো কাৰ্যক্ষম হৈ থকা নাই"</string>
+ <string name="start_server" msgid="3878573341408591975">"ছাৰ্ভাৰ আৰম্ভ কৰক"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"অক্ষম কৰা আছে"</item>
+ <item msgid="3193389681837907872">"সক্ষম কৰা আছে"</item>
+ <item msgid="3124590179479393815">"অমিল"</item>
+ <item msgid="1606753456265236910">"প্ৰ’ভিজনিং"</item>
+ <item msgid="3930807209231347454">"অন্তৰ্ভুক্ত কৰা হ’ল"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"প্ৰ’ভিজন কৰা নাই"</item>
+ <item msgid="7598231293776486217">"প্ৰ’ভিজন কৰা হৈছে"</item>
+ <item msgid="3720547957514534185">"প্ৰয়োজনীয় নহয়"</item>
+ <item msgid="1264673582354896949">"চলি আছে"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-az/strings.xml b/testapps/TestServerApp/app/src/main/res/values-az/strings.xml
new file mode 100644
index 0000000..c7bdb24
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-az/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Ayarlar"</string>
+ <string name="server_running" msgid="2780193626090379172">"Server işləyir..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Serveri dayandırın"</string>
+ <string name="server_down" msgid="1030249207496490556">"Server işləmir"</string>
+ <string name="start_server" msgid="3878573341408591975">"Serveri başladın"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Deaktiv"</item>
+ <item msgid="3193389681837907872">"Aktiv"</item>
+ <item msgid="3124590179479393815">"Uyğun deyil"</item>
+ <item msgid="1606753456265236910">"Təmin edilir"</item>
+ <item msgid="3930807209231347454">"Daxildir"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Təmin edilmir"</item>
+ <item msgid="7598231293776486217">"Təmin edilib"</item>
+ <item msgid="3720547957514534185">"Tələb olunmur"</item>
+ <item msgid="1264673582354896949">"Davam edir"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-bs/strings.xml b/testapps/TestServerApp/app/src/main/res/values-b+sr+Latn/strings.xml
similarity index 72%
rename from testapps/TestServerApp/app/src/main/res/values-bs/strings.xml
rename to testapps/TestServerApp/app/src/main/res/values-b+sr+Latn/strings.xml
index f143153..62aeff1 100644
--- a/testapps/TestServerApp/app/src/main/res/values-bs/strings.xml
+++ b/testapps/TestServerApp/app/src/main/res/values-b+sr+Latn/strings.xml
@@ -2,22 +2,22 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
- <string name="action_settings" msgid="1335152369747372374">"Postavke"</string>
- <string name="server_running" msgid="2780193626090379172">"Server radi…"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Podešavanja"</string>
+ <string name="server_running" msgid="2780193626090379172">"Server je pokrenut…"</string>
<string name="stop_server" msgid="6192029827529013598">"Zaustavi server"</string>
- <string name="server_down" msgid="1030249207496490556">"Server ne radi"</string>
+ <string name="server_down" msgid="1030249207496490556">"Server je pao"</string>
<string name="start_server" msgid="3878573341408591975">"Pokreni server"</string>
<string-array name="entitlement_status">
<item msgid="5560300387618996934">"Onemogućeno"</item>
<item msgid="3193389681837907872">"Omogućeno"</item>
<item msgid="3124590179479393815">"Nekompatibilno"</item>
- <item msgid="1606753456265236910">"Dodjeljivanje"</item>
- <item msgid="3930807209231347454">"Uključeno"</item>
+ <item msgid="1606753456265236910">"Dodeljuje se"</item>
+ <item msgid="3930807209231347454">"Uvršteno"</item>
</string-array>
<string-array name="provision_status">
- <item msgid="3486273747926710021">"Nije dodijeljeno"</item>
- <item msgid="7598231293776486217">"Dodijeljeno"</item>
- <item msgid="3720547957514534185">"Nije potrebno"</item>
+ <item msgid="3486273747926710021">"Nije dodeljeno"</item>
+ <item msgid="7598231293776486217">"Dodeljeno"</item>
+ <item msgid="3720547957514534185">"Nije obavezno"</item>
<item msgid="1264673582354896949">"U toku"</item>
</string-array>
</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-be/strings.xml b/testapps/TestServerApp/app/src/main/res/values-be/strings.xml
new file mode 100644
index 0000000..5f1f581
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-be/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Налады"</string>
+ <string name="server_running" msgid="2780193626090379172">"Сервер працуе..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Спыніць сервер"</string>
+ <string name="server_down" msgid="1030249207496490556">"Сервер не працуе"</string>
+ <string name="start_server" msgid="3878573341408591975">"Запусціць сервер"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Адключана"</item>
+ <item msgid="3193389681837907872">"Уключана"</item>
+ <item msgid="3124590179479393815">"Адсутнічае сумяшчальнасць"</item>
+ <item msgid="1606753456265236910">"Ініцыялізацыя"</item>
+ <item msgid="3930807209231347454">"Уключана"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Не ініцыялізавана"</item>
+ <item msgid="7598231293776486217">"Ініцыялізавана"</item>
+ <item msgid="3720547957514534185">"Не патрабуецца"</item>
+ <item msgid="1264673582354896949">"Выконваецца"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-bg/strings.xml b/testapps/TestServerApp/app/src/main/res/values-bg/strings.xml
new file mode 100644
index 0000000..542d0f7
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-bg/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Настройки"</string>
+ <string name="server_running" msgid="2780193626090379172">"Сървърът работи..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Спиране на сървъра"</string>
+ <string name="server_down" msgid="1030249207496490556">"Сървърът не работи"</string>
+ <string name="start_server" msgid="3878573341408591975">"Стартиране на сървъра"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Деактивирано"</item>
+ <item msgid="3193389681837907872">"Активирано"</item>
+ <item msgid="3124590179479393815">"Несъвместимо"</item>
+ <item msgid="1606753456265236910">"Обезпечава се"</item>
+ <item msgid="3930807209231347454">"Включено"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Не е обезпечено"</item>
+ <item msgid="7598231293776486217">"Обезпечено"</item>
+ <item msgid="3720547957514534185">"Не е задължително"</item>
+ <item msgid="1264673582354896949">"В ход"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-bn/strings.xml b/testapps/TestServerApp/app/src/main/res/values-bn/strings.xml
new file mode 100644
index 0000000..7244308
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-bn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"সেটিংস"</string>
+ <string name="server_running" msgid="2780193626090379172">"সার্ভার রান করছে..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"সার্ভার বন্ধ করুন"</string>
+ <string name="server_down" msgid="1030249207496490556">"সার্ভার কাজ করছে না"</string>
+ <string name="start_server" msgid="3878573341408591975">"সার্ভার চালু করুন"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"বন্ধ আছে"</item>
+ <item msgid="3193389681837907872">"চালু আছে"</item>
+ <item msgid="3124590179479393815">"মানানসই নয়"</item>
+ <item msgid="1606753456265236910">"প্রস্তুতি চলছে"</item>
+ <item msgid="3930807209231347454">"অন্তর্ভুক্ত আছে"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"প্রস্তুত নেই"</item>
+ <item msgid="7598231293776486217">"প্রস্তুত আছে"</item>
+ <item msgid="3720547957514534185">"প্রয়োজন নেই"</item>
+ <item msgid="1264673582354896949">"কাজ চলছে"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-ca/strings.xml b/testapps/TestServerApp/app/src/main/res/values-ca/strings.xml
deleted file mode 100644
index 7b7a999..0000000
--- a/testapps/TestServerApp/app/src/main/res/values-ca/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
- <string name="action_settings" msgid="1335152369747372374">"Configuració"</string>
- <string name="server_running" msgid="2780193626090379172">"El servidor s\'està executant..."</string>
- <string name="stop_server" msgid="6192029827529013598">"Atura el servidor"</string>
- <string name="server_down" msgid="1030249207496490556">"El servidor no funciona"</string>
- <string name="start_server" msgid="3878573341408591975">"Inicia el servidor"</string>
- <string-array name="entitlement_status">
- <item msgid="5560300387618996934">"Desactivat"</item>
- <item msgid="3193389681837907872">"Activat"</item>
- <item msgid="3124590179479393815">"Incompatible"</item>
- <item msgid="1606753456265236910">"S\'està proporcionant"</item>
- <item msgid="3930807209231347454">"Inclòs"</item>
- </string-array>
- <string-array name="provision_status">
- <item msgid="3486273747926710021">"No proporcionat"</item>
- <item msgid="7598231293776486217">"Proporcionat"</item>
- <item msgid="3720547957514534185">"No obligatori"</item>
- <item msgid="1264673582354896949">"En curs"</item>
- </string-array>
-</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-cs/strings.xml b/testapps/TestServerApp/app/src/main/res/values-cs/strings.xml
new file mode 100644
index 0000000..c8dfd8d
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-cs/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Nastavení"</string>
+ <string name="server_running" msgid="2780193626090379172">"Server běží…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"Zastavit server"</string>
+ <string name="server_down" msgid="1030249207496490556">"Server je nedostupný"</string>
+ <string name="start_server" msgid="3878573341408591975">"Spustit server"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Vypnuto"</item>
+ <item msgid="3193389681837907872">"Zapnuto"</item>
+ <item msgid="3124590179479393815">"Nekompatibilní"</item>
+ <item msgid="1606753456265236910">"Zajišťování"</item>
+ <item msgid="3930807209231347454">"Zahrnuto"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Nezajištěno"</item>
+ <item msgid="7598231293776486217">"Zajištěno"</item>
+ <item msgid="3720547957514534185">"Nepovinné"</item>
+ <item msgid="1264673582354896949">"Probíhá"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-da/strings.xml b/testapps/TestServerApp/app/src/main/res/values-da/strings.xml
new file mode 100644
index 0000000..1d8e029
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-da/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"Testserverapp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Indstillinger"</string>
+ <string name="server_running" msgid="2780193626090379172">"Serveren kører…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"Stop server"</string>
+ <string name="server_down" msgid="1030249207496490556">"Serveren er nede"</string>
+ <string name="start_server" msgid="3878573341408591975">"Start server"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Deaktiveret"</item>
+ <item msgid="3193389681837907872">"Aktiveret"</item>
+ <item msgid="3124590179479393815">"Ikke kompatibel"</item>
+ <item msgid="1606753456265236910">"Provisionerer"</item>
+ <item msgid="3930807209231347454">"Inkluderet"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Ikke provisioneret"</item>
+ <item msgid="7598231293776486217">"Provisioneret"</item>
+ <item msgid="3720547957514534185">"Ikke påkrævet"</item>
+ <item msgid="1264673582354896949">"I gang"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-de/strings.xml b/testapps/TestServerApp/app/src/main/res/values-de/strings.xml
new file mode 100644
index 0000000..4adc332
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-de/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Einstellungen"</string>
+ <string name="server_running" msgid="2780193626090379172">"Server ist in Betrieb…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"Server anhalten"</string>
+ <string name="server_down" msgid="1030249207496490556">"Server ist ausgefallen"</string>
+ <string name="start_server" msgid="3878573341408591975">"Server starten"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Deaktiviert"</item>
+ <item msgid="3193389681837907872">"Aktiviert"</item>
+ <item msgid="3124590179479393815">"Nicht kompatibel"</item>
+ <item msgid="1606753456265236910">"Nutzerverwaltung"</item>
+ <item msgid="3930807209231347454">"Enthalten"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Noch nicht von einem Nutzer verwaltet"</item>
+ <item msgid="7598231293776486217">"Von einem Nutzer verwaltet"</item>
+ <item msgid="3720547957514534185">"Nicht erforderlich"</item>
+ <item msgid="1264673582354896949">"In Bearbeitung"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-el/strings.xml b/testapps/TestServerApp/app/src/main/res/values-el/strings.xml
new file mode 100644
index 0000000..540500d
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-el/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Ρυθμίσεις"</string>
+ <string name="server_running" msgid="2780193626090379172">"Ο διακομιστής λειτουργεί…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"Διακοπή διακομιστή"</string>
+ <string name="server_down" msgid="1030249207496490556">"Ο διακομιστής είναι εκτός λειτουργίας"</string>
+ <string name="start_server" msgid="3878573341408591975">"Έναρξη διακομιστή"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Ανενεργό"</item>
+ <item msgid="3193389681837907872">"Ενεργό"</item>
+ <item msgid="3124590179479393815">"Μη συμβατό"</item>
+ <item msgid="1606753456265236910">"Παροχή"</item>
+ <item msgid="3930807209231347454">"Περιλαμβάνεται"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Χωρίς παροχή"</item>
+ <item msgid="7598231293776486217">"Παρέχεται"</item>
+ <item msgid="3720547957514534185">"Δεν απαιτείται"</item>
+ <item msgid="1264673582354896949">"Σε εξέλιξη"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-en-rAU/strings.xml b/testapps/TestServerApp/app/src/main/res/values-en-rAU/strings.xml
deleted file mode 100644
index 9a8beab..0000000
--- a/testapps/TestServerApp/app/src/main/res/values-en-rAU/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
- <string name="action_settings" msgid="1335152369747372374">"Settings"</string>
- <string name="server_running" msgid="2780193626090379172">"Server is running..."</string>
- <string name="stop_server" msgid="6192029827529013598">"Stop server"</string>
- <string name="server_down" msgid="1030249207496490556">"Server is down"</string>
- <string name="start_server" msgid="3878573341408591975">"Start server"</string>
- <string-array name="entitlement_status">
- <item msgid="5560300387618996934">"Disabled"</item>
- <item msgid="3193389681837907872">"Enabled"</item>
- <item msgid="3124590179479393815">"Incompatible"</item>
- <item msgid="1606753456265236910">"Provisioning"</item>
- <item msgid="3930807209231347454">"Included"</item>
- </string-array>
- <string-array name="provision_status">
- <item msgid="3486273747926710021">"Not provisioned"</item>
- <item msgid="7598231293776486217">"Provisioned"</item>
- <item msgid="3720547957514534185">"Not required"</item>
- <item msgid="1264673582354896949">"In progress"</item>
- </string-array>
-</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-en-rGB/strings.xml b/testapps/TestServerApp/app/src/main/res/values-en-rGB/strings.xml
deleted file mode 100644
index 9a8beab..0000000
--- a/testapps/TestServerApp/app/src/main/res/values-en-rGB/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
- <string name="action_settings" msgid="1335152369747372374">"Settings"</string>
- <string name="server_running" msgid="2780193626090379172">"Server is running..."</string>
- <string name="stop_server" msgid="6192029827529013598">"Stop server"</string>
- <string name="server_down" msgid="1030249207496490556">"Server is down"</string>
- <string name="start_server" msgid="3878573341408591975">"Start server"</string>
- <string-array name="entitlement_status">
- <item msgid="5560300387618996934">"Disabled"</item>
- <item msgid="3193389681837907872">"Enabled"</item>
- <item msgid="3124590179479393815">"Incompatible"</item>
- <item msgid="1606753456265236910">"Provisioning"</item>
- <item msgid="3930807209231347454">"Included"</item>
- </string-array>
- <string-array name="provision_status">
- <item msgid="3486273747926710021">"Not provisioned"</item>
- <item msgid="7598231293776486217">"Provisioned"</item>
- <item msgid="3720547957514534185">"Not required"</item>
- <item msgid="1264673582354896949">"In progress"</item>
- </string-array>
-</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-en-rIN/strings.xml b/testapps/TestServerApp/app/src/main/res/values-en-rIN/strings.xml
deleted file mode 100644
index 9a8beab..0000000
--- a/testapps/TestServerApp/app/src/main/res/values-en-rIN/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
- <string name="action_settings" msgid="1335152369747372374">"Settings"</string>
- <string name="server_running" msgid="2780193626090379172">"Server is running..."</string>
- <string name="stop_server" msgid="6192029827529013598">"Stop server"</string>
- <string name="server_down" msgid="1030249207496490556">"Server is down"</string>
- <string name="start_server" msgid="3878573341408591975">"Start server"</string>
- <string-array name="entitlement_status">
- <item msgid="5560300387618996934">"Disabled"</item>
- <item msgid="3193389681837907872">"Enabled"</item>
- <item msgid="3124590179479393815">"Incompatible"</item>
- <item msgid="1606753456265236910">"Provisioning"</item>
- <item msgid="3930807209231347454">"Included"</item>
- </string-array>
- <string-array name="provision_status">
- <item msgid="3486273747926710021">"Not provisioned"</item>
- <item msgid="7598231293776486217">"Provisioned"</item>
- <item msgid="3720547957514534185">"Not required"</item>
- <item msgid="1264673582354896949">"In progress"</item>
- </string-array>
-</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-en-rXC/strings.xml b/testapps/TestServerApp/app/src/main/res/values-en-rXC/strings.xml
deleted file mode 100644
index c6f5304..0000000
--- a/testapps/TestServerApp/app/src/main/res/values-en-rXC/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
- <string name="action_settings" msgid="1335152369747372374">"Settings"</string>
- <string name="server_running" msgid="2780193626090379172">"Server is running..."</string>
- <string name="stop_server" msgid="6192029827529013598">"Stop Server"</string>
- <string name="server_down" msgid="1030249207496490556">"Server is down"</string>
- <string name="start_server" msgid="3878573341408591975">"Start Server"</string>
- <string-array name="entitlement_status">
- <item msgid="5560300387618996934">"Disabled"</item>
- <item msgid="3193389681837907872">"Enabled"</item>
- <item msgid="3124590179479393815">"Incompatible"</item>
- <item msgid="1606753456265236910">"Provisioning"</item>
- <item msgid="3930807209231347454">"Included"</item>
- </string-array>
- <string-array name="provision_status">
- <item msgid="3486273747926710021">"Not Provisioned"</item>
- <item msgid="7598231293776486217">"Provisioned"</item>
- <item msgid="3720547957514534185">"Not Required"</item>
- <item msgid="1264673582354896949">"In Progress"</item>
- </string-array>
-</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-es-rUS/strings.xml b/testapps/TestServerApp/app/src/main/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..50c9ff2
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-es-rUS/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Configuración"</string>
+ <string name="server_running" msgid="2780193626090379172">"El servidor se está ejecutando…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"Detener el servidor"</string>
+ <string name="server_down" msgid="1030249207496490556">"El servidor se encuentra inactivo"</string>
+ <string name="start_server" msgid="3878573341408591975">"Iniciar el servidor"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Inhabilitado"</item>
+ <item msgid="3193389681837907872">"Habilitado"</item>
+ <item msgid="3124590179479393815">"Incompatible"</item>
+ <item msgid="1606753456265236910">"Aprovisionando"</item>
+ <item msgid="3930807209231347454">"Incluido"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"No aprovisionado"</item>
+ <item msgid="7598231293776486217">"Aprovisionado"</item>
+ <item msgid="3720547957514534185">"No se necesita"</item>
+ <item msgid="1264673582354896949">"En curso"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-es/strings.xml b/testapps/TestServerApp/app/src/main/res/values-es/strings.xml
new file mode 100644
index 0000000..002fca7
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-es/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Ajustes"</string>
+ <string name="server_running" msgid="2780193626090379172">"El servidor se está ejecutando..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Detener servidor"</string>
+ <string name="server_down" msgid="1030249207496490556">"El servidor no está operativo"</string>
+ <string name="start_server" msgid="3878573341408591975">"Iniciar servidor"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Inhabilitado"</item>
+ <item msgid="3193389681837907872">"Habilitado"</item>
+ <item msgid="3124590179479393815">"No compatible"</item>
+ <item msgid="1606753456265236910">"En aprovisionamiento"</item>
+ <item msgid="3930807209231347454">"Incluido"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"No aprovisionado"</item>
+ <item msgid="7598231293776486217">"Aprovisionado"</item>
+ <item msgid="3720547957514534185">"No se requiere"</item>
+ <item msgid="1264673582354896949">"En curso"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-et/strings.xml b/testapps/TestServerApp/app/src/main/res/values-et/strings.xml
new file mode 100644
index 0000000..49c3209
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-et/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Seaded"</string>
+ <string name="server_running" msgid="2780193626090379172">"Server töötab ..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Peata server"</string>
+ <string name="server_down" msgid="1030249207496490556">"Serveris on katkestus"</string>
+ <string name="start_server" msgid="3878573341408591975">"Käivita server"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Keelatud"</item>
+ <item msgid="3193389681837907872">"Lubatud"</item>
+ <item msgid="3124590179479393815">"Ühildumatu"</item>
+ <item msgid="1606753456265236910">"Ettevalmistamine"</item>
+ <item msgid="3930807209231347454">"Kaasas"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Ettevalmistamata"</item>
+ <item msgid="7598231293776486217">"Ettevalmistatud"</item>
+ <item msgid="3720547957514534185">"Pole nõutav"</item>
+ <item msgid="1264673582354896949">"Töötluses"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-eu/strings.xml b/testapps/TestServerApp/app/src/main/res/values-eu/strings.xml
new file mode 100644
index 0000000..70f5423
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-eu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Ezarpenak"</string>
+ <string name="server_running" msgid="2780193626090379172">"Zerbitzaria abian da…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"Gelditu zerbitzaria"</string>
+ <string name="server_down" msgid="1030249207496490556">"Zerbitzaria ez dabil"</string>
+ <string name="start_server" msgid="3878573341408591975">"Abiarazi zerbitzaria"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Desgaituta"</item>
+ <item msgid="3193389681837907872">"Gaituta"</item>
+ <item msgid="3124590179479393815">"Bateraezina"</item>
+ <item msgid="1606753456265236910">"Hornitzen"</item>
+ <item msgid="3930807209231347454">"Barne"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Hornitu gabe"</item>
+ <item msgid="7598231293776486217">"Hornituta"</item>
+ <item msgid="3720547957514534185">"Ez da beharrezkoa"</item>
+ <item msgid="1264673582354896949">"Abian"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-fa/strings.xml b/testapps/TestServerApp/app/src/main/res/values-fa/strings.xml
deleted file mode 100644
index 17b4d08..0000000
--- a/testapps/TestServerApp/app/src/main/res/values-fa/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
- <string name="action_settings" msgid="1335152369747372374">"تنظیمات"</string>
- <string name="server_running" msgid="2780193626090379172">"سرور درحال اجرا است…"</string>
- <string name="stop_server" msgid="6192029827529013598">"توقف سرور"</string>
- <string name="server_down" msgid="1030249207496490556">"سرور ازکار افتاده است"</string>
- <string name="start_server" msgid="3878573341408591975">"شروع سرور"</string>
- <string-array name="entitlement_status">
- <item msgid="5560300387618996934">"غیرفعال"</item>
- <item msgid="3193389681837907872">"فعال"</item>
- <item msgid="3124590179479393815">"ناسازگار"</item>
- <item msgid="1606753456265236910">"درحال ارائه دسترسی"</item>
- <item msgid="3930807209231347454">"لحاظشده"</item>
- </string-array>
- <string-array name="provision_status">
- <item msgid="3486273747926710021">"ارائهنشده"</item>
- <item msgid="7598231293776486217">"ارائهشده"</item>
- <item msgid="3720547957514534185">"الزامی نیست"</item>
- <item msgid="1264673582354896949">"درحال انجام"</item>
- </string-array>
-</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-fi/strings.xml b/testapps/TestServerApp/app/src/main/res/values-fi/strings.xml
new file mode 100644
index 0000000..9117e0c
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-fi/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Asetukset"</string>
+ <string name="server_running" msgid="2780193626090379172">"Palvelin on käytössä..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Keskeytä palvelin"</string>
+ <string name="server_down" msgid="1030249207496490556">"Palvelin on poissa käytöstä"</string>
+ <string name="start_server" msgid="3878573341408591975">"Käynnistä palvelin"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Ei käytössä"</item>
+ <item msgid="3193389681837907872">"Käytössä"</item>
+ <item msgid="3124590179479393815">"Yhteensopimaton"</item>
+ <item msgid="1606753456265236910">"Käsitellään"</item>
+ <item msgid="3930807209231347454">"Sisältyy"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Ei käsitelty"</item>
+ <item msgid="7598231293776486217">"Käsitelty"</item>
+ <item msgid="3720547957514534185">"Valinnainen"</item>
+ <item msgid="1264673582354896949">"Käynnissä"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-fr-rCA/strings.xml b/testapps/TestServerApp/app/src/main/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..97fd06a
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-fr-rCA/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Paramètres"</string>
+ <string name="server_running" msgid="2780193626090379172">"Le serveur est en cours d\'exécution…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"Arrêter le serveur"</string>
+ <string name="server_down" msgid="1030249207496490556">"Le serveur est en panne"</string>
+ <string name="start_server" msgid="3878573341408591975">"Démarrer le serveur"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Désactivé"</item>
+ <item msgid="3193389681837907872">"Activé"</item>
+ <item msgid="3124590179479393815">"Incompatible"</item>
+ <item msgid="1606753456265236910">"Approvisionnement en cours…"</item>
+ <item msgid="3930807209231347454">"Inclus"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Non approvisionné"</item>
+ <item msgid="7598231293776486217">"Approvisionné"</item>
+ <item msgid="3720547957514534185">"Facultatif"</item>
+ <item msgid="1264673582354896949">"En cours de traitement"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-fr/strings.xml b/testapps/TestServerApp/app/src/main/res/values-fr/strings.xml
new file mode 100644
index 0000000..be70c79
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-fr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Paramètres"</string>
+ <string name="server_running" msgid="2780193626090379172">"Serveur en cours d\'exécution…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"Arrêter le serveur"</string>
+ <string name="server_down" msgid="1030249207496490556">"Serveur en panne"</string>
+ <string name="start_server" msgid="3878573341408591975">"Démarrer le serveur"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Désactivé"</item>
+ <item msgid="3193389681837907872">"Activé"</item>
+ <item msgid="3124590179479393815">"Incompatible"</item>
+ <item msgid="1606753456265236910">"Provisionnement…"</item>
+ <item msgid="3930807209231347454">"Inclus"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Non provisionné"</item>
+ <item msgid="7598231293776486217">"Provisionné"</item>
+ <item msgid="3720547957514534185">"Facultatif"</item>
+ <item msgid="1264673582354896949">"En cours"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-gl/strings.xml b/testapps/TestServerApp/app/src/main/res/values-gl/strings.xml
new file mode 100644
index 0000000..9de1ceb
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-gl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Configuración"</string>
+ <string name="server_running" msgid="2780193626090379172">"O servidor está executándose…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"Deter servidor"</string>
+ <string name="server_down" msgid="1030249207496490556">"O servidor non está operativo"</string>
+ <string name="start_server" msgid="3878573341408591975">"Iniciar servidor"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Desactivado"</item>
+ <item msgid="3193389681837907872">"Activado"</item>
+ <item msgid="3124590179479393815">"Incompatible"</item>
+ <item msgid="1606753456265236910">"En aprovisionamento"</item>
+ <item msgid="3930807209231347454">"Incluído"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Non aprovisionado"</item>
+ <item msgid="7598231293776486217">"Aprovisionado"</item>
+ <item msgid="3720547957514534185">"Non obrigatorio"</item>
+ <item msgid="1264673582354896949">"En curso"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-gu/strings.xml b/testapps/TestServerApp/app/src/main/res/values-gu/strings.xml
new file mode 100644
index 0000000..d2fc1d0
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-gu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"સેટિંગ"</string>
+ <string name="server_running" msgid="2780193626090379172">"સર્વર ચાલુ છે..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"સર્વર રોકો"</string>
+ <string name="server_down" msgid="1030249207496490556">"સર્વર ડાઉન છે"</string>
+ <string name="start_server" msgid="3878573341408591975">"સર્વર શરૂ કરો"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"બંધ છે"</item>
+ <item msgid="3193389681837907872">"ચાલુ છે"</item>
+ <item msgid="3124590179479393815">"અસંગત છે"</item>
+ <item msgid="1606753456265236910">"જોગવાઈ કરી રહ્યું છે"</item>
+ <item msgid="3930807209231347454">"શામેલ છે"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"કોઈ જોગવાઈ કરેલી નથી"</item>
+ <item msgid="7598231293776486217">"જોગવાઈ કરેલી છે"</item>
+ <item msgid="3720547957514534185">"આવશ્યક નથી"</item>
+ <item msgid="1264673582354896949">"પ્રક્રિયા ચાલુ છે"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-hi/strings.xml b/testapps/TestServerApp/app/src/main/res/values-hi/strings.xml
new file mode 100644
index 0000000..d5be924
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-hi/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"सेटिंग"</string>
+ <string name="server_running" msgid="2780193626090379172">"सर्वर काम कर रहा है..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"सर्वर बंद करें"</string>
+ <string name="server_down" msgid="1030249207496490556">"सर्वर काम नहीं कर रहा है"</string>
+ <string name="start_server" msgid="3878573341408591975">"सर्वर चालू करें"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"बंद है"</item>
+ <item msgid="3193389681837907872">"चालू है"</item>
+ <item msgid="3124590179479393815">"काम नहीं करता"</item>
+ <item msgid="1606753456265236910">"प्रावधान"</item>
+ <item msgid="3930807209231347454">"पहले से मौजूद है"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"प्रावधान नहीं किया गया है"</item>
+ <item msgid="7598231293776486217">"प्रावधान किया गया है"</item>
+ <item msgid="3720547957514534185">"ज़रूरी नहीं है"</item>
+ <item msgid="1264673582354896949">"प्रावधान किया जा रहा है"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-hr/strings.xml b/testapps/TestServerApp/app/src/main/res/values-hr/strings.xml
new file mode 100644
index 0000000..492f2cc
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-hr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Postavke"</string>
+ <string name="server_running" msgid="2780193626090379172">"Poslužitelj je aktivan..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Zaustavljanje poslužitelja"</string>
+ <string name="server_down" msgid="1030249207496490556">"Poslužitelj nije aktivan"</string>
+ <string name="start_server" msgid="3878573341408591975">"Pokretanje poslužitelja"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Onemogućeno"</item>
+ <item msgid="3193389681837907872">"Omogućeno"</item>
+ <item msgid="3124590179479393815">"Nije kompatibilno"</item>
+ <item msgid="1606753456265236910">"Omogućivanje"</item>
+ <item msgid="3930807209231347454">"Uključeno"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Nije omogućeno"</item>
+ <item msgid="7598231293776486217">"Omogućeno"</item>
+ <item msgid="3720547957514534185">"Nije obavezno"</item>
+ <item msgid="1264673582354896949">"U tijeku"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-hu/strings.xml b/testapps/TestServerApp/app/src/main/res/values-hu/strings.xml
new file mode 100644
index 0000000..286fae3
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-hu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Beállítások"</string>
+ <string name="server_running" msgid="2780193626090379172">"A szerver fut..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Szerver leállítása"</string>
+ <string name="server_down" msgid="1030249207496490556">"A szerver leállt"</string>
+ <string name="start_server" msgid="3878573341408591975">"Szerver indítása"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Kikapcsolva"</item>
+ <item msgid="3193389681837907872">"Engedélyezve"</item>
+ <item msgid="3124590179479393815">"Nem kompatibilis"</item>
+ <item msgid="1606753456265236910">"Kiépítés"</item>
+ <item msgid="3930807209231347454">"Tartalmazza"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Nincs kiépítve"</item>
+ <item msgid="7598231293776486217">"Kiépítve"</item>
+ <item msgid="3720547957514534185">"Nem kötelező"</item>
+ <item msgid="1264673582354896949">"Folyamatban"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-hy/strings.xml b/testapps/TestServerApp/app/src/main/res/values-hy/strings.xml
new file mode 100644
index 0000000..03a382b
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-hy/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Կարգավորումներ"</string>
+ <string name="server_running" msgid="2780193626090379172">"Սերվերն աշխատում է..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Կանգնեցնել սերվերը"</string>
+ <string name="server_down" msgid="1030249207496490556">"Սերվերն անջատված է"</string>
+ <string name="start_server" msgid="3878573341408591975">"Գործարկել սերվերը"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Անջատված է"</item>
+ <item msgid="3193389681837907872">"Միացված է"</item>
+ <item msgid="3124590179479393815">"Անհամատեղելիություն"</item>
+ <item msgid="1606753456265236910">"Նախապատրաստում"</item>
+ <item msgid="3930807209231347454">"Ներառված է"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Նախապատրաստված չէ"</item>
+ <item msgid="7598231293776486217">"Նախապատրաստված է"</item>
+ <item msgid="3720547957514534185">"Ոչ պարտադիր"</item>
+ <item msgid="1264673582354896949">"Ընթացքում է"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-in/strings.xml b/testapps/TestServerApp/app/src/main/res/values-in/strings.xml
new file mode 100644
index 0000000..b918582
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-in/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Setelan"</string>
+ <string name="server_running" msgid="2780193626090379172">"Server sedang berjalan..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Hentikan Server"</string>
+ <string name="server_down" msgid="1030249207496490556">"Server tidak berfungsi"</string>
+ <string name="start_server" msgid="3878573341408591975">"Mulai Server"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Nonaktif"</item>
+ <item msgid="3193389681837907872">"Aktif"</item>
+ <item msgid="3124590179479393815">"Tidak kompatibel"</item>
+ <item msgid="1606753456265236910">"Penyediaan"</item>
+ <item msgid="3930807209231347454">"Disertakan"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Tidak Disediakan"</item>
+ <item msgid="7598231293776486217">"Disediakan"</item>
+ <item msgid="3720547957514534185">"Tidak Wajib"</item>
+ <item msgid="1264673582354896949">"Dalam Proses"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-is/strings.xml b/testapps/TestServerApp/app/src/main/res/values-is/strings.xml
new file mode 100644
index 0000000..610755a
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-is/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Stillingar"</string>
+ <string name="server_running" msgid="2780193626090379172">"Þjónn er í gangi..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Stöðva þjón"</string>
+ <string name="server_down" msgid="1030249207496490556">"Þjónn liggur niðri"</string>
+ <string name="start_server" msgid="3878573341408591975">"Ræsa þjón"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Slökkt"</item>
+ <item msgid="3193389681837907872">"Kveikt"</item>
+ <item msgid="3124590179479393815">"Ósamhæft"</item>
+ <item msgid="1606753456265236910">"Úthlutun"</item>
+ <item msgid="3930807209231347454">"Innifalið"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Ekki úthlutað"</item>
+ <item msgid="7598231293776486217">"Úthlutað"</item>
+ <item msgid="3720547957514534185">"Ekki áskilið"</item>
+ <item msgid="1264673582354896949">"Í vinnslu"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-en-rCA/strings.xml b/testapps/TestServerApp/app/src/main/res/values-it/strings.xml
similarity index 100%
rename from testapps/TestServerApp/app/src/main/res/values-en-rCA/strings.xml
rename to testapps/TestServerApp/app/src/main/res/values-it/strings.xml
diff --git a/testapps/TestServerApp/app/src/main/res/values-iw/strings.xml b/testapps/TestServerApp/app/src/main/res/values-iw/strings.xml
deleted file mode 100644
index 701edba..0000000
--- a/testapps/TestServerApp/app/src/main/res/values-iw/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2894617184221823208">"אפליקציית TestServer"</string>
- <string name="action_settings" msgid="1335152369747372374">"הגדרות"</string>
- <string name="server_running" msgid="2780193626090379172">"השרת פועל…"</string>
- <string name="stop_server" msgid="6192029827529013598">"הפסקת השרת"</string>
- <string name="server_down" msgid="1030249207496490556">"תקלה בשרת"</string>
- <string name="start_server" msgid="3878573341408591975">"הפעלת השרת"</string>
- <string-array name="entitlement_status">
- <item msgid="5560300387618996934">"מושבת"</item>
- <item msgid="3193389681837907872">"פועל"</item>
- <item msgid="3124590179479393815">"לא תואם"</item>
- <item msgid="1606753456265236910">"הקצאה"</item>
- <item msgid="3930807209231347454">"כלול"</item>
- </string-array>
- <string-array name="provision_status">
- <item msgid="3486273747926710021">"לא מוקצה"</item>
- <item msgid="7598231293776486217">"מוקצה"</item>
- <item msgid="3720547957514534185">"לא נדרש"</item>
- <item msgid="1264673582354896949">"בתהליך"</item>
- </string-array>
-</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-ja/strings.xml b/testapps/TestServerApp/app/src/main/res/values-ja/strings.xml
new file mode 100644
index 0000000..1b962e4
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-ja/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"設定"</string>
+ <string name="server_running" msgid="2780193626090379172">"サーバーが実行中です..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"サーバーを停止"</string>
+ <string name="server_down" msgid="1030249207496490556">"サーバーがダウンしています"</string>
+ <string name="start_server" msgid="3878573341408591975">"サーバーを起動"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"無効"</item>
+ <item msgid="3193389681837907872">"有効"</item>
+ <item msgid="3124590179479393815">"非対応"</item>
+ <item msgid="1606753456265236910">"プロビジョニング"</item>
+ <item msgid="3930807209231347454">"必須"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"未プロビジョニング"</item>
+ <item msgid="7598231293776486217">"プロビジョニング済み"</item>
+ <item msgid="3720547957514534185">"任意"</item>
+ <item msgid="1264673582354896949">"処理中"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-ka/strings.xml b/testapps/TestServerApp/app/src/main/res/values-ka/strings.xml
deleted file mode 100644
index fc17ddd..0000000
--- a/testapps/TestServerApp/app/src/main/res/values-ka/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
- <string name="action_settings" msgid="1335152369747372374">"პარამეტრები"</string>
- <string name="server_running" msgid="2780193626090379172">"სერვერი მუშაობს..."</string>
- <string name="stop_server" msgid="6192029827529013598">"სერვერის გაჩერება"</string>
- <string name="server_down" msgid="1030249207496490556">"სერვერი გათიშულია"</string>
- <string name="start_server" msgid="3878573341408591975">"სერვერის დაწყება"</string>
- <string-array name="entitlement_status">
- <item msgid="5560300387618996934">"გათიშულია"</item>
- <item msgid="3193389681837907872">"ჩართულია"</item>
- <item msgid="3124590179479393815">"არათავსებადი"</item>
- <item msgid="1606753456265236910">"დანერგვა"</item>
- <item msgid="3930807209231347454">"შედის"</item>
- </string-array>
- <string-array name="provision_status">
- <item msgid="3486273747926710021">"არ არის გათვალისწინებული"</item>
- <item msgid="7598231293776486217">"უზრუნველყოფილი"</item>
- <item msgid="3720547957514534185">"არასავალდებულო"</item>
- <item msgid="1264673582354896949">"მიმდინარე"</item>
- </string-array>
-</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-kk/strings.xml b/testapps/TestServerApp/app/src/main/res/values-kk/strings.xml
new file mode 100644
index 0000000..8a93c31
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-kk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Параметрлер"</string>
+ <string name="server_running" msgid="2780193626090379172">"Сервер істеп тұр…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"Серверді тоқтату"</string>
+ <string name="server_down" msgid="1030249207496490556">"Сервер істемей тұр"</string>
+ <string name="start_server" msgid="3878573341408591975">"Серверді іске қосу"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Өшірулі"</item>
+ <item msgid="3193389681837907872">"Қосулы"</item>
+ <item msgid="3124590179479393815">"Үйлеспейді"</item>
+ <item msgid="1606753456265236910">"Дайындау"</item>
+ <item msgid="3930807209231347454">"Қамтылды"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Дайындалмады"</item>
+ <item msgid="7598231293776486217">"Дайындалды"</item>
+ <item msgid="3720547957514534185">"Міндетті емес"</item>
+ <item msgid="1264673582354896949">"Орындалып жатыр"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-km/strings.xml b/testapps/TestServerApp/app/src/main/res/values-km/strings.xml
deleted file mode 100644
index 888db64..0000000
--- a/testapps/TestServerApp/app/src/main/res/values-km/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
- <string name="action_settings" msgid="1335152369747372374">"ការកំណត់"</string>
- <string name="server_running" msgid="2780193626090379172">"ម៉ាស៊ីនមេកំពុងដំណើរការ..."</string>
- <string name="stop_server" msgid="6192029827529013598">"បញ្ឈប់ម៉ាស៊ីនមេ"</string>
- <string name="server_down" msgid="1030249207496490556">"ម៉ាស៊ីនមេមិនដំណើរការ"</string>
- <string name="start_server" msgid="3878573341408591975">"ចាប់ផ្ដើមម៉ាស៊ីនមេ"</string>
- <string-array name="entitlement_status">
- <item msgid="5560300387618996934">"បានបិទ"</item>
- <item msgid="3193389681837907872">"បានបើក"</item>
- <item msgid="3124590179479393815">"មិនត្រូវគ្នា"</item>
- <item msgid="1606753456265236910">"ការផ្តល់"</item>
- <item msgid="3930807209231347454">"បានរួមបញ្ចូល"</item>
- </string-array>
- <string-array name="provision_status">
- <item msgid="3486273747926710021">"មិនបានផ្ដល់"</item>
- <item msgid="7598231293776486217">"បានផ្ដល់"</item>
- <item msgid="3720547957514534185">"មិនតម្រូវ"</item>
- <item msgid="1264673582354896949">"កំពុងដំណើរការ"</item>
- </string-array>
-</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-kn/strings.xml b/testapps/TestServerApp/app/src/main/res/values-kn/strings.xml
new file mode 100644
index 0000000..227d44b
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-kn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
+ <string name="server_running" msgid="2780193626090379172">"ಸರ್ವರ್ ರನ್ ಆಗುತ್ತಿದೆ..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"ಸರ್ವರ್ ನಿಲ್ಲಿಸಿ"</string>
+ <string name="server_down" msgid="1030249207496490556">"ಸರ್ವರ್ ಡೌನ್ ಆಗಿದೆ"</string>
+ <string name="start_server" msgid="3878573341408591975">"ಸರ್ವರ್ ಪ್ರಾರಂಭಿಸಿ"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</item>
+ <item msgid="3193389681837907872">"ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</item>
+ <item msgid="3124590179479393815">"ಹೊಂದಾಣಿಕೆಯಾಗುವುದಿಲ್ಲ"</item>
+ <item msgid="1606753456265236910">"ಒದಗಿಸಲಾಗುತ್ತಿದೆ"</item>
+ <item msgid="3930807209231347454">"ಒಳಗೊಂಡಿದೆ"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"ಒದಗಿಸಲಾಗಿಲ್ಲ"</item>
+ <item msgid="7598231293776486217">"ಒದಗಿಸಲಾಗಿದೆ"</item>
+ <item msgid="3720547957514534185">"ಅಗತ್ಯವಿಲ್ಲ"</item>
+ <item msgid="1264673582354896949">"ಪ್ರಗತಿಯಲ್ಲಿದೆ"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-ko/strings.xml b/testapps/TestServerApp/app/src/main/res/values-ko/strings.xml
new file mode 100644
index 0000000..ca9b15a
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-ko/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"설정"</string>
+ <string name="server_running" msgid="2780193626090379172">"서버 실행 중…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"서버를 중지하시겠습니까?"</string>
+ <string name="server_down" msgid="1030249207496490556">"서버가 다운됨"</string>
+ <string name="start_server" msgid="3878573341408591975">"서버를 시작하시겠습니까?"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"사용 안함"</item>
+ <item msgid="3193389681837907872">"사용 설정됨"</item>
+ <item msgid="3124590179479393815">"호환되지 않음"</item>
+ <item msgid="1606753456265236910">"프로비저닝"</item>
+ <item msgid="3930807209231347454">"포함됨"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"프로비저닝되지 않음"</item>
+ <item msgid="7598231293776486217">"프로비저닝됨"</item>
+ <item msgid="3720547957514534185">"필요 없음"</item>
+ <item msgid="1264673582354896949">"진행 중"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-ky/strings.xml b/testapps/TestServerApp/app/src/main/res/values-ky/strings.xml
new file mode 100644
index 0000000..3d6c36f
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-ky/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Параметрлер"</string>
+ <string name="server_running" msgid="2780193626090379172">"Сервер иштеп жатат..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Серверди токтотуу"</string>
+ <string name="server_down" msgid="1030249207496490556">"Сервер иштебей калды"</string>
+ <string name="start_server" msgid="3878573341408591975">"Серверди иштетип баштоо"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Өчүрүлдү"</item>
+ <item msgid="3193389681837907872">"Иштетилди"</item>
+ <item msgid="3124590179479393815">"Ылайык келбейт"</item>
+ <item msgid="1606753456265236910">"Камсыз кылууда"</item>
+ <item msgid="3930807209231347454">"Камтылган"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Камсыздалган эмес"</item>
+ <item msgid="7598231293776486217">"Камсыздалган"</item>
+ <item msgid="3720547957514534185">"Талап кылынбайт"</item>
+ <item msgid="1264673582354896949">"Аткарылууда"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-lo/strings.xml b/testapps/TestServerApp/app/src/main/res/values-lo/strings.xml
deleted file mode 100644
index eb725cf..0000000
--- a/testapps/TestServerApp/app/src/main/res/values-lo/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
- <string name="action_settings" msgid="1335152369747372374">"ການຕັ້ງຄ່າ"</string>
- <string name="server_running" msgid="2780193626090379172">"ເຊີບເວີກຳລັງເຮັດວຽກ..."</string>
- <string name="stop_server" msgid="6192029827529013598">"ຢຸດການເຮັດວຽກຂອງເຊີບເວີ"</string>
- <string name="server_down" msgid="1030249207496490556">"ເຊີບເວີຢຸດໃຫ້ບໍລິການ"</string>
- <string name="start_server" msgid="3878573341408591975">"ເລີ່ມການເຮັດວຽກຂອງເຊີບເວີ"</string>
- <string-array name="entitlement_status">
- <item msgid="5560300387618996934">"ປິດການນຳໃຊ້ແລ້ວ"</item>
- <item msgid="3193389681837907872">"ເປີດການນຳໃຊ້ແລ້ວ"</item>
- <item msgid="3124590179479393815">"ໃຊ້ຮ່ວມກັນບໍ່ໄດ້"</item>
- <item msgid="1606753456265236910">"ກຳລັງຈັດກຽມ"</item>
- <item msgid="3930807209231347454">"ຮວມເຂົ້າແລ້ວ"</item>
- </string-array>
- <string-array name="provision_status">
- <item msgid="3486273747926710021">"ບໍ່ໄດ້ຈັດກຽມ"</item>
- <item msgid="7598231293776486217">"ຈັດກຽມໃຫ້ແລ້ວ"</item>
- <item msgid="3720547957514534185">"ບໍ່ຈຳເປັນ"</item>
- <item msgid="1264673582354896949">"ກຳລັງດຳເນີນການ"</item>
- </string-array>
-</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-lt/strings.xml b/testapps/TestServerApp/app/src/main/res/values-lt/strings.xml
new file mode 100644
index 0000000..a7e79e9
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-lt/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Nustatymai"</string>
+ <string name="server_running" msgid="2780193626090379172">"Serveris veikia..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Sustabdyti serverį"</string>
+ <string name="server_down" msgid="1030249207496490556">"Serveris neveikia"</string>
+ <string name="start_server" msgid="3878573341408591975">"Paleisti serverį"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Išjungta"</item>
+ <item msgid="3193389681837907872">"Įgalinta"</item>
+ <item msgid="3124590179479393815">"Nesuderinama"</item>
+ <item msgid="1606753456265236910">"Parengiama"</item>
+ <item msgid="3930807209231347454">"Įtraukta"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Neparengta"</item>
+ <item msgid="7598231293776486217">"Parengta"</item>
+ <item msgid="3720547957514534185">"Nebūtina"</item>
+ <item msgid="1264673582354896949">"Vykdoma"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-lv/strings.xml b/testapps/TestServerApp/app/src/main/res/values-lv/strings.xml
new file mode 100644
index 0000000..a7bff51
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-lv/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"ServeraLietotneTestēšanai"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Iestatījumi"</string>
+ <string name="server_running" msgid="2780193626090379172">"Serveris darbojas…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"Apturēt servera darbību"</string>
+ <string name="server_down" msgid="1030249207496490556">"Serveris nedarbojas"</string>
+ <string name="start_server" msgid="3878573341408591975">"Palaist serveri"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Atspējots"</item>
+ <item msgid="3193389681837907872">"Iespējots"</item>
+ <item msgid="3124590179479393815">"Nav saderīgs"</item>
+ <item msgid="1606753456265236910">"Notiek nodrošināšana"</item>
+ <item msgid="3930807209231347454">"Iekļauts"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Nav nodrošināts"</item>
+ <item msgid="7598231293776486217">"Nodrošināts"</item>
+ <item msgid="3720547957514534185">"Nav nepieciešams"</item>
+ <item msgid="1264673582354896949">"Notiek apstrāde"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-mk/strings.xml b/testapps/TestServerApp/app/src/main/res/values-mk/strings.xml
new file mode 100644
index 0000000..44a255c
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-mk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Поставки"</string>
+ <string name="server_running" msgid="2780193626090379172">"Серверот се извршува…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"Сопри го серверот"</string>
+ <string name="server_down" msgid="1030249207496490556">"Серверот е паднат"</string>
+ <string name="start_server" msgid="3878573341408591975">"Стартувај го серверот"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Оневозможено"</item>
+ <item msgid="3193389681837907872">"Овозможено"</item>
+ <item msgid="3124590179479393815">"Некомпатибилно"</item>
+ <item msgid="1606753456265236910">"Се обезбедува"</item>
+ <item msgid="3930807209231347454">"Опфатено"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Необезбедено"</item>
+ <item msgid="7598231293776486217">"Обезбедено"</item>
+ <item msgid="3720547957514534185">"Незадолжително"</item>
+ <item msgid="1264673582354896949">"Во тек"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-ml/strings.xml b/testapps/TestServerApp/app/src/main/res/values-ml/strings.xml
new file mode 100644
index 0000000..32b305a
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-ml/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"ക്രമീകരണം"</string>
+ <string name="server_running" msgid="2780193626090379172">"സെർവർ പ്രവർത്തിക്കുന്നുണ്ട്..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"സെർവർ നിർത്തുക"</string>
+ <string name="server_down" msgid="1030249207496490556">"സെർവർ ലഭ്യമല്ല"</string>
+ <string name="start_server" msgid="3878573341408591975">"സെർവർ ആരംഭിക്കുക"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"പ്രവർത്തനരഹിതമാക്കി"</item>
+ <item msgid="3193389681837907872">"പ്രവർത്തനക്ഷമമാക്കി"</item>
+ <item msgid="3124590179479393815">"അനുയോജ്യമല്ല"</item>
+ <item msgid="1606753456265236910">"പ്രൊവിഷനിംഗ്"</item>
+ <item msgid="3930807209231347454">"ഉൾപ്പെടുത്തി"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"പ്രൊവിഷൻ ചെയ്തിട്ടില്ല"</item>
+ <item msgid="7598231293776486217">"പ്രൊവിഷൻ ചെയ്തു"</item>
+ <item msgid="3720547957514534185">"ആവശ്യമില്ല"</item>
+ <item msgid="1264673582354896949">"പുരോഗമിക്കുന്നു"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-mn/strings.xml b/testapps/TestServerApp/app/src/main/res/values-mn/strings.xml
new file mode 100644
index 0000000..6c131e2
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-mn/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Тохиргоо"</string>
+ <string name="server_running" msgid="2780193626090379172">"Сервер ажиллаж байна..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Серверийг зогсоох"</string>
+ <string name="server_down" msgid="1030249207496490556">"Сервер унтарсан байна"</string>
+ <string name="start_server" msgid="3878573341408591975">"Серверийг эхлүүлэх"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Идэвхгүй болгосон"</item>
+ <item msgid="3193389681837907872">"Идэвхжүүлсэн"</item>
+ <item msgid="3124590179479393815">"Тохирохгүй"</item>
+ <item msgid="1606753456265236910">"Бэлтгэж байна"</item>
+ <item msgid="3930807209231347454">"Багтсан"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Бэлтгээгүй"</item>
+ <item msgid="7598231293776486217">"Бэлтгэсэн"</item>
+ <item msgid="3720547957514534185">"Заавал биш"</item>
+ <item msgid="1264673582354896949">"Үргэлжилж байна"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-mr/strings.xml b/testapps/TestServerApp/app/src/main/res/values-mr/strings.xml
new file mode 100644
index 0000000..74a0f56
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-mr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"सेटिंग्ज"</string>
+ <string name="server_running" msgid="2780193626090379172">"सर्व्हर रन होत आहे..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"सर्व्हर थांबवा"</string>
+ <string name="server_down" msgid="1030249207496490556">"सर्व्हर बंद आहे"</string>
+ <string name="start_server" msgid="3878573341408591975">"सर्व्हर सुरू करा"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"बंद आहे"</item>
+ <item msgid="3193389681837907872">"सुरू आहे"</item>
+ <item msgid="3124590179479393815">"कंपॅटिबल नाही"</item>
+ <item msgid="1606753456265236910">"तरतूद"</item>
+ <item msgid="3930807209231347454">"समावेश आहे"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"तरतूद केलेली नाही"</item>
+ <item msgid="7598231293776486217">"तरतूद केली आहे"</item>
+ <item msgid="3720547957514534185">"आवश्यक नाही"</item>
+ <item msgid="1264673582354896949">"प्रगतीपथावर आहे"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-ms/strings.xml b/testapps/TestServerApp/app/src/main/res/values-ms/strings.xml
deleted file mode 100644
index 8d60ad4..0000000
--- a/testapps/TestServerApp/app/src/main/res/values-ms/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
- <string name="action_settings" msgid="1335152369747372374">"Tetapan"</string>
- <string name="server_running" msgid="2780193626090379172">"Pelayan sedang dijalankan..."</string>
- <string name="stop_server" msgid="6192029827529013598">"Hentikan Pelayan"</string>
- <string name="server_down" msgid="1030249207496490556">"Pelayan tergendala"</string>
- <string name="start_server" msgid="3878573341408591975">"Mulakan Pelayan"</string>
- <string-array name="entitlement_status">
- <item msgid="5560300387618996934">"Dilumpuhkan"</item>
- <item msgid="3193389681837907872">"Didayakan"</item>
- <item msgid="3124590179479393815">"Tidak serasi"</item>
- <item msgid="1606753456265236910">"Peruntukan"</item>
- <item msgid="3930807209231347454">"Disertakan"</item>
- </string-array>
- <string-array name="provision_status">
- <item msgid="3486273747926710021">"Tidak Diperuntukkan"</item>
- <item msgid="7598231293776486217">"Diperuntukkan"</item>
- <item msgid="3720547957514534185">"Tidak Diperlukan"</item>
- <item msgid="1264673582354896949">"Sedang Berlangsung"</item>
- </string-array>
-</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-my/strings.xml b/testapps/TestServerApp/app/src/main/res/values-my/strings.xml
new file mode 100644
index 0000000..9a0dcdf
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-my/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"ဆက်တင်များ"</string>
+ <string name="server_running" msgid="2780193626090379172">"ဆာဗာ လုပ်ဆောင်နေသည်…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"ဆာဗာ ရပ်ရန်"</string>
+ <string name="server_down" msgid="1030249207496490556">"ဆာဗာကျနေသည်"</string>
+ <string name="start_server" msgid="3878573341408591975">"ဆာဗာ စတင်ရန်"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"ပိတ်ထားသည်"</item>
+ <item msgid="3193389681837907872">"ဖွင့်ထားသည်"</item>
+ <item msgid="3124590179479393815">"တွဲဖက်မသုံးနိုင်ပါ"</item>
+ <item msgid="1606753456265236910">"ပံ့ပိုးပေးခြင်း"</item>
+ <item msgid="3930807209231347454">"ပါဝင်သည်"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"ပံ့ပိုးမထားပါ"</item>
+ <item msgid="7598231293776486217">"ပံ့ပိုးပေးထားသည်"</item>
+ <item msgid="3720547957514534185">"မလိုအပ်ပါ"</item>
+ <item msgid="1264673582354896949">"ဆောင်ရွက်နေဆဲ"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-nb/strings.xml b/testapps/TestServerApp/app/src/main/res/values-nb/strings.xml
new file mode 100644
index 0000000..d28a197
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-nb/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Innstillinger"</string>
+ <string name="server_running" msgid="2780193626090379172">"Tjeneren kjører …"</string>
+ <string name="stop_server" msgid="6192029827529013598">"Stopp tjeneren"</string>
+ <string name="server_down" msgid="1030249207496490556">"Tjeneren er nede"</string>
+ <string name="start_server" msgid="3878573341408591975">"Start tjeneren"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Slått av"</item>
+ <item msgid="3193389681837907872">"Slått på"</item>
+ <item msgid="3124590179479393815">"Inkompatibel"</item>
+ <item msgid="1606753456265236910">"Klargjøring"</item>
+ <item msgid="3930807209231347454">"Inkludert"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Ikke klargjort"</item>
+ <item msgid="7598231293776486217">"Klargjort"</item>
+ <item msgid="3720547957514534185">"Ikke obligatorisk"</item>
+ <item msgid="1264673582354896949">"Under behandling"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-ne/strings.xml b/testapps/TestServerApp/app/src/main/res/values-ne/strings.xml
new file mode 100644
index 0000000..fe1f119
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-ne/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"सेटिङ"</string>
+ <string name="server_running" msgid="2780193626090379172">"सर्भर चल्दै छ..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"सर्भर बन्द गर्नुहोस्"</string>
+ <string name="server_down" msgid="1030249207496490556">"सर्भर डाउन छ"</string>
+ <string name="start_server" msgid="3878573341408591975">"सर्भर प्रयोग गर्न थाल्नुहोस्"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"अफ गरिएको छ"</item>
+ <item msgid="3193389681837907872">"अन गरिएको छ"</item>
+ <item msgid="3124590179479393815">"नमिल्दो"</item>
+ <item msgid="1606753456265236910">"प्रावधान मिलाइँदै छ"</item>
+ <item msgid="3930807209231347454">"समावेश गरिएको छ"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"प्रावधान मिलाइएको छैन"</item>
+ <item msgid="7598231293776486217">"प्रावधान मिलाइएको छ"</item>
+ <item msgid="3720547957514534185">"आवश्यक छैन"</item>
+ <item msgid="1264673582354896949">"प्रक्रियामा छ"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-nl/strings.xml b/testapps/TestServerApp/app/src/main/res/values-nl/strings.xml
new file mode 100644
index 0000000..847dfca
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-nl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Instellingen"</string>
+ <string name="server_running" msgid="2780193626090379172">"Server actief..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Server stoppen"</string>
+ <string name="server_down" msgid="1030249207496490556">"Server offline"</string>
+ <string name="start_server" msgid="3878573341408591975">"Server starten"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Uit"</item>
+ <item msgid="3193389681837907872">"Aan"</item>
+ <item msgid="3124590179479393815">"Niet geschikt"</item>
+ <item msgid="1606753456265236910">"Registratie"</item>
+ <item msgid="3930807209231347454">"Opgenomen"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Niet geregistreerd"</item>
+ <item msgid="7598231293776486217">"Geregistreerd"</item>
+ <item msgid="3720547957514534185">"Niet vereist"</item>
+ <item msgid="1264673582354896949">"In behandeling"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-or/strings.xml b/testapps/TestServerApp/app/src/main/res/values-or/strings.xml
deleted file mode 100644
index 3f951e7..0000000
--- a/testapps/TestServerApp/app/src/main/res/values-or/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
- <string name="action_settings" msgid="1335152369747372374">"ସେଟିଂସ"</string>
- <string name="server_running" msgid="2780193626090379172">"ସର୍ଭର ଚାଲୁଛି..."</string>
- <string name="stop_server" msgid="6192029827529013598">"ସର୍ଭର ବନ୍ଦ କରନ୍ତୁ"</string>
- <string name="server_down" msgid="1030249207496490556">"ସର୍ଭର ଡାଉନ ଅଛି"</string>
- <string name="start_server" msgid="3878573341408591975">"ସର୍ଭର ଆରମ୍ଭ କରନ୍ତୁ"</string>
- <string-array name="entitlement_status">
- <item msgid="5560300387618996934">"ଅକ୍ଷମ କରାଯାଇଛି"</item>
- <item msgid="3193389681837907872">"ସକ୍ଷମ କରାଯାଇଛି"</item>
- <item msgid="3124590179479393815">"ଇନକମ୍ପାଟିବଲ"</item>
- <item msgid="1606753456265236910">"ପ୍ରୋଭିଜନ କରାଯାଉଛି"</item>
- <item msgid="3930807209231347454">"ଅନ୍ତର୍ଭୁକ୍ତ ଅଛି"</item>
- </string-array>
- <string-array name="provision_status">
- <item msgid="3486273747926710021">"ପ୍ରୋଭିଜନ କରାଯାଇନାହିଁ"</item>
- <item msgid="7598231293776486217">"ପ୍ରୋଭିଜନ କରାଯାଇଛି"</item>
- <item msgid="3720547957514534185">"ଆବଶ୍ୟକ ନୁହେଁ"</item>
- <item msgid="1264673582354896949">"ଚାଲୁଛି"</item>
- </string-array>
-</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-pa/strings.xml b/testapps/TestServerApp/app/src/main/res/values-pa/strings.xml
new file mode 100644
index 0000000..f4509fc
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-pa/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"ਸੈਟਿੰਗਾਂ"</string>
+ <string name="server_running" msgid="2780193626090379172">"ਸਰਵਰ ਚੱਲ ਰਿਹਾ ਹੈ..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"ਸਰਵਰ ਬੰਦ ਕਰੋ"</string>
+ <string name="server_down" msgid="1030249207496490556">"ਸਰਵਰ ਨਹੀਂ ਚੱਲ ਰਿਹਾ"</string>
+ <string name="start_server" msgid="3878573341408591975">"ਸਰਵਰ ਚਾਲੂ ਕਰੋ"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"ਬੰਦ"</item>
+ <item msgid="3193389681837907872">"ਚਾਲੂ"</item>
+ <item msgid="3124590179479393815">"ਗੈਰ-ਅਨੁਰੂਪ"</item>
+ <item msgid="1606753456265236910">"ਪ੍ਰਬੰਧਿਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</item>
+ <item msgid="3930807209231347454">"ਸ਼ਾਮਲ"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"ਪ੍ਰਬੰਧਿਤ ਨਹੀਂ ਹੈ"</item>
+ <item msgid="7598231293776486217">"ਪ੍ਰਬੰਧਿਤ ਹੈ"</item>
+ <item msgid="3720547957514534185">"ਲੋੜੀਂਦਾ ਨਹੀਂ"</item>
+ <item msgid="1264673582354896949">"ਜਾਰੀ ਹੈ"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-pl/strings.xml b/testapps/TestServerApp/app/src/main/res/values-pl/strings.xml
new file mode 100644
index 0000000..4a13003
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-pl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Ustawienia"</string>
+ <string name="server_running" msgid="2780193626090379172">"Serwer działa…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"ZatrzymaJ serwer"</string>
+ <string name="server_down" msgid="1030249207496490556">"Serwer nie działa"</string>
+ <string name="start_server" msgid="3878573341408591975">"Uruchom serwer"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Wyłączono"</item>
+ <item msgid="3193389681837907872">"Włączono"</item>
+ <item msgid="3124590179479393815">"Brak zgodności"</item>
+ <item msgid="1606753456265236910">"Obsługa administracyjna"</item>
+ <item msgid="3930807209231347454">"Dostępne"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Nieobsługiwane"</item>
+ <item msgid="7598231293776486217">"Obsługiwane"</item>
+ <item msgid="3720547957514534185">"Niewymagane"</item>
+ <item msgid="1264673582354896949">"W toku"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-pt-rPT/strings.xml b/testapps/TestServerApp/app/src/main/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..e61c84d
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-pt-rPT/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Definições"</string>
+ <string name="server_running" msgid="2780193626090379172">"O servidor está em execução…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"Parar servidor"</string>
+ <string name="server_down" msgid="1030249207496490556">"O servidor está indisponível"</string>
+ <string name="start_server" msgid="3878573341408591975">"Iniciar servidor"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Desativado"</item>
+ <item msgid="3193389681837907872">"Ativado"</item>
+ <item msgid="3124590179479393815">"Incompatível"</item>
+ <item msgid="1606753456265236910">"A aprovisionar"</item>
+ <item msgid="3930807209231347454">"Incluído"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Não aprovisionado"</item>
+ <item msgid="7598231293776486217">"Aprovisionado"</item>
+ <item msgid="3720547957514534185">"Não obrigatório"</item>
+ <item msgid="1264673582354896949">"Em curso"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-pt/strings.xml b/testapps/TestServerApp/app/src/main/res/values-pt/strings.xml
new file mode 100644
index 0000000..1d2310d
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-pt/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Configurações"</string>
+ <string name="server_running" msgid="2780193626090379172">"O servidor está em execução..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Interromper servidor"</string>
+ <string name="server_down" msgid="1030249207496490556">"O servidor está fora do ar."</string>
+ <string name="start_server" msgid="3878573341408591975">"Iniciar servidor"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Desativado"</item>
+ <item msgid="3193389681837907872">"Ativado"</item>
+ <item msgid="3124590179479393815">"Incompatível"</item>
+ <item msgid="1606753456265236910">"Provisionando"</item>
+ <item msgid="3930807209231347454">"Incluso"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Não provisionado"</item>
+ <item msgid="7598231293776486217">"Provisionado"</item>
+ <item msgid="3720547957514534185">"Não obrigatório"</item>
+ <item msgid="1264673582354896949">"Em andamento"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-ro/strings.xml b/testapps/TestServerApp/app/src/main/res/values-ro/strings.xml
new file mode 100644
index 0000000..a84085a
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-ro/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Setări"</string>
+ <string name="server_running" msgid="2780193626090379172">"Serverul rulează..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Oprește serverul"</string>
+ <string name="server_down" msgid="1030249207496490556">"Serverul nu funcționează"</string>
+ <string name="start_server" msgid="3878573341408591975">"Pornește serverul"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Dezactivat"</item>
+ <item msgid="3193389681837907872">"Activat"</item>
+ <item msgid="3124590179479393815">"Incompatibil"</item>
+ <item msgid="1606753456265236910">"Configurarea accesului pentru utilizatori"</item>
+ <item msgid="3930807209231347454">"Inclus"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Nu a fost configurat accesul"</item>
+ <item msgid="7598231293776486217">"A fost configurat accesul"</item>
+ <item msgid="3720547957514534185">"Nu este obligatoriu"</item>
+ <item msgid="1264673582354896949">"În desfășurare"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-ru/strings.xml b/testapps/TestServerApp/app/src/main/res/values-ru/strings.xml
new file mode 100644
index 0000000..1242f96
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-ru/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Настройки"</string>
+ <string name="server_running" msgid="2780193626090379172">"Сервер работает…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"Остановить сервер"</string>
+ <string name="server_down" msgid="1030249207496490556">"Сервер не работает"</string>
+ <string name="start_server" msgid="3878573341408591975">"Запустить сервер"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Неактивно"</item>
+ <item msgid="3193389681837907872">"Активно"</item>
+ <item msgid="3124590179479393815">"Несовместимо"</item>
+ <item msgid="1606753456265236910">"Инициализация"</item>
+ <item msgid="3930807209231347454">"Включено"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Не подготовлено"</item>
+ <item msgid="7598231293776486217">"Подготовлено"</item>
+ <item msgid="3720547957514534185">"Необязательно"</item>
+ <item msgid="1264673582354896949">"На рассмотрении"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-si/strings.xml b/testapps/TestServerApp/app/src/main/res/values-si/strings.xml
new file mode 100644
index 0000000..47dba95
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-si/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"සැකසීම්"</string>
+ <string name="server_running" msgid="2780193626090379172">"සේවාදායකය ධාවනය වේ..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"සේවාදායකය නවත්වන්න"</string>
+ <string name="server_down" msgid="1030249207496490556">"සේවාදායකය බිඳ වැටී ඇත"</string>
+ <string name="start_server" msgid="3878573341408591975">"සේවාදායකය අරඹන්න"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"අබලයි"</item>
+ <item msgid="3193389681837907872">"සබලයි"</item>
+ <item msgid="3124590179479393815">"නොගැළපෙන"</item>
+ <item msgid="1606753456265236910">"ප්රතිපාදනය කරමින්"</item>
+ <item msgid="3930807209231347454">"ඇතුළත් වේ"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"ප්රතිපාදනය කර නැත"</item>
+ <item msgid="7598231293776486217">"ප්රතිපාදන ලත්"</item>
+ <item msgid="3720547957514534185">"අවශ්ය නැත"</item>
+ <item msgid="1264673582354896949">"ප්රගතියේ පවතී"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-sk/strings.xml b/testapps/TestServerApp/app/src/main/res/values-sk/strings.xml
new file mode 100644
index 0000000..ca4bd55
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-sk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Nastavenia"</string>
+ <string name="server_running" msgid="2780193626090379172">"Server je spustený…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"Zastaviť server"</string>
+ <string name="server_down" msgid="1030249207496490556">"Server je nedostupný"</string>
+ <string name="start_server" msgid="3878573341408591975">"Spustiť server"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Vypnuté"</item>
+ <item msgid="3193389681837907872">"Zapnuté"</item>
+ <item msgid="3124590179479393815">"Nekompatibilné"</item>
+ <item msgid="1606753456265236910">"Poskytovanie"</item>
+ <item msgid="3930807209231347454">"Zahrnuté"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Neposkytuje sa"</item>
+ <item msgid="7598231293776486217">"Poskytuje sa"</item>
+ <item msgid="3720547957514534185">"Nepovinné"</item>
+ <item msgid="1264673582354896949">"Spracúva sa"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-sl/strings.xml b/testapps/TestServerApp/app/src/main/res/values-sl/strings.xml
new file mode 100644
index 0000000..570e6e2
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-sl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Nastavitve"</string>
+ <string name="server_running" msgid="2780193626090379172">"Strežnik se izvaja ..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Ustavi strežnik"</string>
+ <string name="server_down" msgid="1030249207496490556">"Strežnik ne deluje"</string>
+ <string name="start_server" msgid="3878573341408591975">"Zaženi strežnik"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Onemogočeno"</item>
+ <item msgid="3193389681837907872">"Omogočeno"</item>
+ <item msgid="3124590179479393815">"Nezdružljivo"</item>
+ <item msgid="1606753456265236910">"Omogočanje uporabe"</item>
+ <item msgid="3930807209231347454">"Vključeno"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Uporaba ni omogočena"</item>
+ <item msgid="7598231293776486217">"Omogočeno"</item>
+ <item msgid="3720547957514534185">"Ni zahtevano"</item>
+ <item msgid="1264673582354896949">"Poteka"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-sq/strings.xml b/testapps/TestServerApp/app/src/main/res/values-sq/strings.xml
new file mode 100644
index 0000000..fbcd4da
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-sq/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Cilësimet"</string>
+ <string name="server_running" msgid="2780193626090379172">"Serveri është në ekzekutim..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Ndalo serverin"</string>
+ <string name="server_down" msgid="1030249207496490556">"Serveri nuk po funksionon"</string>
+ <string name="start_server" msgid="3878573341408591975">"Nis serverin"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Çaktivizuar"</item>
+ <item msgid="3193389681837907872">"Aktivizuar"</item>
+ <item msgid="3124590179479393815">"I papërputhshëm"</item>
+ <item msgid="1606753456265236910">"Po përgatitet"</item>
+ <item msgid="3930807209231347454">"Përfshirë"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Nuk është përgatitur"</item>
+ <item msgid="7598231293776486217">"Përgatitur"</item>
+ <item msgid="3720547957514534185">"Nuk kërkohet"</item>
+ <item msgid="1264673582354896949">"Në vazhdim"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-sr/strings.xml b/testapps/TestServerApp/app/src/main/res/values-sr/strings.xml
new file mode 100644
index 0000000..e8fc322
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-sr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Подешавања"</string>
+ <string name="server_running" msgid="2780193626090379172">"Сервер је покренут…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"Заустави сервер"</string>
+ <string name="server_down" msgid="1030249207496490556">"Сервер је пао"</string>
+ <string name="start_server" msgid="3878573341408591975">"Покрени сервер"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Онемогућено"</item>
+ <item msgid="3193389681837907872">"Омогућено"</item>
+ <item msgid="3124590179479393815">"Некомпатибилно"</item>
+ <item msgid="1606753456265236910">"Додељује се"</item>
+ <item msgid="3930807209231347454">"Уврштено"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Није додељено"</item>
+ <item msgid="7598231293776486217">"Додељено"</item>
+ <item msgid="3720547957514534185">"Није обавезно"</item>
+ <item msgid="1264673582354896949">"У току"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-sv/strings.xml b/testapps/TestServerApp/app/src/main/res/values-sv/strings.xml
new file mode 100644
index 0000000..8470695
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-sv/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Inställningar"</string>
+ <string name="server_running" msgid="2780193626090379172">"Servern körs …"</string>
+ <string name="stop_server" msgid="6192029827529013598">"Stoppa servern"</string>
+ <string name="server_down" msgid="1030249207496490556">"Servern ligger nere"</string>
+ <string name="start_server" msgid="3878573341408591975">"Starta servern"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Inaktiverad"</item>
+ <item msgid="3193389681837907872">"Aktiverad"</item>
+ <item msgid="3124590179479393815">"Ej kompatibel"</item>
+ <item msgid="1606753456265236910">"Certifikaten installeras"</item>
+ <item msgid="3930807209231347454">"Inkluderat"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Ej administrerad"</item>
+ <item msgid="7598231293776486217">"Administrerad"</item>
+ <item msgid="3720547957514534185">"Ej obligatorisk"</item>
+ <item msgid="1264673582354896949">"Pågår"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-sw/strings.xml b/testapps/TestServerApp/app/src/main/res/values-sw/strings.xml
new file mode 100644
index 0000000..c1155b8
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-sw/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Mipangilio"</string>
+ <string name="server_running" msgid="2780193626090379172">"Seva inatekeleza..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Sitisha Seva"</string>
+ <string name="server_down" msgid="1030249207496490556">"Seva iko chini"</string>
+ <string name="start_server" msgid="3878573341408591975">"Washa Seva"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Imezimwa"</item>
+ <item msgid="3193389681837907872">"Imewashwa"</item>
+ <item msgid="3124590179479393815">"Haioani"</item>
+ <item msgid="1606753456265236910">"Inaandaa"</item>
+ <item msgid="3930807209231347454">"Imejumuishwa"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Haijatolewa"</item>
+ <item msgid="7598231293776486217">"Imetolewa"</item>
+ <item msgid="3720547957514534185">"Haihitajiki"</item>
+ <item msgid="1264673582354896949">"Inaendelea"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-ta/strings.xml b/testapps/TestServerApp/app/src/main/res/values-ta/strings.xml
new file mode 100644
index 0000000..adad427
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-ta/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"அமைப்புகள்"</string>
+ <string name="server_running" msgid="2780193626090379172">"சேவையகம் இயக்கத்தில் உள்ளது..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"சேவையகத்தை நிறுத்து"</string>
+ <string name="server_down" msgid="1030249207496490556">"சேவையகம் இயங்கவில்லை"</string>
+ <string name="start_server" msgid="3878573341408591975">"சேவையகத்தைத் தொடங்கு"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"முடக்கப்பட்டது"</item>
+ <item msgid="3193389681837907872">"இயக்கப்பட்டது"</item>
+ <item msgid="3124590179479393815">"இணக்கமற்றது"</item>
+ <item msgid="1606753456265236910">"அமைக்கிறது"</item>
+ <item msgid="3930807209231347454">"சேர்க்கப்பட்டது"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"அமைக்கப்படவில்லை"</item>
+ <item msgid="7598231293776486217">"அமைக்கப்பட்டது"</item>
+ <item msgid="3720547957514534185">"அவசியமில்லை"</item>
+ <item msgid="1264673582354896949">"செயலிலுள்ளது"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-te/strings.xml b/testapps/TestServerApp/app/src/main/res/values-te/strings.xml
new file mode 100644
index 0000000..39cc2fe
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-te/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"టెస్ట్సర్వర్యాప్"</string>
+ <string name="action_settings" msgid="1335152369747372374">"సెట్టింగ్లు"</string>
+ <string name="server_running" msgid="2780193626090379172">"సర్వర్ రన్ అవుతోంది..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"సర్వర్ను ఆపివేయండి"</string>
+ <string name="server_down" msgid="1030249207496490556">"సర్వర్ డౌన్ అయింది"</string>
+ <string name="start_server" msgid="3878573341408591975">"సర్వర్ను ప్రారంభించండి"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"డిజేబుల్ చేయబడింది"</item>
+ <item msgid="3193389681837907872">"ఎనేబుల్ చేయబడింది"</item>
+ <item msgid="3124590179479393815">"అనుకూలంగా లేదు"</item>
+ <item msgid="1606753456265236910">"కేటాయిస్తోంది"</item>
+ <item msgid="3930807209231347454">"చేర్చబడింది"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"కేటాయించలేదు"</item>
+ <item msgid="7598231293776486217">"కేటాయించబడింది"</item>
+ <item msgid="3720547957514534185">"అవసరం లేదు"</item>
+ <item msgid="1264673582354896949">"ప్రోగ్రెస్లో ఉంది"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-th/strings.xml b/testapps/TestServerApp/app/src/main/res/values-th/strings.xml
new file mode 100644
index 0000000..78232ca
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-th/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"แอปเซิร์ฟเวอร์ทดสอบ"</string>
+ <string name="action_settings" msgid="1335152369747372374">"การตั้งค่า"</string>
+ <string name="server_running" msgid="2780193626090379172">"เซิร์ฟเวอร์กำลังทำงาน..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"หยุดเซิร์ฟเวอร์"</string>
+ <string name="server_down" msgid="1030249207496490556">"เซิร์ฟเวอร์ขัดข้อง"</string>
+ <string name="start_server" msgid="3878573341408591975">"เริ่มต้นเซิร์ฟเวอร์"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"ปิดใช้อยู่"</item>
+ <item msgid="3193389681837907872">"เปิดใช้อยู่"</item>
+ <item msgid="3124590179479393815">"ใช้งานร่วมกันไม่ได้"</item>
+ <item msgid="1606753456265236910">"การจัดสรร"</item>
+ <item msgid="3930807209231347454">"รวมอยู่ด้วย"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"ยังไม่ได้จัดสรร"</item>
+ <item msgid="7598231293776486217">"จัดสรรแล้ว"</item>
+ <item msgid="3720547957514534185">"ไม่บังคับ"</item>
+ <item msgid="1264673582354896949">"อยู่ในระหว่างดำเนินการ"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-tl/strings.xml b/testapps/TestServerApp/app/src/main/res/values-tl/strings.xml
deleted file mode 100644
index 0133ed5..0000000
--- a/testapps/TestServerApp/app/src/main/res/values-tl/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
- <string name="action_settings" msgid="1335152369747372374">"Mga Setting"</string>
- <string name="server_running" msgid="2780193626090379172">"Gumagana ang server..."</string>
- <string name="stop_server" msgid="6192029827529013598">"Patigilin ang Server"</string>
- <string name="server_down" msgid="1030249207496490556">"Down ang server"</string>
- <string name="start_server" msgid="3878573341408591975">"Simulan ang Server"</string>
- <string-array name="entitlement_status">
- <item msgid="5560300387618996934">"Naka-disable"</item>
- <item msgid="3193389681837907872">"Naka-enable"</item>
- <item msgid="3124590179479393815">"Hindi Compatible"</item>
- <item msgid="1606753456265236910">"Provisioning"</item>
- <item msgid="3930807209231347454">"Kasama"</item>
- </string-array>
- <string-array name="provision_status">
- <item msgid="3486273747926710021">"Hindi Naka-provision"</item>
- <item msgid="7598231293776486217">"Naka-provision"</item>
- <item msgid="3720547957514534185">"Hindi Kinakailangan"</item>
- <item msgid="1264673582354896949">"Isinasagawa"</item>
- </string-array>
-</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-tr/strings.xml b/testapps/TestServerApp/app/src/main/res/values-tr/strings.xml
deleted file mode 100644
index d5950a1..0000000
--- a/testapps/TestServerApp/app/src/main/res/values-tr/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
- <string name="action_settings" msgid="1335152369747372374">"Ayarlar"</string>
- <string name="server_running" msgid="2780193626090379172">"Sunucu çalışıyor..."</string>
- <string name="stop_server" msgid="6192029827529013598">"Sunucuyu Durdur"</string>
- <string name="server_down" msgid="1030249207496490556">"Sunucu kapalı"</string>
- <string name="start_server" msgid="3878573341408591975">"Sunucuyu Başlat"</string>
- <string-array name="entitlement_status">
- <item msgid="5560300387618996934">"Devre dışı"</item>
- <item msgid="3193389681837907872">"Etkin"</item>
- <item msgid="3124590179479393815">"Uyumsuz"</item>
- <item msgid="1606753456265236910">"Temel Hazırlık Yapılıyor"</item>
- <item msgid="3930807209231347454">"Dahil"</item>
- </string-array>
- <string-array name="provision_status">
- <item msgid="3486273747926710021">"Temel Hazırlığı Yapılmadı"</item>
- <item msgid="7598231293776486217">"Temel Hazırlığı Yapıldı"</item>
- <item msgid="3720547957514534185">"Gerekli Değil"</item>
- <item msgid="1264673582354896949">"Devam Ediyor"</item>
- </string-array>
-</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-uk/strings.xml b/testapps/TestServerApp/app/src/main/res/values-uk/strings.xml
deleted file mode 100644
index 0899971..0000000
--- a/testapps/TestServerApp/app/src/main/res/values-uk/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
- <string name="action_settings" msgid="1335152369747372374">"Налаштування"</string>
- <string name="server_running" msgid="2780193626090379172">"Сервер працює…"</string>
- <string name="stop_server" msgid="6192029827529013598">"Зупинити сервер"</string>
- <string name="server_down" msgid="1030249207496490556">"Сервер не працює"</string>
- <string name="start_server" msgid="3878573341408591975">"Запустити сервер"</string>
- <string-array name="entitlement_status">
- <item msgid="5560300387618996934">"Вимкнено"</item>
- <item msgid="3193389681837907872">"Увімкнено"</item>
- <item msgid="3124590179479393815">"Несумісні"</item>
- <item msgid="1606753456265236910">"Надання"</item>
- <item msgid="3930807209231347454">"Включено"</item>
- </string-array>
- <string-array name="provision_status">
- <item msgid="3486273747926710021">"Не надано"</item>
- <item msgid="7598231293776486217">"Надано"</item>
- <item msgid="3720547957514534185">"Необов’язково"</item>
- <item msgid="1264673582354896949">"Виконується"</item>
- </string-array>
-</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-ur/strings.xml b/testapps/TestServerApp/app/src/main/res/values-ur/strings.xml
new file mode 100644
index 0000000..5df75a7
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-ur/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"ترتیبات"</string>
+ <string name="server_running" msgid="2780193626090379172">"سرور چل رہا ہے..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"سرور روکیں"</string>
+ <string name="server_down" msgid="1030249207496490556">"سرور ڈاؤن ہے"</string>
+ <string name="start_server" msgid="3878573341408591975">"سرور شروع کریں"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"غیر فعال ہے"</item>
+ <item msgid="3193389681837907872">"فعال ہے"</item>
+ <item msgid="3124590179479393815">"غیر مطابقت پذیر"</item>
+ <item msgid="1606753456265236910">"فراہمی"</item>
+ <item msgid="3930807209231347454">"شامل ہے"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"فراہم نہیں کیا گا"</item>
+ <item msgid="7598231293776486217">"فراہم کیا گیا"</item>
+ <item msgid="3720547957514534185">"غیر مطلوب"</item>
+ <item msgid="1264673582354896949">"پیشرفت میں ہے"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-uz/strings.xml b/testapps/TestServerApp/app/src/main/res/values-uz/strings.xml
deleted file mode 100644
index bed928c..0000000
--- a/testapps/TestServerApp/app/src/main/res/values-uz/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
- <string name="action_settings" msgid="1335152369747372374">"Sozlamalar"</string>
- <string name="server_running" msgid="2780193626090379172">"Server ishlayapti..."</string>
- <string name="stop_server" msgid="6192029827529013598">"Serverni to‘xtatish"</string>
- <string name="server_down" msgid="1030249207496490556">"Server ishlamayapti"</string>
- <string name="start_server" msgid="3878573341408591975">"Serverni ishga tushirish"</string>
- <string-array name="entitlement_status">
- <item msgid="5560300387618996934">"Yoqilmagan"</item>
- <item msgid="3193389681837907872">"Yoqilgan"</item>
- <item msgid="3124590179479393815">"Mos emas"</item>
- <item msgid="1606753456265236910">"Sinxronlanmoqda"</item>
- <item msgid="3930807209231347454">"Kiritilgan"</item>
- </string-array>
- <string-array name="provision_status">
- <item msgid="3486273747926710021">"Taqdim etilmagan"</item>
- <item msgid="7598231293776486217">"Taqdim etilgan"</item>
- <item msgid="3720547957514534185">"Majburiy emas"</item>
- <item msgid="1264673582354896949">"Bajarilmoqda"</item>
- </string-array>
-</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-vi/strings.xml b/testapps/TestServerApp/app/src/main/res/values-vi/strings.xml
new file mode 100644
index 0000000..78bd3d8
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-vi/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Cài đặt"</string>
+ <string name="server_running" msgid="2780193626090379172">"Máy chủ đang chạy..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Dừng máy chủ"</string>
+ <string name="server_down" msgid="1030249207496490556">"Máy chủ không hoạt động"</string>
+ <string name="start_server" msgid="3878573341408591975">"Khởi động máy chủ"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Đã tắt"</item>
+ <item msgid="3193389681837907872">"Đã bật"</item>
+ <item msgid="3124590179479393815">"Không tương thích"</item>
+ <item msgid="1606753456265236910">"Đang cung cấp"</item>
+ <item msgid="3930807209231347454">"Đã bao gồm"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Chưa được cung cấp"</item>
+ <item msgid="7598231293776486217">"Đã cung cấp"</item>
+ <item msgid="3720547957514534185">"Không bắt buộc"</item>
+ <item msgid="1264673582354896949">"Đang xử lý"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-zh-rCN/strings.xml b/testapps/TestServerApp/app/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..6e26819
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"设置"</string>
+ <string name="server_running" msgid="2780193626090379172">"服务器正在运行…"</string>
+ <string name="stop_server" msgid="6192029827529013598">"停止服务器"</string>
+ <string name="server_down" msgid="1030249207496490556">"服务器出现故障"</string>
+ <string name="start_server" msgid="3878573341408591975">"启动服务器"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"已停用"</item>
+ <item msgid="3193389681837907872">"已启用"</item>
+ <item msgid="3124590179479393815">"不兼容"</item>
+ <item msgid="1606753456265236910">"正在配置"</item>
+ <item msgid="3930807209231347454">"已包含"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"未配置"</item>
+ <item msgid="7598231293776486217">"已配置"</item>
+ <item msgid="3720547957514534185">"不需要"</item>
+ <item msgid="1264673582354896949">"进行中"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-zh-rHK/strings.xml b/testapps/TestServerApp/app/src/main/res/values-zh-rHK/strings.xml
deleted file mode 100644
index a9ab91c..0000000
--- a/testapps/TestServerApp/app/src/main/res/values-zh-rHK/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
- <string name="action_settings" msgid="1335152369747372374">"設定"</string>
- <string name="server_running" msgid="2780193626090379172">"伺服器運作中…"</string>
- <string name="stop_server" msgid="6192029827529013598">"停止伺服器"</string>
- <string name="server_down" msgid="1030249207496490556">"伺服器故障"</string>
- <string name="start_server" msgid="3878573341408591975">"啟動伺服器"</string>
- <string-array name="entitlement_status">
- <item msgid="5560300387618996934">"已停用"</item>
- <item msgid="3193389681837907872">"已啟用"</item>
- <item msgid="3124590179479393815">"不兼容"</item>
- <item msgid="1606753456265236910">"正在佈建"</item>
- <item msgid="3930807209231347454">"已包括"</item>
- </string-array>
- <string-array name="provision_status">
- <item msgid="3486273747926710021">"未佈建"</item>
- <item msgid="7598231293776486217">"已佈建"</item>
- <item msgid="3720547957514534185">"非必填"</item>
- <item msgid="1264673582354896949">"進行中"</item>
- </string-array>
-</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-zh-rTW/strings.xml b/testapps/TestServerApp/app/src/main/res/values-zh-rTW/strings.xml
deleted file mode 100644
index 8b32974..0000000
--- a/testapps/TestServerApp/app/src/main/res/values-zh-rTW/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="2894617184221823208">"TestServerApp"</string>
- <string name="action_settings" msgid="1335152369747372374">"設定"</string>
- <string name="server_running" msgid="2780193626090379172">"伺服器正在運作中..."</string>
- <string name="stop_server" msgid="6192029827529013598">"停止伺服器"</string>
- <string name="server_down" msgid="1030249207496490556">"伺服器發生問題"</string>
- <string name="start_server" msgid="3878573341408591975">"啟動伺服器"</string>
- <string-array name="entitlement_status">
- <item msgid="5560300387618996934">"已停用"</item>
- <item msgid="3193389681837907872">"已啟用"</item>
- <item msgid="3124590179479393815">"不相容"</item>
- <item msgid="1606753456265236910">"佈建中"</item>
- <item msgid="3930807209231347454">"已納入"</item>
- </string-array>
- <string-array name="provision_status">
- <item msgid="3486273747926710021">"尚未佈建"</item>
- <item msgid="7598231293776486217">"已佈建"</item>
- <item msgid="3720547957514534185">"非必要"</item>
- <item msgid="1264673582354896949">"進行中"</item>
- </string-array>
-</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values-zu/strings.xml b/testapps/TestServerApp/app/src/main/res/values-zu/strings.xml
new file mode 100644
index 0000000..fbb6262
--- /dev/null
+++ b/testapps/TestServerApp/app/src/main/res/values-zu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name" msgid="2894617184221823208">"I-TestServerApp"</string>
+ <string name="action_settings" msgid="1335152369747372374">"Amasethingi"</string>
+ <string name="server_running" msgid="2780193626090379172">"Iseva iyaqhubeka..."</string>
+ <string name="stop_server" msgid="6192029827529013598">"Misa Iseva"</string>
+ <string name="server_down" msgid="1030249207496490556">"Iseva iphansi"</string>
+ <string name="start_server" msgid="3878573341408591975">"Qalisa Iseva"</string>
+ <string-array name="entitlement_status">
+ <item msgid="5560300387618996934">"Kukhutshaziwe"</item>
+ <item msgid="3193389681837907872">"Kunikwe amandla"</item>
+ <item msgid="3124590179479393815">"Ayisebenzisani"</item>
+ <item msgid="1606753456265236910">"Iyahlinzeka"</item>
+ <item msgid="3930807209231347454">"Kuhlanganisiwe"</item>
+ </string-array>
+ <string-array name="provision_status">
+ <item msgid="3486273747926710021">"Akulungiselelwanga"</item>
+ <item msgid="7598231293776486217">"Kulungiselelwe"</item>
+ <item msgid="3720547957514534185">"Akudingekile"</item>
+ <item msgid="1264673582354896949">"Kuyaqhubeka"</item>
+ </string-array>
+</resources>
diff --git a/testapps/TestServerApp/app/src/main/res/values/colors.xml b/testapps/TestServerApp/app/src/main/res/values/colors.xml
deleted file mode 100644
index 09837df..0000000
--- a/testapps/TestServerApp/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <color name="purple_200">#FFBB86FC</color>
- <color name="purple_500">#FF6200EE</color>
- <color name="purple_700">#FF3700B3</color>
- <color name="teal_200">#FF03DAC5</color>
- <color name="teal_700">#FF018786</color>
- <color name="black">#FF000000</color>
- <color name="white">#FFFFFFFF</color>
-</resources>
\ No newline at end of file
diff --git a/testapps/TestServerApp/app/src/main/res/values/strings.xml b/testapps/TestServerApp/app/src/main/res/values/strings.xml
deleted file mode 100644
index 0d1efa8..0000000
--- a/testapps/TestServerApp/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<resources>
- <string name="app_name">TestServerApp</string>
- <string name="action_settings">Settings</string>
- <string name="server_running">Server is running...</string>
- <string name="stop_server">Stop Server</string>
- <string name="server_down">Server is down</string>
- <string name="start_server">Start Server</string>
- <string-array name="entitlement_status">
- <item>Disabled</item>
- <item>Enabled</item>
- <item>Incompatible</item>
- <item>Provisioning</item>
- <item>Included</item>
- </string-array>
- <string-array name="provision_status">
- <item>Not Provisioned</item>
- <item>Provisioned</item>
- <item>Not Required</item>
- <item>In Progress</item>
- </string-array>
-</resources>
\ No newline at end of file
diff --git a/testapps/TestServerApp/app/src/main/res/xml/backup_rules.xml b/testapps/TestServerApp/app/src/main/res/xml/backup_rules.xml
deleted file mode 100644
index 9b42d90..0000000
--- a/testapps/TestServerApp/app/src/main/res/xml/backup_rules.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- Sample backup rules file; uncomment and customize as necessary.
- See https://developer.android.com/guide/topics/data/autobackup
- for details.
- Note: This file is ignored for devices older that API 31
- See https://developer.android.com/about/versions/12/backup-restore
--->
-<full-backup-content>
- <!--
- <include domain="sharedpref" path="."/>
- <exclude domain="sharedpref" path="device.xml"/>
--->
-</full-backup-content>
\ No newline at end of file
diff --git a/testapps/TestServerApp/app/src/main/res/xml/data_extraction_rules.xml b/testapps/TestServerApp/app/src/main/res/xml/data_extraction_rules.xml
deleted file mode 100644
index c6c3bb0..0000000
--- a/testapps/TestServerApp/app/src/main/res/xml/data_extraction_rules.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- Sample data extraction rules file; uncomment and customize as necessary.
- See https://developer.android.com/about/versions/12/backup-restore#xml-changes
- for details.
--->
-<data-extraction-rules>
- <cloud-backup>
- <!-- TODO: Use <include> and <exclude> to control what is backed up.
- <include .../>
- <exclude .../>
- -->
- </cloud-backup>
- <!--
- <device-transfer>
- <include .../>
- <exclude .../>
- </device-transfer>
- -->
-</data-extraction-rules>
\ No newline at end of file
diff --git a/testapps/TestServerApp/app/src/test/java/com/google/android/testserverapp/ExampleUnitTest.java b/testapps/TestServerApp/app/src/test/java/com/google/android/testserverapp/ExampleUnitTest.java
deleted file mode 100644
index 88a8c5a..0000000
--- a/testapps/TestServerApp/app/src/test/java/com/google/android/testserverapp/ExampleUnitTest.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.google.android.testserverapp;
-
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
- */
-public class ExampleUnitTest {
-
- @Test
- public void addition_isCorrect() {
- assertEquals(4, 2 + 2);
- }
-}
\ No newline at end of file
diff --git a/testapps/TestServerApp/build.gradle b/testapps/TestServerApp/build.gradle
deleted file mode 100644
index 90f9008..0000000
--- a/testapps/TestServerApp/build.gradle
+++ /dev/null
@@ -1,5 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-plugins {
- id 'com.android.application' version '7.3.0' apply false
- id 'com.android.library' version '7.3.0' apply false
-}
\ No newline at end of file
diff --git a/testapps/TestServerApp/gradle.properties b/testapps/TestServerApp/gradle.properties
deleted file mode 100644
index 3e927b1..0000000
--- a/testapps/TestServerApp/gradle.properties
+++ /dev/null
@@ -1,21 +0,0 @@
-# Project-wide Gradle settings.
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
-# AndroidX package structure to make it clearer which packages are bundled with the
-# Android operating system, and which are packaged with your app's APK
-# https://developer.android.com/topic/libraries/support-library/androidx-rn
-android.useAndroidX=true
-# Enables namespacing of each library's R class so that its R class includes only the
-# resources declared in the library itself and none from the library's dependencies,
-# thereby reducing the size of the R class for that library
-android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/testapps/TestServerApp/gradle/wrapper/gradle-wrapper.jar b/testapps/TestServerApp/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index e708b1c..0000000
--- a/testapps/TestServerApp/gradle/wrapper/gradle-wrapper.jar
+++ /dev/null
Binary files differ
diff --git a/testapps/TestServerApp/gradle/wrapper/gradle-wrapper.properties b/testapps/TestServerApp/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index e12f4cd..0000000
--- a/testapps/TestServerApp/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-#Sat Nov 05 01:06:49 UTC 2022
-distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
-distributionPath=wrapper/dists
-zipStorePath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
diff --git a/testapps/TestServerApp/gradlew b/testapps/TestServerApp/gradlew
deleted file mode 100755
index 4f906e0..0000000
--- a/testapps/TestServerApp/gradlew
+++ /dev/null
@@ -1,185 +0,0 @@
-#!/usr/bin/env sh
-
-#
-# Copyright 2015 the original author or authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-##############################################################################
-##
-## Gradle start up script for UN*X
-##
-##############################################################################
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn () {
- echo "$*"
-}
-
-die () {
- echo
- echo "$*"
- echo
- exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
-esac
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
- else
- JAVACMD="$JAVA_HOME/bin/java"
- fi
- if [ ! -x "$JAVACMD" ] ; then
- die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
- fi
-else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin or MSYS, switch paths to Windows format before running java
-if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
- fi
- i=`expr $i + 1`
- done
- case $i in
- 0) set -- ;;
- 1) set -- "$args0" ;;
- 2) set -- "$args0" "$args1" ;;
- 3) set -- "$args0" "$args1" "$args2" ;;
- 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
-fi
-
-# Escape application args
-save () {
- for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
- echo " "
-}
-APP_ARGS=`save "$@"`
-
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-
-exec "$JAVACMD" "$@"
diff --git a/testapps/TestServerApp/gradlew.bat b/testapps/TestServerApp/gradlew.bat
deleted file mode 100644
index ac1b06f..0000000
--- a/testapps/TestServerApp/gradlew.bat
+++ /dev/null
@@ -1,89 +0,0 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
diff --git a/testapps/TestServerApp/settings.gradle b/testapps/TestServerApp/settings.gradle
deleted file mode 100644
index a6a2846..0000000
--- a/testapps/TestServerApp/settings.gradle
+++ /dev/null
@@ -1,16 +0,0 @@
-pluginManagement {
- repositories {
- gradlePluginPortal()
- google()
- mavenCentral()
- }
-}
-dependencyResolutionManagement {
- repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
- repositories {
- google()
- mavenCentral()
- }
-}
-rootProject.name = "TestServerApp"
-include ':app'
diff --git a/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/PrioritizeLatency.java b/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/PrioritizeLatency.java
index 9dc4732..0f120f4 100644
--- a/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/PrioritizeLatency.java
+++ b/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/PrioritizeLatency.java
@@ -130,12 +130,18 @@
mPing.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
+ Log.d(LOG_TAG, "Clicking Ping button");
if (mNetwork != null) {
- try {
- new RequestTask().ping(mNetwork);
- } catch (Exception e) {
- Log.e(LOG_TAG, "Exception at ping: " + e);
- }
+ mFixedThreadPool.execute(() -> {
+ try {
+ RequestTask requestTask = new RequestTask();
+ requestTask.ping(mNetwork);
+ updateResultTextView("Result: Ping is done successfully!");
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Exception at ping: " + e);
+ updateResultTextView("Result: Got exception with ping!!!");
+ }
+ });
}
}
});
@@ -149,9 +155,7 @@
public void onAvailable(final Network network) {
Log.d(LOG_TAG, "onAvailable + " + network);
mNetwork = network;
- mPing.setEnabled(true);
- mNetworkRequestRelease.setText(R.string.release_network);
- mResultTextView.setText(R.string.network_available);
+ updateUIOnNetworkAvailable();
}
};
NetworkRequest.Builder builder = new NetworkRequest.Builder();
@@ -246,6 +250,17 @@
});
}
+ private void updateUIOnNetworkAvailable() {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mPing.setEnabled(true);
+ mNetworkRequestRelease.setText(R.string.release_network);
+ mResultTextView.setText(R.string.network_available);
+ }
+ });
+ }
+
private String purchasePremiumResultToText(int result) {
switch (result) {
case PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS:
diff --git a/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/RequestTask.java b/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/RequestTask.java
index 1521a14..569c066 100644
--- a/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/RequestTask.java
+++ b/testapps/TestSliceApp/app/src/main/java/com/google/android/sample/testsliceapp/RequestTask.java
@@ -19,11 +19,11 @@
import android.os.AsyncTask;
import android.util.Log;
-import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
+import java.util.Scanner;
class RequestTask extends AsyncTask<Network, Integer, Integer> {
protected Integer doInBackground(Network... network) {
@@ -59,7 +59,8 @@
try {
InputStream inputStream = connection.getInputStream();
Log.d("httpGet", "httpUrl + " + httpUrl);
- return new BufferedInputStream(inputStream).toString();
+ Scanner scanner = new Scanner(inputStream).useDelimiter("\\A");
+ return scanner.hasNext() ? scanner.next() : "";
} finally {
connection.disconnect();
}
diff --git a/testapps/TestSliceApp/app/src/main/res/values-sw/strings.xml b/testapps/TestSliceApp/app/src/main/res/values-sw/strings.xml
index f79ee6f..c01aa3a 100644
--- a/testapps/TestSliceApp/app/src/main/res/values-sw/strings.xml
+++ b/testapps/TestSliceApp/app/src/main/res/values-sw/strings.xml
@@ -3,36 +3,20 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="1265450418387661962">"TestSliceApp"</string>
<string name="hello_blank_fragment" msgid="1245093642770491175">"Kipande cha salamu kisichokuwa na kitu"</string>
- <!-- no translation found for request_network (8945235490804849914) -->
- <skip />
- <!-- no translation found for release_network (174252378593535238) -->
- <skip />
- <!-- no translation found for ping (7890607576220714932) -->
- <skip />
- <!-- no translation found for result_prefix (3522796186427501399) -->
- <skip />
- <!-- no translation found for latency_title (963052613947017009) -->
- <skip />
- <!-- no translation found for bw_title (3902162973688221344) -->
- <skip />
- <!-- no translation found for cbs_title (5234410535569935600) -->
- <skip />
- <!-- no translation found for purchase (7843181995697372128) -->
- <skip />
- <!-- no translation found for network_available (4780293262690730734) -->
- <skip />
- <!-- no translation found for network_requested (5646123922691865991) -->
- <skip />
- <!-- no translation found for network_released (2992280481133877025) -->
- <skip />
- <!-- no translation found for network_release_failed (256471231420029151) -->
- <skip />
- <!-- no translation found for purchase_exception (8876841120055716671) -->
- <skip />
- <!-- no translation found for purchase_empty_result (7497824191649973928) -->
- <skip />
- <!-- no translation found for premium_not_available (7346368693802644748) -->
- <skip />
- <!-- no translation found for purchase_in_progress (5450288183685032424) -->
- <skip />
+ <string name="request_network" msgid="8945235490804849914">"Request Network"</string>
+ <string name="release_network" msgid="174252378593535238">"Release Network"</string>
+ <string name="ping" msgid="7890607576220714932">"Ping"</string>
+ <string name="result_prefix" msgid="3522796186427501399">"Result:"</string>
+ <string name="latency_title" msgid="963052613947017009">"Prioritize Latency"</string>
+ <string name="bw_title" msgid="3902162973688221344">"Prioritize Bandwidth"</string>
+ <string name="cbs_title" msgid="5234410535569935600">"CBS"</string>
+ <string name="purchase" msgid="7843181995697372128">"Purchase Network Premium"</string>
+ <string name="network_available" msgid="4780293262690730734">"Result: The requested network is available now!"</string>
+ <string name="network_requested" msgid="5646123922691865991">"Result: The network has been requested!"</string>
+ <string name="network_released" msgid="2992280481133877025">"Result: The network has been released!"</string>
+ <string name="network_release_failed" msgid="256471231420029151">"Result: Failed to release the network!!!"</string>
+ <string name="purchase_exception" msgid="8876841120055716671">"Result: Exception when purchasing network premium!!!"</string>
+ <string name="purchase_empty_result" msgid="7497824191649973928">"Result: Got empty result when purchasing network premium!!!"</string>
+ <string name="premium_not_available" msgid="7346368693802644748">"Result: The network premium is not available for purchase!!!"</string>
+ <string name="purchase_in_progress" msgid="5450288183685032424">"Result: The network premium purchase is in progress ..."</string>
</resources>
diff --git a/tests/src/com/android/TestContext.java b/tests/src/com/android/TestContext.java
index 7c3a842..720d235 100644
--- a/tests/src/com/android/TestContext.java
+++ b/tests/src/com/android/TestContext.java
@@ -61,7 +61,11 @@
@Mock ImsManager mMockImsManager;
@Mock UserManager mMockUserManager;
- private SparseArray<PersistableBundle> mCarrierConfigs = new SparseArray<>();
+ private final SparseArray<PersistableBundle> mCarrierConfigs = new SparseArray<>();
+
+ private Intent mIntent;
+
+ private BroadcastReceiver mReceiver;
private final HashSet<String> mPermissionTable = new HashSet<>();
@@ -105,28 +109,42 @@
}
@Override
+ public void sendBroadcast(Intent intent) {
+ mIntent = intent;
+ }
+
+ @Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ mReceiver = receiver;
return null;
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {
+ mReceiver = receiver;
return null;
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
+ mReceiver = receiver;
return null;
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler, int flags) {
+ mReceiver = receiver;
return null;
}
@Override
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ mReceiver = null;
+ }
+
+ @Override
public ContentResolver getContentResolver() {
return null;
}
@@ -134,22 +152,22 @@
@Override
public Object getSystemService(String name) {
switch (name) {
- case (Context.CARRIER_CONFIG_SERVICE) : {
+ case Context.CARRIER_CONFIG_SERVICE: {
return mMockCarrierConfigManager;
}
- case (Context.TELECOM_SERVICE) : {
+ case Context.TELECOM_SERVICE: {
return mMockTelecomManager;
}
- case (Context.TELEPHONY_SERVICE) : {
+ case Context.TELEPHONY_SERVICE: {
return mMockTelephonyManager;
}
- case (Context.TELEPHONY_SUBSCRIPTION_SERVICE) : {
+ case Context.TELEPHONY_SUBSCRIPTION_SERVICE: {
return mMockSubscriptionManager;
}
- case(Context.TELEPHONY_IMS_SERVICE) : {
+ case Context.TELEPHONY_IMS_SERVICE: {
return mMockImsManager;
}
- case(Context.USER_SERVICE) : {
+ case Context.USER_SERVICE: {
return mMockUserManager;
}
}
@@ -170,6 +188,9 @@
if (serviceClass == SubscriptionManager.class) {
return Context.TELEPHONY_SUBSCRIPTION_SERVICE;
}
+ if (serviceClass == ImsManager.class) {
+ return Context.TELEPHONY_IMS_SERVICE;
+ }
if (serviceClass == UserManager.class) {
return Context.USER_SERVICE;
}
@@ -252,6 +273,14 @@
}
}
+ public Intent getBroadcast() {
+ return mIntent;
+ }
+
+ public BroadcastReceiver getBroadcastReceiver() {
+ return mReceiver;
+ }
+
private static void logd(String s) {
Log.d(TAG, s);
}
diff --git a/tests/src/com/android/phone/CarrierConfigLoaderTest.java b/tests/src/com/android/phone/CarrierConfigLoaderTest.java
index 9c425d6..6807422 100644
--- a/tests/src/com/android/phone/CarrierConfigLoaderTest.java
+++ b/tests/src/com/android/phone/CarrierConfigLoaderTest.java
@@ -28,7 +28,9 @@
import static org.mockito.Mockito.doNothing;
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.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
@@ -44,6 +46,7 @@
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.TelephonyRegistryManager;
import android.testing.TestableLooper;
import androidx.test.InstrumentationRegistry;
@@ -85,6 +88,7 @@
@Mock PackageInfo mPackageInfo;
@Mock SubscriptionInfoUpdater mSubscriptionInfoUpdater;
@Mock SharedPreferences mSharedPreferences;
+ @Mock TelephonyRegistryManager mTelephonyRegistryManager;
private TelephonyManager mTelephonyManager;
private CarrierConfigLoader mCarrierConfigLoader;
@@ -96,6 +100,7 @@
public void setUp() throws Exception {
super.setUp();
+ // TODO: replace doReturn/when with when/thenReturn which is more readable
doReturn(mSharedPreferences).when(mContext).getSharedPreferences(anyString(), anyInt());
doReturn(Build.FINGERPRINT).when(mSharedPreferences).getString(eq("build_fingerprint"),
any());
@@ -114,6 +119,10 @@
eq(PLATFORM_CARRIER_CONFIG_PACKAGE), eq(0) /*flags*/);
doReturn(PLATFORM_CARRIER_CONFIG_PACKAGE_VERSION_CODE).when(
mPackageInfo).getLongVersionCode();
+ when(mContext.getSystemServiceName(TelephonyRegistryManager.class)).thenReturn(
+ Context.TELEPHONY_REGISTRY_SERVICE);
+ when(mContext.getSystemService(TelephonyRegistryManager.class)).thenReturn(
+ mTelephonyRegistryManager);
mHandlerThread = new HandlerThread("CarrierConfigLoaderTest");
mHandlerThread.start();
@@ -185,6 +194,11 @@
assertThat(mCarrierConfigLoader.getNoSimConfig().getInt(CARRIER_CONFIG_EXAMPLE_KEY))
.isEqualTo(CARRIER_CONFIG_EXAMPLE_VALUE);
verify(mContext).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class));
+ verify(mTelephonyRegistryManager).notifyCarrierConfigChanged(
+ eq(DEFAULT_PHONE_ID),
+ eq(SubscriptionManager.INVALID_SUBSCRIPTION_ID),
+ eq(TelephonyManager.UNKNOWN_CARRIER_ID),
+ eq(TelephonyManager.UNKNOWN_CARRIER_ID));
}
/**
diff --git a/tests/src/com/android/phone/PhoneInterfaceManagerTest.java b/tests/src/com/android/phone/PhoneInterfaceManagerTest.java
index 3b6d5ee..6e4a65f 100644
--- a/tests/src/com/android/phone/PhoneInterfaceManagerTest.java
+++ b/tests/src/com/android/phone/PhoneInterfaceManagerTest.java
@@ -17,15 +17,21 @@
package com.android.phone;
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 static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.content.pm.PackageManager;
+import android.content.SharedPreferences;
import android.content.res.Resources;
import android.telephony.RadioAccessFamily;
import android.telephony.TelephonyManager;
@@ -50,22 +56,24 @@
@RunWith(AndroidJUnit4.class)
public class PhoneInterfaceManagerTest extends TelephonyTestBase {
private PhoneInterfaceManager mPhoneInterfaceManager;
+ private SharedPreferences mSharedPreferences;
@Mock
PhoneGlobals mPhoneGlobals;
@Mock
Phone mPhone;
- @Mock
- PackageManager mPackageManager;
@Before
@UiThreadTest
public void setUp() throws Exception {
super.setUp();
- doReturn(mPackageManager).when(mPhoneGlobals).getPackageManager();
- doReturn(false).when(mPackageManager).hasSystemFeature(
- PackageManager.FEATURE_TELEPHONY_IMS);
- mPhoneInterfaceManager = PhoneInterfaceManager.init(mPhoneGlobals);
+ // Note that PhoneInterfaceManager is a singleton. Calling init gives us a handle to the
+ // global singleton, but the context that is passed in is unused if the phone app is already
+ // alive on a test devices. You must use the spy to mock behavior. Mocks stemming from the
+ // passed context will remain unused.
+ mPhoneInterfaceManager = spy(PhoneInterfaceManager.init(mPhoneGlobals));
+ mSharedPreferences = mPhoneInterfaceManager.getSharedPreferences();
+ mSharedPreferences.edit().remove(Phone.PREF_NULL_CIPHER_AND_INTEGRITY_ENABLED).commit();
}
@Test
@@ -138,4 +146,81 @@
assertEquals("ff-Latn-BF", resultFfBf);
}
+
+ @Test
+ public void setNullCipherAndIntegrityEnabled_successfullyEnable() {
+ doReturn(201).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+ doNothing().when(mPhoneInterfaceManager).enforceModifyPermission();
+ assertFalse(mSharedPreferences.contains(Phone.PREF_NULL_CIPHER_AND_INTEGRITY_ENABLED));
+
+ mPhoneInterfaceManager.setNullCipherAndIntegrityEnabled(true);
+
+ assertTrue(
+ mSharedPreferences.getBoolean(Phone.PREF_NULL_CIPHER_AND_INTEGRITY_ENABLED, false));
+ }
+
+ @Test
+ public void setNullCipherAndIntegrityEnabled_successfullyDisable() {
+ doReturn(201).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+ doNothing().when(mPhoneInterfaceManager).enforceModifyPermission();
+ assertFalse(mSharedPreferences.contains(Phone.PREF_NULL_CIPHER_AND_INTEGRITY_ENABLED));
+
+ mPhoneInterfaceManager.setNullCipherAndIntegrityEnabled(false);
+
+ assertFalse(
+ mSharedPreferences.getBoolean(Phone.PREF_NULL_CIPHER_AND_INTEGRITY_ENABLED, true));
+ }
+
+ @Test
+ public void setNullCipherAndIntegrityEnabled_lackingNecessaryHal() {
+ doReturn(101).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+ doNothing().when(mPhoneInterfaceManager).enforceModifyPermission();
+
+ assertThrows(UnsupportedOperationException.class, () -> {
+ mPhoneInterfaceManager.setNullCipherAndIntegrityEnabled(true);
+ });
+
+ }
+
+ @Test
+ public void setNullCipherAndIntegrityEnabled_lackingPermissions() {
+ doReturn(201).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+ doThrow(SecurityException.class).when(mPhoneInterfaceManager).enforceModifyPermission();
+
+ assertThrows(SecurityException.class, () -> {
+ mPhoneInterfaceManager.setNullCipherAndIntegrityEnabled(true);
+ });
+ }
+
+ @Test
+ public void isNullCipherAndIntegrityPreferenceEnabled() {
+ doReturn(201).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+ doNothing().when(mPhoneInterfaceManager).enforceModifyPermission();
+
+ assertTrue(mPhoneInterfaceManager.isNullCipherAndIntegrityPreferenceEnabled());
+ mPhoneInterfaceManager.setNullCipherAndIntegrityEnabled(false);
+ assertFalse(
+ mSharedPreferences.getBoolean(Phone.PREF_NULL_CIPHER_AND_INTEGRITY_ENABLED, true));
+ }
+
+ @Test
+ public void isNullCipherAndIntegrityPreferenceEnabled_lackingNecessaryHal() {
+ doReturn(101).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+ doNothing().when(mPhoneInterfaceManager).enforceModifyPermission();
+
+ assertThrows(UnsupportedOperationException.class, () -> {
+ mPhoneInterfaceManager.isNullCipherAndIntegrityPreferenceEnabled();
+ });
+
+ }
+
+ @Test
+ public void isNullCipherAndIntegrityPreferenceEnabled_lackingPermissions() {
+ doReturn(201).when(mPhoneInterfaceManager).getHalVersion(anyInt());
+ doThrow(SecurityException.class).when(mPhoneInterfaceManager).enforceReadPermission();
+
+ assertThrows(SecurityException.class, () -> {
+ mPhoneInterfaceManager.isNullCipherAndIntegrityPreferenceEnabled();
+ });
+ }
}
diff --git a/tests/src/com/android/phone/SlicePurchaseControllerTest.java b/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
similarity index 72%
rename from tests/src/com/android/phone/SlicePurchaseControllerTest.java
rename to tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
index ebcf15d..e2ebac0 100644
--- a/tests/src/com/android/phone/SlicePurchaseControllerTest.java
+++ b/tests/src/com/android/phone/slice/SlicePurchaseControllerTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.phone;
+package com.android.phone.slice;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -26,15 +26,19 @@
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.HandlerThread;
@@ -53,38 +57,42 @@
import com.android.TelephonyTestBase;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.Phone;
-import com.android.phone.slice.PremiumNetworkEntitlementApi;
-import com.android.phone.slice.PremiumNetworkEntitlementResponse;
-import com.android.phone.slice.SlicePurchaseController;
-import com.android.phone.slice.SlicePurchaseController.SlicePurchaseControllerBroadcastReceiver;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.Mockito;
+import java.time.LocalDate;
import java.util.Collections;
import java.util.Map;
@RunWith(AndroidJUnit4.class)
public class SlicePurchaseControllerTest extends TelephonyTestBase {
private static final String TAG = "SlicePurchaseControllerTest";
+ private static final String DAILY_NOTIFICATION_COUNT_KEY = "daily_notification_count0";
+ private static final String MONTHLY_NOTIFICATION_COUNT_KEY = "monthly_notification_count0";
+ private static final int YEAR = 2000;
+ private static final int MONTH = 6;
+ private static final int DATE = 1;
private static final int PHONE_ID = 0;
+ private static final int DAILY_NOTIFICATION_MAX = 3;
+ private static final int MONTHLY_NOTIFICATION_MAX = 5;
private static final long NOTIFICATION_TIMEOUT = 1000;
private static final long PURCHASE_CONDITION_TIMEOUT = 2000;
private static final long NETWORK_SETUP_TIMEOUT = 3000;
private static final long THROTTLE_TIMEOUT = 4000;
@Mock Phone mPhone;
- @Mock Context mMockedContext;
@Mock CarrierConfigManager mCarrierConfigManager;
@Mock CommandsInterface mCommandsInterface;
@Mock ServiceState mServiceState;
@Mock PremiumNetworkEntitlementApi mPremiumNetworkEntitlementApi;
+ @Mock SharedPreferences mSharedPreferences;
+ @Mock SharedPreferences.Editor mEditor;
private SlicePurchaseController mSlicePurchaseController;
- private SlicePurchaseControllerBroadcastReceiver mBroadcastReceiver;
private PersistableBundle mBundle;
private PremiumNetworkEntitlementResponse mEntitlementResponse;
private Handler mHandler;
@@ -106,17 +114,34 @@
mTestableLooper = new TestableLooper(mHandler.getLooper());
doReturn(PHONE_ID).when(mPhone).getPhoneId();
- doReturn(mMockedContext).when(mPhone).getContext();
+ doReturn(mContext).when(mPhone).getContext();
doReturn(mServiceState).when(mPhone).getServiceState();
mPhone.mCi = mCommandsInterface;
- doReturn(Context.CARRIER_CONFIG_SERVICE).when(mMockedContext)
- .getSystemServiceName(eq(CarrierConfigManager.class));
- doReturn(mCarrierConfigManager).when(mMockedContext)
- .getSystemService(eq(Context.CARRIER_CONFIG_SERVICE));
+ doReturn(mCarrierConfigManager).when(mContext)
+ .getSystemService(Context.CARRIER_CONFIG_SERVICE);
mBundle = new PersistableBundle();
+ mBundle.putInt(
+ CarrierConfigManager.KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT,
+ DAILY_NOTIFICATION_MAX);
+ mBundle.putInt(
+ CarrierConfigManager.KEY_PREMIUM_CAPABILITY_MAXIMUM_MONTHLY_NOTIFICATION_COUNT_INT,
+ MONTHLY_NOTIFICATION_MAX);
doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
+ doReturn(mSharedPreferences).when(mContext).getSharedPreferences(anyString(), anyInt());
+ doReturn(mEditor).when(mSharedPreferences).edit();
+ doAnswer(invocation -> {
+ doReturn(invocation.getArgument(1)).when(mSharedPreferences)
+ .getInt(eq(invocation.getArgument(0)), anyInt());
+ return null;
+ }).when(mEditor).putInt(anyString(), anyInt());
+ doAnswer(invocation -> {
+ doReturn(invocation.getArgument(1)).when(mSharedPreferences)
+ .getString(eq(invocation.getArgument(0)), anyString());
+ return null;
+ }).when(mEditor).putString(anyString(), anyString());
+
// create a spy to mock final PendingIntent methods
SlicePurchaseController slicePurchaseController =
new SlicePurchaseController(mPhone, mHandler.getLooper());
@@ -127,6 +152,8 @@
Map.of(PHONE_ID, mSlicePurchaseController));
replaceInstance(SlicePurchaseController.class, "mPremiumNetworkEntitlementApi",
mSlicePurchaseController, mPremiumNetworkEntitlementApi);
+ replaceInstance(SlicePurchaseController.class, "mIsSlicingUpsellEnabled",
+ mSlicePurchaseController, true);
mEntitlementResponse = new PremiumNetworkEntitlementResponse();
doReturn(mEntitlementResponse).when(mPremiumNetworkEntitlementApi)
.checkEntitlementStatus(anyInt());
@@ -159,7 +186,6 @@
new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY});
mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING,
SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
- doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mPhone).getSubId();
// retry to verify available
@@ -168,13 +194,16 @@
}
@Test
- public void testIsUrlValid() {
- // all other conditions met
- doReturn((int) TelephonyManager.NETWORK_TYPE_BITMASK_NR).when(mPhone)
- .getCachedAllowedNetworkTypesBitmask();
- mBundle.putIntArray(CarrierConfigManager.KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY,
- new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY});
- doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mPhone).getSubId();
+ public void testGetPurchaseURL() {
+ mEntitlementResponse.mServiceFlowURL = SlicePurchaseController.SLICE_PURCHASE_TEST_FILE;
+ String purchaseUrl = mSlicePurchaseController.getPurchaseUrl(mEntitlementResponse);
+ assertEquals(purchaseUrl, SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
+
+ mEntitlementResponse.mServiceFlowURL = null;
+ mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING,
+ SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
+ purchaseUrl = mSlicePurchaseController.getPurchaseUrl(mEntitlementResponse);
+ assertEquals(purchaseUrl, SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
String[] invalidUrls = new String[] {
null,
@@ -188,16 +217,56 @@
};
for (String url : invalidUrls) {
mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING, url);
- doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
- assertFalse(mSlicePurchaseController.isPremiumCapabilityAvailableForPurchase(
- TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY));
+ assertEquals("", mSlicePurchaseController.getPurchaseUrl(mEntitlementResponse));
}
+ }
- mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING,
- SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
- doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
- assertTrue(mSlicePurchaseController.isPremiumCapabilityAvailableForPurchase(
- TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY));
+ @Test
+ public void testUpdateNotificationCounts() {
+ mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR, MONTH, DATE));
+ mSlicePurchaseController.updateNotificationCounts();
+
+ // change only date, month and year remain the same
+ Mockito.clearInvocations(mEditor);
+ mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR, MONTH, DATE + 1));
+ mSlicePurchaseController.updateNotificationCounts();
+ verify(mEditor).putInt(eq(DAILY_NOTIFICATION_COUNT_KEY), eq(0));
+ verify(mEditor, never()).putInt(eq(MONTHLY_NOTIFICATION_COUNT_KEY), eq(0));
+
+ // change only month, date and year remain the same
+ Mockito.clearInvocations(mEditor);
+ mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR, MONTH + 1, DATE + 1));
+ mSlicePurchaseController.updateNotificationCounts();
+ verify(mEditor).putInt(eq(DAILY_NOTIFICATION_COUNT_KEY), eq(0));
+ verify(mEditor).putInt(eq(MONTHLY_NOTIFICATION_COUNT_KEY), eq(0));
+
+ // change only year, date and month remain the same
+ Mockito.clearInvocations(mEditor);
+ mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR + 1, MONTH + 1, DATE + 1));
+ mSlicePurchaseController.updateNotificationCounts();
+ verify(mEditor).putInt(eq(DAILY_NOTIFICATION_COUNT_KEY), eq(0));
+ verify(mEditor).putInt(eq(MONTHLY_NOTIFICATION_COUNT_KEY), eq(0));
+
+ // change only month and year, date remains the same
+ Mockito.clearInvocations(mEditor);
+ mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR + 2, MONTH + 2, DATE + 1));
+ mSlicePurchaseController.updateNotificationCounts();
+ verify(mEditor).putInt(eq(DAILY_NOTIFICATION_COUNT_KEY), eq(0));
+ verify(mEditor).putInt(eq(MONTHLY_NOTIFICATION_COUNT_KEY), eq(0));
+
+ // change only date and year, month remains the same
+ Mockito.clearInvocations(mEditor);
+ mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR + 3, MONTH + 2, DATE + 2));
+ mSlicePurchaseController.updateNotificationCounts();
+ verify(mEditor).putInt(eq(DAILY_NOTIFICATION_COUNT_KEY), eq(0));
+ verify(mEditor).putInt(eq(MONTHLY_NOTIFICATION_COUNT_KEY), eq(0));
+
+ // change only date and month, year remains the same
+ Mockito.clearInvocations(mEditor);
+ mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR + 3, MONTH + 3, DATE + 3));
+ mSlicePurchaseController.updateNotificationCounts();
+ verify(mEditor).putInt(eq(DAILY_NOTIFICATION_COUNT_KEY), eq(0));
+ verify(mEditor).putInt(eq(MONTHLY_NOTIFICATION_COUNT_KEY), eq(0));
}
@Test
@@ -237,7 +306,6 @@
new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY});
mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING,
SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
- doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
mSlicePurchaseController.purchasePremiumCapability(
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, TAG,
@@ -255,7 +323,6 @@
new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY});
mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING,
SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
- doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
mSlicePurchaseController.purchasePremiumCapability(
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, TAG,
@@ -285,7 +352,6 @@
new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY});
mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING,
SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
- doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mPhone).getSubId();
mSlicePurchaseController.purchasePremiumCapability(
@@ -314,7 +380,6 @@
new int[]{TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY});
mBundle.putString(CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING,
SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
- doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mPhone).getSubId();
doReturn(TelephonyManager.NETWORK_TYPE_NR).when(mServiceState).getDataNetworkType();
doReturn(null).when(mPremiumNetworkEntitlementApi).checkEntitlementStatus(anyInt());
@@ -342,8 +407,6 @@
// retry with provisioning response
mEntitlementResponse.mProvisionStatus =
PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_PROVISION_STATUS_IN_PROGRESS;
- doReturn(mEntitlementResponse).when(mPremiumNetworkEntitlementApi)
- .checkEntitlementStatus(anyInt());
mSlicePurchaseController.purchasePremiumCapability(
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, TAG,
@@ -357,12 +420,9 @@
PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_PROVISION_STATUS_NOT_PROVISIONED;
mEntitlementResponse.mEntitlementStatus =
PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_ENTITLEMENT_STATUS_INCOMPATIBLE;
- doReturn(mEntitlementResponse).when(mPremiumNetworkEntitlementApi)
- .checkEntitlementStatus(anyInt());
mBundle.putLong(CarrierConfigManager
.KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG,
PURCHASE_CONDITION_TIMEOUT);
- doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
mSlicePurchaseController.purchasePremiumCapability(
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, TAG,
@@ -409,12 +469,13 @@
public void testPurchasePremiumCapabilityResultSuccess() {
sendValidPurchaseRequest();
+ // broadcast SUCCESS response from slice purchase application
Intent intent = new Intent();
intent.setAction("com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_SUCCESS");
intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
- mBroadcastReceiver.onReceive(mMockedContext, intent);
+ mContext.getBroadcastReceiver().onReceive(mContext, intent);
mTestableLooper.processAllMessages();
assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS, mResult);
@@ -443,13 +504,7 @@
public void testPurchasePremiumCapabilityResultAlreadyPurchased() {
testPurchasePremiumCapabilityResultSuccess();
- // TODO: implement slicing config logic properly
- NetworkSlicingConfig slicingConfig = new NetworkSlicingConfig(Collections.emptyList(),
- Collections.singletonList(new NetworkSliceInfo.Builder()
- .setStatus(NetworkSliceInfo.SLICE_STATUS_ALLOWED).build()));
- mSlicePurchaseController.obtainMessage(2 /* EVENT_SLICING_CONFIG_CHANGED */,
- new AsyncResult(null, slicingConfig, null)).sendToTarget();
- mTestableLooper.processAllMessages();
+ sendNetworkSlicingConfig(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, true);
mSlicePurchaseController.purchasePremiumCapability(
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, TAG,
@@ -467,10 +522,7 @@
mResult);
// retry to verify purchase expired
- slicingConfig = new NetworkSlicingConfig(Collections.emptyList(), Collections.emptyList());
- mSlicePurchaseController.obtainMessage(2 /* EVENT_SLICING_CONFIG_CHANGED */,
- new AsyncResult(null, slicingConfig, null)).sendToTarget();
- mTestableLooper.processAllMessages();
+ sendNetworkSlicingConfig(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, false);
testPurchasePremiumCapabilityResultSuccess();
}
@@ -501,12 +553,13 @@
public void testPurchasePremiumCapabilityResultUserCanceled() {
sendValidPurchaseRequest();
+ // broadcast CANCELED response from slice purchase application
Intent intent = new Intent();
intent.setAction("com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_CANCELED");
intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
- mBroadcastReceiver.onReceive(mMockedContext, intent);
+ mContext.getBroadcastReceiver().onReceive(mContext, intent);
mTestableLooper.processAllMessages();
assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED, mResult);
@@ -528,6 +581,7 @@
public void testPurchasePremiumCapabilityResultCarrierError() {
sendValidPurchaseRequest();
+ // broadcast CARRIER_ERROR response from slice purchase application
Intent intent = new Intent();
intent.setAction(
"com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_CARRIER_ERROR");
@@ -536,7 +590,7 @@
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
intent.putExtra(SlicePurchaseController.EXTRA_FAILURE_CODE,
SlicePurchaseController.FAILURE_CODE_SERVER_UNREACHABLE);
- mBroadcastReceiver.onReceive(mMockedContext, intent);
+ mContext.getBroadcastReceiver().onReceive(mContext, intent);
mTestableLooper.processAllMessages();
assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_CARRIER_ERROR, mResult);
@@ -558,13 +612,14 @@
public void testPurchasePremiumCapabilityResultRequestFailed() {
sendValidPurchaseRequest();
+ // broadcast REQUEST_FAILED response from slice purchase application
Intent intent = new Intent();
intent.setAction(
"com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_REQUEST_FAILED");
intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
- mBroadcastReceiver.onReceive(mMockedContext, intent);
+ mContext.getBroadcastReceiver().onReceive(mContext, intent);
mTestableLooper.processAllMessages();
assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED, mResult);
@@ -582,7 +637,7 @@
intent.putExtra(SlicePurchaseController.EXTRA_PHONE_ID, PHONE_ID);
intent.putExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
- mBroadcastReceiver.onReceive(mMockedContext, intent);
+ mContext.getBroadcastReceiver().onReceive(mContext, intent);
mTestableLooper.processAllMessages();
assertEquals(
TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION,
@@ -592,8 +647,70 @@
testPurchasePremiumCapabilityResultSuccess();
}
+ @Test
+ public void testPurchasePremiumCapabilityResultNotificationThrottled() {
+ mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR, MONTH, DATE));
+ mSlicePurchaseController.updateNotificationCounts();
+
+ for (int count = 1; count <= DAILY_NOTIFICATION_MAX; count++) {
+ completeSuccessfulPurchase();
+ verify(mEditor).putInt(eq(DAILY_NOTIFICATION_COUNT_KEY), eq(count));
+ verify(mEditor).putInt(eq(MONTHLY_NOTIFICATION_COUNT_KEY), eq(count));
+ }
+
+ // retry to verify throttled
+ mSlicePurchaseController.purchasePremiumCapability(
+ TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, TAG,
+ mHandler.obtainMessage());
+ mTestableLooper.processAllMessages();
+ assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED, mResult);
+
+ // change the date to trigger daily reset
+ mSlicePurchaseController.setLocalDate(LocalDate.of(YEAR, MONTH, DATE + 1));
+ Mockito.clearInvocations(mEditor);
+
+ for (int count = 1; count <= (MONTHLY_NOTIFICATION_MAX - DAILY_NOTIFICATION_MAX); count++) {
+ completeSuccessfulPurchase();
+ verify(mEditor).putInt(eq(DAILY_NOTIFICATION_COUNT_KEY), eq(count));
+ verify(mEditor).putInt(eq(MONTHLY_NOTIFICATION_COUNT_KEY),
+ eq(count + DAILY_NOTIFICATION_MAX));
+ }
+
+ // retry to verify throttled
+ mSlicePurchaseController.purchasePremiumCapability(
+ TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, TAG,
+ mHandler.obtainMessage());
+ mTestableLooper.processAllMessages();
+ assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED, mResult);
+ }
+
+ private void completeSuccessfulPurchase() {
+ sendValidPurchaseRequest();
+
+ // broadcast NOTIFICATION_SHOWN response from slice purchase application
+ Intent intent = new Intent();
+ intent.setAction(
+ "com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_NOTIFICATION_SHOWN");
+ 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();
+
+ // broadcast SUCCESS response from slice purchase application
+ intent.setAction("com.android.phone.slice.action.SLICE_PURCHASE_APP_RESPONSE_SUCCESS");
+ mContext.getBroadcastReceiver().onReceive(mContext, intent);
+ mTestableLooper.processAllMessages();
+ assertEquals(TelephonyManager.PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS, mResult);
+
+ // complete network setup
+ sendNetworkSlicingConfig(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, true);
+ // purchase expired
+ sendNetworkSlicingConfig(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, false);
+ }
+
private void sendValidPurchaseRequest() {
- clearInvocations(mMockedContext);
+ clearInvocations(mContext);
// feature supported
doReturn((int) TelephonyManager.NETWORK_TYPE_BITMASK_NR).when(mPhone)
@@ -614,7 +731,6 @@
mBundle.putLong(CarrierConfigManager
.KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG,
PURCHASE_CONDITION_TIMEOUT);
- doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
// default data subscription
doReturn(SubscriptionManager.getDefaultDataSubscriptionId()).when(mPhone).getSubId();
// network available
@@ -622,8 +738,6 @@
// entitlement check passed
mEntitlementResponse.mEntitlementStatus =
PremiumNetworkEntitlementResponse.PREMIUM_NETWORK_ENTITLEMENT_STATUS_ENABLED;
- doReturn(mEntitlementResponse).when(mPremiumNetworkEntitlementApi)
- .checkEntitlementStatus(anyInt());
// send purchase request
mSlicePurchaseController.purchasePremiumCapability(
@@ -632,18 +746,27 @@
mTestableLooper.processAllMessages();
// verify that the purchase request was sent successfully
- ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mMockedContext).sendBroadcast(intentCaptor.capture());
- Intent intent = intentCaptor.getValue();
- assertEquals(SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP, intent.getAction());
+ verify(mContext).sendBroadcast(any(Intent.class));
+ assertEquals(SlicePurchaseController.ACTION_START_SLICE_PURCHASE_APP,
+ mContext.getBroadcast().getAction());
assertTrue(mSlicePurchaseController.hasMessages(4 /* EVENT_PURCHASE_TIMEOUT */,
TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY));
+ verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
+ }
- // capture the broadcast receiver to fake responses from the slice purchase application
- ArgumentCaptor<SlicePurchaseControllerBroadcastReceiver> broadcastReceiverCaptor =
- ArgumentCaptor.forClass(SlicePurchaseControllerBroadcastReceiver.class);
- verify(mMockedContext).registerReceiver(
- broadcastReceiverCaptor.capture(), any(IntentFilter.class));
- mBroadcastReceiver = broadcastReceiverCaptor.getValue();
+ 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(),
+ Collections.singletonList(sliceInfo));
+ mSlicePurchaseController.obtainMessage(2 /* EVENT_SLICING_CONFIG_CHANGED */,
+ new AsyncResult(null, slicingConfig, null)).sendToTarget();
+ mTestableLooper.processAllMessages();
}
}
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
index 553dbc9..a1d7405 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
@@ -16,6 +16,13 @@
package com.android.services.telephony;
+import static android.telephony.DisconnectCause.NOT_DISCONNECTED;
+import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+import static android.telephony.emergency.EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE;
+import static android.telephony.ims.ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL;
+
import static com.android.internal.telephony.RILConstants.GSM_PHONE;
import static junit.framework.Assert.assertEquals;
@@ -25,15 +32,17 @@
import static junit.framework.Assert.fail;
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.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
@@ -46,26 +55,34 @@
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
-import android.telephony.CarrierConfigManager;
import android.telephony.RadioAccessFamily;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.ImsReasonInfo;
import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.TelephonyTestBase;
+import com.android.ims.ImsManager;
import com.android.internal.telecom.IConnectionService;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneInternalInterface.DialArgs;
import com.android.internal.telephony.ServiceStateTracker;
import com.android.internal.telephony.data.PhoneSwitcher;
+import com.android.internal.telephony.domainselection.DomainSelectionResolver;
+import com.android.internal.telephony.domainselection.EmergencyCallDomainSelectionConnection;
+import com.android.internal.telephony.domainselection.NormalCallDomainSelectionConnection;
import com.android.internal.telephony.emergency.EmergencyNumberTracker;
+import com.android.internal.telephony.emergency.EmergencyStateTracker;
import com.android.internal.telephony.gsm.SuppServiceNotification;
+import com.android.internal.telephony.imsphone.ImsPhone;
import org.junit.After;
import org.junit.Before;
@@ -73,11 +90,14 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
/**
* Unit tests for TelephonyConnectionService.
@@ -116,6 +136,8 @@
private static final PhoneAccountHandle PHONE_ACCOUNT_HANDLE_2 = new PhoneAccountHandle(
TEST_COMPONENT_NAME, TEST_ACCOUNT_ID2);
private static final Uri TEST_ADDRESS = Uri.parse("tel:+16505551212");
+ private static final String TELECOM_CALL_ID1 = "TC1";
+ private static final String TEST_EMERGENCY_NUMBER = "911";
private android.telecom.Connection mConnection;
@Mock TelephonyConnectionService.TelephonyManagerProxy mTelephonyManagerProxy;
@@ -135,6 +157,11 @@
@Mock Call mCall2;
@Mock com.android.internal.telephony.Connection mInternalConnection;
@Mock com.android.internal.telephony.Connection mInternalConnection2;
+ @Mock DomainSelectionResolver mDomainSelectionResolver;
+ @Mock EmergencyCallDomainSelectionConnection mEmergencyCallDomainSelectionConnection;
+ @Mock NormalCallDomainSelectionConnection mNormalCallDomainSelectionConnection;
+ @Mock ImsPhone mImsPhone;
+ private EmergencyStateTracker mEmergencyStateTracker;
private Phone mPhone0;
private Phone mPhone1;
@@ -180,6 +207,20 @@
mTestConnectionService.setDisconnectCauseFactory(mDisconnectCauseFactory);
mTestConnectionService.onCreate();
mTestConnectionService.setTelephonyManagerProxy(mTelephonyManagerProxy);
+ DomainSelectionResolver.setDomainSelectionResolver(mDomainSelectionResolver);
+ replaceInstance(TelephonyConnectionService.class, "mDomainSelectionResolver",
+ mTestConnectionService, mDomainSelectionResolver);
+ mEmergencyStateTracker = Mockito.mock(EmergencyStateTracker.class);
+ replaceInstance(TelephonyConnectionService.class, "mEmergencyStateTracker",
+ mTestConnectionService, mEmergencyStateTracker);
+ doReturn(CompletableFuture.completedFuture(NOT_DISCONNECTED))
+ .when(mEmergencyStateTracker)
+ .startEmergencyCall(any(), anyString(), eq(false));
+ replaceInstance(TelephonyConnectionService.class,
+ "mDomainSelectionMainExecutor", mTestConnectionService, getExecutor());
+ doReturn(false).when(mDomainSelectionResolver).isDomainSelectionSupported();
+ doReturn(null).when(mDomainSelectionResolver).getDomainSelectionConnection(
+ any(), anyInt(), anyBoolean());
mBinderStub = (IConnectionService.Stub) mTestConnectionService.onBind(null);
}
@@ -1107,221 +1148,6 @@
}
/**
- * Test that the TelephonyConnectionService successfully performs a DDS switch before a call
- * when we are not roaming and the carrier only supports SUPL over the data plane.
- */
- @Test
- @SmallTest
- public void testCreateOutgoingEmergencyConnection_delayDial_carrierconfig_dds() {
- // Setup test to not support SUPL on the non-DDS subscription
- doReturn(true).when(mDeviceState).isSuplDdsSwitchRequiredForEmergencyCall(any());
- getTestContext().getCarrierConfig(0 /*subId*/).putStringArray(
- CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
- null);
- getTestContext().getCarrierConfig(0 /*subId*/).putInt(
- CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
- CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY);
- getTestContext().getCarrierConfig(0 /*subId*/).putString(
- CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "150");
-
- Phone testPhone = setupConnectionServiceForDelayDial(
- false /* isRoaming */, false /* setOperatorName */, null /* operator long name*/,
- null /* operator short name */, null /* operator numeric name */);
- verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(0) /*phoneId*/ ,
- eq(150) /*extensionTime*/, any());
- }
-
- /**
- * Test that the TelephonyConnectionService successfully turns radio on before placing the
- * emergency call.
- */
- @Test
- @SmallTest
- public void testCreateOutgoingEmerge_exitingApm_disconnected() {
- when(mDeviceState.isAirplaneModeOn(any())).thenReturn(true);
- Phone testPhone = setupConnectionServiceInApm();
-
- ArgumentCaptor<RadioOnStateListener.Callback> callback =
- ArgumentCaptor.forClass(RadioOnStateListener.Callback.class);
- verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true),
- eq(testPhone), eq(false));
-
- assertFalse(callback.getValue().isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE));
- when(mSST.isRadioOn()).thenReturn(true);
- assertTrue(callback.getValue().isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE));
-
- mConnection.setDisconnected(null);
- callback.getValue().onComplete(null, true);
- for (Phone phone : mPhoneFactoryProxy.getPhones()) {
- verify(phone).setRadioPower(true, false, false, true);
- }
- }
-
- /**
- * Test that the TelephonyConnectionService successfully turns radio on before placing the
- * emergency call.
- */
- @Test
- @SmallTest
- public void testCreateOutgoingEmergencyConnection_exitingApm_placeCall() {
- when(mDeviceState.isAirplaneModeOn(any())).thenReturn(true);
- Phone testPhone = setupConnectionServiceInApm();
-
- ArgumentCaptor<RadioOnStateListener.Callback> callback =
- ArgumentCaptor.forClass(RadioOnStateListener.Callback.class);
- verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true),
- eq(testPhone), eq(false));
-
- assertFalse(callback.getValue().isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE));
- when(mSST.isRadioOn()).thenReturn(true);
- assertTrue(callback.getValue().isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE));
-
- callback.getValue().onComplete(null, true);
-
- try {
- doAnswer(invocation -> null).when(mContext).startActivity(any());
- verify(testPhone).dial(anyString(), any(), any());
- } catch (CallStateException e) {
- // This shouldn't happen
- fail();
- }
- }
-
- /**
- * Test that the TelephonyConnectionService does not perform a DDS switch when the carrier
- * supports control-plane fallback.
- */
- @Test
- @SmallTest
- public void testCreateOutgoingEmergencyConnection_delayDial_nocarrierconfig() {
- // Setup test to not support SUPL on the non-DDS subscription
- doReturn(true).when(mDeviceState).isSuplDdsSwitchRequiredForEmergencyCall(any());
- getTestContext().getCarrierConfig(0 /*subId*/).putStringArray(
- CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
- null);
- getTestContext().getCarrierConfig(0 /*subId*/).putInt(
- CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
- CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK);
- getTestContext().getCarrierConfig(0 /*subId*/).putString(
- CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0");
-
- Phone testPhone = setupConnectionServiceForDelayDial(
- false /* isRoaming */, false /* setOperatorName */, null /* operator long name*/,
- null /* operator short name */, null /* operator numeric name */);
- verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any());
- }
-
- /**
- * Test that the TelephonyConnectionService does not perform a DDS switch when the carrier
- * supports control-plane fallback.
- */
- @Test
- @SmallTest
- public void testCreateOutgoingEmergencyConnection_delayDial_supportsuplondds() {
- // If the non-DDS supports SUPL, dont switch data
- doReturn(false).when(mDeviceState).isSuplDdsSwitchRequiredForEmergencyCall(any());
- getTestContext().getCarrierConfig(0 /*subId*/).putStringArray(
- CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
- null);
- getTestContext().getCarrierConfig(0 /*subId*/).putInt(
- CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
- CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY);
- getTestContext().getCarrierConfig(0 /*subId*/).putString(
- CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0");
-
- Phone testPhone = setupConnectionServiceForDelayDial(
- false /* isRoaming */, false /* setOperatorName */, null /* operator long name*/,
- null /* operator short name */, null /* operator numeric name */);
- verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any());
- }
-
- /**
- * Test that the TelephonyConnectionService does not perform a DDS switch when the carrier does
- * not support control-plane fallback CarrierConfig while roaming.
- */
- @Test
- @SmallTest
- public void testCreateOutgoingEmergencyConnection_delayDial_roaming_nocarrierconfig() {
- // Setup test to not support SUPL on the non-DDS subscription
- doReturn(true).when(mDeviceState).isSuplDdsSwitchRequiredForEmergencyCall(any());
- getTestContext().getCarrierConfig(0 /*subId*/).putStringArray(
- CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
- null);
- getTestContext().getCarrierConfig(0 /*subId*/).putInt(
- CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
- CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY);
- getTestContext().getCarrierConfig(0 /*subId*/).putString(
- CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0");
-
- Phone testPhone = setupConnectionServiceForDelayDial(
- true /* isRoaming */, false /* setOperatorName */, null /* operator long name*/,
- null /* operator short name */, null /* operator numeric name */);
- verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any());
- }
-
- /**
- * Test that the TelephonyConnectionService does perform a DDS switch even though the carrier
- * supports control-plane fallback CarrierConfig and the roaming partner is configured to look
- * like a home network.
- */
- @Test
- @SmallTest
- public void testCreateOutgoingEmergencyConnection_delayDial_roamingcarrierconfig() {
- doReturn(true).when(mDeviceState).isSuplDdsSwitchRequiredForEmergencyCall(any());
- // Setup voice roaming scenario
- String testRoamingOperator = "001001";
- // Setup test to not support SUPL on the non-DDS subscription
- String[] roamingPlmns = new String[1];
- roamingPlmns[0] = testRoamingOperator;
- getTestContext().getCarrierConfig(0 /*subId*/).putStringArray(
- CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
- roamingPlmns);
- getTestContext().getCarrierConfig(0 /*subId*/).putInt(
- CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
- CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK);
- getTestContext().getCarrierConfig(0 /*subId*/).putString(
- CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0");
-
- Phone testPhone = setupConnectionServiceForDelayDial(
- false /* isRoaming */, true /* setOperatorName */,
- "TestTel" /* operator long name*/, "TestTel" /* operator short name */,
- testRoamingOperator /* operator numeric name */);
- verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(0) /*phoneId*/ ,
- eq(0) /*extensionTime*/, any());
- }
-
- /**
- * Test that the TelephonyConnectionService does perform a DDS switch even though the carrier
- * supports control-plane fallback CarrierConfig if we are roaming and the roaming partner is
- * configured to use data plane only SUPL.
- */
- @Test
- @SmallTest
- public void testCreateOutgoingEmergencyConnection_delayDial__roaming_roamingcarrierconfig() {
- // Setup test to not support SUPL on the non-DDS subscription
- doReturn(true).when(mDeviceState).isSuplDdsSwitchRequiredForEmergencyCall(any());
- // Setup voice roaming scenario
- String testRoamingOperator = "001001";
- String[] roamingPlmns = new String[1];
- roamingPlmns[0] = testRoamingOperator;
- getTestContext().getCarrierConfig(0 /*subId*/).putStringArray(
- CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
- roamingPlmns);
- getTestContext().getCarrierConfig(0 /*subId*/).putInt(
- CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
- CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK);
- getTestContext().getCarrierConfig(0 /*subId*/).putString(
- CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0");
-
- Phone testPhone = setupConnectionServiceForDelayDial(
- false /* isRoaming */, true /* setOperatorName */,
- "TestTel" /* operator long name*/, "TestTel" /* operator short name */,
- testRoamingOperator /* operator numeric name */);
- verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(0) /*phoneId*/ ,
- eq(0) /*extensionTime*/, any());
- }
-
- /**
* Verifies for an incoming call on the same SIM that we don't set
* {@link android.telecom.Connection#EXTRA_ANSWERING_DROPS_FG_CALL} on the incoming call extras.
* @throws Exception
@@ -1527,6 +1353,367 @@
assertFalse(tc1.wasDisconnected);
}
+ /**
+ * Verifies that TelephonyManager is used to determine whether a connection is Emergency when
+ * creating an outgoing connection.
+ */
+ @Test
+ @SmallTest
+ public void testIsEmergencyDeterminedByTelephonyManager() {
+ ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
+ .setAccountHandle(PHONE_ACCOUNT_HANDLE_1)
+ .setAddress(TEST_ADDRESS)
+ .build();
+ mConnection = mTestConnectionService.onCreateOutgoingConnection(
+ PHONE_ACCOUNT_HANDLE_1, connectionRequest);
+
+ verify(mTelephonyManagerProxy)
+ .isCurrentEmergencyNumber(TEST_ADDRESS.getSchemeSpecificPart());
+ }
+
+ @Test
+ public void testDomainSelectionCs() throws Exception {
+ setupForCallTest();
+
+ int selectedDomain = DOMAIN_CS;
+
+ 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());
+
+ 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 testDomainSelectionPs() 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());
+
+ 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 testDomainSelectionCsForTty() throws Exception {
+ setupForCallTest();
+
+ ImsManager imsManager = Mockito.mock(ImsManager.class);
+ doReturn(false).when(imsManager).isNonTtyOrTtyOnVolteEnabled();
+ replaceInstance(TelephonyConnectionService.class,
+ "mImsManager", mTestConnectionService, imsManager);
+
+ setupForDialForDomainSelection(mPhone0, DOMAIN_PS, true);
+
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
+ TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+
+ verify(mEmergencyStateTracker, times(1))
+ .startEmergencyCall(eq(mPhone0), eq(TELECOM_CALL_ID1), eq(false));
+ verify(mDomainSelectionResolver, times(0))
+ .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(true));
+ verify(mEmergencyCallDomainSelectionConnection, times(0))
+ .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(DOMAIN_CS, dialArgs.intentExtras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN, -1));
+ }
+
+ @Test
+ public void testDomainSelectionRedialCs() throws Exception {
+ setupForCallTest();
+
+ int preciseDisconnectCause = com.android.internal.telephony.CallFailCause.ERROR_UNSPECIFIED;
+ int disconnectCause = android.telephony.DisconnectCause.ERROR_UNSPECIFIED;
+ int selectedDomain = DOMAIN_CS;
+
+ TestTelephonyConnection c = setupForReDialForDomainSelection(
+ mPhone0, selectedDomain, preciseDisconnectCause, disconnectCause, true);
+
+ assertTrue(mTestConnectionService.maybeReselectDomain(c, preciseDisconnectCause, null));
+ verify(mEmergencyCallDomainSelectionConnection).reselectDomain(any());
+
+ ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
+
+ Connection nc = Mockito.mock(Connection.class);
+ doReturn(nc).when(mPhone0).dial(anyString(), any(), any());
+
+ 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 testDomainSelectionRedialPs() throws Exception {
+ setupForCallTest();
+
+ int preciseDisconnectCause = com.android.internal.telephony.CallFailCause.ERROR_UNSPECIFIED;
+ int disconnectCause = android.telephony.DisconnectCause.ERROR_UNSPECIFIED;
+ int selectedDomain = DOMAIN_PS;
+
+ TestTelephonyConnection c = setupForReDialForDomainSelection(
+ mPhone0, selectedDomain, preciseDisconnectCause, disconnectCause, true);
+
+ assertTrue(mTestConnectionService.maybeReselectDomain(c, preciseDisconnectCause, null));
+ verify(mEmergencyCallDomainSelectionConnection).reselectDomain(any());
+
+ ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
+
+ Connection nc = Mockito.mock(Connection.class);
+ doReturn(nc).when(mPhone0).dial(anyString(), any(), any());
+
+ 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 testDomainSelectionNormalToEmergencyCs() throws Exception {
+ setupForCallTest();
+
+ int preciseDisconnectCause = com.android.internal.telephony.CallFailCause.ERROR_UNSPECIFIED;
+ int disconnectCause = android.telephony.DisconnectCause.ERROR_UNSPECIFIED;
+ int eccCategory = EMERGENCY_SERVICE_CATEGORY_POLICE;
+ int selectedDomain = DOMAIN_CS;
+
+ setupForDialForDomainSelection(mPhone0, selectedDomain, true);
+
+ TestTelephonyConnection c = setupForReDialForDomainSelection(
+ mPhone0, selectedDomain, preciseDisconnectCause, disconnectCause, false);
+ c.setEmergencyServiceCategory(eccCategory);
+ c.setAddress(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED);
+
+ ImsReasonInfo reasonInfo = new ImsReasonInfo(CODE_SIP_ALTERNATE_EMERGENCY_CALL, 0, null);
+ assertTrue(mTestConnectionService.maybeReselectDomain(c,
+ preciseDisconnectCause, reasonInfo));
+
+ 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());
+
+ 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));
+ assertTrue(dialArgs.isEmergency);
+ assertEquals(eccCategory, dialArgs.eccCategory);
+ }
+
+ @Test
+ public void testDomainSelectionNormalToEmergencyPs() throws Exception {
+ setupForCallTest();
+
+ int preciseDisconnectCause = com.android.internal.telephony.CallFailCause.ERROR_UNSPECIFIED;
+ int disconnectCause = android.telephony.DisconnectCause.ERROR_UNSPECIFIED;
+ int eccCategory = EMERGENCY_SERVICE_CATEGORY_POLICE;
+ int selectedDomain = DOMAIN_PS;
+
+ setupForDialForDomainSelection(mPhone0, selectedDomain, true);
+
+ TestTelephonyConnection c = setupForReDialForDomainSelection(
+ mPhone0, selectedDomain, preciseDisconnectCause, disconnectCause, false);
+ c.setEmergencyServiceCategory(eccCategory);
+ c.setAddress(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED);
+
+ ImsReasonInfo reasonInfo = new ImsReasonInfo(CODE_SIP_ALTERNATE_EMERGENCY_CALL, 0, null);
+ assertTrue(mTestConnectionService.maybeReselectDomain(c,
+ preciseDisconnectCause, reasonInfo));
+
+ 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());
+
+ 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));
+ assertTrue(dialArgs.isEmergency);
+ assertEquals(eccCategory, dialArgs.eccCategory);
+ }
+
+ @Test
+ public void testDomainSelectionWithMmiCode() {
+ //UT domain selection should not be handled by new domain selector.
+ doNothing().when(mContext).startActivity(any());
+ setupForCallTest();
+ setupForDialForDomainSelection(mPhone0, 0, false);
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1, "*%2321%23", TELECOM_CALL_ID1));
+
+ verifyZeroInteractions(mNormalCallDomainSelectionConnection);
+ }
+
+ @Test
+ public void testNormalCallPsDomainSelection() throws Exception {
+ setupForCallTest();
+ int selectedDomain = DOMAIN_PS;
+ setupForDialForDomainSelection(mPhone0, selectedDomain, false);
+
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1, "1234", TELECOM_CALL_ID1));
+
+ verify(mDomainSelectionResolver)
+ .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
+ verify(mNormalCallDomainSelectionConnection).createNormalConnection(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 testNormalCallCsDomainSelection() throws Exception {
+ setupForCallTest();
+ int selectedDomain = DOMAIN_CS;
+ setupForDialForDomainSelection(mPhone0, selectedDomain, false);
+
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1, "1234", TELECOM_CALL_ID1));
+
+ verify(mDomainSelectionResolver)
+ .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
+ verify(mNormalCallDomainSelectionConnection).createNormalConnection(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));
+ }
+
+ private void setupForDialForDomainSelection(Phone mockPhone, int domain, boolean isEmergency) {
+ if (isEmergency) {
+ doReturn(mEmergencyCallDomainSelectionConnection).when(mDomainSelectionResolver)
+ .getDomainSelectionConnection(any(), anyInt(), eq(true));
+ doReturn(CompletableFuture.completedFuture(domain))
+ .when(mEmergencyCallDomainSelectionConnection)
+ .createEmergencyConnection(any(), any());
+ doReturn(true).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(anyString());
+ } else {
+ doReturn(mNormalCallDomainSelectionConnection).when(mDomainSelectionResolver)
+ .getDomainSelectionConnection(any(), eq(SELECTOR_TYPE_CALLING), eq(false));
+ doReturn(CompletableFuture.completedFuture(domain))
+ .when(mNormalCallDomainSelectionConnection)
+ .createNormalConnection(any(), any());
+ doReturn(false).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(anyString());
+ }
+
+ doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported();
+ doReturn(mImsPhone).when(mockPhone).getImsPhone();
+ }
+
+ private TestTelephonyConnection setupForReDialForDomainSelection(
+ Phone mockPhone, int domain, int preciseDisconnectCause,
+ int disconnectCause, boolean fromEmergency) throws Exception {
+ try {
+ if (fromEmergency) {
+ doReturn(CompletableFuture.completedFuture(domain))
+ .when(mEmergencyCallDomainSelectionConnection)
+ .reselectDomain(any());
+ replaceInstance(TelephonyConnectionService.class,
+ "mEmergencyCallDomainSelectionConnection",
+ mTestConnectionService, mEmergencyCallDomainSelectionConnection);
+ replaceInstance(TelephonyConnectionService.class, "mEmergencyCallId",
+ mTestConnectionService, TELECOM_CALL_ID1);
+ } else {
+ doReturn(CompletableFuture.completedFuture(domain))
+ .when(mNormalCallDomainSelectionConnection).reselectDomain(any());
+ replaceInstance(TelephonyConnectionService.class, "mDomainSelectionConnection",
+ mTestConnectionService, mNormalCallDomainSelectionConnection);
+ }
+ } catch (Exception e) {
+ // This shouldn't happen
+ fail();
+ }
+
+ doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported();
+
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setTelecomCallId(TELECOM_CALL_ID1);
+ c.setMockPhone(mockPhone);
+ c.setAddress(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED);
+
+ Connection oc = c.getOriginalConnection();
+ doReturn(disconnectCause).when(oc).getDisconnectCause();
+ doReturn(preciseDisconnectCause).when(oc).getPreciseDisconnectCause();
+
+ return c;
+ }
+
private SimpleTelephonyConnection createTestConnection(PhoneAccountHandle handle,
int properties, boolean isEmergency) {
SimpleTelephonyConnection connection = new SimpleTelephonyConnection();
@@ -1742,4 +1929,18 @@
fail();
}
}
+
+ private ConnectionRequest createConnectionRequest(
+ PhoneAccountHandle accountHandle, String address, String callId) {
+ return new ConnectionRequest.Builder()
+ .setAccountHandle(accountHandle)
+ .setAddress(Uri.parse("tel:" + address))
+ .setExtras(new Bundle())
+ .setTelecomCallId(callId)
+ .build();
+ }
+
+ private Executor getExecutor() {
+ return Runnable::run;
+ }
}
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
index c996e5f..3c309ba 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
@@ -1,5 +1,7 @@
package com.android.services.telephony;
+import static android.telecom.Connection.STATE_DISCONNECTED;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
@@ -7,6 +9,9 @@
import static junit.framework.Assert.fail;
import static junit.framework.TestCase.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+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.mock;
@@ -26,7 +31,6 @@
import com.android.internal.telephony.d2d.DtmfTransport;
import com.android.internal.telephony.d2d.RtpTransport;
import com.android.internal.telephony.imsphone.ImsPhoneConnection;
-import com.android.phone.PhoneGlobals;
import com.android.phone.R;
import org.junit.Before;
@@ -39,6 +43,8 @@
public class TelephonyConnectionTest {
@Mock
private ImsPhoneConnection mImsPhoneConnection;
+ @Mock
+ private TelephonyConnectionService mTelephonyConnectionService;
@Before
public void setUp() throws Exception {
@@ -257,4 +263,44 @@
assertTrue(c.isRttMergeSupported(c.getCarrierConfig()));
}
+ @Test
+ public void testDomainSelectionDisconnected() {
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setOriginalConnection(mImsPhoneConnection);
+ doReturn(Call.State.DISCONNECTED).when(mImsPhoneConnection)
+ .getState();
+ c.setTelephonyConnectionService(mTelephonyConnectionService);
+ c.updateState();
+
+ verify(mTelephonyConnectionService)
+ .maybeReselectDomain(any(), anyInt(), any());
+ }
+
+ @Test
+ public void testDomainSelectionDisconnected_NoRedial() {
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setOriginalConnection(mImsPhoneConnection);
+ doReturn(Call.State.DISCONNECTED).when(mImsPhoneConnection)
+ .getState();
+ c.setTelephonyConnectionService(mTelephonyConnectionService);
+ doReturn(false).when(mTelephonyConnectionService)
+ .maybeReselectDomain(any(), anyInt(), any());
+ c.updateState();
+
+ assertEquals(STATE_DISCONNECTED, c.getState());
+ }
+
+ @Test
+ public void testDomainSelectionDisconnected_Redial() {
+ TestTelephonyConnection c = new TestTelephonyConnection();
+ c.setOriginalConnection(mImsPhoneConnection);
+ doReturn(Call.State.DISCONNECTED).when(mImsPhoneConnection)
+ .getState();
+ c.setTelephonyConnectionService(mTelephonyConnectionService);
+ doReturn(true).when(mTelephonyConnectionService)
+ .maybeReselectDomain(any(), anyInt(), any());
+ c.updateState();
+
+ assertNotEquals(STATE_DISCONNECTED, c.getState());
+ }
}
diff --git a/tests/src/com/android/services/telephony/TelephonyManagerTest.java b/tests/src/com/android/services/telephony/TelephonyManagerTest.java
index 89b558c..eec38ce 100644
--- a/tests/src/com/android/services/telephony/TelephonyManagerTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyManagerTest.java
@@ -15,6 +15,8 @@
*/
package com.android.services.telephony;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.junit.Assert.assertEquals;
@@ -260,4 +262,13 @@
PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION);
return mContext.getPackageManager().hasSystemFeature(feature);
}
+
+ @Test
+ public void getPrimaryImei() throws RemoteException {
+ assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, true));
+ when(mMockITelephony.getPrimaryImei(anyString(), anyString())).thenReturn(
+ "12345");
+ assertEquals("12345", mTelephonyManager.getPrimaryImei());
+ verify(mMockITelephony, times(1)).getPrimaryImei(anyString(), anyString());
+ }
}
diff --git a/tests/src/com/android/services/telephony/domainselection/DomainSelectorBaseTest.java b/tests/src/com/android/services/telephony/domainselection/DomainSelectorBaseTest.java
new file mode 100644
index 0000000..74c3311
--- /dev/null
+++ b/tests/src/com/android/services/telephony/domainselection/DomainSelectorBaseTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.TransportSelectorCallback;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.TestContext;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for DomainSelectorBase.
+ */
+@RunWith(AndroidJUnit4.class)
+public class DomainSelectorBaseTest {
+ public class TestDomainSelectorBase extends DomainSelectorBase {
+ public TestDomainSelectorBase(Context context, int slotId, int subId,
+ @NonNull Looper looper, @NonNull ImsStateTracker imsStateTracker,
+ @NonNull DomainSelectorBase.DestroyListener listener, String logTag) {
+ super(context, slotId, subId, looper, imsStateTracker, listener, logTag);
+ }
+
+ @Override
+ public void cancelSelection() {
+ // No operations.
+ }
+
+ @Override
+ public void reselectDomain(@NonNull SelectionAttributes attr) {
+ // No operations.
+ }
+
+ @Override
+ public void finishSelection() {
+ // No operations.
+ }
+
+ @Override
+ public void selectDomain(SelectionAttributes attr, TransportSelectorCallback callback) {
+ // No operations.
+ }
+ }
+
+ private static final String TAG = DomainSelectorBaseTest.class.getSimpleName();
+ private static final int SLOT_0 = 0;
+ private static final int SUB_1 = 1;
+
+ @Mock private DomainSelectorBase.DestroyListener mDomainSelectorDestroyListener;
+ @Mock private ImsStateTracker mImsStateTracker;
+
+ private Context mContext;
+ private Looper mLooper;
+ private TestDomainSelectorBase mDomainSelector;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext = new TestContext();
+
+ HandlerThread handlerThread = new HandlerThread(TAG);
+ handlerThread.start();
+ mLooper = handlerThread.getLooper();
+ mDomainSelector = new TestDomainSelectorBase(mContext, SLOT_0, SUB_1, mLooper,
+ mImsStateTracker, mDomainSelectorDestroyListener, TAG);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mDomainSelector != null) {
+ mDomainSelector.destroy();
+ mDomainSelector = null;
+ }
+
+ if (mLooper != null) {
+ mLooper.quit();
+ mLooper = null;
+ }
+
+ mDomainSelectorDestroyListener = null;
+ mImsStateTracker = null;
+ mContext = null;
+ }
+
+ @Test
+ @SmallTest
+ public void testInit() {
+ assertEquals(SLOT_0, mDomainSelector.getSlotId());
+ assertEquals(SUB_1, mDomainSelector.getSubId());
+ }
+
+ @Test
+ @SmallTest
+ public void testDestroy() {
+ mDomainSelector.destroy();
+ verify(mDomainSelectorDestroyListener).onDomainSelectorDestroyed(eq(mDomainSelector));
+ mDomainSelector = null;
+ }
+}
diff --git a/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java b/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
new file mode 100644
index 0000000..e1de0ab
--- /dev/null
+++ b/tests/src/com/android/services/telephony/domainselection/EmergencyCallDomainSelectorTest.java
@@ -0,0 +1,1081 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.GERAN;
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.UNKNOWN;
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.UTRAN;
+import static android.telephony.BarringInfo.BARRING_SERVICE_TYPE_EMERGENCY;
+import static android.telephony.BarringInfo.BarringServiceInfo.BARRING_TYPE_UNCONDITIONAL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_SCAN_TIMER_SEC_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT;
+import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL;
+import static android.telephony.CarrierConfigManager.ImsEmergency.SCAN_TYPE_NO_PREFERENCE;
+import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN;
+
+import static com.android.services.telephony.domainselection.EmergencyCallDomainSelector.MSG_NETWORK_SCAN_TIMEOUT;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.os.PowerManager;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.BarringInfo;
+import android.telephony.CarrierConfigManager;
+import android.telephony.CellIdentityLte;
+import android.telephony.DomainSelectionService;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.EmergencyRegResult;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.TelephonyManager;
+import android.telephony.TransportSelectorCallback;
+import android.telephony.WwanSelectorCallback;
+import android.telephony.ims.ImsManager;
+import android.telephony.ims.ImsMmTelManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableLooper;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.TestContext;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Unit tests for EmergencyCallDomainSelector
+ */
+public class EmergencyCallDomainSelectorTest {
+ private static final String TAG = "EmergencyCallDomainSelectorTest";
+
+ private static final int SLOT_0 = 0;
+ private static final int SLOT_0_SUB_ID = 1;
+
+ @Mock private CarrierConfigManager mCarrierConfigManager;
+ @Mock private TelephonyManager mTelephonyManager;
+ @Mock private WwanSelectorCallback mWwanSelectorCallback;
+ @Mock private TransportSelectorCallback mTransportSelectorCallback;
+ @Mock private ImsMmTelManager mMmTelManager;
+ @Mock private ImsStateTracker mImsStateTracker;
+ @Mock private DomainSelectorBase.DestroyListener mDestroyListener;
+
+ private Context mContext;
+
+ private HandlerThread mHandlerThread;
+ private TestableLooper mLooper;
+ private EmergencyCallDomainSelector mDomainSelector;
+ private SelectionAttributes mSelectionAttributes;
+ private @AccessNetworkConstants.RadioAccessNetworkType List<Integer> mAccessNetwork;
+ private PowerManager mPowerManager;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext = new TestContext() {
+ @Override
+ public String getSystemServiceName(Class<?> serviceClass) {
+ if (serviceClass == ImsManager.class) {
+ return Context.TELEPHONY_IMS_SERVICE;
+ } else if (serviceClass == TelephonyManager.class) {
+ return Context.TELEPHONY_SERVICE;
+ } else if (serviceClass == CarrierConfigManager.class) {
+ return Context.CARRIER_CONFIG_SERVICE;
+ } else if (serviceClass == PowerManager.class) {
+ return Context.POWER_SERVICE;
+ }
+ return super.getSystemServiceName(serviceClass);
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ switch (name) {
+ case (Context.POWER_SERVICE) : {
+ return mPowerManager;
+ }
+ }
+ return super.getSystemService(name);
+ }
+
+ @Override
+ public String getOpPackageName() {
+ return "";
+ }
+ };
+
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ mHandlerThread = new HandlerThread("EmergencyCallDomainSelectorTest");
+ mHandlerThread.start();
+
+ try {
+ mLooper = new TestableLooper(mHandlerThread.getLooper());
+ } catch (Exception e) {
+ logd("Unable to create looper from handler.");
+ }
+
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+ when(mTelephonyManager.createForSubscriptionId(anyInt()))
+ .thenReturn(mTelephonyManager);
+ when(mTelephonyManager.getNetworkCountryIso()).thenReturn("");
+
+ mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt()))
+ .thenReturn(getDefaultPersistableBundle());
+
+ IPowerManager powerManager = mock(IPowerManager.class);
+ mPowerManager = new PowerManager(mContext, powerManager, mock(IThermalService.class),
+ new Handler(mHandlerThread.getLooper()));
+
+ ImsManager imsManager = mContext.getSystemService(ImsManager.class);
+ when(imsManager.getImsMmTelManager(anyInt())).thenReturn(mMmTelManager);
+ when(mMmTelManager.isAdvancedCallingSettingEnabled()).thenReturn(true);
+
+ when(mTransportSelectorCallback.onWwanSelected()).thenReturn(mWwanSelectorCallback);
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Consumer<WwanSelectorCallback> consumer =
+ (Consumer<WwanSelectorCallback>) invocation.getArguments()[0];
+ consumer.accept(mWwanSelectorCallback);
+ return null;
+ }
+ }).when(mTransportSelectorCallback).onWwanSelected(any());
+
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ mAccessNetwork = (List<Integer>) invocation.getArguments()[0];
+ return null;
+ }
+ }).when(mWwanSelectorCallback).onRequestEmergencyNetworkScan(
+ any(), anyInt(), any(), any());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mDomainSelector != null) {
+ mDomainSelector.destroy();
+ mDomainSelector = null;
+ }
+
+ if (mLooper != null) {
+ mLooper.destroy();
+ mLooper = null;
+ }
+ }
+
+ @SmallTest
+ @Test
+ public void testInit() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+
+ verify(mWwanSelectorCallback, times(0)).onRequestEmergencyNetworkScan(
+ any(), anyInt(), any(), any());
+ verify(mWwanSelectorCallback, times(0)).onDomainSelected(anyInt());
+ }
+
+ @Test
+ public void testDefaultCombinedImsRegisteredBarredSelectCs() 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();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testDefaultCombinedImsRegisteredSelectPs() 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();
+ }
+
+ @Test
+ public void testDefaultCombinedImsNotRegisteredSelectCs() 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();
+
+ bindImsServiceUnregistered();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testDefaultCombinedImsNotRegisteredBarredSelectCs() 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();
+
+ bindImsServiceUnregistered();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testDefaultCombinedImsRegisteredEmsOffBarredSelectCs() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ true, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testDefaultCombinedImsRegisteredEmsOffSelectCs() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ true, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testDefaultCombinedImsNotRegisteredEmsOffSelectCs() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ true, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testDefaultCombinedImsNotRegisteredEmsOffBarredSelectCs() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ true, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testDefaultCombinedImsRegisteredVopsOffBarredSelectCs() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ false, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testDefaultCombinedImsRegisteredVopsOffSelectCs() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ false, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testDefaultCombinedImsNotRegisteredVopsOffSelectCs() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ false, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testDefaultCombinedImsNotRegisteredVopsOffBarredSelectCs() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ false, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testDefaultCombinedImsRegisteredVopsOffEmsOffBarredSelectCs() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testDefaultCombinedImsRegisteredVopsOffEmsOffSelectCs() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testDefaultCombinedImsNotRegisteredVopsOffEmsOffSelectCs() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testDefaultCombinedImsNotRegisteredVopsOffEmsOffBarredSelectCs() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS | NetworkRegistrationInfo.DOMAIN_PS,
+ false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testDefaultCsSelectCs() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(UTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_CS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testDefaultEpsImsRegisteredBarredScanPsPreferred() throws Exception {
+ 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();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
+ public void testDefaultEpsImsRegisteredSelectPs() 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();
+ }
+
+ @Test
+ public void testDefaultEpsImsNotRegisteredSelectPs() 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();
+
+ bindImsServiceUnregistered();
+
+ verifyPsDialed();
+ }
+
+ @Test
+ public void testDefaultEpsImsNotRegisteredBarredSelectScanPsPreferred() throws Exception {
+ 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();
+
+ bindImsServiceUnregistered();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
+ public void testDefaultEpsImsRegisteredEmsOffBarredScanPsPreferred() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_PS,
+ true, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
+ public void testDefaultEpsImsRegisteredEmsOffScanPsPreferred() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_PS,
+ true, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
+ public void testDefaultEpsImsNotRegisteredEmsOffScanPsPreferred() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_PS,
+ true, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
+ public void testDefaultEpsImsNotRegisteredEmsOffBarredScanPsPreferred() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_PS,
+ true, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
+ public void testDefaultEpsImsRegisteredVopsOffBarredScanPsPreferred() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_PS,
+ false, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
+ public void testDefaultEpsImsRegisteredVopsOffScanPsPreferred() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_PS,
+ false, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
+ public void testDefaultEpsImsNotRegisteredVopsOffScanPsPreferred() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_PS,
+ false, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
+ public void testDefaultEpsImsNotRegisteredVopsOffBarredScanPsPreferred() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_PS,
+ false, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
+ public void testDefaultEpsImsRegisteredVopsOffEmsOffBarredScanPsPreferred() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_PS,
+ false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
+ public void testDefaultEpsImsRegisteredVopsOffEmsOffScanPsPreferred() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_PS,
+ false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsService();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
+ public void testDefaultEpsImsNotRegisteredVopsOffEmsOffScanPsPreferred() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_PS,
+ false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
+ public void testDefaultEpsNotRegisteredVopsOffEmsOffBarredScanPsPreferred() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(true);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_PS,
+ false, false, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
+ public void testDefaultOutOfServiceScanPsPreferred() throws Exception {
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ 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();
+
+ verifyScanPsPreferred();
+ }
+
+ @Test
+ public void testVoLteOnEpsImsNotRegisteredSelectPs() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putBoolean(KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL, true);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+
+ 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();
+
+ bindImsServiceUnregistered();
+
+ // Requires VoLTE enabled and VoLTE is enabled.
+ verifyPsDialed();
+ }
+
+ @Test
+ public void testVoLteOffEpsImsNotRegisteredSelectCs() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putBoolean(KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL, true);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+
+ // Disable VoLTE.
+ when(mMmTelManager.isAdvancedCallingSettingEnabled()).thenReturn(false);
+
+ 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();
+
+ bindImsServiceUnregistered();
+
+ // Requires VoLTE enabled but VoLTE is'nt enabled.
+ verifyCsDialed();
+ }
+
+ @Test
+ public void testRequiresRegEpsImsNotRegisteredScanCsPreferred() throws Exception {
+ PersistableBundle bundle = getDefaultPersistableBundle();
+ bundle.putBoolean(KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL, true);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
+
+ createSelector(SLOT_0_SUB_ID);
+ unsolBarringInfoChanged(false);
+
+ EmergencyRegResult regResult = getEmergencyRegResult(EUTRAN, REGISTRATION_STATE_HOME,
+ NetworkRegistrationInfo.DOMAIN_PS,
+ true, true, 0, 0, "", "");
+ SelectionAttributes attr = getSelectionAttributes(SLOT_0, SLOT_0_SUB_ID, regResult);
+ mDomainSelector.selectDomain(attr, mTransportSelectorCallback);
+ processAllMessages();
+
+ bindImsServiceUnregistered();
+
+ verifyScanCsPreferred();
+ }
+
+ @Test
+ public void testDefaultEpsImsRegisteredBarredScanTimeoutWifi() throws Exception {
+ 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();
+
+ assertTrue(mDomainSelector.hasMessages(MSG_NETWORK_SCAN_TIMEOUT));
+
+ mDomainSelector.handleMessage(mDomainSelector.obtainMessage(MSG_NETWORK_SCAN_TIMEOUT));
+
+ verify(mTransportSelectorCallback, times(1)).onWlanSelected();
+ }
+
+ private void createSelector(int subId) throws Exception {
+ mDomainSelector = new EmergencyCallDomainSelector(
+ mContext, SLOT_0, subId, mHandlerThread.getLooper(),
+ mImsStateTracker, mDestroyListener);
+
+ replaceInstance(DomainSelectorBase.class,
+ "mWwanSelectorCallback", mDomainSelector, mWwanSelectorCallback);
+ }
+
+ private void verifyCsDialed() {
+ verify(mWwanSelectorCallback, times(1)).onDomainSelected(eq(DOMAIN_CS));
+ }
+
+ private void verifyPsDialed() {
+ verify(mWwanSelectorCallback, times(1)).onDomainSelected(eq(DOMAIN_PS));
+ }
+
+ private void verifyScanPsPreferred() {
+ verifyScanPreferred(DomainSelectionService.SCAN_TYPE_NO_PREFERENCE, EUTRAN);
+ }
+
+ private void verifyScanCsPreferred() {
+ verifyScanPreferred(DomainSelectionService.SCAN_TYPE_NO_PREFERENCE, UTRAN);
+ }
+
+ private void verifyScanPreferred(int scanType, int expectedPreferredAccessNetwork) {
+ verify(mWwanSelectorCallback, times(1)).onRequestEmergencyNetworkScan(
+ any(), eq(scanType), any(), any());
+ assertEquals(expectedPreferredAccessNetwork, (int) mAccessNetwork.get(0));
+ }
+
+ private void unsolBarringInfoChanged(boolean barred) {
+ SparseArray<BarringInfo.BarringServiceInfo> serviceInfos = new SparseArray<>();
+ if (barred) {
+ serviceInfos.put(BARRING_SERVICE_TYPE_EMERGENCY,
+ new BarringInfo.BarringServiceInfo(BARRING_TYPE_UNCONDITIONAL, false, 0, 0));
+ }
+ mDomainSelector.onBarringInfoUpdated(new BarringInfo(new CellIdentityLte(), serviceInfos));
+ }
+
+ private void bindImsService() {
+ bindImsService(false);
+ }
+
+ private void bindImsService(boolean isWifi) {
+ doReturn(isWifi).when(mImsStateTracker).isImsRegisteredOverWlan();
+ doReturn(true).when(mImsStateTracker).isImsRegistered();
+ mDomainSelector.onImsRegistrationStateChanged();
+ doReturn(true).when(mImsStateTracker).isImsVoiceCapable();
+ mDomainSelector.onImsMmTelCapabilitiesChanged();
+ }
+
+ private void bindImsServiceUnregistered() {
+ doReturn(false).when(mImsStateTracker).isImsRegistered();
+ mDomainSelector.onImsRegistrationStateChanged();
+ doReturn(false).when(mImsStateTracker).isImsVoiceCapable();
+ mDomainSelector.onImsMmTelCapabilitiesChanged();
+ }
+
+ private static EmergencyRegResult getEmergencyRegResult(
+ @AccessNetworkConstants.RadioAccessNetworkType int accessNetwork,
+ @NetworkRegistrationInfo.RegistrationState int regState,
+ @NetworkRegistrationInfo.Domain int domain,
+ boolean isVopsSupported, boolean isEmcBearerSupported, int emc, int emf,
+ @NonNull String mcc, @NonNull String mnc) {
+ return new EmergencyRegResult(accessNetwork, regState,
+ domain, isVopsSupported, isEmcBearerSupported,
+ emc, emf, mcc, mnc, "");
+ }
+
+ private static PersistableBundle getDefaultPersistableBundle() {
+ int[] imsRats = new int[] { EUTRAN };
+ int[] csRats = new int[] { UTRAN, GERAN };
+ int[] imsRoamRats = new int[] { EUTRAN };
+ int[] csRoamRats = new int[] { UTRAN, GERAN };
+ int[] domainPreference = new int[] {
+ CarrierConfigManager.ImsEmergency.DOMAIN_PS_3GPP,
+ CarrierConfigManager.ImsEmergency.DOMAIN_CS,
+ CarrierConfigManager.ImsEmergency.DOMAIN_PS_NON_3GPP
+ };
+ int[] roamDomainPreference = new int[] {
+ CarrierConfigManager.ImsEmergency.DOMAIN_PS_3GPP,
+ CarrierConfigManager.ImsEmergency.DOMAIN_CS,
+ CarrierConfigManager.ImsEmergency.DOMAIN_PS_NON_3GPP
+ };
+ boolean imsWhenVoiceOnCs = false;
+ int maxRetriesOverWiFi = 1;
+ int cellularScanTimerSec = 10;
+ int scanType = SCAN_TYPE_NO_PREFERENCE;
+ boolean requiresImsRegistration = false;
+ boolean requiresVoLteEnabled = false;
+ boolean ltePreferredAfterNrFailed = false;
+ String[] cdmaPreferredNumbers = new String[] {};
+
+ return getPersistableBundle(imsRats, csRats, imsRoamRats, csRoamRats,
+ domainPreference, roamDomainPreference, imsWhenVoiceOnCs, maxRetriesOverWiFi,
+ cellularScanTimerSec, scanType, requiresImsRegistration, requiresVoLteEnabled,
+ ltePreferredAfterNrFailed, cdmaPreferredNumbers);
+ }
+
+ private static PersistableBundle getPersistableBundle(
+ @Nullable int[] imsRats, @Nullable int[] csRats,
+ @Nullable int[] imsRoamRats, @Nullable int[] csRoamRats,
+ @Nullable int[] domainPreference, @Nullable int[] roamDomainPreference,
+ boolean imsWhenVoiceOnCs, int maxRetriesOverWiFi,
+ int cellularScanTimerSec, int scanType, boolean requiresImsRegistration,
+ boolean requiresVoLteEnabled, boolean ltePreferredAfterNrFailed,
+ @Nullable String[] cdmaPreferredNumbers) {
+
+ PersistableBundle bundle = new PersistableBundle();
+ if (imsRats != null) {
+ bundle.putIntArray(KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY,
+ imsRats);
+ }
+ if (imsRoamRats != null) {
+ bundle.putIntArray(
+ KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY,
+ imsRoamRats);
+ }
+ if (csRats != null) {
+ bundle.putIntArray(KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY,
+ csRats);
+ }
+ if (csRoamRats != null) {
+ bundle.putIntArray(
+ KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY,
+ csRoamRats);
+ }
+ if (domainPreference != null) {
+ bundle.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY, domainPreference);
+ }
+ if (roamDomainPreference != null) {
+ bundle.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY,
+ roamDomainPreference);
+ }
+ bundle.putBoolean(KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL, imsWhenVoiceOnCs);
+ bundle.putInt(KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT, maxRetriesOverWiFi);
+ bundle.putInt(KEY_EMERGENCY_SCAN_TIMER_SEC_INT, cellularScanTimerSec);
+ bundle.putInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT, scanType);
+ bundle.putBoolean(KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL, requiresImsRegistration);
+ bundle.putBoolean(KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL, requiresVoLteEnabled);
+ bundle.putInt(KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT, 0);
+ bundle.putBoolean(KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL,
+ ltePreferredAfterNrFailed);
+ bundle.putStringArray(KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY,
+ cdmaPreferredNumbers);
+
+ return bundle;
+ }
+
+ public static SelectionAttributes getSelectionAttributes(int slotId, int subId,
+ EmergencyRegResult regResult) {
+ SelectionAttributes.Builder builder =
+ new SelectionAttributes.Builder(slotId, subId, SELECTOR_TYPE_CALLING)
+ .setEmergency(true)
+ .setEmergencyRegResult(regResult);
+ return builder.build();
+ }
+
+ private static void replaceInstance(final Class c,
+ final String instanceName, final Object obj, final Object newValue) throws Exception {
+ Field field = c.getDeclaredField(instanceName);
+ field.setAccessible(true);
+ field.set(obj, newValue);
+ }
+
+ private void processAllMessages() {
+ while (!mLooper.getLooper().getQueue().isIdle()) {
+ mLooper.processAllMessages();
+ }
+ }
+
+ private static void logd(String str) {
+ Log.d(TAG, str);
+ }
+}
diff --git a/tests/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelectorTest.java b/tests/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelectorTest.java
new file mode 100644
index 0000000..8b63530
--- /dev/null
+++ b/tests/src/com/android/services/telephony/domainselection/EmergencySmsDomainSelectorTest.java
@@ -0,0 +1,799 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.DomainSelectionService.SELECTOR_TYPE_SMS;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.BarringInfo;
+import android.telephony.CarrierConfigManager;
+import android.telephony.DataSpecificRegistrationInfo;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.telephony.TransportSelectorCallback;
+import android.telephony.VopsSupportInfo;
+import android.telephony.WwanSelectorCallback;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableLooper;
+import android.util.SparseArray;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.TestContext;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Consumer;
+
+/**
+ * Unit tests for EmergencySmsDomainSelector.
+ */
+@RunWith(AndroidJUnit4.class)
+public class EmergencySmsDomainSelectorTest {
+ private static final int SLOT_0 = 0;
+ private static final int SUB_1 = 1;
+
+ @Mock private ServiceState mServiceState;
+ @Mock private TransportSelectorCallback mTransportSelectorCallback;
+ @Mock private WwanSelectorCallback mWwanSelectorCallback;
+ @Mock private VopsSupportInfo mVopsSupportInfo;
+ @Mock private ImsStateTracker mImsStateTracker;
+ @Mock private DomainSelectorBase.DestroyListener mDomainSelectorDestroyListener;
+
+ private final SelectionAttributes mSelectionAttributes =
+ new SelectionAttributes.Builder(SLOT_0, SUB_1, SELECTOR_TYPE_SMS)
+ .setEmergency(true)
+ .build();
+ private Context mContext;
+ private Looper mLooper;
+ private TestableLooper mTestableLooper;
+ private CarrierConfigManager mCarrierConfigManager;
+ private NetworkRegistrationInfo mNetworkRegistrationInfo;
+ private boolean mCarrierConfigManagerNullTest = false;
+ private BarringInfo mBarringInfo = new BarringInfo();
+ private ImsStateTracker.ImsStateListener mImsStateListener;
+ private ImsStateTracker.BarringInfoListener mBarringInfoListener;
+ private ImsStateTracker.ServiceStateListener mServiceStateListener;
+ private EmergencySmsDomainSelector mDomainSelector;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext = new TestContext() {
+ @Override
+ public Object getSystemService(String name) {
+ if (name.equals(Context.CARRIER_CONFIG_SERVICE)) {
+ if (mCarrierConfigManagerNullTest) {
+ return null;
+ }
+ }
+
+ return super.getSystemService(name);
+ }
+ };
+
+ HandlerThread handlerThread = new HandlerThread(
+ EmergencySmsDomainSelectorTest.class.getSimpleName());
+ handlerThread.start();
+ mLooper = handlerThread.getLooper();
+ mTestableLooper = new TestableLooper(mLooper);
+ mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
+
+ mDomainSelector = new EmergencySmsDomainSelector(mContext, SLOT_0, SUB_1,
+ mLooper, mImsStateTracker, mDomainSelectorDestroyListener);
+
+ ArgumentCaptor<ImsStateTracker.ServiceStateListener> serviceStateListenerCaptor =
+ ArgumentCaptor.forClass(ImsStateTracker.ServiceStateListener.class);
+ verify(mImsStateTracker).addServiceStateListener(serviceStateListenerCaptor.capture());
+ mServiceStateListener = serviceStateListenerCaptor.getValue();
+ assertNotNull(mServiceStateListener);
+
+ ArgumentCaptor<ImsStateTracker.BarringInfoListener> barringInfoListenerCaptor =
+ ArgumentCaptor.forClass(ImsStateTracker.BarringInfoListener.class);
+ verify(mImsStateTracker).addBarringInfoListener(barringInfoListenerCaptor.capture());
+ mBarringInfoListener = barringInfoListenerCaptor.getValue();
+ assertNotNull(mBarringInfoListener);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mTestableLooper != null) {
+ mTestableLooper.destroy();
+ mTestableLooper = null;
+ }
+
+ if (mDomainSelector != null) {
+ mDomainSelector.destroy();
+ verify(mImsStateTracker).removeImsStateListener(eq(mDomainSelector));
+ verify(mImsStateTracker).removeBarringInfoListener(eq(mDomainSelector));
+ verify(mImsStateTracker).removeServiceStateListener(eq(mDomainSelector));
+ }
+
+ if (mLooper != null) {
+ mLooper.quit();
+ mLooper = null;
+ }
+
+ mDomainSelector = null;
+ mNetworkRegistrationInfo = null;
+ mVopsSupportInfo = null;
+ mDomainSelectorDestroyListener = null;
+ mWwanSelectorCallback = null;
+ mTransportSelectorCallback = null;
+ mServiceState = null;
+ mCarrierConfigManager = null;
+ mCarrierConfigManagerNullTest = false;
+ }
+
+ @Test
+ @SmallTest
+ public void testFinishSelection() {
+ setUpImsStateTracker(AccessNetworkType.EUTRAN);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ mServiceStateListener.onServiceStateUpdated(mServiceState);
+ mBarringInfoListener.onBarringInfoUpdated(mBarringInfo);
+
+ assertTrue(mDomainSelector.isDomainSelectionRequested());
+ assertTrue(mDomainSelector.isDomainSelectionReady());
+
+ mDomainSelector.finishSelection();
+
+ assertFalse(mDomainSelector.isDomainSelectionReady());
+ assertFalse(mDomainSelector.isDomainSelectionRequested());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsDomainSelectionReady() {
+ setUpImsStateTracker(AccessNetworkType.EUTRAN);
+
+ assertFalse(mDomainSelector.isDomainSelectionReady());
+
+ mServiceStateListener.onServiceStateUpdated(mServiceState);
+ mBarringInfoListener.onBarringInfoUpdated(mBarringInfo);
+
+ assertTrue(mDomainSelector.isDomainSelectionReady());
+ assertFalse(mDomainSelector.isDomainSelectionRequested());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsDomainSelectionReadyAndSelectDomain() {
+ setUpImsStateTracker(AccessNetworkType.EUTRAN);
+ setUpWwanSelectorCallback();
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ mServiceStateListener.onServiceStateUpdated(mServiceState);
+ mBarringInfoListener.onBarringInfoUpdated(mBarringInfo);
+
+ assertTrue(mDomainSelector.isDomainSelectionRequested());
+ assertTrue(mDomainSelector.isDomainSelectionReady());
+
+ processAllMessages();
+
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+ assertFalse(mDomainSelector.isDomainSelectionRequested());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenImsRegistered() {
+ assertFalse(mDomainSelector.isSmsOverImsAvailable());
+
+ setUpImsStateTracker(AccessNetworkType.EUTRAN);
+
+ assertTrue(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenImsRegisteredAndConfigEnabledAndLteAvailable() {
+ setUpImsStateTracker(AccessNetworkType.EUTRAN);
+ setUpCarrierConfig(true);
+ setUpLteInService(false, false, true, false, false);
+
+ assertTrue(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenImsRegisteredAndConfigEnabledAndLteNotAvailable() {
+ setUpImsStateTracker(AccessNetworkType.EUTRAN);
+ setUpCarrierConfig(true);
+ setUpLteInService(false, false, false, false, false);
+
+ assertFalse(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenCarrierConfigManagerIsNull() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ mCarrierConfigManagerNullTest = true;
+
+ assertFalse(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenCarrierConfigIsNull() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(null);
+
+ assertFalse(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenCarrierConfigNotEnabled() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpCarrierConfig(false);
+
+ assertFalse(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenMmTelFeatureUnavailable() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN, false, false);
+ setUpCarrierConfig(true);
+
+ assertFalse(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenServiceStateIsNull() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpCarrierConfig(true);
+
+ assertFalse(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenNoLte() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpCarrierConfig(true);
+ mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+ .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UMTS)
+ .build();
+ when(mServiceState.getNetworkRegistrationInfo(
+ anyInt(), eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
+ .thenReturn(mNetworkRegistrationInfo);
+
+ mServiceStateListener.onServiceStateUpdated(mServiceState);
+
+ assertFalse(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenLteNotRegisteredOrEmergencyNotEnabled() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpCarrierConfig(true);
+ mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+ .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+ .build();
+ when(mServiceState.getNetworkRegistrationInfo(
+ anyInt(), eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
+ .thenReturn(mNetworkRegistrationInfo);
+
+ mServiceStateListener.onServiceStateUpdated(mServiceState);
+
+ assertFalse(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenLteInServiceAndNoDataSpecificRegistrationInfo() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpCarrierConfig(true);
+ setUpLteInService(true, true, true, true, false);
+
+ assertFalse(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenLteInServiceAndNoVopsSupportInfo() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpCarrierConfig(true);
+ setUpLteInService(false, true, true, true, false);
+
+ assertFalse(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenLteInServiceAndEmcBsNotSupported() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpCarrierConfig(true);
+ setUpLteInService(false, false, false, true, false);
+
+ assertFalse(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenLteInServiceAndEmcBsSupportedAndNoBarringInfo() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpCarrierConfig(true);
+ setUpLteInService(false, false, true, true, false);
+
+ assertTrue(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenLteInServiceAndEmcBsSupportedAndBarred() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpCarrierConfig(true);
+ setUpLteInService(false, false, true, false, true);
+
+ assertFalse(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenLteInServiceAndEmcBsSupportedAndNotBarred() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpCarrierConfig(true);
+ setUpLteInService(false, false, true, false, false);
+
+ assertTrue(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testIsSmsOverImsAvailableWhenLteInLimitedService() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpCarrierConfig(true);
+ setUpLimitedLteService(false, false, true, false, false);
+
+ assertTrue(mDomainSelector.isSmsOverImsAvailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhilePreviousRequestInProgress() {
+ setUpImsStateTracker(AccessNetworkType.EUTRAN);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpLteInService(false, false, true, false, false);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ mServiceStateListener.onServiceStateUpdated(mServiceState);
+ mBarringInfoListener.onBarringInfoUpdated(mBarringInfo);
+
+ assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+ processAllMessages();
+
+ // onDomainSelected will be invoked only once
+ // even though the domain selection was requested twice.
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+ assertFalse(mDomainSelector.isDomainSelectionRequested());
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenImsNotRegisteredAndConfigDisabled() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(false);
+ setUpLteInService(false, false, true, false, false);
+ setUpImsStateListener(true, false, false);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: CS network
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenImsNotRegisteredAndUmtsNetwork() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpNonLteService(TelephonyManager.NETWORK_TYPE_UMTS);
+ setUpImsStateListener(true, false, false);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: CS network
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenImsNotRegisteredAndUnknownNetwork() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpNonLteService(TelephonyManager.NETWORK_TYPE_UNKNOWN);
+ setUpImsStateListener(true, false, false);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: CS network
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenImsNotRegisteredAndLteInService() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpLteInService(false, false, true, false, false);
+ setUpImsStateListener(true, false, false);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: PS network
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenImsNotRegisteredAndLteEmcBsNotSupported() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpLteInService(false, false, false, false, false);
+ setUpImsStateListener(true, false, false);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: CS network
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenImsNotRegisteredAndLteEmergencyBarred() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpLteInService(false, false, true, false, true);
+ setUpImsStateListener(true, false, false);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: CS network
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenImsNotRegisteredAndLimitedLteService() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpLimitedLteService(false, false, true, false, false);
+ setUpImsStateListener(true, false, false);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: PS network
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenImsNotRegisteredAndLimitedLteEmcBsNotSupported() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpLimitedLteService(false, false, false, false, false);
+ setUpImsStateListener(true, false, false);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: CS network
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenImsNotRegisteredAndLimitedLteEmergencyBarred() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpLimitedLteService(false, false, true, false, true);
+ setUpImsStateListener(true, false, false);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: CS network
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenImsRegisteredOnLte() {
+ setUpImsStateTracker(AccessNetworkType.EUTRAN);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpLteInService(false, false, true, false, false);
+ setUpImsStateListener(true, true, true);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: PS network
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenImsRegisteredOnLteAndEmcBsNotSupported() {
+ setUpImsStateTracker(AccessNetworkType.EUTRAN);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpLteInService(false, false, false, false, false);
+ setUpImsStateListener(true, true, true);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: CS network
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenImsRegisteredOnLteAndEmergencyBarred() {
+ setUpImsStateTracker(AccessNetworkType.EUTRAN);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpLteInService(false, false, true, false, true);
+ setUpImsStateListener(true, true, true);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: CS network
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenImsRegisteredOnIwlanAndConfigDisabled() {
+ setUpImsStateTracker(AccessNetworkType.IWLAN);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(false);
+ setUpLteInService(false, false, true, false, false);
+ setUpImsStateListener(true, true, true);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: WLAN
+ verify(mTransportSelectorCallback).onWlanSelected();
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenImsRegisteredOnIwlanAndLteNotAvailable() {
+ setUpImsStateTracker(AccessNetworkType.IWLAN);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpLteInService(false, false, false, false, false);
+ setUpImsStateListener(true, true, true);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: CS network - even though IMS is successfully registered over Wi-Fi,
+ // if the emergency SMS messages over IMS is enabled in the carrier configuration and
+ // the PS network does not allow the emergency service, this MO SMS should be routed to
+ // CS domain.
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenImsRegisteredOnIwlanAndLteAvailable() {
+ setUpImsStateTracker(AccessNetworkType.IWLAN);
+ setUpWwanSelectorCallback();
+ setUpCarrierConfig(true);
+ setUpLteInService(false, false, true, false, false);
+ setUpImsStateListener(true, true, true);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+ processAllMessages();
+
+ // Expected: PS network
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+ }
+
+ private void setUpCarrierConfig(boolean supported) {
+ PersistableBundle b = new PersistableBundle();
+ b.putBoolean(CarrierConfigManager.KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL, supported);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(b);
+ }
+
+ private void setUpNonLteService(int networkType) {
+ mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+ .setAccessNetworkTechnology(networkType)
+ .setRegistrationState(networkType == TelephonyManager.NETWORK_TYPE_UNKNOWN
+ ? NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN
+ : NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+ .build();
+ when(mServiceState.getNetworkRegistrationInfo(
+ anyInt(), eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
+ .thenReturn(mNetworkRegistrationInfo);
+
+ mServiceStateListener.onServiceStateUpdated(mServiceState);
+ mBarringInfoListener.onBarringInfoUpdated(null);
+ }
+
+ private void setUpLteInService(boolean noDataSpecificRegistrationInfo,
+ boolean noVopsSupportInfo, boolean emcBsSupported,
+ boolean noBarringInfo, boolean barred) {
+ DataSpecificRegistrationInfo dsri = noDataSpecificRegistrationInfo
+ ? null : new DataSpecificRegistrationInfo(
+ 8, false, false, false, noVopsSupportInfo ? null : mVopsSupportInfo);
+
+ mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+ .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+ .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+ .setDataSpecificInfo(dsri)
+ .build();
+ when(mServiceState.getNetworkRegistrationInfo(
+ anyInt(), eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
+ .thenReturn(mNetworkRegistrationInfo);
+ when(mVopsSupportInfo.isEmergencyServiceSupported()).thenReturn(emcBsSupported);
+
+ BarringInfo barringInfo = null;
+
+ if (!noBarringInfo) {
+ SparseArray<BarringInfo.BarringServiceInfo> barringServiceInfos = new SparseArray<>();
+ barringServiceInfos.put(BarringInfo.BARRING_SERVICE_TYPE_EMERGENCY,
+ new BarringInfo.BarringServiceInfo(
+ barred ? BarringInfo.BarringServiceInfo.BARRING_TYPE_UNCONDITIONAL :
+ BarringInfo.BarringServiceInfo.BARRING_TYPE_NONE, false, 0, 0));
+ barringInfo = new BarringInfo(null, barringServiceInfos);
+ }
+
+ mServiceStateListener.onServiceStateUpdated(mServiceState);
+ mBarringInfoListener.onBarringInfoUpdated(barringInfo);
+ }
+
+ private void setUpLimitedLteService(boolean noDataSpecificRegistrationInfo,
+ boolean noVopsSupportInfo, boolean emcBsSupported,
+ boolean noBarringInfo, boolean barred) {
+ DataSpecificRegistrationInfo dsri = noDataSpecificRegistrationInfo
+ ? null : new DataSpecificRegistrationInfo(
+ 8, false, false, false, noVopsSupportInfo ? null : mVopsSupportInfo);
+
+ mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
+ .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+ .setEmergencyOnly(true)
+ .setDataSpecificInfo(dsri)
+ .build();
+ when(mServiceState.getNetworkRegistrationInfo(
+ anyInt(), eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
+ .thenReturn(mNetworkRegistrationInfo);
+ when(mVopsSupportInfo.isEmergencyServiceSupported()).thenReturn(emcBsSupported);
+
+ BarringInfo barringInfo = null;
+
+ if (!noBarringInfo) {
+ SparseArray<BarringInfo.BarringServiceInfo> barringServiceInfos = new SparseArray<>();
+ barringServiceInfos.put(BarringInfo.BARRING_SERVICE_TYPE_EMERGENCY,
+ new BarringInfo.BarringServiceInfo(
+ barred ? BarringInfo.BarringServiceInfo.BARRING_TYPE_UNCONDITIONAL :
+ BarringInfo.BarringServiceInfo.BARRING_TYPE_NONE, false, 0, 0));
+ barringInfo = new BarringInfo(null, barringServiceInfos);
+ }
+
+ mServiceStateListener.onServiceStateUpdated(mServiceState);
+ mBarringInfoListener.onBarringInfoUpdated(barringInfo);
+ }
+
+ private void setUpImsStateTracker(@RadioAccessNetworkType int accessNetworkType) {
+ setUpImsStateTracker(accessNetworkType, true, true);
+ }
+
+ private void setUpImsStateTracker(@RadioAccessNetworkType int accessNetworkType,
+ boolean mmTelFeatureAvailable, boolean smsCapable) {
+ when(mImsStateTracker.isMmTelFeatureAvailable()).thenReturn(mmTelFeatureAvailable);
+ when(mImsStateTracker.isImsRegistered())
+ .thenReturn(accessNetworkType != AccessNetworkType.UNKNOWN);
+ when(mImsStateTracker.isImsRegisteredOverWlan())
+ .thenReturn(accessNetworkType == AccessNetworkType.IWLAN);
+ when(mImsStateTracker.getImsAccessNetworkType()).thenReturn(accessNetworkType);
+ when(mImsStateTracker.isImsSmsCapable()).thenReturn(smsCapable);
+ }
+
+ private void setUpWwanSelectorCallback() {
+ doAnswer((invocation) -> {
+ Object[] args = invocation.getArguments();
+ final Consumer<WwanSelectorCallback> callback =
+ (Consumer<WwanSelectorCallback>) args[0];
+ callback.accept(mWwanSelectorCallback);
+ return null;
+ }).when(mTransportSelectorCallback).onWwanSelected(any(Consumer.class));
+ }
+
+ private void setUpImsStateListener(boolean notifyMmTelFeatureAvailable,
+ boolean notifyImsRegState, boolean notifyMmTelCapability) {
+ doAnswer((invocation) -> {
+ Object[] args = invocation.getArguments();
+ final ImsStateTracker.ImsStateListener listener =
+ (ImsStateTracker.ImsStateListener) args[0];
+ mDomainSelector.post(() -> {
+ if (notifyMmTelFeatureAvailable) {
+ listener.onImsMmTelFeatureAvailableChanged();
+ }
+ if (notifyImsRegState) {
+ listener.onImsRegistrationStateChanged();
+ }
+ if (notifyMmTelCapability) {
+ listener.onImsMmTelCapabilitiesChanged();
+ }
+ });
+ return null;
+ }).when(mImsStateTracker).addImsStateListener(any(ImsStateTracker.ImsStateListener.class));
+ }
+
+ private void processAllMessages() {
+ while (!mTestableLooper.getLooper().getQueue().isIdle()) {
+ mTestableLooper.processAllMessages();
+ }
+ }
+}
diff --git a/tests/src/com/android/services/telephony/domainselection/ImsStateTrackerTest.java b/tests/src/com/android/services/telephony/domainselection/ImsStateTrackerTest.java
new file mode 100644
index 0000000..b00926f
--- /dev/null
+++ b/tests/src/com/android/services/telephony/domainselection/ImsStateTrackerTest.java
@@ -0,0 +1,744 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.BarringInfo;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsManager;
+import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
+import android.telephony.ims.ImsStateCallback;
+import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.TestContext;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Unit tests for ImsStateTracker.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ImsStateTrackerTest {
+ private static final int SLOT_0 = 0;
+ private static final int SUB_1 = 1;
+ private static final int SUB_2 = 2;
+ private static final long TIMEOUT_MS = 100;
+
+ @Mock private ImsMmTelManager mMmTelManager;
+ @Mock private ImsMmTelManager mMmTelManager2;
+ @Mock private ImsStateTracker.BarringInfoListener mBarringInfoListener;
+ @Mock private ImsStateTracker.ServiceStateListener mServiceStateListener;
+ @Mock private ImsStateTracker.ImsStateListener mImsStateListener;
+ @Mock private ServiceState mServiceState;
+
+ private Context mContext;
+ private Looper mLooper;
+ private BarringInfo mBarringInfo = new BarringInfo();
+ private ImsStateTracker mImsStateTracker;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext = new TestContext() {
+ @Override
+ public String getSystemServiceName(Class<?> serviceClass) {
+ if (serviceClass == ImsManager.class) {
+ return Context.TELEPHONY_IMS_SERVICE;
+ }
+ return super.getSystemServiceName(serviceClass);
+ }
+ };
+
+ HandlerThread handlerThread = new HandlerThread(
+ ImsStateTrackerTest.class.getSimpleName());
+ handlerThread.start();
+ mLooper = handlerThread.getLooper();
+ mImsStateTracker = new ImsStateTracker(mContext, SLOT_0, mLooper);
+
+ ImsManager imsManager = mContext.getSystemService(ImsManager.class);
+ when(imsManager.getImsMmTelManager(eq(SUB_1))).thenReturn(mMmTelManager);
+ when(imsManager.getImsMmTelManager(eq(SUB_2))).thenReturn(mMmTelManager2);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mImsStateTracker.destroy();
+ mImsStateTracker = null;
+ mMmTelManager = null;
+
+ if (mLooper != null) {
+ mLooper.quit();
+ mLooper = null;
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testInit() {
+ assertEquals(SLOT_0, mImsStateTracker.getSlotId());
+ assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID, mImsStateTracker.getSubId());
+ }
+
+ @Test
+ @SmallTest
+ public void testStartWithInvalidSubId() {
+ mImsStateTracker.start(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+ assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID, mImsStateTracker.getSubId());
+ assertTrue(isImsStateUnavailable());
+ }
+
+ @Test
+ @SmallTest
+ public void testStart() throws ImsException {
+ mImsStateTracker.start(SUB_1);
+
+ assertEquals(SUB_1, mImsStateTracker.getSubId());
+ assertTrue(isImsStateInit());
+ verify(mMmTelManager).registerImsStateCallback(
+ any(Executor.class), any(ImsStateCallback.class));
+ }
+
+ @Test
+ @SmallTest
+ public void testStartWithDifferentSubId() throws ImsException {
+ mImsStateTracker.start(SUB_1);
+
+ assertEquals(SUB_1, mImsStateTracker.getSubId());
+ assertTrue(isImsStateInit());
+
+ mImsStateTracker.start(SUB_2);
+
+ assertEquals(SUB_2, mImsStateTracker.getSubId());
+ assertTrue(isImsStateInit());
+ verify(mMmTelManager).registerImsStateCallback(
+ any(Executor.class), any(ImsStateCallback.class));
+ verify(mMmTelManager).unregisterImsStateCallback(
+ any(ImsStateCallback.class));
+ verify(mMmTelManager2).registerImsStateCallback(
+ any(Executor.class), any(ImsStateCallback.class));
+ }
+
+ @Test
+ @SmallTest
+ public void testStartWithSameSubId() throws ImsException {
+ mImsStateTracker.start(SUB_1);
+
+ assertEquals(SUB_1, mImsStateTracker.getSubId());
+ assertTrue(isImsStateInit());
+
+ mImsStateTracker.start(SUB_1);
+
+ assertEquals(SUB_1, mImsStateTracker.getSubId());
+ assertTrue(isImsStateInit());
+ verify(mMmTelManager).registerImsStateCallback(
+ any(Executor.class), any(ImsStateCallback.class));
+ verify(mMmTelManager, never()).unregisterImsStateCallback(
+ any(ImsStateCallback.class));
+ }
+
+ @Test
+ @SmallTest
+ public void testStartWhenRegisteringCallbacksThrowException() throws ImsException {
+ doAnswer((invocation) -> {
+ throw new ImsException("Intended exception for ImsStateCallback.");
+ }).when(mMmTelManager).registerImsStateCallback(
+ any(Executor.class), any(ImsStateCallback.class));
+
+ mImsStateTracker.start(SUB_1);
+
+ assertEquals(SUB_1, mImsStateTracker.getSubId());
+
+ mImsStateTracker.start(SUB_2);
+
+ assertEquals(SUB_2, mImsStateTracker.getSubId());
+
+ verify(mMmTelManager, never()).unregisterImsStateCallback(
+ any(ImsStateCallback.class));
+ }
+
+ @Test
+ @SmallTest
+ public void testUpdateServiceStateBeforeAddingListener() {
+ mImsStateTracker.updateServiceState(mServiceState);
+ mImsStateTracker.addServiceStateListener(mServiceStateListener);
+ waitForHandlerAction(mImsStateTracker.getHandler(), TIMEOUT_MS);
+
+ verify(mServiceStateListener).onServiceStateUpdated(eq(mServiceState));
+
+ mImsStateTracker.removeServiceStateListener(mServiceStateListener);
+ ServiceState ss = Mockito.mock(ServiceState.class);
+ mImsStateTracker.updateServiceState(ss);
+ waitForHandlerAction(mImsStateTracker.getHandler(), TIMEOUT_MS);
+
+ verifyNoMoreInteractions(mServiceStateListener);
+ }
+
+ @Test
+ @SmallTest
+ public void testUpdateServiceStateAfterAddingListener() {
+ mImsStateTracker.addServiceStateListener(mServiceStateListener);
+ mImsStateTracker.updateServiceState(mServiceState);
+ waitForHandlerAction(mImsStateTracker.getHandler(), TIMEOUT_MS);
+
+ verify(mServiceStateListener).onServiceStateUpdated(eq(mServiceState));
+
+ mImsStateTracker.removeServiceStateListener(mServiceStateListener);
+ ServiceState ss = Mockito.mock(ServiceState.class);
+ mImsStateTracker.updateServiceState(ss);
+ waitForHandlerAction(mImsStateTracker.getHandler(), TIMEOUT_MS);
+
+ verifyNoMoreInteractions(mServiceStateListener);
+ }
+
+ @Test
+ @SmallTest
+ public void testAddAndRemoveServiceStateListener() {
+ mImsStateTracker.updateServiceState(mServiceState);
+ mImsStateTracker.addServiceStateListener(mServiceStateListener);
+ mImsStateTracker.removeServiceStateListener(mServiceStateListener);
+ waitForHandlerAction(mImsStateTracker.getHandler(), TIMEOUT_MS);
+
+ verify(mServiceStateListener, never()).onServiceStateUpdated(eq(mServiceState));
+ }
+
+ @Test
+ @SmallTest
+ public void testUpdateBarringInfoBeforeAddingListener() {
+ mImsStateTracker.updateBarringInfo(mBarringInfo);
+ mImsStateTracker.addBarringInfoListener(mBarringInfoListener);
+ waitForHandlerAction(mImsStateTracker.getHandler(), TIMEOUT_MS);
+
+ verify(mBarringInfoListener).onBarringInfoUpdated(eq(mBarringInfo));
+
+ mImsStateTracker.removeBarringInfoListener(mBarringInfoListener);
+ BarringInfo bi = new BarringInfo();
+ mImsStateTracker.updateBarringInfo(bi);
+ waitForHandlerAction(mImsStateTracker.getHandler(), TIMEOUT_MS);
+
+ verifyNoMoreInteractions(mBarringInfoListener);
+ }
+
+ @Test
+ @SmallTest
+ public void testUpdateBarringInfoAfterAddingListener() {
+ mImsStateTracker.addBarringInfoListener(mBarringInfoListener);
+ mImsStateTracker.updateBarringInfo(mBarringInfo);
+ waitForHandlerAction(mImsStateTracker.getHandler(), TIMEOUT_MS);
+
+ verify(mBarringInfoListener).onBarringInfoUpdated(eq(mBarringInfo));
+
+ mImsStateTracker.removeBarringInfoListener(mBarringInfoListener);
+ BarringInfo bi = new BarringInfo();
+ mImsStateTracker.updateBarringInfo(bi);
+ waitForHandlerAction(mImsStateTracker.getHandler(), TIMEOUT_MS);
+
+ verifyNoMoreInteractions(mBarringInfoListener);
+ }
+
+ @Test
+ @SmallTest
+ public void testAddAndRemoveBarringInfoListener() {
+ mImsStateTracker.updateBarringInfo(mBarringInfo);
+ mImsStateTracker.addBarringInfoListener(mBarringInfoListener);
+ mImsStateTracker.removeBarringInfoListener(mBarringInfoListener);
+ waitForHandlerAction(mImsStateTracker.getHandler(), TIMEOUT_MS);
+
+ verify(mBarringInfoListener, never()).onBarringInfoUpdated(eq(mBarringInfo));
+ }
+
+ @Test
+ @SmallTest
+ public void testNotifyImsStateCallbackOnAvailable() throws ImsException {
+ ImsStateCallback callback = setUpImsStateCallback();
+ callback.onAvailable();
+
+ assertTrue(mImsStateTracker.isMmTelFeatureAvailable());
+ assertFalse(mImsStateTracker.isImsStateReady());
+ verify(mMmTelManager).registerImsRegistrationCallback(
+ any(Executor.class), any(RegistrationManager.RegistrationCallback.class));
+ verify(mMmTelManager).registerMmTelCapabilityCallback(
+ any(Executor.class), any(ImsMmTelManager.CapabilityCallback.class));
+ verify(mImsStateListener).onImsMmTelFeatureAvailableChanged();
+ }
+
+ @Test
+ @SmallTest
+ public void testNotifyImsStateCallbackOnUnavailableWithReasonUnknownPermanentError()
+ throws ImsException {
+ ImsStateCallback callback = setUpImsStateCallback();
+ callback.onUnavailable(ImsStateCallback.REASON_UNKNOWN_PERMANENT_ERROR);
+
+ assertTrue(isImsStateUnavailable());
+ assertTrue(mImsStateTracker.isImsStateReady());
+ verify(mImsStateListener).onImsMmTelFeatureAvailableChanged();
+ }
+
+ @Test
+ @SmallTest
+ public void testNotifyImsStateCallbackOnUnavailableWithReasonNoImsServiceConfigured()
+ throws ImsException {
+ ImsStateCallback callback = setUpImsStateCallback();
+ callback.onUnavailable(ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED);
+
+ assertTrue(isImsStateUnavailable());
+ assertTrue(mImsStateTracker.isImsStateReady());
+ verify(mImsStateListener).onImsMmTelFeatureAvailableChanged();
+ }
+
+ @Test
+ public void testNotifyImsStateCallbackOnUnavailableWithReasonUnknownTemporaryError()
+ throws ImsException {
+ ImsStateCallback callback = setUpImsStateCallback();
+ callback.onUnavailable(ImsStateCallback.REASON_UNKNOWN_TEMPORARY_ERROR);
+
+ assertFalse(mImsStateTracker.isMmTelFeatureAvailable());
+ assertFalse(isImsStateUnavailable());
+ assertFalse(mImsStateTracker.isImsStateReady());
+
+ waitForHandlerActionDelayed(mImsStateTracker.getHandler(),
+ ImsStateTracker.MMTEL_FEATURE_AVAILABLE_WAIT_TIME_MILLIS,
+ ImsStateTracker.MMTEL_FEATURE_AVAILABLE_WAIT_TIME_MILLIS + TIMEOUT_MS);
+
+ assertFalse(mImsStateTracker.isMmTelFeatureAvailable());
+ assertTrue(isImsStateUnavailable());
+ assertTrue(mImsStateTracker.isImsStateReady());
+ verify(mImsStateListener).onImsMmTelFeatureAvailableChanged();
+ }
+
+ @Test
+ public void testNotifyImsStateCallbackOnUnavailableWithReasonImsServiceNotReady()
+ throws ImsException {
+ ImsStateCallback callback = setUpImsStateCallback();
+ callback.onUnavailable(ImsStateCallback.REASON_IMS_SERVICE_NOT_READY);
+
+ assertFalse(mImsStateTracker.isMmTelFeatureAvailable());
+ assertFalse(isImsStateUnavailable());
+ assertFalse(mImsStateTracker.isImsStateReady());
+
+ waitForHandlerActionDelayed(mImsStateTracker.getHandler(),
+ ImsStateTracker.MMTEL_FEATURE_AVAILABLE_WAIT_TIME_MILLIS,
+ ImsStateTracker.MMTEL_FEATURE_AVAILABLE_WAIT_TIME_MILLIS + TIMEOUT_MS);
+
+ assertFalse(mImsStateTracker.isMmTelFeatureAvailable());
+ assertTrue(isImsStateUnavailable());
+ assertTrue(mImsStateTracker.isImsStateReady());
+ verify(mImsStateListener).onImsMmTelFeatureAvailableChanged();
+ }
+
+ @Test
+ public void testNotifyImsStateCallbackOnUnavailableWithReasonImsServiceDisconnected()
+ throws ImsException {
+ ImsStateCallback callback = setUpImsStateCallback();
+ callback.onUnavailable(ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED);
+
+ assertFalse(mImsStateTracker.isMmTelFeatureAvailable());
+ assertFalse(isImsStateUnavailable());
+ assertFalse(mImsStateTracker.isImsStateReady());
+
+ waitForHandlerActionDelayed(mImsStateTracker.getHandler(),
+ ImsStateTracker.MMTEL_FEATURE_AVAILABLE_WAIT_TIME_MILLIS,
+ ImsStateTracker.MMTEL_FEATURE_AVAILABLE_WAIT_TIME_MILLIS + TIMEOUT_MS);
+
+ assertFalse(mImsStateTracker.isMmTelFeatureAvailable());
+ assertTrue(isImsStateUnavailable());
+ assertTrue(mImsStateTracker.isImsStateReady());
+ verify(mMmTelManager, never()).unregisterImsRegistrationCallback(
+ any(RegistrationManager.RegistrationCallback.class));
+ verify(mMmTelManager, never()).unregisterMmTelCapabilityCallback(
+ any(ImsMmTelManager.CapabilityCallback.class));
+ verify(mImsStateListener).onImsMmTelFeatureAvailableChanged();
+ }
+
+ @Test
+ @SmallTest
+ public void testNotifyImsStateCallbackOnUnavailableWithReasonSubscriptionInactive()
+ throws ImsException {
+ ImsStateCallback callback = setUpImsStateCallback();
+ callback.onUnavailable(ImsStateCallback.REASON_SUBSCRIPTION_INACTIVE);
+
+ assertFalse(mImsStateTracker.isMmTelFeatureAvailable());
+ assertTrue(isImsStateUnavailable());
+ assertTrue(mImsStateTracker.isImsStateReady());
+ verify(mMmTelManager, never()).unregisterImsRegistrationCallback(
+ any(RegistrationManager.RegistrationCallback.class));
+ verify(mMmTelManager, never()).unregisterMmTelCapabilityCallback(
+ any(ImsMmTelManager.CapabilityCallback.class));
+ verify(mImsStateListener).onImsMmTelFeatureAvailableChanged();
+ }
+
+ @Test
+ public void testNotifyImsStateCallbackOnAvailableUnavailableWithReasonImsServiceDisconnected()
+ throws ImsException {
+ ImsStateCallback callback = setUpImsStateCallback();
+ callback.onAvailable();
+ callback.onUnavailable(ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED);
+
+ assertFalse(mImsStateTracker.isMmTelFeatureAvailable());
+ assertFalse(isImsStateUnavailable());
+ assertFalse(mImsStateTracker.isImsStateReady());
+
+ waitForHandlerActionDelayed(mImsStateTracker.getHandler(),
+ ImsStateTracker.MMTEL_FEATURE_AVAILABLE_WAIT_TIME_MILLIS,
+ ImsStateTracker.MMTEL_FEATURE_AVAILABLE_WAIT_TIME_MILLIS + TIMEOUT_MS);
+
+ assertFalse(mImsStateTracker.isMmTelFeatureAvailable());
+ assertTrue(isImsStateUnavailable());
+ assertTrue(mImsStateTracker.isImsStateReady());
+ verify(mMmTelManager).registerImsRegistrationCallback(
+ any(Executor.class), any(RegistrationManager.RegistrationCallback.class));
+ verify(mMmTelManager).registerMmTelCapabilityCallback(
+ any(Executor.class), any(ImsMmTelManager.CapabilityCallback.class));
+ verify(mMmTelManager).unregisterImsRegistrationCallback(
+ any(RegistrationManager.RegistrationCallback.class));
+ verify(mMmTelManager).unregisterMmTelCapabilityCallback(
+ any(ImsMmTelManager.CapabilityCallback.class));
+ verify(mImsStateListener, times(2)).onImsMmTelFeatureAvailableChanged();
+ }
+
+ @Test
+ public void testNotifyImsStateCallbackOnUnavailableAvailableWithReasonImsServiceDisconnected()
+ throws ImsException {
+ ImsStateCallback callback = setUpImsStateCallback();
+ callback.onUnavailable(ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED);
+ callback.onAvailable();
+
+ assertTrue(mImsStateTracker.isMmTelFeatureAvailable());
+ assertFalse(isImsStateUnavailable());
+ assertFalse(mImsStateTracker.isImsStateReady());
+
+ waitForHandlerActionDelayed(mImsStateTracker.getHandler(),
+ ImsStateTracker.MMTEL_FEATURE_AVAILABLE_WAIT_TIME_MILLIS,
+ ImsStateTracker.MMTEL_FEATURE_AVAILABLE_WAIT_TIME_MILLIS + TIMEOUT_MS);
+
+ assertTrue(mImsStateTracker.isMmTelFeatureAvailable());
+ assertFalse(isImsStateUnavailable());
+ assertFalse(mImsStateTracker.isImsStateReady());
+ verify(mMmTelManager).registerImsRegistrationCallback(
+ any(Executor.class), any(RegistrationManager.RegistrationCallback.class));
+ verify(mMmTelManager).registerMmTelCapabilityCallback(
+ any(Executor.class), any(ImsMmTelManager.CapabilityCallback.class));
+ verify(mMmTelManager, never()).unregisterImsRegistrationCallback(
+ any(RegistrationManager.RegistrationCallback.class));
+ verify(mMmTelManager, never()).unregisterMmTelCapabilityCallback(
+ any(ImsMmTelManager.CapabilityCallback.class));
+ verify(mImsStateListener).onImsMmTelFeatureAvailableChanged();
+ }
+
+ @Test
+ @SmallTest
+ public void testNotifyImsStateCallbackOnAvailableUnavailableWithReasonSubscriptionInactive()
+ throws ImsException {
+ ImsStateCallback callback = setUpImsStateCallback();
+ callback.onAvailable();
+ callback.onUnavailable(ImsStateCallback.REASON_SUBSCRIPTION_INACTIVE);
+
+ assertFalse(mImsStateTracker.isMmTelFeatureAvailable());
+ assertTrue(isImsStateUnavailable());
+ assertTrue(mImsStateTracker.isImsStateReady());
+ verify(mMmTelManager).registerImsRegistrationCallback(
+ any(Executor.class), any(RegistrationManager.RegistrationCallback.class));
+ verify(mMmTelManager).registerMmTelCapabilityCallback(
+ any(Executor.class), any(ImsMmTelManager.CapabilityCallback.class));
+ verify(mMmTelManager).unregisterImsRegistrationCallback(
+ any(RegistrationManager.RegistrationCallback.class));
+ verify(mMmTelManager).unregisterMmTelCapabilityCallback(
+ any(ImsMmTelManager.CapabilityCallback.class));
+ verify(mImsStateListener, times(2)).onImsMmTelFeatureAvailableChanged();
+ }
+
+ @Test
+ @SmallTest
+ public void testNotifyImsRegistrationCallbackOnRegistered() throws ImsException {
+ RegistrationManager.RegistrationCallback callback = setUpImsRegistrationCallback();
+ callback.onRegistered(new ImsRegistrationAttributes.Builder(
+ ImsRegistrationImplBase.REGISTRATION_TECH_LTE).build());
+
+ // It's false because the MMTEL capabilities are not updated yet.
+ assertFalse(mImsStateTracker.isImsStateReady());
+ assertTrue(mImsStateTracker.isImsRegistered());
+ assertFalse(mImsStateTracker.isImsRegisteredOverWlan());
+ assertEquals(AccessNetworkType.EUTRAN, mImsStateTracker.getImsAccessNetworkType());
+
+ callback.onRegistered(new ImsRegistrationAttributes.Builder(
+ ImsRegistrationImplBase.REGISTRATION_TECH_NR).build());
+
+ assertFalse(mImsStateTracker.isImsStateReady());
+ assertTrue(mImsStateTracker.isImsRegistered());
+ assertFalse(mImsStateTracker.isImsRegisteredOverWlan());
+ assertEquals(AccessNetworkType.NGRAN, mImsStateTracker.getImsAccessNetworkType());
+
+ callback.onRegistered(new ImsRegistrationAttributes.Builder(
+ ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN).build());
+
+ assertFalse(mImsStateTracker.isImsStateReady());
+ assertTrue(mImsStateTracker.isImsRegistered());
+ assertTrue(mImsStateTracker.isImsRegisteredOverWlan());
+ assertEquals(AccessNetworkType.IWLAN, mImsStateTracker.getImsAccessNetworkType());
+
+ callback.onRegistered(new ImsRegistrationAttributes.Builder(
+ ImsRegistrationImplBase.REGISTRATION_TECH_NONE).build());
+
+ assertFalse(mImsStateTracker.isImsStateReady());
+ assertTrue(mImsStateTracker.isImsRegistered());
+ assertFalse(mImsStateTracker.isImsRegisteredOverWlan());
+ assertEquals(AccessNetworkType.UNKNOWN, mImsStateTracker.getImsAccessNetworkType());
+
+ verify(mImsStateListener, times(4)).onImsRegistrationStateChanged();
+ }
+
+ @Test
+ @SmallTest
+ public void testNotifyImsRegistrationCallbackOnUnregistered() throws ImsException {
+ RegistrationManager.RegistrationCallback callback = setUpImsRegistrationCallback();
+ callback.onRegistered(new ImsRegistrationAttributes.Builder(
+ ImsRegistrationImplBase.REGISTRATION_TECH_LTE).build());
+
+ // It's false because the MMTEL capabilities are not updated yet.
+ assertFalse(mImsStateTracker.isImsStateReady());
+ assertTrue(mImsStateTracker.isImsRegistered());
+ assertFalse(mImsStateTracker.isImsRegisteredOverWlan());
+ assertEquals(AccessNetworkType.EUTRAN, mImsStateTracker.getImsAccessNetworkType());
+
+ callback.onUnregistered(new ImsReasonInfo(ImsReasonInfo.CODE_REGISTRATION_ERROR, 0, null));
+
+ // When IMS is unregistered, the MMTEL capability is also reset.
+ assertTrue(mImsStateTracker.isImsStateReady());
+ assertFalse(mImsStateTracker.isImsRegistered());
+ assertFalse(mImsStateTracker.isImsRegisteredOverWlan());
+ assertEquals(AccessNetworkType.UNKNOWN, mImsStateTracker.getImsAccessNetworkType());
+
+ verify(mImsStateListener, times(2)).onImsRegistrationStateChanged();
+ }
+
+ @Test
+ @SmallTest
+ public void testNotifyMmTelCapabilityCallbackOnCapabilitiesStatusChanged() throws ImsException {
+ ImsMmTelManager.CapabilityCallback callback = setUpMmTelCapabilityCallback();
+
+ assertFalse(mImsStateTracker.isImsVoiceCapable());
+ assertFalse(mImsStateTracker.isImsVideoCapable());
+ assertFalse(mImsStateTracker.isImsSmsCapable());
+ assertFalse(mImsStateTracker.isImsUtCapable());
+
+ MmTelCapabilities capabilities = new MmTelCapabilities(
+ MmTelCapabilities.CAPABILITY_TYPE_VOICE
+ | MmTelCapabilities.CAPABILITY_TYPE_VIDEO
+ | MmTelCapabilities.CAPABILITY_TYPE_SMS
+ | MmTelCapabilities.CAPABILITY_TYPE_UT
+ );
+ callback.onCapabilitiesStatusChanged(capabilities);
+
+ assertTrue(mImsStateTracker.isImsStateReady());
+ assertTrue(mImsStateTracker.isImsVoiceCapable());
+ assertTrue(mImsStateTracker.isImsVideoCapable());
+ assertTrue(mImsStateTracker.isImsSmsCapable());
+ assertTrue(mImsStateTracker.isImsUtCapable());
+
+ capabilities = new MmTelCapabilities();
+ callback.onCapabilitiesStatusChanged(capabilities);
+
+ assertTrue(mImsStateTracker.isImsStateReady());
+ assertFalse(mImsStateTracker.isImsVoiceCapable());
+ assertFalse(mImsStateTracker.isImsVideoCapable());
+ assertFalse(mImsStateTracker.isImsSmsCapable());
+ assertFalse(mImsStateTracker.isImsUtCapable());
+
+ verify(mImsStateListener, times(2)).onImsMmTelCapabilitiesChanged();
+ }
+
+ @Test
+ @SmallTest
+ public void testAddImsStateListenerWhenImsStateReady() throws ImsException {
+ ImsMmTelManager.CapabilityCallback callback = setUpMmTelCapabilityCallback();
+
+ MmTelCapabilities capabilities = new MmTelCapabilities(
+ MmTelCapabilities.CAPABILITY_TYPE_VOICE
+ | MmTelCapabilities.CAPABILITY_TYPE_VIDEO
+ | MmTelCapabilities.CAPABILITY_TYPE_SMS
+ | MmTelCapabilities.CAPABILITY_TYPE_UT
+ );
+ callback.onCapabilitiesStatusChanged(capabilities);
+
+ ImsStateTracker.ImsStateListener listener =
+ Mockito.mock(ImsStateTracker.ImsStateListener.class);
+ mImsStateTracker.addImsStateListener(listener);
+ waitForHandlerAction(mImsStateTracker.getHandler(), TIMEOUT_MS);
+
+ verify(listener).onImsMmTelFeatureAvailableChanged();
+ verify(listener).onImsRegistrationStateChanged();
+ verify(listener).onImsMmTelCapabilitiesChanged();
+ }
+
+ @Test
+ @SmallTest
+ public void testAddAndRemoveImsStateListenerWhenImsStateReady() throws ImsException {
+ ImsMmTelManager.CapabilityCallback callback = setUpMmTelCapabilityCallback();
+
+ MmTelCapabilities capabilities = new MmTelCapabilities(
+ MmTelCapabilities.CAPABILITY_TYPE_VOICE
+ | MmTelCapabilities.CAPABILITY_TYPE_VIDEO
+ | MmTelCapabilities.CAPABILITY_TYPE_SMS
+ | MmTelCapabilities.CAPABILITY_TYPE_UT
+ );
+ callback.onCapabilitiesStatusChanged(capabilities);
+
+ Handler handler = new Handler(mLooper);
+ ImsStateTracker.ImsStateListener listener =
+ Mockito.mock(ImsStateTracker.ImsStateListener.class);
+ handler.post(() -> {
+ mImsStateTracker.addImsStateListener(listener);
+ mImsStateTracker.removeImsStateListener(listener);
+ });
+ waitForHandlerAction(mImsStateTracker.getHandler(), TIMEOUT_MS);
+
+ verify(listener, never()).onImsMmTelFeatureAvailableChanged();
+ verify(listener, never()).onImsRegistrationStateChanged();
+ verify(listener, never()).onImsMmTelCapabilitiesChanged();
+ }
+
+ private ImsStateCallback setUpImsStateCallback() throws ImsException {
+ mImsStateTracker.start(SUB_1);
+ mImsStateTracker.addImsStateListener(mImsStateListener);
+ waitForHandlerAction(mImsStateTracker.getHandler(), TIMEOUT_MS);
+
+ assertEquals(SUB_1, mImsStateTracker.getSubId());
+ assertFalse(mImsStateTracker.isMmTelFeatureAvailable());
+ ArgumentCaptor<ImsStateCallback> callbackCaptor =
+ ArgumentCaptor.forClass(ImsStateCallback.class);
+ verify(mMmTelManager).registerImsStateCallback(
+ any(Executor.class), callbackCaptor.capture());
+
+ ImsStateCallback imsStateCallback = callbackCaptor.getValue();
+ assertNotNull(imsStateCallback);
+ return imsStateCallback;
+ }
+
+ private RegistrationManager.RegistrationCallback setUpImsRegistrationCallback()
+ throws ImsException {
+ ImsStateCallback imsStateCallback = setUpImsStateCallback();
+ imsStateCallback.onAvailable();
+
+ assertTrue(mImsStateTracker.isMmTelFeatureAvailable());
+ ArgumentCaptor<RegistrationManager.RegistrationCallback> callbackCaptor =
+ ArgumentCaptor.forClass(RegistrationManager.RegistrationCallback.class);
+ verify(mMmTelManager).registerImsRegistrationCallback(
+ any(Executor.class), callbackCaptor.capture());
+
+ RegistrationManager.RegistrationCallback registrationCallback = callbackCaptor.getValue();
+ assertNotNull(registrationCallback);
+ return registrationCallback;
+ }
+
+ private ImsMmTelManager.CapabilityCallback setUpMmTelCapabilityCallback()
+ throws ImsException {
+ RegistrationManager.RegistrationCallback registrationCallback =
+ setUpImsRegistrationCallback();
+ registrationCallback.onRegistered(new ImsRegistrationAttributes.Builder(
+ ImsRegistrationImplBase.REGISTRATION_TECH_LTE).build());
+
+ assertTrue(mImsStateTracker.isMmTelFeatureAvailable());
+ // It's false because the MMTEL capabilities are not updated.
+ assertFalse(mImsStateTracker.isImsStateReady());
+ assertTrue(mImsStateTracker.isImsRegistered());
+ assertFalse(mImsStateTracker.isImsRegisteredOverWlan());
+ assertEquals(AccessNetworkType.EUTRAN, mImsStateTracker.getImsAccessNetworkType());
+ ArgumentCaptor<ImsMmTelManager.CapabilityCallback> callbackCaptor =
+ ArgumentCaptor.forClass(ImsMmTelManager.CapabilityCallback.class);
+ verify(mMmTelManager).registerMmTelCapabilityCallback(
+ any(Executor.class), callbackCaptor.capture());
+
+ ImsMmTelManager.CapabilityCallback capabilityCallback = callbackCaptor.getValue();
+ assertNotNull(capabilityCallback);
+ return capabilityCallback;
+ }
+
+ private boolean isImsStateUnavailable() {
+ return mImsStateTracker.isImsStateReady()
+ && !mImsStateTracker.isImsRegistered()
+ && !mImsStateTracker.isMmTelFeatureAvailable()
+ && !mImsStateTracker.isImsVoiceCapable()
+ && !mImsStateTracker.isImsVideoCapable()
+ && !mImsStateTracker.isImsSmsCapable()
+ && !mImsStateTracker.isImsUtCapable()
+ && (AccessNetworkType.UNKNOWN == mImsStateTracker.getImsAccessNetworkType());
+ }
+
+ private boolean isImsStateInit() {
+ return !mImsStateTracker.isImsStateReady()
+ && !mImsStateTracker.isImsRegistered()
+ && !mImsStateTracker.isMmTelFeatureAvailable()
+ && !mImsStateTracker.isImsVoiceCapable()
+ && !mImsStateTracker.isImsVideoCapable()
+ && !mImsStateTracker.isImsSmsCapable()
+ && !mImsStateTracker.isImsUtCapable()
+ && (AccessNetworkType.UNKNOWN == mImsStateTracker.getImsAccessNetworkType());
+ }
+
+ private void waitForHandlerAction(Handler h, long timeoutMillis) {
+ waitForHandlerActionDelayed(h, 0, timeoutMillis);
+ }
+
+ private void waitForHandlerActionDelayed(Handler h, long delayMillis, long timeoutMillis) {
+ final CountDownLatch lock = new CountDownLatch(1);
+ h.postDelayed(lock::countDown, delayMillis);
+ while (lock.getCount() > 0) {
+ try {
+ lock.await(timeoutMillis, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+ }
+ }
+}
diff --git a/tests/src/com/android/services/telephony/domainselection/NormalCallDomainSelectorTest.java b/tests/src/com/android/services/telephony/domainselection/NormalCallDomainSelectorTest.java
new file mode 100644
index 0000000..832e480
--- /dev/null
+++ b/tests/src/com/android/services/telephony/domainselection/NormalCallDomainSelectorTest.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.DomainSelectionService.SELECTOR_TYPE_CALLING;
+import static android.telephony.DomainSelectionService.SELECTOR_TYPE_UT;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.DisconnectCause;
+import android.telephony.DomainSelectionService;
+import android.telephony.DomainSelector;
+import android.telephony.EmergencyRegResult;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+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.util.Log;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+
+/**
+ * Unit tests for DomainSelectorBase.
+ */
+@RunWith(AndroidJUnit4.class)
+public class NormalCallDomainSelectorTest {
+ private static final String TAG = "NormalCallDomainSelectorTest";
+
+ private static final int SLOT_ID = 0;
+ private static final int SUB_ID_1 = 1;
+ private static final int SUB_ID_2 = 2;
+ private static final String TEST_CALLID = "01234";
+
+ private HandlerThread mHandlerThread;
+ private NormalCallDomainSelector mNormalCallDomainSelector;
+
+ @Mock private Context mMockContext;
+ @Mock private ImsManager mMockImsManager;
+ @Mock private ImsMmTelManager mMockMmTelManager;
+ @Mock private ServiceState mMockServiceState;
+ @Mock private ImsStateTracker mMockImsStateTracker;
+ @Mock private DomainSelectorBase.DestroyListener mMockDestroyListener;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ doReturn(Context.TELEPHONY_IMS_SERVICE).when(mMockContext)
+ .getSystemServiceName(ImsManager.class);
+ doReturn(mMockImsManager).when(mMockContext)
+ .getSystemService(Context.TELEPHONY_IMS_SERVICE);
+ doReturn(mMockMmTelManager).when(mMockImsManager).getImsMmTelManager(SUB_ID_1);
+ doReturn(mMockMmTelManager).when(mMockImsManager).getImsMmTelManager(SUB_ID_2);
+ doNothing().when(mMockImsStateTracker).removeServiceStateListener(any());
+ doNothing().when(mMockImsStateTracker).removeImsStateListener(any());
+ doReturn(true).when(mMockImsStateTracker).isMmTelFeatureAvailable();
+
+ // Set up the looper if it does not exist on the test thread.
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ mHandlerThread = new HandlerThread(
+ NormalCallDomainSelectorTest.class.getSimpleName());
+ mHandlerThread.start();
+
+ mNormalCallDomainSelector = new NormalCallDomainSelector(mMockContext, SLOT_ID, SUB_ID_1,
+ mHandlerThread.getLooper(), mMockImsStateTracker, mMockDestroyListener);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mHandlerThread != null) {
+ mHandlerThread.quit();
+ }
+ }
+
+ private void initialize(ServiceState serviceState, boolean isImsRegistered,
+ boolean isImsRegisteredOverWlan, boolean isImsVoiceCapable,
+ boolean isImsVideoCapable) {
+ if (serviceState != null) mNormalCallDomainSelector.onServiceStateUpdated(serviceState);
+ doReturn(isImsRegistered).when(mMockImsStateTracker).isImsStateReady();
+ doReturn(isImsRegistered).when(mMockImsStateTracker).isImsRegistered();
+ doReturn(isImsVoiceCapable).when(mMockImsStateTracker).isImsVoiceCapable();
+ doReturn(isImsVideoCapable).when(mMockImsStateTracker).isImsVideoCapable();
+ doReturn(isImsRegisteredOverWlan).when(mMockImsStateTracker).isImsRegisteredOverWlan();
+ mNormalCallDomainSelector.onImsRegistrationStateChanged();
+ mNormalCallDomainSelector.onImsMmTelCapabilitiesChanged();
+ }
+
+ @Test
+ public void testInit() {
+ assertEquals(SLOT_ID, mNormalCallDomainSelector.getSlotId());
+ assertEquals(SUB_ID_1, mNormalCallDomainSelector.getSubId());
+ }
+
+ @Test
+ public void testSelectDomainInputParams() {
+ MockTransportSelectorCallback transportSelectorCallback =
+ new MockTransportSelectorCallback();
+
+ DomainSelectionService.SelectionAttributes attributes =
+ new DomainSelectionService.SelectionAttributes.Builder(
+ SLOT_ID, SUB_ID_1, SELECTOR_TYPE_CALLING)
+ .setCallId(TEST_CALLID)
+ .setEmergency(false)
+ .setVideoCall(true)
+ .setExitedFromAirplaneMode(false)
+ .build();
+ mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+
+
+ // Case 1: null inputs
+ try {
+ mNormalCallDomainSelector.selectDomain(null, null);
+ } catch (Exception e) {
+ fail("Invalid input params not handled.");
+ }
+
+ // Case 2: null TransportSelectorCallback
+ try {
+ mNormalCallDomainSelector.selectDomain(attributes, null);
+ } catch (Exception e) {
+ fail("Invalid params (SelectionAttributes) not handled.");
+ }
+
+ // Case 3: null SelectionAttributes
+ transportSelectorCallback.mSelectionTerminated = false;
+ try {
+ mNormalCallDomainSelector.selectDomain(null, transportSelectorCallback);
+ } catch (Exception e) {
+ fail("Invalid params (SelectionAttributes) not handled.");
+ }
+
+ assertTrue(transportSelectorCallback
+ .verifyOnSelectionTerminated(DisconnectCause.OUTGOING_FAILURE));
+
+ // Case 4: Invalid Subscription-id
+ attributes = new DomainSelectionService.SelectionAttributes.Builder(
+ SLOT_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID, SELECTOR_TYPE_CALLING)
+ .setCallId(TEST_CALLID)
+ .setEmergency(false)
+ .setVideoCall(true)
+ .setExitedFromAirplaneMode(false)
+ .build();
+ try {
+ mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+ } catch (Exception e) {
+ fail("Invalid params (SelectionAttributes) not handled.");
+ }
+
+ assertTrue(transportSelectorCallback
+ .verifyOnSelectionTerminated(DisconnectCause.OUTGOING_FAILURE));
+
+ // Case 5: Invalid SELECTOR_TYPE
+ attributes =
+ new DomainSelectionService.SelectionAttributes.Builder(
+ SLOT_ID, SUB_ID_1, SELECTOR_TYPE_UT)
+ .setCallId(TEST_CALLID)
+ .setEmergency(false)
+ .setVideoCall(true)
+ .setExitedFromAirplaneMode(false)
+ .build();
+ try {
+ mNormalCallDomainSelector.selectDomain(null, transportSelectorCallback);
+ } catch (Exception e) {
+ fail("Invalid params (SelectionAttributes) not handled.");
+ }
+
+ assertTrue(transportSelectorCallback
+ .verifyOnSelectionTerminated(DisconnectCause.OUTGOING_FAILURE));
+
+ // Case 6: Emergency Call
+ attributes = new DomainSelectionService.SelectionAttributes.Builder(
+ SLOT_ID, SUB_ID_1, SELECTOR_TYPE_UT)
+ .setCallId(TEST_CALLID)
+ .setEmergency(true)
+ .setVideoCall(true)
+ .setExitedFromAirplaneMode(false)
+ .build();
+ try {
+ mNormalCallDomainSelector.selectDomain(null, transportSelectorCallback);
+ } catch (Exception e) {
+ fail("Invalid params (SelectionAttributes) not handled.");
+ }
+
+ assertTrue(transportSelectorCallback
+ .verifyOnSelectionTerminated(DisconnectCause.OUTGOING_FAILURE));
+ }
+
+ @Test
+ public void testOutOfService() {
+ MockTransportSelectorCallback transportSelectorCallback =
+ new MockTransportSelectorCallback();
+ DomainSelectionService.SelectionAttributes attributes =
+ new DomainSelectionService.SelectionAttributes.Builder(
+ SLOT_ID, SUB_ID_1, SELECTOR_TYPE_CALLING)
+ .setCallId(TEST_CALLID)
+ .setEmergency(false)
+ .setVideoCall(true)
+ .setExitedFromAirplaneMode(false)
+ .build();
+ mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+ ServiceState serviceState = new ServiceState();
+ serviceState.setStateOutOfService();
+ mNormalCallDomainSelector.onServiceStateUpdated(serviceState);
+ assertTrue(transportSelectorCallback
+ .verifyOnSelectionTerminated(DisconnectCause.OUT_OF_SERVICE));
+ }
+
+ @Test
+ public void testDomainSelection() {
+ MockTransportSelectorCallback transportSelectorCallback =
+ new MockTransportSelectorCallback();
+ DomainSelectionService.SelectionAttributes attributes =
+ new DomainSelectionService.SelectionAttributes.Builder(
+ SLOT_ID, SUB_ID_1, SELECTOR_TYPE_CALLING)
+ .setCallId(TEST_CALLID)
+ .setEmergency(false)
+ .setVideoCall(false)
+ .setExitedFromAirplaneMode(false)
+ .build();
+
+ // Case 1: WLAN
+ ServiceState serviceState = new ServiceState();
+ serviceState.setState(ServiceState.STATE_IN_SERVICE);
+ initialize(serviceState, true, true, true, true);
+ mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+ assertTrue(transportSelectorCallback.verifyOnWlanSelected());
+
+ // Case 2: 5G
+ mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+ initialize(serviceState, true, false, true, true);
+ mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+ assertTrue(transportSelectorCallback.verifyOnWwanSelected());
+ assertTrue(transportSelectorCallback
+ .verifyOnDomainSelected(NetworkRegistrationInfo.DOMAIN_PS));
+
+ // Case 3: PS -> CS redial
+ ImsReasonInfo imsReasonInfo = new ImsReasonInfo();
+ imsReasonInfo.mCode = ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED;
+ attributes = new DomainSelectionService.SelectionAttributes.Builder(
+ SLOT_ID, SUB_ID_1, SELECTOR_TYPE_CALLING)
+ .setCallId(TEST_CALLID)
+ .setEmergency(false)
+ .setVideoCall(false)
+ .setExitedFromAirplaneMode(false)
+ .setPsDisconnectCause(imsReasonInfo)
+ .build();
+ mNormalCallDomainSelector.reselectDomain(attributes);
+ assertTrue(transportSelectorCallback
+ .verifyOnDomainSelected(NetworkRegistrationInfo.DOMAIN_CS));
+
+ // Case 4: CS call
+ NetworkRegistrationInfo nwRegistrationInfo = new NetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+ NetworkRegistrationInfo.REGISTRATION_STATE_HOME,
+ AccessNetworkConstants.AccessNetworkType.UTRAN, 0, false,
+ null, null, null, false, 0, 0, 0);
+ serviceState.addNetworkRegistrationInfo(nwRegistrationInfo);
+ mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+ initialize(serviceState, false, false, false, false);
+ mNormalCallDomainSelector.selectDomain(attributes, transportSelectorCallback);
+ assertTrue(transportSelectorCallback.verifyOnWwanSelected());
+ assertTrue(transportSelectorCallback
+ .verifyOnDomainSelected(NetworkRegistrationInfo.DOMAIN_CS));
+ }
+
+ class MockTransportSelectorCallback implements TransportSelectorCallback, WwanSelectorCallback {
+ public boolean mCreated = false;
+ public boolean mWlanSelected = false;
+ public boolean mWwanSelected = false;
+ public boolean mSelectionTerminated = false;
+ int mCauseCode = 0;
+ int mSelectedDomain = 0;
+
+ @Override
+ public synchronized void onCreated(DomainSelector selector) {
+ Log.d(TAG, "onCreated");
+ mCreated = true;
+ notifyAll();
+ }
+
+ public boolean verifyOnCreated() {
+ mCreated = false;
+ Log.d(TAG, "verifyOnCreated");
+ waitForCallback();
+ return mCreated;
+ }
+
+ @Override
+ public synchronized void onWlanSelected() {
+ Log.d(TAG, "onWlanSelected");
+ mWlanSelected = true;
+ notifyAll();
+ }
+
+ public boolean verifyOnWlanSelected() {
+ Log.d(TAG, "verifyOnWlanSelected");
+ waitForCallback();
+ return mWlanSelected;
+ }
+
+ @Override
+ public synchronized WwanSelectorCallback onWwanSelected() {
+ mWwanSelected = true;
+ notifyAll();
+ return (WwanSelectorCallback) this;
+ }
+
+ @Override
+ public void onWwanSelected(final Consumer<WwanSelectorCallback> consumer) {
+ mWwanSelected = true;
+ Executors.newSingleThreadExecutor().execute(() -> {
+ consumer.accept(this);
+ });
+ }
+
+ public boolean verifyOnWwanSelected() {
+ waitForCallback();
+ return mWwanSelected;
+ }
+
+ @Override
+ public synchronized void onSelectionTerminated(int cause) {
+ Log.i(TAG, "onSelectionTerminated - called");
+ mCauseCode = cause;
+ mSelectionTerminated = true;
+ notifyAll();
+ }
+
+ public boolean verifyOnSelectionTerminated(int cause) {
+ Log.i(TAG, "verifyOnSelectionTerminated - called");
+ if (!mSelectionTerminated) {
+ waitForCallback();
+ }
+ return (mSelectionTerminated && cause == mCauseCode);
+ }
+
+ private synchronized void waitForCallback() {
+ try {
+ wait(1000);
+ } catch (Exception e) {
+ return;
+ }
+ }
+
+ @Override
+ public void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
+ int scanType,
+ @NonNull CancellationSignal signal,
+ @NonNull Consumer<EmergencyRegResult> consumer) {
+ Log.i(TAG, "onRequestEmergencyNetworkScan - called");
+
+ }
+
+ public synchronized void onDomainSelected(@NetworkRegistrationInfo.Domain int domain) {
+ Log.i(TAG, "onDomainSelected - called");
+ mSelectedDomain = domain;
+ notifyAll();
+ }
+
+ public boolean verifyOnDomainSelected(int domain) {
+ Log.i(TAG, "verifyOnDomainSelected - called");
+ waitForCallback();
+ return (domain == mSelectedDomain);
+ }
+ }
+}
diff --git a/tests/src/com/android/services/telephony/domainselection/SmsDomainSelectorTest.java b/tests/src/com/android/services/telephony/domainselection/SmsDomainSelectorTest.java
new file mode 100644
index 0000000..412d83d
--- /dev/null
+++ b/tests/src/com/android/services/telephony/domainselection/SmsDomainSelectorTest.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.DomainSelectionService.SELECTOR_TYPE_SMS;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.TransportSelectorCallback;
+import android.telephony.WwanSelectorCallback;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableLooper;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.TestContext;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Consumer;
+
+/**
+ * Unit tests for SmsDomainSelector.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SmsDomainSelectorTest {
+ private static final String LOG_TAG = "DomainSelector-SMS";
+ private static final int SLOT_0 = 0;
+ private static final int SUB_1 = 1;
+ private static final int SUB_2 = 2;
+
+ @Mock private TransportSelectorCallback mTransportSelectorCallback;
+ @Mock private WwanSelectorCallback mWwanSelectorCallback;
+ @Mock private ImsStateTracker mImsStateTracker;
+ @Mock private DomainSelectorBase.DestroyListener mDomainSelectorDestroyListener;
+
+ private final SelectionAttributes mSelectionAttributes =
+ new SelectionAttributes.Builder(SLOT_0, SUB_1, SELECTOR_TYPE_SMS).build();
+ private Context mContext;
+ private Looper mLooper;
+ private TestableLooper mTestableLooper;
+ private SmsDomainSelector mDomainSelector;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext = new TestContext();
+ HandlerThread handlerThread = new HandlerThread(
+ SmsDomainSelectorTest.class.getSimpleName());
+ handlerThread.start();
+ mLooper = handlerThread.getLooper();
+ mTestableLooper = new TestableLooper(mLooper);
+ mDomainSelector = new SmsDomainSelector(mContext, SLOT_0, SUB_1,
+ mLooper, mImsStateTracker, mDomainSelectorDestroyListener);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mTestableLooper != null) {
+ mTestableLooper.destroy();
+ mTestableLooper = null;
+ }
+
+ if (mDomainSelector != null) {
+ mDomainSelector.destroy();
+ verify(mImsStateTracker).removeImsStateListener(eq(mDomainSelector));
+ }
+
+ if (mLooper != null) {
+ mLooper.quit();
+ mLooper = null;
+ }
+
+ mDomainSelector = null;
+ mWwanSelectorCallback = null;
+ mTransportSelectorCallback = null;
+ mImsStateTracker = null;
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenImsRegisteredOnEutran() {
+ selectDomain(AccessNetworkType.EUTRAN);
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenImsRegisteredOnNgran() {
+ selectDomain(AccessNetworkType.NGRAN);
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenImsRegisteredOnIwlan() {
+ selectDomain(AccessNetworkType.IWLAN);
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenImsNotRegistered() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpImsStateListener(true, false, false);
+ setUpWwanSelectorCallback();
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+ assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+ processAllMessages();
+
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+ assertFalse(mDomainSelector.isDomainSelectionRequested());
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhenWwanSelectorCallbackNull() {
+ setUpImsStateTracker(AccessNetworkType.EUTRAN);
+ doAnswer((invocation) -> {
+ Object[] args = invocation.getArguments();
+ final Consumer<WwanSelectorCallback> callback =
+ (Consumer<WwanSelectorCallback>) args[0];
+ callback.accept(null);
+ return null;
+ }).when(mTransportSelectorCallback).onWwanSelected(any(Consumer.class));
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+ assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+ processAllMessages();
+
+ verify(mTransportSelectorCallback).onSelectionTerminated(anyInt());
+ assertFalse(mDomainSelector.isDomainSelectionRequested());
+ }
+
+ @Test
+ @SmallTest
+ public void testSelectDomainWhilePreviousRequestInProgress() {
+ setUpImsStateTracker(AccessNetworkType.EUTRAN);
+ setUpWwanSelectorCallback();
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+ assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+ processAllMessages();
+
+ // onDomainSelected will be invoked only once
+ // even though the domain selection was requested twice.
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+ assertFalse(mDomainSelector.isDomainSelectionRequested());
+ }
+
+ @Test
+ @SmallTest
+ public void testCancelSelection() {
+ setUpImsStateTracker(AccessNetworkType.EUTRAN);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+ assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+ mDomainSelector.cancelSelection();
+
+ assertFalse(mDomainSelector.isDomainSelectionRequested());
+ verify(mDomainSelectorDestroyListener).onDomainSelectorDestroyed(eq(mDomainSelector));
+ }
+
+ @Test
+ @SmallTest
+ public void testFinishSelection() {
+ setUpImsStateTracker(AccessNetworkType.EUTRAN);
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+ assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+ mDomainSelector.finishSelection();
+
+ assertFalse(mDomainSelector.isDomainSelectionRequested());
+ verify(mDomainSelectorDestroyListener).onDomainSelectorDestroyed(eq(mDomainSelector));
+ }
+
+ @Test
+ @SmallTest
+ public void testReselectDomain() {
+ setUpImsStateTracker(AccessNetworkType.EUTRAN, AccessNetworkType.IWLAN);
+ setUpWwanSelectorCallback();
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+ assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+ processAllMessages();
+
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+ assertFalse(mDomainSelector.isDomainSelectionRequested());
+
+ mDomainSelector.reselectDomain(mSelectionAttributes);
+
+ assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+ processAllMessages();
+
+ verify(mTransportSelectorCallback).onWlanSelected();
+ assertFalse(mDomainSelector.isDomainSelectionRequested());
+ }
+
+ @Test
+ @SmallTest
+ public void testReselectDomainWhilePreviousRequestInProgress() {
+ setUpImsStateTracker(AccessNetworkType.EUTRAN, AccessNetworkType.IWLAN);
+ setUpWwanSelectorCallback();
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+ assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+ mDomainSelector.reselectDomain(mSelectionAttributes);
+ processAllMessages();
+
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+ assertFalse(mDomainSelector.isDomainSelectionRequested());
+ verify(mTransportSelectorCallback, never()).onWlanSelected();
+ assertFalse(mDomainSelector.isDomainSelectionRequested());
+ }
+
+ @Test
+ @SmallTest
+ public void testOnImsRegistrationStateChangedWhenNotRegistered() {
+ setUpImsStateTracker(AccessNetworkType.UNKNOWN);
+ setUpImsStateListener(false, true, false);
+ setUpWwanSelectorCallback();
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+ assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+ processAllMessages();
+
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+ assertFalse(mDomainSelector.isDomainSelectionRequested());
+ }
+
+ @Test
+ @SmallTest
+ public void testOnImsRegistrationStateChangedWhenRegisteredAndSmsCapable() {
+ setUpImsStateTracker(AccessNetworkType.EUTRAN, true);
+ setUpImsStateListener(false, true, false);
+ setUpWwanSelectorCallback();
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+ assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+ processAllMessages();
+
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+ assertFalse(mDomainSelector.isDomainSelectionRequested());
+ }
+
+ @Test
+ @SmallTest
+ public void testOnImsRegistrationStateChangedWhenRegisteredAndSmsIncapable() {
+ setUpImsStateTracker(AccessNetworkType.EUTRAN, false);
+ setUpImsStateListener(false, true, false);
+ setUpWwanSelectorCallback();
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+ assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+ processAllMessages();
+
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+ assertFalse(mDomainSelector.isDomainSelectionRequested());
+ }
+
+ @Test
+ @SmallTest
+ public void testOnImsMmTelCapabilitiesChangedWhenSmsCapable() {
+ setUpImsStateTracker(AccessNetworkType.EUTRAN, true);
+ setUpImsStateListener(false, false, true);
+ setUpWwanSelectorCallback();
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+ assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+ processAllMessages();
+
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+ assertFalse(mDomainSelector.isDomainSelectionRequested());
+ }
+
+ @Test
+ @SmallTest
+ public void testOnImsMmTelCapabilitiesChangedWhenSmsIncapable() {
+ setUpImsStateTracker(AccessNetworkType.EUTRAN, false);
+ setUpImsStateListener(false, false, true);
+ setUpWwanSelectorCallback();
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+ assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+ processAllMessages();
+
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_CS));
+ assertFalse(mDomainSelector.isDomainSelectionRequested());
+ }
+
+ private void selectDomain(@RadioAccessNetworkType int accessNetworkType) {
+ setUpImsStateTracker(accessNetworkType);
+ setUpWwanSelectorCallback();
+
+ mDomainSelector.selectDomain(mSelectionAttributes, mTransportSelectorCallback);
+
+ assertTrue(mDomainSelector.isDomainSelectionRequested());
+
+ processAllMessages();
+
+ if (accessNetworkType == AccessNetworkType.IWLAN) {
+ verify(mTransportSelectorCallback).onWlanSelected();
+ } else {
+ verify(mWwanSelectorCallback).onDomainSelected(eq(NetworkRegistrationInfo.DOMAIN_PS));
+ }
+ assertFalse(mDomainSelector.isDomainSelectionRequested());
+ }
+
+ private void setUpImsStateTracker(@RadioAccessNetworkType int accessNetworkType) {
+ setUpImsStateTracker(accessNetworkType, true);
+ }
+
+ private void setUpImsStateTracker(@RadioAccessNetworkType int accessNetworkType,
+ boolean smsCapable) {
+ when(mImsStateTracker.isMmTelFeatureAvailable()).thenReturn(true);
+ when(mImsStateTracker.isImsRegistered())
+ .thenReturn(accessNetworkType != AccessNetworkType.UNKNOWN);
+ when(mImsStateTracker.isImsRegisteredOverWlan())
+ .thenReturn(accessNetworkType == AccessNetworkType.IWLAN);
+ when(mImsStateTracker.getImsAccessNetworkType()).thenReturn(accessNetworkType);
+ when(mImsStateTracker.isImsSmsCapable()).thenReturn(smsCapable);
+ }
+
+ private void setUpImsStateTracker(@RadioAccessNetworkType int firstAccessNetworkType,
+ @RadioAccessNetworkType int secondAccessNetworkType) {
+ when(mImsStateTracker.isMmTelFeatureAvailable()).thenReturn(true);
+ when(mImsStateTracker.isImsRegistered()).thenReturn(
+ firstAccessNetworkType != AccessNetworkType.UNKNOWN,
+ secondAccessNetworkType != AccessNetworkType.UNKNOWN);
+ when(mImsStateTracker.isImsRegisteredOverWlan()).thenReturn(
+ firstAccessNetworkType == AccessNetworkType.IWLAN,
+ secondAccessNetworkType == AccessNetworkType.IWLAN);
+ when(mImsStateTracker.getImsAccessNetworkType()).thenReturn(
+ firstAccessNetworkType,
+ secondAccessNetworkType);
+ when(mImsStateTracker.isImsSmsCapable()).thenReturn(true);
+ }
+
+ private void setUpWwanSelectorCallback() {
+ doAnswer((invocation) -> {
+ Object[] args = invocation.getArguments();
+ final Consumer<WwanSelectorCallback> callback =
+ (Consumer<WwanSelectorCallback>) args[0];
+ callback.accept(mWwanSelectorCallback);
+ return null;
+ }).when(mTransportSelectorCallback).onWwanSelected(any(Consumer.class));
+ }
+
+ private void setUpImsStateListener(boolean notifyMmTelFeatureAvailable,
+ boolean notifyImsRegState, boolean notifyMmTelCapability) {
+ doAnswer((invocation) -> {
+ Object[] args = invocation.getArguments();
+ final ImsStateTracker.ImsStateListener listener =
+ (ImsStateTracker.ImsStateListener) args[0];
+ mDomainSelector.post(() -> {
+ if (notifyMmTelFeatureAvailable) {
+ listener.onImsMmTelFeatureAvailableChanged();
+ }
+ if (notifyImsRegState) {
+ listener.onImsRegistrationStateChanged();
+ }
+ if (notifyMmTelCapability) {
+ listener.onImsMmTelCapabilitiesChanged();
+ }
+ });
+ return null;
+ }).when(mImsStateTracker).addImsStateListener(any(ImsStateTracker.ImsStateListener.class));
+ }
+
+ private void processAllMessages() {
+ while (!mTestableLooper.getLooper().getQueue().isIdle()) {
+ mTestableLooper.processAllMessages();
+ }
+ }
+}
diff --git a/tests/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionServiceTest.java b/tests/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionServiceTest.java
new file mode 100644
index 0000000..ace59e3
--- /dev/null
+++ b/tests/src/com/android/services/telephony/domainselection/TelephonyDomainSelectionServiceTest.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.telephony.BarringInfo;
+import android.telephony.DomainSelectionService;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.DomainSelectionService.SelectorType;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.TransportSelectorCallback;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableLooper;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.TestContext;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Unit tests for TelephonyDomainSelectionService.
+ */
+@RunWith(AndroidJUnit4.class)
+public class TelephonyDomainSelectionServiceTest {
+ private TelephonyDomainSelectionService.ImsStateTrackerFactory mImsStateTrackerFactory =
+ new TelephonyDomainSelectionService.ImsStateTrackerFactory() {
+ @Override
+ public ImsStateTracker create(Context context, int slotId,
+ @NonNull Looper looper) {
+ return mImsStateTracker;
+ }
+ };
+ private TelephonyDomainSelectionService.DomainSelectorFactory mDomainSelectorFactory =
+ new TelephonyDomainSelectionService.DomainSelectorFactory() {
+ @Override
+ public DomainSelectorBase create(Context context, int slotId, int subId,
+ @SelectorType int selectorType, boolean isEmergency,
+ @NonNull Looper looper, @NonNull ImsStateTracker imsStateTracker,
+ @NonNull DomainSelectorBase.DestroyListener listener) {
+ switch (selectorType) {
+ case DomainSelectionService.SELECTOR_TYPE_CALLING: // fallthrough
+ case DomainSelectionService.SELECTOR_TYPE_SMS: // fallthrough
+ case DomainSelectionService.SELECTOR_TYPE_UT:
+ mDomainSelectorDestroyListener = listener;
+ if (subId == SUB_1) {
+ return mDomainSelectorBase1;
+ } else {
+ return mDomainSelectorBase2;
+ }
+ default:
+ return null;
+ }
+ }
+ };
+ private static final int SLOT_0 = 0;
+ private static final int SUB_1 = 1;
+ private static final int SUB_2 = 2;
+ private static final String CALL_ID = "Call_1";
+ private static final @SelectorType int TEST_SELECTOR_TYPE =
+ DomainSelectionService.SELECTOR_TYPE_CALLING;
+ private static final @SelectorType int INVALID_SELECTOR_TYPE = -1;
+
+ @Mock private DomainSelectorBase mDomainSelectorBase1;
+ @Mock private DomainSelectorBase mDomainSelectorBase2;
+ @Mock private TransportSelectorCallback mSelectorCallback1;
+ @Mock private TransportSelectorCallback mSelectorCallback2;
+ @Mock private ImsStateTracker mImsStateTracker;
+
+ private final ServiceState mServiceState = new ServiceState();
+ private final BarringInfo mBarringInfo = new BarringInfo();
+ private Context mContext;
+ private Handler mServiceHandler;
+ private TestableLooper mTestableLooper;
+ private SubscriptionManager mSubscriptionManager;
+ private OnSubscriptionsChangedListener mOnSubscriptionsChangedListener;
+ private DomainSelectorBase.DestroyListener mDomainSelectorDestroyListener;
+ private TelephonyDomainSelectionService mDomainSelectionService;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ mContext = new TestContext();
+ mDomainSelectionService = new TelephonyDomainSelectionService(mContext,
+ mImsStateTrackerFactory, mDomainSelectorFactory);
+ mServiceHandler = new Handler(mDomainSelectionService.getLooper());
+ mTestableLooper = new TestableLooper(mDomainSelectionService.getLooper());
+
+ mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
+ ArgumentCaptor<OnSubscriptionsChangedListener> listenerCaptor =
+ ArgumentCaptor.forClass(OnSubscriptionsChangedListener.class);
+ verify(mSubscriptionManager).addOnSubscriptionsChangedListener(
+ any(Executor.class), listenerCaptor.capture());
+ mOnSubscriptionsChangedListener = listenerCaptor.getValue();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mTestableLooper != null) {
+ mTestableLooper.destroy();
+ mTestableLooper = null;
+ }
+ mServiceHandler = null;
+
+ if (mDomainSelectionService != null) {
+ mDomainSelectionService.onDestroy();
+ mDomainSelectionService = null;
+ }
+
+ mDomainSelectorBase1 = null;
+ mDomainSelectorBase2 = null;
+ mSelectorCallback1 = null;
+ mSelectorCallback2 = null;
+ mImsStateTracker = null;
+ mSubscriptionManager = null;
+ mOnSubscriptionsChangedListener = null;
+ mDomainSelectorDestroyListener = null;
+ }
+
+ @Test
+ @SmallTest
+ public void testGetExecutor() {
+ assertNotNull(mDomainSelectionService.getExecutor());
+ }
+
+ @Test
+ @SmallTest
+ public void testOnDomainSelection() {
+ SelectionAttributes attr1 = new SelectionAttributes.Builder(
+ SLOT_0, SUB_1, TEST_SELECTOR_TYPE)
+ .setCallId(CALL_ID)
+ .setEmergency(true)
+ .build();
+ mServiceHandler.post(() -> {
+ mDomainSelectionService.onDomainSelection(attr1, mSelectorCallback1);
+ });
+ processAllMessages();
+
+ verify(mImsStateTracker).start(eq(SUB_1));
+ verify(mSelectorCallback1).onCreated(eq(mDomainSelectorBase1));
+ verifyNoMoreInteractions(mSelectorCallback1);
+ verify(mDomainSelectorBase1).selectDomain(eq(attr1), eq(mSelectorCallback1));
+ }
+
+ @Test
+ @SmallTest
+ public void testOnDomainSelectionWithInvalidSelectorType() {
+ SelectionAttributes attr1 = new SelectionAttributes.Builder(
+ SLOT_0, SUB_1, INVALID_SELECTOR_TYPE)
+ .setCallId(CALL_ID)
+ .setEmergency(true)
+ .build();
+ mServiceHandler.post(() -> {
+ mDomainSelectionService.onDomainSelection(attr1, mSelectorCallback1);
+ });
+ processAllMessages();
+
+ verify(mImsStateTracker, never()).start(anyInt());
+ verify(mSelectorCallback1).onSelectionTerminated(anyInt());
+ verifyNoMoreInteractions(mSelectorCallback1);
+ verify(mDomainSelectorBase1, never()).selectDomain(eq(attr1), eq(mSelectorCallback1));
+ }
+
+ @Test
+ @SmallTest
+ public void testOnDomainSelectionTwiceWithDestroy() {
+ SelectionAttributes attr1 = new SelectionAttributes.Builder(
+ SLOT_0, SUB_1, TEST_SELECTOR_TYPE)
+ .setCallId(CALL_ID)
+ .setEmergency(true)
+ .build();
+ mServiceHandler.post(() -> {
+ mDomainSelectionService.onDomainSelection(attr1, mSelectorCallback1);
+ });
+ processAllMessages();
+
+ verify(mImsStateTracker).start(eq(SUB_1));
+ verify(mSelectorCallback1).onCreated(eq(mDomainSelectorBase1));
+ verifyNoMoreInteractions(mSelectorCallback1);
+ verify(mDomainSelectorBase1).selectDomain(eq(attr1), eq(mSelectorCallback1));
+
+ // Notify the domain selection service that this domain selector is destroyed.
+ mDomainSelectorDestroyListener.onDomainSelectorDestroyed(mDomainSelectorBase1);
+
+ SelectionAttributes attr2 = new SelectionAttributes.Builder(
+ SLOT_0, SUB_2, TEST_SELECTOR_TYPE)
+ .setCallId(CALL_ID)
+ .setEmergency(true)
+ .build();
+ mServiceHandler.post(() -> {
+ mDomainSelectionService.onDomainSelection(attr2, mSelectorCallback2);
+ });
+ processAllMessages();
+
+ verify(mImsStateTracker).start(eq(SUB_2));
+ verify(mSelectorCallback2).onCreated(eq(mDomainSelectorBase2));
+ verifyNoMoreInteractions(mSelectorCallback2);
+ verify(mDomainSelectorBase2).selectDomain(eq(attr2), eq(mSelectorCallback2));
+ }
+
+ @Test
+ @SmallTest
+ public void testOnDomainSelectionTwiceWithoutDestroy() {
+ SelectionAttributes attr1 = new SelectionAttributes.Builder(
+ SLOT_0, SUB_1, TEST_SELECTOR_TYPE)
+ .setCallId(CALL_ID)
+ .setEmergency(true)
+ .build();
+ mServiceHandler.post(() -> {
+ mDomainSelectionService.onDomainSelection(attr1, mSelectorCallback1);
+ });
+ processAllMessages();
+
+ verify(mImsStateTracker).start(eq(SUB_1));
+ verify(mSelectorCallback1).onCreated(eq(mDomainSelectorBase1));
+ verifyNoMoreInteractions(mSelectorCallback1);
+ verify(mDomainSelectorBase1).selectDomain(eq(attr1), eq(mSelectorCallback1));
+
+ SelectionAttributes attr2 = new SelectionAttributes.Builder(
+ SLOT_0, SUB_2, TEST_SELECTOR_TYPE)
+ .setCallId(CALL_ID)
+ .setEmergency(true)
+ .build();
+ mServiceHandler.post(() -> {
+ mDomainSelectionService.onDomainSelection(attr2, mSelectorCallback2);
+ });
+ processAllMessages();
+
+ verify(mImsStateTracker).start(eq(SUB_2));
+ verify(mSelectorCallback2).onCreated(eq(mDomainSelectorBase2));
+ verifyNoMoreInteractions(mSelectorCallback2);
+ verify(mDomainSelectorBase2).selectDomain(eq(attr2), eq(mSelectorCallback2));
+ }
+
+ @Test
+ @SmallTest
+ public void testOnServiceStateUpdated() {
+ mDomainSelectionService.onServiceStateUpdated(SLOT_0, SUB_1, mServiceState);
+
+ verify(mImsStateTracker).updateServiceState(eq(mServiceState));
+ }
+
+ @Test
+ @SmallTest
+ public void testOnBarringInfoUpdated() {
+ mDomainSelectionService.onBarringInfoUpdated(SLOT_0, SUB_1, mBarringInfo);
+
+ verify(mImsStateTracker).updateBarringInfo(eq(mBarringInfo));
+ }
+
+ @Test
+ @SmallTest
+ public void testOnDestroy() {
+ SelectionAttributes attr1 = new SelectionAttributes.Builder(
+ SLOT_0, SUB_1, TEST_SELECTOR_TYPE)
+ .setCallId(CALL_ID)
+ .setEmergency(true)
+ .build();
+ mServiceHandler.post(() -> {
+ mDomainSelectionService.onDomainSelection(attr1, mSelectorCallback1);
+ });
+ processAllMessages();
+
+ mDomainSelectionService.onDestroy();
+
+ verify(mImsStateTracker).destroy();
+ verify(mDomainSelectorBase1).destroy();
+ verify(mSubscriptionManager).removeOnSubscriptionsChangedListener(any());
+ }
+
+ @Test
+ @SmallTest
+ public void testHandleSubscriptionsChangedWithEmptySubscriptionInfo() {
+ when(mSubscriptionManager.getActiveSubscriptionInfoList())
+ .thenReturn(null, new ArrayList<SubscriptionInfo>());
+
+ mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+ mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+
+ verify(mImsStateTracker, never()).start(anyInt());
+ }
+
+ @Test
+ @SmallTest
+ public void testHandleSubscriptionsChangedWithActiveSubscriptionInfoAndInvalidSlotIndex() {
+ SubscriptionInfo subsInfo = Mockito.mock(SubscriptionInfo.class);
+ List<SubscriptionInfo> subsInfoList = new ArrayList<>();
+ subsInfoList.add(subsInfo);
+ when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(subsInfoList);
+ when(subsInfo.getSimSlotIndex()).thenReturn(SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+
+ mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+
+ verify(mImsStateTracker, never()).start(anyInt());
+ }
+
+ @Test
+ @SmallTest
+ public void testHandleSubscriptionsChangedWithActiveSubscriptionInfo() {
+ SubscriptionInfo subsInfo = Mockito.mock(SubscriptionInfo.class);
+ List<SubscriptionInfo> subsInfoList = new ArrayList<>();
+ subsInfoList.add(subsInfo);
+ when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(subsInfoList);
+ when(subsInfo.getSubscriptionId())
+ .thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID, SUB_1);
+ when(subsInfo.getSimSlotIndex()).thenReturn(SLOT_0);
+
+ mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+ mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+
+ verify(mImsStateTracker).start(eq(SubscriptionManager.INVALID_SUBSCRIPTION_ID));
+ verify(mImsStateTracker).start(eq(SUB_1));
+ }
+
+ private void processAllMessages() {
+ while (!mTestableLooper.getLooper().getQueue().isIdle()) {
+ mTestableLooper.processAllMessages();
+ }
+ }
+}
diff --git a/tests/src/com/android/services/telephony/rcs/RcsFeatureControllerTest.java b/tests/src/com/android/services/telephony/rcs/RcsFeatureControllerTest.java
index 2a30e1a..07c9fd0 100644
--- a/tests/src/com/android/services/telephony/rcs/RcsFeatureControllerTest.java
+++ b/tests/src/com/android/services/telephony/rcs/RcsFeatureControllerTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.fail;
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.mock;
import static org.mockito.Mockito.never;
@@ -171,12 +172,13 @@
verify(mFeatureManager).registerImsRegistrationCallback(captor.capture());
assertNotNull(captor.getValue());
- captor.getValue().onDeregistered(REASON_DISCONNECTED);
+ captor.getValue().onDeregistered(REASON_DISCONNECTED, 0, 0);
controller.getRegistrationState(result -> {
assertNotNull(result);
assertEquals(RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED, result.intValue());
});
- verify(mRegistrationCallback).handleImsUnregistered(REASON_DISCONNECTED);
+ verify(mRegistrationCallback).handleImsUnregistered(eq(REASON_DISCONNECTED),
+ anyInt(), anyInt());
ImsRegistrationAttributes attr = new ImsRegistrationAttributes.Builder(
ImsRegistrationImplBase.REGISTRATION_TECH_LTE).build();
@@ -193,8 +195,7 @@
assertNotNull(result);
assertEquals(RegistrationManager.REGISTRATION_STATE_REGISTERED, result.intValue());
});
- verify(mRegistrationCallback).handleImsRegistered(
- AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+ verify(mRegistrationCallback).handleImsRegistered(attr);
}
@Test
diff --git a/tests/src/com/android/services/telephony/rcs/SipSessionTrackerTest.java b/tests/src/com/android/services/telephony/rcs/SipSessionTrackerTest.java
index 37abb83..3874321 100644
--- a/tests/src/com/android/services/telephony/rcs/SipSessionTrackerTest.java
+++ b/tests/src/com/android/services/telephony/rcs/SipSessionTrackerTest.java
@@ -22,26 +22,44 @@
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import android.content.Context;
import android.net.Uri;
+import android.os.RemoteException;
+import android.telephony.BinderCacheManager;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.SipDialogState;
+import android.telephony.ims.SipDialogStateCallback;
import android.telephony.ims.SipMessage;
+import android.telephony.ims.aidl.IImsRcsController;
+import android.util.ArraySet;
import android.util.Base64;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.android.internal.telephony.ISipDialogStateCallback;
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.metrics.RcsStats;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -103,14 +121,29 @@
private static final String TEST_INVITE_SIP_METHOD = "INVITE";
private static final int TEST_SIP_RESPONSE_CODE = 200;
private static final int TEST_SIP_CLOSE_RESPONSE_CODE = 0;
- @Mock
- private RcsStats mRcsStats;
+
+ @Mock private RcsStats mRcsStats;
+ private boolean mUpdatedState = false;
+ private SipDialogStateCallback mCallback;
+ private SipDelegateManager mSipManager;
+ private ISipDialogStateCallback mCbBinder;
+ IImsRcsController mMockImsRcsInterface;
+ BinderCacheManager<ITelephony> mBinderCache;
+ BinderCacheManager<IImsRcsController> mRcsBinderCache;
@Before
- public void setUp() {
+ public void setUp() throws Exception {
mStringEntryCounter = 0;
MockitoAnnotations.initMocks(this);
mTrackerUT = new SipSessionTracker(TEST_SUB_ID, mRcsStats);
+ mMockImsRcsInterface = mock(IImsRcsController.class);
+ mBinderCache = mock(BinderCacheManager.class);
+ mRcsBinderCache = mock(BinderCacheManager.class);
+ doReturn(mMockImsRcsInterface).when(mRcsBinderCache)
+ .listenOnBinder(any(), any(Runnable.class));
+ doReturn(mMockImsRcsInterface).when(mRcsBinderCache)
+ .removeRunnable(any(SipDialogStateCallback.class));
+ doReturn(mMockImsRcsInterface).when(mRcsBinderCache).getBinder();
}
@Test
@@ -425,6 +458,143 @@
verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
}
+ @Test
+ public void testActiveDialogsChanged() throws ImsException {
+ sipDialogStateCallback();
+
+ // first dialog
+ DialogAttributes attr1 = new DialogAttributes();
+ createConfirmedDialog(attr1);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1);
+
+ verifyConfirmedStates(true);
+
+ // add a second dialog
+ DialogAttributes attr2 = new DialogAttributes();
+ createConfirmedDialog(attr2);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1, attr2);
+ verifyConfirmedStates(true);
+
+ // Send BYE request on first dialog
+ SipMessage byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr1);
+ filterMessage(byeRequest, attr1);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr2);
+ verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr1);
+ mTrackerUT.cleanupSession(attr1.callId);
+ verifyConfirmedStates(true);
+
+ // Send BYE request on second dialog
+ byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr2);
+ filterMessage(byeRequest, attr2);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr2);
+ mTrackerUT.cleanupSession(attr2.callId);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
+ verifyConfirmedStates(false);
+ unRegisterCallback();
+ }
+
+ @Test
+ public void testActiveSipDialogsChangedClearAll() throws ImsException {
+ sipDialogStateCallback();
+
+ // first dialog
+ DialogAttributes attr1 = new DialogAttributes();
+ createConfirmedDialog(attr1);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1);
+ verifyConfirmedStates(true);
+
+ // add a second dialog
+ DialogAttributes attr2 = new DialogAttributes();
+ createConfirmedDialog(attr2);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1, attr2);
+ verifyConfirmedStates(true);
+
+ // cleanAllSessions
+ mTrackerUT.clearAllSessions();
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
+ verifyConfirmedStates(false);
+ unRegisterCallback();
+ }
+
+ private void sipDialogStateCallback() throws ImsException {
+ mCallback = new SipDialogStateCallback() {
+ @Override
+ public void onActiveSipDialogsChanged(List<SipDialogState> dialogs) {
+ mUpdatedState = isSipDialogActiveState(dialogs);
+ }
+
+ @Override
+ public void onError() { }
+ };
+ registerCallback();
+ }
+
+ private void verifyConfirmedStates(boolean currentState) {
+ List<SipDialogState> dialogStates = new ArrayList<>();
+ for (SipDialog d : (ArraySet<SipDialog>) mTrackerUT.getTrackedDialogs()) {
+ SipDialogState dialog = new SipDialogState.Builder(d.getState()).build();
+ dialogStates.add(dialog);
+ }
+ try {
+ mCbBinder.onActiveSipDialogsChanged(dialogStates);
+ } catch (RemoteException e) {
+ //onActiveSipDialogsChanged error
+ }
+ assertEquals(currentState, mUpdatedState);
+ }
+
+ private void registerCallback() throws ImsException {
+ // Capture the Runnable that was registered.
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ // Capture the ISipDialogStateCallback that was registered.
+ ArgumentCaptor<ISipDialogStateCallback> callbackCaptor =
+ ArgumentCaptor.forClass(ISipDialogStateCallback.class);
+
+ Context context = PhoneFactory.getDefaultPhone().getContext();
+ mSipManager = new SipDelegateManager(context,
+ TEST_SUB_ID, mRcsBinderCache, mBinderCache);
+
+ mSipManager.registerSipDialogStateCallback(Runnable::run, mCallback);
+
+ verify(mRcsBinderCache).listenOnBinder(any(), runnableCaptor.capture());
+ try {
+ verify(mMockImsRcsInterface).registerSipDialogStateCallback(
+ eq(TEST_SUB_ID), callbackCaptor.capture());
+ } catch (RemoteException e) {
+ //registerSipDialogStateCallback error
+ }
+ mCbBinder = callbackCaptor.getValue();
+ }
+
+ private void unRegisterCallback() {
+ try {
+ mSipManager.unregisterSipDialogStateCallback(mCallback);
+ } catch (ImsException e) {
+ //unregisterSipDialogStateCallback error
+ }
+ }
+
+ private boolean isSipDialogActiveState(List<SipDialogState> dialogs) {
+ int confirmedSize = dialogs.stream().filter(
+ d -> d.getState() == SipDialogState.STATE_CONFIRMED)
+ .collect(Collectors.toSet()).size();
+ if (confirmedSize > 0) {
+ return true;
+ }
+ return false;
+ }
+
private void filterMessage(SipMessage m, DialogAttributes attr) {
mTrackerUT.filterSipMessage(
SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, m);