Merge "eSIM deprecated API Refactoring"
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index a5c5e86..7738bcd 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -310,7 +310,7 @@
<string name="sim_change_data_title" msgid="9142726786345906606">"Promijeniti SIM za prijenos podataka?"</string>
<string name="sim_change_data_message" msgid="3567358694255933280">"Koristiti SIM karticu <xliff:g id="NEW_SIM">%1$s</xliff:g> umjesto SIM kartice <xliff:g id="OLD_SIM">%2$s</xliff:g> za prijenos podataka na mobilnoj mreži?"</string>
<string name="wifi_calling_settings_title" msgid="5800018845662016507">"Pozivanje putem WiFi-ja"</string>
- <string name="video_calling_settings_title" msgid="342829454913266078">"Operater video pozivanja"</string>
+ <string name="video_calling_settings_title" msgid="342829454913266078">"Video pozivi putem operatera"</string>
<string name="gsm_umts_options" msgid="4968446771519376808">"GSM/UMTS opcije"</string>
<string name="cdma_options" msgid="3669592472226145665">"CDMA opcije"</string>
<string name="throttle_data_usage" msgid="1944145350660420711">"Korištenje podataka"</string>
@@ -839,7 +839,7 @@
<string name="radio_info_data_connection_enable" msgid="6183729739783252840">"Omogućite vezu za prijenos podataka"</string>
<string name="radio_info_data_connection_disable" msgid="6404751291511368706">"Onemogući vezu za prijenos podataka"</string>
<string name="volte_provisioned_switch_string" msgid="4812874990480336178">"VoLTE omogućen"</string>
- <string name="vt_provisioned_switch_string" msgid="8295542122512195979">"Video poziv obezbijeđen"</string>
+ <string name="vt_provisioned_switch_string" msgid="8295542122512195979">"Video pozivi su omogućeni"</string>
<string name="wfc_provisioned_switch_string" msgid="3835004640321078988">"WiFi poziv obezbijeđen"</string>
<string name="eab_provisioned_switch_string" msgid="4449676720736033035">"EAB/Omogućeno prisustvo"</string>
<string name="cbrs_data_switch_string" msgid="6060356430838077653">"Cbrs podaci"</string>
@@ -858,7 +858,7 @@
<string name="radio_info_ims_reg_status_not_registered" msgid="8045821447288876085">"Nije registrirano"</string>
<string name="radio_info_ims_feature_status_available" msgid="6493200914756969292">"Dostupno"</string>
<string name="radio_info_ims_feature_status_unavailable" msgid="8930391136839759778">"Nedostupno"</string>
- <string name="radio_info_ims_reg_status" msgid="25582845222446390">"IMS registracija: <xliff:g id="STATUS">%1$s</xliff:g>\nGovor putem LTE mreže: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nGovor putem WiFi mreže: <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nVideo pozivanje: <xliff:g id="AVAILABILITY_2">%4$s</xliff:g>\nUT interfejs: <xliff:g id="AVAILABILITY_3">%5$s</xliff:g>"</string>
+ <string name="radio_info_ims_reg_status" msgid="25582845222446390">"IMS registracija: <xliff:g id="STATUS">%1$s</xliff:g>\nGovor putem LTE mreže: <xliff:g id="AVAILABILITY_0">%2$s</xliff:g>\nGovor putem WiFi mreže: <xliff:g id="AVAILABILITY_1">%3$s</xliff:g>\nVideo pozivi: <xliff:g id="AVAILABILITY_2">%4$s</xliff:g>\nUT interfejs: <xliff:g id="AVAILABILITY_3">%5$s</xliff:g>"</string>
<string name="radioInfo_service_in" msgid="45753418231446400">"Aktivno"</string>
<string name="radioInfo_service_out" msgid="287972405416142312">"Ne radi"</string>
<string name="radioInfo_service_emergency" msgid="4763879891415016848">"Samo hitni pozivi"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index c941be2..22aa185 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -890,14 +890,14 @@
<string name="radio_info_ppp_resets_label" msgid="9131901102339077661">"चालू करने के बाद से पीपीपी रीसेट की संख्या:"</string>
<string name="radio_info_current_network_label" msgid="3052098695239642450">"मौजूदा नेटवर्क:"</string>
<string name="radio_info_ppp_received_label" msgid="5753592451640644889">"मिलने वाला डेटा :"</string>
- <string name="radio_info_gsm_service_label" msgid="6443348321714241328">"Voice की सेवा:"</string>
+ <string name="radio_info_gsm_service_label" msgid="6443348321714241328">"वॉइस सेवा:"</string>
<string name="radio_info_signal_strength_label" msgid="5545444702102543260">"सिग्नल कितना अच्छा है:"</string>
- <string name="radio_info_call_status_label" msgid="7693575431923095487">"Voice कॉल की स्थिति:"</string>
+ <string name="radio_info_call_status_label" msgid="7693575431923095487">"वॉइस कॉल की स्थिति:"</string>
<string name="radio_info_ppp_sent_label" msgid="6542208429356199695">"भेजा गया डेटा :"</string>
<string name="radio_info_message_waiting_label" msgid="1886549432566952078">"मैसेज वेटिंग:"</string>
<string name="radio_info_phone_number_label" msgid="2533852539562512203">"फ़ोन नंबर:"</string>
<string name="radio_info_band_mode_label" msgid="23480556225515290">"रेडियो का बैंड चुनें"</string>
- <string name="radio_info_voice_network_type_label" msgid="2395347336419593265">"Voice के नेटवर्क प्रकार:"</string>
+ <string name="radio_info_voice_network_type_label" msgid="2395347336419593265">"वॉइस नेटवर्क टाइप:"</string>
<string name="radio_info_data_network_type_label" msgid="8886597029237501929">"डेटा नेटवर्क प्रकार:"</string>
<string name="phone_index_label" msgid="6222406512768964268">"फ़ोन इंडेक्स चुनें"</string>
<string name="radio_info_set_perferred_label" msgid="7408131389363136210">"पसंदीदा नेटवर्क प्रकार सेट करें:"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 18ee718..06af4bc 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -297,7 +297,7 @@
<string name="sip_accounts_removed_notification_title" msgid="3528076957535736095">"Account SIP deprecati trovati e rimossi"</string>
<string name="sip_accounts_removed_notification_message" msgid="1916856744869791592">"Le chiamate SIP non sono più supportate dalla piattaforma Android.\nI tuoi account SIP esistenti (<xliff:g id="REMOVED_SIP_ACCOUNTS">%s</xliff:g>) sono stati rimossi.\nConferma l\'impostazione del tuo account predefinito per le chiamate."</string>
<string name="sip_accounts_removed_notification_action" msgid="3772778402370555562">"Vai alle impostazioni"</string>
- <string name="data_usage_title" msgid="8438592133893837464">"Utilizzo dei dati delle app"</string>
+ <string name="data_usage_title" msgid="8438592133893837464">"Utilizzo dati delle app"</string>
<string name="data_usage_template" msgid="6287906680674061783">"Dati mobili usati: <xliff:g id="ID_1">%1$s</xliff:g> nel periodo <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="advanced_options_title" msgid="9208195294513520934">"Avanzate"</string>
<string name="carrier_settings_euicc" msgid="1190237227261337749">"Operatore"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 142d1e1..e16c4fa 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -596,7 +596,7 @@
<string name="hac_mode_summary" msgid="7774989500136009881">"Slå på kompatibilitet med høreapparater"</string>
<string name="rtt_mode_title" msgid="3075948111362818043">"Sanntidstekst-anrop (STT)"</string>
<string name="rtt_mode_summary" msgid="8631541375609989562">"Tillat meldingsutveksling i talesamtaler"</string>
- <string name="rtt_mode_more_information" msgid="587500128658756318">"STT er til hjelp for brukere som er døve, tunghørte, har talefunksjonshemning eller trenger mer enn bare tale.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>Finn ut mer</a>\n <br><br> – STT-anrop lagres som meldingstranskripsjoner\n <br> – STT er ikke tilgjengelig for videoanrop"</string>
+ <string name="rtt_mode_more_information" msgid="587500128658756318">"STT er til hjelp for brukere som er døve, hørselshemmede, har taleproblemer eller trenger mer enn bare tale.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>Finn ut mer</a>\n <br><br> – STT-anrop lagres som meldingstranskripsjoner\n <br> – STT er ikke tilgjengelig for videoanrop"</string>
<string name="no_rtt_when_roaming" msgid="5268008247378355389">"Merk: RTT er ikke tilgjengelig ved roaming"</string>
<string-array name="tty_mode_entries">
<item msgid="3238070884803849303">"TTY av"</item>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 6ff98c6..226dc72 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -125,9 +125,9 @@
<string name="sum_cfnrc_disabled" msgid="739289696796917683">"Uit"</string>
<string name="disable_cfnrc_forbidden" msgid="775348748084726890">"Je provider biedt geen ondersteuning voor het uitschakelen van oproepdoorschakelingen wanneer je telefoon niet bereikbaar is."</string>
<string name="registration_cf_forbidden" msgid="4386482610771190420">"Je provider ondersteunt het doorschakelen van gesprekken niet."</string>
- <string name="cdma_call_waiting" msgid="4565070960879673216">"Wisselgesprek inschakelen?"</string>
+ <string name="cdma_call_waiting" msgid="4565070960879673216">"Wisselgesprek aanzetten?"</string>
<string name="enable_cdma_call_waiting_setting" msgid="5906811747921744307">"Tijdens een gesprek krijg je een melding over inkomende gesprekken"</string>
- <string name="enable_cdma_cw" msgid="811047045863422232">"Inschakelen"</string>
+ <string name="enable_cdma_cw" msgid="811047045863422232">"Aanzetten"</string>
<string name="disable_cdma_cw" msgid="7119290446496301734">"Annuleren"</string>
<string name="cdma_call_waiting_in_ims_on" msgid="6390979414188659218">"CDMA-wisselgesprek onder IMS aan"</string>
<string name="cdma_call_waiting_in_ims_off" msgid="1099246114368636334">"CDMA-wisselgesprek onder IMS uit"</string>
@@ -147,7 +147,7 @@
<string name="fdn_check_failure" msgid="1833769746374185247">"De instelling \'Vaste nummers\' in je app Telefoon is ingeschakeld. Hierdoor werken sommige oproepgerelateerde functies niet."</string>
<string name="radio_off_error" msgid="8321564164914232181">"Schakel de radio in voordat je deze instellingen bekijkt."</string>
<string name="close_dialog" msgid="1074977476136119408">"OK"</string>
- <string name="enable" msgid="2636552299455477603">"Inschakelen"</string>
+ <string name="enable" msgid="2636552299455477603">"Aanzetten"</string>
<string name="disable" msgid="1122698860799462116">"Uit"</string>
<string name="change_num" msgid="6982164494063109334">"Updaten"</string>
<string-array name="clir_display_values">
@@ -443,20 +443,20 @@
<string name="fdn_activation" msgid="2178637004710435895">"FDN-activering"</string>
<string name="fdn_enabled" msgid="7017355494808056447">"Vaste nummers zijn ingeschakeld"</string>
<string name="fdn_disabled" msgid="6696468878037736600">"Vaste nummers zijn uitgeschakeld"</string>
- <string name="enable_fdn" msgid="4830555730418033723">"FDN inschakelen"</string>
+ <string name="enable_fdn" msgid="4830555730418033723">"FDN aanzetten"</string>
<string name="disable_fdn" msgid="3918794950264647541">"FDN uitschakelen"</string>
<string name="change_pin2" msgid="3110844547237754871">"PIN2-code wijzigen"</string>
<string name="enable_fdn_ok" msgid="5080925177369329827">"FDN uitschakelen"</string>
- <string name="disable_fdn_ok" msgid="3745475926874838676">"FDN inschakelen"</string>
+ <string name="disable_fdn_ok" msgid="3745475926874838676">"FDN aanzetten"</string>
<string name="sum_fdn" msgid="6152246141642323582">"Vaste nummers beheren"</string>
<string name="sum_fdn_change_pin" msgid="3510994280557335727">"Pincode voor FDN-toegang wijzigen"</string>
<string name="sum_fdn_manage_list" msgid="3311397063233992907">"Lijst met telefoonnummers beheren"</string>
<string name="voice_privacy" msgid="7346935172372181951">"Spraakprivacy"</string>
- <string name="voice_privacy_summary" msgid="3556460926168473346">"Geavanceerde privacymodus inschakelen"</string>
+ <string name="voice_privacy_summary" msgid="3556460926168473346">"Geavanceerde privacymodus aanzetten"</string>
<string name="tty_mode_option_title" msgid="3843817710032641703">"TTY-modus"</string>
<string name="tty_mode_option_summary" msgid="4770510287236494371">"TTY-modus instellen"</string>
<string name="auto_retry_mode_title" msgid="2985801935424422340">"Automatisch opnieuw proberen"</string>
- <string name="auto_retry_mode_summary" msgid="2863919925349511402">"Modus voor automatisch opnieuw proberen inschakelen"</string>
+ <string name="auto_retry_mode_summary" msgid="2863919925349511402">"Modus voor automatisch opnieuw proberen aanzetten"</string>
<string name="tty_mode_not_allowed_video_call" msgid="6551976083652752815">"De TTY-modus mag niet worden gewijzigd tijdens een videogesprek"</string>
<string name="menu_add" msgid="5616487894975773141">"Contact toevoegen"</string>
<string name="menu_edit" msgid="3593856941552460706">"Contact bewerken"</string>
@@ -593,7 +593,7 @@
<string name="singleContactImportedMsg" msgid="3619804066300998934">"Contact geïmporteerd"</string>
<string name="failedToImportSingleContactMsg" msgid="228095510489830266">"Kan contact niet importeren"</string>
<string name="hac_mode_title" msgid="4127986689621125468">"Hoortoestellen"</string>
- <string name="hac_mode_summary" msgid="7774989500136009881">"Compatibiliteit voor hoortoestel inschakelen"</string>
+ <string name="hac_mode_summary" msgid="7774989500136009881">"Compatibiliteit voor hoortoestel aanzetten"</string>
<string name="rtt_mode_title" msgid="3075948111362818043">"Realtime tekstoproep (RTT)"</string>
<string name="rtt_mode_summary" msgid="8631541375609989562">"Berichten in een audiogesprek toestaan"</string>
<string name="rtt_mode_more_information" msgid="587500128658756318">"RTT helpt bellers die doof of slechthorend zijn, een spraakbeperking hebben of meer dan alleen een stem nodig hebben.<br> <a href=<xliff:g id="URL">http://support.google.com/mobile?p=telephony_rtt</xliff:g>>Meer informatie</a>\n <br><br> - RTT-gesprekken worden opgeslagen als berichttranscript.\n <br> - RTT is niet beschikbaar voor videogesprekken"</string>
@@ -673,8 +673,8 @@
<string name="voicemail_change_pin_dialog_title" msgid="4633077715231764435">"Pincode wijzigen"</string>
<string name="preference_category_ringtone" msgid="8787281191375434976">"Ringtone en trillen"</string>
<string name="pstn_connection_service_label" msgid="9200102709997537069">"Ingebouwde simkaarten"</string>
- <string name="enable_video_calling_title" msgid="7246600931634161830">"Videogesprekken inschakelen"</string>
- <string name="enable_video_calling_dialog_msg" msgid="7141478720386203540">"Als je videogesprekken wilt inschakelen, moet je de geoptimaliseerde 4G LTE-modus inschakelen in de netwerkinstellingen."</string>
+ <string name="enable_video_calling_title" msgid="7246600931634161830">"Videogesprekken aanzetten"</string>
+ <string name="enable_video_calling_dialog_msg" msgid="7141478720386203540">"Als je videogesprekken wilt aanzetten, moet je de geoptimaliseerde 4G LTE-modus aanzetten in de netwerkinstellingen."</string>
<string name="enable_video_calling_dialog_settings" msgid="8697890611305307110">"Netwerkinstellingen"</string>
<string name="enable_video_calling_dialog_close" msgid="4298929725917045270">"Sluiten"</string>
<string name="sim_label_emergency_calls" msgid="9078241989421522310">"Noodoproepen"</string>
@@ -833,22 +833,22 @@
<string name="supp_service_over_ut_precautions_dual_sim" msgid="5166866975550910474">"Als je <xliff:g id="SUPP_SERVICE">%1$s</xliff:g> wilt gebruiken, zorg je dat je mobiele data hebt ingeschakeld voor sim <xliff:g id="SIM_NUMBER">%2$d</xliff:g>. Je kunt dit wijzigen via de mobiele netwerkinstellingen."</string>
<string name="supp_service_over_ut_precautions_roaming_dual_sim" msgid="6627654855191817965">"Als je <xliff:g id="SUPP_SERVICE">%1$s</xliff:g> wilt gebruiken, zorg je dat je mobiele data en dataroaming hebt ingeschakeld voor sim <xliff:g id="SIM_NUMBER">%2$d</xliff:g>. Je kunt dit wijzigen via de mobiele netwerkinstellingen."</string>
<string name="supp_service_over_ut_precautions_dialog_dismiss" msgid="5934541487903081652">"Sluiten"</string>
- <string name="radio_info_data_connection_enable" msgid="6183729739783252840">"Dataverbinding inschakelen"</string>
+ <string name="radio_info_data_connection_enable" msgid="6183729739783252840">"Dataverbinding aanzetten"</string>
<string name="radio_info_data_connection_disable" msgid="6404751291511368706">"Dataverbinding uitschakelen"</string>
<string name="volte_provisioned_switch_string" msgid="4812874990480336178">"VoLTE-registratie"</string>
<string name="vt_provisioned_switch_string" msgid="8295542122512195979">"Videogesprekken geregistreerd"</string>
<string name="wfc_provisioned_switch_string" msgid="3835004640321078988">"Wifi-gesprekken geregistreerd"</string>
<string name="eab_provisioned_switch_string" msgid="4449676720736033035">"EAB/aanwezigheid geregistreerd"</string>
<string name="cbrs_data_switch_string" msgid="6060356430838077653">"CBRS-gegevens"</string>
- <string name="dsds_switch_string" msgid="7564769822086764796">"DSDS inschakelen"</string>
+ <string name="dsds_switch_string" msgid="7564769822086764796">"DSDS aanzetten"</string>
<string name="dsds_dialog_title" msgid="8494569893941847575">"Apparaat opnieuw opstarten?"</string>
<string name="dsds_dialog_message" msgid="4047480385678538850">"Start het apparaat opnieuw op om de instelling te wijzigen."</string>
<string name="dsds_dialog_confirm" msgid="9032004888134129885">"Opnieuw opstarten"</string>
<string name="dsds_dialog_cancel" msgid="3245958947099586655">"Annuleren"</string>
<string name="radio_info_radio_power" msgid="8805595022160471587">"Mobiel radiovermogen"</string>
- <string name="radioInfo_menu_viewADN" msgid="4533179730908559846">"Adresboek op simkaart weergeven"</string>
- <string name="radioInfo_menu_viewFDN" msgid="1847236480527032061">"Vaste nummers weergeven"</string>
- <string name="radioInfo_menu_viewSDN" msgid="2613431584522392842">"Servicenummers weergeven"</string>
+ <string name="radioInfo_menu_viewADN" msgid="4533179730908559846">"Adresboek op simkaart bekijken"</string>
+ <string name="radioInfo_menu_viewFDN" msgid="1847236480527032061">"Vaste nummers bekijken"</string>
+ <string name="radioInfo_menu_viewSDN" msgid="2613431584522392842">"Servicenummers bekijken"</string>
<string name="radioInfo_menu_getIMS" msgid="1950869267853198232">"IMS-servicestatus"</string>
<string name="radio_info_ims_reg_status_title" msgid="6875885401313992007">"IMS-status"</string>
<string name="radio_info_ims_reg_status_registered" msgid="7095182114078864326">"Geregistreerd"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 65376c9..f0da34a 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -451,8 +451,8 @@
<string name="sum_fdn" msgid="6152246141642323582">"Hantera fasta nummer"</string>
<string name="sum_fdn_change_pin" msgid="3510994280557335727">"Ändra PIN-kod för FDN-åtkomst"</string>
<string name="sum_fdn_manage_list" msgid="3311397063233992907">"Hantera nummerlistan"</string>
- <string name="voice_privacy" msgid="7346935172372181951">"Sekretess för Voice"</string>
- <string name="voice_privacy_summary" msgid="3556460926168473346">"Aktivera avancerat sekretessläge"</string>
+ <string name="voice_privacy" msgid="7346935172372181951">"Integritet för Voice"</string>
+ <string name="voice_privacy_summary" msgid="3556460926168473346">"Aktivera avancerat integritetsläge"</string>
<string name="tty_mode_option_title" msgid="3843817710032641703">"TTY-läge"</string>
<string name="tty_mode_option_summary" msgid="4770510287236494371">"Ange TTY-läge"</string>
<string name="auto_retry_mode_title" msgid="2985801935424422340">"Försök igen automatiskt"</string>
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index 8df411b..45ca974 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -108,6 +108,10 @@
private CarrierServiceConnection[] mServiceConnection;
// Service connection for binding to carrier config app for no SIM config.
private CarrierServiceConnection[] mServiceConnectionForNoSimConfig;
+ // Whether we are bound to a service for each phone
+ private boolean[] mServiceBound;
+ // Whether we are bound to a service for no SIM config
+ private boolean[] mServiceBoundForNoSimConfig;
// Whether we have sent config change broadcast for each phone id.
private boolean[] mHasSentConfigChange;
// Whether the broadcast was sent from EVENT_SYSTEM_UNLOCKED, to track rebroadcasts
@@ -308,7 +312,7 @@
final CarrierServiceConnection conn = (CarrierServiceConnection) msg.obj;
// If new service connection has been created, unbind.
if (mServiceConnection[phoneId] != conn || conn.service == null) {
- unbindIfBound(mContext, conn);
+ unbindIfBound(mContext, conn, phoneId);
break;
}
final CarrierIdentifier carrierId = getCarrierIdentifierForPhoneId(phoneId);
@@ -317,7 +321,7 @@
new ResultReceiver(this) {
@Override
public void onReceiveResult(int resultCode, Bundle resultData) {
- unbindIfBound(mContext, conn);
+ unbindIfBound(mContext, conn, phoneId);
removeMessages(EVENT_FETCH_DEFAULT_TIMEOUT,
getMessageToken(phoneId));
// If new service connection has been created, this is stale.
@@ -352,7 +356,7 @@
} catch (RemoteException e) {
loge("Failed to get carrier config from default app: " +
mPlatformCarrierConfigPackage + " err: " + e.toString());
- unbindIfBound(mContext, conn);
+ unbindIfBound(mContext, conn, phoneId);
break; // So we don't set a timeout.
}
sendMessageDelayed(
@@ -372,7 +376,7 @@
if (mServiceConnection[phoneId] != null) {
// If a ResponseReceiver callback is in the queue when this happens, we will
// unbind twice and throw an exception.
- unbindIfBound(mContext, mServiceConnection[phoneId]);
+ unbindIfBound(mContext, mServiceConnection[phoneId], phoneId);
broadcastConfigChangedIntent(phoneId);
}
// Put a stub bundle in place so that the rest of the logic continues smoothly.
@@ -438,7 +442,7 @@
final CarrierServiceConnection conn = (CarrierServiceConnection) msg.obj;
// If new service connection has been created, unbind.
if (mServiceConnection[phoneId] != conn || conn.service == null) {
- unbindIfBound(mContext, conn);
+ unbindIfBound(mContext, conn, phoneId);
break;
}
final CarrierIdentifier carrierId = getCarrierIdentifierForPhoneId(phoneId);
@@ -447,7 +451,7 @@
new ResultReceiver(this) {
@Override
public void onReceiveResult(int resultCode, Bundle resultData) {
- unbindIfBound(mContext, conn);
+ unbindIfBound(mContext, conn, phoneId);
removeMessages(EVENT_FETCH_CARRIER_TIMEOUT,
getMessageToken(phoneId));
// If new service connection has been created, this is stale.
@@ -491,7 +495,7 @@
+ " carrierid: " + carrierId.toString());
} catch (RemoteException e) {
loge("Failed to get carrier config: " + e.toString());
- unbindIfBound(mContext, conn);
+ unbindIfBound(mContext, conn, phoneId);
break; // So we don't set a timeout.
}
sendMessageDelayed(
@@ -512,7 +516,7 @@
if (mServiceConnection[phoneId] != null) {
// If a ResponseReceiver callback is in the queue when this happens, we will
// unbind twice and throw an exception.
- unbindIfBound(mContext, mServiceConnection[phoneId]);
+ unbindIfBound(mContext, mServiceConnection[phoneId], phoneId);
broadcastConfigChangedIntent(phoneId);
}
// Put a stub bundle in place so that the rest of the logic continues smoothly.
@@ -606,7 +610,8 @@
if (mServiceConnectionForNoSimConfig[phoneId] != null) {
// If a ResponseReceiver callback is in the queue when this happens, we will
// unbind twice and throw an exception.
- unbindIfBound(mContext, mServiceConnectionForNoSimConfig[phoneId]);
+ unbindIfBoundForNoSimConfig(mContext,
+ mServiceConnectionForNoSimConfig[phoneId], phoneId);
}
broadcastConfigChangedIntent(phoneId, false);
break;
@@ -617,7 +622,7 @@
final CarrierServiceConnection conn = (CarrierServiceConnection) msg.obj;
// If new service connection has been created, unbind.
if (mServiceConnectionForNoSimConfig[phoneId] != conn || conn.service == null) {
- unbindIfBound(mContext, conn);
+ unbindIfBoundForNoSimConfig(mContext, conn, phoneId);
break;
}
@@ -626,7 +631,7 @@
new ResultReceiver(this) {
@Override
public void onReceiveResult(int resultCode, Bundle resultData) {
- unbindIfBound(mContext, conn);
+ unbindIfBoundForNoSimConfig(mContext, conn, phoneId);
// If new service connection has been created, this is stale.
if (mServiceConnectionForNoSimConfig[phoneId] != conn) {
loge("Received response for stale request.");
@@ -658,7 +663,7 @@
} catch (RemoteException e) {
loge("Failed to get no sim carrier config from default app: " +
mPlatformCarrierConfigPackage + " err: " + e.toString());
- unbindIfBound(mContext, conn);
+ unbindIfBoundForNoSimConfig(mContext, conn, phoneId);
break; // So we don't set a timeout.
}
sendMessageDelayed(
@@ -705,9 +710,11 @@
mOverrideConfigs = new PersistableBundle[numPhones];
mNoSimConfig = new PersistableBundle();
mServiceConnection = new CarrierServiceConnection[numPhones];
+ mServiceBound = new boolean[numPhones];
mHasSentConfigChange = new boolean[numPhones];
mFromSystemUnlocked = new boolean[numPhones];
mServiceConnectionForNoSimConfig = new CarrierServiceConnection[numPhones];
+ mServiceBoundForNoSimConfig = new boolean[numPhones];
logd("CarrierConfigLoader has started");
mSubscriptionInfoUpdater = subscriptionInfoUpdater;
mHandler.sendEmptyMessage(EVENT_CHECK_SYSTEM_UPDATE);
@@ -842,7 +849,11 @@
try {
if (mContext.bindService(carrierService, serviceConnection,
Context.BIND_AUTO_CREATE)) {
- serviceConnection.isBound = true;
+ if (eventId == EVENT_CONNECTED_TO_DEFAULT_FOR_NO_SIM_CONFIG) {
+ mServiceBoundForNoSimConfig[phoneId] = true;
+ } else {
+ mServiceBound[phoneId] = true;
+ }
return true;
} else {
return false;
@@ -1384,9 +1395,18 @@
return mOverrideConfigs[phoneId];
}
- private void unbindIfBound(Context context, CarrierServiceConnection conn) {
- if (conn.isBound) {
- conn.isBound = false;
+ private void unbindIfBound(Context context, CarrierServiceConnection conn,
+ int phoneId) {
+ if (mServiceBound[phoneId]) {
+ mServiceBound[phoneId] = false;
+ context.unbindService(conn);
+ }
+ }
+
+ private void unbindIfBoundForNoSimConfig(Context context, CarrierServiceConnection conn,
+ int phoneId) {
+ if (mServiceBoundForNoSimConfig[phoneId]) {
+ mServiceBoundForNoSimConfig[phoneId] = false;
context.unbindService(conn);
}
}
@@ -1611,15 +1631,11 @@
final String pkgName;
final int eventId;
IBinder service;
- // If bindService was called and return true which means unbindService
- // must be called later to release the connection
- boolean isBound;
CarrierServiceConnection(int phoneId, String pkgName, int eventId) {
this.phoneId = phoneId;
this.pkgName = pkgName;
this.eventId = eventId;
- this.isBound = false;
}
@Override
diff --git a/src/com/android/phone/ImsStateCallbackController.java b/src/com/android/phone/ImsStateCallbackController.java
new file mode 100644
index 0000000..28fca59
--- /dev/null
+++ b/src/com/android/phone/ImsStateCallbackController.java
@@ -0,0 +1,1184 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import static android.telephony.ims.ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED;
+import static android.telephony.ims.ImsStateCallback.REASON_IMS_SERVICE_NOT_READY;
+import static android.telephony.ims.ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED;
+import static android.telephony.ims.ImsStateCallback.REASON_SUBSCRIPTION_INACTIVE;
+import static android.telephony.ims.ImsStateCallback.REASON_UNKNOWN_PERMANENT_ERROR;
+import static android.telephony.ims.ImsStateCallback.REASON_UNKNOWN_TEMPORARY_ERROR;
+import static android.telephony.ims.feature.ImsFeature.FEATURE_MMTEL;
+import static android.telephony.ims.feature.ImsFeature.FEATURE_RCS;
+import static android.telephony.ims.feature.ImsFeature.STATE_READY;
+import static android.telephony.ims.feature.ImsFeature.STATE_UNAVAILABLE;
+
+import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED;
+import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_IMS_UNSUPPORTED;
+import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_NOT_READY;
+import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyRegistryManager;
+import android.telephony.ims.feature.ImsFeature;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.ims.FeatureConnector;
+import com.android.ims.ImsManager;
+import com.android.ims.RcsFeatureManager;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.IImsStateCallback;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConfigurationManager;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.ims.ImsResolver;
+import com.android.internal.telephony.util.HandlerExecutor;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.services.telephony.rcs.RcsFeatureController;
+import com.android.telephony.Rlog;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.concurrent.Executor;
+
+/**
+ * Implementation of the controller managing {@link ImsStateCallback}s
+ */
+public class ImsStateCallbackController {
+ private static final String TAG = "ImsStateCallbackController";
+ private static final boolean VDBG = false;
+ private static final int LOG_SIZE = 50;
+
+ /**
+ * Create a FeatureConnector for this class to use to connect to an ImsManager.
+ */
+ @VisibleForTesting
+ public interface MmTelFeatureConnectorFactory {
+ /**
+ * Create a FeatureConnector for this class to use to connect to an ImsManager.
+ * @param listener will receive ImsManager instance.
+ * @param executor that the Listener callbacks will be called on.
+ * @return A FeatureConnector
+ */
+ FeatureConnector<ImsManager> create(Context context, int slotId,
+ String logPrefix, FeatureConnector.Listener<ImsManager> listener,
+ Executor executor);
+ }
+
+ /**
+ * Create a FeatureConnector for this class to use to connect to an RcsFeatureManager.
+ */
+ @VisibleForTesting
+ public interface RcsFeatureConnectorFactory {
+ /**
+ * Create a FeatureConnector for this class to use to connect to an RcsFeatureManager.
+ * @param listener will receive RcsFeatureManager instance.
+ * @param executor that the Listener callbacks will be called on.
+ * @return A FeatureConnector
+ */
+ FeatureConnector<RcsFeatureManager> create(Context context, int slotId,
+ FeatureConnector.Listener<RcsFeatureManager> listener,
+ Executor executor, String logPrefix);
+ }
+
+ /** Indicates that the state is not valid, used in ExternalRcsFeatureState only */
+ private static final int STATE_UNKNOWN = -1;
+
+ /** The unavailable reason of ImsFeature is not initialized */
+ private static final int NOT_INITIALIZED = -1;
+ /** The ImsFeature is available. */
+ private static final int AVAILABLE = 0;
+
+ private static final int EVENT_SUB_CHANGED = 1;
+ private static final int EVENT_REGISTER_CALLBACK = 2;
+ private static final int EVENT_UNREGISTER_CALLBACK = 3;
+ private static final int EVENT_CARRIER_CONFIG_CHANGED = 4;
+ private static final int EVENT_EXTERNAL_RCS_STATE_CHANGED = 5;
+ private static final int EVENT_MSIM_CONFIGURATION_CHANGE = 6;
+
+ private static ImsStateCallbackController sInstance;
+ private static final LocalLog sLocalLog = new LocalLog(LOG_SIZE);
+
+ /**
+ * get the instance
+ */
+ public static ImsStateCallbackController getInstance() {
+ synchronized (ImsStateCallbackController.class) {
+ return sInstance;
+ }
+ }
+
+ private final PhoneGlobals mApp;
+ private final Handler mHandler;
+ private final ImsResolver mImsResolver;
+ private final SparseArray<MmTelFeatureListener> mMmTelFeatureListeners = new SparseArray<>();
+ private final SparseArray<RcsFeatureListener> mRcsFeatureListeners = new SparseArray<>();
+
+ private final SubscriptionManager mSubscriptionManager;
+ private final TelephonyRegistryManager mTelephonyRegistryManager;
+ private MmTelFeatureConnectorFactory mMmTelFeatureFactory;
+ private RcsFeatureConnectorFactory mRcsFeatureFactory;
+
+ private HashMap<IBinder, CallbackWrapper> mWrappers = new HashMap<>();
+
+ private final Object mDumpLock = new Object();
+
+ private int mNumSlots;
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent == null) {
+ return;
+ }
+ if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+ Bundle bundle = intent.getExtras();
+ if (bundle == null) {
+ return;
+ }
+ int slotId = bundle.getInt(CarrierConfigManager.EXTRA_SLOT_INDEX,
+ SubscriptionManager.INVALID_PHONE_INDEX);
+ int subId = bundle.getInt(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+ if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+ loge("onReceive ACTION_CARRIER_CONFIG_CHANGED invalid slotId");
+ return;
+ }
+
+ if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ loge("onReceive ACTION_CARRIER_CONFIG_CHANGED invalid subId");
+ //subscription changed will be notified by mSubChangedListener
+ return;
+ }
+
+ notifyCarrierConfigChanged(slotId);
+ }
+ }
+ };
+
+ private final SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener =
+ new SubscriptionManager.OnSubscriptionsChangedListener() {
+ @Override
+ public void onSubscriptionsChanged() {
+ if (!mHandler.hasMessages(EVENT_SUB_CHANGED)) {
+ mHandler.sendEmptyMessage(EVENT_SUB_CHANGED);
+ }
+ }
+ };
+
+ private final class MyHandler extends Handler {
+ MyHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (VDBG) logv("handleMessage: " + msg);
+ synchronized (mDumpLock) {
+ switch (msg.what) {
+ case EVENT_SUB_CHANGED:
+ onSubChanged();
+ break;
+
+ case EVENT_REGISTER_CALLBACK:
+ onRegisterCallback((ImsStateCallbackController.CallbackWrapper) msg.obj);
+ break;
+
+ case EVENT_UNREGISTER_CALLBACK:
+ onUnregisterCallback((IImsStateCallback) msg.obj);
+ break;
+
+ case EVENT_CARRIER_CONFIG_CHANGED:
+ onCarrierConfigChanged(msg.arg1);
+ break;
+
+ case EVENT_EXTERNAL_RCS_STATE_CHANGED:
+ if (msg.obj == null) break;
+ onExternalRcsStateChanged((ExternalRcsFeatureState) msg.obj);
+ break;
+
+ case EVENT_MSIM_CONFIGURATION_CHANGE:
+ AsyncResult result = (AsyncResult) msg.obj;
+ Integer numSlots = (Integer) result.result;
+ if (numSlots == null) {
+ Log.w(TAG, "msim config change with null num slots");
+ break;
+ }
+ updateFeatureControllerSize(numSlots);
+ break;
+
+ default:
+ loge("Unhandled event " + msg.what);
+ }
+ }
+ }
+ }
+
+ private final class MmTelFeatureListener implements FeatureConnector.Listener<ImsManager> {
+ private FeatureConnector<ImsManager> mConnector;
+ private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private int mState = STATE_UNAVAILABLE;
+ private int mReason = REASON_IMS_SERVICE_DISCONNECTED;
+
+ /*
+ * Remember the last return of verifyImsMmTelConfigured().
+ * true means ImsResolver found an IMS package for FEATURE_MMTEL.
+ *
+ * mReason is updated through connectionUnavailable triggered by ImsResolver.
+ * mHasConfig is update through notifyConfigChanged triggered by mReceiver.
+ * mHasConfig can be a redundancy of (mReason == REASON_NO_IMS_SERVICE_CONFIGURED).
+ * However, when a carrier config changes, we are not sure the order
+ * of execution of connectionUnavailable and notifyConfigChanged.
+ * So, it's safe to use a separated state to retain it.
+ * We assume mHasConfig is true, until it's determined explicitly.
+ */
+ private boolean mHasConfig = true;
+
+ private int mSlotId = -1;
+ private String mLogPrefix = "";
+
+ MmTelFeatureListener(int slotId) {
+ mSlotId = slotId;
+ mLogPrefix = "[" + slotId + ", MMTEL] ";
+ if (VDBG) logv(mLogPrefix + "created");
+
+ mConnector = mMmTelFeatureFactory.create(
+ mApp, slotId, TAG, this, new HandlerExecutor(mHandler));
+ mConnector.connect();
+ }
+
+ void setSubId(int subId) {
+ if (VDBG) logv(mLogPrefix + "setSubId mSubId=" + mSubId + ", subId=" + subId);
+ if (mSubId == subId) return;
+ logd(mLogPrefix + "setSubId changed subId=" + subId);
+
+ mSubId = subId;
+ }
+
+ void destroy() {
+ if (VDBG) logv(mLogPrefix + "destroy");
+ mConnector.disconnect();
+ mConnector = null;
+ }
+
+ @Override
+ public void connectionReady(ImsManager manager) {
+ logd(mLogPrefix + "connectionReady");
+
+ mState = STATE_READY;
+ mReason = AVAILABLE;
+ mHasConfig = true;
+ onFeatureStateChange(mSubId, FEATURE_MMTEL, mState, mReason);
+ }
+
+ @Override
+ public void connectionUnavailable(int reason) {
+ logd(mLogPrefix + "connectionUnavailable reason=" + connectorReasonToString(reason));
+
+ reason = convertReasonType(reason);
+ if (mReason == reason) return;
+
+ connectionUnavailableInternal(reason);
+ }
+
+ private void connectionUnavailableInternal(int reason) {
+ mState = STATE_UNAVAILABLE;
+ mReason = reason;
+
+ /* If having no IMS package for MMTEL,
+ * dicard the reason except REASON_NO_IMS_SERVICE_CONFIGURED. */
+ if (!mHasConfig && reason != REASON_NO_IMS_SERVICE_CONFIGURED) return;
+
+ onFeatureStateChange(mSubId, FEATURE_MMTEL, mState, mReason);
+ }
+
+ void notifyConfigChanged(boolean hasConfig) {
+ if (mHasConfig == hasConfig) return;
+
+ logd(mLogPrefix + "notifyConfigChanged " + hasConfig);
+
+ mHasConfig = hasConfig;
+ if (hasConfig) {
+ // REASON_NO_IMS_SERVICE_CONFIGURED is already reported to the clients,
+ // since there is no configuration of IMS package for MMTEL.
+ // Now, a carrier configuration change is notified and
+ // the response from ImsResolver is changed from false to true.
+ if (mState != STATE_READY) {
+ if (mReason == REASON_NO_IMS_SERVICE_CONFIGURED) {
+ // In this case, notify clients the reason, REASON_DISCONNCTED,
+ // to update the state.
+ connectionUnavailable(UNAVAILABLE_REASON_DISCONNECTED);
+ } else {
+ // ImsResolver and ImsStateCallbackController run with different Looper.
+ // In this case, FeatureConnectorListener is updated ahead of this.
+ // But, connectionUnavailable didn't notify clients since mHasConfig is
+ // false. So, notify clients here.
+ connectionUnavailableInternal(mReason);
+ }
+ }
+ } else {
+ // FeatureConnector doesn't report UNAVAILABLE_REASON_IMS_UNSUPPORTED,
+ // so report the reason here.
+ connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
+ }
+ }
+
+ // called from onRegisterCallback
+ boolean notifyState(CallbackWrapper wrapper) {
+ if (VDBG) logv(mLogPrefix + "notifyState subId=" + wrapper.mSubId);
+
+ return wrapper.notifyState(mSubId, FEATURE_MMTEL, mState, mReason);
+ }
+
+ void dump(IndentingPrintWriter pw) {
+ pw.println("Listener={slotId=" + mSlotId
+ + ", subId=" + mSubId
+ + ", state=" + ImsFeature.STATE_LOG_MAP.get(mState)
+ + ", reason=" + imsStateReasonToString(mReason)
+ + ", hasConfig=" + mHasConfig
+ + "}");
+ }
+ }
+
+ private final class RcsFeatureListener implements FeatureConnector.Listener<RcsFeatureManager> {
+ private FeatureConnector<RcsFeatureManager> mConnector;
+ private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private int mState = STATE_UNAVAILABLE;
+ private int mReason = REASON_IMS_SERVICE_DISCONNECTED;
+
+ /*
+ * Remember the last return of verifyImsMmTelConfigured().
+ * true means ImsResolver found an IMS package for FEATURE_RCS.
+ *
+ * mReason is updated through connectionUnavailable triggered by ImsResolver.
+ * mHasConfig is update through notifyConfigChanged triggered by mReceiver,
+ * and notifyExternalRcsState which triggered by TelephonyRcsService refers it.
+ * mHasConfig can be a redundancy of (mReason == REASON_NO_IMS_SERVICE_CONFIGURED).
+ * However, when a carrier config changes, we are not sure the order
+ * of execution of connectionUnavailable, notifyConfigChanged and notifyExternalRcsState.
+ * So, it's safe to use a separated state to retain it.
+ * We assume mHasConfig is true, until it's determined explicitly.
+ */
+ private boolean mHasConfig = true;
+
+ /*
+ * TelephonyRcsService doesn’t try to connect to RcsFeature if there is no active feature
+ * for a given subscription. The active features are declared by carrier configs and
+ * configuration resources. The APIs of ImsRcsManager and SipDelegateManager are available
+ * only when the RcsFeatureController has a STATE_READY state connection.
+ * This configuration is different from the configuration of IMS package for RCS.
+ * ImsStateCallbackController's FeatureConnectorListener can be STATE_READY state,
+ * even in case there is no active RCS feature. But Manager's APIs throws exception.
+ *
+ * For RCS, in addition to mHasConfig, the sate of TelephonyRcsService and
+ * RcsFeatureConnector will be traced to determine the state to be notified to clients.
+ */
+ private ExternalRcsFeatureState mExternalState = null;
+
+ private int mSlotId = -1;
+ private String mLogPrefix = "";
+
+ RcsFeatureListener(int slotId) {
+ mSlotId = slotId;
+ mLogPrefix = "[" + slotId + ", RCS] ";
+ if (VDBG) logv(mLogPrefix + "created");
+
+ mConnector = mRcsFeatureFactory.create(
+ mApp, slotId, this, new HandlerExecutor(mHandler), TAG);
+ mConnector.connect();
+ }
+
+ void setSubId(int subId) {
+ if (VDBG) logv(mLogPrefix + "setSubId mSubId=" + mSubId + ", subId=" + subId);
+ if (mSubId == subId) return;
+ logd(mLogPrefix + "setSubId changed subId=" + subId);
+
+ mSubId = subId;
+ }
+
+ void destroy() {
+ if (VDBG) logv(mLogPrefix + "destroy");
+
+ mConnector.disconnect();
+ mConnector = null;
+ }
+
+ @Override
+ public void connectionReady(RcsFeatureManager manager) {
+ logd(mLogPrefix + "connectionReady");
+
+ mState = STATE_READY;
+ mReason = AVAILABLE;
+ mHasConfig = true;
+
+ if (mExternalState != null && mExternalState.isReady()) {
+ onFeatureStateChange(mSubId, FEATURE_RCS, mState, mReason);
+ }
+ }
+
+ @Override
+ public void connectionUnavailable(int reason) {
+ logd(mLogPrefix + "connectionUnavailable reason=" + connectorReasonToString(reason));
+
+ reason = convertReasonType(reason);
+ if (mReason == reason) return;
+
+ connectionUnavailableInternal(reason);
+ }
+
+ private void connectionUnavailableInternal(int reason) {
+ mState = STATE_UNAVAILABLE;
+ mReason = reason;
+
+ /* If having no IMS package for RCS,
+ * dicard the reason except REASON_NO_IMS_SERVICE_CONFIGURED. */
+ if (!mHasConfig && reason != REASON_NO_IMS_SERVICE_CONFIGURED) return;
+
+ if (mExternalState == null && reason != REASON_NO_IMS_SERVICE_CONFIGURED) {
+ // Wait until TelephonyRcsService notifies its state.
+ return;
+ }
+
+ if (mExternalState != null && !mExternalState.hasActiveFeatures()) {
+ // notifyExternalRcsState has notified REASON_NO_IMS_SERVICE_CONFIGURED already
+ // ignore it
+ return;
+ }
+
+ if ((mExternalState != null && mExternalState.hasActiveFeatures())
+ || mReason == REASON_NO_IMS_SERVICE_CONFIGURED) {
+ onFeatureStateChange(mSubId, FEATURE_RCS, mState, mReason);
+ }
+ }
+
+ void notifyConfigChanged(boolean hasConfig) {
+ if (mHasConfig == hasConfig) return;
+
+ logd(mLogPrefix + "notifyConfigChanged " + hasConfig);
+
+ mHasConfig = hasConfig;
+ if (hasConfig) {
+ // REASON_NO_IMS_SERVICE_CONFIGURED is already reported to the clients,
+ // since there is no configuration of IMS package for RCS.
+ // Now, a carrier configuration change is notified and
+ // the response from ImsResolver is changed from false to true.
+ if (mState != STATE_READY) {
+ if (mReason == REASON_NO_IMS_SERVICE_CONFIGURED) {
+ // In this case, notify clients the reason, REASON_DISCONNCTED,
+ // to update the state.
+ connectionUnavailable(UNAVAILABLE_REASON_DISCONNECTED);
+ } else {
+ // ImsResolver and ImsStateCallbackController run with different Looper.
+ // In this case, FeatureConnectorListener is updated ahead of this.
+ // But, connectionUnavailable didn't notify clients since mHasConfig is
+ // false. So, notify clients here.
+ connectionUnavailableInternal(mReason);
+ }
+ }
+ } else {
+ // FeatureConnector doesn't report UNAVAILABLE_REASON_IMS_UNSUPPORTED,
+ // so report the reason here.
+ connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
+ }
+ }
+
+ void notifyExternalRcsState(ExternalRcsFeatureState fs) {
+ if (VDBG) {
+ logv(mLogPrefix + "notifyExternalRcsState"
+ + " state=" + (fs.mState == STATE_UNKNOWN
+ ? "" : ImsFeature.STATE_LOG_MAP.get(fs.mState))
+ + ", reason=" + imsStateReasonToString(fs.mReason));
+ }
+
+ ExternalRcsFeatureState oldFs = mExternalState;
+ // External state is from TelephonyRcsService while a feature is added or removed.
+ if (fs.mState == STATE_UNKNOWN) {
+ if (oldFs != null) fs.mState = oldFs.mState;
+ else fs.mState = STATE_UNAVAILABLE;
+ }
+
+ mExternalState = fs;
+
+ // No IMS package found.
+ // REASON_NO_IMS_SERVICE_CONFIGURED is notified to clients already.
+ if (!mHasConfig) return;
+
+ if (fs.hasActiveFeatures()) {
+ if (mState == STATE_READY) {
+ if ((oldFs == null || !oldFs.isReady()) && fs.isReady()) {
+ // it is waiting RcsFeatureConnector's notification.
+ // notify clients here.
+ onFeatureStateChange(mSubId, FEATURE_RCS, mState, mReason);
+ } else if (!fs.isReady()) {
+ // Wait RcsFeatureConnector's notification
+ } else {
+ // ignore duplicated notification
+ }
+ }
+ } else {
+ // notify only once
+ if (oldFs == null || oldFs.hasActiveFeatures()) {
+ if (mReason != REASON_NO_IMS_SERVICE_CONFIGURED) {
+ onFeatureStateChange(
+ mSubId, FEATURE_RCS, STATE_UNAVAILABLE,
+ REASON_NO_IMS_SERVICE_CONFIGURED);
+ }
+ } else {
+ // ignore duplicated notification
+ }
+ }
+ }
+
+ // called from onRegisterCallback
+ boolean notifyState(CallbackWrapper wrapper) {
+ if (VDBG) logv(mLogPrefix + "notifyState subId=" + wrapper.mSubId);
+
+ if (mHasConfig) {
+ if (mExternalState == null) {
+ // Wait until TelephonyRcsService notifies its state.
+ return wrapper.notifyState(mSubId, FEATURE_RCS, STATE_UNAVAILABLE,
+ REASON_IMS_SERVICE_DISCONNECTED);
+ } else if (!mExternalState.hasActiveFeatures()) {
+ return wrapper.notifyState(mSubId, FEATURE_RCS, STATE_UNAVAILABLE,
+ REASON_NO_IMS_SERVICE_CONFIGURED);
+ }
+ }
+
+ return wrapper.notifyState(mSubId, FEATURE_RCS, mState, mReason);
+ }
+
+ void dump(IndentingPrintWriter pw) {
+ pw.println("Listener={slotId=" + mSlotId
+ + ", subId=" + mSubId
+ + ", state=" + ImsFeature.STATE_LOG_MAP.get(mState)
+ + ", reason=" + imsStateReasonToString(mReason)
+ + ", hasConfig=" + mHasConfig
+ + ", isReady=" + (mExternalState == null ? false : mExternalState.isReady())
+ + ", hasFeatures=" + (mExternalState == null ? false
+ : mExternalState.hasActiveFeatures())
+ + "}");
+ }
+ }
+
+ /**
+ * A wrapper class for the callback registered
+ */
+ private static class CallbackWrapper {
+ private final int mSubId;
+ private final int mRequiredFeature;
+ private final IImsStateCallback mCallback;
+ private final IBinder mBinder;
+ private final String mCallingPackage;
+ private int mLastReason = NOT_INITIALIZED;
+
+ CallbackWrapper(int subId, int feature, IImsStateCallback callback,
+ String callingPackage) {
+ mSubId = subId;
+ mRequiredFeature = feature;
+ mCallback = callback;
+ mBinder = callback.asBinder();
+ mCallingPackage = callingPackage;
+ }
+
+ /**
+ * @return false when accessing callback binder throws an Exception.
+ * That means the callback binder is not valid any longer.
+ * The death of remote process can cause this.
+ * This instance shall be removed from the list.
+ */
+ boolean notifyState(int subId, int feature, int state, int reason) {
+ if (VDBG) {
+ logv("CallbackWrapper notifyState subId=" + subId
+ + ", feature=" + ImsFeature.FEATURE_LOG_MAP.get(feature)
+ + ", state=" + ImsFeature.STATE_LOG_MAP.get(state)
+ + ", reason=" + imsStateReasonToString(reason));
+ }
+
+ try {
+ if (state == STATE_READY) {
+ mCallback.onAvailable();
+ } else {
+ mCallback.onUnavailable(reason);
+ }
+ mLastReason = reason;
+ } catch (Exception e) {
+ loge("CallbackWrapper notifyState e=" + e);
+ return false;
+ }
+
+ return true;
+ }
+
+ void notifyInactive() {
+ if (VDBG) logv("CallbackWrapper notifyInactive subId=" + mSubId);
+
+ try {
+ mCallback.onUnavailable(REASON_SUBSCRIPTION_INACTIVE);
+ } catch (Exception e) {
+ // ignored
+ }
+ }
+
+ void dump(IndentingPrintWriter pw) {
+ pw.println("CallbackWrapper={subId=" + mSubId
+ + ", feature=" + ImsFeature.FEATURE_LOG_MAP.get(mRequiredFeature)
+ + ", reason=" + imsStateReasonToString(mLastReason)
+ + ", pkg=" + mCallingPackage
+ + "}");
+ }
+ }
+
+ private static class ExternalRcsFeatureState {
+ private int mSlotId;
+ private int mState = STATE_UNAVAILABLE;
+ private int mReason = NOT_INITIALIZED;
+
+ ExternalRcsFeatureState(int slotId, int state, int reason) {
+ mSlotId = slotId;
+ mState = state;
+ mReason = reason;
+ }
+
+ boolean hasActiveFeatures() {
+ return mReason != REASON_NO_IMS_SERVICE_CONFIGURED;
+ }
+
+ boolean isReady() {
+ return mState == STATE_READY;
+ }
+ }
+
+ /**
+ * create an instance
+ */
+ public static ImsStateCallbackController make(PhoneGlobals app, int numSlots) {
+ synchronized (ImsStateCallbackController.class) {
+ if (sInstance == null) {
+ logd("ImsStateCallbackController created");
+
+ HandlerThread handlerThread = new HandlerThread(TAG);
+ handlerThread.start();
+ sInstance = new ImsStateCallbackController(app, handlerThread.getLooper(), numSlots,
+ ImsManager::getConnector, RcsFeatureManager::getConnector,
+ ImsResolver.getInstance());
+ }
+ }
+ return sInstance;
+ }
+
+ @VisibleForTesting
+ public ImsStateCallbackController(PhoneGlobals app, Looper looper, int numSlots,
+ MmTelFeatureConnectorFactory mmTelFactory, RcsFeatureConnectorFactory rcsFactory,
+ ImsResolver imsResolver) {
+ mApp = app;
+ mHandler = new MyHandler(looper);
+ mImsResolver = imsResolver;
+ mSubscriptionManager = mApp.getSystemService(SubscriptionManager.class);
+ mTelephonyRegistryManager = mApp.getSystemService(TelephonyRegistryManager.class);
+ mMmTelFeatureFactory = mmTelFactory;
+ mRcsFeatureFactory = rcsFactory;
+
+ updateFeatureControllerSize(numSlots);
+
+ mTelephonyRegistryManager.addOnSubscriptionsChangedListener(
+ mSubChangedListener, mSubChangedListener.getHandlerExecutor());
+
+ PhoneConfigurationManager.registerForMultiSimConfigChange(mHandler,
+ EVENT_MSIM_CONFIGURATION_CHANGE, null);
+
+ mApp.registerReceiver(mReceiver, new IntentFilter(
+ CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+
+ onSubChanged();
+ }
+
+ /**
+ * Update the number of {@link RcsFeatureController}s that are created based on the number of
+ * active slots on the device.
+ */
+ @VisibleForTesting
+ public void updateFeatureControllerSize(int newNumSlots) {
+ if (mNumSlots != newNumSlots) {
+ logd("updateFeatures: oldSlots=" + mNumSlots
+ + ", newNumSlots=" + newNumSlots);
+ if (mNumSlots < newNumSlots) {
+ for (int i = mNumSlots; i < newNumSlots; i++) {
+ MmTelFeatureListener m = new MmTelFeatureListener(i);
+ mMmTelFeatureListeners.put(i, m);
+ RcsFeatureListener r = new RcsFeatureListener(i);
+ mRcsFeatureListeners.put(i, r);
+ }
+ } else {
+ for (int i = (mNumSlots - 1); i > (newNumSlots - 1); i--) {
+ MmTelFeatureListener m = mMmTelFeatureListeners.get(i);
+ if (m != null) {
+ mMmTelFeatureListeners.remove(i);
+ m.destroy();
+ }
+ RcsFeatureListener r = mRcsFeatureListeners.get(i);
+ if (r != null) {
+ mRcsFeatureListeners.remove(i);
+ r.destroy();
+ }
+ }
+ }
+ }
+ mNumSlots = newNumSlots;
+ }
+
+ /**
+ * Dependencies for testing.
+ */
+ @VisibleForTesting
+ public void onSubChanged() {
+ for (int i = 0; i < mMmTelFeatureListeners.size(); i++) {
+ MmTelFeatureListener l = mMmTelFeatureListeners.valueAt(i);
+ l.setSubId(getSubId(i));
+ }
+
+ for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
+ RcsFeatureListener l = mRcsFeatureListeners.valueAt(i);
+ l.setSubId(getSubId(i));
+ }
+
+ if (mWrappers.size() == 0) return;
+
+ ArrayList<IBinder> inactiveCallbacks = new ArrayList<>();
+ final int[] activeSubs = mSubscriptionManager.getActiveSubscriptionIdList();
+
+ if (VDBG) logv("onSubChanged activeSubs=" + Arrays.toString(activeSubs));
+
+ // Remove callbacks for inactive subscriptions
+ for (IBinder binder : mWrappers.keySet()) {
+ CallbackWrapper wrapper = mWrappers.get(binder);
+ if (wrapper != null) {
+ if (!isActive(activeSubs, wrapper.mSubId)) {
+ // inactive subscription
+ inactiveCallbacks.add(binder);
+ }
+ } else {
+ // unexpected, remove it
+ inactiveCallbacks.add(binder);
+ }
+ }
+ removeInactiveCallbacks(inactiveCallbacks, "onSubChanged");
+ }
+
+ private void onFeatureStateChange(int subId, int feature, int state, int reason) {
+ if (VDBG) {
+ logv("onFeatureStateChange subId=" + subId
+ + ", feature=" + ImsFeature.FEATURE_LOG_MAP.get(feature)
+ + ", state=" + ImsFeature.STATE_LOG_MAP.get(state)
+ + ", reason=" + imsStateReasonToString(reason));
+ }
+
+ ArrayList<IBinder> inactiveCallbacks = new ArrayList<>();
+ mWrappers.values().forEach(wrapper -> {
+ if (subId == wrapper.mSubId
+ && feature == wrapper.mRequiredFeature
+ && !wrapper.notifyState(subId, feature, state, reason)) {
+ // callback has exception, remove it
+ inactiveCallbacks.add(wrapper.mBinder);
+ }
+ });
+ removeInactiveCallbacks(inactiveCallbacks, "onFeatureStateChange");
+ }
+
+ private void onRegisterCallback(CallbackWrapper wrapper) {
+ if (wrapper == null) return;
+
+ if (VDBG) logv("onRegisterCallback before size=" + mWrappers.size());
+ if (VDBG) {
+ logv("onRegisterCallback subId=" + wrapper.mSubId
+ + ", feature=" + wrapper.mRequiredFeature);
+ }
+
+ // Not sure the following case can happen or not:
+ // step1) Subscription changed
+ // step2) ImsStateCallbackController not processed onSubChanged yet
+ // step3) Client registers with a strange subId
+ // The validity of the subId is checked PhoneInterfaceManager#registerImsStateCallback.
+ // So, register the wrapper here before trying to notifyState.
+ // TODO: implement the recovery for this case, notifying the current reson, in onSubChanged
+ mWrappers.put(wrapper.mBinder, wrapper);
+
+ if (wrapper.mRequiredFeature == FEATURE_MMTEL) {
+ for (int i = 0; i < mMmTelFeatureListeners.size(); i++) {
+ MmTelFeatureListener l = mMmTelFeatureListeners.valueAt(i);
+ if (l.mSubId == wrapper.mSubId
+ && !l.notifyState(wrapper)) {
+ mWrappers.remove(wrapper.mBinder);
+ break;
+ }
+ }
+ } else if (wrapper.mRequiredFeature == FEATURE_RCS) {
+ for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
+ RcsFeatureListener l = mRcsFeatureListeners.valueAt(i);
+ if (l.mSubId == wrapper.mSubId
+ && !l.notifyState(wrapper)) {
+ mWrappers.remove(wrapper.mBinder);
+ break;
+ }
+ }
+ }
+
+ if (VDBG) logv("onRegisterCallback after size=" + mWrappers.size());
+ }
+
+ private void onUnregisterCallback(IImsStateCallback cb) {
+ if (cb == null) return;
+ mWrappers.remove(cb.asBinder());
+ }
+
+ private void onCarrierConfigChanged(int slotId) {
+ if (slotId >= mNumSlots) {
+ logd("onCarrierConfigChanged invalid slotId "
+ + slotId + ", mNumSlots=" + mNumSlots);
+ return;
+ }
+
+ logv("onCarrierConfigChanged slotId=" + slotId);
+
+ boolean hasConfig = verifyImsMmTelConfigured(slotId);
+ if (slotId < mMmTelFeatureListeners.size()) {
+ MmTelFeatureListener listener = mMmTelFeatureListeners.valueAt(slotId);
+ listener.notifyConfigChanged(hasConfig);
+ }
+
+ hasConfig = verifyImsRcsConfigured(slotId);
+ if (slotId < mRcsFeatureListeners.size()) {
+ RcsFeatureListener listener = mRcsFeatureListeners.valueAt(slotId);
+ listener.notifyConfigChanged(hasConfig);
+ }
+ }
+
+ private void onExternalRcsStateChanged(ExternalRcsFeatureState fs) {
+ logv("onExternalRcsStateChanged slotId=" + fs.mSlotId
+ + ", state=" + (fs.mState == STATE_UNKNOWN
+ ? "" : ImsFeature.STATE_LOG_MAP.get(fs.mState))
+ + ", reason=" + imsStateReasonToString(fs.mReason));
+
+ RcsFeatureListener listener = mRcsFeatureListeners.get(fs.mSlotId);
+ if (listener != null) {
+ listener.notifyExternalRcsState(fs);
+ } else {
+ // unexpected state
+ loge("onExternalRcsStateChanged slotId=" + fs.mSlotId + ", no listener.");
+ }
+ }
+
+ /**
+ * Interface to be notified from TelephonyRcsSerice and RcsFeatureController
+ *
+ * @param ready true if feature's state is STATE_READY. Valid only when it is true.
+ * @param hasActiveFeatures true if the RcsFeatureController has active features.
+ */
+ public void notifyExternalRcsStateChanged(
+ int slotId, boolean ready, boolean hasActiveFeatures) {
+ int state = STATE_UNKNOWN;
+ int reason = REASON_IMS_SERVICE_DISCONNECTED;
+
+ if (ready) {
+ // From RcsFeatureController
+ state = STATE_READY;
+ reason = AVAILABLE;
+ } else if (!hasActiveFeatures) {
+ // From TelephonyRcsService
+ reason = REASON_NO_IMS_SERVICE_CONFIGURED;
+ state = STATE_UNAVAILABLE;
+ } else {
+ // From TelephonyRcsService
+ // TelephonyRcsService doesn't know the exact state of FeatureConnection.
+ // Only when there is no feature, we can assume the state.
+ }
+
+ if (VDBG) {
+ logv("notifyExternalRcsStateChanged slotId=" + slotId
+ + ", ready=" + ready
+ + ", hasActiveFeatures=" + hasActiveFeatures);
+ }
+
+ ExternalRcsFeatureState fs = new ExternalRcsFeatureState(slotId, state, reason);
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_EXTERNAL_RCS_STATE_CHANGED, fs));
+ }
+
+ /**
+ * Notifies carrier configuration has changed.
+ */
+ @VisibleForTesting
+ public void notifyCarrierConfigChanged(int slotId) {
+ if (VDBG) logv("notifyCarrierConfigChanged slotId=" + slotId);
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_CARRIER_CONFIG_CHANGED, slotId, 0));
+ }
+ /**
+ * Register IImsStateCallback
+ *
+ * @param feature for which state is changed, ImsFeature.FEATURE_*
+ */
+ public void registerImsStateCallback(int subId, int feature, IImsStateCallback cb,
+ String callingPackage) {
+ if (VDBG) {
+ logv("registerImsStateCallback subId=" + subId
+ + ", feature=" + feature + ", pkg=" + callingPackage);
+ }
+
+ CallbackWrapper wrapper = new CallbackWrapper(subId, feature, cb, callingPackage);
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_CALLBACK, wrapper));
+ }
+
+ /**
+ * Unegister previously registered callback
+ */
+ public void unregisterImsStateCallback(IImsStateCallback cb) {
+ if (VDBG) logv("unregisterImsStateCallback");
+
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_CALLBACK, cb));
+ }
+
+ private void removeInactiveCallbacks(
+ ArrayList<IBinder> inactiveCallbacks, String message) {
+ if (inactiveCallbacks == null || inactiveCallbacks.size() == 0) return;
+
+ if (VDBG) {
+ logv("removeInactiveCallbacks size="
+ + inactiveCallbacks.size() + " from " + message);
+ }
+
+ for (IBinder binder : inactiveCallbacks) {
+ CallbackWrapper wrapper = mWrappers.get(binder);
+ if (wrapper != null) {
+ // Send the reason REASON_SUBSCRIPTION_INACTIVE to the client
+ wrapper.notifyInactive();
+ mWrappers.remove(binder);
+ }
+ }
+ inactiveCallbacks.clear();
+ }
+
+ private int getSubId(int slotId) {
+ Phone phone = mPhoneFactoryProxy.getPhone(slotId);
+ if (phone != null) return phone.getSubId();
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
+
+ private static boolean isActive(final int[] activeSubs, int subId) {
+ for (int i : activeSubs) {
+ if (i == subId) return true;
+ }
+ return false;
+ }
+
+ private static int convertReasonType(int reason) {
+ switch(reason) {
+ case UNAVAILABLE_REASON_NOT_READY:
+ return REASON_IMS_SERVICE_NOT_READY;
+ case UNAVAILABLE_REASON_IMS_UNSUPPORTED:
+ return REASON_NO_IMS_SERVICE_CONFIGURED;
+ default:
+ break;
+ }
+
+ return REASON_IMS_SERVICE_DISCONNECTED;
+ }
+
+ private boolean verifyImsMmTelConfigured(int slotId) {
+ boolean ret = false;
+ if (mImsResolver == null) {
+ loge("verifyImsMmTelConfigured mImsResolver is null");
+ } else {
+ ret = mImsResolver.isImsServiceConfiguredForFeature(slotId, FEATURE_MMTEL);
+ }
+ if (VDBG) logv("verifyImsMmTelConfigured slotId=" + slotId + ", ret=" + ret);
+ return ret;
+ }
+
+ private boolean verifyImsRcsConfigured(int slotId) {
+ boolean ret = false;
+ if (mImsResolver == null) {
+ loge("verifyImsRcsConfigured mImsResolver is null");
+ } else {
+ ret = mImsResolver.isImsServiceConfiguredForFeature(slotId, FEATURE_RCS);
+ }
+ if (VDBG) logv("verifyImsRcsConfigured slotId=" + slotId + ", ret=" + ret);
+ return ret;
+ }
+
+ private static String connectorReasonToString(int reason) {
+ switch(reason) {
+ case UNAVAILABLE_REASON_DISCONNECTED:
+ return "DISCONNECTED";
+ case UNAVAILABLE_REASON_NOT_READY:
+ return "NOT_READY";
+ case UNAVAILABLE_REASON_IMS_UNSUPPORTED:
+ return "IMS_UNSUPPORTED";
+ case UNAVAILABLE_REASON_SERVER_UNAVAILABLE:
+ return "SERVER_UNAVAILABLE";
+ default:
+ break;
+ }
+ return "";
+ }
+
+ private static String imsStateReasonToString(int reason) {
+ switch(reason) {
+ case AVAILABLE:
+ return "READY";
+ case REASON_UNKNOWN_TEMPORARY_ERROR:
+ return "UNKNOWN_TEMPORARY_ERROR";
+ case REASON_UNKNOWN_PERMANENT_ERROR:
+ return "UNKNOWN_PERMANENT_ERROR";
+ case REASON_IMS_SERVICE_DISCONNECTED:
+ return "IMS_SERVICE_DISCONNECTED";
+ case REASON_NO_IMS_SERVICE_CONFIGURED:
+ return "NO_IMS_SERVICE_CONFIGURED";
+ case REASON_SUBSCRIPTION_INACTIVE:
+ return "SUBSCRIPTION_INACTIVE";
+ case REASON_IMS_SERVICE_NOT_READY:
+ return "IMS_SERVICE_NOT_READY";
+ default:
+ break;
+ }
+ return "";
+ }
+
+ /**
+ * PhoneFactory Dependencies for testing.
+ */
+ @VisibleForTesting
+ public interface PhoneFactoryProxy {
+ /**
+ * Override getPhone for testing.
+ */
+ Phone getPhone(int index);
+ }
+
+ private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() {
+ @Override
+ public Phone getPhone(int index) {
+ return PhoneFactory.getPhone(index);
+ }
+ };
+
+ private void release() {
+ if (VDBG) logv("release");
+
+ mTelephonyRegistryManager.removeOnSubscriptionsChangedListener(mSubChangedListener);
+ mApp.unregisterReceiver(mReceiver);
+
+ for (int i = 0; i < mMmTelFeatureListeners.size(); i++) {
+ mMmTelFeatureListeners.valueAt(i).destroy();
+ }
+ mMmTelFeatureListeners.clear();
+
+ for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
+ mRcsFeatureListeners.valueAt(i).destroy();
+ }
+ mRcsFeatureListeners.clear();
+ }
+
+ /**
+ * destroy the instance
+ */
+ @VisibleForTesting
+ public void destroy() {
+ if (VDBG) logv("destroy it");
+
+ release();
+ mHandler.getLooper().quit();
+ }
+
+ /**
+ * get the handler
+ */
+ @VisibleForTesting
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ /**
+ * Determine whether the callback is registered or not
+ */
+ @VisibleForTesting
+ public boolean isRegistered(IImsStateCallback cb) {
+ if (cb == null) return false;
+ return mWrappers.containsKey(cb.asBinder());
+ }
+
+ /**
+ * Dump this instance into a readable format for dumpsys usage.
+ */
+ public void dump(IndentingPrintWriter pw) {
+ pw.increaseIndent();
+ synchronized (mDumpLock) {
+ pw.println("CallbackWrappers:");
+ pw.increaseIndent();
+ mWrappers.values().forEach(wrapper -> wrapper.dump(pw));
+ pw.decreaseIndent();
+ pw.println("MmTelFeatureListeners:");
+ pw.increaseIndent();
+ for (int i = 0; i < mNumSlots; i++) {
+ MmTelFeatureListener l = mMmTelFeatureListeners.get(i);
+ if (l == null) continue;
+ l.dump(pw);
+ }
+ pw.decreaseIndent();
+ pw.println("RcsFeatureListeners:");
+ pw.increaseIndent();
+ for (int i = 0; i < mNumSlots; i++) {
+ RcsFeatureListener l = mRcsFeatureListeners.get(i);
+ if (l == null) continue;
+ l.dump(pw);
+ }
+ pw.decreaseIndent();
+ pw.println("Most recent logs:");
+ pw.increaseIndent();
+ sLocalLog.dump(pw);
+ pw.decreaseIndent();
+ }
+ pw.decreaseIndent();
+ }
+
+ private static void logv(String msg) {
+ Rlog.d(TAG, msg);
+ }
+
+ private static void logd(String msg) {
+ Rlog.d(TAG, msg);
+ sLocalLog.log(msg);
+ }
+
+ private static void loge(String msg) {
+ Rlog.e(TAG, msg);
+ sLocalLog.log(msg);
+ }
+}
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 23cbaac..e7cb28c 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -160,6 +160,7 @@
TelephonyRcsService mTelephonyRcsService;
public PhoneInterfaceManager phoneMgr;
public ImsRcsController imsRcsController;
+ public ImsStateCallbackController mImsStateCallbackController;
CarrierConfigLoader configLoader;
private Phone phoneInEcm;
@@ -463,6 +464,8 @@
imsRcsController = ImsRcsController.init(this);
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS)) {
+ mImsStateCallbackController =
+ ImsStateCallbackController.make(this, PhoneFactory.getPhones().length);
mTelephonyRcsService = new TelephonyRcsService(this,
PhoneFactory.getPhones().length);
mTelephonyRcsService.initialize();
@@ -1035,6 +1038,12 @@
} catch (Exception e) {
e.printStackTrace();
}
+ pw.println("ImsStateCallbackController:");
+ try {
+ if (mImsStateCallbackController != null) mImsStateCallbackController.dump(pw);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
pw.decreaseIndent();
pw.println("------- End PhoneGlobals -------");
}
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index f09b338..3721c40 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -22,6 +22,7 @@
import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_GSM;
import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_IMS;
import static com.android.internal.telephony.PhoneConstants.SUBSCRIPTION_KEY;
+import static com.android.internal.telephony.TelephonyStatsLog.RCS_CLIENT_PROVISIONING_STATS__EVENT__CLIENT_PARAMS_SENT;
import android.Manifest;
import android.Manifest.permission;
@@ -157,6 +158,7 @@
import com.android.internal.telephony.HalVersion;
import com.android.internal.telephony.IBooleanConsumer;
import com.android.internal.telephony.ICallForwardingInfoCallback;
+import com.android.internal.telephony.IImsStateCallback;
import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.INumberVerificationCallback;
import com.android.internal.telephony.ITelephony;
@@ -185,6 +187,7 @@
import com.android.internal.telephony.ims.ImsResolver;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
+import com.android.internal.telephony.metrics.RcsStats;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
import com.android.internal.telephony.uicc.IccIoResult;
@@ -1113,7 +1116,9 @@
// any service for voice call.
if ((callForwardInfo.serviceClass
& CommandsInterface.SERVICE_CLASS_VOICE) > 0) {
- callForwardingInfo = new CallForwardingInfo(true,
+ callForwardingInfo = new CallForwardingInfo(
+ callForwardInfo.status
+ == CommandsInterface.CF_ACTION_ENABLE,
callForwardInfo.reason,
callForwardInfo.number,
callForwardInfo.timeSeconds);
@@ -3411,17 +3416,6 @@
mApp.enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null);
}
- /**
- * Make sure the caller is system.
- *
- * @throws SecurityException if the caller is not system.
- */
- private static void enforceSystemCaller() {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- throw new SecurityException("Caller must be system");
- }
- }
-
private void enforceActiveEmergencySessionPermission() {
mApp.enforceCallingOrSelfPermission(
android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION, null);
@@ -6603,34 +6597,6 @@
}
/**
- * Enable or disable always reporting signal strength changes from radio.
- *
- * @param isEnable {@code true} for enabling; {@code false} for disabling.
- */
- @Override
- public void setAlwaysReportSignalStrength(int subId, boolean isEnable) {
- enforceModifyPermission();
- enforceSystemCaller();
-
- final long identity = Binder.clearCallingIdentity();
- final Phone phone = getPhone(subId);
- try {
- if (phone != null) {
- if (DBG) {
- log("setAlwaysReportSignalStrength: subId=" + subId
- + " isEnable=" + isEnable);
- }
- phone.setAlwaysReportSignalStrength(isEnable);
- } else {
- loge("setAlwaysReportSignalStrength: no phone found for subId="
- + subId);
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- /**
* Get the user enabled state of Mobile Data.
*
* TODO: remove and use isUserDataEnabled.
@@ -8340,6 +8306,16 @@
int result = (int) sendRequest(CMD_ENABLE_VONR, enabled, subId,
workSource);
if (DBG) log("setVoNrEnabled result: " + result);
+
+ if (result == TelephonyManager.ENABLE_VONR_SUCCESS) {
+ if (DBG) {
+ log("Set VoNR settings in siminfo db; subId=" + subId + ", value:" + enabled);
+ }
+ SubscriptionManager.setSubscriptionProperty(
+ subId, SubscriptionManager.NR_ADVANCED_CALLING_ENABLED,
+ (enabled ? "1" : "0"));
+ }
+
return result;
} finally {
Binder.restoreCallingIdentity(identity);
@@ -10527,6 +10503,9 @@
} else {
configBinder.setRcsClientConfiguration(rcc);
}
+
+ RcsStats.getInstance().onRcsClientProvisioningStats(subId,
+ RCS_CLIENT_PROVISIONING_STATS__EVENT__CLIENT_PARAMS_SENT);
} catch (RemoteException e) {
Rlog.e(LOG_TAG, "fail to setRcsClientConfiguration " + e.getMessage());
throw new ServiceSpecificException(ImsException.CODE_ERROR_SERVICE_UNAVAILABLE,
@@ -10929,7 +10908,7 @@
mApp.getSystemService(AppOpsManager.class)
.checkPackage(callingUid, callingPackage);
- validateSignalStrengthUpdateRequest(request, callingUid);
+ validateSignalStrengthUpdateRequest(mApp, request, callingUid);
final long identity = Binder.clearCallingIdentity();
try {
@@ -10968,19 +10947,19 @@
}
}
- private static void validateSignalStrengthUpdateRequest(SignalStrengthUpdateRequest request,
- int callingUid) {
+ private static void validateSignalStrengthUpdateRequest(Context context,
+ SignalStrengthUpdateRequest request, int callingUid) {
if (callingUid == Process.PHONE_UID || callingUid == Process.SYSTEM_UID) {
// phone/system process do not have further restriction on request
return;
}
// Applications has restrictions on how to use the request:
- // Only system caller can set mIsSystemThresholdReportingRequestedWhileIdle
+ // Non-system callers need permission to set mIsSystemThresholdReportingRequestedWhileIdle
if (request.isSystemThresholdReportingRequestedWhileIdle()) {
- // This is not system caller which has been checked above
- throw new IllegalArgumentException(
- "Only system can set isSystemThresholdReportingRequestedWhileIdle");
+ context.enforceCallingOrSelfPermission(
+ android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH,
+ "validateSignalStrengthUpdateRequest");
}
for (SignalThresholdInfo info : request.getSignalThresholdInfos()) {
@@ -11061,4 +11040,73 @@
Binder.restoreCallingIdentity(identity);
}
}
+
+ /**
+ * Register an IMS connection state callback
+ */
+ @Override
+ public void registerImsStateCallback(int subId, int feature, IImsStateCallback cb,
+ String callingPackage) {
+ if (feature == ImsFeature.FEATURE_MMTEL) {
+ // ImsMmTelManager
+ // The following also checks READ_PRIVILEGED_PHONE_STATE.
+ TelephonyPermissions
+ .enforceCallingOrSelfReadPrecisePhoneStatePermissionOrCarrierPrivilege(
+ mApp, subId, "registerImsStateCallback");
+ } else if (feature == ImsFeature.FEATURE_RCS) {
+ // ImsRcsManager or SipDelegateManager
+ TelephonyPermissions.enforceAnyPermissionGrantedOrCarrierPrivileges(mApp, subId,
+ Binder.getCallingUid(), "registerImsStateCallback",
+ Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ Manifest.permission.READ_PRECISE_PHONE_STATE,
+ Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE,
+ Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION);
+ }
+
+ if (!ImsManager.isImsSupportedOnDevice(mApp)) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "IMS not available on device.");
+ }
+
+ if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_INVALID_SUBSCRIPTION);
+ }
+
+ ImsStateCallbackController controller = ImsStateCallbackController.getInstance();
+ if (controller == null) {
+ throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+ "IMS not available on device.");
+ }
+
+ if (callingPackage == null) {
+ callingPackage = getCurrentPackageName();
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ int slotId = getSlotIndexOrException(subId);
+ controller.registerImsStateCallback(subId, feature, cb, callingPackage);
+ } catch (ImsException e) {
+ throw new ServiceSpecificException(e.getCode());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Unregister an IMS connection state callback
+ */
+ @Override
+ public void unregisterImsStateCallback(IImsStateCallback cb) {
+ final long token = Binder.clearCallingIdentity();
+ ImsStateCallbackController controller = ImsStateCallbackController.getInstance();
+ if (controller == null) {
+ return;
+ }
+ try {
+ controller.unregisterImsStateCallback(cb);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
}
diff --git a/src/com/android/phone/RcsProvisioningMonitor.java b/src/com/android/phone/RcsProvisioningMonitor.java
index 6d2bd6f..e819afc 100644
--- a/src/com/android/phone/RcsProvisioningMonitor.java
+++ b/src/com/android/phone/RcsProvisioningMonitor.java
@@ -16,6 +16,10 @@
package com.android.phone;
+import static com.android.internal.telephony.TelephonyStatsLog.RCS_ACS_PROVISIONING_STATS__RESPONSE_TYPE__PROVISIONING_XML;
+import static com.android.internal.telephony.TelephonyStatsLog.RCS_CLIENT_PROVISIONING_STATS__EVENT__DMA_CHANGED;
+import static com.android.internal.telephony.TelephonyStatsLog.RCS_CLIENT_PROVISIONING_STATS__EVENT__TRIGGER_RCS_RECONFIGURATION;
+
import android.Manifest;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
@@ -47,6 +51,8 @@
import com.android.ims.FeatureUpdates;
import com.android.ims.RcsFeatureManager;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.metrics.RcsStats;
+import com.android.internal.telephony.metrics.RcsStats.RcsProvisioningCallback;
import com.android.internal.telephony.util.HandlerExecutor;
import com.android.internal.util.CollectionUtils;
import com.android.telephony.Rlog;
@@ -100,6 +106,8 @@
private final RoleManagerAdapter mRoleManager;
private FeatureConnectorFactory<RcsFeatureManager> mFeatureFactory;
+ private RcsStats mRcsStats;
+
private static RcsProvisioningMonitor sInstance;
private final SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener =
@@ -227,6 +235,10 @@
if (mSingleRegistrationCapability != singleRegistrationCapability) {
mSingleRegistrationCapability = singleRegistrationCapability;
notifyDma();
+
+ // update whether single registration supported.
+ mRcsStats.setEnableSingleRegistration(mSubId,
+ mSingleRegistrationCapability == ProvisioningManager.STATUS_CAPABLE);
}
}
@@ -338,6 +350,9 @@
} else {
notifyRcsAutoConfigurationReceived();
}
+
+ // check callback for metrics if not registered, register callback
+ registerMetricsCallback();
} else {
// clear callbacks if rcs disconnected
clearCallbacks();
@@ -397,6 +412,18 @@
}
}
}
+
+ private void registerMetricsCallback() {
+ RcsProvisioningCallback rcsProvisioningCallback = mRcsStats.getRcsProvisioningCallback(
+ mSubId, mSingleRegistrationCapability == ProvisioningManager.STATUS_CAPABLE);
+
+ // if not yet registered, register callback and set registered value
+ if (rcsProvisioningCallback != null && !rcsProvisioningCallback.getRegistered()) {
+ if (addRcsConfigCallback(rcsProvisioningCallback)) {
+ rcsProvisioningCallback.setRegistered(true);
+ }
+ }
+ }
}
@VisibleForTesting
@@ -454,7 +481,7 @@
@VisibleForTesting
public RcsProvisioningMonitor(PhoneGlobals app, Looper looper, RoleManagerAdapter roleManager,
- FeatureConnectorFactory<RcsFeatureManager> factory) {
+ FeatureConnectorFactory<RcsFeatureManager> factory, RcsStats rcsStats) {
mPhone = app;
mHandler = new MyHandler(looper);
mCarrierConfigManager = mPhone.getSystemService(CarrierConfigManager.class);
@@ -465,6 +492,7 @@
logv("DMA is " + mDmaPackageName);
mDmaChangedListener = new DmaChangedListener();
mFeatureFactory = factory;
+ mRcsStats = rcsStats;
init();
}
@@ -477,7 +505,8 @@
HandlerThread handlerThread = new HandlerThread(TAG);
handlerThread.start();
sInstance = new RcsProvisioningMonitor(app, handlerThread.getLooper(),
- new RoleManagerAdapterImpl(app), RcsFeatureManager::getConnector);
+ new RoleManagerAdapterImpl(app), RcsFeatureManager::getConnector,
+ RcsStats.getInstance());
}
return sInstance;
}
@@ -704,6 +733,10 @@
logv("acs not used, set cached config and notify.");
v.setConfig(cachedConfig);
}
+
+ // store RCS metrics - DMA changed event
+ mRcsStats.onRcsClientProvisioningStats(k,
+ RCS_CLIENT_PROVISIONING_STATS__EVENT__DMA_CHANGED);
});
}
}
@@ -803,6 +836,14 @@
}
info.setConfig(isCompressed ? RcsConfig.decompressGzip(config) : config);
updateConfigForSub(subId, config, isCompressed);
+
+ // Supporting ACS means config data comes from ACS
+ // store RCS metrics - received provisioning event
+ if (isAcsUsed(subId)) {
+ mRcsStats.onRcsAcsProvisioningStats(subId, 200,
+ RCS_ACS_PROVISIONING_STATS__RESPONSE_TYPE__PROVISIONING_XML,
+ isRcsVolteSingleRegistrationEnabled(subId));
+ }
}
private void onReconfigRequest(int subId) {
@@ -814,6 +855,10 @@
updateConfigForSub(subId, null, true);
info.triggerRcsReconfiguration();
}
+
+ // store RCS metrics - reconfig event
+ mRcsStats.onRcsClientProvisioningStats(subId,
+ RCS_CLIENT_PROVISIONING_STATS__EVENT__TRIGGER_RCS_RECONFIGURATION);
}
private void notifyDmaForSub(int subId, int capability) {
diff --git a/src/com/android/phone/settings/RadioInfo.java b/src/com/android/phone/settings/RadioInfo.java
index d4c926e..7681a02 100644
--- a/src/com/android/phone/settings/RadioInfo.java
+++ b/src/com/android/phone/settings/RadioInfo.java
@@ -17,6 +17,7 @@
package com.android.phone.settings;
import static android.net.ConnectivityManager.NetworkCallback;
+
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.ComponentName;
@@ -57,7 +58,6 @@
import android.telephony.CellSignalStrengthLte;
import android.telephony.CellSignalStrengthWcdma;
import android.telephony.DataSpecificRegistrationInfo;
-import android.telephony.data.NetworkSlicingConfig;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhysicalChannelConfig;
import android.telephony.RadioAccessFamily;
@@ -66,6 +66,7 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
+import android.telephony.data.NetworkSlicingConfig;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
@@ -102,8 +103,8 @@
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeoutException;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* Radio Information Class
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 0be927a..c62b4fa 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -257,7 +257,7 @@
@Override
public void onConnectionPropertiesChanged(Connection c, int connectionProperties) {
- Log.d(this, "onConnectionPropertiesChanged: Connection: %s,"
+ Log.i(ImsConference.this, "onConnectionPropertiesChanged: Connection: %s,"
+ " connectionProperties: %s", c, connectionProperties);
updateConnectionProperties(connectionProperties);
}
@@ -383,6 +383,11 @@
private boolean mIsUsingSimCallManager = false;
/**
+ * See {@link #isRemotelyHosted()} for details.
+ */
+ private boolean mWasRemotelyHosted = false;
+
+ /**
* Where {@link #isMultiparty()} is {@code false}, contains the
* {@link ConferenceParticipantConnection#getUserEntity()} and
* {@link ConferenceParticipantConnection#getEndpoint()} of the single participant which this
@@ -522,11 +527,16 @@
(properties & Connection.PROPERTY_IS_EXTERNAL_CALL) != 0);
conferenceProperties = changeBitmask(conferenceProperties,
- Connection.PROPERTY_REMOTELY_HOSTED, !isConferenceHost());
+ Connection.PROPERTY_REMOTELY_HOSTED, isRemotelyHosted());
conferenceProperties = changeBitmask(conferenceProperties,
Connection.PROPERTY_IS_ADHOC_CONFERENCE,
(properties & Connection.PROPERTY_IS_ADHOC_CONFERENCE) != 0);
+ Log.i(this, "applyHostProperties: confProp=%s", conferenceProperties);
+
+ conferenceProperties = changeBitmask(conferenceProperties,
+ Connection.PROPERTY_CROSS_SIM,
+ (properties & Connection.PROPERTY_CROSS_SIM) != 0);
return conferenceProperties;
}
@@ -774,6 +784,26 @@
}
/**
+ * Returns whether the conference is remotely hosted or not.
+ * This method will cache the current remotely hosted state when the conference host or
+ * original connection becomes null. This is important for scenarios where the conference host
+ * or original connection changes midway through a conference such as in an SRVCC scenario.
+ * @return {@code true} if the conference was remotely hosted based on the conference host and
+ * its original connection, or based on the last known remotely hosted state. {@code false}
+ * otherwise.
+ */
+ public boolean isRemotelyHosted() {
+ if (mConferenceHost == null || mConferenceHost.getOriginalConnection() == null) {
+ return mWasRemotelyHosted;
+ }
+ com.android.internal.telephony.Connection originalConnection =
+ mConferenceHost.getOriginalConnection();
+ mWasRemotelyHosted = originalConnection.isMultiparty()
+ && !originalConnection.isConferenceHost();
+ return mWasRemotelyHosted;
+ }
+
+ /**
* Determines if this conference is hosted on the current device or the peer device.
*
* @return {@code true} if this conference is hosted on the current device, {@code false} if it
@@ -1209,6 +1239,7 @@
ConferenceParticipantConnection connection = new ConferenceParticipantConnection(
parent.getOriginalConnection(), participant,
!isConferenceHost() /* isRemotelyHosted */);
+
if (participant.getConnectTime() == 0) {
connection.setConnectTimeMillis(parent.getConnectTimeMillis());
connection.setConnectionStartElapsedRealtimeMillis(
diff --git a/src/com/android/services/telephony/TelephonyConferenceController.java b/src/com/android/services/telephony/TelephonyConferenceController.java
index 228541a..9aa3dbe 100644
--- a/src/com/android/services/telephony/TelephonyConferenceController.java
+++ b/src/com/android/services/telephony/TelephonyConferenceController.java
@@ -40,6 +40,7 @@
*/
final class TelephonyConferenceController {
private static final int TELEPHONY_CONFERENCE_MAX_SIZE = 5;
+ private static final String RIL_REPORTED_CONFERENCE_CALL_STRING = "Conference Call";
private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
new TelephonyConnection.TelephonyConnectionListener() {
@@ -179,7 +180,6 @@
private void recalculateConference() {
Set<TelephonyConnection> conferencedConnections = new HashSet<>();
int numGsmConnections = 0;
-
for (TelephonyConnection connection : mTelephonyConnections) {
com.android.internal.telephony.Connection radioConnection =
connection.getOriginalConnection();
@@ -271,11 +271,19 @@
// Remove all instances of PROPERTY_IS_DOWNGRADED_CONFERENCE. This
// property should only be set on the parent call (i.e. the newly
// created TelephonyConference.
- Log.d(this, "Removing PROPERTY_IS_DOWNGRADED_CONFERENCE from connection"
- + " %s", connection);
- int newProperties = connection.getConnectionProperties()
- & ~Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE;
- connection.setTelephonyConnectionProperties(newProperties);
+ // This doesn't apply to a connection whose address is "Conference
+ // Call", which may be updated by some modem to create a connection
+ // to represent a merged conference connection in SRVCC.
+ if (connection.getAddress() == null
+ || !connection.getAddress().getSchemeSpecificPart()
+ .equalsIgnoreCase(
+ RIL_REPORTED_CONFERENCE_CALL_STRING)) {
+ Log.d(this, "Removing PROPERTY_IS_DOWNGRADED_CONFERENCE"
+ + " from connection %s", connection);
+ int newProperties = connection.getConnectionProperties()
+ & ~Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE;
+ connection.setTelephonyConnectionProperties(newProperties);
+ }
isDowngradedConference = true;
}
mTelephonyConference.addTelephonyConnection(connection);
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 18a40cf..ed07726 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -3814,4 +3814,13 @@
mCommunicator.sendMessages(set);
}
}
+
+ /**
+ * Returns the current telephony connection listeners for test purposes.
+ * @return list of telephony connection listeners.
+ */
+ @VisibleForTesting
+ public List<TelephonyConnectionListener> getTelephonyConnectionListeners() {
+ return new ArrayList<>(mTelephonyListeners);
+ }
}
diff --git a/src/com/android/services/telephony/rcs/DelegateStateTracker.java b/src/com/android/services/telephony/rcs/DelegateStateTracker.java
index 321c7ba..18aab88 100644
--- a/src/com/android/services/telephony/rcs/DelegateStateTracker.java
+++ b/src/com/android/services/telephony/rcs/DelegateStateTracker.java
@@ -24,9 +24,12 @@
import android.telephony.ims.aidl.ISipDelegate;
import android.telephony.ims.aidl.ISipDelegateConnectionStateCallback;
import android.telephony.ims.stub.DelegateConnectionStateCallback;
+import android.util.ArraySet;
import android.util.LocalLog;
import android.util.Log;
+import com.android.internal.telephony.metrics.RcsStats;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -50,11 +53,15 @@
private boolean mCreatedCalled = false;
private int mRegistrationStateOverride = -1;
+ private Set<String> mDelegateSupportedTags;
+ private final RcsStats mRcsStats;
+
public DelegateStateTracker(int subId, ISipDelegateConnectionStateCallback appStateCallback,
- ISipDelegate localDelegateImpl) {
+ ISipDelegate localDelegateImpl, RcsStats rcsStats) {
mSubId = subId;
mAppStateCallback = appStateCallback;
mLocalDelegateImpl = localDelegateImpl;
+ mRcsStats = rcsStats;
}
/**
@@ -63,10 +70,13 @@
* Registration and state updates will be send via the
* {@link SipDelegateBinderConnection.StateCallback} callback implemented by this class as they
* arrive.
+ * @param supportedTags the tags supported by the SipTransportController and ImsService creating
+ * the SipDelegate. These tags will be used as a key for SipDelegate
+ * metrics.
* @param deniedTags The tags denied by the SipTransportController and ImsService creating the
* SipDelegate. These tags will need to be notified back to the IMS application.
*/
- public void sipDelegateConnected(Set<FeatureTagState> deniedTags) {
+ public void sipDelegateConnected(Set<String> supportedTags, Set<FeatureTagState> deniedTags) {
logi("SipDelegate connected with denied tags:" + deniedTags);
// From the IMS application perspective, we only call onCreated/onDestroyed once and
// provide the local implementation of ISipDelegate, which doesn't change, even though
@@ -74,6 +84,8 @@
if (!mCreatedCalled) {
mCreatedCalled = true;
notifySipDelegateCreated();
+ mDelegateSupportedTags = supportedTags;
+ mRcsStats.createSipDelegateStats(mSubId, mDelegateSupportedTags);
}
mRegistrationStateOverride = -1;
mDelegateDeniedTags = new ArrayList<>(deniedTags);
@@ -84,8 +96,8 @@
*
* This will trigger an override of the IMS application's registration state. All feature tags
* in the REGISTERED state will be overridden to move to the deregistering state specified until
- * a new SipDelegate was successfully created and {@link #sipDelegateConnected(Set)} was called
- * or it was destroyed and {@link #sipDelegateDestroyed(int)} was called.
+ * a new SipDelegate was successfully created and {@link #sipDelegateConnected(Set, Set)} was
+ * called or it was destroyed and {@link #sipDelegateDestroyed(int)} was called.
* @param deregisteringReason The new deregistering reason that all feature tags in the
* registered state should now report.
*/
@@ -115,6 +127,7 @@
mRegistrationStateOverride = -1;
try {
mAppStateCallback.onDestroyed(reason);
+ mRcsStats.onSipDelegateStats(mSubId, mDelegateSupportedTags, reason);
} catch (RemoteException e) {
logw("sipDelegateDestroyed: IMS application is dead: " + e);
}
@@ -141,6 +154,11 @@
logi("onRegistrationStateChanged: sending reg state " + registrationState);
try {
mAppStateCallback.onFeatureTagStatusChanged(registrationState, mDelegateDeniedTags);
+ Set<String> registeredFeatureTags = registrationState.getRegisteredFeatureTags();
+ mRcsStats.onSipTransportFeatureTagStats(mSubId,
+ new ArraySet<FeatureTagState>(mDelegateDeniedTags),
+ registrationState.getDeregisteredFeatureTags(),
+ registeredFeatureTags);
} catch (RemoteException e) {
logw("onRegistrationStateChanged: IMS application is dead: " + e);
}
diff --git a/src/com/android/services/telephony/rcs/RcsFeatureController.java b/src/com/android/services/telephony/rcs/RcsFeatureController.java
index 7834903..cc1a2cc 100644
--- a/src/com/android/services/telephony/rcs/RcsFeatureController.java
+++ b/src/com/android/services/telephony/rcs/RcsFeatureController.java
@@ -33,6 +33,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.imsphone.ImsRegistrationCallbackHelper;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.phone.ImsStateCallbackController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -139,6 +140,8 @@
// ImsService is gone.
updateConnectionStatus(manager);
setupConnectionToService(manager);
+ ImsStateCallbackController.getInstance()
+ .notifyExternalRcsStateChanged(mSlotId, true, true);
} catch (ImsException e) {
updateConnectionStatus(null /*manager*/);
// Use deprecated Exception for compatibility.
diff --git a/src/com/android/services/telephony/rcs/SipDelegateController.java b/src/com/android/services/telephony/rcs/SipDelegateController.java
index 8cc70a4..c728141 100644
--- a/src/com/android/services/telephony/rcs/SipDelegateController.java
+++ b/src/com/android/services/telephony/rcs/SipDelegateController.java
@@ -34,6 +34,7 @@
import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.metrics.RcsStats;
import com.android.internal.util.IndentingPrintWriter;
import java.io.PrintWriter;
@@ -102,7 +103,7 @@
mMessageTransportWrapper = new MessageTransportWrapper(mSubId, executorService,
messageCallback);
mDelegateStateTracker = new DelegateStateTracker(mSubId, stateCallback,
- mMessageTransportWrapper.getDelegateConnection());
+ mMessageTransportWrapper.getDelegateConnection(), RcsStats.getInstance());
}
/**
@@ -191,7 +192,7 @@
.collect(Collectors.toSet()));
mMessageTransportWrapper.openTransport(resultPair.first, allowedTags,
resultPair.second);
- mDelegateStateTracker.sipDelegateConnected(resultPair.second);
+ mDelegateStateTracker.sipDelegateConnected(allowedTags, resultPair.second);
return true;
});
}
diff --git a/src/com/android/services/telephony/rcs/SipSessionTracker.java b/src/com/android/services/telephony/rcs/SipSessionTracker.java
index 5ab482f..68e3065 100644
--- a/src/com/android/services/telephony/rcs/SipSessionTracker.java
+++ b/src/com/android/services/telephony/rcs/SipSessionTracker.java
@@ -24,6 +24,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.SipMessageParsingUtils;
+import com.android.internal.telephony.metrics.RcsStats;
import com.android.internal.util.IndentingPrintWriter;
import java.io.PrintWriter;
@@ -66,6 +67,14 @@
// associated pending operation.
private final ArrayMap<String, Runnable> mPendingAck = new ArrayMap<>();
+ private final RcsStats mRcsStats;
+ int mSubId;
+
+ public SipSessionTracker(int subId, RcsStats rcsStats) {
+ mSubId = subId;
+ mRcsStats = rcsStats;
+ }
+
/**
* Filter a SIP message to determine if it will result in a new SIP dialog. This will need to be
* successfully acknowledged by the remote IMS stack using
@@ -73,10 +82,10 @@
*
* @param message The Incoming SIP message.
*/
- public void filterSipMessage(SipMessage message) {
+ public void filterSipMessage(int direction, SipMessage message) {
final Runnable r;
if (startsEarlyDialog(message)) {
- r = getCreateDialogRunnable(message);
+ r = getCreateDialogRunnable(direction, message);
} else if (closesDialog(message)) {
r = getCloseDialogRunnable(message);
} else if (SipMessageParsingUtils.isSipResponse(message.getStartLine())) {
@@ -137,6 +146,8 @@
if (dialogsToCleanup.isEmpty()) return;
logi("Cleanup dialogs associated with call id: " + callId);
for (SipDialog d : dialogsToCleanup) {
+ mRcsStats.onSipTransportSessionClosed(mSubId, callId, 0,
+ d.getState() == d.STATE_CLOSED);
d.close();
logi("Dialog closed: " + d);
}
@@ -197,6 +208,9 @@
* Clears all tracked sessions.
*/
public void clearAllSessions() {
+ for (SipDialog d : mTrackedDialogs) {
+ mRcsStats.onSipTransportSessionClosed(mSubId, d.getCallId(), 0, false);
+ }
mTrackedDialogs.clear();
mPendingAck.clear();
}
@@ -262,7 +276,7 @@
return SIP_CLOSE_DIALOG_REQUEST_METHOD.equalsIgnoreCase(startLineSegments[0]);
}
- private Runnable getCreateDialogRunnable(SipMessage m) {
+ private Runnable getCreateDialogRunnable(int direction, SipMessage m) {
return () -> {
List<SipDialog> duplicateDialogs = mTrackedDialogs.stream()
.filter(d -> d.getCallId().equals(m.getCallIdParameter()))
@@ -273,6 +287,10 @@
return;
}
SipDialog dialog = SipDialog.fromSipMessage(m);
+ String[] startLineSegments =
+ SipMessageParsingUtils.splitStartLineAndVerify(m.getStartLine());
+ mRcsStats.earlySipTransportSession(startLineSegments[0], dialog.getCallId(),
+ direction);
logi("Starting new SipDialog: " + dialog);
mTrackedDialogs.add(dialog);
};
@@ -285,6 +303,7 @@
.collect(Collectors.toList());
if (dialogsToClose.isEmpty()) return;
logi("Closing dialogs associated with: " + m);
+ mRcsStats.onSipTransportSessionClosed(mSubId, m.getCallIdParameter(), 0, true);
for (SipDialog d : dialogsToClose) {
d.close();
logi("Dialog closed: " + d);
@@ -344,11 +363,13 @@
if (statusCode <= 100) return;
// If 300+, then this dialog has received an error response and should move to closed state.
if (statusCode >= 300) {
+ mRcsStats.onSipTransportSessionClosed(mSubId, m.getCallIdParameter(), statusCode, true);
d.close();
return;
}
if (toTag == null) logw("updateSipDialogState: No to tag for message: " + m);
if (statusCode >= 200) {
+ mRcsStats.confirmedSipTransportSession(m.getCallIdParameter(), statusCode);
d.confirm(toTag);
return;
}
diff --git a/src/com/android/services/telephony/rcs/TelephonyRcsService.java b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
index 034382c..dfcea74 100644
--- a/src/com/android/services/telephony/rcs/TelephonyRcsService.java
+++ b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
@@ -32,7 +32,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.PhoneConfigurationManager;
+import com.android.internal.telephony.metrics.RcsStats;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.phone.ImsStateCallbackController;
import com.android.phone.R;
import java.io.FileDescriptor;
@@ -163,6 +165,7 @@
mFeatureControllers = new SparseArray<>(numSlots);
mSlotToAssociatedSubIds = new SparseArray<>(numSlots);
mRcsUceEnabled = sResourceProxy.getDeviceUceEnabled(mContext);
+ RcsStats.getInstance().registerUceCallback();
}
@VisibleForTesting
@@ -173,6 +176,7 @@
mSlotToAssociatedSubIds = new SparseArray<>(numSlots);
sResourceProxy = resourceProxy;
mRcsUceEnabled = sResourceProxy.getDeviceUceEnabled(mContext);
+ RcsStats.getInstance().registerUceCallback();
}
/**
@@ -310,6 +314,9 @@
}
// Only start the connection procedure if we have active features.
if (c.hasActiveFeatures()) c.connect();
+
+ ImsStateCallbackController.getInstance()
+ .notifyExternalRcsStateChanged(slotId, false, c.hasActiveFeatures());
}
/**
diff --git a/src/com/android/services/telephony/rcs/TransportSipMessageValidator.java b/src/com/android/services/telephony/rcs/TransportSipMessageValidator.java
index 777026c..3b1b26d 100644
--- a/src/com/android/services/telephony/rcs/TransportSipMessageValidator.java
+++ b/src/com/android/services/telephony/rcs/TransportSipMessageValidator.java
@@ -16,6 +16,9 @@
package com.android.services.telephony.rcs;
+import static com.android.internal.telephony.TelephonyStatsLog.SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__INCOMING;
+import static com.android.internal.telephony.TelephonyStatsLog.SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING;
+
import android.telephony.ims.DelegateRegistrationState;
import android.telephony.ims.FeatureTagState;
import android.telephony.ims.SipDelegateConfiguration;
@@ -26,6 +29,8 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.SipMessageParsingUtils;
+import com.android.internal.telephony.metrics.RcsStats;
import com.android.internal.util.IndentingPrintWriter;
import com.android.services.telephony.rcs.validator.IncomingTransportStateValidator;
import com.android.services.telephony.rcs.validator.MalformedSipMessageValidator;
@@ -145,11 +150,13 @@
private PendingTask mPendingClose;
private PendingRegCleanupTask mPendingRegCleanup;
private Consumer<Set<String>> mRegistrationAppliedConsumer;
+ private final RcsStats mRcsStats;
public TransportSipMessageValidator(int subId, ScheduledExecutorService executor) {
mSubId = subId;
mExecutor = executor;
- mSipSessionTracker = new SipSessionTracker();
+ mRcsStats = RcsStats.getInstance();
+ mSipSessionTracker = new SipSessionTracker(subId, mRcsStats);
mOutgoingTransportStateValidator = new OutgoingTransportStateValidator(mSipSessionTracker);
mIncomingTransportStateValidator = new IncomingTransportStateValidator();
mOutgoingMessageValidator = new MalformedSipMessageValidator().andThen(
@@ -163,7 +170,7 @@
public TransportSipMessageValidator(int subId, ScheduledExecutorService executor,
SipSessionTracker sipSessionTracker,
OutgoingTransportStateValidator outgoingStateValidator,
- IncomingTransportStateValidator incomingStateValidator) {
+ IncomingTransportStateValidator incomingStateValidator, RcsStats rcsStats) {
mSubId = subId;
mExecutor = executor;
mSipSessionTracker = sipSessionTracker;
@@ -171,6 +178,7 @@
mIncomingTransportStateValidator = incomingStateValidator;
mOutgoingMessageValidator = mOutgoingTransportStateValidator;
mIncomingMessageValidator = mIncomingTransportStateValidator;
+ mRcsStats = rcsStats;
}
/**
@@ -365,7 +373,11 @@
}
ValidationResult result = mOutgoingMessageValidator.validate(message);
logi("verifyOutgoingMessage: " + result + ", message=" + message);
- if (result.isValidated) mSipSessionTracker.filterSipMessage(message);
+ if (result.isValidated) {
+ mSipSessionTracker.filterSipMessage(
+ SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, message);
+ }
+ updateForMetrics(SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, message, result);
return result;
}
@@ -378,7 +390,11 @@
public ValidationResult verifyIncomingMessage(SipMessage message) {
ValidationResult result = mIncomingMessageValidator.validate(message);
logi("verifyIncomingMessage: " + result + ", message=" + message);
- if (result.isValidated) mSipSessionTracker.filterSipMessage(message);
+ if (result.isValidated) {
+ mSipSessionTracker.filterSipMessage(
+ SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__INCOMING, message);
+ }
+ updateForMetrics(SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__INCOMING, message, result);
return result;
}
@@ -539,6 +555,28 @@
.collect(Collectors.toSet());
}
+ private void updateForMetrics(int direction, SipMessage m, ValidationResult result) {
+ String[] startLineSegments = SipMessageParsingUtils
+ .splitStartLineAndVerify(m.getStartLine());
+ if (SipMessageParsingUtils.isSipRequest(m.getStartLine())) {
+ if (result.isValidated) {
+ // SipMessage add to list for Metrics stats
+ mRcsStats.onSipMessageRequest(m.getCallIdParameter(), startLineSegments[0],
+ direction);
+ } else {
+ //Message sending fail and there is no response.
+ mRcsStats.invalidatedMessageResult(mSubId, startLineSegments[0], direction,
+ result.restrictedReason);
+ }
+ } else if (SipMessageParsingUtils.isSipResponse(m.getStartLine())) {
+ int statusCode = Integer.parseInt(startLineSegments[1]);
+ mRcsStats.onSipMessageResponse(mSubId, m.getCallIdParameter(), statusCode,
+ result.restrictedReason);
+ } else {
+ logw("Message is Restricted");
+ }
+ }
+
private void logi(String log) {
Log.i(SipTransportController.LOG_TAG, LOG_TAG + "[" + mSubId + "] " + log);
mLocalLog.log("[I] " + log);
diff --git a/tests/src/com/android/phone/ImsStateCallbackControllerTest.java b/tests/src/com/android/phone/ImsStateCallbackControllerTest.java
new file mode 100644
index 0000000..c493f6b
--- /dev/null
+++ b/tests/src/com/android/phone/ImsStateCallbackControllerTest.java
@@ -0,0 +1,895 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import static android.telephony.ims.ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED;
+import static android.telephony.ims.ImsStateCallback.REASON_IMS_SERVICE_NOT_READY;
+import static android.telephony.ims.ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED;
+import static android.telephony.ims.ImsStateCallback.REASON_SUBSCRIPTION_INACTIVE;
+import static android.telephony.ims.feature.ImsFeature.FEATURE_MMTEL;
+import static android.telephony.ims.feature.ImsFeature.FEATURE_RCS;
+
+import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED;
+import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_IMS_UNSUPPORTED;
+import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_NOT_READY;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyRegistryManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableLooper;
+import android.util.Log;
+
+import com.android.ims.FeatureConnector;
+import com.android.ims.ImsManager;
+import com.android.ims.RcsFeatureManager;
+import com.android.internal.telephony.IImsStateCallback;
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.ims.ImsResolver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+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.concurrent.Executor;
+
+/**
+ * Unit tests for RcsProvisioningMonitor
+ */
+public class ImsStateCallbackControllerTest {
+ private static final String TAG = "ImsStateCallbackControllerTest";
+ private static final int FAKE_SUB_ID_BASE = 0x0FFFFFF0;
+
+ private static final int SLOT_0 = 0;
+ private static final int SLOT_1 = 1;
+
+ private static final int SLOT_0_SUB_ID = 1;
+ private static final int SLOT_1_SUB_ID = 2;
+ private static final int SLOT_2_SUB_ID = 3;
+
+ private ImsStateCallbackController mImsStateCallbackController;
+ private Handler mHandler;
+ private HandlerThread mHandlerThread;
+ private TestableLooper mLooper;
+ @Mock private SubscriptionManager mSubscriptionManager;
+ private SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener;
+ @Mock private TelephonyRegistryManager mTelephonyRegistryManager;
+ @Mock private ITelephony.Stub mITelephony;
+ @Mock private RcsFeatureManager mRcsFeatureManager;
+ @Mock private ImsManager mMmTelFeatureManager;
+ @Mock private ImsStateCallbackController.MmTelFeatureConnectorFactory mMmTelFeatureFactory;
+ @Mock private ImsStateCallbackController.RcsFeatureConnectorFactory mRcsFeatureFactory;
+ @Mock private FeatureConnector<ImsManager> mMmTelFeatureConnectorSlot0;
+ @Mock private FeatureConnector<ImsManager> mMmTelFeatureConnectorSlot1;
+ @Mock private FeatureConnector<RcsFeatureManager> mRcsFeatureConnectorSlot0;
+ @Mock private FeatureConnector<RcsFeatureManager> mRcsFeatureConnectorSlot1;
+ @Captor ArgumentCaptor<FeatureConnector.Listener<ImsManager>> mMmTelConnectorListenerSlot0;
+ @Captor ArgumentCaptor<FeatureConnector.Listener<ImsManager>> mMmTelConnectorListenerSlot1;
+ @Captor ArgumentCaptor<FeatureConnector.Listener<RcsFeatureManager>> mRcsConnectorListenerSlot0;
+ @Captor ArgumentCaptor<FeatureConnector.Listener<RcsFeatureManager>> mRcsConnectorListenerSlot1;
+ @Mock private PhoneGlobals mPhone;
+ @Mock ImsStateCallbackController.PhoneFactoryProxy mPhoneFactoryProxy;
+ @Mock Phone mPhoneSlot0;
+ @Mock Phone mPhoneSlot1;
+ @Mock private IBinder mBinder0;
+ @Mock private IBinder mBinder1;
+ @Mock private IBinder mBinder2;
+ @Mock private IBinder mBinder3;
+ @Mock private IImsStateCallback mCallback0;
+ @Mock private IImsStateCallback mCallback1;
+ @Mock private IImsStateCallback mCallback2;
+ @Mock private IImsStateCallback mCallback3;
+ @Mock private ImsResolver mImsResolver;
+
+ private Executor mExecutor = new Executor() {
+ @Override
+ public void execute(Runnable r) {
+ r.run();
+ }
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ when(mPhone.getMainExecutor()).thenReturn(mExecutor);
+ when(mPhone.getSystemServiceName(eq(SubscriptionManager.class)))
+ .thenReturn(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ when(mPhone.getSystemService(eq(Context.TELEPHONY_SUBSCRIPTION_SERVICE)))
+ .thenReturn(mSubscriptionManager);
+ when(mPhone.getSystemServiceName(eq(TelephonyRegistryManager.class)))
+ .thenReturn(Context.TELEPHONY_REGISTRY_SERVICE);
+ when(mPhone.getSystemService(eq(Context.TELEPHONY_REGISTRY_SERVICE)))
+ .thenReturn(mTelephonyRegistryManager);
+ when(mPhoneFactoryProxy.getPhone(eq(0))).thenReturn(mPhoneSlot0);
+ when(mPhoneFactoryProxy.getPhone(eq(1))).thenReturn(mPhoneSlot1);
+ when(mPhoneSlot0.getSubId()).thenReturn(SLOT_0_SUB_ID);
+ when(mPhoneSlot1.getSubId()).thenReturn(SLOT_1_SUB_ID);
+
+ when(mCallback0.asBinder()).thenReturn(mBinder0);
+ when(mCallback1.asBinder()).thenReturn(mBinder1);
+ when(mCallback2.asBinder()).thenReturn(mBinder2);
+ when(mCallback3.asBinder()).thenReturn(mBinder3);
+
+ // slot 0
+ when(mImsResolver.isImsServiceConfiguredForFeature(eq(0), eq(FEATURE_MMTEL)))
+ .thenReturn(true);
+ when(mImsResolver.isImsServiceConfiguredForFeature(eq(0), eq(FEATURE_RCS)))
+ .thenReturn(true);
+
+ // slot 1
+ when(mImsResolver.isImsServiceConfiguredForFeature(eq(1), eq(FEATURE_MMTEL)))
+ .thenReturn(true);
+ when(mImsResolver.isImsServiceConfiguredForFeature(eq(1), eq(FEATURE_RCS)))
+ .thenReturn(true);
+
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ mSubChangedListener = (SubscriptionManager.OnSubscriptionsChangedListener)
+ invocation.getArguments()[0];
+ return null;
+ }
+ }).when(mTelephonyRegistryManager).addOnSubscriptionsChangedListener(
+ any(SubscriptionManager.OnSubscriptionsChangedListener.class),
+ any());
+
+ mHandlerThread = new HandlerThread("ImsStateCallbackControllerTest");
+ mHandlerThread.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mImsStateCallbackController != null) {
+ mImsStateCallbackController.destroy();
+ mImsStateCallbackController = null;
+ }
+
+ if (mLooper != null) {
+ mLooper.destroy();
+ mLooper = null;
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testMmTelRegisterThenUnregisterCallback() throws Exception {
+ createController(1);
+
+ mImsStateCallbackController
+ .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_MMTEL, mCallback0, "callback0");
+ processAllMessages();
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+ mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+ processAllMessages();
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+ }
+
+ @Test
+ @SmallTest
+ public void testMmTelConnectionUnavailable() throws Exception {
+ createController(1);
+
+ mImsStateCallbackController
+ .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_MMTEL, mCallback0, "callback0");
+ processAllMessages();
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+ mMmTelConnectorListenerSlot0.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+ processAllMessages();
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+
+ mMmTelConnectorListenerSlot0.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
+ processAllMessages();
+ verify(mCallback0, times(1)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+
+ mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+ processAllMessages();
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+ }
+
+ @Test
+ @SmallTest
+ public void testMmTelConnectionReady() throws Exception {
+ createController(1);
+
+ mImsStateCallbackController
+ .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_MMTEL, mCallback0, "callback0");
+ processAllMessages();
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+ verify(mCallback0, times(0)).onAvailable();
+
+ mMmTelConnectorListenerSlot0.getValue().connectionReady(null);
+ processAllMessages();
+ verify(mCallback0, atLeastOnce()).onAvailable();
+
+ mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+ processAllMessages();
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+ }
+
+ @Test
+ @SmallTest
+ public void testMmTelIgnoreDuplicatedConsecutiveReason() throws Exception {
+ createController(1);
+
+ mImsStateCallbackController
+ .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_MMTEL, mCallback0, "callback0");
+ processAllMessages();
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+ mMmTelConnectorListenerSlot0.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
+ processAllMessages();
+ verify(mCallback0, times(1)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+
+ mMmTelConnectorListenerSlot0.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
+ processAllMessages();
+ verify(mCallback0, times(1)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+
+ mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+ processAllMessages();
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+ }
+
+ @Test
+ @SmallTest
+ public void testRcsRegisterThenUnregisterCallback() throws Exception {
+ createController(1);
+
+ mImsStateCallbackController
+ .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_RCS, mCallback0, "callback0");
+ processAllMessages();
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+ mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+ processAllMessages();
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+ }
+
+ @Test
+ @SmallTest
+ public void testRcsConnectionUnavailable() throws Exception {
+ createController(1);
+
+ mImsStateCallbackController
+ .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_RCS, mCallback0, "callback0");
+ processAllMessages();
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+ // TelephonyRcsService notifying active features
+ mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_0, false, true);
+ processAllMessages();
+
+ mRcsConnectorListenerSlot0.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+ processAllMessages();
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+
+ mRcsConnectorListenerSlot0.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
+ processAllMessages();
+ verify(mCallback0, times(1)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+
+ mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+ processAllMessages();
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+ }
+
+ @Test
+ @SmallTest
+ public void testRcsConnectionReady() throws Exception {
+ createController(1);
+
+ mImsStateCallbackController
+ .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_RCS, mCallback0, "callback0");
+ processAllMessages();
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+ // TelephonyRcsService notifying active features
+ mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_0, false, true);
+ processAllMessages();
+
+ mRcsConnectorListenerSlot0.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+ processAllMessages();
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+
+ mRcsConnectorListenerSlot0.getValue().connectionReady(null);
+ processAllMessages();
+ verify(mCallback0, times(0)).onAvailable();
+
+ // RcsFeatureController notifying STATE_READY
+ mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_0, true, true);
+ processAllMessages();
+ verify(mCallback0, times(1)).onAvailable();
+
+ mRcsConnectorListenerSlot0.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_DISCONNECTED);
+ processAllMessages();
+ verify(mCallback0, times(2)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+ mRcsConnectorListenerSlot0.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+ processAllMessages();
+ verify(mCallback0, times(2)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+
+ // RcsFeatureController notifying STATE_READY
+ mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_0, true, true);
+ processAllMessages();
+ verify(mCallback0, times(1)).onAvailable();
+
+ mRcsConnectorListenerSlot0.getValue().connectionReady(null);
+ processAllMessages();
+ verify(mCallback0, times(2)).onAvailable();
+
+ mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+ processAllMessages();
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+ }
+
+ @Test
+ @SmallTest
+ public void testRcsHasNoActiveFeature() throws Exception {
+ createController(1);
+
+ mImsStateCallbackController
+ .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_RCS, mCallback0, "callback0");
+ processAllMessages();
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+ // TelephonyRcsService notifying NO active feature
+ mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_0, false, false);
+ processAllMessages();
+ verify(mCallback0, times(1)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+
+ mRcsConnectorListenerSlot0.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+ processAllMessages();
+ verify(mCallback0, times(0)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+
+ mRcsConnectorListenerSlot0.getValue().connectionReady(null);
+ processAllMessages();
+ verify(mCallback0, times(0)).onAvailable();
+
+ mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+ processAllMessages();
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+ }
+
+ @Test
+ @SmallTest
+ public void testRcsIgnoreDuplicatedConsecutiveReason() throws Exception {
+ createController(1);
+
+ mImsStateCallbackController
+ .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_RCS, mCallback0, "callback0");
+ processAllMessages();
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+ // TelephonyRcsService notifying active features
+ mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_0, false, true);
+ processAllMessages();
+
+ mRcsConnectorListenerSlot0.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+ processAllMessages();
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+
+ mRcsConnectorListenerSlot0.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+ processAllMessages();
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+
+ mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+ processAllMessages();
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+ }
+
+ @Test
+ @SmallTest
+ public void testCallbackRemovedWhenSubInfoChanged() throws Exception {
+ createController(2);
+
+ mImsStateCallbackController
+ .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_MMTEL, mCallback0, "callback0");
+ mImsStateCallbackController
+ .registerImsStateCallback(SLOT_1_SUB_ID, FEATURE_RCS, mCallback1, "callback1");
+ processAllMessages();
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback1));
+
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+ verify(mCallback1, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+ makeFakeActiveSubIds(0);
+ mExecutor.execute(() -> mSubChangedListener.onSubscriptionsChanged());
+ processAllMessages();
+
+ verify(mCallback0, times(1)).onUnavailable(REASON_SUBSCRIPTION_INACTIVE);
+ verify(mCallback1, times(1)).onUnavailable(REASON_SUBSCRIPTION_INACTIVE);
+
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback1));
+ }
+
+ @Test
+ @SmallTest
+ public void testCarrierConfigurationChanged() throws Exception {
+ createController(2);
+
+ when(mImsResolver.isImsServiceConfiguredForFeature(eq(1), eq(FEATURE_MMTEL)))
+ .thenReturn(false);
+
+ mImsStateCallbackController
+ .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_MMTEL, mCallback0, "callback0");
+ mImsStateCallbackController
+ .registerImsStateCallback(SLOT_1_SUB_ID, FEATURE_MMTEL, mCallback1, "callback1");
+ mImsStateCallbackController
+ .registerImsStateCallback(SLOT_1_SUB_ID, FEATURE_RCS, mCallback2, "callback2");
+ processAllMessages();
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback1));
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback2));
+
+ // check initial reason
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+ verify(mCallback1, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+ verify(mCallback2, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+ verify(mCallback0, times(0)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+ verify(mCallback1, times(0)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+ verify(mCallback2, times(0)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+
+ // ensure only one reason reported until now
+ verify(mCallback0, times(1)).onUnavailable(anyInt());
+ verify(mCallback1, times(1)).onUnavailable(anyInt());
+ verify(mCallback2, times(1)).onUnavailable(anyInt());
+
+ // state change in RCS for slot 0
+ mRcsConnectorListenerSlot0.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+
+ // ensure there is no change, since callbacks are not interested RCS on slot 0
+ verify(mCallback0, times(1)).onUnavailable(anyInt());
+ verify(mCallback1, times(1)).onUnavailable(anyInt());
+ verify(mCallback2, times(1)).onUnavailable(anyInt());
+
+ // carrier config changed, no MMTEL package for slot 1
+ mImsStateCallbackController.notifyCarrierConfigChanged(SLOT_1);
+ processAllMessages();
+
+ // only the callback for MMTEL of slot 1 received the reason
+ verify(mCallback0, times(0)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+ verify(mCallback1, times(1)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+ verify(mCallback2, times(0)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+
+ // ensure no other callbacks
+ verify(mCallback0, times(1)).onUnavailable(anyInt());
+ verify(mCallback1, times(2)).onUnavailable(anyInt());
+ verify(mCallback2, times(1)).onUnavailable(anyInt());
+
+ mMmTelConnectorListenerSlot1.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+ mMmTelConnectorListenerSlot1.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_DISCONNECTED);
+
+ // resons except REASON_NO_IMS_SERVICE_CONFIGURED are discared
+ verify(mCallback0, times(1)).onUnavailable(anyInt());
+ verify(mCallback1, times(2)).onUnavailable(anyInt());
+ verify(mCallback2, times(1)).onUnavailable(anyInt());
+
+ // IMS package for MMTEL of slot 1 is added
+ when(mImsResolver.isImsServiceConfiguredForFeature(eq(1), eq(FEATURE_MMTEL)))
+ .thenReturn(true);
+ mImsStateCallbackController.notifyCarrierConfigChanged(SLOT_1);
+ processAllMessages();
+
+ // ensure the callback to MMTEL of slot 1 only received REASON_IMS_SERVICE_DISCONNECTED
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+ verify(mCallback1, times(2)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+ verify(mCallback2, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+ // ensure no other reason repored
+ verify(mCallback0, times(1)).onUnavailable(anyInt());
+ verify(mCallback1, times(3)).onUnavailable(anyInt());
+ verify(mCallback2, times(1)).onUnavailable(anyInt());
+
+ // carrier config changed, no MMTEL package for slot 1
+ when(mImsResolver.isImsServiceConfiguredForFeature(eq(1), eq(FEATURE_MMTEL)))
+ .thenReturn(false);
+ mImsStateCallbackController.notifyCarrierConfigChanged(SLOT_1);
+ mImsStateCallbackController.notifyCarrierConfigChanged(SLOT_1);
+ processAllMessages();
+ // only the callback for MMTEL of slot 1 received the reason
+ verify(mCallback0, times(0)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+ verify(mCallback1, times(2)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+ verify(mCallback2, times(0)).onUnavailable(REASON_NO_IMS_SERVICE_CONFIGURED);
+
+ // ensure no other reason repored
+ verify(mCallback0, times(1)).onUnavailable(anyInt());
+ verify(mCallback1, times(4)).onUnavailable(anyInt());
+ verify(mCallback2, times(1)).onUnavailable(anyInt());
+
+ mMmTelConnectorListenerSlot1.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+
+ // resons except REASON_NO_IMS_SERVICE_CONFIGURED are discared
+ verify(mCallback0, times(1)).onUnavailable(anyInt());
+ verify(mCallback1, times(4)).onUnavailable(anyInt());
+ verify(mCallback2, times(1)).onUnavailable(anyInt());
+
+ // IMS package for MMTEL of slot 1 is added
+ when(mImsResolver.isImsServiceConfiguredForFeature(eq(1), eq(FEATURE_MMTEL)))
+ .thenReturn(true);
+ mImsStateCallbackController.notifyCarrierConfigChanged(SLOT_1);
+ processAllMessages();
+
+ // ensure the callback to MMTEL of slot 1
+ // there is a pending reason UNAVAILABLE_REASON_NOT_READY
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+ verify(mCallback1, times(2)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+ verify(mCallback1, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+ verify(mCallback2, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+ // ensure no other reason repored
+ verify(mCallback0, times(1)).onUnavailable(anyInt());
+ verify(mCallback1, times(5)).onUnavailable(anyInt());
+ verify(mCallback2, times(1)).onUnavailable(anyInt());
+
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback1));
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback2));
+
+ mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+ mImsStateCallbackController.unregisterImsStateCallback(mCallback1);
+ mImsStateCallbackController.unregisterImsStateCallback(mCallback2);
+ processAllMessages();
+
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback1));
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback2));
+ }
+
+ @Test
+ @SmallTest
+ public void testMultiSubscriptions() throws Exception {
+ createController(2);
+
+ // registration
+ mImsStateCallbackController
+ .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_MMTEL, mCallback0, "callback0");
+ mImsStateCallbackController
+ .registerImsStateCallback(SLOT_0_SUB_ID, FEATURE_RCS, mCallback1, "callback1");
+ mImsStateCallbackController
+ .registerImsStateCallback(SLOT_1_SUB_ID, FEATURE_MMTEL, mCallback2, "callback2");
+ mImsStateCallbackController
+ .registerImsStateCallback(SLOT_1_SUB_ID, FEATURE_RCS, mCallback3, "callback3");
+ processAllMessages();
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback0));
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback1));
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback2));
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback3));
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+ verify(mCallback1, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+ verify(mCallback2, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+ verify(mCallback3, times(1)).onUnavailable(REASON_IMS_SERVICE_DISCONNECTED);
+
+ // TelephonyRcsService notifying active features
+ // slot 0
+ mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_0, false, true);
+ // slot 1
+ mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_1, false, true);
+ processAllMessages();
+
+ verify(mCallback0, times(1)).onUnavailable(anyInt());
+ verify(mCallback1, times(1)).onUnavailable(anyInt());
+ verify(mCallback2, times(1)).onUnavailable(anyInt());
+ verify(mCallback3, times(1)).onUnavailable(anyInt());
+
+ verify(mCallback0, times(0)).onAvailable();
+ verify(mCallback1, times(0)).onAvailable();
+ verify(mCallback2, times(0)).onAvailable();
+ verify(mCallback3, times(0)).onAvailable();
+
+ // connectionUnavailable
+ mMmTelConnectorListenerSlot0.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+ processAllMessages();
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+ verify(mCallback0, times(2)).onUnavailable(anyInt());
+ verify(mCallback1, times(1)).onUnavailable(anyInt());
+ verify(mCallback1, times(1)).onUnavailable(anyInt());
+ verify(mCallback2, times(1)).onUnavailable(anyInt());
+ verify(mCallback2, times(1)).onUnavailable(anyInt());
+ verify(mCallback3, times(1)).onUnavailable(anyInt());
+ verify(mCallback3, times(1)).onUnavailable(anyInt());
+
+ mRcsConnectorListenerSlot0.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+ processAllMessages();
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+ verify(mCallback0, times(2)).onUnavailable(anyInt());
+ verify(mCallback1, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+ verify(mCallback1, times(2)).onUnavailable(anyInt());
+ verify(mCallback2, times(1)).onUnavailable(anyInt());
+ verify(mCallback2, times(1)).onUnavailable(anyInt());
+ verify(mCallback3, times(1)).onUnavailable(anyInt());
+ verify(mCallback3, times(1)).onUnavailable(anyInt());
+
+ mMmTelConnectorListenerSlot1.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+ processAllMessages();
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+ verify(mCallback0, times(2)).onUnavailable(anyInt());
+ verify(mCallback1, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+ verify(mCallback1, times(2)).onUnavailable(anyInt());
+ verify(mCallback2, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+ verify(mCallback2, times(2)).onUnavailable(anyInt());
+ verify(mCallback3, times(1)).onUnavailable(anyInt());
+ verify(mCallback3, times(1)).onUnavailable(anyInt());
+
+ mRcsConnectorListenerSlot1.getValue()
+ .connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
+ processAllMessages();
+ verify(mCallback0, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+ verify(mCallback0, times(2)).onUnavailable(anyInt());
+ verify(mCallback1, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+ verify(mCallback1, times(2)).onUnavailable(anyInt());
+ verify(mCallback2, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+ verify(mCallback2, times(2)).onUnavailable(anyInt());
+ verify(mCallback3, times(1)).onUnavailable(REASON_IMS_SERVICE_NOT_READY);
+ verify(mCallback3, times(2)).onUnavailable(anyInt());
+
+ // connectionReady
+ mMmTelConnectorListenerSlot0.getValue().connectionReady(null);
+ processAllMessages();
+ verify(mCallback0, times(1)).onAvailable();
+ verify(mCallback1, times(0)).onAvailable();
+ verify(mCallback2, times(0)).onAvailable();
+ verify(mCallback3, times(0)).onAvailable();
+ verify(mCallback0, times(2)).onUnavailable(anyInt());
+ verify(mCallback1, times(2)).onUnavailable(anyInt());
+ verify(mCallback2, times(2)).onUnavailable(anyInt());
+ verify(mCallback3, times(2)).onUnavailable(anyInt());
+
+ mRcsConnectorListenerSlot0.getValue().connectionReady(null);
+ processAllMessages();
+ verify(mCallback0, times(1)).onAvailable();
+ verify(mCallback1, times(0)).onAvailable();
+ verify(mCallback2, times(0)).onAvailable();
+ verify(mCallback3, times(0)).onAvailable();
+ verify(mCallback0, times(2)).onUnavailable(anyInt());
+ verify(mCallback1, times(2)).onUnavailable(anyInt());
+ verify(mCallback2, times(2)).onUnavailable(anyInt());
+ verify(mCallback3, times(2)).onUnavailable(anyInt());
+
+ mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_0, true, true);
+ processAllMessages();
+ verify(mCallback0, times(1)).onAvailable();
+ verify(mCallback1, times(1)).onAvailable();
+ verify(mCallback2, times(0)).onAvailable();
+ verify(mCallback3, times(0)).onAvailable();
+ verify(mCallback0, times(2)).onUnavailable(anyInt());
+ verify(mCallback1, times(2)).onUnavailable(anyInt());
+ verify(mCallback2, times(2)).onUnavailable(anyInt());
+ verify(mCallback3, times(2)).onUnavailable(anyInt());
+
+ mMmTelConnectorListenerSlot1.getValue().connectionReady(null);
+ processAllMessages();
+ verify(mCallback0, times(1)).onAvailable();
+ verify(mCallback1, times(1)).onAvailable();
+ verify(mCallback2, times(1)).onAvailable();
+ verify(mCallback3, times(0)).onAvailable();
+ verify(mCallback0, times(2)).onUnavailable(anyInt());
+ verify(mCallback1, times(2)).onUnavailable(anyInt());
+ verify(mCallback2, times(2)).onUnavailable(anyInt());
+ verify(mCallback3, times(2)).onUnavailable(anyInt());
+
+ mRcsConnectorListenerSlot1.getValue().connectionReady(null);
+ processAllMessages();
+ verify(mCallback0, times(1)).onAvailable();
+ verify(mCallback1, times(1)).onAvailable();
+ verify(mCallback2, times(1)).onAvailable();
+ verify(mCallback3, times(0)).onAvailable();
+ verify(mCallback0, times(2)).onUnavailable(anyInt());
+ verify(mCallback1, times(2)).onUnavailable(anyInt());
+ verify(mCallback2, times(2)).onUnavailable(anyInt());
+ verify(mCallback3, times(2)).onUnavailable(anyInt());
+
+ mImsStateCallbackController.notifyExternalRcsStateChanged(SLOT_1, true, true);
+ processAllMessages();
+ verify(mCallback0, times(1)).onAvailable();
+ verify(mCallback1, times(1)).onAvailable();
+ verify(mCallback2, times(1)).onAvailable();
+ verify(mCallback3, times(1)).onAvailable();
+ verify(mCallback0, times(2)).onUnavailable(anyInt());
+ verify(mCallback1, times(2)).onUnavailable(anyInt());
+ verify(mCallback2, times(2)).onUnavailable(anyInt());
+ verify(mCallback3, times(2)).onUnavailable(anyInt());
+
+ // unregistration
+ mImsStateCallbackController.unregisterImsStateCallback(mCallback0);
+ processAllMessages();
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback1));
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback2));
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback3));
+
+ mImsStateCallbackController.unregisterImsStateCallback(mCallback1);
+ processAllMessages();
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback1));
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback2));
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback3));
+
+ mImsStateCallbackController.unregisterImsStateCallback(mCallback2);
+ processAllMessages();
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback1));
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback2));
+ assertTrue(mImsStateCallbackController.isRegistered(mCallback3));
+
+ mImsStateCallbackController.unregisterImsStateCallback(mCallback3);
+ processAllMessages();
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback0));
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback1));
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback2));
+ assertFalse(mImsStateCallbackController.isRegistered(mCallback3));
+ }
+
+ @Test
+ @SmallTest
+ public void testSlotUpdates() throws Exception {
+ createController(1);
+
+ verify(mMmTelFeatureConnectorSlot0, times(1)).connect();
+ verify(mRcsFeatureConnectorSlot0, times(1)).connect();
+ verify(mMmTelFeatureConnectorSlot0, times(0)).disconnect();
+ verify(mRcsFeatureConnectorSlot0, times(0)).disconnect();
+
+ // Add a new slot.
+ mImsStateCallbackController.updateFeatureControllerSize(2);
+
+ // connect in slot 1
+ verify(mMmTelFeatureConnectorSlot1, times(1)).connect();
+ verify(mRcsFeatureConnectorSlot1, times(1)).connect();
+
+ // no change in slot 0
+ verify(mMmTelFeatureConnectorSlot0, times(1)).connect();
+ verify(mRcsFeatureConnectorSlot0, times(1)).connect();
+
+ // Remove a slot.
+ mImsStateCallbackController.updateFeatureControllerSize(1);
+
+ // destroy in slot 1
+ verify(mMmTelFeatureConnectorSlot1, times(1)).disconnect();
+ verify(mRcsFeatureConnectorSlot1, times(1)).disconnect();
+
+ // no change in slot 0
+ verify(mMmTelFeatureConnectorSlot0, times(0)).disconnect();
+ verify(mRcsFeatureConnectorSlot0, times(0)).disconnect();
+ }
+
+ private void createController(int slotCount) throws Exception {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ makeFakeActiveSubIds(slotCount);
+
+ when(mMmTelFeatureFactory
+ .create(any(), eq(0), any(), mMmTelConnectorListenerSlot0.capture(), any()))
+ .thenReturn(mMmTelFeatureConnectorSlot0);
+ when(mMmTelFeatureFactory
+ .create(any(), eq(1), any(), mMmTelConnectorListenerSlot1.capture(), any()))
+ .thenReturn(mMmTelFeatureConnectorSlot1);
+ when(mRcsFeatureFactory
+ .create(any(), eq(0), mRcsConnectorListenerSlot0.capture(), any(), any()))
+ .thenReturn(mRcsFeatureConnectorSlot0);
+ when(mRcsFeatureFactory
+ .create(any(), eq(1), mRcsConnectorListenerSlot1.capture(), any(), any()))
+ .thenReturn(mRcsFeatureConnectorSlot1);
+
+ mImsStateCallbackController =
+ new ImsStateCallbackController(mPhone, mHandlerThread.getLooper(),
+ slotCount, mMmTelFeatureFactory, mRcsFeatureFactory, mImsResolver);
+
+ replaceInstance(ImsStateCallbackController.class,
+ "mPhoneFactoryProxy", mImsStateCallbackController, mPhoneFactoryProxy);
+ mImsStateCallbackController.onSubChanged();
+
+ mHandler = mImsStateCallbackController.getHandler();
+ try {
+ mLooper = new TestableLooper(mHandler.getLooper());
+ } catch (Exception e) {
+ logd("Unable to create looper from handler.");
+ }
+
+ verify(mRcsFeatureConnectorSlot0, atLeastOnce()).connect();
+ verify(mMmTelFeatureConnectorSlot0, atLeastOnce()).connect();
+
+ if (slotCount == 1) {
+ verify(mRcsFeatureConnectorSlot1, times(0)).connect();
+ verify(mMmTelFeatureConnectorSlot1, times(0)).connect();
+ } else {
+ verify(mRcsFeatureConnectorSlot1, atLeastOnce()).connect();
+ verify(mMmTelFeatureConnectorSlot1, atLeastOnce()).connect();
+ }
+ }
+
+ 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 makeFakeActiveSubIds(int count) {
+ final int[] subIds = new int[count];
+ for (int i = 0; i < count; i++) {
+ subIds[i] = FAKE_SUB_ID_BASE + i;
+ }
+ when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(subIds);
+ }
+
+ 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/phone/RcsProvisioningMonitorTest.java b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
index 8e5e073..8873402 100644
--- a/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
+++ b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
@@ -16,6 +16,10 @@
package com.android.phone;
+import static com.android.internal.telephony.TelephonyStatsLog.RCS_ACS_PROVISIONING_STATS__RESPONSE_TYPE__PROVISIONING_XML;
+import static com.android.internal.telephony.TelephonyStatsLog.RCS_CLIENT_PROVISIONING_STATS__EVENT__DMA_CHANGED;
+import static com.android.internal.telephony.TelephonyStatsLog.RCS_CLIENT_PROVISIONING_STATS__EVENT__TRIGGER_RCS_RECONFIGURATION;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
@@ -64,6 +68,7 @@
import com.android.ims.FeatureConnector;
import com.android.ims.RcsFeatureManager;
import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.metrics.RcsStats;
import org.junit.After;
import org.junit.Before;
@@ -179,6 +184,10 @@
private IRcsConfigCallback mCallback;
@Mock
private PackageManager mPackageManager;
+ @Mock
+ private RcsStats mRcsStats;
+ @Mock
+ private RcsStats.RcsProvisioningCallback mRcsProvisioningCallback;
private Executor mExecutor = new Executor() {
@Override
@@ -768,6 +777,66 @@
assertNull(mRcsProvisioningMonitor.getImsFeatureValidationOverride(FAKE_SUB_ID_BASE));
}
+ @Test
+ @SmallTest
+ public void testMetricsAcsNotUsed() throws Exception {
+ createMonitor(1);
+
+ // Not used ACS
+ mBundle.putBoolean(CarrierConfigManager.KEY_USE_ACS_FOR_RCS_BOOL, false);
+ broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+ processAllMessages();
+ mRcsProvisioningMonitor.updateConfig(FAKE_SUB_ID_BASE, CONFIG_DEFAULT.getBytes(), false);
+ processAllMessages();
+ verify(mRcsStats, never()).onRcsAcsProvisioningStats(anyInt(), anyInt(),
+ anyInt(), anyBoolean());
+ }
+
+ @Test
+ @SmallTest
+ public void testMetricsAcsUsed() throws Exception {
+ when(mRcsStats.getRcsProvisioningCallback(anyInt(), anyBoolean()))
+ .thenReturn(mRcsProvisioningCallback);
+ createMonitor(1);
+
+ verify(mIImsConfig, times(1))
+ .notifyRcsAutoConfigurationReceived(any(), anyBoolean());
+ // verify RcsStats.getRcsProvisioningCallback() is called
+ verify(mRcsStats, times(1)).getRcsProvisioningCallback(
+ eq(FAKE_SUB_ID_BASE), anyBoolean());
+ // verify registered callback obj which comes from RcsStats.getRcsProvisioningCallback()
+ verify(mIImsConfig, times(1))
+ .addRcsConfigCallback(eq(mRcsProvisioningCallback));
+
+ // Config data received and ACS used
+ int errorCode = 200;
+ mBundle.putBoolean(CarrierConfigManager.KEY_USE_ACS_FOR_RCS_BOOL, true);
+ broadcastCarrierConfigChange(FAKE_SUB_ID_BASE);
+ processAllMessages();
+ mRcsProvisioningMonitor.updateConfig(FAKE_SUB_ID_BASE, CONFIG_DEFAULT.getBytes(), false);
+ processAllMessages();
+ verify(mRcsStats, times(1)).onRcsAcsProvisioningStats(eq(FAKE_SUB_ID_BASE), eq(errorCode),
+ eq(RCS_ACS_PROVISIONING_STATS__RESPONSE_TYPE__PROVISIONING_XML), anyBoolean());
+ }
+
+ @Test
+ @SmallTest
+ public void testMetricsClientProvisioningStats() throws Exception {
+ createMonitor(1);
+
+ // reconfig trigger
+ mRcsProvisioningMonitor.requestReconfig(FAKE_SUB_ID_BASE);
+ processAllMessages();
+ verify(mRcsStats, times(1)).onRcsClientProvisioningStats(eq(FAKE_SUB_ID_BASE),
+ eq(RCS_CLIENT_PROVISIONING_STATS__EVENT__TRIGGER_RCS_RECONFIGURATION));
+
+ // DMA changed
+ updateDefaultMessageApplication(DEFAULT_MESSAGING_APP2);
+ processAllMessages();
+ verify(mRcsStats, times(1)).onRcsClientProvisioningStats(eq(FAKE_SUB_ID_BASE),
+ eq(RCS_CLIENT_PROVISIONING_STATS__EVENT__DMA_CHANGED));
+ }
+
private void createMonitor(int subCount) throws Exception {
if (Looper.myLooper() == null) {
Looper.prepare();
@@ -777,7 +846,7 @@
.thenReturn(mFeatureConnector);
when(mFeatureManager.getConfig()).thenReturn(mIImsConfig);
mRcsProvisioningMonitor = new RcsProvisioningMonitor(mPhone, mHandlerThread.getLooper(),
- mRoleManager, mFeatureFactory);
+ mRoleManager, mFeatureFactory, mRcsStats);
mHandler = mRcsProvisioningMonitor.getHandler();
try {
mLooper = new TestableLooper(mHandler.getLooper());
diff --git a/tests/src/com/android/services/telephony/ImsConferenceTest.java b/tests/src/com/android/services/telephony/ImsConferenceTest.java
index 3bc5ee8..9d2f5ac 100644
--- a/tests/src/com/android/services/telephony/ImsConferenceTest.java
+++ b/tests/src/com/android/services/telephony/ImsConferenceTest.java
@@ -622,6 +622,46 @@
}
/**
+ * Tests a scenario where a handover connection arrives via
+ * {@link TelephonyConnection#onOriginalConnectionRedialed(
+ * com.android.internal.telephony.Connection)}. During this process, the conference properties
+ * get updated. Since the original connection is null at this point, we need to verify that
+ * the remotely hosted property is retained from before the original connection was nulled.
+ */
+ @Test
+ public void testIsConferenceRemotelyHostedCachingOnSRVCC() {
+ mConferenceHost.setIsImsConnection(true);
+ when(mConferenceHost.getMockImsPhoneConnection().isMultiparty()).thenReturn(true);
+ when(mConferenceHost.getMockImsPhoneConnection().isConferenceHost()).thenReturn(true);
+
+ // Start out with a valid conference host.
+ ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
+ mMockTelephonyConnectionServiceProxy, mConferenceHost,
+ null /* phoneAccountHandle */, () -> false /* featureFlagProxy */,
+ new ImsConference.CarrierConfiguration.Builder().build());
+
+ // By default it is not remotely hosted.
+ assertFalse(imsConference.isRemotelyHosted());
+ assertEquals(0,
+ imsConference.getConnectionProperties() & Connection.PROPERTY_REMOTELY_HOSTED);
+
+ // Simulate a change to the original connection due to srvcc
+ com.android.internal.telephony.Connection previousOriginalConnection =
+ mConferenceHost.getMockImsPhoneConnection();
+ mConferenceHost.setMockImsPhoneConnection(null);
+
+ // Trigger the property update which takes place when the original connection changes.
+ mConferenceHost.getTelephonyConnectionListeners().forEach(
+ l -> l.onConnectionPropertiesChanged(mConferenceHost,
+ mConferenceHost.getConnectionProperties()));
+
+ // Should still NOT be remotely hosted based on cached value.
+ assertFalse(imsConference.isRemotelyHosted());
+ assertEquals(0,
+ imsConference.getConnectionProperties() & Connection.PROPERTY_REMOTELY_HOSTED);
+ }
+
+ /**
* Verifies that an ImsConference can handle SIP and TEL URIs for both the P-Associated-Uri and
* conference event package identities.
*/
diff --git a/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java b/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java
index cfdc2fd..b7fe988 100644
--- a/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java
@@ -23,9 +23,11 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.net.Uri;
import android.os.Looper;
import android.telecom.Conference;
import android.telecom.Connection;
+import android.telecom.PhoneAccount;
import android.test.suitebuilder.annotation.SmallTest;
import org.junit.Before;
@@ -35,7 +37,9 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
/**
* Tests the functionality in TelephonyConferenceController.java
@@ -111,6 +115,46 @@
}
/**
+ * Verify the connection with "Conference Call" with PROPERTY_IS_DOWNGRADED_CONFERENCE
+ * during SRVCC
+ */
+ @Test
+ @SmallTest
+ public void testSrvccConferenceConnection() {
+ when(mTestTelephonyConnectionA.mMockRadioConnection.getCall()
+ .isMultiparty()).thenReturn(true);
+ when(mTestTelephonyConnectionB.mMockRadioConnection.getCall()
+ .isMultiparty()).thenReturn(true);
+
+ List<Connection> listConnections = Arrays.asList(
+ mTestTelephonyConnectionA, mTestTelephonyConnectionB);
+ when(mMockTelephonyConnectionServiceProxy.getAllConnections()).thenReturn(listConnections);
+
+ mTestTelephonyConnectionA.setAddress(
+ Uri.fromParts(PhoneAccount.SCHEME_TEL, "Conference Call", null), 0);
+ mTestTelephonyConnectionA.setTelephonyConnectionProperties(
+ Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE);
+ mTestTelephonyConnectionB.setAddress(
+ Uri.fromParts(PhoneAccount.SCHEME_TEL, "5551213", null), 0);
+ mTestTelephonyConnectionB.setTelephonyConnectionProperties(
+ Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE);
+
+ // add telephony connection B
+ mControllerTest.add(mTestTelephonyConnectionB);
+
+ // add telephony connection A
+ mControllerTest.add(mTestTelephonyConnectionA);
+
+ // verify the connection with "Conference Call" has PROPERTY_IS_DOWNGRADED_CONFERENCE
+ assertTrue((mTestTelephonyConnectionA.getConnectionProperties()
+ & Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE) != 0);
+
+ // verify the connection with "5551213" hasn't PROPERTY_IS_DOWNGRADED_CONFERENCE
+ assertFalse((mTestTelephonyConnectionB.getConnectionProperties()
+ & Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE) != 0);
+ }
+
+ /**
* Behavior: add telephony connection B and A to conference controller,
* set status for connections and merged calls, remove one call
* Assumption: after performing the behaviours, the status of Connection A is STATE_ACTIVE;
diff --git a/tests/src/com/android/services/telephony/TestTelephonyConnection.java b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
index 817e9a7..e149d3b 100644
--- a/tests/src/com/android/services/telephony/TestTelephonyConnection.java
+++ b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
@@ -305,4 +305,12 @@
public PersistableBundle getCarrierConfigBundle() {
return mCarrierConfig;
}
+
+ public ImsPhoneConnection getMockImsPhoneConnection() {
+ return mImsPhoneConnection;
+ }
+
+ public void setMockImsPhoneConnection(ImsPhoneConnection connection) {
+ mImsPhoneConnection = connection;
+ }
}
diff --git a/tests/src/com/android/services/telephony/rcs/DelegateStateTrackerTest.java b/tests/src/com/android/services/telephony/rcs/DelegateStateTrackerTest.java
index 8236f44..25b5339 100644
--- a/tests/src/com/android/services/telephony/rcs/DelegateStateTrackerTest.java
+++ b/tests/src/com/android/services/telephony/rcs/DelegateStateTrackerTest.java
@@ -37,6 +37,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.TelephonyTestBase;
+import com.android.internal.telephony.metrics.RcsStats;
import org.junit.After;
import org.junit.Before;
@@ -56,6 +57,7 @@
@Mock private ISipDelegate mSipDelegate;
@Mock private ISipDelegateConnectionStateCallback mAppCallback;
+ @Mock private RcsStats mRcsStats;
@Before
public void setUp() throws Exception {
@@ -76,12 +78,14 @@
@Test
public void testDelegateCreated() throws Exception {
DelegateStateTracker stateTracker = new DelegateStateTracker(TEST_SUB_ID, mAppCallback,
- mSipDelegate);
+ mSipDelegate, mRcsStats);
Set<FeatureTagState> deniedTags = getMmTelDeniedTag();
- stateTracker.sipDelegateConnected(deniedTags);
+ Set<String> supportedTags = getSupportedTags();
+ stateTracker.sipDelegateConnected(supportedTags, deniedTags);
// Calling connected multiple times should not generate multiple onCreated events.
- stateTracker.sipDelegateConnected(deniedTags);
+ stateTracker.sipDelegateConnected(supportedTags, deniedTags);
verify(mAppCallback).onCreated(mSipDelegate);
+ verify(mRcsStats).createSipDelegateStats(TEST_SUB_ID, supportedTags);
// Ensure status updates are sent to app as expected.
DelegateRegistrationState regState = new DelegateRegistrationState.Builder()
@@ -97,8 +101,10 @@
stateTracker.onConfigurationChanged(c);
verify(mAppCallback).onFeatureTagStatusChanged(eq(regState),
eq(new ArrayList<>(deniedTags)));
+ verify(mRcsStats).onSipTransportFeatureTagStats(TEST_SUB_ID, deniedTags,
+ regState.getDeregisteredFeatureTags(),
+ regState.getRegisteredFeatureTags());
verify(mAppCallback).onConfigurationChanged(c);
-
verify(mAppCallback, never()).onDestroyed(anyInt());
}
@@ -109,14 +115,18 @@
@Test
public void testDelegateDestroyed() throws Exception {
DelegateStateTracker stateTracker = new DelegateStateTracker(TEST_SUB_ID, mAppCallback,
- mSipDelegate);
+ mSipDelegate, mRcsStats);
Set<FeatureTagState> deniedTags = getMmTelDeniedTag();
- stateTracker.sipDelegateConnected(deniedTags);
+ Set<String> supportedTags = getSupportedTags();
+ stateTracker.sipDelegateConnected(supportedTags, deniedTags);
+ verify(mRcsStats).createSipDelegateStats(eq(TEST_SUB_ID), eq(supportedTags));
stateTracker.sipDelegateDestroyed(
SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
verify(mAppCallback).onDestroyed(
SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+ verify(mRcsStats).onSipDelegateStats(eq(TEST_SUB_ID), eq(supportedTags),
+ eq(SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP));
}
/**
@@ -130,9 +140,10 @@
@Test
public void testDelegateChangingRegisteredTagsOverride() throws Exception {
DelegateStateTracker stateTracker = new DelegateStateTracker(TEST_SUB_ID, mAppCallback,
- mSipDelegate);
+ mSipDelegate, mRcsStats);
Set<FeatureTagState> deniedTags = getMmTelDeniedTag();
- stateTracker.sipDelegateConnected(deniedTags);
+ Set<String> supportedTags = getSupportedTags();
+ stateTracker.sipDelegateConnected(supportedTags, deniedTags);
// SipDelegate created
verify(mAppCallback).onCreated(mSipDelegate);
DelegateRegistrationState regState = new DelegateRegistrationState.Builder()
@@ -158,7 +169,7 @@
DelegateRegistrationState.DEREGISTERED_REASON_NOT_PROVISIONED)
.build();
// new underlying SipDelegate created
- stateTracker.sipDelegateConnected(deniedTags);
+ stateTracker.sipDelegateConnected(supportedTags, deniedTags);
stateTracker.onRegistrationStateChanged(regState);
// Verify registration state through the process:
@@ -187,9 +198,10 @@
@Test
public void testDelegateChangingDeniedTagsChanged() throws Exception {
DelegateStateTracker stateTracker = new DelegateStateTracker(TEST_SUB_ID, mAppCallback,
- mSipDelegate);
+ mSipDelegate, mRcsStats);
Set<FeatureTagState> deniedTags = getMmTelDeniedTag();
- stateTracker.sipDelegateConnected(deniedTags);
+ Set<String> supportedTags = getSupportedTags();
+ stateTracker.sipDelegateConnected(supportedTags, deniedTags);
// SipDelegate created
verify(mAppCallback).onCreated(mSipDelegate);
DelegateRegistrationState regState = new DelegateRegistrationState.Builder()
@@ -220,7 +232,7 @@
// new underlying SipDelegate created, but SipDelegate denied one to one chat
deniedTags.add(new FeatureTagState(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG,
SipDelegateManager.DENIED_REASON_NOT_ALLOWED));
- stateTracker.sipDelegateConnected(deniedTags);
+ stateTracker.sipDelegateConnected(supportedTags, deniedTags);
DelegateRegistrationState fullyDeniedRegState = new DelegateRegistrationState.Builder()
.build();
// In this special case, it will be the SipDelegateConnectionBase that will trigger
@@ -244,9 +256,10 @@
@Test
public void testDelegateChangingDeniedTagsChangingToDestroy() throws Exception {
DelegateStateTracker stateTracker = new DelegateStateTracker(TEST_SUB_ID, mAppCallback,
- mSipDelegate);
+ mSipDelegate, mRcsStats);
Set<FeatureTagState> deniedTags = getMmTelDeniedTag();
- stateTracker.sipDelegateConnected(deniedTags);
+ Set<String> supportedTags = getSupportedTags();
+ stateTracker.sipDelegateConnected(supportedTags, deniedTags);
// SipDelegate created
verify(mAppCallback).onCreated(mSipDelegate);
DelegateRegistrationState regState = new DelegateRegistrationState.Builder()
@@ -296,4 +309,11 @@
SipDelegateManager.DENIED_REASON_NOT_ALLOWED));
return deniedTags;
}
+
+ private Set<String> getSupportedTags() {
+ Set<String> supportedTags = new ArraySet<>();
+ supportedTags.add(ImsSignallingUtils.ONE_TO_ONE_CHAT_TAG);
+ supportedTags.add(ImsSignallingUtils.GROUP_CHAT_TAG);
+ return supportedTags;
+ }
}
diff --git a/tests/src/com/android/services/telephony/rcs/SipDelegateControllerTest.java b/tests/src/com/android/services/telephony/rcs/SipDelegateControllerTest.java
index 5b0e7c5..78f6894 100644
--- a/tests/src/com/android/services/telephony/rcs/SipDelegateControllerTest.java
+++ b/tests/src/com/android/services/telephony/rcs/SipDelegateControllerTest.java
@@ -107,7 +107,8 @@
assertTrue(future.get());
verify(mMockMessageTracker).openTransport(mMockSipDelegate, request.getFeatureTags(),
Collections.emptySet());
- verify(mMockDelegateStateTracker).sipDelegateConnected(Collections.emptySet());
+ verify(mMockDelegateStateTracker).sipDelegateConnected(request.getFeatureTags(),
+ Collections.emptySet());
}
@SmallTest
@@ -138,7 +139,7 @@
allowedTags.removeAll(deniedTags.stream().map(FeatureTagState::getFeatureTag)
.collect(Collectors.toSet()));
verify(mMockMessageTracker).openTransport(mMockSipDelegate, allowedTags, deniedTags);
- verify(mMockDelegateStateTracker).sipDelegateConnected(deniedTags);
+ verify(mMockDelegateStateTracker).sipDelegateConnected(allowedTags, deniedTags);
}
@SmallTest
@@ -249,7 +250,9 @@
Collections.emptySet());
verify(mMockMessageTracker).openTransport(mMockSipDelegate, newFts,
Collections.emptySet());
- verify(mMockDelegateStateTracker, times(2)).sipDelegateConnected(Collections.emptySet());
+ verify(mMockDelegateStateTracker).sipDelegateConnected(
+ request.getFeatureTags(), Collections.emptySet());
+ verify(mMockDelegateStateTracker).sipDelegateConnected(newFts, Collections.emptySet());
}
private void createSipDelegate(DelegateRequest request, SipDelegateController controller)
diff --git a/tests/src/com/android/services/telephony/rcs/SipSessionTrackerTest.java b/tests/src/com/android/services/telephony/rcs/SipSessionTrackerTest.java
index 823a8be..37abb83 100644
--- a/tests/src/com/android/services/telephony/rcs/SipSessionTrackerTest.java
+++ b/tests/src/com/android/services/telephony/rcs/SipSessionTrackerTest.java
@@ -16,19 +16,28 @@
package com.android.services.telephony.rcs;
+import static com.android.internal.telephony.TelephonyStatsLog.SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+
import android.net.Uri;
import android.telephony.ims.SipMessage;
import android.util.Base64;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.android.internal.telephony.metrics.RcsStats;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.nio.ByteBuffer;
import java.util.Arrays;
@@ -90,11 +99,104 @@
// Keep track of the string entry so we can generate unique strings.
private int mStringEntryCounter = 0;
private SipSessionTracker mTrackerUT;
+ private static final int TEST_SUB_ID = 1;
+ 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;
@Before
public void setUp() {
mStringEntryCounter = 0;
- mTrackerUT = new SipSessionTracker();
+ MockitoAnnotations.initMocks(this);
+ mTrackerUT = new SipSessionTracker(TEST_SUB_ID, mRcsStats);
+ }
+
+ @Test
+ public void testMetricsEndedGracefullyBye() {
+ DialogAttributes attr = new DialogAttributes();
+ // INVITE
+ SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr);
+ filterMessage(inviteRequest, attr);
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr);
+
+ // confirmed dialog
+ attr.setToTag();
+ SipMessage inviteConfirm = generateSipResponse("200", "OK", attr);
+ filterMessage(inviteConfirm, attr);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
+
+ // Gracefully Ended
+ SipMessage inviteClose = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr);
+ filterMessage(inviteClose, attr);
+
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr);
+
+ // verify Metrics information
+ verify(mRcsStats).onSipTransportSessionClosed(eq(TEST_SUB_ID), eq(attr.callId),
+ eq(TEST_SIP_CLOSE_RESPONSE_CODE), eq(true));
+ }
+
+ @Test
+ public void testMetricsCloseCleanupSession() {
+ //mTrackerUT.setRcsStats(mRcsStats);
+ DialogAttributes attr = new DialogAttributes();
+ // INVITE A -> B
+ SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr);
+ filterMessage(inviteRequest, attr);
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr);
+
+ // confirmed dialog
+ attr.setToTag();
+ SipMessage inviteConfirm = generateSipResponse("200", "OK", attr);
+ filterMessage(inviteConfirm, attr);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
+
+ //forcefully close session
+ mTrackerUT.cleanupSession(attr.callId);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
+
+ // verify Metrics information
+ verify(mRcsStats).onSipTransportSessionClosed(eq(TEST_SUB_ID), eq(attr.callId),
+ eq(TEST_SIP_CLOSE_RESPONSE_CODE), eq(false));
+ }
+
+ @Test
+ public void testMetricsCloseClearAllSessions() {
+ //mTrackerUT.setRcsStats(mRcsStats);
+ DialogAttributes attr = new DialogAttributes();
+
+ // INVITE
+ SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr);
+ filterMessage(inviteRequest, attr);
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr);
+
+ // confirmed dialog
+ attr.setToTag();
+ SipMessage inviteConfirm = generateSipResponse("200", "OK", attr);
+ filterMessage(inviteConfirm, attr);
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
+
+ //forcefully close session
+ mTrackerUT.clearAllSessions();
+ assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
+ assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
+ assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
+
+ // verify Metrics information
+ verify(mRcsStats).onSipTransportSessionClosed(eq(TEST_SUB_ID), eq(attr.callId),
+ eq(TEST_SIP_CLOSE_RESPONSE_CODE), eq(false));
}
@Test
@@ -291,7 +393,8 @@
public void testAcknowledgeMessageFailed() {
DialogAttributes attr = new DialogAttributes();
SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr);
- mTrackerUT.filterSipMessage(inviteRequest);
+ mTrackerUT.filterSipMessage(
+ SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, inviteRequest);
// Do not acknowledge the request and ensure that the operation has not been applied yet.
assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
@@ -310,8 +413,10 @@
// We unexpectedly received two filter requests for the same branchId without
// acknowledgePendingMessage being called in between. Ensure that when it is called, it
// applies both operations.
- mTrackerUT.filterSipMessage(inviteRequest);
- mTrackerUT.filterSipMessage(inviteConfirm);
+ mTrackerUT.filterSipMessage(
+ SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, inviteRequest);
+ mTrackerUT.filterSipMessage(
+ SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, inviteConfirm);
assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
// we should skip right to confirmed as both operations run back-to-back
@@ -321,7 +426,8 @@
}
private void filterMessage(SipMessage m, DialogAttributes attr) {
- mTrackerUT.filterSipMessage(m);
+ mTrackerUT.filterSipMessage(
+ SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, m);
mTrackerUT.acknowledgePendingMessage(attr.branchId);
}
private void verifyContainsCallIds(Set<SipDialog> callIdSet, DialogAttributes... attrs) {
diff --git a/tests/src/com/android/services/telephony/rcs/TransportSipMessageValidatorTest.java b/tests/src/com/android/services/telephony/rcs/TransportSipMessageValidatorTest.java
index 4d222e3..0fe897f 100644
--- a/tests/src/com/android/services/telephony/rcs/TransportSipMessageValidatorTest.java
+++ b/tests/src/com/android/services/telephony/rcs/TransportSipMessageValidatorTest.java
@@ -16,6 +16,9 @@
package com.android.services.telephony.rcs;
+import static com.android.internal.telephony.TelephonyStatsLog.SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__INCOMING;
+import static com.android.internal.telephony.TelephonyStatsLog.SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -23,6 +26,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -37,6 +41,7 @@
import com.android.TelephonyTestBase;
import com.android.TestExecutorService;
+import com.android.internal.telephony.metrics.RcsStats;
import com.android.services.telephony.rcs.validator.IncomingTransportStateValidator;
import com.android.services.telephony.rcs.validator.OutgoingTransportStateValidator;
import com.android.services.telephony.rcs.validator.ValidationResult;
@@ -76,6 +81,8 @@
private IncomingTransportStateValidator mIncomingStateValidator;
@Mock
private OutgoingTransportStateValidator mOutgoingStateValidator;
+ @Mock
+ private RcsStats mRcsStats;
@Before
public void setUp() throws Exception {
@@ -117,12 +124,50 @@
}
@Test
+ public void testMetricsResponse() {
+ String testSipInviteMethod = "INVITE";
+ String testSipMessageMethod = "MESSAGE";
+ int testSipResponseCode = 200;
+ int testMessageError = 0;
+
+ TestExecutorService executor = new TestExecutorService();
+ TransportSipMessageValidator tracker = openTransport(executor);
+ // Since the incoming/outgoing messages were verified, there should have been two calls
+ // to filter the message.
+ verify(mSipSessionTracker).filterSipMessage(
+ SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, TEST_MESSAGE);
+ verify(mSipSessionTracker).filterSipMessage(
+ SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__INCOMING, TEST_MESSAGE);
+
+ assertTrue(tracker.verifyOutgoingMessage(generateSipRequest("INVITE",
+ "testId1"), TEST_CONFIG_VERSION).isValidated);
+ verify(mRcsStats).onSipMessageRequest(eq("testId1"),
+ eq(testSipInviteMethod),
+ eq(SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING));
+
+ assertTrue(tracker.verifyOutgoingMessage(generateSipRequest("MESSAGE",
+ "testId2"), TEST_CONFIG_VERSION).isValidated);
+ verify(mRcsStats).onSipMessageRequest(eq("testId2"),
+ eq(testSipMessageMethod),
+ eq(SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING));
+
+ assertTrue(tracker.verifyIncomingMessage(
+ generateSipResponse("200", "OK", "testId2"))
+ .isValidated);
+ verify(mRcsStats).onSipMessageResponse(eq(TEST_SUB_ID), eq("testId2"),
+ eq(testSipResponseCode), eq(testMessageError));
+ }
+
+ @Test
public void testSessionTrackerFiltering() {
TestExecutorService executor = new TestExecutorService();
TransportSipMessageValidator tracker = openTransport(executor);
// Since the incoming/outgoing messages were verified, there should have been two calls
// to filter the message.
- verify(mSipSessionTracker, times(2)).filterSipMessage(TEST_MESSAGE);
+ verify(mSipSessionTracker).filterSipMessage(
+ SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, TEST_MESSAGE);
+ verify(mSipSessionTracker).filterSipMessage(
+ SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__INCOMING, TEST_MESSAGE);
// ensure pass through methods are working
tracker.acknowledgePendingMessage("abc");
verify(mSipSessionTracker).acknowledgePendingMessage("abc");
@@ -140,7 +185,10 @@
assertFalse(tracker.verifyOutgoingMessage(TEST_MESSAGE, TEST_CONFIG_VERSION).isValidated);
// The number of times the filter method was called should still only be two after these
// messages were not validated.
- verify(mSipSessionTracker, times(2)).filterSipMessage(TEST_MESSAGE);
+ verify(mSipSessionTracker).filterSipMessage(
+ SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, TEST_MESSAGE);
+ verify(mSipSessionTracker).filterSipMessage(
+ SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__INCOMING, TEST_MESSAGE);
}
@@ -482,6 +530,27 @@
doReturn(ValidationResult.SUCCESS).when(mIncomingStateValidator).validate(any());
doReturn(mIncomingStateValidator).when(mIncomingStateValidator).andThen(any());
return new TransportSipMessageValidator(TEST_SUB_ID, executor, mSipSessionTracker,
- mOutgoingStateValidator, mIncomingStateValidator);
+ mOutgoingStateValidator, mIncomingStateValidator, mRcsStats);
+ }
+
+ private SipMessage generateSipResponse(String statusCode, String statusString, String callId) {
+ String fromHeader = "Alice <sip:alice@atlanta.com>;tag=1928301774";
+ String toHeader = "Bob <sip:bob@biloxi.com>";
+ String branchId = "AAAA";
+ String fromTag = "tag=1928301774";
+ String toTag = "";
+ return SipMessageUtils.generateSipResponse(statusCode, statusString, fromHeader,
+ toHeader, branchId, callId, fromTag, toTag);
+ }
+
+ private SipMessage generateSipRequest(String requestMethod, String callId) {
+ String fromHeader = "Alice <sip:alice@atlanta.com>;tag=1928301774";
+ String toHeader = "Bob <sip:bob@biloxi.com>";
+ String branchId = "AAAA";
+ String fromTag = "tag=1928301774";
+ String toTag = "";
+ String toUri = "sip:bob@biloxi.com";
+ return SipMessageUtils.generateSipRequest(requestMethod, fromHeader, toHeader,
+ toUri, branchId, callId, fromTag, toTag);
}
}