Merge "Revert "Revert "Ensure MediaSession is ONLY made active when routed to wired headset.""" into udc-dev
diff --git a/Android.bp b/Android.bp
index 1b422aa..c5141ca 100644
--- a/Android.bp
+++ b/Android.bp
@@ -28,6 +28,9 @@
static_libs: [
"androidx.annotation_annotation",
],
+ libs: [
+ "services",
+ ],
resource_dirs: ["res"],
proto: {
type: "nano",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 648b4a9..d42dcff 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -64,6 +64,7 @@
<uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"/>
+ <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<permission android:name="android.permission.BROADCAST_CALLLOG_INFO"
android:label="Broadcast the call type/duration information"
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 9874044..489fab7 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,2 +1,3 @@
-[Hook Scripts]
-aosp_hook = ${REPO_ROOT}/packages/services/Telecomm/scripts/aosp_tag_preupload.py ${PREUPLOAD_COMMIT}
+# Uncomment to re-enable aosp warning.
+#[Hook Scripts]
+#aosp_hook = ${REPO_ROOT}/packages/services/Telecomm/scripts/aosp_tag_preupload.py ${PREUPLOAD_COMMIT}
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index fc36464..79f29bf 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -90,7 +90,7 @@
<string name="answering_ends_other_managed_video_call" msgid="1988508241432031327">"መመለስ እየተካሄደ ያለ የቪዲዮ ጥሪዎን ይጨርሳል"</string>
<string name="answer_incoming_call" msgid="2045888814782215326">"ይመልሱ"</string>
<string name="decline_incoming_call" msgid="922147089348451310">"አትቀበል"</string>
- <string name="cant_call_due_to_no_supported_service" msgid="1635626384149947077">"የዚህን አይነት ጥሪዎች የሚደግፉ መደወያ መለያዎች ስለሌሉ ጥሪ መደረግ አይችልም።"</string>
+ <string name="cant_call_due_to_no_supported_service" msgid="1635626384149947077">"የዚህን ዓይነት ጥሪዎች የሚደግፉ መደወያ መለያዎች ስለሌሉ ጥሪ መደረግ አይችልም።"</string>
<string name="cant_call_due_to_ongoing_call" msgid="8004235328451385493">"በ<xliff:g id="OTHER_CALL">%1$s</xliff:g> ጥሪዎ ምክንያት ጥሪ መደረግ አይችልም።"</string>
<string name="cant_call_due_to_ongoing_calls" msgid="6379163795277824868">"በ<xliff:g id="OTHER_CALL">%1$s</xliff:g> ጥሪዎችዎ ምክንያት ጥሪዎች መደረግ አይችሉም።"</string>
<string name="cant_call_due_to_ongoing_unknown_call" msgid="8243532328969433172">"በሌላ መተግበሪያ ውስጥ ባለ ጥሪ ምክንያት ጥሪ መደረግ አይችልም።"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index b6db5c4..0b279b4 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -123,7 +123,7 @@
<string name="developer_title" msgid="9146088855661672353">"Menu do programador de telecomunicações"</string>
<string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Não é possível atender chamadas durante uma chamada de emergência."</string>
<string name="cancel" msgid="6733466216239934756">"Cancelar"</string>
- <string name="back" msgid="6915955601805550206">"Voltar atrás"</string>
+ <string name="back" msgid="6915955601805550206">"Anterior"</string>
<string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Auricular"</string>
<string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
<string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Auscultadores com microfone integrado com fios"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 98115db..5ed2ebe 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -51,7 +51,7 @@
<string name="outgoing_call_error_no_phone_number_supplied" msgid="7665135102566099778">"కాల్ చేయడానికి, చెల్లుబాటు అయ్యే నంబర్ను నమోదు చేయండి."</string>
<string name="duplicate_video_call_not_allowed" msgid="5754746140185781159">"ఈ సమయంలో కాల్ను జోడించడం సాధ్యపడదు."</string>
<string name="no_vm_number" msgid="2179959110602180844">"వాయిస్ మెయిల్ నంబర్ లేదు"</string>
- <string name="no_vm_number_msg" msgid="1339245731058529388">"సిమ్ కార్డులో వాయిస్ మెయిల్ నంబర్ ఏదీ నిల్వ చేయబడలేదు."</string>
+ <string name="no_vm_number_msg" msgid="1339245731058529388">"సిమ్ కార్డులో వాయిస్ మెయిల్ నంబర్ ఏదీ స్టోరేజ్ చేయబడలేదు."</string>
<string name="add_vm_number_str" msgid="5179510133063168998">"నంబర్ను జోడించండి"</string>
<string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"<xliff:g id="NEW_APP">%s</xliff:g>ను మీ ఆటోమేటిక్ ఫోన్ యాప్గా చేయాలా?"</string>
<string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"ఆటోమేటిక్గా సెట్ చేయండి"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 7033f36..689828e 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -27,8 +27,8 @@
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"واپس کال کریں"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"پیغام"</string>
<string name="notification_disconnectedCall_title" msgid="1790131923692416928">"کال غیر منسلک کر دیا گیا"</string>
- <string name="notification_disconnectedCall_body" msgid="600491714584417536">"ہنگامی کال کی وجہ سے <xliff:g id="CALLER">%s</xliff:g> کی کال کو غیر منسلک کر دیا گیا ہے۔"</string>
- <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"ہنگامی کال لگائے جانے کی وجہ سے آپ کی کال غیر منسلک ہوگئی ہے۔"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"ایمرجنسی کال کی وجہ سے <xliff:g id="CALLER">%s</xliff:g> کی کال کو غیر منسلک کر دیا گیا ہے۔"</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"ایمرجنسی کال لگائے جانے کی وجہ سے آپ کی کال غیر منسلک ہوگئی ہے۔"</string>
<string name="notification_audioProcessing_title" msgid="1619035039880584575">"پس منظر کی کال"</string>
<string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> پس منظر میں کال پر کارروائی کر رہی ہے۔ یہ ایپ کال کے دوران آواز تک رسائی حاصل اور چلا سکتی ہے۔"</string>
<string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> نے جواب دینا بند کر دیا"</string>
@@ -46,7 +46,7 @@
<string name="respond_via_sms_confirmation_format" msgid="2932395476561267842">"پیغام <xliff:g id="PHONE_NUMBER">%s</xliff:g> کو بھیج دیا گیا۔"</string>
<string name="respond_via_sms_failure_format" msgid="5198680980054596391">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> پر پیغام نہیں بھیجا جا سکا۔"</string>
<string name="enable_account_preference_title" msgid="6949224486748457976">"کالنگ اکاؤنٹس"</string>
- <string name="outgoing_call_not_allowed_user_restriction" msgid="3424338207838851646">"صرف ہنگامی کالز کی اجازت ہے۔"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="3424338207838851646">"صرف ایمرجنسی کالز کی اجازت ہے۔"</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="8590468836581488679">"یہ ایپلی کیشن فون کی اجازت کے بغیر باہر جانے والی کالیں نہیں کر سکتی۔"</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="7665135102566099778">"کال کرنے کیلئے، ایک درست نمبر درج کریں۔"</string>
<string name="duplicate_video_call_not_allowed" msgid="5754746140185781159">"اس وقت کال شامل نہیں کی جا سکتی ہے۔"</string>
@@ -73,11 +73,11 @@
<string name="non_primary_user" msgid="315564589279622098">"صرف آلہ کا مالک مسدود کردہ نمبرز کو دیکھ سکتا ہے اور ان کا نظم کر سکتا ہے۔"</string>
<string name="delete_icon_description" msgid="5335959254954774373">"غیر مسدود کریں"</string>
<string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"مسدود کرنا عارضی طور پر آف ہے"</string>
- <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"جب آپ کوئی ایمرجنسی نمبر ڈائل کرتے یا اسے متن بھیجتے ہیں تو انسداد کو آف کر دیا جاتا ہے تاکہ ہنگامی سروسز آپ سے رابطہ کر سکیں۔"</string>
+ <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"جب آپ کوئی ایمرجنسی نمبر ڈائل کرتے یا اسے متن بھیجتے ہیں تو انسداد کو آف کر دیا جاتا ہے تاکہ ایمرجنسی سروسز آپ سے رابطہ کر سکیں۔"</string>
<string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"اب دوبارہ فعال کریں"</string>
<string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> مسدود کر دیا گیا"</string>
<string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> غیر مسدود کر دیا گیا"</string>
- <string name="blocked_numbers_block_emergency_number_message" msgid="4198550501500893890">"ہنگامی نمبر مسدود کرنے سے قاصر۔"</string>
+ <string name="blocked_numbers_block_emergency_number_message" msgid="4198550501500893890">"ایمرجنسی نمبر مسدود کرنے سے قاصر۔"</string>
<string name="blocked_numbers_number_already_blocked_message" msgid="2301270825735665458">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> پہلے ہی مسدود ہے۔"</string>
<string name="toast_personal_call_msg" msgid="5817631570381795610">"کال کرنے کیلئے ذاتی ڈائلر استعمال ہو رہا ہے"</string>
<string name="notification_incoming_call" msgid="1233481138362230894">"<xliff:g id="CALL_FROM">%2$s</xliff:g> کی جانب سے <xliff:g id="CALL_VIA">%1$s</xliff:g> کال"</string>
@@ -118,10 +118,10 @@
<string name="phone_settings_unavailable_summary_txt" msgid="8221686031038282633">"کالز کو مسدود کریں جہاں یہ نمبر دستیاب نہ ہو"</string>
<string name="phone_strings_call_blocking_turned_off_notification_title_txt" msgid="2895809176537908791">"کال مسدود کرنا"</string>
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="1713632946174016619">"کال مسدود کرنا غیر فعال ہو گیا ہے"</string>
- <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"ہنگامی کال کی گئی"</string>
- <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"ہنگامی حالت میں جواب دہندگان کو آپ سے رابطہ کرنے کی اجازت دینے کیلئے کال مسدود کرنا غیر فعال ہو گیا ہے۔"</string>
- <string name="developer_title" msgid="9146088855661672353">"ٹیلی کام ڈویلپر مینو"</string>
- <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"ہنگامی کال کے دوران کالز نہیں لی جائیں گی۔"</string>
+ <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"ایمرجنسی کال کی گئی"</string>
+ <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"ایمرجنسی حالت میں جواب دہندگان کو آپ سے رابطہ کرنے کی اجازت دینے کیلئے کال مسدود کرنا غیر فعال ہو گیا ہے۔"</string>
+ <string name="developer_title" msgid="9146088855661672353">"ٹیلی کام ڈویلپر مینیو"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"ایمرجنسی کال کے دوران کالز نہیں لی جائیں گی۔"</string>
<string name="cancel" msgid="6733466216239934756">"منسوخ کریں"</string>
<string name="back" msgid="6915955601805550206">"پیچھے"</string>
<string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ایئر پیس"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index e7b1a07..b422c73 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -28,11 +28,11 @@
<string name="notification_missedCall_message" msgid="4054698824390076431">"短訊"</string>
<string name="notification_disconnectedCall_title" msgid="1790131923692416928">"已中斷的通話"</string>
<string name="notification_disconnectedCall_body" msgid="600491714584417536">"因撥打緊急電話緣故,與<xliff:g id="CALLER">%s</xliff:g>的通話已中斷。"</string>
- <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"因撥打緊急電話緣故,您的通話已中斷。"</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"因撥打緊急電話緣故,你的通話已中斷。"</string>
<string name="notification_audioProcessing_title" msgid="1619035039880584575">"背景通話"</string>
<string name="notification_audioProcessing_body" msgid="8811420157964118913">"「<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>」正在處理背景中的通話。這個應用程式或會存取通話,或是在通話中播放音訊。"</string>
<string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g>已停止回應"</string>
- <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"您使用了裝置隨付的手機應用程式來通話"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"你使用了裝置隨付的手機應用程式來通話"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"通話已靜音。"</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"擴音器已啟用"</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"我現在不方便通話,有什麼事呢?"</string>
@@ -56,14 +56,14 @@
<string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"要將<xliff:g id="NEW_APP">%s</xliff:g>設為預設電話應用程式嗎?"</string>
<string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"設為預設"</string>
<string name="change_default_dialer_dialog_negative" msgid="8648669840052697821">"取消"</string>
- <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"「<xliff:g id="NEW_APP">%s</xliff:g>」將可撥打電話並控制所有相關功能。只有您信任的應用程式,才應設為預設手機應用程式。"</string>
+ <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"「<xliff:g id="NEW_APP">%s</xliff:g>」將可撥打電話並控制所有相關功能。只有你信任的應用程式,才應設為預設手機應用程式。"</string>
<string name="change_default_call_screening_dialog_title" msgid="5365787219927262408">"要將「<xliff:g id="NEW_APP">%s</xliff:g>」設為預設來電過濾應用程式嗎?"</string>
<string name="change_default_call_screening_warning_message_for_disable_old_app" msgid="2039830033533243164">"「<xliff:g id="OLD_APP">%s</xliff:g>」無法再篩選來電。"</string>
- <string name="change_default_call_screening_warning_message" msgid="9020537562292754269">"「<xliff:g id="NEW_APP">%s</xliff:g>」將可查看通訊錄以外來電者的相關資訊,並封鎖這些來電。只有您信任的應用程式才適合設為預設來電過濾應用程式。"</string>
+ <string name="change_default_call_screening_warning_message" msgid="9020537562292754269">"「<xliff:g id="NEW_APP">%s</xliff:g>」將可查看通訊錄以外來電者的相關資訊,並封鎖這些來電。只有你信任的應用程式才適合設為預設來電過濾應用程式。"</string>
<string name="change_default_call_screening_dialog_affirmative" msgid="7162433828280058647">"設為預設"</string>
<string name="change_default_call_screening_dialog_negative" msgid="1839266125623106342">"取消"</string>
<string name="blocked_numbers" msgid="8322134197039865180">"已封鎖的號碼"</string>
- <string name="blocked_numbers_msg" msgid="2797422132329662697">"您不會收到已封鎖號碼的來電或短訊。"</string>
+ <string name="blocked_numbers_msg" msgid="2797422132329662697">"你不會收到已封鎖號碼的來電或短訊。"</string>
<string name="block_number" msgid="3784343046852802722">"新增號碼"</string>
<string name="unblock_dialog_body" msgid="2723393535797217261">"要解除封鎖 <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> 嗎?"</string>
<string name="unblock_button" msgid="8732021675729981781">"解除封鎖"</string>
@@ -73,7 +73,7 @@
<string name="non_primary_user" msgid="315564589279622098">"只有裝置擁有者可查看和管理已封鎖的號碼。"</string>
<string name="delete_icon_description" msgid="5335959254954774373">"解除封鎖"</string>
<string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"暫時關閉封鎖功能"</string>
- <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"在您撥打或發短訊至緊急號碼後,封鎖功能會停用,以確保緊急服務可與您聯絡。"</string>
+ <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"在你撥打或發短訊至緊急號碼後,封鎖功能會停用,以確保緊急服務可與你聯絡。"</string>
<string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"立即重新啟用"</string>
<string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"已封鎖 <xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g>"</string>
<string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"已解除對 <xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> 的封鎖"</string>
@@ -82,17 +82,17 @@
<string name="toast_personal_call_msg" msgid="5817631570381795610">"使用個人撥號器撥打電話"</string>
<string name="notification_incoming_call" msgid="1233481138362230894">"來自<xliff:g id="CALL_FROM">%2$s</xliff:g>的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 通話"</string>
<string name="notification_incoming_video_call" msgid="5795968314037063900">"來自<xliff:g id="CALL_FROM">%2$s</xliff:g>的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 視像通話"</string>
- <string name="answering_ends_other_call" msgid="8653544281903986641">"如果接聽,您的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 通話將會結束"</string>
- <string name="answering_ends_other_calls" msgid="3702302838456922535">"如果接聽,您的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 通話將會結束"</string>
- <string name="answering_ends_other_video_call" msgid="8572022039304239958">"如果接聽,您的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 視像通話將會結束"</string>
- <string name="answering_ends_other_managed_call" msgid="4031778317409881805">"如果接聽,您進行中的通話將會結束"</string>
- <string name="answering_ends_other_managed_calls" msgid="3974069768615307659">"如果接聽,您進行中的通話將會結束"</string>
- <string name="answering_ends_other_managed_video_call" msgid="1988508241432031327">"如果接聽,您進行中的視像通話將會結束"</string>
+ <string name="answering_ends_other_call" msgid="8653544281903986641">"如果接聽,你的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 通話將會結束"</string>
+ <string name="answering_ends_other_calls" msgid="3702302838456922535">"如果接聽,你的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 通話將會結束"</string>
+ <string name="answering_ends_other_video_call" msgid="8572022039304239958">"如果接聽,你的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 視像通話將會結束"</string>
+ <string name="answering_ends_other_managed_call" msgid="4031778317409881805">"如果接聽,你進行中的通話將會結束"</string>
+ <string name="answering_ends_other_managed_calls" msgid="3974069768615307659">"如果接聽,你進行中的通話將會結束"</string>
+ <string name="answering_ends_other_managed_video_call" msgid="1988508241432031327">"如果接聽,你進行中的視像通話將會結束"</string>
<string name="answer_incoming_call" msgid="2045888814782215326">"接聽"</string>
<string name="decline_incoming_call" msgid="922147089348451310">"拒絕"</string>
<string name="cant_call_due_to_no_supported_service" msgid="1635626384149947077">"沒有通話帳戶支援這類通話,因此無法撥打電話。"</string>
- <string name="cant_call_due_to_ongoing_call" msgid="8004235328451385493">"由於您已在進行 <xliff:g id="OTHER_CALL">%1$s</xliff:g> 通話,因此無法撥打電話。"</string>
- <string name="cant_call_due_to_ongoing_calls" msgid="6379163795277824868">"由於您已在進行 <xliff:g id="OTHER_CALL">%1$s</xliff:g> 通話,因此無法撥打電話。"</string>
+ <string name="cant_call_due_to_ongoing_call" msgid="8004235328451385493">"由於你已在進行 <xliff:g id="OTHER_CALL">%1$s</xliff:g> 通話,因此無法撥打電話。"</string>
+ <string name="cant_call_due_to_ongoing_calls" msgid="6379163795277824868">"由於你已在進行 <xliff:g id="OTHER_CALL">%1$s</xliff:g> 通話,因此無法撥打電話。"</string>
<string name="cant_call_due_to_ongoing_unknown_call" msgid="8243532328969433172">"由於已在另一個應用程式中進行通話,因此無法撥打電話。"</string>
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"來電"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"未接來電"</string>
@@ -100,11 +100,11 @@
<string name="notification_channel_background_calls" msgid="7785659903711350506">"背景通話"</string>
<string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"已中斷的通話"</string>
<string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"當機的手機應用程式"</string>
- <string name="alert_outgoing_call" msgid="5319895109298927431">"如果撥打此電話,您的 <xliff:g id="OTHER_APP">%1$s</xliff:g> 通話將會結束。"</string>
+ <string name="alert_outgoing_call" msgid="5319895109298927431">"如果撥打此電話,你的 <xliff:g id="OTHER_APP">%1$s</xliff:g> 通話將會結束。"</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"選擇如何撥打此電話"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"使用「<xliff:g id="OTHER_APP">%1$s</xliff:g>」將通話重新導向"</string>
<string name="alert_place_unredirect_outgoing_call" msgid="2467608535225764006">"使用我的電話號碼撥打"</string>
- <string name="alert_redirect_outgoing_call_timeout" msgid="5568101425637373060">"<xliff:g id="OTHER_APP">%1$s</xliff:g>無法撥打電話。建議您使用其他通話重新導向應用程式,或向開發人員求助。"</string>
+ <string name="alert_redirect_outgoing_call_timeout" msgid="5568101425637373060">"<xliff:g id="OTHER_APP">%1$s</xliff:g>無法撥打電話。建議你使用其他通話重新導向應用程式,或向開發人員求助。"</string>
<string name="phone_settings_call_blocking_txt" msgid="7311523114822507178">"來電封鎖"</string>
<string name="phone_settings_number_not_in_contact_txt" msgid="2602249106007265757">"不在通訊錄中的號碼"</string>
<string name="phone_settings_number_not_in_contact_summary_txt" msgid="963327038085718969">"封鎖不在通訊錄中的號碼"</string>
@@ -119,7 +119,7 @@
<string name="phone_strings_call_blocking_turned_off_notification_title_txt" msgid="2895809176537908791">"來電封鎖"</string>
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="1713632946174016619">"已停用來電封鎖功能"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"已撥緊急電話"</string>
- <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"已停用來電封鎖功能,以便救援人員與您聯絡。"</string>
+ <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"已停用來電封鎖功能,以便救援人員與你聯絡。"</string>
<string name="developer_title" msgid="9146088855661672353">"電信開發商選單"</string>
<string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"使用緊急電話期間無法接聽電話。"</string>
<string name="cancel" msgid="6733466216239934756">"取消"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index b0e50b0..15f765b 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -73,4 +73,8 @@
<!-- When set, Telecom will attempt to bind to the {@link CallDiagnosticService} implementation
defined by the app with this package name. -->
<string name="call_diagnostic_service_package_name"></string>
+
+ <!-- When true, the options in the call blocking settings to block unavailable and unknown
+ callers are combined into a single toggle. -->
+ <bool name="combine_options_to_block_unavailable_and_unknown_callers">true</bool>
</resources>
diff --git a/src/com/android/server/telecom/AsyncRingtonePlayer.java b/src/com/android/server/telecom/AsyncRingtonePlayer.java
index 24a65b7..3fbac1f 100644
--- a/src/com/android/server/telecom/AsyncRingtonePlayer.java
+++ b/src/com/android/server/telecom/AsyncRingtonePlayer.java
@@ -18,9 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.media.AudioAttributes;
import android.media.Ringtone;
-import android.media.RingtoneManager;
import android.media.VolumeShaper;
import android.net.Uri;
import android.os.Handler;
@@ -32,6 +30,9 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.util.Preconditions;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+
/**
* Plays the default ringtone. Uses {@link Ringtone} in a separate thread so that this class can be
* used from the main thread.
@@ -57,13 +58,16 @@
* If {@link VolumeShaper.Configuration} is specified, it is applied to the ringtone to change
* the volume of the ringtone as it plays.
*
- * @param ringtone The {@link Ringtone}.
+ * @param ringtoneSupplier The {@link Ringtone} factory.
+ * @param ringtoneConsumer The {@link Ringtone} post-creation callback (to start the vibration).
*/
- public void play(@NonNull Ringtone ringtone) {
+ public void play(@NonNull Supplier<Ringtone> ringtoneSupplier,
+ BiConsumer<Ringtone, Boolean> ringtoneConsumer) {
Log.d(this, "Posting play.");
SomeArgs args = SomeArgs.obtain();
- args.arg1 = ringtone;
- args.arg2 = Log.createSubsession();
+ args.arg1 = ringtoneSupplier;
+ args.arg2 = ringtoneConsumer;
+ args.arg3 = Log.createSubsession();
postMessage(EVENT_PLAY, true /* shouldCreateHandler */, args);
}
@@ -122,30 +126,50 @@
* Starts the actual playback of the ringtone. Executes on ringtone-thread.
*/
private void handlePlay(SomeArgs args) {
- Ringtone ringtone = (Ringtone) args.arg1;
- Session session = (Session) args.arg2;
+ Supplier<Ringtone> ringtoneSupplier = (Supplier<Ringtone>) args.arg1;
+ BiConsumer<Ringtone, Boolean> ringtoneConsumer = (BiConsumer<Ringtone, Boolean>) args.arg2;
+ Session session = (Session) args.arg3;
args.recycle();
Log.continueSession(session, "ARP.hP");
try {
- // don't bother with any of this if there is an EVENT_STOP waiting.
+ // Don't bother with any of this if there is an EVENT_STOP waiting, but give the
+ // consumer a chance to do anything no matter what.
if (mHandler.hasMessages(EVENT_STOP)) {
+ ringtoneConsumer.accept(null, /* stopped= */ true);
return;
}
-
- ThreadUtil.checkNotOnMainThread();
-
- setRingtone(ringtone);
- Uri uri = mRingtone.getUri();
- String uriString = (uri != null ? uri.toSafeString() : "");
- Log.i(this, "handlePlay: Play ringtone. Uri: " + uriString);
- mRingtone.setLooping(true);
- if (mRingtone.isPlaying()) {
- Log.d(this, "Ringtone already playing.");
- return;
+ Ringtone ringtone = null;
+ boolean hasStopped = false;
+ try {
+ ringtone = ringtoneSupplier.get();
+ // Ringtone supply can be slow. Re-check for stop event.
+ if (mHandler.hasMessages(EVENT_STOP)) {
+ hasStopped = true;
+ ringtone.stop(); // proactively release the ringtone.
+ return;
+ }
+ // setRingtone even if null - it also stops any current ringtone to be consistent
+ // with the overall state.
+ setRingtone(ringtone);
+ if (mRingtone == null) {
+ // The ringtoneConsumer can still vibrate at this stage.
+ Log.w(this, "No ringtone was found bail out from playing.");
+ return;
+ }
+ Uri uri = mRingtone.getUri();
+ String uriString = (uri != null ? uri.toSafeString() : "");
+ Log.i(this, "handlePlay: Play ringtone. Uri: " + uriString);
+ mRingtone.setLooping(true);
+ if (mRingtone.isPlaying()) {
+ Log.d(this, "Ringtone already playing.");
+ return;
+ }
+ mRingtone.play();
+ Log.i(this, "Play ringtone, looping.");
+ } finally {
+ ringtoneConsumer.accept(ringtone, hasStopped);
}
- mRingtone.play();
- Log.i(this, "Play ringtone, looping.");
} finally {
Log.cancelSubsession(session);
}
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 02dff13..e2d8489 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -17,6 +17,7 @@
package com.android.server.telecom;
import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
+import static android.telecom.Call.EVENT_DISPLAY_SOS_MESSAGE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -38,6 +39,7 @@
import android.provider.CallLog;
import android.provider.ContactsContract.Contacts;
import android.telecom.BluetoothCallQualityReport;
+import android.telecom.CallAttributes;
import android.telecom.CallAudioState;
import android.telecom.CallDiagnosticService;
import android.telecom.CallDiagnostics;
@@ -316,6 +318,12 @@
*/
private long mCreationTimeMillis;
+ /**
+ * The elapsed realtime millis when this call was created; this can be used to determine how
+ * long has elapsed since the call was first created.
+ */
+ private long mCreationElapsedRealtimeMillis;
+
/** The time this call was made active. */
private long mConnectTimeMillis = 0;
@@ -555,6 +563,25 @@
private boolean mIsSelfManaged = false;
private boolean mIsTransactionalCall = false;
+ private CallingPackageIdentity mCallingPackageIdentity = new CallingPackageIdentity();
+
+ /**
+ * CallingPackageIdentity is responsible for storing properties about the calling package that
+ * initiated the call. For example, if MyVoipApp requests to add a call with Telecom, we can
+ * store their UID and PID when we are still bound to that package.
+ */
+ public static class CallingPackageIdentity {
+ public int mCallingPackageUid = -1;
+ public int mCallingPackagePid = -1;
+
+ public CallingPackageIdentity() {
+ }
+
+ CallingPackageIdentity(Bundle extras) {
+ mCallingPackageUid = extras.getInt(CallAttributes.CALLER_UID_KEY, -1);
+ mCallingPackagePid = extras.getInt(CallAttributes.CALLER_PID_KEY, -1);
+ }
+ }
/**
* Indicates whether this call is streaming.
@@ -801,6 +828,7 @@
mClockProxy = clockProxy;
mToastFactory = toastFactory;
mCreationTimeMillis = mClockProxy.currentTimeMillis();
+ mCreationElapsedRealtimeMillis = mClockProxy.elapsedRealtime();
mMissedReason = MISSED_REASON_NOT_MISSED;
mStartRingTime = 0;
@@ -1830,6 +1858,17 @@
setConnectionProperties(getConnectionProperties());
}
+ public void setCallingPackageIdentity(Bundle extras) {
+ mCallingPackageIdentity = new CallingPackageIdentity(extras);
+ // These extras should NOT be propagated to Dialer and should be removed.
+ extras.remove(CallAttributes.CALLER_PID_KEY);
+ extras.remove(CallAttributes.CALLER_UID_KEY);
+ }
+
+ public CallingPackageIdentity getCallingPackageIdentity() {
+ return mCallingPackageIdentity;
+ }
+
public void setTransactionServiceWrapper(TransactionalServiceWrapper service) {
mTransactionalService = service;
}
@@ -2019,8 +2058,12 @@
return mCreationTimeMillis;
}
- public void setCreationTimeMillis(long time) {
- mCreationTimeMillis = time;
+ /**
+ * @return The elapsed realtime millis when the call was created; ONLY useful for determining
+ * how long has elapsed since the call was first created.
+ */
+ public long getCreationElapsedRealtimeMillis() {
+ return mCreationElapsedRealtimeMillis;
}
public long getConnectTimeMillis() {
@@ -2397,6 +2440,8 @@
@Override
public void handleCreateConferenceFailure(DisconnectCause disconnectCause) {
+ Log.i(this, "handleCreateConferenceFailure; callid=%s, disconnectCause=%s",
+ getId(), disconnectCause);
clearConnectionService();
setDisconnectCause(disconnectCause);
mCallsManager.markCallAsDisconnected(this, disconnectCause);
@@ -2417,6 +2462,8 @@
@Override
public void handleCreateConnectionFailure(DisconnectCause disconnectCause) {
+ Log.i(this, "handleCreateConnectionFailure; callid=%s, disconnectCause=%s",
+ getId(), disconnectCause);
clearConnectionService();
setDisconnectCause(disconnectCause);
mCallsManager.markCallAsDisconnected(this, disconnectCause);
@@ -3655,6 +3702,7 @@
if (mTransactionalService != null) {
Log.i(this, "stopRtt: called on TransactionalService. doing nothing");
} else if (mConnectionService != null) {
+ Log.addEvent(this, LogUtils.Events.REQUEST_RTT, "stop");
mConnectionService.stopRtt(this);
} else {
// If this gets called by the in-call app before the connection service is set, we'll
@@ -3668,6 +3716,7 @@
Log.i(this, "sendRttRequest: called on TransactionalService. doing nothing");
return;
}
+ Log.addEvent(this, LogUtils.Events.REQUEST_RTT, "start");
createRttStreams();
mConnectionService.startRtt(this, getInCallToCsRttPipeForCs(), getCsToInCallRttPipeForCs());
}
@@ -3691,12 +3740,14 @@
public void onRttConnectionFailure(int reason) {
Log.i(this, "Got RTT initiation failure with reason %d", reason);
+ Log.addEvent(this, LogUtils.Events.ON_RTT_FAILED, "reason=" + reason);
for (Listener l : mListeners) {
l.onRttInitiationFailure(this, reason);
}
}
public void onRemoteRttRequest() {
+ Log.addEvent(this, LogUtils.Events.ON_RTT_REQUEST);
if (isRttCall()) {
Log.w(this, "Remote RTT request on a call that's already RTT");
return;
@@ -3721,6 +3772,8 @@
Log.i(this, "handleRttRequestResponse: called on TransactionalService. doing nothing");
return;
}
+ Log.addEvent(this, LogUtils.Events.RESPOND_TO_RTT_REQUEST, "id=" + id + ", accept="
+ + accept);
if (accept) {
createRttStreams();
Log.i(this, "RTT request %d accepted.", id);
@@ -3903,6 +3956,10 @@
return mCallDirection == CALL_DIRECTION_UNKNOWN;
}
+ public boolean isOutgoing() {
+ return mCallDirection == CALL_DIRECTION_OUTGOING;
+ }
+
/**
* Determines if this call is in a disconnecting state.
*
@@ -3997,7 +4054,8 @@
public void setRttMode(int mode) {
mRttMode = mode;
- // TODO: hook this up to CallAudioManager
+ Log.addEvent(this, LogUtils.Events.SET_RRT_MODE, "mode=" + mode);
+ // TODO: hook this up to CallAudioManager.
}
/**
@@ -4072,6 +4130,12 @@
l.onReceivedCallQualityReport(this, callQuality);
}
} else {
+ if (event.equals(EVENT_DISPLAY_SOS_MESSAGE) && !isEmergencyCall()) {
+ Log.w(this, "onConnectionEvent: EVENT_DISPLAY_SOS_MESSAGE is sent "
+ + "without an emergency call");
+ return;
+ }
+
for (Listener l : mListeners) {
l.onConnectionEvent(this, event, extras);
}
diff --git a/src/com/android/server/telecom/CallAnomalyWatchdog.java b/src/com/android/server/telecom/CallAnomalyWatchdog.java
index d3dbbc9..045671e 100644
--- a/src/com/android/server/telecom/CallAnomalyWatchdog.java
+++ b/src/com/android/server/telecom/CallAnomalyWatchdog.java
@@ -43,6 +43,8 @@
* Watchdog class responsible for detecting potential anomalous conditions for {@link Call}s.
*/
public class CallAnomalyWatchdog extends CallsManagerListenerBase implements Call.Listener {
+ private final EmergencyCallDiagnosticLogger mEmergencyCallDiagnosticLogger;
+
/**
* Class used to track the call state as it pertains to the watchdog. The watchdog cares about
* both the call state and whether a {@link ConnectionService} has finished creating the
@@ -146,15 +148,21 @@
public CallAnomalyWatchdog(ScheduledExecutorService executorService,
TelecomSystem.SyncRoot lock,
- Timeouts.Adapter timeoutAdapter, ClockProxy clockProxy) {
+ Timeouts.Adapter timeoutAdapter, ClockProxy clockProxy,
+ EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger) {
mScheduledExecutorService = executorService;
mLock = lock;
mTimeoutAdapter = timeoutAdapter;
mClockProxy = clockProxy;
+ mEmergencyCallDiagnosticLogger = emergencyCallDiagnosticLogger;
}
+ /**
+ * Start tracking a call that we're waiting for a ConnectionService to create.
+ * @param call the call.
+ */
@Override
- public void onCallCreated(Call call) {
+ public void onStartCreateConnection(Call call) {
maybeTrackCall(call);
call.addListener(this);
}
@@ -165,23 +173,24 @@
}
/**
+ * Override of {@link CallsManagerListenerBase} to track when calls have failed to be created by
+ * a ConnectionService. These calls should no longer be tracked by the CallAnomalyWatchdog.
+ * @param call the call
+ */
+ @Override
+ public void onCreateConnectionFailed(Call call) {
+ Log.i(this, "onCreateConnectionFailed: call=%s", call.toString());
+ stopTrackingCall(call);
+ }
+
+ /**
* Override of {@link CallsManagerListenerBase} to track when calls are removed
* @param call the call
*/
@Override
public void onCallRemoved(Call call) {
- if (mScheduledFutureMap.containsKey(call)) {
- ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
- existingTimeout.cancel(false /* cancelIfRunning */);
- mScheduledFutureMap.remove(call);
- }
- if (mCallsPendingDestruction.contains(call)) {
- mCallsPendingDestruction.remove(call);
- }
- if (mWatchdogCallStateMap.containsKey(call)) {
- mWatchdogCallStateMap.remove(call);
- }
- call.removeListener(this);
+ Log.i(this, "onCallRemoved: call=%s", call.toString());
+ stopTrackingCall(call);
}
/**
@@ -192,7 +201,10 @@
* @param newState the new state
*/
@Override
- public void onCallStateChanged(Call call, int oldState, int newState) { maybeTrackCall(call); }
+ public void onCallStateChanged(Call call, int oldState, int newState) {
+ Log.i(this, "onCallStateChanged: call=%s", call.toString());
+ maybeTrackCall(call);
+ }
/**
* Override of {@link Call.Listener} so we can capture successful creation of calls.
@@ -211,7 +223,8 @@
*/
@Override
public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {
- maybeTrackCall(call);
+ Log.i(this, "onFailedOutgoingCall: call=%s", call.toString());
+ stopTrackingCall(call);
}
/**
@@ -229,7 +242,27 @@
*/
@Override
public void onFailedIncomingCall(Call call) {
- maybeTrackCall(call);
+ Log.i(this, "onFailedIncomingCall: call=%s", call.toString());
+ stopTrackingCall(call);
+ }
+
+ /**
+ * Helper method used to stop CallAnomalyWatchdog from tracking or destroying the call.
+ * @param call the call.
+ */
+ private void stopTrackingCall(Call call) {
+ if (mScheduledFutureMap.containsKey(call)) {
+ ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
+ existingTimeout.cancel(false /* cancelIfRunning */);
+ mScheduledFutureMap.remove(call);
+ }
+ if (mCallsPendingDestruction.contains(call)) {
+ mCallsPendingDestruction.remove(call);
+ }
+ if (mWatchdogCallStateMap.containsKey(call)) {
+ mWatchdogCallStateMap.remove(call);
+ }
+ call.removeListener(this);
}
/**
@@ -273,7 +306,7 @@
}
}
- private long getTimeoutMillis(Call call, WatchdogCallState state) {
+ public long getTimeoutMillis(Call call, WatchdogCallState state) {
boolean isVoip = call.getIsVoipAudioMode();
boolean isEmergency = call.isEmergencyCall();
@@ -328,6 +361,7 @@
mAnomalyReporter.reportAnomaly(
WATCHDOG_DISCONNECTED_STUCK_EMERGENCY_CALL_UUID,
WATCHDOG_DISCONNECTED_STUCK_EMERGENCY_CALL_MSG);
+ mEmergencyCallDiagnosticLogger.reportStuckCall(call);
} else {
mAnomalyReporter.reportAnomaly(
WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index f56320f..531f42e 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -49,6 +49,7 @@
import java.util.HashMap;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.Executor;
/**
* This class describes the available routes of a call as a state machine.
@@ -81,14 +82,16 @@
WiredHeadsetManager wiredHeadsetManager,
StatusBarNotifier statusBarNotifier,
CallAudioManager.AudioServiceFactory audioServiceFactory,
- int earpieceControl) {
+ int earpieceControl,
+ Executor asyncTaskExecutor) {
return new CallAudioRouteStateMachine(context,
callsManager,
bluetoothManager,
wiredHeadsetManager,
statusBarNotifier,
audioServiceFactory,
- earpieceControl);
+ earpieceControl,
+ asyncTaskExecutor);
}
}
/** Values for CallAudioRouteStateMachine constructor's earPieceRouting arg. */
@@ -554,6 +557,7 @@
return HANDLED;
case STREAMING_FORCE_ENABLED:
transitionTo(mStreamingState);
+ return HANDLED;
default:
return NOT_HANDLED;
}
@@ -1478,6 +1482,8 @@
private final QuiescentSpeakerRoute mQuiescentSpeakerRoute = new QuiescentSpeakerRoute();
private final StreamingState mStreamingState = new StreamingState();
+ private final Executor mAsyncTaskExecutor;
+
/**
* A few pieces of hidden state. Used to avoid exponential explosion of number of explicit
* states
@@ -1516,7 +1522,8 @@
WiredHeadsetManager wiredHeadsetManager,
StatusBarNotifier statusBarNotifier,
CallAudioManager.AudioServiceFactory audioServiceFactory,
- int earpieceControl) {
+ int earpieceControl,
+ Executor asyncTaskExecutor) {
super(NAME);
mContext = context;
mCallsManager = callsManager;
@@ -1526,7 +1533,7 @@
mStatusBarNotifier = statusBarNotifier;
mAudioServiceFactory = audioServiceFactory;
mLock = callsManager.getLock();
-
+ mAsyncTaskExecutor = asyncTaskExecutor;
createStates(earpieceControl);
}
@@ -1538,7 +1545,7 @@
WiredHeadsetManager wiredHeadsetManager,
StatusBarNotifier statusBarNotifier,
CallAudioManager.AudioServiceFactory audioServiceFactory,
- int earpieceControl, Looper looper) {
+ int earpieceControl, Looper looper, Executor asyncTaskExecutor) {
super(NAME, looper);
mContext = context;
mCallsManager = callsManager;
@@ -1548,6 +1555,7 @@
mStatusBarNotifier = statusBarNotifier;
mAudioServiceFactory = audioServiceFactory;
mLock = callsManager.getLock();
+ mAsyncTaskExecutor = asyncTaskExecutor;
createStates(earpieceControl);
}
@@ -1629,7 +1637,8 @@
new IntentFilter(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED));
mStatusBarNotifier.notifyMute(initState.isMuted());
- mStatusBarNotifier.notifySpeakerphone(initState.getRoute() == CallAudioState.ROUTE_SPEAKER);
+ // We used to call mStatusBarNotifier.notifySpeakerphone, but that makes no sense as there
+ // is never a call at this boot (init) time.
setInitialState(mRouteCodeToQuiescentState.get(initState.getRoute()));
start();
}
@@ -1722,26 +1731,32 @@
private void setSpeakerphoneOn(boolean on) {
Log.i(this, "turning speaker phone %s", on);
- AudioDeviceInfo speakerDevice = null;
- for (AudioDeviceInfo info : mAudioManager.getAvailableCommunicationDevices()) {
- if (info.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
- speakerDevice = info;
- break;
+ final boolean hasAnyCalls = mCallsManager.hasAnyCalls();
+ // These APIs are all via two-way binder calls so can potentially block Telecom. Since none
+ // of this has to happen in the Telecom lock we'll offload it to the async executor.
+ mAsyncTaskExecutor.execute(() -> {
+ AudioDeviceInfo speakerDevice = null;
+ for (AudioDeviceInfo info : mAudioManager.getAvailableCommunicationDevices()) {
+ if (info.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+ speakerDevice = info;
+ break;
+ }
}
- }
- boolean speakerOn = false;
- if (speakerDevice != null && on) {
- boolean result = mAudioManager.setCommunicationDevice(speakerDevice);
- if (result) {
- speakerOn = true;
+ boolean speakerOn = false;
+ if (speakerDevice != null && on) {
+ boolean result = mAudioManager.setCommunicationDevice(speakerDevice);
+ if (result) {
+ speakerOn = true;
+ }
+ } else {
+ AudioDeviceInfo curDevice = mAudioManager.getCommunicationDevice();
+ if (curDevice != null
+ && curDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+ mAudioManager.clearCommunicationDevice();
+ }
}
- } else {
- AudioDeviceInfo curDevice = mAudioManager.getCommunicationDevice();
- if (curDevice != null && curDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
- mAudioManager.clearCommunicationDevice();
- }
- }
- mStatusBarNotifier.notifySpeakerphone(speakerOn);
+ mStatusBarNotifier.notifySpeakerphone(hasAnyCalls && speakerOn);
+ });
}
private void setBluetoothOn(String address) {
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index 7f864b8..7953324 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -182,9 +182,10 @@
boolean isPrivilegedDialer = defaultDialerCache.isDefaultOrSystemDialer(callingPackage,
initiatingUser.getIdentifier());
+
NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
context, callsManager, intent, callsManager.getPhoneNumberUtilsAdapter(),
- isPrivilegedDialer, defaultDialerCache);
+ isPrivilegedDialer, defaultDialerCache, new MmiUtils());
// If the broadcaster comes back with an immediate error, disconnect and show a dialog.
NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall();
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
old mode 100755
new mode 100644
index 0ec2362..a7bbf84
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -386,7 +386,12 @@
if (okayToLog) {
AddCallArgs args = new AddCallArgs(mContext, paramBuilder.build(),
logCallCompletedListener);
+ Log.addEvent(call, LogUtils.Events.LOG_CALL, "number=" + Log.piiHandle(logNumber)
+ + ",postDial=" + Log.piiHandle(call.getPostDialDigits()) + ",pres="
+ + call.getHandlePresentation());
logCallAsync(args);
+ } else {
+ Log.addEvent(call, LogUtils.Events.SKIP_CALL_LOG);
}
}
diff --git a/src/com/android/server/telecom/CallStreamingController.java b/src/com/android/server/telecom/CallStreamingController.java
index cd8a690..31b2235 100644
--- a/src/com/android/server/telecom/CallStreamingController.java
+++ b/src/com/android/server/telecom/CallStreamingController.java
@@ -54,10 +54,12 @@
private CallStreamingServiceConnection mConnection;
private boolean mIsStreaming;
private final Object mLock;
+ private TelecomSystem.SyncRoot mTelecomLock;
- public CallStreamingController(Context context) {
+ public CallStreamingController(Context context, TelecomSystem.SyncRoot telecomLock) {
mLock = new Object();
mContext = context;
+ mTelecomLock = telecomLock;
}
private void onConnectedInternal(Call call, TransactionalServiceWrapper wrapper,
@@ -101,6 +103,7 @@
private final CallsManager mCallsManager;
public QueryCallStreamingTransaction(CallsManager callsManager) {
+ super(callsManager.getLock());
mCallsManager = callsManager;
}
@@ -128,7 +131,9 @@
private Call mCall;
private boolean mEnterInterception;
- public AudioInterceptionTransaction(Call call, boolean enterInterception) {
+ public AudioInterceptionTransaction(Call call, boolean enterInterception,
+ TelecomSystem.SyncRoot lock) {
+ super(lock);
mCall = call;
mEnterInterception = enterInterception;
}
@@ -164,6 +169,7 @@
public StreamingServiceTransaction(Context context, TransactionalServiceWrapper wrapper,
Call call) {
+ super(mTelecomLock);
mWrapper = wrapper;
mCall = call;
mUserHandle = mCall.getInitiatingUser();
@@ -177,7 +183,7 @@
CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
RoleManager roleManager = mContext.getSystemService(RoleManager.class);
- PackageManager packageManager = mContext.getSystemService(PackageManager.class);
+ PackageManager packageManager = mContext.getPackageManager();
if (roleManager == null || packageManager == null) {
Log.e(TAG, "Can't find system service");
future.complete(new VoipCallTransactionResult(
@@ -185,9 +191,8 @@
return future;
}
- // TODO: change this role to RoleManager.ROLE_STREAMING
- List<String> holders = roleManager.getRoleHoldersAsUser(RoleManager.ROLE_DIALER,
- mUserHandle);
+ List<String> holders = roleManager.getRoleHoldersAsUser(
+ RoleManager.ROLE_SYSTEM_CALL_STREAMING, mUserHandle);
if (holders.isEmpty()) {
Log.e(TAG, "Can't find streaming app");
future.complete(new VoipCallTransactionResult(
@@ -241,6 +246,7 @@
private static final String TAG = "UnbindStreamingServiceTransaction";
public UnbindStreamingServiceTransaction() {
+ super(mTelecomLock);
}
@SuppressLint("LongLogTag")
@@ -306,6 +312,7 @@
@StreamingCall.StreamingCallState int mState;
public CallStreamingStateChangeTransaction(@StreamingCall.StreamingCallState int state) {
+ super(mTelecomLock);
mState = state;
}
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 09d8787..ccc8e59 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -60,8 +60,6 @@
import android.media.MediaPlayer;
import android.media.ToneGenerator;
import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
@@ -78,6 +76,7 @@
import android.provider.BlockedNumberContract;
import android.provider.BlockedNumberContract.SystemContract;
import android.provider.CallLog.Calls;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.sysprop.TelephonyProperties;
import android.telecom.CallAttributes;
@@ -101,14 +100,15 @@
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telephony.CarrierConfigManager;
+import android.telephony.CellIdentity;
import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
import android.widget.Button;
import com.android.internal.annotations.VisibleForTesting;
@@ -117,6 +117,7 @@
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
import com.android.server.telecom.callfiltering.BlockCheckerAdapter;
import com.android.server.telecom.callfiltering.BlockCheckerFilter;
+import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
import com.android.server.telecom.callfiltering.CallFilterResultCallback;
import com.android.server.telecom.callfiltering.CallFilteringResult;
import com.android.server.telecom.callfiltering.CallFilteringResult.Builder;
@@ -127,7 +128,6 @@
import com.android.server.telecom.callredirection.CallRedirectionProcessor;
import com.android.server.telecom.components.ErrorDialogActivity;
import com.android.server.telecom.components.TelecomBroadcastReceiver;
-import com.android.server.telecom.settings.BlockedNumbersUtil;
import com.android.server.telecom.stats.CallFailureCause;
import com.android.server.telecom.ui.AudioProcessingNotification;
import com.android.server.telecom.ui.CallRedirectionTimeoutDialogActivity;
@@ -135,6 +135,8 @@
import com.android.server.telecom.ui.DisconnectedCallNotifier;
import com.android.server.telecom.ui.IncomingCallNotifier;
import com.android.server.telecom.ui.ToastFactory;
+import com.android.server.telecom.voip.TransactionManager;
+import com.android.server.telecom.voip.VoipCallMonitor;
import java.util.ArrayList;
import java.util.Arrays;
@@ -175,13 +177,13 @@
@VisibleForTesting
public interface CallsManagerListener {
/**
- * Informs listeners when a {@link Call} is newly created but not yet added to
- * {@link #mCalls}. This is where the call has not yet been created by the underlying
- * {@link ConnectionService}.
+ * Informs listeners when a {@link Call} is newly created, but not yet returned by a
+ * {@link android.telecom.ConnectionService} implementation.
* @param call the call.
*/
- default void onCallCreated(Call call) {}
+ default void onStartCreateConnection(Call call) {}
void onCallAdded(Call call);
+ void onCreateConnectionFailed(Call call);
void onCallRemoved(Call call);
void onCallStateChanged(Call call, int oldState, int newState);
void onConnectionServiceChanged(
@@ -279,6 +281,15 @@
public static final String EXCEPTION_RETRIEVING_PHONE_ACCOUNTS_EMERGENCY_ERROR_MSG =
"Exception thrown while retrieving list of potential phone accounts when placing an "
+ "emergency call.";
+ public static final UUID EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_UUID =
+ UUID.fromString("f9a916c8-8d61-4550-9ad3-11c2e84f6364");
+ public static final String EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_MSG =
+ "An emergency call was disconnected after the connection was created but before the "
+ + "call was successfully added to CallsManager.";
+ public static final UUID EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_UUID =
+ UUID.fromString("2e994acb-1997-4345-8bf3-bad04303de26");
+ public static final String EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_MSG =
+ "An emergency call was aborted since there were no available phone accounts.";
private static final int[] OUTGOING_CALL_STATES =
{CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
@@ -427,9 +438,14 @@
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final EmergencyCallHelper mEmergencyCallHelper;
private final RoleManagerAdapter mRoleManagerAdapter;
+ private final VoipCallMonitor mVoipCallMonitor;
private final CallEndpointController mCallEndpointController;
private final CallAnomalyWatchdog mCallAnomalyWatchdog;
+
+ private final EmergencyCallDiagnosticLogger mEmergencyCallDiagnosticLogger;
private final CallStreamingController mCallStreamingController;
+ private final BlockedNumbersAdapter mBlockedNumbersAdapter;
+ private final TransactionManager mTransactionManager;
private final ConnectionServiceFocusManager.CallsManagerRequester mRequester =
new ConnectionServiceFocusManager.CallsManagerRequester() {
@@ -450,8 +466,6 @@
private boolean mCanAddCall = true;
- private int mMaxNumberOfSimultaneouslyActiveSims = -1;
-
private Runnable mStopTone;
private LinkedList<HandlerThread> mGraphHandlerThreads;
@@ -463,6 +477,7 @@
private AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();
+ private final MmiUtils mMmiUtils = new MmiUtils();
/**
* Listener to PhoneAccountRegistrar events.
*/
@@ -493,34 +508,14 @@
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- Log.startSession("CM.CCCR");
String action = intent.getAction();
if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)
|| SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED.equals(action)) {
- new UpdateEmergencyCallNotificationTask().doInBackground(
- Pair.create(context, Log.createSubsession()));
+ updateEmergencyCallNotificationAsync(context);
}
}
};
- private static class UpdateEmergencyCallNotificationTask
- extends AsyncTask<Pair<Context, Session>, Void, Void> {
- @SafeVarargs
- @Override
- protected final Void doInBackground(Pair<Context, Session>... args) {
- if (args == null || args.length != 1 || args[0] == null) {
- Log.e(this, new IllegalArgumentException(), "Incorrect invocation");
- return null;
- }
- Log.continueSession(args[0].second, "CM.UECNT");
- Context context = args[0].first;
- BlockedNumbersUtil.updateEmergencyCallNotification(context,
- SystemContract.shouldShowEmergencyCallNotification(context));
- Log.endSession();
- return null;
- }
- }
-
/**
* Initializes the required Telecom components.
*/
@@ -558,8 +553,12 @@
ToastFactory toastFactory,
CallEndpointControllerFactory callEndpointControllerFactory,
CallAnomalyWatchdog callAnomalyWatchdog,
+ Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter,
Executor asyncTaskExecutor,
- Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter) {
+ BlockedNumbersAdapter blockedNumbersAdapter,
+ TransactionManager transactionManager,
+ EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger) {
+
mContext = context;
mLock = lock;
mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
@@ -576,6 +575,7 @@
mTimeoutsAdapter = timeoutsAdapter;
mEmergencyCallHelper = emergencyCallHelper;
mCallerInfoLookupHelper = callerInfoLookupHelper;
+ mEmergencyCallDiagnosticLogger = emergencyCallDiagnosticLogger;
mDtmfLocalTonePlayer =
new DtmfLocalTonePlayer(new DtmfLocalTonePlayer.ToneGeneratorProxy());
@@ -587,7 +587,8 @@
wiredHeadsetManager,
statusBarNotifier,
audioServiceFactory,
- CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT
+ CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
+ asyncTaskExecutor
);
callAudioRouteStateMachine.initialize();
@@ -641,7 +642,10 @@
mClockProxy = clockProxy;
mToastFactory = toastFactory;
mRoleManagerAdapter = roleManagerAdapter;
- mCallStreamingController = new CallStreamingController(mContext);
+ mTransactionManager = transactionManager;
+ mBlockedNumbersAdapter = blockedNumbersAdapter;
+ mCallStreamingController = new CallStreamingController(mContext, mLock);
+ mVoipCallMonitor = new VoipCallMonitor(mContext, mLock);
mListeners.add(mInCallWakeLockController);
mListeners.add(statusBarNotifier);
@@ -657,10 +661,14 @@
mListeners.add(mProximitySensorManager);
mListeners.add(audioProcessingNotification);
mListeners.add(callAnomalyWatchdog);
+ mListeners.add(mEmergencyCallDiagnosticLogger);
mListeners.add(mCallStreamingController);
// this needs to be after the mCallAudioManager
mListeners.add(mPhoneStateBroadcaster);
+ mListeners.add(mVoipCallMonitor);
+
+ mVoipCallMonitor.startMonitor();
// There is no USER_SWITCHED broadcast for user 0, handle it here explicitly.
final UserManager userManager = UserManager.get(mContext);
@@ -735,8 +743,7 @@
@Override
public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {
- Log.v(this, "onFailedOutgoingCall, call: %s", call);
-
+ Log.i(this, "onFailedOutgoingCall for call %s", call);
markCallAsRemoved(call);
}
@@ -862,6 +869,9 @@
return;
}
+ // Store the shouldSuppress value in the call object which will be passed to InCallServices
+ incomingCall.setCallIsSuppressedByDoNotDisturb(result.shouldSuppressCallDueToDndStatus);
+
// Inform our connection service that call filtering is done (if it was performed at all).
if (incomingCall.isUsingCallFiltering()) {
boolean isInContacts = incomingCall.getCallerInfo() != null
@@ -991,14 +1001,15 @@
@Override
public void onFailedIncomingCall(Call call) {
+ Log.i(this, "onFailedIncomingCall for call %s", call);
setCallState(call, CallState.DISCONNECTED, "failed incoming call");
call.removeListener(this);
}
@Override
public void onSuccessfulUnknownCall(Call call, int callState) {
- setCallState(call, callState, "successful unknown call");
Log.i(this, "onSuccessfulUnknownCall for call %s", call);
+ setCallState(call, callState, "successful unknown call");
addCall(call);
}
@@ -1278,6 +1289,10 @@
return mEmergencyCallHelper;
}
+ EmergencyCallDiagnosticLogger getEmergencyCallDiagnosticLogger() {
+ return mEmergencyCallDiagnosticLogger;
+ }
+
public DefaultDialerCache getDefaultDialerCache() {
return mDefaultDialerCache;
}
@@ -1387,11 +1402,11 @@
isConference, /* isConference */
mClockProxy,
mToastFactory);
- notifyCallCreated(call);
// set properties for transactional call
if (extras.containsKey(TelecomManager.TRANSACTION_CALL_ID_KEY)) {
call.setIsTransactionalCall(true);
+ call.setCallingPackageIdentity(extras);
call.setConnectionCapabilities(
extras.getInt(CallAttributes.CALL_CAPABILITIES_KEY,
CallAttributes.SUPPORTS_SET_INACTIVE), true);
@@ -1551,8 +1566,11 @@
// transactional calls should skip Call#startCreateConnection below
// as that is meant for Call objects with a ConnectionServiceWrapper
call.setState(CallState.RINGING, "explicitly set new incoming to ringing");
+ // Transactional calls don't get created via a connection service; they are added now.
+ call.setIsCreateConnectionComplete(true);
addCall(call);
} else {
+ notifyStartCreateConnection(call);
call.startCreateConnection(mPhoneAccountRegistrar);
}
return call;
@@ -1579,12 +1597,11 @@
false, /* isConference */
mClockProxy,
mToastFactory);
- notifyCallCreated(call);
-
call.initAnalytics();
setIntentExtrasAndStartTime(call, extras);
call.addListener(this);
+ notifyStartCreateConnection(call);
call.startCreateConnection(mPhoneAccountRegistrar);
}
@@ -1700,6 +1717,7 @@
if (extras.containsKey(TelecomManager.TRANSACTION_CALL_ID_KEY)) {
call.setIsTransactionalCall(true);
+ call.setCallingPackageIdentity(extras);
call.setConnectionCapabilities(
extras.getInt(CallAttributes.CALL_CAPABILITIES_KEY,
CallAttributes.SUPPORTS_SET_INACTIVE), true);
@@ -1707,8 +1725,23 @@
}
call.initAnalytics(callingPackage, creationLogs.toString());
- // Let listeners know that we just created a new call but haven't added it yet.
- notifyCallCreated(call);
+
+ // Log info for emergency call
+ if (call.isEmergencyCall()) {
+ String simNumeric = "";
+ String networkNumeric = "";
+ int defaultVoiceSubId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+ if (defaultVoiceSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ TelephonyManager tm = getTelephonyManager().createForSubscriptionId(
+ defaultVoiceSubId);
+ CellIdentity cellIdentity = tm.getLastKnownCellIdentity();
+ simNumeric = tm.getSimOperatorNumeric();
+ networkNumeric = (cellIdentity != null) ? cellIdentity.getPlmn() : "";
+ }
+ TelecomStatsLog.write(TelecomStatsLog.EMERGENCY_NUMBER_DIALED,
+ handle.getSchemeSpecificPart(),
+ callingPackage, simNumeric, networkNumeric);
+ }
// Ensure new calls related to self-managed calls/connections are set as such. This
// will be overridden when the actual connection is returned in startCreateConnection,
@@ -1831,7 +1864,7 @@
CompletableFuture<Call> makeRoomForCall = setAccountHandle.thenComposeAsync(
potentialPhoneAccounts -> {
Log.i(CallsManager.this, "make room for outgoing call stage");
- if (isPotentialInCallMMICode(handle) && !isSelfManaged) {
+ if (mMmiUtils.isPotentialInCallMMICode(handle) && !isSelfManaged) {
return CompletableFuture.completedFuture(finalCall);
}
// If a call is being reused, then it has already passed the
@@ -1923,19 +1956,23 @@
return CompletableFuture.completedFuture(null);
}
if (accountSuggestions == null || accountSuggestions.isEmpty()) {
- Uri callUri = callToPlace.getHandle();
- if (PhoneAccount.SCHEME_TEL.equals(callUri.getScheme())) {
- int managedProfileUserId = getManagedProfileUserId(mContext,
- initiatingUser.getIdentifier());
- if (managedProfileUserId != UserHandle.USER_NULL
- && mPhoneAccountRegistrar.getCallCapablePhoneAccounts(
- handle.getScheme(), false,
- UserHandle.of(managedProfileUserId), false).size()
- != 0) {
- boolean dialogShown = showSwitchToManagedProfileDialog(
- callUri, initiatingUser, managedProfileUserId);
- if (dialogShown) {
- return CompletableFuture.completedFuture(null);
+ if (isSwitchToManagedProfileDialogFlagEnabled()) {
+ Uri callUri = callToPlace.getHandle();
+ if (PhoneAccount.SCHEME_TEL.equals(callUri.getScheme())) {
+ int managedProfileUserId = getManagedProfileUserId(mContext,
+ initiatingUser.getIdentifier());
+ if (managedProfileUserId != UserHandle.USER_NULL
+ &&
+ mPhoneAccountRegistrar.getCallCapablePhoneAccounts(
+ handle.getScheme(), false,
+ UserHandle.of(managedProfileUserId),
+ false).size()
+ != 0) {
+ boolean dialogShown = showSwitchToManagedProfileDialog(
+ callUri, initiatingUser, managedProfileUserId);
+ if (dialogShown) {
+ return CompletableFuture.completedFuture(null);
+ }
}
}
}
@@ -1943,6 +1980,12 @@
Log.i(CallsManager.this, "Aborting call since there are no"
+ " available accounts.");
showErrorMessage(R.string.cant_call_due_to_no_supported_service);
+ mListeners.forEach(l -> l.onCreateConnectionFailed(callToPlace));
+ if (callToPlace.isEmergencyCall()){
+ mAnomalyReporter.reportAnomaly(
+ EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_UUID,
+ EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_MSG);
+ }
return CompletableFuture.completedFuture(null);
}
boolean needsAccountSelection = accountSuggestions.size() > 1
@@ -2064,7 +2107,7 @@
setIntentExtrasAndStartTime(callToUse, extras);
setCallSourceToAnalytics(callToUse, originalIntent);
- if (isPotentialMMICode(handle) && !isSelfManaged) {
+ if (mMmiUtils.isPotentialMMICode(handle) && !isSelfManaged) {
// Do not add the call if it is a potential MMI code.
callToUse.addListener(this);
} else if (!mCalls.contains(callToUse)) {
@@ -2092,6 +2135,11 @@
return UserHandle.USER_NULL;
}
+ private boolean isSwitchToManagedProfileDialogFlagEnabled() {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+ "enable_switch_to_managed_profile_dialog", false);
+ }
+
private boolean showSwitchToManagedProfileDialog(Uri callUri, UserHandle initiatingUser,
int managedProfileUserId) {
try {
@@ -2644,6 +2692,7 @@
disconnectSelfManagedCalls("place emerg call" /* reason */);
}
try {
+ notifyStartCreateConnection(call);
call.startCreateConnection(mPhoneAccountRegistrar);
} catch (Exception exception) {
// If an exceptions is thrown while creating the connection, prompt the user to
@@ -3117,6 +3166,18 @@
return constructPossiblePhoneAccounts(handle, user, isVideo, isEmergency, false);
}
+ // Returns whether the device is capable of 2 simultaneous active voice calls on different subs.
+ private boolean isDsdaCallingPossible() {
+ try {
+ return getTelephonyManager().getMaxNumberOfSimultaneouslyActiveSims() > 1
+ || getTelephonyManager().getPhoneCapability()
+ .getMaxActiveVoiceSubscriptions() > 1;
+ } catch (Exception e) {
+ Log.w(this, "exception in isDsdaCallingPossible(): ", e);
+ return false;
+ }
+ }
+
public List<PhoneAccountHandle> constructPossiblePhoneAccounts(Uri handle, UserHandle user,
boolean isVideo, boolean isEmergency, boolean isConference) {
@@ -3133,13 +3194,12 @@
capabilities,
isEmergency ? 0 : PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY,
isEmergency);
- if (mMaxNumberOfSimultaneouslyActiveSims < 0) {
- mMaxNumberOfSimultaneouslyActiveSims =
- getTelephonyManager().getMaxNumberOfSimultaneouslyActiveSims();
- }
// Only one SIM PhoneAccount can be active at one time for DSDS. Only that SIM PhoneAccount
// should be available if a call is already active on the SIM account.
- if (mMaxNumberOfSimultaneouslyActiveSims == 1) {
+ // Similarly, the emergency call should be attempted over the same PhoneAccount as the
+ // ongoing call. However, if the ongoing call is over cross-SIM registration, then the
+ // emergency call will be attempted over a different Phone object at a later stage.
+ if (isEmergency || !isDsdaCallingPossible()) {
List<PhoneAccountHandle> simAccounts =
mPhoneAccountRegistrar.getSimPhoneAccountsOfCurrentUser();
PhoneAccountHandle ongoingCallAccount = null;
@@ -3491,6 +3551,8 @@
* @param disconnectCause The disconnect cause, see {@link android.telecom.DisconnectCause}.
*/
public void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
+ Log.i(this, "markCallAsDisconnected: call=%s; disconnectCause=%s",
+ call.toString(), disconnectCause.toString());
int oldState = call.getState();
if (call.getState() == CallState.SIMULATED_RINGING
&& disconnectCause.getCode() == DisconnectCause.REMOTE) {
@@ -3515,6 +3577,17 @@
}
}
+ // Notify listeners that the call was disconnected before being added to CallsManager.
+ // Listeners will not receive onAdded or onRemoved callbacks.
+ if (!mCalls.contains(call)) {
+ if (call.isEmergencyCall()) {
+ mAnomalyReporter.reportAnomaly(
+ EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_UUID,
+ EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_MSG);
+ }
+ mListeners.forEach(l -> l.onCreateConnectionFailed(call));
+ }
+
// If a call diagnostic service is in use, we will log the original telephony-provided
// disconnect cause, inform the CDS of the disconnection, and then chain the update of the
// call state until AFTER the CDS reports it's result back.
@@ -3584,40 +3657,57 @@
* @param call The call.
*/
private void performRemoval(Call call) {
- mInCallController.getBindingFuture().thenRunAsync(() -> {
- call.maybeCleanupHandover();
- removeCall(call);
- Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
- if (mLocallyDisconnectingCalls.contains(call)) {
- boolean isDisconnectingChildCall = call.isDisconnectingChildCall();
- Log.v(this, "performRemoval: isDisconnectingChildCall = "
- + isDisconnectingChildCall + "call -> %s", call);
- mLocallyDisconnectingCalls.remove(call);
- // Auto-unhold the foreground call due to a locally disconnected call, except if the
- // call which was disconnected is a member of a conference (don't want to auto
- // un-hold the conference if we remove a member of the conference).
- if (!isDisconnectingChildCall && foregroundCall != null
- && foregroundCall.getState() == CallState.ON_HOLD) {
- foregroundCall.unhold();
- }
- } else if (foregroundCall != null &&
- !foregroundCall.can(Connection.CAPABILITY_SUPPORT_HOLD) &&
- foregroundCall.getState() == CallState.ON_HOLD) {
+ if (mInCallController.getBindingFuture() != null) {
+ mInCallController.getBindingFuture().thenRunAsync(() -> {
+ doRemoval(call);
+ }, new LoggedHandlerExecutor(mHandler, "CM.pR", mLock))
+ .exceptionally((throwable) -> {
+ Log.e(TAG, throwable, "Error while executing call removal");
+ mAnomalyReporter.reportAnomaly(CALL_REMOVAL_EXECUTION_ERROR_UUID,
+ CALL_REMOVAL_EXECUTION_ERROR_MSG);
+ return null;
+ });
+ } else {
+ doRemoval(call);
+ }
+ }
- // The new foreground call is on hold, however the carrier does not display the hold
- // button in the UI. Therefore, we need to auto unhold the held call since the user
- // has no means of unholding it themselves.
- Log.i(this, "performRemoval: Auto-unholding held foreground call (call doesn't "
- + "support hold)");
+ /**
+ * Code to perform removal of a call. Called above from {@link #performRemoval(Call)} either
+ * async (in live code) or sync (in testing).
+ * @param call the call to remove.
+ */
+ private void doRemoval(Call call) {
+ call.maybeCleanupHandover();
+ removeCall(call);
+ Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
+ if (mLocallyDisconnectingCalls.contains(call)) {
+ boolean isDisconnectingChildCall = call.isDisconnectingChildCall();
+ Log.v(this, "performRemoval: isDisconnectingChildCall = "
+ + isDisconnectingChildCall + "call -> %s", call);
+ mLocallyDisconnectingCalls.remove(call);
+ // Auto-unhold the foreground call due to a locally disconnected call, except if the
+ // call which was disconnected is a member of a conference (don't want to auto
+ // un-hold the conference if we remove a member of the conference).
+ // Also, ensure that the call we're removing is from the same ConnectionService as
+ // the one we're removing. We don't want to auto-unhold between ConnectionService
+ // implementations, especially if one is managed and the other is a VoIP CS.
+ if (!isDisconnectingChildCall && foregroundCall != null
+ && foregroundCall.getState() == CallState.ON_HOLD
+ && areFromSameSource(foregroundCall, call)) {
foregroundCall.unhold();
}
- }, new LoggedHandlerExecutor(mHandler, "CM.pR", mLock))
- .exceptionally((throwable) -> {
- Log.e(TAG, throwable, "Error while executing call removal");
- mAnomalyReporter.reportAnomaly(CALL_REMOVAL_EXECUTION_ERROR_UUID,
- CALL_REMOVAL_EXECUTION_ERROR_MSG);
- return null;
- });
+ } else if (foregroundCall != null &&
+ !foregroundCall.can(Connection.CAPABILITY_SUPPORT_HOLD) &&
+ foregroundCall.getState() == CallState.ON_HOLD) {
+
+ // The new foreground call is on hold, however the carrier does not display the hold
+ // button in the UI. Therefore, we need to auto unhold the held call since the user
+ // has no means of unholding it themselves.
+ Log.i(this, "performRemoval: Auto-unholding held foreground call (call doesn't "
+ + "support hold)");
+ foregroundCall.unhold();
+ }
}
/**
@@ -3930,7 +4020,11 @@
connectElapsedTime,
mClockProxy,
mToastFactory);
- notifyCallCreated(call);
+
+ // Unlike connections, conferences are not created first and then notified as create
+ // connection complete from the CS. They originate from the CS and are reported directly to
+ // telecom where they're added (see below).
+ call.setIsCreateConnectionComplete(true);
setCallState(call, Call.getStateFromConnectionState(parcelableConference.getState()),
"new conference call");
@@ -4327,37 +4421,6 @@
}
}
- private boolean isPotentialMMICode(Uri handle) {
- return (handle != null && handle.getSchemeSpecificPart() != null
- && handle.getSchemeSpecificPart().contains("#"));
- }
-
- /**
- * Determines if a dialed number is potentially an In-Call MMI code. In-Call MMI codes are
- * MMI codes which can be dialed when one or more calls are in progress.
- * <P>
- * Checks for numbers formatted similar to the MMI codes defined in:
- * {@link com.android.internal.telephony.Phone#handleInCallMmiCommands(String)}
- *
- * @param handle The URI to call.
- * @return {@code True} if the URI represents a number which could be an in-call MMI code.
- */
- private boolean isPotentialInCallMMICode(Uri handle) {
- if (handle != null && handle.getSchemeSpecificPart() != null &&
- handle.getScheme() != null &&
- handle.getScheme().equals(PhoneAccount.SCHEME_TEL)) {
-
- String dialedNumber = handle.getSchemeSpecificPart();
- return (dialedNumber.equals("0") ||
- (dialedNumber.startsWith("1") && dialedNumber.length() <= 2) ||
- (dialedNumber.startsWith("2") && dialedNumber.length() <= 2) ||
- dialedNumber.equals("3") ||
- dialedNumber.equals("4") ||
- dialedNumber.equals("5"));
- }
- return false;
- }
-
/**
* Determines if there are any ongoing self managed calls for the given package/user.
* @param packageName The package name to check.
@@ -4788,9 +4851,18 @@
return true;
}
- // If the live call is stuck in a connecting state, then we should disconnect it in favor
- // of the new outgoing call and prompt the user to generate a bugreport.
- if (liveCall.getState() == CallState.CONNECTING) {
+ // If the live call is stuck in a connecting state for longer than the transitory timeout,
+ // then we should disconnect it in favor of the new outgoing call and prompt the user to
+ // generate a bugreport.
+ // TODO: In the future we should let the CallAnomalyWatchDog do this disconnection of the
+ // live call stuck in the connecting state. Unfortunately that code will get tripped up by
+ // calls that have a longer than expected new outgoing call broadcast response time. This
+ // mitigation is intended to catch calls stuck in a CONNECTING state for a long time that
+ // block outgoing calls. However, if the user dials two calls in quick succession it will
+ // result in both calls getting disconnected, which is not optimal.
+ if (liveCall.getState() == CallState.CONNECTING
+ && ((mClockProxy.elapsedRealtime() - liveCall.getCreationElapsedRealtimeMillis())
+ > mTimeoutsAdapter.getNonVoipCallTransitoryStateTimeoutMillis())) {
mAnomalyReporter.reportAnomaly(LIVE_CALL_STUCK_CONNECTING_ERROR_UUID,
LIVE_CALL_STUCK_CONNECTING_ERROR_MSG);
liveCall.disconnect("Force disconnect CONNECTING call.");
@@ -4926,6 +4998,25 @@
}
/**
+ * Asynchronously updates the emergency call notification.
+ * @param context the context for the update.
+ */
+ private void updateEmergencyCallNotificationAsync(Context context) {
+ mAsyncTaskExecutor.execute(() -> {
+ Log.startSession("CM.UEMCNA");
+ try {
+ boolean shouldShow = mBlockedNumbersAdapter.shouldShowEmergencyCallNotification(
+ context);
+ Log.i(CallsManager.this, "updateEmergencyCallNotificationAsync; show=%b",
+ shouldShow);
+ mBlockedNumbersAdapter.updateEmergencyCallNotification(context, shouldShow);
+ } finally {
+ Log.endSession();
+ }
+ });
+ }
+
+ /**
* Creates a new call for an existing connection.
*
* @param callId The id of the new call.
@@ -4989,6 +5080,9 @@
call.setParentCall(parentCall);
}
}
+ // Existing connections originate from a connection service, so they are completed creation
+ // by the ConnectionService implicitly.
+ call.setIsCreateConnectionComplete(true);
addCall(call);
if (parentCall != null) {
// Now, set the call as a child of the parent since it has been added to Telecom. This
@@ -5300,7 +5394,7 @@
*
* @param pw The {@code IndentingPrintWriter} to write the state to.
*/
- public void dump(IndentingPrintWriter pw) {
+ public void dump(IndentingPrintWriter pw, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
if (mCalls != null) {
pw.println("mCalls: ");
@@ -5362,6 +5456,14 @@
mCallAnomalyWatchdog.dump(pw);
pw.decreaseIndent();
}
+
+ if (mEmergencyCallDiagnosticLogger != null) {
+ pw.println("mEmergencyCallDiagnosticLogger:");
+ pw.increaseIndent();
+ mEmergencyCallDiagnosticLogger.dump(pw, args);
+ pw.decreaseIndent();
+ }
+
if (mDefaultDialerCache != null) {
pw.println("mDefaultDialerCache:");
pw.increaseIndent();
@@ -5391,8 +5493,10 @@
* @param call The call.
*/
private void maybeShowErrorDialogOnDisconnect(Call call) {
- if (call.getState() == CallState.DISCONNECTED && (isPotentialMMICode(call.getHandle())
- || isPotentialInCallMMICode(call.getHandle())) && !mCalls.contains(call)) {
+ if (call.getState() == CallState.DISCONNECTED && (mMmiUtils.isPotentialMMICode(
+ call.getHandle())
+ || mMmiUtils.isPotentialInCallMMICode(call.getHandle())) && !mCalls.contains(
+ call)) {
DisconnectCause disconnectCause = call.getDisconnectCause();
if (!TextUtils.isEmpty(disconnectCause.getDescription()) && ((disconnectCause.getCode()
== DisconnectCause.ERROR) || (disconnectCause.getCode()
@@ -5468,6 +5572,9 @@
} else {
call.setConnectionService(service);
service.createConnectionFailed(call);
+ if (!mCalls.contains(call)){
+ mListeners.forEach(l -> l.onCreateConnectionFailed(call));
+ }
}
}
@@ -5490,16 +5597,19 @@
} else {
call.setConnectionService(service);
service.createConferenceFailed(call);
+ if (!mCalls.contains(call)){
+ mListeners.forEach(l -> l.onCreateConnectionFailed(call));
+ }
}
}
/**
- * Notify interested parties that a new call has been created, but not yet added to
- * CallsManager.
+ * Notify interested parties that a new call is about to be handed off to a ConnectionService to
+ * be created.
* @param theCall the new call.
*/
- private void notifyCallCreated(final Call theCall) {
- mListeners.forEach(l -> l.onCallCreated(theCall));
+ private void notifyStartCreateConnection(final Call theCall) {
+ mListeners.forEach(l -> l.onStartCreateConnection(theCall));
}
/**
@@ -5664,7 +5774,7 @@
// Disconnect all self-managed calls to make priority for emergency call.
disconnectSelfManagedCalls("emergency call");
}
-
+ notifyStartCreateConnection(call);
call.startCreateConnection(mPhoneAccountRegistrar);
}
@@ -5841,7 +5951,7 @@
extras.putBoolean(TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, true);
extras.putParcelable(TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT,
fromCall.getTargetPhoneAccount());
-
+ notifyStartCreateConnection(call);
call.startCreateConnection(mPhoneAccountRegistrar);
}
@@ -5961,34 +6071,53 @@
}
/**
- * Intended for ongoing or new calls that would like to go active/answered and need to
- * update the mConnectionSvrFocusMgr before setting the state
+ * This helper mainly requests mConnectionSvrFocusMgr to update the call focus via a
+ * {@link TransactionalFocusRequestCallback}. However, in the case of a held call, the
+ * state must be set first and then a request must be made.
+ *
+ * @param newCallFocus to set active/answered
+ * @param resultCallback that back propagates the focusManager result
+ *
+ * Note: This method should only be called if there are no active calls.
*/
- public void transactionRequestNewFocusCall(Call call, int newCallState,
- OutcomeReceiver<Boolean, CallException> callback) {
- Log.d(this, "transactionRequestNewFocusCall");
- PendingAction pendingAction = new ActionSetCallState(call, newCallState,
- "transactional ActionSetCallState");
+ public void requestNewCallFocusAndVerify(Call newCallFocus,
+ OutcomeReceiver<Boolean, CallException> resultCallback) {
+ int currentCallState = newCallFocus.getState();
+ PendingAction pendingAction = null;
+
+ // if the current call is in a state that can become the new call focus, we can set the
+ // state afterwards...
+ if (ConnectionServiceFocusManager.PRIORITY_FOCUS_CALL_STATE.contains(currentCallState)) {
+ pendingAction = new ActionSetCallState(newCallFocus, CallState.ACTIVE,
+ "vCFC: pending action set state");
+ } else {
+ // However, HELD calls need to be set to ACTIVE before requesting call focus.
+ setCallState(newCallFocus, CallState.ACTIVE, "vCFC: immediately set active");
+ }
+
mConnectionSvrFocusMgr
- .requestFocus(call,
- new TransactionalFocusRequestCallback(pendingAction, call, callback));
+ .requestFocus(newCallFocus,
+ new TransactionalFocusRequestCallback(pendingAction, currentCallState,
+ newCallFocus, resultCallback));
}
/**
* Request a new call focus and ensure the request was successful via an OutcomeReceiver. Also,
- * include a PendingAction that will execute if the call focus change is successful.
+ * conditionally include a PendingAction that will execute if and only if the call focus change
+ * is successful.
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public class TransactionalFocusRequestCallback implements
ConnectionServiceFocusManager.RequestFocusCallback {
private PendingAction mPendingAction;
- @NonNull
- private Call mTargetCallFocus;
+ private int mPreviousCallState;
+ @NonNull private Call mTargetCallFocus;
private OutcomeReceiver<Boolean, CallException> mCallback;
- TransactionalFocusRequestCallback(PendingAction pendingAction, @NonNull Call call,
- OutcomeReceiver<Boolean, CallException> callback) {
+ TransactionalFocusRequestCallback(PendingAction pendingAction, int previousState,
+ @NonNull Call call, OutcomeReceiver<Boolean, CallException> callback) {
mPendingAction = pendingAction;
+ mPreviousCallState = previousState;
mTargetCallFocus = call;
mCallback = callback;
}
@@ -6001,12 +6130,18 @@
mTargetCallFocus, currentCallFocus);
if (currentCallFocus == null ||
!currentCallFocus.getId().equals(mTargetCallFocus.getId())) {
+ // possibly reset the call state
+ if (mTargetCallFocus.getState() != mPreviousCallState) {
+ mTargetCallFocus.setState(mPreviousCallState, "resetting call state");
+ }
mCallback.onError(new CallException("failed to switch focus to requested call",
CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE));
return;
}
// at this point, we know the FocusManager is able to update successfully
- mPendingAction.performAction(); // set the call state
+ if (mPendingAction != null) {
+ mPendingAction.performAction(); // set the call state
+ }
mCallback.onResult(true); // complete the transaction
}
}
@@ -6174,6 +6309,10 @@
return mRinger;
}
+ @VisibleForTesting
+ public VoipCallMonitor getVoipCallMonitor() {
+ return mVoipCallMonitor;
+ }
/**
* This method should only be used for testing.
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index 6b8e2fe..43f3b90 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -35,6 +35,10 @@
}
@Override
+ public void onCreateConnectionFailed(Call call) {
+ }
+
+ @Override
public void onCallStateChanged(Call call, int oldState, int newState) {
}
diff --git a/src/com/android/server/telecom/ConnectionServiceFocusManager.java b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
index 8db98e9..6fbc494 100644
--- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java
+++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
@@ -32,6 +32,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -153,10 +154,9 @@
void setCallsManagerListener(CallsManager.CallsManagerListener listener);
}
- private static final int[] PRIORITY_FOCUS_CALL_STATE = new int[] {
- CallState.ACTIVE, CallState.CONNECTING, CallState.DIALING, CallState.AUDIO_PROCESSING,
- CallState.RINGING
- };
+ public static final Set<Integer> PRIORITY_FOCUS_CALL_STATE
+ = Set.of(CallState.ACTIVE, CallState.CONNECTING, CallState.DIALING,
+ CallState.AUDIO_PROCESSING, CallState.RINGING);
private static final int MSG_REQUEST_FOCUS = 1;
private static final int MSG_RELEASE_CONNECTION_FOCUS = 2;
@@ -374,17 +374,15 @@
&& call.isFocusable())
.collect(Collectors.toList());
- for (int i = 0; i < PRIORITY_FOCUS_CALL_STATE.length; i++) {
- for (CallFocus call : calls) {
- if (call.getState() == PRIORITY_FOCUS_CALL_STATE[i]) {
- mCurrentFocusCall = call;
- Log.d(this, "updateCurrentFocusCall %s", mCurrentFocusCall);
- return;
- }
+ for (CallFocus call : calls) {
+ if (PRIORITY_FOCUS_CALL_STATE.contains(call.getState())) {
+ mCurrentFocusCall = call;
+ Log.i(this, "updateCurrentFocusCall %s", mCurrentFocusCall);
+ return;
}
}
- Log.d(this, "updateCurrentFocusCall = null");
+ Log.i(this, "updateCurrentFocusCall = null");
}
private void onRequestFocusDone(FocusRequest focusRequest) {
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 874b77f..cc5932c 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -89,7 +89,6 @@
ConnectionServiceFocusManager.ConnectionServiceFocus {
private static final String TELECOM_ABBREVIATION = "cast";
-
private CompletableFuture<Pair<Integer, Location>> mQueryLocationFuture = null;
private @Nullable CancellationSignal mOngoingQueryLocationRequest = null;
private final ExecutorService mQueryLocationExecutor = Executors.newSingleThreadExecutor();
@@ -1432,7 +1431,11 @@
callback.send(0,
getQueryLocationErrorResult(QueryLocationException.ERROR_UNSPECIFIED));
}
+ //make sure we don't pass mock locations diretly, always reset() mock locations
if (result.second != null) {
+ if(result.second.isMock()) {
+ result.second.reset();
+ }
callback.send(1, getQueryLocationResult(result.second));
} else {
callback.send(0, getQueryLocationErrorResult(result.first));
@@ -1597,7 +1600,7 @@
public void onSuccess() {
String callId = mCallIdMapper.getCallId(call);
if (callId == null) {
- Log.w(ConnectionServiceWrapper.this, "Call not present"
+ Log.i(ConnectionServiceWrapper.this, "Call not present"
+ " in call id mapper, maybe it was aborted before the bind"
+ " completed successfully?");
response.handleCreateConnectionFailure(
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index 331c32b..6702f03 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -429,19 +429,28 @@
// Get user preferred PA if it exists.
PhoneAccount preferredPA = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
preferredPAH);
- // Next, add all SIM phone accounts which can place emergency calls.
- sortSimPhoneAccountsForEmergency(allAccounts, preferredPA);
- // and pick the first one that can place emergency calls.
- for (PhoneAccount phoneAccount : allAccounts) {
- if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
- && phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
- PhoneAccountHandle phoneAccountHandle = phoneAccount.getAccountHandle();
- Log.i(this, "Will try PSTN account %s for emergency", phoneAccountHandle);
- mAttemptRecords.add(new CallAttemptRecord(phoneAccountHandle,
- phoneAccountHandle));
- // Add only one emergency SIM PhoneAccount to the attempt list, telephony will
- // perform retries if the call fails.
- break;
+ if (mCall.isIncoming() && preferredPA != null) {
+ // The phone account for the incoming call should be used.
+ mAttemptRecords.add(new CallAttemptRecord(preferredPA.getAccountHandle(),
+ preferredPA.getAccountHandle()));
+ } else {
+ // Next, add all SIM phone accounts which can place emergency calls.
+ sortSimPhoneAccountsForEmergency(allAccounts, preferredPA);
+ Log.i(this, "The preferred PA is: %s", preferredPA);
+ // and pick the first one that can place emergency calls.
+ for (PhoneAccount phoneAccount : allAccounts) {
+ if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
+ && phoneAccount.hasCapabilities(
+ PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
+ PhoneAccountHandle phoneAccountHandle = phoneAccount.getAccountHandle();
+ Log.i(this, "Will try PSTN account %s for emergency",
+ phoneAccountHandle);
+ mAttemptRecords.add(new CallAttemptRecord(phoneAccountHandle,
+ phoneAccountHandle));
+ // Add only one emergency SIM PhoneAccount to the attempt list, telephony
+ // will perform retries if the call fails.
+ break;
+ }
}
}
diff --git a/src/com/android/server/telecom/DefaultDialerCache.java b/src/com/android/server/telecom/DefaultDialerCache.java
index a4a0242..3ce394e 100644
--- a/src/com/android/server/telecom/DefaultDialerCache.java
+++ b/src/com/android/server/telecom/DefaultDialerCache.java
@@ -265,7 +265,7 @@
if (packageName == null ||
Objects.equals(packageName, mCurrentDefaultDialerPerUser.get(userId))) {
String newDefaultDialer = refreshCacheForUser(userId);
- Log.i(LOG_TAG, "Refreshing default dialer for user %d: now %s",
+ Log.v(LOG_TAG, "Refreshing default dialer for user %d: now %s",
userId, newDefaultDialer);
}
}
diff --git a/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java b/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java
new file mode 100644
index 0000000..af79da3
--- /dev/null
+++ b/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom;
+
+import static android.telephony.TelephonyManager.EmergencyCallDiagnosticParams;
+
+import android.os.BugreportManager;
+import android.os.DropBoxManager;
+import android.provider.DeviceConfig;
+import android.telecom.DisconnectCause;
+import android.telecom.Log;
+import android.telephony.TelephonyManager;
+import android.util.LocalLog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+
+/**
+ * The EmergencyCallDiagnosticsLogger monitors information required to diagnose potential outgoing
+ * ecall failures on the device. When a potential failure is detected, it calls a Telephony API to
+ * persist relevant information (dumpsys, logcat etc.) to the dropbox. This acts as a central place
+ * to determine when and what to collect.
+ *
+ * <p>When a bugreport is triggered, this module will read the dropbox entries and add them to the
+ * telecom dump.
+ */
+public class EmergencyCallDiagnosticLogger extends CallsManagerListenerBase
+ implements Call.Listener {
+
+ public static final int REPORT_REASON_RANGE_START = -1; //!!DO NOT CHANGE
+ public static final int REPORT_REASON_RANGE_END = 5; //increment this and add new reason above
+ public static final int COLLECTION_TYPE_BUGREPORT = 10;
+ public static final int COLLECTION_TYPE_TELECOM_STATE = 11;
+ public static final int COLLECTION_TYPE_TELEPHONY_STATE = 12;
+ public static final int COLLECTION_TYPE_LOGCAT_BUFFERS = 13;
+ private static final int REPORT_REASON_STUCK_CALL_DETECTED = 0;
+ private static final int REPORT_REASON_INACTIVE_CALL_TERMINATED_BY_USER_AFTER_DELAY = 1;
+ private static final int REPORT_REASON_CALL_FAILED = 2;
+ private static final int REPORT_REASON_CALL_CREATED_BUT_NEVER_ADDED = 3;
+ private static final int REPORT_REASON_SHORT_DURATION_AFTER_GOING_ACTIVE = 4;
+ private static final String DROPBOX_TAG = "ecall_diagnostic_data";
+ private static final String ENABLE_BUGREPORT_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS =
+ "enable_bugreport_collection_for_emergency_call_diagnostics";
+ private static final String ENABLE_TELECOM_DUMP_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS =
+ "enable_telecom_dump_collection_for_emergency_call_diagnostics";
+
+ private static final String ENABLE_LOGCAT_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS =
+ "enable_logcat_collection_for_emergency_call_diagnostics";
+ private static final String ENABLE_TELEPHONY_DUMP_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS =
+ "enable_telephony_dump_collection_for_emergency_call_diagnostics";
+
+ private static final String DUMPSYS_ARG_FOR_DIAGNOSTICS = "EmergencyDiagnostics";
+
+ // max text size to read from dropbox entry
+ private static final int DEFAULT_MAX_READ_BYTES_PER_DROP_BOX_ENTRY = 500000;
+ private static final String MAX_BYTES_PER_DROP_BOX_ENTRY = "max_bytes_per_dropbox_entry";
+ private static final int MAX_DROPBOX_ENTRIES_TO_DUMP = 6;
+
+ private final Timeouts.Adapter mTimeoutAdapter;
+ // This map holds all calls, but keeps pruning non-emergency calls when we can determine it
+ private final Map<Call, CallEventTimestamps> mEmergencyCallsMap = new ConcurrentHashMap<>(2);
+ private final DropBoxManager mDropBoxManager;
+ private final LocalLog mLocalLog = new LocalLog(10);
+ private final TelephonyManager mTelephonyManager;
+ private final BugreportManager mBugreportManager;
+ private final Executor mAsyncTaskExecutor;
+ private final ClockProxy mClockProxy;
+
+ public EmergencyCallDiagnosticLogger(
+ TelephonyManager tm,
+ BugreportManager brm,
+ Timeouts.Adapter timeoutAdapter, DropBoxManager dropBoxManager,
+ Executor asyncTaskExecutor, ClockProxy clockProxy) {
+ mTimeoutAdapter = timeoutAdapter;
+ mDropBoxManager = dropBoxManager;
+ mTelephonyManager = tm;
+ mBugreportManager = brm;
+ mAsyncTaskExecutor = asyncTaskExecutor;
+ mClockProxy = clockProxy;
+ }
+
+ // this calculates time from ACTIVE --> removed
+ private static long getCallTimeInActiveStateSec(CallEventTimestamps ts) {
+ if (ts.getCallActiveTime() == 0 || ts.getCallRemovedTime() == 0) {
+ return 0;
+ } else {
+ return (ts.getCallRemovedTime() - ts.getCallActiveTime()) / 1000;
+ }
+ }
+
+ // this calculates time from call created --> removed
+ private static long getTotalCallTimeSec(CallEventTimestamps ts) {
+ if (ts.getCallRemovedTime() == 0 || ts.getCallCreatedTime() == 0) {
+ return 0;
+ } else {
+ return (ts.getCallRemovedTime() - ts.getCallCreatedTime()) / 1000;
+ }
+ }
+
+ //determines what to collect based on fail reason
+ //if COLLECTION_TYPE_BUGREPORT is present in the returned list, then that
+ //should be the only collection type in the list
+ @VisibleForTesting
+ public static List<Integer> getDataCollectionTypes(int reason) {
+ switch (reason) {
+ case REPORT_REASON_SHORT_DURATION_AFTER_GOING_ACTIVE:
+ return Arrays.asList(COLLECTION_TYPE_TELECOM_STATE);
+ case REPORT_REASON_CALL_CREATED_BUT_NEVER_ADDED:
+ return Arrays.asList(
+ COLLECTION_TYPE_TELECOM_STATE, COLLECTION_TYPE_TELEPHONY_STATE);
+ case REPORT_REASON_CALL_FAILED:
+ case REPORT_REASON_INACTIVE_CALL_TERMINATED_BY_USER_AFTER_DELAY:
+ case REPORT_REASON_STUCK_CALL_DETECTED:
+ return Arrays.asList(
+ COLLECTION_TYPE_TELECOM_STATE,
+ COLLECTION_TYPE_TELEPHONY_STATE,
+ COLLECTION_TYPE_LOGCAT_BUFFERS);
+ default:
+ }
+ return new ArrayList<>();
+ }
+
+ private int getMaxBytesPerDropboxEntry() {
+ return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TELEPHONY,
+ MAX_BYTES_PER_DROP_BOX_ENTRY, DEFAULT_MAX_READ_BYTES_PER_DROP_BOX_ENTRY);
+ }
+
+ @VisibleForTesting
+ public Map<Call, CallEventTimestamps> getEmergencyCallsMap() {
+ return mEmergencyCallsMap;
+ }
+
+ private void triggerDiagnosticsCollection(Call call, int reason) {
+ Log.i(this, "Triggering diagnostics for call %s reason: %d", call.getId(), reason);
+ List<Integer> dataCollectionTypes = getDataCollectionTypes(reason);
+ boolean invokeTelephonyPersistApi = false;
+ CallEventTimestamps ts = mEmergencyCallsMap.get(call);
+ EmergencyCallDiagnosticParams dp =
+ new EmergencyCallDiagnosticParams();
+ for (Integer dataCollectionType : dataCollectionTypes) {
+ switch (dataCollectionType) {
+ case COLLECTION_TYPE_TELECOM_STATE:
+ if (isTelecomDumpCollectionEnabled()) {
+ dp.setTelecomDumpSysCollection(true);
+ invokeTelephonyPersistApi = true;
+ }
+ break;
+ case COLLECTION_TYPE_TELEPHONY_STATE:
+ if (isTelephonyDumpCollectionEnabled()) {
+ dp.setTelephonyDumpSysCollection(true);
+ invokeTelephonyPersistApi = true;
+ }
+ break;
+ case COLLECTION_TYPE_LOGCAT_BUFFERS:
+ if (isLogcatCollectionEnabled()) {
+ dp.setLogcatCollection(true, ts.getCallCreatedTime());
+ invokeTelephonyPersistApi = true;
+ }
+ break;
+ case COLLECTION_TYPE_BUGREPORT:
+ if (isBugreportCollectionEnabled()) {
+ mAsyncTaskExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ persistBugreport();
+ }
+ });
+ }
+ break;
+ default:
+ }
+ }
+ if (invokeTelephonyPersistApi) {
+ mAsyncTaskExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ Log.i(this, "Requesting Telephony to persist data %s", dp.toString());
+ try {
+ mTelephonyManager.persistEmergencyCallDiagnosticData(DROPBOX_TAG, dp);
+ } catch (Exception e) {
+ Log.w(this,
+ "Exception while invoking "
+ + "Telephony#persistEmergencyCallDiagnosticData %s",
+ e.toString());
+ }
+ }
+ });
+ }
+ }
+
+ private boolean isBugreportCollectionEnabled() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_TELEPHONY,
+ ENABLE_BUGREPORT_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS,
+ false);
+ }
+
+ private boolean isTelecomDumpCollectionEnabled() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_TELEPHONY,
+ ENABLE_TELECOM_DUMP_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS,
+ true);
+ }
+
+ private boolean isLogcatCollectionEnabled() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_TELEPHONY,
+ ENABLE_LOGCAT_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS,
+ true);
+ }
+
+ private boolean isTelephonyDumpCollectionEnabled() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_TELEPHONY,
+ ENABLE_TELEPHONY_DUMP_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS,
+ true);
+ }
+
+ private void persistBugreport() {
+ if (isBugreportCollectionEnabled()) {
+ // TODO:
+ }
+ }
+
+ private boolean shouldTrackCall(Call call) {
+ return (call != null && call.isEmergencyCall() && call.isOutgoing());
+ }
+
+ public void reportStuckCall(Call call) {
+ if (shouldTrackCall(call)) {
+ Log.i(this, "Triggering diagnostics for stuck call %s", call.getId());
+ triggerDiagnosticsCollection(call, REPORT_REASON_STUCK_CALL_DETECTED);
+ call.removeListener(this);
+ mEmergencyCallsMap.remove(call);
+ }
+ }
+
+ @Override
+ public void onStartCreateConnection(Call call) {
+ if (shouldTrackCall(call)) {
+ long currentTime = mClockProxy.currentTimeMillis();
+ call.addListener(this);
+ Log.i(this, "Tracking call %s timestamp: %d", call.getId(), currentTime);
+ mEmergencyCallsMap.put(call, new CallEventTimestamps(currentTime));
+ }
+ }
+
+ @Override
+ public void onCreateConnectionFailed(Call call) {
+ if (shouldTrackCall(call)) {
+ Log.i(this, "Triggering diagnostics for call %s that was never added", call.getId());
+ triggerDiagnosticsCollection(call, REPORT_REASON_CALL_CREATED_BUT_NEVER_ADDED);
+ call.removeListener(this);
+ mEmergencyCallsMap.remove(call);
+ }
+ }
+
+ /**
+ * Override of {@link CallsManagerListenerBase} to track when calls are removed
+ *
+ * @param call the call
+ */
+ @Override
+ public void onCallRemoved(Call call) {
+ if (call != null && (mEmergencyCallsMap.get(call) != null)) {
+ call.removeListener(this);
+
+ CallEventTimestamps ts = mEmergencyCallsMap.get(call);
+ long currentTime = mClockProxy.currentTimeMillis();
+ ts.setCallRemovedTime(currentTime);
+
+ maybeTriggerDiagnosticsCollection(call, ts);
+ mEmergencyCallsMap.remove(call);
+ }
+ }
+
+ // !NOTE!: this method should only be called after we get onCallRemoved
+ private void maybeTriggerDiagnosticsCollection(Call removedCall, CallEventTimestamps ts) {
+ Log.i(this, "Evaluating emergency call for diagnostic logging: %s", removedCall.getId());
+ boolean wentActive = (ts.getCallActiveTime() != 0);
+ long callActiveTimeSec = (wentActive ? getCallTimeInActiveStateSec(ts) : 0);
+ long timeSinceCallCreatedSec = getTotalCallTimeSec(ts);
+ int dc = removedCall.getDisconnectCause().getCode();
+
+ if (wentActive) {
+ if (callActiveTimeSec
+ < mTimeoutAdapter.getEmergencyCallActiveTimeThresholdMillis() / 1000) {
+ // call connected but did not go on for long
+ triggerDiagnosticsCollection(
+ removedCall, REPORT_REASON_SHORT_DURATION_AFTER_GOING_ACTIVE);
+ }
+ } else {
+
+ if (dc == DisconnectCause.LOCAL
+ && timeSinceCallCreatedSec
+ > mTimeoutAdapter.getEmergencyCallTimeBeforeUserDisconnectThresholdMillis()
+ / 1000) {
+ // call was disconnected by the user (but not immediately)
+ triggerDiagnosticsCollection(
+ removedCall, REPORT_REASON_INACTIVE_CALL_TERMINATED_BY_USER_AFTER_DELAY);
+ } else if (dc != DisconnectCause.LOCAL) {
+ // this can be a case for a full bugreport
+ triggerDiagnosticsCollection(removedCall, REPORT_REASON_CALL_FAILED);
+ }
+ }
+ }
+
+ /**
+ * Override of {@link com.android.server.telecom.CallsManager.CallsManagerListener} to track
+ * call state changes.
+ *
+ * @param call the call
+ * @param oldState its old state
+ * @param newState the new state
+ */
+ @Override
+ public void onCallStateChanged(Call call, int oldState, int newState) {
+
+ if (call != null && mEmergencyCallsMap.get(call) != null && newState == CallState.ACTIVE) {
+ CallEventTimestamps ts = mEmergencyCallsMap.get(call);
+ if (ts != null) {
+ long currentTime = mClockProxy.currentTimeMillis();
+ ts.setCallActiveTime(currentTime);
+ }
+ }
+ }
+
+ private void dumpDiagnosticDataFromDropbox(IndentingPrintWriter pw) {
+ pw.increaseIndent();
+ pw.println("PERSISTED DIAGNOSTIC DATA FROM DROP BOX");
+ int totalEntriesDumped = 0;
+ long currentTime = mClockProxy.currentTimeMillis();
+ long entriesAfterTime =
+ currentTime - (mTimeoutAdapter.getDaysBackToSearchEmergencyDiagnosticEntries() * 24
+ * 60L * 60L * 1000L);
+ Log.i(this, "current time: %d entriesafter: %d", currentTime, entriesAfterTime);
+ DropBoxManager.Entry entry;
+ entry = mDropBoxManager.getNextEntry(DROPBOX_TAG, entriesAfterTime);
+ while (entry != null) {
+ Log.i(this, "found entry with ts: %d", entry.getTimeMillis());
+ String content[] = entry.getText(getMaxBytesPerDropboxEntry()).split(
+ System.lineSeparator());
+ long entryTime = entry.getTimeMillis();
+ if (content != null) {
+ pw.increaseIndent();
+ pw.println("------------BEGIN ENTRY (" + entryTime + ")--------");
+ for (String line : content) {
+ pw.println(line);
+ }
+ pw.println("--------END ENTRY--------");
+ pw.decreaseIndent();
+ totalEntriesDumped++;
+ }
+ entry = mDropBoxManager.getNextEntry(DROPBOX_TAG, entryTime);
+ if (totalEntriesDumped > MAX_DROPBOX_ENTRIES_TO_DUMP) {
+ /*
+ Since Emergency calls are a rare/once in a lifetime time occurrence for most users,
+ we should not be seeing too many entries. This code just guards against edge case
+ like load testing, b2b failures etc. We may accumulate a lot of dropbox entries in
+ such cases, but we limit to dumping only MAX_DROPBOX_ENTRIES_TO_DUMP in the
+ bugreport
+
+ The Dropbox API in its current state does not allow to query Entries in reverse
+ chronological order efficiently.
+ */
+
+ Log.i(this, "Skipping dump for remaining entries. dumped :%d", totalEntriesDumped);
+ break;
+ }
+ }
+ pw.println("END OF PERSISTED DIAGNOSTIC DATA FROM DROP BOX");
+ pw.decreaseIndent();
+ }
+
+ public void dump(IndentingPrintWriter pw, String[] args) {
+ pw.increaseIndent();
+ mLocalLog.dump(pw);
+ pw.decreaseIndent();
+ if (args != null && args.length > 0 && args[0].equals(DUMPSYS_ARG_FOR_DIAGNOSTICS)) {
+ //dont read dropbox entries since this dump is triggered by telephony for diagnostics
+ Log.i(this, "skipped dumping diagnostic data");
+ return;
+ }
+ dumpDiagnosticDataFromDropbox(pw);
+ }
+
+ private static class CallEventTimestamps {
+
+ private final long mCallCreatedTime;
+ private long mCallActiveTime;
+ private long mCallRemovedTime;
+
+ public CallEventTimestamps(long createdTime) {
+ mCallCreatedTime = createdTime;
+ }
+
+ public long getCallActiveTime() {
+ return mCallActiveTime;
+ }
+
+ public void setCallActiveTime(long callActiveTime) {
+ this.mCallActiveTime = callActiveTime;
+ }
+
+ public long getCallCreatedTime() {
+ return mCallCreatedTime;
+ }
+
+ public long getCallRemovedTime() {
+ return mCallRemovedTime;
+ }
+
+ public void setCallRemovedTime(long callRemovedTime) {
+ this.mCallRemovedTime = callRemovedTime;
+ }
+ }
+}
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index f5214ed..9ce10bd 100755
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -695,7 +695,7 @@
@Override
public void sendRttRequest(String callId) {
try {
- Log.startSession("ICA.sRR");
+ Log.startSession("ICA.sRR", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -717,7 +717,7 @@
@Override
public void respondToRttRequest(String callId, int id, boolean accept) {
try {
- Log.startSession("ICA.rTRR");
+ Log.startSession("ICA.rTRR", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -739,7 +739,7 @@
@Override
public void stopRtt(String callId) {
try {
- Log.startSession("ICA.sRTT");
+ Log.startSession("ICA.sRTT", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -761,11 +761,16 @@
@Override
public void setRttMode(String callId, int mode) {
try {
- Log.startSession("ICA.sRM");
+ Log.startSession("ICA.sRM", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- // TODO
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setRttMode(mode);
+ } else {
+ Log.w(this, "setRttMode(): call %s not found", callId);
+ }
}
} finally {
Binder.restoreCallingIdentity(token);
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 33ddf0a..3215605 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -98,14 +98,6 @@
UUID.fromString("0c2adf96-353a-433c-afe9-1e5564f304f9");
public static final String SET_IN_CALL_ADAPTER_ERROR_MSG =
"Exception thrown while setting the in-call adapter.";
- public static final UUID BIND_TO_IN_CALL_ERROR_UUID =
- UUID.fromString("1261231d-b16a-4e0c-a322-623f8bb8e599");
- public static final String BIND_TO_IN_CALL_ERROR_MSG =
- "Failed to connect when attempting to bind to InCall.";
- public static final UUID BIND_TO_IN_CALL_EMERGENCY_ERROR_UUID =
- UUID.fromString("9ec8f1f0-3f0b-4079-9e9f-325f1262a8c7");
- public static final String BIND_TO_IN_CALL_EMERGENCY_ERROR_MSG =
- "Outgoing emergency call failed to connect when attempting to bind to InCall.";
@VisibleForTesting
public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter){
@@ -344,18 +336,15 @@
mIsConnected = true;
mInCallServiceInfo.setBindingStartTime(mClockProxy.elapsedRealtime());
UserHandle userToBind = getUserFromCall(call);
+ boolean isManagedProfile = UserUtil.isManagedProfile(mContext, userToBind);
+ // Note that UserHandle.CURRENT fails to capture the work profile, so we need to handle
+ // it separately to ensure that the ICS is bound to the appropriate user.
+ userToBind = isManagedProfile ? userToBind : UserHandle.CURRENT;
if (!mContext.bindServiceAsUser(intent, mServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
| Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
| Context.BIND_SCHEDULE_LIKE_TOP_APP, userToBind)) {
Log.w(this, "Failed to connect.");
- if (call != null && call.isEmergencyCall()) {
- mAnomalyReporter.reportAnomaly(BIND_TO_IN_CALL_EMERGENCY_ERROR_UUID,
- BIND_TO_IN_CALL_EMERGENCY_ERROR_MSG);
- } else {
- mAnomalyReporter.reportAnomaly(BIND_TO_IN_CALL_ERROR_UUID,
- BIND_TO_IN_CALL_ERROR_MSG);
- }
mIsConnected = false;
}
@@ -816,6 +805,9 @@
}
Call callToConnectWith = mCallIdMapper.getCalls().iterator().next();
for (InCallServiceBindingConnection newConnection : newConnections) {
+ // Ensure we track the new sub-connection so that when we later disconnect we will
+ // be able to disconnect it.
+ mSubConnections.add(newConnection);
newConnection.connect(callToConnectWith);
}
}
@@ -2204,7 +2196,8 @@
* Adds the call to the list of calls tracked by the {@link InCallController}.
* @param call The call to add.
*/
- private void addCall(Call call) {
+ @VisibleForTesting
+ public void addCall(Call call) {
if (mCallIdMapper.getCalls().size() == 0) {
mAppOpsManager.startWatchingActive(new String[] { OPSTR_RECORD_AUDIO },
java.lang.Runnable::run, this);
@@ -2600,8 +2593,17 @@
private UserHandle getUserFromCall(Call call) {
// Call may never be specified, so we can fall back to using the CallManager current user.
- return call == null
- ? mCallsManager.getCurrentUserHandle()
- : call.getUserHandleFromTargetPhoneAccount();
+ if (call == null) {
+ return mCallsManager.getCurrentUserHandle();
+ } else {
+ UserHandle userFromCall = call.getUserHandleFromTargetPhoneAccount();
+ UserManager userManager = mContext.getSystemService(UserManager.class);
+ // Emergency call should never be blocked, so if the user associated with call is in
+ // quite mode, use the primary user for the emergency call.
+ if (call.isEmergencyCall() && userManager.isQuietModeEnabled(userFromCall)) {
+ return mCallsManager.getCurrentUserHandle();
+ }
+ return userFromCall;
+ }
}
}
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 0957eb4..3cc4aac 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -170,7 +170,7 @@
private static final int RELATIVE_VOLUME_EMERGENCY = 100;
private static final int RELATIVE_VOLUME_HIPRI = 80;
- private static final int RELATIVE_VOLUME_LOPRI = 50;
+ private static final int RELATIVE_VOLUME_LOPRI = 30;
private static final int RELATIVE_VOLUME_UNDEFINED = -1;
// Buffer time (in msec) to add on to the tone timeout value. Needed mainly when the timeout
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index 35f8d1e..8ce5dc3 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -105,10 +105,17 @@
public static final String SET_RINGING = "SET_RINGING";
public static final String SET_ANSWERED = "SET_ANSWERED";
public static final String SET_DISCONNECTED = "SET_DISCONNECTED";
+ public static final String SKIP_CALL_LOG = "SKIP_CALL_LOG";
+ public static final String LOG_CALL = "LOG_CALL";
public static final String SET_DISCONNECTING = "SET_DISCONNECTING";
public static final String SET_SELECT_PHONE_ACCOUNT = "SET_SELECT_PHONE_ACCOUNT";
public static final String SET_AUDIO_PROCESSING = "SET_AUDIO_PROCESSING";
public static final String SET_SIMULATED_RINGING = "SET_SIMULATED_RINGING";
+ public static final String REQUEST_RTT = "REQUEST_RTT";
+ public static final String RESPOND_TO_RTT_REQUEST = "RESPOND_TO_RTT_REQUEST";
+ public static final String SET_RRT_MODE = "SET_RTT_MODE";
+ public static final String ON_RTT_FAILED = "ON_RTT_FAILED";
+ public static final String ON_RTT_REQUEST = "ON_RTT_REQUEST";
public static final String REQUEST_HOLD = "REQUEST_HOLD";
public static final String REQUEST_UNHOLD = "REQUEST_UNHOLD";
public static final String REQUEST_DISCONNECT = "REQUEST_DISCONNECT";
@@ -216,6 +223,8 @@
public static final String ICS_EXTRAS_CHANGED = "ICS_EXTRAS_CHANGED";
public static final String FLASH_NOTIFICATION_START = "FLASH_NOTIFICATION_START";
public static final String FLASH_NOTIFICATION_STOP = "FLASH_NOTIFICATION_STOP";
+ public static final String GAINED_FGS_DELEGATION = "GAINED_FGS_DELEGATION";
+ public static final String LOST_FGS_DELEGATION = "LOST_FGS_DELEGATION";
public static class Timings {
public static final String ACCEPT_TIMING = "accept";
diff --git a/src/com/android/server/telecom/MmiUtils.java b/src/com/android/server/telecom/MmiUtils.java
new file mode 100644
index 0000000..11f6d59
--- /dev/null
+++ b/src/com/android/server/telecom/MmiUtils.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom;
+
+import android.net.Uri;
+import android.telecom.PhoneAccount;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class MmiUtils {
+ // See TS 22.030 6.5.2 "Structure of the MMI"
+
+ private static Pattern sPatternSuppService = Pattern.compile(
+ "((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)(.*)");
+ /* 1 2 3 4 5 6 7 8 9 10 11
+ 12
+
+ 1 = Full string up to and including #
+ 2 = action (activation/interrogation/registration/erasure)
+ 3 = service code
+ 5 = SIA
+ 7 = SIB
+ 9 = SIC
+ 10 = dialing number
+ */
+ //regex groups
+ static final int MATCH_GROUP_POUND_STRING = 1;
+ static final int MATCH_GROUP_ACTION = 2; //(activation/interrogation/registration/erasure)
+ static final int MATCH_GROUP_SERVICE_CODE = 3;
+ static final int MATCH_GROUP_SIA = 5;
+ static final int MATCH_GROUP_SIB = 7;
+ static final int MATCH_GROUP_SIC = 9;
+ static final int MATCH_GROUP_PWD_CONFIRM = 11;
+ static final int MATCH_GROUP_DIALING_NUMBER = 12;
+ // Call Forwarding service codes
+ static final String SC_CFU = "21";
+ static final String SC_CFB = "67";
+ static final String SC_CFNRy = "61";
+ static final String SC_CFNR = "62";
+ static final String SC_CF_All = "002";
+ static final String SC_CF_All_Conditional = "004";
+
+ //see: https://nationalnanpa.com/number_resource_info/vsc_assignments.html
+ @SuppressWarnings("DoubleBraceInitialization")
+ private static Set<String> sDangerousVerticalServiceCodes = new HashSet<String>()
+ {{
+ add("*09"); //Selective Call Blocking/Reporting
+ add("*42"); //Change Forward-To Number for Cust Programmable Call Forwarding Don't Answer
+ add("*56"); //Change Forward-To Number for ISDN Call Forwarding
+ add("*60"); //Selective Call Rejection Activation
+ add("*63"); //Selective Call Forwarding Activation
+ add("*64"); //Selective Call Acceptance Activation
+ add("*68"); //Call Forwarding Busy Line/Don't Answer Activation
+ add("*72"); //Call Forwarding Activation
+ add("*77"); //Anonymous Call Rejection Activation
+ add("*78"); //Do Not Disturb Activation
+ }};
+ private final int mMinLenInDangerousSet;
+ private final int mMaxLenInDangerousSet;
+
+ public MmiUtils() {
+ mMinLenInDangerousSet = sDangerousVerticalServiceCodes.stream()
+ .mapToInt(String::length)
+ .min()
+ .getAsInt();
+ mMaxLenInDangerousSet = sDangerousVerticalServiceCodes.stream()
+ .mapToInt(String::length)
+ .max()
+ .getAsInt();
+ }
+
+ /**
+ * Determines if the Uri represents a call forwarding related mmi code
+ *
+ * @param handle The URI to call.
+ * @return {@code True} if the URI represents a call forwarding related MMI
+ */
+ private static boolean isCallForwardingMmiCode(Uri handle) {
+ Matcher m;
+ String dialString = handle.getSchemeSpecificPart();
+ m = sPatternSuppService.matcher(dialString);
+
+ if (m.matches()) {
+ String sc = m.group(MATCH_GROUP_SERVICE_CODE);
+ return sc != null &&
+ (sc.equals(SC_CFU)
+ || sc.equals(SC_CFB) || sc.equals(SC_CFNRy)
+ || sc.equals(SC_CFNR) || sc.equals(SC_CF_All)
+ || sc.equals(SC_CF_All_Conditional));
+ }
+
+ return false;
+
+ }
+
+ private static boolean isTelScheme(Uri handle) {
+ return (handle != null && handle.getSchemeSpecificPart() != null &&
+ handle.getScheme() != null &&
+ handle.getScheme().equals(PhoneAccount.SCHEME_TEL));
+ }
+
+ private boolean isDangerousVerticalServiceCode(Uri handle) {
+ if (isTelScheme(handle)) {
+ String dialedNumber = handle.getSchemeSpecificPart();
+ if (dialedNumber.length() >= mMinLenInDangerousSet && dialedNumber.charAt(0) == '*') {
+ //we only check vertical codes defined by The North American Numbering Plan Admin
+ //see: https://nationalnanpa.com/number_resource_info/vsc_assignments.html
+ //only two or 3-digit codes are valid as of today, but the code is generic enough.
+ for (int prefixLen = mMaxLenInDangerousSet; prefixLen <= mMaxLenInDangerousSet;
+ prefixLen++) {
+ String prefix = dialedNumber.substring(0, prefixLen);
+ if (sDangerousVerticalServiceCodes.contains(prefix)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Determines if a dialed number is potentially an In-Call MMI code. In-Call MMI codes are
+ * MMI codes which can be dialed when one or more calls are in progress.
+ * <P>
+ * Checks for numbers formatted similar to the MMI codes defined in:
+ * {@link com.android.internal.telephony.Phone#handleInCallMmiCommands(String)}
+ *
+ * @param handle The URI to call.
+ * @return {@code True} if the URI represents a number which could be an in-call MMI code.
+ */
+ public boolean isPotentialInCallMMICode(Uri handle) {
+ if (isTelScheme(handle)) {
+ String dialedNumber = handle.getSchemeSpecificPart();
+ return (dialedNumber.equals("0") ||
+ (dialedNumber.startsWith("1") && dialedNumber.length() <= 2) ||
+ (dialedNumber.startsWith("2") && dialedNumber.length() <= 2) ||
+ dialedNumber.equals("3") ||
+ dialedNumber.equals("4") ||
+ dialedNumber.equals("5"));
+ }
+ return false;
+ }
+
+ public boolean isPotentialMMICode(Uri handle) {
+ return (handle != null && handle.getSchemeSpecificPart() != null
+ && handle.getSchemeSpecificPart().contains("#"));
+ }
+
+ /**
+ * Determines if the Uri represents a dangerous MMI code or Vertical Service code. Dangerous
+ * codes are ones, for which,
+ * we normally expect the user to be aware that an application has dialed them
+ *
+ * @param handle The URI to call.
+ * @return {@code True} if the URI represents a dangerous code
+ */
+ public boolean isDangerousMmiOrVerticalCode(Uri handle) {
+ if (isPotentialMMICode(handle)) {
+ return isCallForwardingMmiCode(handle);
+ //since some dangerous mmi codes could be carrier specific, in the future,
+ //we can add a carrier config item which can list carrier specific dangerous mmi codes
+ } else if (isDangerousVerticalServiceCode(handle)) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index 41aa2fb..8426d1f 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -16,14 +16,12 @@
package com.android.server.telecom;
-import android.app.AppOpsManager;
-
import android.app.Activity;
+import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.Trace;
@@ -78,6 +76,7 @@
private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
private final TelecomSystem.SyncRoot mLock;
private final DefaultDialerCache mDefaultDialerCache;
+ private final MmiUtils mMmiUtils;
/*
* Whether or not the outgoing call intent originated from the default phone application. If
@@ -101,7 +100,7 @@
@VisibleForTesting
public NewOutgoingCallIntentBroadcaster(Context context, CallsManager callsManager,
Intent intent, PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
- boolean isDefaultPhoneApp, DefaultDialerCache defaultDialerCache) {
+ boolean isDefaultPhoneApp, DefaultDialerCache defaultDialerCache, MmiUtils mmiUtils) {
mContext = context;
mCallsManager = callsManager;
mIntent = intent;
@@ -109,6 +108,7 @@
mIsDefaultOrSystemPhoneApp = isDefaultPhoneApp;
mLock = mCallsManager.getLock();
mDefaultDialerCache = defaultDialerCache;
+ mMmiUtils = mmiUtils;
}
/**
@@ -291,6 +291,16 @@
result.callImmediately = true;
result.requestRedirection = false;
}
+ } else if (mMmiUtils.isDangerousMmiOrVerticalCode(intent.getData())) {
+ if (!mIsDefaultOrSystemPhoneApp) {
+ Log.w(this,
+ "Potentially dangerous MMI code %s with CALL Intent %s can only be "
+ + "sent if caller is the system or default dialer",
+ number, intent);
+ launchSystemDialer(intent.getData());
+ result.disconnectCause = DisconnectCause.OUTGOING_CANCELED;
+ return result;
+ }
}
} else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) {
if (!isEmergencyNumber) {
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index a7824b4..f5a3450 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -873,13 +873,17 @@
public void registerPhoneAccount(PhoneAccount account) {
// Enforce the requirement that a connection service for a phone account has the correct
// permission.
- if (!hasTransactionalCallCapabilites(account) &&
+ if (!hasTransactionalCallCapabilities(account) &&
!phoneAccountRequiresBindPermission(account.getAccountHandle())) {
Log.w(this,
"Phone account %s does not have BIND_TELECOM_CONNECTION_SERVICE permission.",
account.getAccountHandle());
- throw new SecurityException("PhoneAccount connection service requires "
- + "BIND_TELECOM_CONNECTION_SERVICE permission.");
+ throw new SecurityException("Registering a PhoneAccount requires either: "
+ + "(1) The Service definition requires that the ConnectionService is guarded"
+ + " with the BIND_TELECOM_CONNECTION_SERVICE, which can be defined using the"
+ + " android:permission tag as part of the Service definition. "
+ + "(2) The PhoneAccount capability called"
+ + " CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS.");
}
enforceCharacterLimit(account);
enforceIconSizeLimit(account);
@@ -962,18 +966,20 @@
String[] fields =
{"Package Name", "Class Name", "PhoneAccountHandle Id", "Label", "ShortDescription",
- "GroupId", "Address"};
+ "GroupId", "Address", "SubscriptionAddress"};
CharSequence[] args = {handle.getComponentName().getPackageName(),
handle.getComponentName().getClassName(), handle.getId(), account.getLabel(),
account.getShortDescription(), account.getGroupId(),
- (account.getAddress() != null ? account.getAddress().toString() : "")};
+ (account.getAddress() != null ? account.getAddress().toString() : ""),
+ (account.getSubscriptionAddress() != null ?
+ account.getSubscriptionAddress().toString() : "")};
for (int i = 0; i < fields.length; i++) {
if (args[i] != null && args[i].length() > MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT) {
EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
"enforceCharacterLimit");
- throw new IllegalArgumentException("The PhoneAccount or PhoneAccountHandle"
- + fields[i] + " field has an invalid character count. PhoneAccount and "
+ throw new IllegalArgumentException("The PhoneAccount or PhoneAccountHandle ["
+ + fields[i] + "] field has an invalid character count. PhoneAccount and "
+ "PhoneAccountHandle String and Char-Sequence fields are limited to "
+ MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT + " characters.");
}
@@ -1060,7 +1066,7 @@
boolean isNewAccount;
// add self-managed capability for transactional accounts that are missing it
- if (hasTransactionalCallCapabilites(account) &&
+ if (hasTransactionalCallCapabilities(account) &&
!account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
account = account.toBuilder()
.setCapabilities(account.getCapabilities()
@@ -1371,10 +1377,6 @@
return false;
}
- if (hasTransactionalCallCapabilites(getPhoneAccountUnchecked(phoneAccountHandle))) {
- return false;
- }
-
for (ResolveInfo resolveInfo : resolveInfos) {
ServiceInfo serviceInfo = resolveInfo.serviceInfo;
if (serviceInfo == null) {
@@ -1394,7 +1396,7 @@
}
@VisibleForTesting
- public boolean hasTransactionalCallCapabilites(PhoneAccount phoneAccount) {
+ public boolean hasTransactionalCallCapabilities(PhoneAccount phoneAccount) {
if (phoneAccount == null) {
return false;
}
@@ -1528,7 +1530,10 @@
}
PhoneAccountHandle handle = m.getAccountHandle();
- if (resolveComponent(handle).isEmpty()) {
+ // PhoneAccounts with CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS do not require a
+ // ConnectionService and will fail [resolveComponent(PhoneAccountHandle)]. Bypass
+ // the [resolveComponent(PhoneAccountHandle)] for transactional accounts.
+ if (!hasTransactionalCallCapabilities(m) && resolveComponent(handle).isEmpty()) {
// This component cannot be resolved anymore; skip this one.
continue;
}
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index c04cf6b..cdacab0 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -33,6 +33,8 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
@@ -49,6 +51,8 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
/**
* Controls the ringtone player.
@@ -172,7 +176,7 @@
/**
* Call objects that are ringing, vibrating or call-waiting. These are used only for logging
- * purposes.
+ * purposes (except mVibratingCall is also used to ensure consistency).
*/
private Call mRingingCall;
private Call mVibratingCall;
@@ -244,195 +248,257 @@
}
public boolean startRinging(Call foregroundCall, boolean isHfpDeviceAttached) {
- if (foregroundCall == null) {
- Log.wtf(this, "startRinging called with null foreground call.");
- return false;
- }
-
- if (foregroundCall.getState() != CallState.RINGING
- && foregroundCall.getState() != CallState.SIMULATED_RINGING) {
- // Its possible for bluetooth to connect JUST as a call goes active, which would mean
- // the call would start ringing again.
- Log.i(this, "startRinging called for non-ringing foreground callid=%s",
- foregroundCall.getId());
- return false;
- }
-
- // Use completable future to establish a timeout, not intent to make these work outside the
- // main thread asynchronously
- // TODO: moving these RingerAttributes calculation out of Telecom lock to avoid blocking.
- CompletableFuture<RingerAttributes> ringerAttributesFuture = CompletableFuture
- .supplyAsync(() -> getRingerAttributes(foregroundCall, isHfpDeviceAttached),
- new LoggedHandlerExecutor(getHandler(), "R.sR", null));
-
- RingerAttributes attributes = null;
+ boolean deferBlockOnRingingFuture = false;
+ // try-finally to ensure that the block on ringing future is always called.
try {
- mAttributesLatch = new CountDownLatch(1);
- attributes = ringerAttributesFuture.get(
- RINGER_ATTRIBUTES_TIMEOUT, TimeUnit.MILLISECONDS);
- } catch (ExecutionException | InterruptedException | TimeoutException e) {
- // Keep attributes as null
- Log.i(this, "getAttributes error: " + e);
- }
-
- if (attributes == null) {
- Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "RingerAttributes error");
- return false;
- }
-
- if (attributes.isEndEarly()) {
- boolean acquireAudioFocus = attributes.shouldAcquireAudioFocus();
- if (attributes.letDialerHandleRinging()) {
- Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Dialer handles");
- // Dialer will setup a ringtone, provide the audio focus if its audible.
- acquireAudioFocus |= attributes.isRingerAudible();
+ if (foregroundCall == null) {
+ Log.wtf(this, "startRinging called with null foreground call.");
+ return false;
}
- if (attributes.isSilentRingingRequested()) {
- Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Silent ringing "
- + "requested");
+ if (foregroundCall.getState() != CallState.RINGING
+ && foregroundCall.getState() != CallState.SIMULATED_RINGING) {
+ // It's possible for bluetooth to connect JUST as a call goes active, which would
+ // mean the call would start ringing again.
+ Log.i(this, "startRinging called for non-ringing foreground callid=%s",
+ foregroundCall.getId());
+ return false;
}
- if (mBlockOnRingingFuture != null) {
- mBlockOnRingingFuture.complete(null);
+
+ // Use completable future to establish a timeout, not intent to make these work outside
+ // the main thread asynchronously
+ // TODO: moving these RingerAttributes calculation out of Telecom lock to avoid blocking
+ CompletableFuture<RingerAttributes> ringerAttributesFuture = CompletableFuture
+ .supplyAsync(() -> getRingerAttributes(foregroundCall, isHfpDeviceAttached),
+ new LoggedHandlerExecutor(getHandler(), "R.sR", null));
+
+ RingerAttributes attributes = null;
+ try {
+ mAttributesLatch = new CountDownLatch(1);
+ attributes = ringerAttributesFuture.get(
+ RINGER_ATTRIBUTES_TIMEOUT, TimeUnit.MILLISECONDS);
+ } catch (ExecutionException | InterruptedException | TimeoutException e) {
+ // Keep attributes as null
+ Log.i(this, "getAttributes error: " + e);
}
- return acquireAudioFocus;
- }
- stopCallWaiting();
+ if (attributes == null) {
+ Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING,
+ "RingerAttributes error");
+ return false;
+ }
- final boolean shouldFlash = attributes.shouldRingForContact();
- if (mAccessibilityManagerAdapter != null && shouldFlash) {
- Log.addEvent(foregroundCall, LogUtils.Events.FLASH_NOTIFICATION_START);
- getHandler().post(() ->
- mAccessibilityManagerAdapter.startFlashNotificationSequence(mContext,
- AccessibilityManager.FLASH_REASON_CALL));
- }
+ if (attributes.isEndEarly()) {
+ boolean acquireAudioFocus = attributes.shouldAcquireAudioFocus();
+ if (attributes.letDialerHandleRinging()) {
+ Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Dialer handles");
+ // Dialer will setup a ringtone, provide the audio focus if its audible.
+ acquireAudioFocus |= attributes.isRingerAudible();
+ }
- // Determine if the settings and DND mode indicate that the vibrator can be used right now.
- final boolean isVibratorEnabled =
- isVibratorEnabled(mContext, attributes.shouldRingForContact());
- boolean shouldApplyRampingRinger =
- isVibratorEnabled && mSystemSettingsUtil.isRampingRingerEnabled(mContext);
+ if (attributes.isSilentRingingRequested()) {
+ Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Silent ringing "
+ + "requested");
+ }
+ if (attributes.isWorkProfileInQuietMode()) {
+ Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING,
+ "Work profile in quiet mode");
+ }
+ return acquireAudioFocus;
+ }
- boolean isHapticOnly = false;
- boolean useCustomVibrationEffect = false;
+ stopCallWaiting();
- mVolumeShaperConfig = null;
+ final boolean shouldFlash = attributes.shouldRingForContact();
+ if (mAccessibilityManagerAdapter != null && shouldFlash) {
+ Log.addEvent(foregroundCall, LogUtils.Events.FLASH_NOTIFICATION_START);
+ getHandler().post(() ->
+ mAccessibilityManagerAdapter.startFlashNotificationSequence(mContext,
+ AccessibilityManager.FLASH_REASON_CALL));
+ }
- if (attributes.isRingerAudible()) {
- mRingingCall = foregroundCall;
- Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER);
- // Because we wait until a contact info query to complete before processing a
- // call (for the purposes of direct-to-voicemail), the information about custom
- // ringtones should be available by the time this code executes. We can safely
- // request the custom ringtone from the call and expect it to be current.
- if (shouldApplyRampingRinger) {
- Log.i(this, "create ramping ringer.");
- float silencePoint = (float) (RAMPING_RINGER_VIBRATION_DURATION)
- / (float) (RAMPING_RINGER_VIBRATION_DURATION + RAMPING_RINGER_DURATION);
- mVolumeShaperConfig =
- new VolumeShaper.Configuration.Builder()
- .setDuration(RAMPING_RINGER_VIBRATION_DURATION + RAMPING_RINGER_DURATION)
- .setCurve(
- new float[] {0.f, silencePoint + EPSILON /*keep monotonicity*/, 1.f},
- new float[] {0.f, 0.f, 1.f})
- .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
- .build();
- if (mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()) {
- useCustomVibrationEffect = true;
+ // Determine if the settings and DND mode indicate that the vibrator can be used right
+ // now.
+ final boolean isVibratorEnabled =
+ isVibratorEnabled(mContext, attributes.shouldRingForContact());
+ boolean shouldApplyRampingRinger =
+ isVibratorEnabled && mSystemSettingsUtil.isRampingRingerEnabled(mContext);
+
+ boolean isHapticOnly = false;
+ boolean useCustomVibrationEffect = false;
+
+ mVolumeShaperConfig = null;
+
+ if (attributes.isRingerAudible()) {
+ mRingingCall = foregroundCall;
+ Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER);
+ // Because we wait until a contact info query to complete before processing a
+ // call (for the purposes of direct-to-voicemail), the information about custom
+ // ringtones should be available by the time this code executes. We can safely
+ // request the custom ringtone from the call and expect it to be current.
+ if (shouldApplyRampingRinger) {
+ Log.i(this, "create ramping ringer.");
+ float silencePoint = (float) (RAMPING_RINGER_VIBRATION_DURATION)
+ / (float) (RAMPING_RINGER_VIBRATION_DURATION + RAMPING_RINGER_DURATION);
+ mVolumeShaperConfig =
+ new VolumeShaper.Configuration.Builder()
+ .setDuration(RAMPING_RINGER_VIBRATION_DURATION
+ + RAMPING_RINGER_DURATION)
+ .setCurve(
+ new float[]{0.f, silencePoint + EPSILON
+ /*keep monotonicity*/, 1.f},
+ new float[]{0.f, 0.f, 1.f})
+ .setInterpolatorType(
+ VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
+ .build();
+ if (mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()) {
+ useCustomVibrationEffect = true;
+ }
+ } else {
+ if (DEBUG_RINGER) {
+ Log.i(this, "Create ringer with custom vibration effect");
+ }
+ // Ramping ringtone is not enabled.
+ useCustomVibrationEffect = true;
}
} else {
- if (DEBUG_RINGER) {
- Log.i(this, "Create ringer with custom vibration effect");
- }
- // Ramping ringtone is not enabled.
- useCustomVibrationEffect = true;
- }
- } else {
- Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING,
- "Inaudible: " + attributes.getInaudibleReason()
- + " isVibratorEnabled=" + isVibratorEnabled);
+ Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING,
+ "Inaudible: " + attributes.getInaudibleReason()
+ + " isVibratorEnabled=" + isVibratorEnabled);
- if (isVibratorEnabled) {
- // If ringer is not audible for this call, then the phone is in "Vibrate" mode.
- // Use haptic-only ringtone or do not play anything.
- isHapticOnly = true;
- if (DEBUG_RINGER) {
- Log.i(this, "Set ringtone as haptic only: " + isHapticOnly);
+ if (isVibratorEnabled) {
+ // If ringer is not audible for this call, then the phone is in "Vibrate" mode.
+ // Use haptic-only ringtone or do not play anything.
+ isHapticOnly = true;
+ if (DEBUG_RINGER) {
+ Log.i(this, "Set ringtone as haptic only: " + isHapticOnly);
+ }
+ } else {
+ foregroundCall.setUserMissed(USER_MISSED_NO_VIBRATE);
+ return attributes.shouldAcquireAudioFocus(); // ringer not audible
+ }
+ }
+
+ boolean hapticChannelsMuted = !isVibratorEnabled || !mIsHapticPlaybackSupportedByDevice;
+ if (shouldApplyRampingRinger
+ && !mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()
+ && isVibratorEnabled) {
+ Log.i(this, "Muted haptic channels since audio coupled ramping ringer is disabled");
+ hapticChannelsMuted = true;
+ } else if (hapticChannelsMuted) {
+ Log.i(this,
+ "Muted haptic channels isVibratorEnabled=%s, hapticPlaybackSupported=%s",
+ isVibratorEnabled, mIsHapticPlaybackSupportedByDevice);
+ }
+ // Defer ringtone creation to the async player thread.
+ Supplier<Ringtone> ringtoneSupplier;
+ final boolean finalHapticChannelsMuted = hapticChannelsMuted;
+ if (isHapticOnly) {
+ if (hapticChannelsMuted) {
+ Log.i(this,
+ "want haptic only ringtone but haptics are muted, skip ringtone play");
+ ringtoneSupplier = null;
+ } else {
+ ringtoneSupplier = mRingtoneFactory::getHapticOnlyRingtone;
}
} else {
- if (mBlockOnRingingFuture != null) {
- mBlockOnRingingFuture.complete(null);
- }
+ ringtoneSupplier = () -> mRingtoneFactory.getRingtone(
+ foregroundCall, mVolumeShaperConfig, finalHapticChannelsMuted);
+ }
+
+ // If vibration will be done, reserve the vibrator.
+ boolean vibratorReserved = isVibratorEnabled && attributes.shouldRingForContact()
+ && tryReserveVibration(foregroundCall);
+ if (!vibratorReserved) {
foregroundCall.setUserMissed(USER_MISSED_NO_VIBRATE);
- return attributes.shouldAcquireAudioFocus(); // ringer not audible
+ Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION,
+ "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, "
+ + "isVibratorEnabled=%b",
+ mVibrator.hasVibrator(),
+ mSystemSettingsUtil.isRingVibrationEnabled(mContext),
+ mAudioManager.getRingerMode(), isVibratorEnabled);
}
- }
- boolean hapticChannelsMuted = !isVibratorEnabled || !mIsHapticPlaybackSupportedByDevice;
- if (shouldApplyRampingRinger
- && !mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()
- && isVibratorEnabled) {
- Log.i(this, "Muted haptic channels since audio coupled ramping ringer is disabled");
- hapticChannelsMuted = true;
- } else if (hapticChannelsMuted) {
- Log.i(this, "Muted haptic channels isVibratorEnabled=%s, hapticPlaybackSupported=%s",
- isVibratorEnabled, mIsHapticPlaybackSupportedByDevice);
- }
- Ringtone ringtone;
- if (isHapticOnly) {
- if (hapticChannelsMuted) {
- Log.i(this, "want haptic only ringtone but haptics are muted, skip ringtone play");
- ringtone = null;
+ // The vibration logic depends on the loaded ringtone, but we need to defer the ringtone
+ // load to the async ringtone thread. Hence, we bundle up the final part of this method
+ // for that thread to run after loading the ringtone. This logic is intended to run even
+ // if the loaded ringtone is null. However if a stop event arrives before the ringtone
+ // creation finishes, then this consumer can be skipped.
+ final boolean finalUseCustomVibrationEffect = useCustomVibrationEffect;
+ BiConsumer<Ringtone, Boolean> afterRingtoneLogic =
+ (Ringtone ringtone, Boolean stopped) -> {
+ try {
+ if (stopped.booleanValue() || !vibratorReserved) {
+ // don't start vibration if the ringing is already abandoned, or the
+ // vibrator wasn't reserved. This still triggers the mBlockOnRingingFuture.
+ return;
+ }
+ final VibrationEffect vibrationEffect;
+ if (ringtone != null && finalUseCustomVibrationEffect) {
+ if (DEBUG_RINGER) {
+ Log.d(this, "Using ringtone defined vibration effect.");
+ }
+ vibrationEffect = getVibrationEffectForRingtone(ringtone);
+ } else {
+ vibrationEffect = mDefaultVibrationEffect;
+ }
+
+ boolean isUsingAudioCoupledHaptics =
+ !finalHapticChannelsMuted && ringtone != null
+ && ringtone.hasHapticChannels();
+ vibrateIfNeeded(isUsingAudioCoupledHaptics, foregroundCall, vibrationEffect);
+ } finally {
+ // This is used to signal to tests that the async play() call has completed.
+ if (mBlockOnRingingFuture != null) {
+ mBlockOnRingingFuture.complete(null);
+ }
+ }
+ };
+ deferBlockOnRingingFuture = true; // Run in vibrationLogic.
+ if (ringtoneSupplier != null) {
+ mRingtonePlayer.play(ringtoneSupplier, afterRingtoneLogic);
} else {
- ringtone = mRingtoneFactory.getHapticOnlyRingtone();
+ afterRingtoneLogic.accept(/* ringtone= */ null, /* stopped= */ false);
}
- } else {
- ringtone = mRingtoneFactory.getRingtone(
- foregroundCall, mVolumeShaperConfig, hapticChannelsMuted);
- }
- final VibrationEffect vibrationEffect;
- if (useCustomVibrationEffect) {
- if (DEBUG_RINGER) {
- Log.d(this, "Using ringtone defined vibration effect.");
- }
- vibrationEffect = getVibrationEffectForRingtone(ringtone);
- } else {
- vibrationEffect = mDefaultVibrationEffect;
- }
-
- if (ringtone == null) {
- Log.w(this, "No ringtone was found bail out from playing.");
- // No ringtone was found, try as a last resort to check if vibration can be performed.
- vibrateIfNeeded(/* isUsingAudioCoupledHaptics */ false, attributes, foregroundCall,
- vibrationEffect, isVibratorEnabled);
- if (mBlockOnRingingFuture != null) {
+ // shouldAcquireAudioFocus is meant to be true, but that check is deferred to here
+ // because until now is when we actually know if the ringtone loading worked.
+ return attributes.shouldAcquireAudioFocus()
+ || (!isHapticOnly && attributes.isRingerAudible());
+ } finally {
+ // This is used to signal to tests that the async play() call has completed. It can
+ // be deferred into AsyncRingtonePlayer
+ if (mBlockOnRingingFuture != null && !deferBlockOnRingingFuture) {
mBlockOnRingingFuture.complete(null);
}
- return attributes.shouldAcquireAudioFocus();
}
-
- boolean isUsingAudioCoupledHaptics = !hapticChannelsMuted && ringtone.hasHapticChannels();
-
- // There is a ringtone and we want to play it.
- mRingtonePlayer.play(ringtone);
-
- vibrateIfNeeded(isUsingAudioCoupledHaptics, attributes, foregroundCall, vibrationEffect,
- isVibratorEnabled);
- if (mBlockOnRingingFuture != null) {
- mBlockOnRingingFuture.complete(null);
- }
-
- // shouldAcquireAudioFocus is meant to be true, but that check is deferred to here
- // because until now is when we actually know if the ringtone loading worked.
- return attributes.shouldAcquireAudioFocus() || attributes.isRingerAudible();
}
- private void vibrateIfNeeded(boolean isUsingAudioCoupledHaptics, RingerAttributes attributes,
- Call foregroundCall, VibrationEffect effect, boolean isVibratorEnabled) {
- final boolean shouldRingForContact = attributes.shouldRingForContact();
+ /**
+ * Try to reserve the vibrator for this call, returning false if it's already committed.
+ * The vibration will be started by AsyncRingtonePlayer to ensure timing is aligned with the
+ * audio. The logic uses mVibratingCall to say which call is currently getting ready to vibrate,
+ * or actually vibrating (indicated by mIsVibrating).
+ *
+ * Once reserved, the vibrateIfNeeded method is expected to be called. Note that if
+ * audio-coupled haptics were used instead of vibrator, the reservation still stays until
+ * ringing is stopped, because the vibrator is exclusive to a single vibration source.
+ *
+ * Note that this "reservation" is only local to the Ringer - it's not locking the vibrator, so
+ * if it's busy with some other important vibration, this ringer's one may not displace it.
+ */
+ private boolean tryReserveVibration(Call foregroundCall) {
+ synchronized (mLock) {
+ if (mVibratingCall != null || mIsVibrating) {
+ return false;
+ }
+ mVibratingCall = foregroundCall;
+ return true;
+ }
+ }
+ private void vibrateIfNeeded(boolean isUsingAudioCoupledHaptics, Call foregroundCall,
+ VibrationEffect effect) {
if (isUsingAudioCoupledHaptics) {
Log.addEvent(
foregroundCall, LogUtils.Events.SKIP_VIBRATION, "using audio-coupled haptics");
@@ -440,48 +506,40 @@
}
synchronized (mLock) {
- if (isVibratorEnabled && !mIsVibrating && shouldRingForContact) {
+ // Ensure the reservation is live. The mIsVibrating check should be redundant.
+ if (foregroundCall == mVibratingCall && !mIsVibrating) {
Log.addEvent(foregroundCall, LogUtils.Events.START_VIBRATOR,
"hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
mVibrator.hasVibrator(), mSystemSettingsUtil.isRingVibrationEnabled(mContext),
mAudioManager.getRingerMode(), mIsVibrating);
- mVibratingCall = foregroundCall;
mIsVibrating = true;
mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
Log.i(this, "start vibration.");
- } else {
- foregroundCall.setUserMissed(USER_MISSED_NO_VIBRATE);
- Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION,
- "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
- mVibrator.hasVibrator(), mSystemSettingsUtil.isRingVibrationEnabled(mContext),
- mAudioManager.getRingerMode(), mIsVibrating);
}
+ // else stopped already: this isn't started unless a reservation was made.
}
}
- private VibrationEffect getVibrationEffectForRingtone(Ringtone ringtone) {
- VibrationEffect effect = null;
- Uri ringtoneUri = ringtone != null ? ringtone.getUri() : null;
- if (ringtoneUri != null) {
- try {
- effect = mVibrationEffectProxy.get(ringtoneUri, mContext);
- if (effect == null) {
- Log.i(this, "did not find vibration effect, falling back to default vibration");
- }
- } catch (IllegalArgumentException iae) {
- // Deep in the bowels of the VibrationEffect class it is possible for an
- // IllegalArgumentException to be thrown if there is an invalid URI specified in the
- // device config, or a content provider failure. Rather than crashing the Telecom
- // process we will just use the default vibration effect.
- Log.e(this, iae, "getVibrationEffectForRingtone: failed to get vibration effect");
- effect = null;
+ private VibrationEffect getVibrationEffectForRingtone(@NonNull Ringtone ringtone) {
+ Uri ringtoneUri = ringtone.getUri();
+ if (ringtoneUri == null) {
+ return mDefaultVibrationEffect;
+ }
+ try {
+ VibrationEffect effect = mVibrationEffectProxy.get(ringtoneUri, mContext);
+ if (effect == null) {
+ Log.i(this, "did not find vibration effect, falling back to default vibration");
+ return mDefaultVibrationEffect;
}
+ return effect;
+ } catch (IllegalArgumentException iae) {
+ // Deep in the bowels of the VibrationEffect class it is possible for an
+ // IllegalArgumentException to be thrown if there is an invalid URI specified in the
+ // device config, or a content provider failure. Rather than crashing the Telecom
+ // process we will just use the default vibration effect.
+ Log.e(this, iae, "getVibrationEffectForRingtone: failed to get vibration effect");
+ return mDefaultVibrationEffect;
}
-
- if (effect == null) {
- effect = mDefaultVibrationEffect;
- }
- return effect;
}
public void startCallWaiting(Call call) {
@@ -537,8 +595,8 @@
Log.addEvent(mVibratingCall, LogUtils.Events.STOP_VIBRATOR);
mVibrator.cancel();
mIsVibrating = false;
- mVibratingCall = null;
}
+ mVibratingCall = null; // Prevents vibrations from starting via AsyncRingtonePlayer.
}
}
@@ -640,16 +698,20 @@
boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging(
call.getUserHandleFromTargetPhoneAccount());
timer.record("letDialerHandleRinging");
+ boolean isWorkProfileInQuietMode =
+ isProfileInQuietMode(call.getUserHandleFromTargetPhoneAccount());
+ timer.record("isWorkProfileInQuietMode");
Log.i(this, "startRinging timings: " + timer);
boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged ||
- hasExternalRinger || isSilentRingingRequested;
+ hasExternalRinger || isSilentRingingRequested || isWorkProfileInQuietMode;
if (endEarly) {
Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " +
- "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s",
+ "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s, " +
+ "isWorkProfileInQuietMode=%s",
isTheaterModeOn, letDialerHandleRinging, isSelfManaged, hasExternalRinger,
- isSilentRingingRequested);
+ isSilentRingingRequested, isWorkProfileInQuietMode);
}
// Acquire audio focus under any of the following conditions:
@@ -657,8 +719,8 @@
// 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone
// present. (This check is deferred until ringer knows the ringtone)
// 3. The call is self-managed.
- boolean shouldAcquireAudioFocus =
- (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged;
+ boolean shouldAcquireAudioFocus = !isWorkProfileInQuietMode &&
+ ((isHfpDeviceAttached && shouldRingForContact) || isSelfManaged);
// Set missed reason according to attributes
if (!isVolumeOverZero) {
@@ -676,9 +738,15 @@
.setInaudibleReason(inaudibleReason)
.setShouldRingForContact(shouldRingForContact)
.setSilentRingingRequested(isSilentRingingRequested)
+ .setWorkProfileQuietMode(isWorkProfileInQuietMode)
.build();
}
+ private boolean isProfileInQuietMode(UserHandle user) {
+ UserManager um = mContext.getSystemService(UserManager.class);
+ return um.isManagedProfile(user.getIdentifier()) && um.isQuietModeEnabled(user);
+ }
+
private Handler getHandler() {
if (mHandler == null) {
HandlerThread handlerThread = new HandlerThread("Ringer");
diff --git a/src/com/android/server/telecom/RingerAttributes.java b/src/com/android/server/telecom/RingerAttributes.java
index 840d815..e0d3e1c 100644
--- a/src/com/android/server/telecom/RingerAttributes.java
+++ b/src/com/android/server/telecom/RingerAttributes.java
@@ -25,6 +25,7 @@
private String mInaudibleReason;
private boolean mShouldRingForContact;
private boolean mSilentRingingRequested;
+ private boolean mWorkProfileQuietMode;
public RingerAttributes.Builder setEndEarly(boolean endEarly) {
mEndEarly = endEarly;
@@ -61,10 +62,15 @@
return this;
}
+ public RingerAttributes.Builder setWorkProfileQuietMode(boolean workProfileQuietMode) {
+ mWorkProfileQuietMode = workProfileQuietMode;
+ return this;
+ }
+
public RingerAttributes build() {
return new RingerAttributes(mEndEarly, mLetDialerHandleRinging, mAcquireAudioFocus,
mRingerAudible, mInaudibleReason, mShouldRingForContact,
- mSilentRingingRequested);
+ mSilentRingingRequested, mWorkProfileQuietMode);
}
}
@@ -75,10 +81,12 @@
private String mInaudibleReason;
private boolean mShouldRingForContact;
private boolean mSilentRingingRequested;
+ private boolean mWorkProfileQuietMode;
private RingerAttributes(boolean endEarly, boolean letDialerHandleRinging,
boolean acquireAudioFocus, boolean ringerAudible, String inaudibleReason,
- boolean shouldRingForContact, boolean silentRingingRequested) {
+ boolean shouldRingForContact, boolean silentRingingRequested,
+ boolean workProfileQuietMode) {
mEndEarly = endEarly;
mLetDialerHandleRinging = letDialerHandleRinging;
mAcquireAudioFocus = acquireAudioFocus;
@@ -86,6 +94,7 @@
mInaudibleReason = inaudibleReason;
mShouldRingForContact = shouldRingForContact;
mSilentRingingRequested = silentRingingRequested;
+ mWorkProfileQuietMode = workProfileQuietMode;
}
public boolean isEndEarly() {
@@ -115,4 +124,8 @@
public boolean isSilentRingingRequested() {
return mSilentRingingRequested;
}
+
+ public boolean isWorkProfileInQuietMode() {
+ return mWorkProfileQuietMode;
+ }
}
diff --git a/src/com/android/server/telecom/RingtoneFactory.java b/src/com/android/server/telecom/RingtoneFactory.java
index 68c95a6..309c86e 100644
--- a/src/com/android/server/telecom/RingtoneFactory.java
+++ b/src/com/android/server/telecom/RingtoneFactory.java
@@ -66,12 +66,16 @@
public Ringtone getRingtone(Call incomingCall,
@Nullable VolumeShaper.Configuration volumeShaperConfig, boolean hapticChannelsMuted) {
+ // Initializing ringtones on the main thread can deadlock
+ ThreadUtil.checkNotOnMainThread();
+
AudioAttributes audioAttrs = getDefaultRingtoneAudioAttributes(hapticChannelsMuted);
// Use the default ringtone of the work profile if the contact is a work profile contact.
+ // or the default ringtone of the receiving user.
Context userContext = isWorkContact(incomingCall) ?
getWorkProfileContextForUser(mCallsManager.getCurrentUserHandle()) :
- getContextForUserHandle(mCallsManager.getCurrentUserHandle());
+ getContextForUserHandle(incomingCall.getUserHandleFromTargetPhoneAccount());
Uri ringtoneUri = incomingCall.getRingtone();
Ringtone ringtone = null;
@@ -116,7 +120,7 @@
return ringtone;
}
- public AudioAttributes getDefaultRingtoneAudioAttributes(boolean hapticChannelsMuted) {
+ private AudioAttributes getDefaultRingtoneAudioAttributes(boolean hapticChannelsMuted) {
return new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
@@ -127,6 +131,8 @@
/** Returns a ringtone to be used when ringer is not audible for the incoming call. */
@Nullable
public Ringtone getHapticOnlyRingtone() {
+ // Initializing ringtones on the main thread can deadlock
+ ThreadUtil.checkNotOnMainThread();
Uri ringtoneUri = Uri.parse("file://" + mContext.getString(
com.android.internal.R.string.config_defaultRingtoneVibrationSound));
AudioAttributes audioAttrs = getDefaultRingtoneAudioAttributes(
diff --git a/src/com/android/server/telecom/StatusBarNotifier.java b/src/com/android/server/telecom/StatusBarNotifier.java
index d1de958..772335e 100644
--- a/src/com/android/server/telecom/StatusBarNotifier.java
+++ b/src/com/android/server/telecom/StatusBarNotifier.java
@@ -79,13 +79,15 @@
mIsShowingMute = isMuted;
}
+ /**
+ * Update the status bar manager with the new speakerphone state.
+ *
+ * IMPORTANT: DO NOT call into any Telecom code here; this is usually scheduled on an async
+ * executor to save Telecom from blocking on outgoing binder calls.
+ * @param isSpeakerphone
+ */
@VisibleForTesting
public void notifySpeakerphone(boolean isSpeakerphone) {
- // Never display anything if there are no calls.
- if (!mCallsManager.hasAnyCalls()) {
- isSpeakerphone = false;
- }
-
if (mIsShowingSpeakerphone == isSpeakerphone) {
return;
}
diff --git a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
index e1f2d08..0be90e0 100644
--- a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
+++ b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
@@ -16,10 +16,12 @@
package com.android.server.telecom;
+import android.app.BroadcastOptions;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.Bundle;
import android.os.UserHandle;
import android.telecom.Log;
import android.widget.Toast;
@@ -247,8 +249,14 @@
* Closes open system dialogs and the notification shade.
*/
private void closeSystemDialogs(Context context) {
- Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- context.sendBroadcastAsUser(intent, UserHandle.ALL);
+ Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ Bundle options = BroadcastOptions.makeBasic()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
+ .toBundle();
+ context.sendBroadcastAsUser(intent, UserHandle.ALL, null /* receiverPermission */,
+ options);
}
private void sendSmsIntent(Intent intent, UserHandle userHandle) {
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 6826290..99a8d3d 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.CALL_PHONE;
import static android.Manifest.permission.CALL_PRIVILEGED;
import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.MANAGE_OWN_CALLS;
import static android.Manifest.permission.MODIFY_PHONE_STATE;
import static android.Manifest.permission.READ_PHONE_NUMBERS;
import static android.Manifest.permission.READ_PHONE_STATE;
@@ -26,11 +27,10 @@
import static android.Manifest.permission.READ_SMS;
import static android.Manifest.permission.REGISTER_SIM_SUBSCRIPTION;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
-import static android.Manifest.permission.MANAGE_OWN_CALLS;
import static android.telecom.CallAttributes.DIRECTION_INCOMING;
import static android.telecom.CallAttributes.DIRECTION_OUTGOING;
-import static android.telecom.TelecomManager.TELECOM_TRANSACTION_SUCCESS;
import static android.telecom.CallException.CODE_ERROR_UNKNOWN;
+import static android.telecom.TelecomManager.TELECOM_TRANSACTION_SUCCESS;
import android.Manifest;
import android.app.ActivityManager;
@@ -47,6 +47,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -58,7 +59,6 @@
import android.provider.BlockedNumberContract;
import android.provider.Settings;
import android.telecom.CallAttributes;
-
import android.telecom.CallException;
import android.telecom.Log;
import android.telecom.PhoneAccount;
@@ -90,6 +90,7 @@
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.UUID;
@@ -193,16 +194,21 @@
enforcePhoneAccountIsRegisteredEnabled(handle, handle.getUserHandle());
enforceCallingPackage(callingPackage, "addCall");
+ // add extras about info used for FGS delegation
+ Bundle extras = new Bundle();
+ extras.putInt(CallAttributes.CALLER_UID_KEY, Binder.getCallingUid());
+ extras.putInt(CallAttributes.CALLER_PID_KEY, Binder.getCallingPid());
+
VoipCallTransaction transaction = null;
// create transaction based on the call direction
switch (callAttributes.getDirection()) {
case DIRECTION_OUTGOING:
transaction = new OutgoingCallTransaction(callId, mContext, callAttributes,
- mCallsManager);
+ mCallsManager, extras);
break;
case DIRECTION_INCOMING:
transaction = new IncomingCallTransaction(callId, callAttributes,
- mCallsManager);
+ mCallsManager, extras);
break;
default:
throw new IllegalArgumentException(String.format("Invalid Call Direction. "
@@ -362,12 +368,13 @@
}
synchronized (mLock) {
final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ boolean crossUserAccess = hasInAppCrossUserPermission();
long token = Binder.clearCallingIdentity();
try {
return new ParceledListSlice<>(
mPhoneAccountRegistrar.getCallCapablePhoneAccounts(null,
includeDisabledAccounts, callingUserHandle,
- hasInAppCrossUserPermission()));
+ crossUserAccess));
} catch (Exception e) {
Log.e(this, e, "getCallCapablePhoneAccounts");
mAnomalyReporter.reportAnomaly(GET_CALL_CAPABLE_ACCOUNTS_ERROR_UUID,
@@ -540,15 +547,18 @@
throw e;
}
}
+ Set<String> permissions = computePermissionsForBoundPackage(
+ Set.of(MODIFY_PHONE_STATE), null);
long token = Binder.clearCallingIdentity();
try {
// In ideal case, we should not resolve the handle across profiles. But
// given the fact that profile's call is handled by its parent user's
// in-call UI, parent user's in call UI need to be able to get phone account
// from the profile's phone account handle.
- return mPhoneAccountRegistrar
+ PhoneAccount account = mPhoneAccountRegistrar
.getPhoneAccount(accountHandle, callingUserHandle,
/* acrossProfiles */ true);
+ return maybeCleansePhoneAccount(account, permissions);
} catch (Exception e) {
Log.e(this, e, "getPhoneAccount %s", accountHandle);
mAnomalyReporter.reportAnomaly(GET_PHONE_ACCOUNT_ERROR_UUID,
@@ -637,11 +647,12 @@
synchronized (mLock) {
final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ boolean crossUserAccess = hasInAppCrossUserPermission();
long token = Binder.clearCallingIdentity();
try {
return new ParceledListSlice<>(mPhoneAccountRegistrar
.getAllPhoneAccountHandles(callingUserHandle,
- hasInAppCrossUserPermission()));
+ crossUserAccess));
} catch (Exception e) {
Log.e(this, e, "getAllPhoneAccounts");
throw e;
@@ -767,6 +778,9 @@
.build();
}
+ // Validate the profile boundary of the given image URI.
+ validateAccountIconUserBoundary(account.getIcon());
+
final long token = Binder.clearCallingIdentity();
try {
Log.i(this, "registerPhoneAccount: account=%s",
@@ -945,12 +959,13 @@
synchronized (mLock) {
enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ boolean crossUserAccess = hasInAppCrossUserPermission();
long token = Binder.clearCallingIdentity();
try {
Log.i(this, "Silence Ringer requested by %s", callingPackage);
Set<UserHandle> userHandles = mCallsManager.getCallAudioManager().
silenceRingers(mContext, callingUserHandle,
- hasInAppCrossUserPermission());
+ crossUserAccess);
mCallsManager.getInCallController().silenceRinger(userHandles);
} finally {
Binder.restoreCallingIdentity(token);
@@ -1739,7 +1754,6 @@
enforceCallingPackage(callingPackage, "placeCall");
PhoneAccountHandle phoneAccountHandle = null;
- boolean clearPhoneAccountHandleExtra = false;
if (extras != null) {
phoneAccountHandle = extras.getParcelable(
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
@@ -1748,33 +1762,42 @@
extras.remove(TelecomManager.EXTRA_IS_HANDOVER);
}
}
- boolean isSelfManaged = phoneAccountHandle != null &&
+ ComponentName phoneAccountComponentName = phoneAccountHandle != null
+ ? phoneAccountHandle.getComponentName() : null;
+ String phoneAccountPackageName = phoneAccountComponentName != null
+ ? phoneAccountComponentName.getPackageName() : null;
+ boolean isCallerOwnerOfPhoneAccount =
+ callingPackage.equals(phoneAccountPackageName);
+ boolean isSelfManagedPhoneAccount =
isSelfManagedConnectionService(phoneAccountHandle);
- if (isSelfManaged) {
- try {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.MANAGE_OWN_CALLS,
- "Self-managed ConnectionServices require "
- + "MANAGE_OWN_CALLS permission.");
- } catch (SecurityException e) {
- // Fallback to use mobile network to avoid disclosing phone account handle
- // package information
- clearPhoneAccountHandleExtra = true;
- }
-
- if (!clearPhoneAccountHandleExtra && !callingPackage.equals(
- phoneAccountHandle.getComponentName().getPackageName())
- && !canCallPhone(callingPackage, callingFeatureId,
- "CALL_PHONE permission required to place calls.")) {
- // The caller is not allowed to place calls, so fallback to use mobile
- // network.
- clearPhoneAccountHandleExtra = true;
- }
- } else if (!canCallPhone(callingPackage, callingFeatureId, "placeCall")) {
+ // Ensure the app's calling package matches the PhoneAccount package name before
+ // checking self-managed status so that we do not leak installed package
+ // information.
+ boolean isSelfManagedRequest = isCallerOwnerOfPhoneAccount &&
+ isSelfManagedPhoneAccount;
+ if (isSelfManagedRequest) {
+ // The package name of the caller matches the package name of the
+ // PhoneAccountHandle, so ensure the app has MANAGE_OWN_CALLS permission if
+ // self-managed.
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_OWN_CALLS,
+ "Self-managed ConnectionServices require MANAGE_OWN_CALLS permission.");
+ } else if (!canCallPhone(callingPackage, callingFeatureId,
+ "CALL_PHONE permission required to place calls.")) {
+ // not self-managed, so CALL_PHONE is required.
mAnomalyReporter.reportAnomaly(PLACE_CALL_SECURITY_EXCEPTION_ERROR_UUID,
PLACE_CALL_SECURITY_EXCEPTION_ERROR_MSG);
- throw new SecurityException("Package " + callingPackage
- + " is not allowed to place phone calls");
+ throw new SecurityException(
+ "CALL_PHONE permission required to place calls.");
+ }
+
+ // An application can not place a call with a self-managed PhoneAccount that
+ // they do not own. If this is the case (and the app has CALL_PHONE permission),
+ // remove the PhoneAccount from the request and place the call as if it was a
+ // managed call request with no PhoneAccount specified.
+ if (!isCallerOwnerOfPhoneAccount && isSelfManagedPhoneAccount) {
+ // extras can not be null if isSelfManagedPhoneAccount is true
+ extras.remove(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
}
// Note: we can still get here for the default/system dialer, even if the Phone
@@ -1805,16 +1828,12 @@
final Intent intent = new Intent(hasCallPrivilegedPermission ?
Intent.ACTION_CALL_PRIVILEGED : Intent.ACTION_CALL, handle);
if (extras != null) {
- if (clearPhoneAccountHandleExtra) {
- extras.remove(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
- }
extras.setDefusable(true);
intent.putExtras(extras);
}
mUserCallIntentProcessorFactory.create(mContext, userHandle)
- .processIntent(
- intent, callingPackage, isSelfManaged ||
- (hasCallAppOp && hasCallPermission),
+ .processIntent(intent, callingPackage, isSelfManagedRequest,
+ (hasCallAppOp && hasCallPermission),
true /* isLocalInvocation */);
} finally {
Binder.restoreCallingIdentity(token);
@@ -1921,19 +1940,21 @@
}
- if (args.length > 0 && Analytics.ANALYTICS_DUMPSYS_ARG.equals(args[0])) {
+ if (args != null && args.length > 0 && Analytics.ANALYTICS_DUMPSYS_ARG.equals(
+ args[0])) {
Binder.withCleanCallingIdentity(() ->
Analytics.dumpToEncodedProto(mContext, writer, args));
return;
}
- boolean isTimeLineView = (args.length > 0 && TIME_LINE_ARG.equalsIgnoreCase(args[0]));
+ boolean isTimeLineView =
+ (args != null && args.length > 0 && TIME_LINE_ARG.equalsIgnoreCase(args[0]));
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
if (mCallsManager != null) {
pw.println("CallsManager: ");
pw.increaseIndent();
- mCallsManager.dump(pw);
+ mCallsManager.dump(pw, args);
pw.decreaseIndent();
pw.println("PhoneAccountRegistrar: ");
@@ -2711,6 +2732,46 @@
return packageUid == callingUid;
}
+ /**
+ * Note: This method should be called BEFORE clearing the binder identity.
+ *
+ * @param permissionsToValidate set of permissions that should be checked
+ * @param alreadyComputedPermissions a list of permissions that were already checked
+ * @return all the permissions that
+ */
+ private Set<String> computePermissionsForBoundPackage(
+ Set<String> permissionsToValidate,
+ Set<String> alreadyComputedPermissions) {
+ Set<String> permissions = Objects.requireNonNullElseGet(alreadyComputedPermissions,
+ HashSet::new);
+ for (String permission : permissionsToValidate) {
+ if (mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
+ permissions.add(permission);
+ }
+ }
+ return permissions;
+ }
+
+ /**
+ * This method should be used to clear {@link PhoneAccount} properties based on a
+ * callingPackages permissions.
+ *
+ * @param account to clear properties from
+ * @param permissions the list of permissions the callingPackge has
+ * @return the account that callingPackage will receive
+ */
+ private PhoneAccount maybeCleansePhoneAccount(PhoneAccount account,
+ Set<String> permissions) {
+ if (account == null) {
+ return null;
+ }
+ PhoneAccount.Builder accountBuilder = new PhoneAccount.Builder(account);
+ if (!permissions.contains(MODIFY_PHONE_STATE)) {
+ accountBuilder.setGroupId("***");
+ }
+ return accountBuilder.build();
+ }
+
private void enforceTelecomFeature() {
PackageManager pm = mContext.getPackageManager();
if (!pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)
@@ -3077,4 +3138,22 @@
mContext.sendBroadcast(intent);
}
}
+
+ private void validateAccountIconUserBoundary(Icon icon) {
+ // Refer to Icon#getUriString for context. The URI string is invalid for icons of
+ // incompatible types.
+ if (icon != null && (icon.getType() == Icon.TYPE_URI
+ || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
+ String encodedUser = icon.getUri().getEncodedUserInfo();
+ // If there is no encoded user, the URI is calling into the calling user space
+ if (encodedUser != null) {
+ int userId = Integer.parseInt(encodedUser);
+ if (userId != UserHandle.getUserId(Binder.getCallingUid())) {
+ // If we are transcending the profile boundary, throw an error.
+ throw new IllegalArgumentException("Attempting to register a phone account with"
+ + " an image icon belonging to another user.");
+ }
+ }
+ }
+ }
}
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index f9c2508..8477d49 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -26,12 +26,14 @@
import android.content.ServiceConnection;
import android.content.pm.ResolveInfo;
import android.net.Uri;
+import android.os.BugreportManager;
+import android.os.DropBoxManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.telecom.Log;
import android.telecom.PhoneAccountHandle;
import android.telephony.AnomalyReporter;
-import android.view.accessibility.AccessibilityManager;
+import android.telephony.TelephonyManager;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -42,6 +44,7 @@
import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
+import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
import com.android.server.telecom.components.UserCallIntentProcessor;
import com.android.server.telecom.components.UserCallIntentProcessorFactory;
import com.android.server.telecom.ui.AudioProcessingNotification;
@@ -49,10 +52,12 @@
import com.android.server.telecom.ui.IncomingCallNotifier;
import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
import com.android.server.telecom.ui.ToastFactory;
+import com.android.server.telecom.voip.TransactionManager;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;
+import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
@@ -210,9 +215,12 @@
RoleManagerAdapter roleManagerAdapter,
ContactsAsyncHelper.Factory contactsAsyncHelperFactory,
DeviceIdleControllerAdapter deviceIdleControllerAdapter,
- Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter) {
+ Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter,
+ Executor asyncTaskExecutor,
+ BlockedNumbersAdapter blockedNumbersAdapter) {
mContext = context.getApplicationContext();
LogUtils.initLogging(mContext);
+ android.telecom.Log.setLock(mLock);
AnomalyReporter.initialize(mContext);
DefaultDialerManagerAdapter defaultDialerAdapter =
new DefaultDialerCache.DefaultDialerManagerAdapterImpl();
@@ -331,10 +339,17 @@
}
};
+ EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger =
+ new EmergencyCallDiagnosticLogger(mContext.getSystemService(
+ TelephonyManager.class), mContext.getSystemService(
+ BugreportManager.class), timeoutsAdapter, mContext.getSystemService(
+ DropBoxManager.class), asyncTaskExecutor, clockProxy);
+
CallAnomalyWatchdog callAnomalyWatchdog = new CallAnomalyWatchdog(
Executors.newSingleThreadScheduledExecutor(),
- mLock, timeoutsAdapter, clockProxy);
+ mLock, timeoutsAdapter, clockProxy, emergencyCallDiagnosticLogger);
+ TransactionManager transactionManager = TransactionManager.getInstance();
mCallsManager = new CallsManager(
mContext,
mLock,
@@ -367,8 +382,11 @@
toastFactory,
callEndpointControllerFactory,
callAnomalyWatchdog,
- Executors.newSingleThreadExecutor(),
- accessibilityManagerAdapter);
+ accessibilityManagerAdapter,
+ asyncTaskExecutor,
+ blockedNumbersAdapter,
+ transactionManager,
+ emergencyCallDiagnosticLogger);
mIncomingCallNotifier = incomingCallNotifier;
incomingCallNotifier.setCallsManagerProxy(new IncomingCallNotifier.CallsManagerProxy() {
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index d4713e8..c5fdd4c 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -20,8 +20,8 @@
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.telecom.CallDiagnosticService;
-import android.telecom.CallRedirectionService;
import android.telecom.CallDiagnostics;
+import android.telecom.CallRedirectionService;
import android.telephony.ims.ImsReasonInfo;
import java.util.concurrent.TimeUnit;
@@ -112,12 +112,46 @@
public long getNonVoipEmergencyCallIntermediateStateTimeoutMillis() {
return Timeouts.getNonVoipEmergencyCallIntermediateStateTimeoutMillis();
}
+
+ public long getEmergencyCallTimeBeforeUserDisconnectThresholdMillis(){
+ return Timeouts.getEmergencyCallTimeBeforeUserDisconnectThresholdMillis();
+ }
+
+ public long getEmergencyCallActiveTimeThresholdMillis(){
+ return Timeouts.getEmergencyCallActiveTimeThresholdMillis();
+ }
+
+ public int getDaysBackToSearchEmergencyDiagnosticEntries(){
+ return Timeouts.getDaysBackToSearchEmergencyDiagnosticEntries();
+
+ }
}
/** A prefix to use for all keys so to not clobber the global namespace. */
private static final String PREFIX = "telecom.";
/**
+ * threshold used to filter out ecalls that the user may have dialed by mistake
+ * It is used only when the disconnect cause is LOCAL by EmergencyDiagnosticLogger
+ */
+ private static final String EMERGENCY_CALL_TIME_BEFORE_USER_DISCONNECT_THRESHOLD_MILLIS =
+ "emergency_call_time_before_user_disconnect_threshold_millis";
+
+ /**
+ * Returns the threshold used to detect ecalls that transition to active but only for a very
+ * short duration. These short duration active calls can result in Diagnostic data collection.
+ */
+ private static final String EMERGENCY_CALL_ACTIVE_TIME_THRESHOLD_MILLIS =
+ "emergency_call_active_time_threshold_millis";
+
+ /**
+ * Time in Days that is used to filter out old dropbox entries for emergency call diagnostic
+ * data. Entries older than this are ignored
+ */
+ private static final String DAYS_BACK_TO_SEARCH_EMERGENCY_DROP_BOX_ENTRIES =
+ "days_back_to_search_emergency_drop_box_entries";
+
+ /**
* A prefix to use for {@link DeviceConfig} for the transitory state timeout of
* VoIP Call, in millis.
*/
@@ -335,6 +369,36 @@
TRANSITORY_STATE_VOIP_NORMAL_TIMEOUT_MILLIS, 5000L);
}
+
+ /**
+ * Returns the threshold used to filter out ecalls that the user may have dialed by mistake
+ * It is used only when the disconnect cause is LOCAL by EmergencyDiagnosticLogger
+ * @return the threshold in milliseconds
+ */
+ public static long getEmergencyCallTimeBeforeUserDisconnectThresholdMillis() {
+ return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+ EMERGENCY_CALL_TIME_BEFORE_USER_DISCONNECT_THRESHOLD_MILLIS, 20000L);
+ }
+
+ /**
+ * Returns the threshold used to detect ecalls that transition to active but only for a very
+ * short duration. These short duration active calls can result in Diagnostic data collection.
+ * @return the threshold in milliseconds
+ */
+ public static long getEmergencyCallActiveTimeThresholdMillis() {
+ return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+ EMERGENCY_CALL_ACTIVE_TIME_THRESHOLD_MILLIS, 15000L);
+ }
+
+ /**
+ * Time in Days that is used to filter out old dropbox entries for emergency call diagnostic
+ * data. Entries older than this are ignored
+ */
+ public static int getDaysBackToSearchEmergencyDiagnosticEntries() {
+ return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TELEPHONY,
+ DAYS_BACK_TO_SEARCH_EMERGENCY_DROP_BOX_ENTRIES, 30);
+ }
+
/**
* Returns the duration of time an emergency VoIP call can be in a transitory state before
* Telecom will try to clean up the call.
@@ -352,7 +416,7 @@
*/
public static long getNonVoipCallTransitoryStateTimeoutMillis() {
return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
- TRANSITORY_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 5000L);
+ TRANSITORY_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 10000L);
}
/**
@@ -362,7 +426,7 @@
*/
public static long getNonVoipEmergencyCallTransitoryStateTimeoutMillis() {
return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
- TRANSITORY_STATE_NON_VOIP_EMERGENCY_TIMEOUT_MILLIS, 5000L);
+ TRANSITORY_STATE_NON_VOIP_EMERGENCY_TIMEOUT_MILLIS, 10000L);
}
/**
@@ -392,7 +456,7 @@
*/
public static long getNonVoipCallIntermediateStateTimeoutMillis() {
return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
- INTERMEDIATE_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 60000L);
+ INTERMEDIATE_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 120000L);
}
/**
diff --git a/src/com/android/server/telecom/TransactionalServiceWrapper.java b/src/com/android/server/telecom/TransactionalServiceWrapper.java
index d97d192..1e6403e 100644
--- a/src/com/android/server/telecom/TransactionalServiceWrapper.java
+++ b/src/com/android/server/telecom/TransactionalServiceWrapper.java
@@ -38,14 +38,13 @@
import com.android.internal.telecom.ICallControl;
import com.android.internal.telecom.ICallEventCallback;
-import com.android.server.telecom.voip.AnswerCallTransaction;
import com.android.server.telecom.voip.CallEventCallbackAckTransaction;
import com.android.server.telecom.voip.EndpointChangeTransaction;
import com.android.server.telecom.voip.HoldCallTransaction;
import com.android.server.telecom.voip.EndCallTransaction;
-import com.android.server.telecom.voip.HoldActiveCallForNewCallTransaction;
+import com.android.server.telecom.voip.MaybeHoldCallForNewCallTransaction;
import com.android.server.telecom.voip.ParallelTransaction;
-import com.android.server.telecom.voip.RequestFocusTransaction;
+import com.android.server.telecom.voip.RequestNewActiveCallTransaction;
import com.android.server.telecom.voip.SerialTransaction;
import com.android.server.telecom.voip.TransactionManager;
import com.android.server.telecom.voip.VoipCallTransaction;
@@ -86,7 +85,7 @@
private ConnectionServiceFocusManager.ConnectionServiceFocusListener mConnSvrFocusListener;
// init when constructor is called
private final Hashtable<String, Call> mTrackedCalls = new Hashtable<>();
- private final Object mLock;
+ private final TelecomSystem.SyncRoot mLock;
private final String mPackageName;
// needs to be non-final for testing
private TransactionManager mTransactionManager;
@@ -105,7 +104,7 @@
mPackageName = phoneAccountHandle.getComponentName().getPackageName();
mTransactionManager = TransactionManager.getInstance();
mStreamingController = mCallsManager.getCallStreamingController();
- mLock = new Object();
+ mLock = mCallsManager.getLock();
}
@VisibleForTesting
@@ -247,11 +246,12 @@
if (call != null) {
switch (action) {
case SET_ACTIVE:
- addTransactionsToManager(createSetActiveTransactions(call), callback);
+ handleCallControlNewCallFocusTransactions(call, SET_ACTIVE,
+ false /* isAnswer */, 0/*VideoState (ignored)*/, callback);
break;
case ANSWER:
- addTransactionsToManager(createSetAnswerTransactions(call,
- (int) objects[0]), callback);
+ handleCallControlNewCallFocusTransactions(call, ANSWER,
+ true /* isAnswer */, (int) objects[0] /*VideoState*/, callback);
break;
case DISCONNECT:
addTransactionsToManager(new EndCallTransaction(mCallsManager,
@@ -278,6 +278,32 @@
}
}
+ // The client is request their VoIP call state go ACTIVE/ANSWERED.
+ // This request is originating from the VoIP application.
+ private void handleCallControlNewCallFocusTransactions(Call call, String action,
+ boolean isAnswer, int potentiallyNewVideoState, ResultReceiver callback) {
+ mTransactionManager.addTransaction(createSetActiveTransactions(call),
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(VoipCallTransactionResult result) {
+ Log.i(TAG, String.format(Locale.US,
+ "%s: onResult: callId=[%s]", action, call.getId()));
+ if (isAnswer) {
+ call.setVideoState(potentiallyNewVideoState);
+ }
+ callback.send(TELECOM_TRANSACTION_SUCCESS, new Bundle());
+ }
+
+ @Override
+ public void onError(CallException exception) {
+ Bundle extras = new Bundle();
+ extras.putParcelable(TRANSACTION_EXCEPTION_KEY, exception);
+ callback.send(exception == null ? CallException.CODE_ERROR_UNKNOWN :
+ exception.getCode(), extras);
+ }
+ });
+ }
+
@Override
public void requestCallEndpointChange(CallEndpoint endpoint, ResultReceiver callback) {
try {
@@ -299,14 +325,12 @@
Call call = mTrackedCalls.get(callId);
if (call != null) {
call.onConnectionEvent(event, extras);
- }
- else{
+ } else {
Log.i(TAG,
"sendEvent: was called but there is no call with id=[%s] cannot be "
+ "found. Most likely the call has been disconnected");
}
- }
- finally {
+ } finally {
Log.endSession();
}
}
@@ -348,7 +372,8 @@
try {
Log.startSession("TSW.oSA");
Log.d(TAG, String.format(Locale.US, "onSetActive: callId=[%s]", call.getId()));
- handleNewActiveCallCallbacks(call, ON_SET_ACTIVE, 0);
+ handleCallEventCallbackNewFocus(call, ON_SET_ACTIVE, false /*isAnswerRequest*/,
+ 0 /*VideoState*/);
} finally {
Log.endSession();
}
@@ -358,42 +383,51 @@
try {
Log.startSession("TSW.oA");
Log.d(TAG, String.format(Locale.US, "onAnswer: callId=[%s]", call.getId()));
- handleNewActiveCallCallbacks(call, ON_ANSWER, videoState);
+ handleCallEventCallbackNewFocus(call, ON_ANSWER, true /*isAnswerRequest*/,
+ videoState /*VideoState*/);
} finally {
Log.endSession();
}
}
- // need to create multiple transactions for onSetActive and onAnswer which both seek to set
- // the call to active
- private void handleNewActiveCallCallbacks(Call call, String action, int videoState) {
+ // handle a CallEventCallback to set a call ACTIVE/ANSWERED. Must get ack from client since the
+ // request has come from another source (ex. Android Auto is requesting a call to go active)
+ private void handleCallEventCallbackNewFocus(Call call, String action, boolean isAnswerRequest,
+ int potentiallyNewVideoState) {
// save CallsManager state before sending client state changes
Call foregroundCallBeforeSwap = mCallsManager.getForegroundCall();
boolean wasActive = foregroundCallBeforeSwap != null && foregroundCallBeforeSwap.isActive();
- // create 3 serial transactions:
- // -- hold active
- // -- set newCall as active
- // -- ack from client
SerialTransaction serialTransactions = createSetActiveTransactions(call);
- serialTransactions.appendTransaction(
- new CallEventCallbackAckTransaction(mICallEventCallback,
- action, call.getId(), videoState));
+ // 3. get ack from client (that the requested call can go active)
+ if (isAnswerRequest) {
+ serialTransactions.appendTransaction(
+ new CallEventCallbackAckTransaction(mICallEventCallback,
+ action, call.getId(), potentiallyNewVideoState, mLock));
+ } else {
+ serialTransactions.appendTransaction(
+ new CallEventCallbackAckTransaction(mICallEventCallback,
+ action, call.getId(), mLock));
+ }
// do CallsManager workload before asking client and
// reset CallsManager state if client does NOT ack
- mTransactionManager.addTransaction(serialTransactions, new OutcomeReceiver<>() {
- @Override
- public void onResult(VoipCallTransactionResult result) {
- Log.i(TAG, String.format(Locale.US,
- "%s: onResult: callId=[%s]", action, call.getId()));
- }
+ mTransactionManager.addTransaction(serialTransactions,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(VoipCallTransactionResult result) {
+ Log.i(TAG, String.format(Locale.US,
+ "%s: onResult: callId=[%s]", action, call.getId()));
+ if (isAnswerRequest) {
+ call.setVideoState(potentiallyNewVideoState);
+ }
+ }
- @Override
- public void onError(CallException exception) {
- maybeResetForegroundCall(foregroundCallBeforeSwap, wasActive);
- }
- });
+ @Override
+ public void onError(CallException exception) {
+ maybeResetForegroundCall(foregroundCallBeforeSwap, wasActive);
+ }
+ });
}
@@ -403,7 +437,7 @@
Log.i(TAG, String.format(Locale.US, "onSetInactive: callId=[%s]", call.getId()));
mTransactionManager.addTransaction(
new CallEventCallbackAckTransaction(mICallEventCallback,
- ON_SET_INACTIVE, call.getId()), new OutcomeReceiver<>() {
+ ON_SET_INACTIVE, call.getId(), mLock), new OutcomeReceiver<>() {
@Override
public void onResult(VoipCallTransactionResult result) {
mCallsManager.markCallAsOnHold(call);
@@ -426,7 +460,7 @@
mTransactionManager.addTransaction(
new CallEventCallbackAckTransaction(mICallEventCallback, ON_DISCONNECT,
- call.getId(), cause), new OutcomeReceiver<>() {
+ call.getId(), cause, mLock), new OutcomeReceiver<>() {
@Override
public void onResult(VoipCallTransactionResult result) {
removeCallFromCallsManager(call, cause);
@@ -451,7 +485,7 @@
mTransactionManager.addTransaction(
new CallEventCallbackAckTransaction(mICallEventCallback, ON_STREAMING_STARTED,
- call.getId()), new OutcomeReceiver<>() {
+ call.getId(), mLock), new OutcomeReceiver<>() {
@Override
public void onResult(VoipCallTransactionResult result) {
}
@@ -519,7 +553,7 @@
}
}
- public void onEvent(Call call, String event, Bundle extras){
+ public void onEvent(Call call, String event, Bundle extras) {
if (call != null) {
try {
mICallEventCallback.onEvent(call.getId(), event, extras);
@@ -553,28 +587,12 @@
// create list for multiple transactions
List<VoipCallTransaction> transactions = new ArrayList<>();
- // add t1. hold potential active call
- transactions.add(new HoldActiveCallForNewCallTransaction(mCallsManager, call));
+ // potentially hold the current active call in order to set a new call (active/answered)
+ transactions.add(new MaybeHoldCallForNewCallTransaction(mCallsManager, call));
+ // And request a new focus call update
+ transactions.add(new RequestNewActiveCallTransaction(mCallsManager, call));
- // add t2. send request to set the current call active
- transactions.add(new RequestFocusTransaction(mCallsManager, call));
-
- // send off to Transaction Manager to process
- return new SerialTransaction(transactions);
- }
-
- private SerialTransaction createSetAnswerTransactions(Call call, int videoState) {
- // create list for multiple transactions
- List<VoipCallTransaction> transactions = new ArrayList<>();
-
- // add t1. hold potential active call
- transactions.add(new HoldActiveCallForNewCallTransaction(mCallsManager, call));
-
- // add t2. answer current call
- transactions.add(new AnswerCallTransaction(mCallsManager, call, videoState));
-
- // send off to Transaction Manager to process
- return new SerialTransaction(transactions);
+ return new SerialTransaction(transactions, mLock);
}
/***
@@ -631,12 +649,13 @@
subTransactions.add(mStreamingController.getCallStreamingServiceTransaction(
mCallsManager.getContext(), this, call));
// add t2.2 audio route operations
- subTransactions.add(new CallStreamingController.AudioInterceptionTransaction(call, true));
+ subTransactions.add(new CallStreamingController.AudioInterceptionTransaction(call,
+ true, mLock));
// add t2
- transactions.add(new ParallelTransaction(subTransactions));
+ transactions.add(new ParallelTransaction(subTransactions, mLock));
// send off to Transaction Manager to process
- return new SerialTransaction(transactions);
+ return new SerialTransaction(transactions, mLock);
}
private VoipCallTransaction createStopStreamingTransaction(Call call) {
@@ -647,8 +666,9 @@
// 1. unbind to call streaming service
transactions.add(mStreamingController.getUnbindStreamingServiceTransaction());
// 2. audio route operations
- transactions.add(new CallStreamingController.AudioInterceptionTransaction(call, false));
- return new ParallelTransaction(transactions);
+ transactions.add(new CallStreamingController.AudioInterceptionTransaction(call,
+ false, mLock));
+ return new ParallelTransaction(transactions, mLock);
}
diff --git a/src/com/android/server/telecom/callfiltering/BlockedNumbersAdapter.java b/src/com/android/server/telecom/callfiltering/BlockedNumbersAdapter.java
new file mode 100644
index 0000000..b8658d8
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/BlockedNumbersAdapter.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.callfiltering;
+
+import android.content.Context;
+
+/**
+ * Adapter interface that wraps methods from
+ * {@link android.provider.BlockedNumberContract.SystemContract} and
+ * {@link com.android.server.telecom.settings.BlockedNumbersUtil} to make things testable.
+ */
+public interface BlockedNumbersAdapter {
+ boolean shouldShowEmergencyCallNotification (Context context);
+ void updateEmergencyCallNotification(Context context, boolean showNotification);
+}
diff --git a/src/com/android/server/telecom/callfiltering/DndCallFilter.java b/src/com/android/server/telecom/callfiltering/DndCallFilter.java
index a100bad..f6ed646 100644
--- a/src/com/android/server/telecom/callfiltering/DndCallFilter.java
+++ b/src/com/android/server/telecom/callfiltering/DndCallFilter.java
@@ -55,9 +55,6 @@
// query NotificationManager to determine if the call should ring or be suppressed
boolean shouldSuppress = !mRinger.shouldRingForContact(mCall);
- // store the shouldSuppress value in the call object which will be passed to InCallServices
- mCall.setCallIsSuppressedByDoNotDisturb(shouldSuppress);
-
// end timer
Log.addEvent(mCall, LogUtils.Events.DND_PRE_CHECK_COMPLETED, shouldSuppress);
diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
index 039526b..963e923 100644
--- a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
+++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
@@ -162,6 +162,19 @@
Log.endSession();
}
}
+
+ @Override
+ public void onNullBinding(ComponentName componentName) {
+ // Make sure we unbind the service if onBind returns null
+ Log.startSession("CRSC.oNB");
+ try {
+ synchronized (mTelecomLock) {
+ finishCallRedirection();
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
}
private class CallRedirectionAdapter extends ICallRedirectionAdapter.Stub {
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 3881154..ef85fc7 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -26,6 +26,7 @@
import android.os.PowerManager;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.provider.BlockedNumberContract;
import android.telecom.Log;
import android.telecom.CallerInfoAsyncQuery;
@@ -59,10 +60,14 @@
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.TelecomWakeLock;
import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
+import com.android.server.telecom.settings.BlockedNumbersUtil;
import com.android.server.telecom.ui.IncomingCallNotifier;
import com.android.server.telecom.ui.MissedCallNotifierImpl;
import com.android.server.telecom.ui.NotificationChannelManager;
+import java.util.concurrent.Executors;
+
/**
* Implementation of the ITelecom interface.
*/
@@ -156,7 +161,7 @@
new InCallWakeLockControllerFactory() {
@Override
public InCallWakeLockController create(Context context,
- CallsManager callsManager) {
+ CallsManager callsManager) {
return new InCallWakeLockController(
new TelecomWakeLock(context,
PowerManager.FULL_WAKE_LOCK,
@@ -208,6 +213,22 @@
return context.getSystemService(AccessibilityManager.class)
.stopFlashNotificationSequence(context);
}
+ },
+ Executors.newCachedThreadPool(),
+ new BlockedNumbersAdapter() {
+ @Override
+ public boolean shouldShowEmergencyCallNotification(Context
+ context) {
+ return BlockedNumberContract.SystemContract
+ .shouldShowEmergencyCallNotification(context);
+ }
+
+ @Override
+ public void updateEmergencyCallNotification(Context context,
+ boolean showNotification) {
+ BlockedNumbersUtil.updateEmergencyCallNotification(context,
+ showNotification);
+ }
}));
}
}
diff --git a/src/com/android/server/telecom/components/UserCallActivity.java b/src/com/android/server/telecom/components/UserCallActivity.java
index 1d85884..d7b2001 100644
--- a/src/com/android/server/telecom/components/UserCallActivity.java
+++ b/src/com/android/server/telecom/components/UserCallActivity.java
@@ -74,7 +74,8 @@
// ActivityThread.ActivityClientRecord#intent directly.
// Modifying directly may be a potential risk when relaunching this activity.
new UserCallIntentProcessor(this, userHandle).processIntent(new Intent(intent),
- getCallingPackage(), true /* hasCallAppOp*/, false /* isLocalInvocation */);
+ getCallingPackage(), false, true /* hasCallAppOp*/,
+ false /* isLocalInvocation */);
} finally {
Log.endSession();
wakelock.release();
diff --git a/src/com/android/server/telecom/components/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
index cad7b4c..a4602c1 100755
--- a/src/com/android/server/telecom/components/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -69,6 +69,7 @@
*
* @param intent The intent.
* @param callingPackageName The package name of the calling app.
+ * @param isSelfManaged {@code true} if SelfManaged profile enabled.
* @param canCallNonEmergency {@code true} if the caller is permitted to call non-emergency
* numbers.
* @param isLocalInvocation {@code true} if the caller is within the system service (i.e. the
@@ -79,19 +80,21 @@
* service resides.
*/
public void processIntent(Intent intent, String callingPackageName,
- boolean canCallNonEmergency, boolean isLocalInvocation) {
+ boolean isSelfManaged, boolean canCallNonEmergency,
+ boolean isLocalInvocation) {
String action = intent.getAction();
if (Intent.ACTION_CALL.equals(action) ||
Intent.ACTION_CALL_PRIVILEGED.equals(action) ||
Intent.ACTION_CALL_EMERGENCY.equals(action)) {
- processOutgoingCallIntent(intent, callingPackageName, canCallNonEmergency,
- isLocalInvocation);
+ processOutgoingCallIntent(intent, callingPackageName, isSelfManaged,
+ canCallNonEmergency, isLocalInvocation);
}
}
private void processOutgoingCallIntent(Intent intent, String callingPackageName,
- boolean canCallNonEmergency, boolean isLocalInvocation) {
+ boolean isSelfManaged, boolean canCallNonEmergency,
+ boolean isLocalInvocation) {
Uri handle = intent.getData();
if (handle == null) return;
String scheme = handle.getScheme();
@@ -102,40 +105,43 @@
handle = Uri.fromParts(PhoneAccount.SCHEME_SIP, uriString, null);
}
- // Check DISALLOW_OUTGOING_CALLS restriction. Note: We are skipping this check in a managed
- // profile user because this check can always be bypassed by copying and pasting the phone
- // number into the personal dialer.
- if (!UserUtil.isManagedProfile(mContext, mUserHandle)) {
- // Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
- // restriction.
- if (!TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
- final UserManager userManager = (UserManager) mContext.getSystemService(
- Context.USER_SERVICE);
- if (userManager.hasBaseUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
- mUserHandle)) {
- showErrorDialogForRestrictedOutgoingCall(mContext,
- R.string.outgoing_call_not_allowed_user_restriction);
- Log.w(this, "Rejecting non-emergency phone call due to DISALLOW_OUTGOING_CALLS "
- + "restriction");
- return;
- } else if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
- mUserHandle)) {
- final DevicePolicyManager dpm =
- mContext.getSystemService(DevicePolicyManager.class);
- if (dpm == null) {
+ if(!isSelfManaged) {
+ // Check DISALLOW_OUTGOING_CALLS restriction. Note: We are skipping this
+ // check in a managed profile user because this check can always be bypassed
+ // by copying and pasting the phone number into the personal dialer.
+ if (!UserUtil.isManagedProfile(mContext, mUserHandle)) {
+ // Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
+ // restriction.
+ if (!TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
+ final UserManager userManager =
+ (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ if (userManager.hasBaseUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
+ mUserHandle)) {
+ showErrorDialogForRestrictedOutgoingCall(mContext,
+ R.string.outgoing_call_not_allowed_user_restriction);
+ Log.w(this, "Rejecting non-emergency phone call "
+ + "due to DISALLOW_OUTGOING_CALLS restriction");
+ return;
+ } else if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
+ mUserHandle)) {
+ final DevicePolicyManager dpm =
+ mContext.getSystemService(DevicePolicyManager.class);
+ if (dpm == null) {
+ return;
+ }
+ final Intent adminSupportIntent = dpm.createAdminSupportIntent(
+ UserManager.DISALLOW_OUTGOING_CALLS);
+ if (adminSupportIntent != null) {
+ mContext.startActivity(adminSupportIntent);
+ }
return;
}
- final Intent adminSupportIntent = dpm.createAdminSupportIntent(
- UserManager.DISALLOW_OUTGOING_CALLS);
- if (adminSupportIntent != null) {
- mContext.startActivity(adminSupportIntent);
- }
- return;
}
}
}
- if (!canCallNonEmergency && !TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
+ if (!isSelfManaged && !canCallNonEmergency &&
+ !TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
showErrorDialogForRestrictedOutgoingCall(mContext,
R.string.outgoing_call_not_allowed_no_permission);
Log.w(this, "Rejecting non-emergency phone call because "
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
index 6e2eb97..7345a67 100644
--- a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
+++ b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
@@ -40,7 +40,6 @@
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
@@ -156,7 +155,8 @@
}
};
registerReceiver(mBlockingStatusReceiver, new IntentFilter(
- BlockedNumberContract.SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED));
+ BlockedNumberContract.SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED),
+ Context.RECEIVER_EXPORTED);
getLoaderManager().initLoader(0, null, this);
}
diff --git a/src/com/android/server/telecom/settings/EnhancedCallBlockingFragment.java b/src/com/android/server/telecom/settings/EnhancedCallBlockingFragment.java
index c0bb56a..b1a1b0e 100644
--- a/src/com/android/server/telecom/settings/EnhancedCallBlockingFragment.java
+++ b/src/com/android/server/telecom/settings/EnhancedCallBlockingFragment.java
@@ -45,6 +45,7 @@
private static final String BLOCK_UNAVAILABLE_NUMBERS_KEY =
"block_unavailable_calls_setting";
private boolean mIsCombiningRestrictedAndUnknownOption = false;
+ private boolean mIsCombiningUnavailableAndUnknownOption = false;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -94,11 +95,17 @@
R.bool.combine_options_to_block_restricted_and_unknown_callers);
if (mIsCombiningRestrictedAndUnknownOption) {
Preference restricted_pref = findPreference(BLOCK_RESTRICTED_NUMBERS_KEY);
- Preference unavailable_pref = findPreference(BLOCK_UNAVAILABLE_NUMBERS_KEY);
screen.removePreference(restricted_pref);
- screen.removePreference(unavailable_pref);
Log.i(this, "onCreate: removed block restricted preference.");
}
+
+ mIsCombiningUnavailableAndUnknownOption = getResources().getBoolean(
+ R.bool.combine_options_to_block_unavailable_and_unknown_callers);
+ if (mIsCombiningUnavailableAndUnknownOption) {
+ Preference unavailable_pref = findPreference(BLOCK_UNAVAILABLE_NUMBERS_KEY);
+ screen.removePreference(unavailable_pref);
+ Log.i(this, "onCreate: removed block unavailable preference.");
+ }
}
/**
@@ -136,14 +143,20 @@
@Override
public boolean onPreferenceChange(Preference preference, Object objValue) {
- if (mIsCombiningRestrictedAndUnknownOption
- && preference.getKey().equals(BLOCK_UNKNOWN_NUMBERS_KEY)) {
- Log.i(this, "onPreferenceChange: changing %s and %s to %b",
- preference.getKey(), BLOCK_RESTRICTED_NUMBERS_KEY, (boolean) objValue);
- BlockedNumbersUtil.setEnhancedBlockSetting(getActivity(), BLOCK_RESTRICTED_NUMBERS_KEY,
- (boolean) objValue);
- BlockedNumbersUtil.setEnhancedBlockSetting(getActivity(),
- BLOCK_UNAVAILABLE_NUMBERS_KEY, (boolean) objValue);
+ if (preference.getKey().equals(BLOCK_UNKNOWN_NUMBERS_KEY)) {
+ if (mIsCombiningRestrictedAndUnknownOption) {
+ Log.i(this, "onPreferenceChange: changing %s and %s to %b",
+ preference.getKey(), BLOCK_RESTRICTED_NUMBERS_KEY, (boolean) objValue);
+ BlockedNumbersUtil.setEnhancedBlockSetting(getActivity(),
+ BLOCK_RESTRICTED_NUMBERS_KEY, (boolean) objValue);
+ }
+
+ if (mIsCombiningUnavailableAndUnknownOption) {
+ Log.i(this, "onPreferenceChange: changing %s and %s to %b",
+ preference.getKey(), BLOCK_UNAVAILABLE_NUMBERS_KEY, (boolean) objValue);
+ BlockedNumbersUtil.setEnhancedBlockSetting(getActivity(),
+ BLOCK_UNAVAILABLE_NUMBERS_KEY, (boolean) objValue);
+ }
}
BlockedNumbersUtil.setEnhancedBlockSetting(getActivity(), preference.getKey(),
(boolean) objValue);
diff --git a/src/com/android/server/telecom/voip/AnswerCallTransaction.java b/src/com/android/server/telecom/voip/AnswerCallTransaction.java
deleted file mode 100644
index 1eb34c4..0000000
--- a/src/com/android/server/telecom/voip/AnswerCallTransaction.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.telecom.voip;
-
-import android.os.OutcomeReceiver;
-import android.telecom.CallException;
-import android.util.Log;
-
-import com.android.server.telecom.Call;
-import com.android.server.telecom.CallState;
-import com.android.server.telecom.CallsManager;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-
-/**
- * This transaction should be created for new incoming calls that request to go from
- * CallState.Ringing to CallState.Answered. Before changing the CallState, the focus manager must
- * be updated. Once the focus manager updates, the call state will be set. If there is an issue
- * answering the call, the transaction will fail.
- */
-public class AnswerCallTransaction extends VoipCallTransaction {
-
- private static final String TAG = AnswerCallTransaction.class.getSimpleName();
- private final CallsManager mCallsManager;
- private final Call mCall;
- private final int mVideoState;
-
- public AnswerCallTransaction(CallsManager callsManager, Call call, int videoState) {
- mCallsManager = callsManager;
- mCall = call;
- mVideoState = videoState;
- }
-
- @Override
- public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
- Log.d(TAG, "processTransaction");
- CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
-
- mCall.setVideoState(mVideoState);
-
- mCallsManager.transactionRequestNewFocusCall(mCall, CallState.ANSWERED,
- new OutcomeReceiver<>() {
- @Override
- public void onResult(Boolean result) {
- Log.d(TAG, "processTransaction: onResult");
- future.complete(new VoipCallTransactionResult(
- VoipCallTransactionResult.RESULT_SUCCEED, null));
- }
-
- @Override
- public void onError(CallException exception) {
- Log.d(TAG, "processTransaction: onError");
- future.complete(new VoipCallTransactionResult(
- exception.getCode(), exception.getMessage()));
- }
- });
-
- return future;
- }
-}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java b/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
index f47e4c5..8b4ffed 100644
--- a/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
+++ b/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
@@ -22,10 +22,12 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.telecom.CallAttributes;
import android.telecom.DisconnectCause;
import android.util.Log;
import com.android.internal.telecom.ICallEventCallback;
+import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.TransactionalServiceWrapper;
import java.util.concurrent.CompletableFuture;
@@ -43,7 +45,7 @@
private final String mAction;
private final String mCallId;
// optional values
- private int mVideoState = 0;
+ private int mVideoState = CallAttributes.AUDIO_CALL;
private DisconnectCause mDisconnectCause = null;
private final VoipCallTransactionResult TRANSACTION_FAILED = new VoipCallTransactionResult(
@@ -66,7 +68,8 @@
}
public CallEventCallbackAckTransaction(ICallEventCallback service, String action,
- String callId) {
+ String callId, TelecomSystem.SyncRoot lock) {
+ super(lock);
mICallEventCallback = service;
mAction = action;
mCallId = callId;
@@ -74,7 +77,8 @@
public CallEventCallbackAckTransaction(ICallEventCallback service, String action, String callId,
- int videoState) {
+ int videoState, TelecomSystem.SyncRoot lock) {
+ super(lock);
mICallEventCallback = service;
mAction = action;
mCallId = callId;
@@ -82,7 +86,8 @@
}
public CallEventCallbackAckTransaction(ICallEventCallback service, String action, String callId,
- DisconnectCause cause) {
+ DisconnectCause cause, TelecomSystem.SyncRoot lock) {
+ super(lock);
mICallEventCallback = service;
mAction = action;
mCallId = callId;
diff --git a/src/com/android/server/telecom/voip/EndCallTransaction.java b/src/com/android/server/telecom/voip/EndCallTransaction.java
index 7a5006a..0cb7458 100644
--- a/src/com/android/server/telecom/voip/EndCallTransaction.java
+++ b/src/com/android/server/telecom/voip/EndCallTransaction.java
@@ -36,6 +36,7 @@
private DisconnectCause mCause;
public EndCallTransaction(CallsManager callsManager, DisconnectCause cause, Call call) {
+ super(callsManager.getLock());
mCallsManager = callsManager;
mCause = cause;
mCall = call;
diff --git a/src/com/android/server/telecom/voip/EndpointChangeTransaction.java b/src/com/android/server/telecom/voip/EndpointChangeTransaction.java
index 88630c8..e037a79 100644
--- a/src/com/android/server/telecom/voip/EndpointChangeTransaction.java
+++ b/src/com/android/server/telecom/voip/EndpointChangeTransaction.java
@@ -32,6 +32,7 @@
private final CallsManager mCallsManager;
public EndpointChangeTransaction(CallEndpoint endpoint, CallsManager callsManager) {
+ super(callsManager.getLock());
mCallEndpoint = endpoint;
mCallsManager = callsManager;
}
diff --git a/src/com/android/server/telecom/voip/HoldCallTransaction.java b/src/com/android/server/telecom/voip/HoldCallTransaction.java
index bf0805e..6c4e8b7 100644
--- a/src/com/android/server/telecom/voip/HoldCallTransaction.java
+++ b/src/com/android/server/telecom/voip/HoldCallTransaction.java
@@ -32,6 +32,7 @@
private final Call mCall;
public HoldCallTransaction(CallsManager callsManager, Call call) {
+ super(callsManager.getLock());
mCallsManager = callsManager;
mCall = call;
}
diff --git a/src/com/android/server/telecom/voip/IncomingCallTransaction.java b/src/com/android/server/telecom/voip/IncomingCallTransaction.java
index 7bb9736..c0bb93d 100644
--- a/src/com/android/server/telecom/voip/IncomingCallTransaction.java
+++ b/src/com/android/server/telecom/voip/IncomingCallTransaction.java
@@ -20,7 +20,6 @@
import android.os.Bundle;
import android.telecom.CallAttributes;
-import android.telecom.CallControl;
import android.telecom.CallException;
import android.telecom.TelecomManager;
import android.util.Log;
@@ -37,14 +36,22 @@
private final String mCallId;
private final CallAttributes mCallAttributes;
private final CallsManager mCallsManager;
+ private final Bundle mExtras;
public IncomingCallTransaction(String callId, CallAttributes callAttributes,
- CallsManager callsManager) {
+ CallsManager callsManager, Bundle extras) {
+ super(callsManager.getLock());
+ mExtras = extras;
mCallId = callId;
mCallAttributes = callAttributes;
mCallsManager = callsManager;
}
+ public IncomingCallTransaction(String callId, CallAttributes callAttributes,
+ CallsManager callsManager) {
+ this(callId, callAttributes, callsManager, new Bundle());
+ }
+
@Override
public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
Log.d(TAG, "processTransaction");
@@ -69,11 +76,10 @@
}
}
- private Bundle generateExtras(CallAttributes callAttributes){
- Bundle extras = new Bundle();
- extras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
- extras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
- extras.putInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE, callAttributes.getCallType());
- return extras;
+ private Bundle generateExtras(CallAttributes callAttributes) {
+ mExtras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
+ mExtras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
+ mExtras.putInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE, callAttributes.getCallType());
+ return mExtras;
}
}
diff --git a/src/com/android/server/telecom/voip/HoldActiveCallForNewCallTransaction.java b/src/com/android/server/telecom/voip/MaybeHoldCallForNewCallTransaction.java
similarity index 87%
rename from src/com/android/server/telecom/voip/HoldActiveCallForNewCallTransaction.java
rename to src/com/android/server/telecom/voip/MaybeHoldCallForNewCallTransaction.java
index 5d269b1..a245c1c 100644
--- a/src/com/android/server/telecom/voip/HoldActiveCallForNewCallTransaction.java
+++ b/src/com/android/server/telecom/voip/MaybeHoldCallForNewCallTransaction.java
@@ -26,13 +26,14 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
-public class HoldActiveCallForNewCallTransaction extends VoipCallTransaction {
+public class MaybeHoldCallForNewCallTransaction extends VoipCallTransaction {
- private static final String TAG = HoldActiveCallForNewCallTransaction.class.getSimpleName();
+ private static final String TAG = MaybeHoldCallForNewCallTransaction.class.getSimpleName();
private final CallsManager mCallsManager;
private final Call mCall;
- public HoldActiveCallForNewCallTransaction(CallsManager callsManager, Call call) {
+ public MaybeHoldCallForNewCallTransaction(CallsManager callsManager, Call call) {
+ super(callsManager.getLock());
mCallsManager = callsManager;
mCall = call;
}
diff --git a/src/com/android/server/telecom/voip/OutgoingCallTransaction.java b/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
index 169fc48..0b17da2 100644
--- a/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
+++ b/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
@@ -43,14 +43,22 @@
private final String mCallingPackage;
private final CallAttributes mCallAttributes;
private final CallsManager mCallsManager;
+ private final Bundle mExtras;
+
+ public OutgoingCallTransaction(String callId, Context context, CallAttributes callAttributes,
+ CallsManager callsManager, Bundle extras) {
+ super(callsManager.getLock());
+ mCallId = callId;
+ mContext = context;
+ mCallAttributes = callAttributes;
+ mCallsManager = callsManager;
+ mExtras = extras;
+ mCallingPackage = mContext.getOpPackageName();
+ }
public OutgoingCallTransaction(String callId, Context context, CallAttributes callAttributes,
CallsManager callsManager) {
- mCallId = callId;
- mContext = context;
- mCallingPackage = mContext.getOpPackageName();
- mCallAttributes = callAttributes;
- mCallsManager = callsManager;
+ this(callId, context, callAttributes, callsManager, new Bundle());
}
@Override
@@ -112,13 +120,12 @@
}
}
- private Bundle generateExtras(CallAttributes callAttributes){
- Bundle extras = new Bundle();
- extras.setDefusable(true);
- extras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
- extras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
- extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+ private Bundle generateExtras(CallAttributes callAttributes) {
+ mExtras.setDefusable(true);
+ mExtras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
+ mExtras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
+ mExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
callAttributes.getCallType());
- return extras;
+ return mExtras;
}
}
diff --git a/src/com/android/server/telecom/voip/ParallelTransaction.java b/src/com/android/server/telecom/voip/ParallelTransaction.java
index c2d532c..2762949 100644
--- a/src/com/android/server/telecom/voip/ParallelTransaction.java
+++ b/src/com/android/server/telecom/voip/ParallelTransaction.java
@@ -16,6 +16,8 @@
package com.android.server.telecom.voip;
+import com.android.server.telecom.TelecomSystem;
+
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@@ -23,8 +25,9 @@
* A VoipCallTransaction implementation that its sub transactions will be executed in parallel
*/
public class ParallelTransaction extends VoipCallTransaction {
- public ParallelTransaction(List<VoipCallTransaction> subTransactions) {
- super(subTransactions);
+ public ParallelTransaction(List<VoipCallTransaction> subTransactions,
+ TelecomSystem.SyncRoot lock) {
+ super(subTransactions, lock);
}
@Override
diff --git a/src/com/android/server/telecom/voip/RequestFocusTransaction.java b/src/com/android/server/telecom/voip/RequestFocusTransaction.java
deleted file mode 100644
index 5dedbda..0000000
--- a/src/com/android/server/telecom/voip/RequestFocusTransaction.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.telecom.voip;
-
-import android.os.OutcomeReceiver;
-import android.telecom.CallException;
-import android.util.Log;
-
-import com.android.server.telecom.Call;
-import com.android.server.telecom.CallState;
-import com.android.server.telecom.CallsManager;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-
-public class RequestFocusTransaction extends VoipCallTransaction {
-
- private static final String TAG = RequestFocusTransaction.class.getSimpleName();
- private final CallsManager mCallsManager;
- private final Call mCall;
-
- public RequestFocusTransaction(CallsManager callsManager, Call call) {
- mCallsManager = callsManager;
- mCall = call;
- }
-
- @Override
- public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
- Log.d(TAG, "processTransaction");
- CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
-
- mCallsManager.transactionRequestNewFocusCall(mCall, CallState.ACTIVE,
- new OutcomeReceiver<>() {
- @Override
- public void onResult(Boolean result) {
- Log.d(TAG, "processTransaction: onResult");
- future.complete(new VoipCallTransactionResult(
- VoipCallTransactionResult.RESULT_SUCCEED, null));
- }
-
- @Override
- public void onError(CallException exception) {
- Log.d(TAG, "processTransaction: onError");
- future.complete(new VoipCallTransactionResult(
- exception.getCode(), exception.getMessage()));
- }
- });
-
- return future;
- }
-}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/voip/RequestNewActiveCallTransaction.java b/src/com/android/server/telecom/voip/RequestNewActiveCallTransaction.java
new file mode 100644
index 0000000..f586cc3
--- /dev/null
+++ b/src/com/android/server/telecom/voip/RequestNewActiveCallTransaction.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.voip;
+
+import android.os.OutcomeReceiver;
+import android.telecom.CallAttributes;
+import android.telecom.CallException;
+import android.util.Log;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ConnectionServiceFocusManager;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+/**
+ * This transaction should be created when a requesting call would like to go from a valid inactive
+ * state (ex. HELD, RINGING, DIALING) to ACTIVE.
+ *
+ * This class performs some pre-checks to spot a failure in requesting a new call focus and sends
+ * the official request to transition the requested call to ACTIVE.
+ *
+ * Note:
+ * - This Transaction is used for CallControl and CallEventCallbacks, do not put logic in the
+ * onResult/onError that pertains to one direction.
+ * - MaybeHoldCallForNewCallTransaction was performed before this so any potential active calls
+ * should be held now.
+ */
+public class RequestNewActiveCallTransaction extends VoipCallTransaction {
+
+ private static final String TAG = RequestNewActiveCallTransaction.class.getSimpleName();
+ private final CallsManager mCallsManager;
+ private final Call mCall;
+
+ public RequestNewActiveCallTransaction(CallsManager callsManager, Call call) {
+ super(callsManager.getLock());
+ mCallsManager = callsManager;
+ mCall = call;
+ }
+
+ @Override
+ public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ Log.d(TAG, "processTransaction");
+ CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+ int currentCallState = mCall.getState();
+
+ // certain calls cannot go active/answered (ex. disconnect calls, etc.)
+ if (!canBecomeNewCallFocus(currentCallState)) {
+ future.complete(new VoipCallTransactionResult(
+ CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE,
+ "CallState cannot be set to active or answered due to current call"
+ + " state being in invalid state"));
+ return future;
+ }
+
+ if (mCallsManager.getActiveCall() != null) {
+ future.complete(new VoipCallTransactionResult(
+ CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE,
+ "Already an active call. Request hold on current active call."));
+ return future;
+ }
+
+ mCallsManager.requestNewCallFocusAndVerify(mCall, new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Boolean result) {
+ Log.d(TAG, "processTransaction: onResult");
+ future.complete(new VoipCallTransactionResult(
+ VoipCallTransactionResult.RESULT_SUCCEED, null));
+ }
+
+ @Override
+ public void onError(CallException exception) {
+ Log.d(TAG, "processTransaction: onError");
+ future.complete(new VoipCallTransactionResult(
+ exception.getCode(), exception.getMessage()));
+ }
+ });
+
+ return future;
+ }
+
+ private boolean isPriorityCallingState(int currentCallState) {
+ return ConnectionServiceFocusManager.PRIORITY_FOCUS_CALL_STATE.contains(currentCallState);
+ }
+
+ private boolean canBecomeNewCallFocus(int currentCallState) {
+ return isPriorityCallingState(currentCallState) || currentCallState == CallState.ON_HOLD;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/voip/SerialTransaction.java b/src/com/android/server/telecom/voip/SerialTransaction.java
index 87bc9ec..4c6d02e 100644
--- a/src/com/android/server/telecom/voip/SerialTransaction.java
+++ b/src/com/android/server/telecom/voip/SerialTransaction.java
@@ -16,14 +16,17 @@
package com.android.server.telecom.voip;
+import com.android.server.telecom.TelecomSystem;
+
import java.util.List;
/**
* A VoipCallTransaction implementation that its sub transactions will be executed in serial
*/
public class SerialTransaction extends VoipCallTransaction {
- public SerialTransaction(List<VoipCallTransaction> subTransactions) {
- super(subTransactions);
+ public SerialTransaction(List<VoipCallTransaction> subTransactions,
+ TelecomSystem.SyncRoot lock) {
+ super(subTransactions, lock);
}
public void appendTransaction(VoipCallTransaction transaction){
diff --git a/src/com/android/server/telecom/voip/TransactionManager.java b/src/com/android/server/telecom/voip/TransactionManager.java
index a0955c8..98faf3d 100644
--- a/src/com/android/server/telecom/voip/TransactionManager.java
+++ b/src/com/android/server/telecom/voip/TransactionManager.java
@@ -16,6 +16,8 @@
package com.android.server.telecom.voip;
+import static android.telecom.CallException.CODE_OPERATION_TIMED_OUT;
+
import android.os.OutcomeReceiver;
import android.telecom.TelecomManager;
import android.telecom.CallException;
@@ -79,8 +81,8 @@
@Override
public void onTransactionTimeout(String transactionName){
- receiver.onResult(new VoipCallTransactionResult(
- VoipCallTransactionResult.RESULT_FAILED, transactionName + " timeout"));
+ receiver.onError(new CallException(transactionName + " timeout",
+ CODE_OPERATION_TIMED_OUT));
finishTransaction();
}
});
diff --git a/src/com/android/server/telecom/voip/VoipCallMonitor.java b/src/com/android/server/telecom/voip/VoipCallMonitor.java
new file mode 100644
index 0000000..d0304a9
--- /dev/null
+++ b/src/com/android/server/telecom/voip/VoipCallMonitor.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.voip;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
+import android.app.Notification;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManagerListenerBase;
+import com.android.server.telecom.LogUtils;
+import com.android.server.telecom.TelecomSystem;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class VoipCallMonitor extends CallsManagerListenerBase {
+
+ private final List<Call> mPendingCalls;
+ // Same notification may be passed as different object in onNotificationPosted and
+ // onNotificationRemoved. Use its string as key to cache ongoing notifications.
+ private final Map<String, Call> mNotifications;
+ private final Map<PhoneAccountHandle, Set<Call>> mPhoneAccountHandleListMap;
+ private ActivityManagerInternal mActivityManagerInternal;
+ private final Map<PhoneAccountHandle, ServiceConnection> mServices;
+ private NotificationListenerService mNotificationListener;
+ private final Object mLock = new Object();
+ private final HandlerThread mHandlerThread;
+ private final Handler mHandler;
+ private final Context mContext;
+ private List<StatusBarNotification> mPendingSBN;
+
+ public VoipCallMonitor(Context context, TelecomSystem.SyncRoot lock) {
+ mContext = context;
+ mHandlerThread = new HandlerThread(this.getClass().getSimpleName());
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mPendingCalls = new ArrayList<>();
+ mPendingSBN = new ArrayList<>();
+ mNotifications = new HashMap<>();
+ mServices = new HashMap<>();
+ mPhoneAccountHandleListMap = new HashMap<>();
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+
+ mNotificationListener = new NotificationListenerService() {
+ @Override
+ public void onNotificationPosted(StatusBarNotification sbn) {
+ synchronized (mLock) {
+ if (sbn.getNotification().isStyle(Notification.CallStyle.class)) {
+ boolean sbnMatched = false;
+ for (Call call : mPendingCalls) {
+ if (notificationMatchedCall(sbn, call)) {
+ mPendingCalls.remove(call);
+ mNotifications.put(sbn.toString(), call);
+ sbnMatched = true;
+ break;
+ }
+ }
+ if (!sbnMatched) {
+ // notification may posted before we started to monitor the call, cache
+ // this notification and try to match it later with new added call.
+ mPendingSBN.add(sbn);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn) {
+ synchronized (mLock) {
+ mPendingSBN.remove(sbn);
+ if (mNotifications.isEmpty()) {
+ return;
+ }
+ Call call = mNotifications.getOrDefault(sbn.toString(), null);
+ if (call != null) {
+ mNotifications.remove(sbn.toString(), call);
+ stopFGSDelegation(call.getTargetPhoneAccount());
+ }
+ }
+ }
+ };
+
+ }
+
+ public void startMonitor() {
+ try {
+ mNotificationListener.registerAsSystemService(mContext,
+ new ComponentName(this.getClass().getPackageName(),
+ this.getClass().getCanonicalName()), ActivityManager.getCurrentUser());
+ } catch (RemoteException e) {
+ Log.e(this, e, "Cannot register notification listener");
+ }
+ }
+
+ public void stopMonitor() {
+ try {
+ mNotificationListener.unregisterAsSystemService();
+ } catch (RemoteException e) {
+ Log.e(this, e, "Cannot unregister notification listener");
+ }
+ }
+
+ @Override
+ public void onCallAdded(Call call) {
+ if (!call.isTransactionalCall()) {
+ return;
+ }
+
+ synchronized (mLock) {
+ PhoneAccountHandle phoneAccountHandle = call.getTargetPhoneAccount();
+ Set<Call> callList = mPhoneAccountHandleListMap.computeIfAbsent(phoneAccountHandle,
+ k -> new HashSet<>());
+ callList.add(call);
+
+ mHandler.post(
+ () -> startFGSDelegation(call.getCallingPackageIdentity().mCallingPackagePid,
+ call.getCallingPackageIdentity().mCallingPackageUid, call));
+ }
+ }
+
+ @Override
+ public void onCallRemoved(Call call) {
+ if (!call.isTransactionalCall()) {
+ return;
+ }
+
+ synchronized (mLock) {
+ stopMonitorWorks(call);
+ PhoneAccountHandle phoneAccountHandle = call.getTargetPhoneAccount();
+ Set<Call> callList = mPhoneAccountHandleListMap.computeIfAbsent(phoneAccountHandle,
+ k -> new HashSet<>());
+ callList.remove(call);
+
+ if (callList.isEmpty()) {
+ stopFGSDelegation(phoneAccountHandle);
+ }
+ }
+ }
+
+ private void startFGSDelegation(int pid, int uid, Call call) {
+ Log.i(this, "startFGSDelegation for call %s", call.getId());
+ if (mActivityManagerInternal != null) {
+ PhoneAccountHandle handle = call.getTargetPhoneAccount();
+ ForegroundServiceDelegationOptions options = new ForegroundServiceDelegationOptions(pid,
+ uid, handle.getComponentName().getPackageName(), null /* clientAppThread */,
+ false /* isSticky */, String.valueOf(handle.hashCode()),
+ 0 /* foregroundServiceType */,
+ ForegroundServiceDelegationOptions.DELEGATION_SERVICE_PHONE_CALL);
+ ServiceConnection fgsConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.addEvent(call, LogUtils.Events.GAINED_FGS_DELEGATION);
+ mServices.put(handle, this);
+ startMonitorWorks(call);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.addEvent(call, LogUtils.Events.LOST_FGS_DELEGATION);
+ mServices.remove(handle);
+ }
+ };
+ try {
+ mActivityManagerInternal.startForegroundServiceDelegate(options, fgsConnection);
+ } catch (Exception e) {
+ Log.i(this, "startForegroundServiceDelegate failed due to: " + e);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public void stopFGSDelegation(PhoneAccountHandle handle) {
+ synchronized (mLock) {
+ Log.i(this, "stopFGSDelegation of handle %s", handle);
+ Set<Call> calls = mPhoneAccountHandleListMap.get(handle);
+ for (Call call : calls) {
+ stopMonitorWorks(call);
+ }
+ mPhoneAccountHandleListMap.remove(handle);
+
+ if (mActivityManagerInternal != null) {
+ ServiceConnection fgsConnection = mServices.get(handle);
+ if (fgsConnection != null) {
+ mActivityManagerInternal.stopForegroundServiceDelegate(fgsConnection);
+ }
+ }
+ }
+ }
+
+ private void startMonitorWorks(Call call) {
+ startMonitorNotification(call);
+ }
+
+ private void stopMonitorWorks(Call call) {
+ stopMonitorNotification(call);
+ }
+
+ private void startMonitorNotification(Call call) {
+ synchronized (mLock) {
+ boolean sbnMatched = false;
+ for (StatusBarNotification sbn : mPendingSBN) {
+ if (notificationMatchedCall(sbn, call)) {
+ mPendingSBN.remove(sbn);
+ mNotifications.put(sbn.toString(), call);
+ sbnMatched = true;
+ break;
+ }
+ }
+ if (!sbnMatched) {
+ // Only continue to
+ mPendingCalls.add(call);
+ mHandler.postDelayed(() -> {
+ synchronized (mLock) {
+ if (mPendingCalls.contains(call)) {
+ Log.i(this, "Notification for voip-call %s haven't "
+ + "posted in time, stop delegation.", call.getId());
+ stopFGSDelegation(call.getTargetPhoneAccount());
+ mPendingCalls.remove(call);
+ }
+ }
+ }, 5000L);
+ }
+ }
+ }
+
+ private void stopMonitorNotification(Call call) {
+ mPendingCalls.remove(call);
+ }
+
+ @VisibleForTesting
+ public void setActivityManagerInternal(ActivityManagerInternal ami) {
+ mActivityManagerInternal = ami;
+ }
+
+ @VisibleForTesting
+ public void setNotificationListenerService(NotificationListenerService listener) {
+ mNotificationListener = listener;
+ }
+
+ private boolean notificationMatchedCall(StatusBarNotification sbn, Call call) {
+ String packageName = sbn.getPackageName();
+ UserHandle userHandle = sbn.getUser();
+ PhoneAccountHandle accountHandle = call.getTargetPhoneAccount();
+
+ return packageName != null &&
+ packageName.equals(call.getTargetPhoneAccount()
+ .getComponentName().getPackageName())
+ && userHandle != null
+ && userHandle.equals(accountHandle.getUserHandle());
+ }
+}
diff --git a/src/com/android/server/telecom/voip/VoipCallTransaction.java b/src/com/android/server/telecom/voip/VoipCallTransaction.java
index 413a1cd..a1cc13c 100644
--- a/src/com/android/server/telecom/voip/VoipCallTransaction.java
+++ b/src/com/android/server/telecom/voip/VoipCallTransaction.java
@@ -20,6 +20,7 @@
import android.os.HandlerThread;
import com.android.server.telecom.LoggedHandlerExecutor;
+import com.android.server.telecom.TelecomSystem;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -36,17 +37,19 @@
protected Handler mHandler;
protected TransactionManager.TransactionCompleteListener mCompleteListener;
protected List<VoipCallTransaction> mSubTransactions;
+ private TelecomSystem.SyncRoot mLock;
public VoipCallTransaction(
- List<VoipCallTransaction> subTransactions) {
+ List<VoipCallTransaction> subTransactions, TelecomSystem.SyncRoot lock) {
mSubTransactions = subTransactions;
mHandlerThread = new HandlerThread(this.toString());
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+ mLock = lock;
}
- public VoipCallTransaction() {
- this(null /** mSubTransactions */);
+ public VoipCallTransaction(TelecomSystem.SyncRoot lock) {
+ this(null /** mSubTransactions */, lock);
}
public void start() {
@@ -68,7 +71,7 @@
CompletableFuture<Void> future = CompletableFuture.completedFuture(null);
future.thenComposeAsync(this::processTransaction,
new LoggedHandlerExecutor(mHandler, mTransactionName + "@"
- + hashCode() + ".pT", null))
+ + hashCode() + ".pT", mLock))
.thenApplyAsync(
(Function<VoipCallTransactionResult, Void>) result -> {
mCompleted.set(true);
diff --git a/testapps/transactionalVoipApp/AndroidManifest.xml b/testapps/transactionalVoipApp/AndroidManifest.xml
index d0aa50b..e4968db 100644
--- a/testapps/transactionalVoipApp/AndroidManifest.xml
+++ b/testapps/transactionalVoipApp/AndroidManifest.xml
@@ -15,13 +15,20 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- coreApp="true"
- package="com.android.server.telecom.transactionalVoipApp">
+ coreApp="true"
+ package="com.android.server.telecom.transactionalVoipApp">
<uses-sdk android:minSdkVersion="28"
- android:targetSdkVersion="33"/>
+ android:targetSdkVersion="33"/>
- <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
+ <uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/>
+ <!-- Needed to test media/audio -->
+ <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+ <!-- Needed for foreground services -->
+ <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL"/>
<application android:label="Transactional Voip">
<uses-library android:name="android.test.runner"/>
@@ -30,10 +37,26 @@
android:exported="true"
android:label="Transactional Voip">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name="com.android.server.telecom.transactionalVoipApp.InCallActivity"
+ android:exported="true"
+ android:launchMode="singleInstance"
+ android:label="InCall VoIP Activity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <service
+ android:name=".BackgroundIncomingCallService"
+ android:foregroundServiceType="phoneCall"
+ android:exported="false"
+ />
+
</application>
</manifest>
diff --git a/testapps/transactionalVoipApp/res/layout/in_call_activity.xml b/testapps/transactionalVoipApp/res/layout/in_call_activity.xml
new file mode 100644
index 0000000..54d467e
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/layout/in_call_activity.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/getCallIdTextView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/get_call_id"
+ />
+
+ <Button
+ android:id="@+id/answer_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/answer"/>
+
+ <Button
+ android:id="@+id/set_call_active_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/set_call_active"/>
+
+ <Button
+ android:id="@+id/set_call_inactive_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/set_call_inactive"/>
+
+ <Button
+ android:id="@+id/disconnect_call_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/disconnect_call"/>
+
+ <Button
+ android:id="@+id/start_stream_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/start_stream"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/current_endpoint"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/request_earpiece"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/request_earpiece_endpoint"/>
+
+ <Button
+ android:id="@+id/request_speaker"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/request_speaker_endpoint"/>
+
+ <Button
+ android:id="@+id/request_bluetooth"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/request_bluetooth_endpoint"/>
+ </LinearLayout>
+
+ </LinearLayout>
+</LinearLayout>
diff --git a/testapps/transactionalVoipApp/res/layout/main_activity.xml b/testapps/transactionalVoipApp/res/layout/main_activity.xml
index 86d8e20..28f0744 100644
--- a/testapps/transactionalVoipApp/res/layout/main_activity.xml
+++ b/testapps/transactionalVoipApp/res/layout/main_activity.xml
@@ -38,53 +38,31 @@
android:layout_height="wrap_content"
android:text="@string/register_phone_account"/>
- <ToggleButton
- android:id="@+id/callDirectionButton"
+ <Button
+ android:id="@+id/startForegroundService"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textOff="@string/direction_outgoing"
- android:textOn="@string/direction_incoming"
+ android:text="@string/start_foreground_service"
/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
- <Button
- android:id="@+id/add_call_1_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/add_call_1"/>
<Button
- android:id="@+id/disconnect_call_1_button"
+ android:id="@+id/startOutgoingCall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/disconnect_call_1"/>
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
+ android:text="@string/start_outgoing"
+ />
<Button
- android:id="@+id/add_call_2_button"
+ android:id="@+id/startIncomingCall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/add_call_2"/>
-
- <Button
- android:id="@+id/set_call_2_active_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/set_call_active"/>
-
- <Button
- android:id="@+id/disconnect_call_2_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/disconnect_call_2"/>
+ android:text="@string/start_incoming"
+ />
</LinearLayout>
</LinearLayout>
</LinearLayout>
diff --git a/testapps/transactionalVoipApp/res/raw/sample_audio.ogg b/testapps/transactionalVoipApp/res/raw/sample_audio.ogg
new file mode 100644
index 0000000..0129b46
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/raw/sample_audio.ogg
Binary files differ
diff --git a/testapps/transactionalVoipApp/res/raw/sample_audio2.ogg b/testapps/transactionalVoipApp/res/raw/sample_audio2.ogg
new file mode 100644
index 0000000..a0b39b4
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/raw/sample_audio2.ogg
Binary files differ
diff --git a/testapps/transactionalVoipApp/res/values-af/strings.xml b/testapps/transactionalVoipApp/res/values-af/strings.xml
index 3e12b43..78abd1b 100644
--- a/testapps/transactionalVoipApp/res/values-af/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-af/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Transactional API-toetsaktiwiteit"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Transaksionele inoproepaktiwiteit"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Registreer foonrekening"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"uitgaande"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"inkomend"</string>
- <string name="add_call_1" msgid="5825706540046010457">"voeg oproep 1 by"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"beëindig oproep 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"voeg oproep 2 by"</string>
- <string name="set_call_active" msgid="248748409907478011">"stel oproep 2 as aktief"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"beëindig oproep 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Begin voorgronddiens (simuleer masjienvertaling + app op agtergrond)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Begin uitgaande oproep"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Begin inkomende oproep"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"oproep-id is nie gestel nie"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"antwoord"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"ontkoppel"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Oorstuk"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Luidspreker"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"begin stroom"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-am/strings.xml b/testapps/transactionalVoipApp/res/values-am/strings.xml
index 9aba40a..2766bf8 100644
--- a/testapps/transactionalVoipApp/res/values-am/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-am/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"የግብይት ኤፒአይ ሙከራ እንቅስቃሴ"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"በጥሪ እንቅስቃሴ ውስጥ ግብይታዊ"</string>
<string name="register_phone_account" msgid="1920315963082350332">"የስልክ መለያ መዝግብ"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"ወጪ"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"መጪ"</string>
- <string name="add_call_1" msgid="5825706540046010457">"ጥሪ 1ን አክል"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"የጥሪ 1ን ግንኙነት አቋርጥ"</string>
- <string name="add_call_2" msgid="6706005258041717434">"ጥሪ 2ን አክል"</string>
- <string name="set_call_active" msgid="248748409907478011">"ጥሪ 2ን ወደ ንቁ አቀናብር"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"የጥሪ 2ን ግንኙነት አቋርጥ"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS ይጀምሩ (በዳራው ውስጥ MT + መተግበሪያን ያስመስላል)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"ወጪ ጥሪን ይጀምሩ"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"ገቢ ጥሪን ይጀምሩ"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"የደዋይ መታወቂያ አልተቀናበረም"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"ወደ ገቢር ተቀናብሯል"</string>
+ <string name="answer" msgid="5423590397665409939">"መልስ"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"ወደ ገቢር ያልሆነ ተቀናብሯል"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"ግንኙነትን ያቋርጡ"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ማዳመጫ"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"ድምጽ ማውጫ"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ብሉቱዝ"</string>
+ <string name="start_stream" msgid="3567634786280097431">"ዥረት ይጀምሩ"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ar/strings.xml b/testapps/transactionalVoipApp/res/values-ar/strings.xml
index c76746b..8a42e30 100644
--- a/testapps/transactionalVoipApp/res/values-ar/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ar/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"نشاط اختبار واجهة برمجة التطبيقات من خلال المعاملات"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"نشاط المعاملات أثناء المكالمة"</string>
<string name="register_phone_account" msgid="1920315963082350332">"تسجيل حساب الهاتف"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"الصادرة"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"الواردة"</string>
- <string name="add_call_1" msgid="5825706540046010457">"إضافة المكالمة 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"قطع المكالمة 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"إضافة المكالمة 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"ضبط حالة المكالمة 2 على نشطة"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"قطع المكالمة 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"بدء FGS (محاكاة الترجمة الآلية + التطبيق في الخلفية)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"بدء مكالمة صادرة"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"بدء مكالمة واردة"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"لم يتم ضبط رقم تعريف المكالمة"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"الإجابة"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"إلغاء الربط"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"سماعة الأذن"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"مكبّر الصوت"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"البلوتوث"</string>
+ <string name="start_stream" msgid="3567634786280097431">"بدء البث"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-as/strings.xml b/testapps/transactionalVoipApp/res/values-as/strings.xml
index 66fae5f..56014c4 100644
--- a/testapps/transactionalVoipApp/res/values-as/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-as/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"লেনদেন সম্বন্ধীয় API পৰীক্ষণৰ কাৰ্যকলাপ"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"কলত হোৱা লেনদেন সম্বন্ধীয় কাৰ্যকলাপ"</string>
<string name="register_phone_account" msgid="1920315963082350332">"ফ\'নৰ একাউণ্ট পঞ্জীয়ন কৰক"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"বহিৰ্গামী"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"অন্তৰ্গামী"</string>
- <string name="add_call_1" msgid="5825706540046010457">"কল ১ যোগ কৰক"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"কল ১ৰ সংযোগ বিচ্ছিন্ন কৰক"</string>
- <string name="add_call_2" msgid="6706005258041717434">"কল ২ যোগ কৰক"</string>
- <string name="set_call_active" msgid="248748409907478011">"কল ২ক সক্ৰিয় হিচাপে ছেট কৰক"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"কল ২ৰ সংযোগ বিচ্ছিন্ন কৰক"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS আৰম্ভ কৰক (নেপথ্যত MT + এপ্ ছিমুলে’ট কৰক)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"বহিৰ্গামী কল আৰম্ভ কৰক"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"অন্তৰ্গামী কল আৰম্ভ কৰক"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"কলৰ আইডিটো ছেট কৰা হোৱা নাই"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"সক্ৰিয় হিচাপে ছেট কৰক"</string>
+ <string name="answer" msgid="5423590397665409939">"উত্তৰ দিয়ক"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"নিষ্ক্ৰিয় হিচাপে ছেট কৰক"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"সংযোগ বিচ্ছিন্ন কৰক"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ইয়েৰপিচ"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"স্পীকাৰ"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ব্লুটুথ"</string>
+ <string name="start_stream" msgid="3567634786280097431">"ষ্ট্ৰীম কৰিবলৈ আৰম্ভ কৰক"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-az/strings.xml b/testapps/transactionalVoipApp/res/values-az/strings.xml
index 1bdb545..14af0ab 100644
--- a/testapps/transactionalVoipApp/res/values-az/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-az/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Tranzaksiya ilə bağlı API test Fəaliyyəti"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Tranzaksiya üzrə Zəngdaxili Fəaliyyət"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Telefon Hesabını Qeydiyyatdan Keçirin"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"gedən"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"gələn"</string>
- <string name="add_call_1" msgid="5825706540046010457">"1-ci zəngi əlavə edin"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"1-ci zəngi bitirin"</string>
- <string name="add_call_2" msgid="6706005258041717434">"2-ci zəngi əlavə edin"</string>
- <string name="set_call_active" msgid="248748409907478011">"2-ci zəngi aktivləşdirin"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"2-ci zəngi bitirin"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS-ni başladın (arxa fonda MT + tətbiqini simulyasiya edin)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Gedən zəng başladın"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Gələn zəng başladın"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"zəng ID-si təyin olunmayıb"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"Aktiv kimi təyin edin"</string>
+ <string name="answer" msgid="5423590397665409939">"cavab"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"Qeyri-aktiv kimi təyin edin"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"əlaqəni kəsin"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Qulaqlıq"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Dinamik"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"yayıma başlayın"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-b+sr+Latn/strings.xml b/testapps/transactionalVoipApp/res/values-b+sr+Latn/strings.xml
index ad4605d..3c4019c 100644
--- a/testapps/transactionalVoipApp/res/values-b+sr+Latn/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-b+sr+Latn/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Aktivnost testiranja transakcionog API-ja"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Aktivnost poziva u vezi sa transakcijama"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Registruj nalog telefona"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"odlazni"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"dolazni"</string>
- <string name="add_call_1" msgid="5825706540046010457">"dodaj 1. poziv"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"prekini 1. poziv"</string>
- <string name="add_call_2" msgid="6706005258041717434">"dodaj 2. poziv"</string>
- <string name="set_call_active" msgid="248748409907478011">"podesi 2. poziv kao aktivan"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"prekini 2. poziv"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Pokreni FGS (simulirajte MT + aplikaciju u pozadini)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Započnite odlazni poziv"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Započnite dolazni poziv"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"ID poziva nije podešen"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"odgovori"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"prekini vezu"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Slušalica"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Zvučnik"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"počnite da strimujete"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-be/strings.xml b/testapps/transactionalVoipApp/res/values-be/strings.xml
index ecb1464..9decf62 100644
--- a/testapps/transactionalVoipApp/res/values-be/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-be/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Праверачныя дзеянні API трансакцый"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Дзеянні падчас выклікаў"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Зарэгістраваць уліковы запіс тэлефона"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"выходны"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"уваходны"</string>
- <string name="add_call_1" msgid="5825706540046010457">"дадаць выклік 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"завяршыць выклік 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"дадаць выклік 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"зрабіць выклік 2 актыўным"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"завяршыць выклік 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Запусціць FGS (сімуляцыя MT + праграма ў фонавым рэжыме)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Пачаць выходны выклік"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Пачаць уваходны выклік"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"ідэнтыфікатар выкліку не зададзены"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"адказаць"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"завяршыць выклік"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Навушнік"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Дынамік"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"пачаць перадачу плынню"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-bg/strings.xml b/testapps/transactionalVoipApp/res/values-bg/strings.xml
index c822ad5..63b55f9 100644
--- a/testapps/transactionalVoipApp/res/values-bg/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-bg/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Активност за тестване на API за транзакции"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Транзакционална активност в обаждане"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Регистриране на профила на телефона"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"изходящо"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"входящо"</string>
- <string name="add_call_1" msgid="5825706540046010457">"добавяне на обаждане 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"прекратяване на обаждане 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"добавяне на обаждане 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"задаване на обаждане 2 като активно"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"прекратяване на обаждане 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Стартиране на FGS (симулиране на MT + приложението на заден план)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Започване на изходящо обаждане"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Започване на входящо обаждане"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"идентификаторът на обаждането не е зададен"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"отговаряне"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"прекратяване на връзката"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Слушалка"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Високоговорител"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"започване на поточно предаване"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-bn/strings.xml b/testapps/transactionalVoipApp/res/values-bn/strings.xml
index af4ecbc..b03123a 100644
--- a/testapps/transactionalVoipApp/res/values-bn/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-bn/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Transactional API টেস্ট সংক্রান্ত অ্যাক্টিভিটি"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"কল অ্যাক্টিভিটিতে হওয়া ট্রানজ্যাকশন"</string>
<string name="register_phone_account" msgid="1920315963082350332">"ফোনের অ্যাকাউন্ট রেজিস্টার করুন"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"আউটগোয়িং"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"ইনকামিং"</string>
- <string name="add_call_1" msgid="5825706540046010457">"কল ১ যোগ করুন"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"কল ১ ডিসকানেক্ট করুন"</string>
- <string name="add_call_2" msgid="6706005258041717434">"কল ২ যোগ করুন"</string>
- <string name="set_call_active" msgid="248748409907478011">"কল ২ চালু আছে হিসেবে সেট করুন"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"কল ২ ডিসকানেক্ট করুন"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS শুরু করুন (সিমুলেট MT + ব্যাকগ্রাউন্ডে থাকা অ্যাপ)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"আউটগোয়িং কল শুরু করুন"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"ইনকামিং কল শুরু করুন"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"কলার আইডি সেট করা নেই"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"উত্তর দিন"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"ডিসকানেক্ট করুন"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ইয়ারপিস"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"স্পিকার"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ব্লুটুথ"</string>
+ <string name="start_stream" msgid="3567634786280097431">"স্ট্রিমিং শুরু করুন"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-bs/strings.xml b/testapps/transactionalVoipApp/res/values-bs/strings.xml
index 96cedc5..e4cbb08 100644
--- a/testapps/transactionalVoipApp/res/values-bs/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-bs/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Aktivnost testa transakcijskog API-ja"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Transakcijska aktivnost u pozivu"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Registrirajte račun telefona"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"odlazno"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"dolazno"</string>
- <string name="add_call_1" msgid="5825706540046010457">"dodaj poziv 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"prekini poziv 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"dodaj poziv 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"postavi poziv 2 kao aktivan"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"prekini poziv 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Pokreni FGS (simuliraj MT i aplikaciju u pozadini)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Pokreni odlazni poziv"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Pokreni dolazni poziv"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"ID poziva nije postavljen"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"postavi na Aktivno"</string>
+ <string name="answer" msgid="5423590397665409939">"odgovori"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"postavi na Neaktivno"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"prekini vezu"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Slušalica"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Zvučnik"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"pokreni prijenos"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ca/strings.xml b/testapps/transactionalVoipApp/res/values-ca/strings.xml
index ab16064..6780882 100644
--- a/testapps/transactionalVoipApp/res/values-ca/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ca/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Activitat de prova de l\'API transaccional"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Activitat de transaccions durant la trucada"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Registra el compte del telèfon"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"sortint"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"entrant"</string>
- <string name="add_call_1" msgid="5825706540046010457">"afegeix la trucada 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"desconnecta la trucada 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"afegeix la trucada 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"defineix la trucada 2 com a activa"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"desconnecta la trucada 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Inicia FGS (simula MT + aplicació en segon pla)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Inicia una trucada sortint"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Inicia una trucada entrant"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"identificador de trucada no definit"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"defineix com a activa"</string>
+ <string name="answer" msgid="5423590397665409939">"respon"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"defineix com a inactiva"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"desconnecta"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Auricular"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Altaveu"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"inicia la reproducció en continu"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-cs/strings.xml b/testapps/transactionalVoipApp/res/values-cs/strings.xml
index b7be57f..46a938b 100644
--- a/testapps/transactionalVoipApp/res/values-cs/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-cs/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Aktivita testování v transakčním rozhraní API"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Transakční aktivita během hovoru"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Registrovat telefonní účet"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"odchozí"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"příchozí"</string>
- <string name="add_call_1" msgid="5825706540046010457">"přidat hovor 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"odpojit hovor 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"přidat hovor 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"nastavit hovor 2 jako aktivní"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"odpojit hovor 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Spustit službu v popředí (simulovat MT a aplikaci v pozadí)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Zahájit odchozí hovor"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Zahájit příchozí hovor"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"ID hovoru není nastaveno"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"odpověď"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"odpojit"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Sluchátko"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Reproduktor"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"zahájit streamování"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-da/strings.xml b/testapps/transactionalVoipApp/res/values-da/strings.xml
index 46e6a33..e857f3e 100644
--- a/testapps/transactionalVoipApp/res/values-da/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-da/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Testaktivitet for transaktions-API"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Transaktionsrelateret aktivitet i opkald"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Registrer telefonkonto"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"udgående"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"indgående"</string>
- <string name="add_call_1" msgid="5825706540046010457">"tilføj opkald 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"afslut opkald 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"tilføj opkald 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"konfigurer opkald 2 som aktivt"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"afslut opkald 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Start FGS (simuler maskinoversættelse + app i baggrunden)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Start udgående opkald"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Start indgående opkald"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"opkalds-id ikke konfigureret"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"Indstil som aktiv"</string>
+ <string name="answer" msgid="5423590397665409939">"svar"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"Indstil som inaktiv"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"afslut opkald"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Højttaler"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Højttaler"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"start med at streame"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-de/strings.xml b/testapps/transactionalVoipApp/res/values-de/strings.xml
index b7d04e0..cf3116c 100644
--- a/testapps/transactionalVoipApp/res/values-de/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-de/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Testaktivität zur transaktionalen API"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Transaktionsaktivität bei aktiven Anruf"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Telefonkonto registrieren"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"Ausgehend"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"Eingehend"</string>
- <string name="add_call_1" msgid="5825706540046010457">"Anruf 1 hinzufügen"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"Anruf 1 trennen"</string>
- <string name="add_call_2" msgid="6706005258041717434">"Anruf 2 hinzufügen"</string>
- <string name="set_call_active" msgid="248748409907478011">"Anruf 2 aktivieren"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"Anruf 2 trennen"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS starten (MT und App im Hintergrund simulieren)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Ausgehenden Anruf starten"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Eingehenden Anruf starten"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"Anrufer-ID nicht festgelegt"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"aktiv"</string>
+ <string name="answer" msgid="5423590397665409939">"annehmen"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"inaktiv"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"beenden"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Kopfhörer"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Lautsprecher"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"Streaming starten"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-el/strings.xml b/testapps/transactionalVoipApp/res/values-el/strings.xml
index bb5b0d9..d838d2e 100644
--- a/testapps/transactionalVoipApp/res/values-el/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-el/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Δοκιμαστική δραστηριότητα API συναλλαγών"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Δραστηριότητα συναλλαγής στην κλήση"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Εγγραφή λογαριασμού τηλεφώνου"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"εξερχόμενη"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"εισερχόμενη"</string>
- <string name="add_call_1" msgid="5825706540046010457">"προσθήκη κλήσης 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"αποσύνδεση κλήσης 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"προσθήκη κλήσης 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"ορισμός κλήσης 2 ως ενεργής"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"αποσύνδεση κλήσης 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Έναρξη FGS (προσομοίωση MT + εφαρμογή στο παρασκήνιο)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Έναρξη εξερχόμενης κλήσης"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Έναρξη εισερχόμενης κλήσης"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"δεν έχει οριστεί αναγνωριστικό κλήσης"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"απάντηση"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"αποσύνδεση"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Ακουστικό τηλεφώνου"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Ηχείο"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"έναρξη ροής"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-en-rAU/strings.xml b/testapps/transactionalVoipApp/res/values-en-rAU/strings.xml
index 95c71e4..5bfa1a1 100644
--- a/testapps/transactionalVoipApp/res/values-en-rAU/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-en-rAU/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Transactional API test activity"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Transactional in-call activity"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Register phone account"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"outgoing"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"incoming"</string>
- <string name="add_call_1" msgid="5825706540046010457">"add call 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"disconnect call 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"add call 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"set call 2 active"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"disconnect call 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Start FGS (simulate MT + app in background)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Start outgoing call"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Start incoming call"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"call ID not set"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"answer"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"disconnect"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Earpiece"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Speaker"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"start streaming"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-en-rCA/strings.xml b/testapps/transactionalVoipApp/res/values-en-rCA/strings.xml
index 0c705e3..1014001 100644
--- a/testapps/transactionalVoipApp/res/values-en-rCA/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-en-rCA/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Transactional API test Activity"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Transactional In Call Activity"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Register Phone Account"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"outgoing"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"incoming"</string>
- <string name="add_call_1" msgid="5825706540046010457">"add call 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"disconnect call 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"add call 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"set call 2 active"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"disconnect call 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Start FGS (simulate MT + app in background)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Start Outgoing Call"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Start Incoming Call"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"call id not set"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"answer"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"disconnect"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Earpiece"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Speaker"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"start streaming"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-en-rGB/strings.xml b/testapps/transactionalVoipApp/res/values-en-rGB/strings.xml
index 95c71e4..5bfa1a1 100644
--- a/testapps/transactionalVoipApp/res/values-en-rGB/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-en-rGB/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Transactional API test activity"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Transactional in-call activity"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Register phone account"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"outgoing"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"incoming"</string>
- <string name="add_call_1" msgid="5825706540046010457">"add call 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"disconnect call 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"add call 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"set call 2 active"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"disconnect call 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Start FGS (simulate MT + app in background)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Start outgoing call"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Start incoming call"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"call ID not set"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"answer"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"disconnect"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Earpiece"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Speaker"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"start streaming"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-en-rIN/strings.xml b/testapps/transactionalVoipApp/res/values-en-rIN/strings.xml
index 95c71e4..5bfa1a1 100644
--- a/testapps/transactionalVoipApp/res/values-en-rIN/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-en-rIN/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Transactional API test activity"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Transactional in-call activity"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Register phone account"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"outgoing"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"incoming"</string>
- <string name="add_call_1" msgid="5825706540046010457">"add call 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"disconnect call 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"add call 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"set call 2 active"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"disconnect call 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Start FGS (simulate MT + app in background)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Start outgoing call"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Start incoming call"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"call ID not set"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"answer"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"disconnect"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Earpiece"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Speaker"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"start streaming"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-en-rXC/strings.xml b/testapps/transactionalVoipApp/res/values-en-rXC/strings.xml
index 2e69f39..40b0016 100644
--- a/testapps/transactionalVoipApp/res/values-en-rXC/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-en-rXC/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Transactional API test Activity"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Transactional In Call Activity"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Register Phone Account"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"outgoing"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"incoming"</string>
- <string name="add_call_1" msgid="5825706540046010457">"add call 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"disconnect call 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"add call 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"set call 2 active"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"disconnect call 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Start FGS (simulate MT + app in background)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Start Outgoing Call"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Start Incoming Call"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"call id not set"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"answer"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"disconnect"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Earpiece"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Speaker"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"start streaming"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-es-rUS/strings.xml b/testapps/transactionalVoipApp/res/values-es-rUS/strings.xml
index 06098c4..3410a16 100644
--- a/testapps/transactionalVoipApp/res/values-es-rUS/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-es-rUS/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Actividad de prueba de la API transaccional"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Actividad transaccional en las llamadas"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Registrar cuenta telefónica"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"saliente"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"entrante"</string>
- <string name="add_call_1" msgid="5825706540046010457">"agregar llamada 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"desconectar la llamada 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"agregar llamada 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"establecer llamada 2 activa"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"desconectar la llamada 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Iniciar FGS (simulación de TA y app en segundo plano)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Iniciar llamada saliente"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Iniciar llamada entrante"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"no se estableció el identificador de llamadas"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"responder"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"desconectar"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Auricular"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Bocina"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"Iniciar transmisión"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-es/strings.xml b/testapps/transactionalVoipApp/res/values-es/strings.xml
index 1391d3c..2ce1e81 100644
--- a/testapps/transactionalVoipApp/res/values-es/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-es/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Actividad de prueba de API transaccional"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Actividad transaccional durante la llamada"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Registrar cuenta de teléfono"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"saliente"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"entrante"</string>
- <string name="add_call_1" msgid="5825706540046010457">"añadir llamada 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"desconectar llamada 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"añadir llamada 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"activar llamada 2"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"desconectar llamada 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Iniciar FGS (simular MT + aplicación en segundo plano)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Iniciar llamada saliente"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Iniciar llamada entrante"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"identificador de llamada no definido"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"Activar"</string>
+ <string name="answer" msgid="5423590397665409939">"responder"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"Desactivar"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"desconectar"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Auricular"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Altavoz"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"iniciar emisión"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-et/strings.xml b/testapps/transactionalVoipApp/res/values-et/strings.xml
index c2dbe2e..477dec5 100644
--- a/testapps/transactionalVoipApp/res/values-et/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-et/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Tehingupõhise API testimise tegevus"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Kõnesisene toimingutegevus"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Telefonikonto registreerimine"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"väljaminevad"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"sissetulevad"</string>
- <string name="add_call_1" msgid="5825706540046010457">"lisa kõne 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"lõpeta kõne 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"lisa kõne 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"kõne 2 aktiivseks seadmine"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"lõpeta kõne 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Käivita FGS (simuleeri taustal MT-d ja rakendust)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Alusta väljuvat kõnet"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Alusta sissetulevat kõnet"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"helistaja ID pole seadistatud"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"vastus"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"katkesta ühendus"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Kuular"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Kõlar"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"käivita voogesitus"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-eu/strings.xml b/testapps/transactionalVoipApp/res/values-eu/strings.xml
index f3b0406..962346f 100644
--- a/testapps/transactionalVoipApp/res/values-eu/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-eu/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Transakzio bidezko APIen proba-jarduerak"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Deiko transakzio-jarduerak"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Erregistratu telefonoaren kontua"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"irteerakoa"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"sarrerakoa"</string>
- <string name="add_call_1" msgid="5825706540046010457">"gehitu 1. deia"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"deskonektatu 1. deia"</string>
- <string name="add_call_2" msgid="6706005258041717434">"gehitu 2. deia"</string>
- <string name="set_call_active" msgid="248748409907478011">"ezarri 2. deia aktibo gisa"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"deskonektatu 2. deia"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Hasi FGS (simulatu itzulpen automatikoa + aplikazioa atzeko planoan)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Hasi irteerako dei bat simulatzen"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Hasi sarrerako dei bat simulatzen"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"ez da ezarri deiaren identifikatzailea"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"erantzun"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"deskonektatu"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Aurikularrak"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Bozgorailua"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetootha"</string>
+ <string name="start_stream" msgid="3567634786280097431">"hasi zuzenean igortzen"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-fa/strings.xml b/testapps/transactionalVoipApp/res/values-fa/strings.xml
index 634f55e..bd9cddf 100644
--- a/testapps/transactionalVoipApp/res/values-fa/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-fa/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"فعالیت آزمایشی Transactional API"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"تبادلی در فعالیت تماس"</string>
<string name="register_phone_account" msgid="1920315963082350332">"ثبت حساب تلفن"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"خروجی"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"ورودی"</string>
- <string name="add_call_1" msgid="5825706540046010457">"افزودن تماس ۱"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"قطع تماس ۱"</string>
- <string name="add_call_2" msgid="6706005258041717434">"افزودن تماس ۲"</string>
- <string name="set_call_active" msgid="248748409907478011">"تنظیم تماس ۲ روی حالت فعال"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"قطع تماس ۲"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"شروع FGS (شبیهسازی ترجمه ماشینی + برنامه در پسزمینه)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"شروع تماس خروجی"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"شروع تماس ورودی"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"شناسه تماس تنظیم نشده است"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"تنظیم بهعنوان فعال"</string>
+ <string name="answer" msgid="5423590397665409939">"پاسخ"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"تنظیم بهعنوان غیرفعال"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"قطع ارتباط"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"گوشی"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"بلندگو"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"بلوتوث"</string>
+ <string name="start_stream" msgid="3567634786280097431">"شروع جاریسازی"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-fi/strings.xml b/testapps/transactionalVoipApp/res/values-fi/strings.xml
index 6c9c5f7..c95efcb 100644
--- a/testapps/transactionalVoipApp/res/values-fi/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-fi/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Tapahtuman API-testitoiminta"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Tapahtuman puhelunaikainen toiminta"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Rekisteröi puhelintili"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"lähtevä"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"saapuva"</string>
- <string name="add_call_1" msgid="5825706540046010457">"lisää puhelu 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"katkaise puhelu 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"lisää puhelu 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"aseta puhelu 2 aktiiviseksi"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"katkaise puhelu 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Käynnistä FGS (simuloi MT + sovellus taustalla)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Aloita lähtevä puhelu"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Aloita saapuva puhelu"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"soittajan tunnusta ei asetettu"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"aseta aktiiviseksi"</string>
+ <string name="answer" msgid="5423590397665409939">"vastaa"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"aseta ei-aktiiviseksi"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"katkaise yhteys"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Kaiutin"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Kaiutin"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"aloita suoratoisto"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-fr-rCA/strings.xml b/testapps/transactionalVoipApp/res/values-fr-rCA/strings.xml
index 5c9a397..64df91c 100644
--- a/testapps/transactionalVoipApp/res/values-fr-rCA/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-fr-rCA/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Activité de test de l\'API transactionnelle"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Activité transactionnelle durant l\'appel"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Inscrire un compte téléphonique"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"sortant"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"entrant"</string>
- <string name="add_call_1" msgid="5825706540046010457">"ajouter l\'appel 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"déconnecter l\'appel 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"ajouter l\'appel 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"définir l\'appel 2 comme actif"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"déconnecter l\'appel 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Démarrer FGS (simuler TA + application en arrière-plan)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Démarrer un appel sortant"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Démarrer un appel entrant"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"identifiant de l\'appel non défini"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"répondre"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"déconnecter"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Écouteur"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Haut-parleur"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"démarrer une diffusion"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-fr/strings.xml b/testapps/transactionalVoipApp/res/values-fr/strings.xml
index 48d8062..f1d1bd7 100644
--- a/testapps/transactionalVoipApp/res/values-fr/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-fr/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Activité de test de l\'API transactionnelle"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Activité transactionnelle en cours d\'appel"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Enregistrer un compte de téléphonie"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"sortant"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"entrant"</string>
- <string name="add_call_1" msgid="5825706540046010457">"ajouter un appel 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"mettre fin à l\'appel 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"ajouter un appel 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"définir l\'appel 2 comme actif"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"mettre fin à l\'appel 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Démarrer les services de premier plan (simuler la MT + l\'application en arrière-plan)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Démarrer un appel sortant"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Démarrer un appel entrant"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"affichage du numéro de l\'appelant non défini"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"Définir comme actif"</string>
+ <string name="answer" msgid="5423590397665409939">"réponse"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"Définir comme inactif"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"raccrocher"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Écouteur"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Haut-parleur"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"démarrer la diffusion"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-gl/strings.xml b/testapps/transactionalVoipApp/res/values-gl/strings.xml
index 70cc9f6..76fbb34 100644
--- a/testapps/transactionalVoipApp/res/values-gl/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-gl/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Actividade de proba da API transaccional"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Actividade transaccional nas chamadas"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Rexistrar conta do teléfono"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"saínte"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"entrante"</string>
- <string name="add_call_1" msgid="5825706540046010457">"engadir chamada 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"desconectar chamada 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"engadir chamada 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"definir chamada 2 como activa"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"desconectar chamada 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Iniciar FGS (simular MT + aplicación en segundo plano)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Iniciar chamada saínte"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Iniciar chamada entrante"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"identificador de chamada non definido"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"responder"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"desconectar"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Auricular"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Altofalante"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"iniciar reprodución en tempo real"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-gu/strings.xml b/testapps/transactionalVoipApp/res/values-gu/strings.xml
index d9935ae..b0066da 100644
--- a/testapps/transactionalVoipApp/res/values-gu/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-gu/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Transactional APIના પરીક્ષણની પ્રવૃત્તિ"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"કૉલમાંની વ્યવહારિક પ્રવૃત્તિ"</string>
<string name="register_phone_account" msgid="1920315963082350332">"ફોન એકાઉન્ટ રજિસ્ટર કરો"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"આઉટગોઇંગ"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"ઇનકમિંગ"</string>
- <string name="add_call_1" msgid="5825706540046010457">"કૉલ 1 ઉમેરો"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"કૉલ 1 ડિસ્કનેક્ટ કરો"</string>
- <string name="add_call_2" msgid="6706005258041717434">"કૉલ 2 ઉમેરો"</string>
- <string name="set_call_active" msgid="248748409907478011">"કૉલ 2ને સક્રિય પર સેટ કરો"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"કૉલ 2 ડિસ્કનેક્ટ કરો"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS (MT સિમ્યુલેટ કરવું + બૅકગ્રાઉન્ડમાં ઍપ) શરૂ કરો"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"આઉટગોઇંગ કૉલ શરૂ કરો"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"ઇનકમિંગ કૉલ શરૂ કરો"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"કૉલર ID સેટ કરેલું નથી"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"સક્રિય તરીકે સેટ કરો"</string>
+ <string name="answer" msgid="5423590397665409939">"જવાબ"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"નિષ્ક્રિય તરીકે સેટ કરો"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"ડિસ્કનેક્ટ કરો"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ઇયરપીસ"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"સ્પીકર"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"બ્લૂટૂથ"</string>
+ <string name="start_stream" msgid="3567634786280097431">"સ્ટ્રીમિંગ શરૂ કરો"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-hi/strings.xml b/testapps/transactionalVoipApp/res/values-hi/strings.xml
index 9121a37..a6e4a10 100644
--- a/testapps/transactionalVoipApp/res/values-hi/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-hi/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Transactional API से जुड़ी टेस्ट गतिविधि"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"कॉल में क्लाइंट और सर्वर के बीच हुई बातचीत से जुड़ी गतिविधि"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Phone Account में रजिस्टर करें"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"किए जाने वाले (आउटगोइंग) कॉल"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"आने वाले (इनकमिंग) कॉल"</string>
- <string name="add_call_1" msgid="5825706540046010457">"कॉल 1 जोड़ें"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"कॉल 1 को डिसकनेक्ट करें"</string>
- <string name="add_call_2" msgid="6706005258041717434">"कॉल 2 जोड़ें"</string>
- <string name="set_call_active" msgid="248748409907478011">"कॉल 2 को \'चालू है\' के तौर पर सेट करें"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"कॉल 2 को डिसकनेक्ट करें"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS शुरू करें (बैकग्राउंड में MT + ऐप्लिकेशन को सिम्युलेट करें)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"आउटगोइंग कॉल शुरू करें"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"इनकमिंग कॉल शुरू करें"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"कॉल आईडी सेट नहीं है"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"जवाब"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"डिसकनेक्ट करें"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ईयरपीस"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"स्पीकर"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ब्लूटूथ"</string>
+ <string name="start_stream" msgid="3567634786280097431">"स्ट्रीमिंग शुरू करें"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-hr/strings.xml b/testapps/transactionalVoipApp/res/values-hr/strings.xml
index 68291d6..768d378 100644
--- a/testapps/transactionalVoipApp/res/values-hr/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-hr/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Testna aktivnost API-ja za transakcije"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"transakcijska aktivnost u pozivu"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Registracija telefonskog računa"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"odlazni"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"dolazni"</string>
- <string name="add_call_1" msgid="5825706540046010457">"dodavanje 1. poziva"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"prekid 1. poziva"</string>
- <string name="add_call_2" msgid="6706005258041717434">"dodavanje 2. poziva"</string>
- <string name="set_call_active" msgid="248748409907478011">"postavljane 2. poziva kao aktivnog"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"prekid 2. poziva"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Pokretanje FGS-a (simulacija: MT i aplikacija u pozadini)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Pokretanje odlaznog poziva"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Pokretanje dolaznog poziva"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"id poziva nije postavljen"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"Postavljanje kao aktivno"</string>
+ <string name="answer" msgid="5423590397665409939">"odgovor"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"Postavljanje kao neaktivno"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"prekid veze"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Slušalica"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Zvučnik"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"pokretanje streaminga"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-hu/strings.xml b/testapps/transactionalVoipApp/res/values-hu/strings.xml
index fc903f8..cda3b7e 100644
--- a/testapps/transactionalVoipApp/res/values-hu/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-hu/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Tranzakciós API-teszttevékenység"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Hívás közbeni tranzakciós tevékenység"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Telefonáláshoz használt fiók regisztrálása"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"kimenő"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"bejövő"</string>
- <string name="add_call_1" msgid="5825706540046010457">"1. hívás hozzáadása"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"1. hívás megszakítása"</string>
- <string name="add_call_2" msgid="6706005258041717434">"2. hívás hozzáadása"</string>
- <string name="set_call_active" msgid="248748409907478011">"2. hívás aktívra állítása"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"2. hívás megszakítása"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Előtérben futó szolgáltatás indítása (gépi fordítás + alkalmazás szimulálása a háttérben)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Kimenő hívás indítása"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Bejövő hívás indítása"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"nincs beállítva hívásazonosító"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"válasz"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"leválasztás"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Fülhallgató"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Hangszóró"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"streamelés indítása"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-hy/strings.xml b/testapps/transactionalVoipApp/res/values-hy/strings.xml
index a781b20..b56941f 100644
--- a/testapps/transactionalVoipApp/res/values-hy/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-hy/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Գործարքային API-ների փորձարկման գործողություն"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Գործարքներ զանգի ժամանակ"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Հեռախոսի հաշվի գրանցում"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"ելքային"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"մուտքային"</string>
- <string name="add_call_1" msgid="5825706540046010457">"ավելացնել զանգ 1-ը"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"ընդհատել զանգ 1-ը"</string>
- <string name="add_call_2" msgid="6706005258041717434">"ավելացնել զանգ 2-ը"</string>
- <string name="set_call_active" msgid="248748409907478011">"զանգ 2-ը դարձնել ակտիվ"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"ընդհատել զանգ 2-ը"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Գործարկել FGS-ը (ՄԹ-ի սիմուլացիա + հավելված ֆոնային ռեժիմում)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Սկսել ելքային զանգ"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Սկսել մուտքային զանգ"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"զանգի նույնացուցիչ սահմանված չէ"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"ակտիվացնել"</string>
+ <string name="answer" msgid="5423590397665409939">"պատասխանել"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"ապակտիվացնել"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"անջատել"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Լսափող"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Բարձրախոս"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"սկսել հեռարձակում"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-in/strings.xml b/testapps/transactionalVoipApp/res/values-in/strings.xml
index 0b1f12f..e29fea7 100644
--- a/testapps/transactionalVoipApp/res/values-in/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-in/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Aktivitas pengujian API Transaksional"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Aktivitas Transaksi Dalam Panggilan"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Daftarkan Akun Ponsel"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"keluar"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"masuk"</string>
- <string name="add_call_1" msgid="5825706540046010457">"tambahkan panggilan 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"akhiri panggilan 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"tambahkan panggilan 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"setel panggilan 2 ke aktif"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"akhiri panggilan 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Mulai FGS (simulasikan MT + aplikasi di latar belakang)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Mulai Panggilan Keluar"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Mulai Panggilan Masuk"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"id panggilan tidak ditetapkan"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setelAktif"</string>
+ <string name="answer" msgid="5423590397665409939">"jawab"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setelNonaktif"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"putuskan koneksi"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Earpiece"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Speaker"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"mulai streaming"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-is/strings.xml b/testapps/transactionalVoipApp/res/values-is/strings.xml
index c067c74..4ecb2ca 100644
--- a/testapps/transactionalVoipApp/res/values-is/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-is/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Prófun á virkni forritaskila færslna"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Virkni í símtali"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Skrá símareikning"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"hringt"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"móttekið"</string>
- <string name="add_call_1" msgid="5825706540046010457">"bæta við símtali 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"slíta símtali 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"bæta við símtali 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"virkja símtal 2"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"slíta símtali 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Ræsa FGS (líkja eftir MT + forriti í bakgrunni)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Hefja hringt símtal"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Hefja símtal sem berst"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"númerabirting ekki stillt"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"svara"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"aftengja"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Eyrnatól"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Hátalari"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"hefja streymi"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-it/strings.xml b/testapps/transactionalVoipApp/res/values-it/strings.xml
index 35da8ae..bb83aa1 100644
--- a/testapps/transactionalVoipApp/res/values-it/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-it/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Attività di test dell\'API transazionale"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Attività di transazione durante la chiamata"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Registra account telefono"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"in uscita"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"in arrivo"</string>
- <string name="add_call_1" msgid="5825706540046010457">"aggiungi chiamata 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"termina chiamata 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"aggiungi chiamata 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"imposta chiamata 2 attiva"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"termina chiamata 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Avvia FGS (simulazione di MT + app in background)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Avvia chiamata in uscita"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Avvia chiamata in arrivo"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"id chiamata non impostato"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"risposta"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"disconnetti"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Auricolare"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Altoparlante"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"avvia streaming"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-iw/strings.xml b/testapps/transactionalVoipApp/res/values-iw/strings.xml
index 95557df..4de997e 100644
--- a/testapps/transactionalVoipApp/res/values-iw/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-iw/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Transactional API test Activity"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"בר ביצוע בפעילות השיחה"</string>
<string name="register_phone_account" msgid="1920315963082350332">"רישום חשבון הטלפון"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"שיחה יוצאת"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"שיחה נכנסת"</string>
- <string name="add_call_1" msgid="5825706540046010457">"הוספת שיחה 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"ניתוק שיחה 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"הוספת שיחה 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"הגדרת שיחה 2 פעילה"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"ניתוק שיחה 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"הפעלת FGS (סימולציה של MT + אפליקציה ברקע)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"התחלת שיחה יוצאת"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"התחלת שיחה נכנסת"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"מזהה השיחה לא הוגדר"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"הגדרה כפעיל"</string>
+ <string name="answer" msgid="5423590397665409939">"תשובה"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"הגדרה כלא פעיל"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"ניתוק"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"אוזניה"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"רמקול"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"התחלת השידור"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ja/strings.xml b/testapps/transactionalVoipApp/res/values-ja/strings.xml
index 86d6189..a5e8251 100644
--- a/testapps/transactionalVoipApp/res/values-ja/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ja/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Transactional API テスト アクティビティ"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Transactional 通話アクティビティ"</string>
<string name="register_phone_account" msgid="1920315963082350332">"スマートフォン アカウントを登録"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"発信"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"着信"</string>
- <string name="add_call_1" msgid="5825706540046010457">"通話 1 を追加"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"通話 1 を切る"</string>
- <string name="add_call_2" msgid="6706005258041717434">"通話 2 を追加"</string>
- <string name="set_call_active" msgid="248748409907478011">"通話 2 をアクティブに設定"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"通話 2 を切る"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS を開始(MT + アプリをバックグラウンドでシミュレート)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"発信を開始"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"着信を開始"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"通話 ID が設定されていません"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"応答"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"切断"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"受話口"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"スピーカー"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"ストリーミングを開始"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ka/strings.xml b/testapps/transactionalVoipApp/res/values-ka/strings.xml
index 1f3e69e..671cffb 100644
--- a/testapps/transactionalVoipApp/res/values-ka/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ka/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"ტრანზაქციული API ტესტის აქტივობა"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"ტრანზაქციის ზარის აქტივობა"</string>
<string name="register_phone_account" msgid="1920315963082350332">"ტელეფონის ანგარიშის რეგისტრაცია"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"გამავალი"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"შემომავალი"</string>
- <string name="add_call_1" msgid="5825706540046010457">"ზარი 1-ის დამატება"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"ზარი 1-ის გათიშვა"</string>
- <string name="add_call_2" msgid="6706005258041717434">"ზარი 2-ის დამატება"</string>
- <string name="set_call_active" msgid="248748409907478011">"ზარი 2-ის აქტივაცია"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"ზარის 2-ის გათიშვა"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS-ის დაწყება (MT + აპის სიმულაცია ფონზე)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"დაიწყეთ გამავალი ზარი"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"დაიწყეთ შემომავალი ზარი"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"აბონენტის ID არ არის დაყენებული"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"პასუხი"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"კავშირის გაწყვეტა"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ყურმილი"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"დინამიკი"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"სტრიმინგის დაწყება"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-kk/strings.xml b/testapps/transactionalVoipApp/res/values-kk/strings.xml
index a82c02a..2713491 100644
--- a/testapps/transactionalVoipApp/res/values-kk/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-kk/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Транзакциялық API сынағына қатысты әрекет"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Қоңыраулар тарихындағы транзакциялық қолданба"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Телефон аккаунтын тіркеу"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"шығыс"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"кіріс"</string>
- <string name="add_call_1" msgid="5825706540046010457">"1-қоңырауды қосу"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"1-қоңырауды ажырату"</string>
- <string name="add_call_2" msgid="6706005258041717434">"2-қоңырауды қосу"</string>
- <string name="set_call_active" msgid="248748409907478011">"2-қоңырауды белсенді қылу"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"2-қоңырауды ажырату"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS-ті бастау (MT мен қолданбаны фонда симуляциялау)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Шығыс қоңырауын бастау"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Кіріс қоңырауын бастау"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"қоңырау идентификаторы орнатылмады"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"жауап беру"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"ажырату"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Динамик"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Динамик"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"трансляцияны бастау"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-km/strings.xml b/testapps/transactionalVoipApp/res/values-km/strings.xml
index 518fda6..13f4983 100644
--- a/testapps/transactionalVoipApp/res/values-km/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-km/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"សកម្មភាពធ្វើតេស្ត API ប្រតិបត្តិការ"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"សកម្មភាពប្រតិបត្តិការនៅក្នុងការហៅទូរសព្ទ"</string>
<string name="register_phone_account" msgid="1920315963082350332">"ចុះឈ្មោះគណនីទូរសព្ទ"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"ចេញ"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"ចូល"</string>
- <string name="add_call_1" msgid="5825706540046010457">"បញ្ចូលការហៅទូរសព្ទទី 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"ផ្ដាច់ការហៅទូរសព្ទទី 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"បញ្ចូលការហៅទូរសព្ទទី 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"កំណត់ការហៅទូរសព្ទទី 2 ឱ្យសកម្ម"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"ផ្ដាច់ការហៅទូរសព្ទទី 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"ចាប់ផ្ដើម FGS (ត្រាប់តាម MT + កម្មវិធីនៅផ្ទៃខាងក្រោយ)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"ចាប់ផ្ដើមការហៅចេញ"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"ចាប់ផ្ដើមការហៅចូល"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"មិនបានកំណត់លេខសម្គាល់ការហៅទូរសព្ទទេ"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"ឆ្លើយ"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"ផ្ដាច់"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ឧបករណ៍ស្ដាប់សំឡេង"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"ឧបករណ៍បំពងសំឡេង"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ប៊្លូធូស"</string>
+ <string name="start_stream" msgid="3567634786280097431">"ចាប់ផ្ដើមការផ្សាយ"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-kn/strings.xml b/testapps/transactionalVoipApp/res/values-kn/strings.xml
index 094bf01..b994f92 100644
--- a/testapps/transactionalVoipApp/res/values-kn/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-kn/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"ಟ್ರಾನ್ಸಾಕ್ಷನಲ್ API ಪರೀಕ್ಷಾ ಚಟುವಟಿಕೆ"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"ಕರೆ ಚಟುವಟಿಕೆಯಲ್ಲಿ ಟ್ರಾನ್ಸಾಕ್ಷನಲ್"</string>
<string name="register_phone_account" msgid="1920315963082350332">"ಫೋನ್ ಖಾತೆಯನ್ನು ನೋಂದಾಯಿಸಿ"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"ಹೊರಹೋಗುವುದು"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"ಒಳಬರುವುದು"</string>
- <string name="add_call_1" msgid="5825706540046010457">"ಕರೆ 1 ಅನ್ನು ಸೇರಿಸಿ"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"ಕರೆ 1 ಅನ್ನು ಕಡಿತಗೊಳಿಸಿ"</string>
- <string name="add_call_2" msgid="6706005258041717434">"ಕರೆ 2 ಅನ್ನು ಸೇರಿಸಿ"</string>
- <string name="set_call_active" msgid="248748409907478011">"ಕರೆ 2 ಅನ್ನು ಸಕ್ರಿಯ ಎಂದು ಸೆಟ್ ಮಾಡಿ"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"ಕರೆ 2 ಅನ್ನು ಕಡಿತಗೊಳಿಸಿ"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS ಅನ್ನು ಪ್ರಾರಂಭಿಸಿ (MT + ಆ್ಯಪ್ ಅನ್ನು ಹಿನ್ನೆಲೆಯಲ್ಲಿ ಅನುಕರಿಸಿ)."</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"ಹೊರಹೋಗುವ ಕರೆಯನ್ನು ಪ್ರಾರಂಭಿಸಿ"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"ಒಳಬರುವ ಕರೆಯನ್ನು ಪ್ರಾರಂಭಿಸಿ"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"ಕರೆಮಾಡುವವರ ID ಅನ್ನು ಸೆಟ್ ಮಾಡಿಲ್ಲ"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"ಉತ್ತರ"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"ಡಿಸ್ಕನೆಕ್ಟ್"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ಇಯರ್ಪೀಸ್"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"ಸ್ಪೀಕರ್"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ಬ್ಲೂಟೂತ್"</string>
+ <string name="start_stream" msgid="3567634786280097431">"ಸ್ಟ್ರೀಮ್ ಮಾಡುವುದನ್ನು ಪ್ರಾರಂಭಿಸಿ"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ko/strings.xml b/testapps/transactionalVoipApp/res/values-ko/strings.xml
index 10c9f62..9eb4556 100644
--- a/testapps/transactionalVoipApp/res/values-ko/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ko/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"트랜잭션 API 테스트 활동"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"통화 중 거래 활동"</string>
<string name="register_phone_account" msgid="1920315963082350332">"전화 계정 등록"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"발신"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"수신"</string>
- <string name="add_call_1" msgid="5825706540046010457">"통화 1 추가"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"통화 1 끊기"</string>
- <string name="add_call_2" msgid="6706005258041717434">"통화 2 추가"</string>
- <string name="set_call_active" msgid="248748409907478011">"통화 2 활성으로 설정"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"통화 2 끊기"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS 시작(MT 및 백그라운드 앱 시뮬레이션)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"발신 전화 시작"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"수신 전화 시작"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"통화 ID가 설정되지 않음"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"활성으로 설정"</string>
+ <string name="answer" msgid="5423590397665409939">"답변"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"비활성으로 설정"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"연결 해제"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"스피커"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"스피커"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"블루투스"</string>
+ <string name="start_stream" msgid="3567634786280097431">"스트리밍 시작"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ky/strings.xml b/testapps/transactionalVoipApp/res/values-ky/strings.xml
index 06bff74..577dcda 100644
--- a/testapps/transactionalVoipApp/res/values-ky/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ky/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Транзакциялык API сыноосунун активдүүлүгү"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Чалуу учурундагы транзакциялар"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Телефон аккаунтун каттоо"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"чыгуучу"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"келүүчү"</string>
- <string name="add_call_1" msgid="5825706540046010457">"1-чалууну кошуу"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"1-чалууну үзүү"</string>
- <string name="add_call_2" msgid="6706005258041717434">"2-чалууну кошуу"</string>
- <string name="set_call_active" msgid="248748409907478011">"2-чалууну активдүү кылуу"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"2-чалууну үзүү"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS\'ти иштетүү (фондо MT + колдонмону симуляциялоо)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Чыгуучу чалууну баштоо"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Кирүүчү чалууну баштоо"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"чалуунун идентификатору коюлган жок"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"жооп берүү"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"ажыратуу"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Кулакчын"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Динамик"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"агымды баштоо"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-lo/strings.xml b/testapps/transactionalVoipApp/res/values-lo/strings.xml
index f5f6dd5..69126d9 100644
--- a/testapps/transactionalVoipApp/res/values-lo/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-lo/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"ກິດຈະກໍາການທົດສອບ API ທຸລະກໍາ"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"ການເຄື່ອນໄຫວຂອງທຸລະກຳລະຫວ່າງການໂທ"</string>
<string name="register_phone_account" msgid="1920315963082350332">"ລົງທະບຽນບັນຊີໂທລະສັບ"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"ສາຍໂທອອກ"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"ສາຍໂທເຂົ້າ"</string>
- <string name="add_call_1" msgid="5825706540046010457">"ເພີ່ມການໂທ 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"ຕັດເຊື່ອມຕໍ່ການໂທ 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"ເພີ່ມການໂທ 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"ຕັ້ງການໂທ 2 ເປັນນຳໃຊ້"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"ຕັດເຊື່ອມຕໍ່ການໂທ 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"ເລີ່ມ FGS (ຈຳລອງ MT + ແອັບໃນພື້ນຫຼັງ)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"ເລີ່ມສາຍໂທອອກ"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"ເລີ່ມສາຍໂທເຂົ້າ"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"ບໍ່ໄດ້ຕັ້ງໝາຍເລກຜູ້ໂທ"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"ຕັ້ງຄ່າເປັນນຳໃຊ້ຢູ່"</string>
+ <string name="answer" msgid="5423590397665409939">"ຄຳຕອບ"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"ຕັ້ງຄ່າເປັນບໍ່ໄດ້ນຳໃຊ້"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"ຕັດການເຊື່ອມຕໍ່"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ຫູຟັງ"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"ລຳໂພງ"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"ເລີ່ມການສະຕຣີມ"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-lt/strings.xml b/testapps/transactionalVoipApp/res/values-lt/strings.xml
index 5e66c15..91e51fe 100644
--- a/testapps/transactionalVoipApp/res/values-lt/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-lt/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Operacijų API testavimo veikla"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Operacijų skambutyje veikla"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Užregistruoti telefono paskyrą"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"siunčiamieji"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"gaunamieji"</string>
- <string name="add_call_1" msgid="5825706540046010457">"pridėti 1 skambutį"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"atjungti 1 skambutį"</string>
- <string name="add_call_2" msgid="6706005258041717434">"pridėti 2 skambutį"</string>
- <string name="set_call_active" msgid="248748409907478011">"nustatyti 2 skambutį kaip aktyvų"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"atjungti 2 skambutį"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Pradėti FGS (modeliuoti MT ir programą fone)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Pradėti siunčiamąjį skambutį"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Pradėti gaunamąjį skambutį"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"skambučio ID nenustatytas"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"atsakyti"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"atsijungti"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Garsiakalbis prie ausies"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Garsiakalbis"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"pradėti srautinį perdavimą"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-lv/strings.xml b/testapps/transactionalVoipApp/res/values-lv/strings.xml
index 6678d6e..ae6896f 100644
--- a/testapps/transactionalVoipApp/res/values-lv/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-lv/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Transakciju API testa darbība"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Ar darījumiem saistītas darbības zvana laikā"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Reģistrēt tālruņa kontu"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"izejošs"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"ienākošs"</string>
- <string name="add_call_1" msgid="5825706540046010457">"pievienot 1. zvanu"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"pārtraukt 1. zvanu"</string>
- <string name="add_call_2" msgid="6706005258041717434">"pievienot 2. zvanu"</string>
- <string name="set_call_active" msgid="248748409907478011">"iestatīt 2. zvanu kā aktīvu"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"pārtraukt 2. zvanu"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Sākt FGS (simulēt mašīntulkojumu un lietotni fonā)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Sākt izejoša zvana simulāciju"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Sākt ienākoša zvana simulāciju"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"zvana ID nav iestatīts"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"atbildēt"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"pārtraukt"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Auss skaļrunis"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Skaļrunis"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"sākt straumēšanu"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-mk/strings.xml b/testapps/transactionalVoipApp/res/values-mk/strings.xml
index 01cd42e..8501eaf 100644
--- a/testapps/transactionalVoipApp/res/values-mk/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-mk/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Активност на тестирање на API за трансакции"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Трансакциска активност во повикот"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Регистрирај телефонска сметка"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"појдовен"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"дојдовен"</string>
- <string name="add_call_1" msgid="5825706540046010457">"додај повик 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"прекини го повикот 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"додај повик 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"постави го повик 2 како активен"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"прекини го повикот 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Започни FGS (симулирај MT + апликација во заднина)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Започни појдовен повик"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Започни дојдовен повик"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"не е поставен ID на повикувач"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"одговори"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"прекини врска"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Слушалка"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Звучник"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"започни стриминг"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ml/strings.xml b/testapps/transactionalVoipApp/res/values-ml/strings.xml
index 66ec4ef..67e4e34 100644
--- a/testapps/transactionalVoipApp/res/values-ml/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ml/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"ട്രാൻസാക്ഷണൽ API ടെസ്റ്റ് ആക്റ്റിവിറ്റി"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"ട്രാൻസാക്ഷണൽ ഇൻ കോൾ ആക്റ്റിവിറ്റി"</string>
<string name="register_phone_account" msgid="1920315963082350332">"ഫോൺ അക്കൗണ്ട് രജിസ്റ്റർ ചെയ്യുക"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"ഔട്ട്ഗോയിംഗ്"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"ഇൻകമിംഗ്"</string>
- <string name="add_call_1" msgid="5825706540046010457">"കോൾ 1 ചേർക്കുക"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"കോൾ 1 വിച്ഛേദിക്കുക"</string>
- <string name="add_call_2" msgid="6706005258041717434">"കോൾ 2 ചേർക്കുക"</string>
- <string name="set_call_active" msgid="248748409907478011">"കോൾ 2 സജീവമായി സജ്ജീകരിക്കുക"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"കോൾ 2 വിച്ഛേദിക്കുക"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS ആരംഭിക്കുക (പശ്ചാത്തലത്തിൽ മെഷീൻ ട്രാൻസ്ലേഷൻ + ആപ്പ് സിമുലേറ്റ് ചെയ്യുക)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"ഔട്ട്ഗോയിംഗ് കോൾ ആരംഭിക്കുക"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"ഇൻകമിംഗ് കോൾ ആരംഭിക്കുക"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"കോൾ ഐഡി സജ്ജീകരിച്ചിട്ടില്ല"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"സജീവമെന്ന് സജ്ജീകരിക്കുക"</string>
+ <string name="answer" msgid="5423590397665409939">"ഉത്തരം നൽകുക"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"സജീവമല്ലെന്ന് സജ്ജീകരിക്കുക"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"വിച്ഛേദിക്കുക"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ഇയർഫോൺ"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"സ്പീക്കർ"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"സ്ട്രീമിംഗ് ആരംഭിക്കുക"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-mn/strings.xml b/testapps/transactionalVoipApp/res/values-mn/strings.xml
index f056b2b..e4b6f36 100644
--- a/testapps/transactionalVoipApp/res/values-mn/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-mn/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Гүйлгээний API-н туршилтын үйл ажиллагаа"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Дуудлагын үйл ажиллагааны гүйлгээ"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Утасны бүртгэл бүртгүүлэх"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"залгаж буй"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"ирж буй"</string>
- <string name="add_call_1" msgid="5825706540046010457">"1-р дуудлага нэмэх"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"1-р дуудлагыг салгах"</string>
- <string name="add_call_2" msgid="6706005258041717434">"2-р дуудлага нэмэх"</string>
- <string name="set_call_active" msgid="248748409907478011">"2-р дуудлагыг идэвхтэй болгож тохируулах"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"2-р дуудлагыг салгах"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS-г эхлүүлэх (дэвсгэрт MT + аппыг загварчлах)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Залгасан дуудлагыг эхлүүлэх"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Ирсэн дуудлагыг эхлүүлэх"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"дуудлагын ID-г тохируулаагүй"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"хариулах"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"салгах"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Чихний спикер"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Чанга яригч"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"дамжуулалтыг эхлүүлэх"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-mr/strings.xml b/testapps/transactionalVoipApp/res/values-mr/strings.xml
index 4ca3b8e..dfb3184 100644
--- a/testapps/transactionalVoipApp/res/values-mr/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-mr/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"व्यावहारिक API चाचणी अॅक्टिव्हिटी"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"कॉल अॅक्टिव्हिटी यामधील व्यवहार"</string>
<string name="register_phone_account" msgid="1920315963082350332">"फोन खात्याची नोंदणी करा"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"आउटगोइंग"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"इनकमिंग"</string>
- <string name="add_call_1" msgid="5825706540046010457">"कॉल १ जोडा"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"कॉल १ डिस्कनेक्ट करा"</string>
- <string name="add_call_2" msgid="6706005258041717434">"कॉल २ जोडा"</string>
- <string name="set_call_active" msgid="248748409907478011">"कॉल २ अॅक्टिव्ह यावर सेट करा"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"कॉल २ डिस्कनेक्ट करा"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS सुरू करा (बॅकग्राउंडमध्ये MT + अॅप सिम्युलेट करा)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"आउटगोइंग कॉल सुरू करा"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"इनकमिंग कॉल सुरू करा"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"कॉल आयडी सेट केलेला नाही"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"उत्तर"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"डिस्कनेक्ट करा"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"इअरपिस"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"स्पीकर"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ब्लूटूथ"</string>
+ <string name="start_stream" msgid="3567634786280097431">"स्ट्रीम करणे सुरू करा"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ms/strings.xml b/testapps/transactionalVoipApp/res/values-ms/strings.xml
index d888672..3005391 100644
--- a/testapps/transactionalVoipApp/res/values-ms/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ms/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Aktiviti ujian API transaksi"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Transaksi Aktiviti Dalam Panggilan"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Daftar Akaun Telefon"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"keluar"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"masuk"</string>
- <string name="add_call_1" msgid="5825706540046010457">"tambahkan panggilan 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"putuskan panggilan 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"tambahkan panggilan 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"tetapkan panggilan 2 sebagai aktif"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"putuskan panggilan 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Mulakan FGS (simulasi MT + apl pada latar)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Mulakan Panggilan Keluar"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Mulakan Panggilan Masuk"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"ID panggilan tidak ditetapkan"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"jawab"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"putuskan sambungan"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Alat dengar"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Pembesar suara"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"mulakan penstriman"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-my/strings.xml b/testapps/transactionalVoipApp/res/values-my/strings.xml
index 7422922..818a3f7 100644
--- a/testapps/transactionalVoipApp/res/values-my/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-my/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"အသိအမှတ်ပြုမှုဆိုင်ရာ API စမ်းသပ်လုပ်ဆောင်ချက်"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"ခေါ်ဆိုမှုလုပ်ဆောင်ချက်ရှိ မှတ်တမ်းဆိုင်ရာ"</string>
<string name="register_phone_account" msgid="1920315963082350332">"ဖုန်းအကောင့် မှတ်ပုံတင်ရန်"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"အထွက်"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"အဝင်"</string>
- <string name="add_call_1" msgid="5825706540046010457">"ခေါ်ဆိုမှု ၁ ထည့်ရန်"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"ခေါ်ဆိုမှု ၁ ဖြတ်တောက်ရန်"</string>
- <string name="add_call_2" msgid="6706005258041717434">"ခေါ်ဆိုမှု ၂ ထည့်ရန်"</string>
- <string name="set_call_active" msgid="248748409907478011">"ခေါ်ဆိုမှု ၂ ကို လက်ရှိပြောနေကြောင်း သတ်မှတ်ရန်"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"ခေါ်ဆိုမှု ၂ ဖြတ်တောက်ရန်"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS (အသွင်တူ MT + နောက်ခံရှိ အက်ပ်) စတင်ရန်"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"အထွက် ခေါ်ဆိုမှု စတင်ရန်"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"အဝင်ခေါ်ဆိုမှု စတင်ရန်"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"ခေါ်ဆိုမှု id သတ်မှတ်မထားပါ"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"ပြောနေသည်ဟု သတ်မှတ်ရန်"</string>
+ <string name="answer" msgid="5423590397665409939">"ဖြေကြားရန်"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"ပြောမနေပါဟု သတ်မှတ်ရန်"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"ချိတ်ဆက်မှုဖြုတ်ရန်"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"တယ်လီဖုန်းနားခွက်"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"စပီကာ"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ဘလူးတုသ်"</string>
+ <string name="start_stream" msgid="3567634786280097431">"တိုက်ရိုက်လွှင့်ခြင်း စတင်ရန်"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-nb/strings.xml b/testapps/transactionalVoipApp/res/values-nb/strings.xml
index 124e0a0..ab0353d 100644
--- a/testapps/transactionalVoipApp/res/values-nb/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-nb/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Testaktivitet for Transactional API"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Transaksjonell i samtale-aktivitet"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Registrer telefonkonto"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"utgående"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"innkommende"</string>
- <string name="add_call_1" msgid="5825706540046010457">"legg til anrop 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"avslutt anrop 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"legg til anrop 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"angi anrop 2 som aktivt"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"avslutt anrop 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Start FGS (simuler MT + app i bakgrunnen)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Start utgående anrop"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Start innkommende anrop"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"anrops-ID er ikke angitt"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"svar"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"koble fra"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Ørehøyttaler"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Høyttaler"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"start strømming"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ne/strings.xml b/testapps/transactionalVoipApp/res/values-ne/strings.xml
index 84481ec..3a12a70 100644
--- a/testapps/transactionalVoipApp/res/values-ne/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ne/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Transactional API को परीक्षणसम्बन्धी गतिविधि"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"कलमा क्लाइन्ट र सर्भरबिच गरिएको कुराकानीसम्बन्धी क्रियाकलाप"</string>
<string name="register_phone_account" msgid="1920315963082350332">"फोन खाता दर्ता गर्नुहोस्"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"बहिर्गमन"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"आगमन"</string>
- <string name="add_call_1" msgid="5825706540046010457">"कल १ कनेक्ट गर्नुहोस्"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"कल १ डिस्कनेक्ट गर्नुहोस्"</string>
- <string name="add_call_2" msgid="6706005258041717434">"कल २ कनेक्ट गर्नुहोस्"</string>
- <string name="set_call_active" msgid="248748409907478011">"कल २ लाई सक्रिय कलका रूपमा सेट गर्नुहोस्"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"कल २ डिस्कनेक्ट गर्नुहोस्"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS सुरु गर्नुहोस् (ब्याकग्राउन्डमा MT + एप सिमुलेट गर्नुहोस्)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"बहिर्गमन कल सुरु गर्नुहोस्"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"आगमन कल सुरु गर्नुहोस्"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"कल ID सेट गरिएको छैन"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"कल उठाउनुहोस्"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"डिस्कनेक्ट गर्नुहोस्"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"इयरपिस"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"स्पिकर"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ब्लुटुथ"</string>
+ <string name="start_stream" msgid="3567634786280097431">"स्ट्रिम गर्न थाल्नुहोस्"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-nl/strings.xml b/testapps/transactionalVoipApp/res/values-nl/strings.xml
index 949d71e..7c9ce32 100644
--- a/testapps/transactionalVoipApp/res/values-nl/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-nl/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Testactiviteit Transactional API"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Beveiligd gesprek"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Telefoonaccount registreren"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"uitgaand"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"binnenkomend"</string>
- <string name="add_call_1" msgid="5825706540046010457">"gesprek 1 toevoegen"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"gesprek 1 beëindigen"</string>
- <string name="add_call_2" msgid="6706005258041717434">"gesprek 2 toevoegen"</string>
- <string name="set_call_active" msgid="248748409907478011">"gesprek 2 instellen als actief"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"gesprek 2 beëindigen"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Service op de voorgrond (FGS) starten (MT + app op de achtergrond simuleren)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Uitgaand gesprek starten"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Inkomend gesprek starten"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"Beller-ID niet ingesteld"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"antwoord"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"loskoppelen"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Oortelefoon"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Speaker"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"streamen starten"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-or/strings.xml b/testapps/transactionalVoipApp/res/values-or/strings.xml
index 7cda1c8..7a805f4 100644
--- a/testapps/transactionalVoipApp/res/values-or/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-or/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"ଟ୍ରାଞ୍ଜେକସନାଲ API ପରୀକ୍ଷଣର କାର୍ଯ୍ୟକଳାପ"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"ଟ୍ରାଞ୍ଜେକସନାଲ ଇନ କଲ କାର୍ଯ୍ୟକଳାପ"</string>
<string name="register_phone_account" msgid="1920315963082350332">"ଫୋନ ଆକାଉଣ୍ଟର ପଞ୍ଜିକରଣ କରନ୍ତୁ"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"ଆଉଟଗୋଇଂ"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"ଇନକମିଂ"</string>
- <string name="add_call_1" msgid="5825706540046010457">"କଲ 1 ଯୋଗ କରନ୍ତୁ"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"କଲ 1 ଡିସକନେକ୍ଟ କରନ୍ତୁ"</string>
- <string name="add_call_2" msgid="6706005258041717434">"କଲ 2 ଯୋଗ କରନ୍ତୁ"</string>
- <string name="set_call_active" msgid="248748409907478011">"କଲ 2କୁ ସକ୍ରିୟ ଭାବେ ସେଟ କରନ୍ତୁ"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"କଲ 2 ଡିସକନେକ୍ଟ କରନ୍ତୁ"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS ଆରମ୍ଭ କରନ୍ତୁ (ପୃଷ୍ଠପଟରେ MT + ଆପକୁ ସିମୁଲେଟ କରନ୍ତୁ)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"ଆଉଟଗୋଇଂ କଲ ଆରମ୍ଭ କରନ୍ତୁ"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"ଇନକମିଂ କଲ ଆରମ୍ଭ କରନ୍ତୁ"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"କଲ ID ସେଟ କରାଯାଇନାହିଁ"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"ଉତ୍ତର ଦିଅନ୍ତୁ"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"ଡିସକନେକ୍ଟ କରନ୍ତୁ"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ଇୟରପିସ"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"ସ୍ପିକର"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ବ୍ଲୁଟୁଥ"</string>
+ <string name="start_stream" msgid="3567634786280097431">"ଷ୍ଟ୍ରିମିଂ ଆରମ୍ଭ କରନ୍ତୁ"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-pa/strings.xml b/testapps/transactionalVoipApp/res/values-pa/strings.xml
index c01d6a3..8293899 100644
--- a/testapps/transactionalVoipApp/res/values-pa/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-pa/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"ਲੈਣ-ਦੇਣ API ਜਾਂਚ ਸਰਗਰਮੀ"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"ਲੈਣ-ਦੇਣ ਸੰਬੰਧੀ ਇਨ-ਕਾਲ ਸਰਗਰਮੀ"</string>
<string name="register_phone_account" msgid="1920315963082350332">"ਫ਼ੋਨ ਖਾਤਾ ਰਜਿਸਟਰ ਕਰੋ"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"ਆਊਟਗੋਇੰਗ"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"ਇਨਕਮਿੰਗ"</string>
- <string name="add_call_1" msgid="5825706540046010457">"ਕਾਲ 1 ਸ਼ਾਮਲ ਕਰੋ"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"ਕਾਲ 1 ਕੱਟੋ"</string>
- <string name="add_call_2" msgid="6706005258041717434">"ਕਾਲ 2 ਸ਼ਾਮਲ ਕਰੋ"</string>
- <string name="set_call_active" msgid="248748409907478011">"ਕਾਲ 2 ਨੂੰ ਕਿਰਿਆਸ਼ੀਲ ਵਜੋਂ ਸੈੱਟ ਕਰੋ"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"ਕਾਲ 2 ਕੱਟੋ"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS ਸ਼ੁਰੂ ਕਰੋ (ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ MT + ਐਪ ਨੂੰ ਸਿਮੂਲੇਟ ਕਰੋ)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"ਆਊਟਗੋਇੰਗ ਕਾਲ ਸ਼ੁਰੂ ਕਰੋ"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"ਇਨਕਮਿੰਗ ਕਾਲ ਸ਼ੁਰੂ ਕਰੋ"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"ਕਾਲਰ ਆਈਡੀ ਸੈੱਟ ਨਹੀਂ ਹੈ"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"ਜਵਾਬ"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"ਡਿਸਕਨੈਕਟ ਕਰੋ"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ਈਯਰਪੀਸ"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"ਸਪੀਕਰ"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ਬਲੂਟੁੱਥ"</string>
+ <string name="start_stream" msgid="3567634786280097431">"ਸਟ੍ਰੀਮਿੰਗ ਸ਼ੁਰੂ ਕਰੋ"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-pl/strings.xml b/testapps/transactionalVoipApp/res/values-pl/strings.xml
index 60f370b..3cb8ac4 100644
--- a/testapps/transactionalVoipApp/res/values-pl/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-pl/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Czynność testowa dotycząca transakcji związanej z interfejsem API"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Aktywność transakcyjna w trakcie rozmowy"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Zarejestruj konto telefonu"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"wychodzące"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"przychodzące"</string>
- <string name="add_call_1" msgid="5825706540046010457">"dodaj połączenie 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"rozłącz połączenie 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"dodaj połączenie 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"ustaw połączenie 2 jako aktywne"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"rozłącz połączenie 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Uruchom FGS (symulacja MT + aplikacja w tle)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Rozpocznij połączenie wychodzące"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Rozpocznij połączenie przychodzące"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"nie ustawiono ID rozmówcy"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"ustawAktywny"</string>
+ <string name="answer" msgid="5423590397665409939">"odpowiedź"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"ustawNieaktywny"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"rozłącz"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Słuchawka"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Głośnik"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"rozpocznij transmisję"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-pt-rPT/strings.xml b/testapps/transactionalVoipApp/res/values-pt-rPT/strings.xml
index ebeb482..6c4f149 100644
--- a/testapps/transactionalVoipApp/res/values-pt-rPT/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-pt-rPT/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Atividade de teste da API transacional"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Transacional na atividade da chamada"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Registar conta do telemóvel"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"feita"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"recebida"</string>
- <string name="add_call_1" msgid="5825706540046010457">"adicionar chamada 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"desligar chamada 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"adicionar chamada 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"definir chamada 2 como ativa"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"desligar chamada 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Iniciar FGS (simular TA + app em segundo plano)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Iniciar chamada feita"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Iniciar chamada recebida"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"ID da chamada não definido"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"atender"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"desligar"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Auricular"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Altifalante"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"Iniciar stream"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-pt/strings.xml b/testapps/transactionalVoipApp/res/values-pt/strings.xml
index ea46628..97bba50 100644
--- a/testapps/transactionalVoipApp/res/values-pt/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-pt/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Atividade de teste da API transacional"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Atividade em chamadas transacionais"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Registrar conta telefônica"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"realizada"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"recebida"</string>
- <string name="add_call_1" msgid="5825706540046010457">"adicionar ligação 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"encerrar ligação 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"adicionar ligação 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"definir a ligação 2 como ativa"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"encerrar ligação 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Iniciar FGS (simular MT + app em segundo plano)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Iniciar ligação efetuada"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Iniciar ligação recebida"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"identificador de chamadas não definido"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"resposta"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"desconectar"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Minifone de ouvido"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Alto-falante"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"Iniciar transmissão"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ro/strings.xml b/testapps/transactionalVoipApp/res/values-ro/strings.xml
index ee6bfa2..bb630a8 100644
--- a/testapps/transactionalVoipApp/res/values-ro/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ro/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Activitate de testare a API-ului tranzacțional"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Activitate tranzacțională în timpul apelului"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Înregistrează contul de telefon"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"efectuat"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"primit"</string>
- <string name="add_call_1" msgid="5825706540046010457">"adaugă apelul 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"deconectează apelul 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"adaugă apelul 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"setează apelul 2 ca activ"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"deconectează apelul 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Pornește FGS (simulează MT + aplicația în fundal)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Inițiază un apel efectuat"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Inițiază un apel primit"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"ID-ul apelului nu este setat"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"răspuns"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"deconectează"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Cască"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Difuzor"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"începe streamingul"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ru/strings.xml b/testapps/transactionalVoipApp/res/values-ru/strings.xml
index 5345e7b..87c06f1 100644
--- a/testapps/transactionalVoipApp/res/values-ru/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ru/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Активность тестирования API транзакций"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Транзакции во время вызовов"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Зарегистрировать аккаунт телефона"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"исходящий"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"входящий"</string>
- <string name="add_call_1" msgid="5825706540046010457">"добавить звонок 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"отключить звонок 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"добавить звонок 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"сделать звонок 2 активным"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"отключить звонок 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Запустить активную службу (симуляция МП + приложение в фоновом режиме)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Начать исходящий вызов"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Начать входящий вызов"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"идентификатор вызова не задан"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"Активировать"</string>
+ <string name="answer" msgid="5423590397665409939">"ответить"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"Деактивировать"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"разъединить"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Динамик телефона"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Колонка"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"Начать трансляцию"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-si/strings.xml b/testapps/transactionalVoipApp/res/values-si/strings.xml
index 87d421c..c28e166 100644
--- a/testapps/transactionalVoipApp/res/values-si/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-si/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"ගනුදෙනු API පරීක්ෂණ ක්රියාකාරකම්"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"ඇමතුම් ක්රියාකාරකම්වල ගනුදෙනු"</string>
<string name="register_phone_account" msgid="1920315963082350332">"දුරකථන ගිණුම ලියාපදිංචි කරන්න"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"පිටතට යන"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"ඇතුළට එන"</string>
- <string name="add_call_1" msgid="5825706540046010457">"ඇමතුම 1 එක් කරන්න"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"ඇමතුම 1 විසන්ධි කරන්න"</string>
- <string name="add_call_2" msgid="6706005258041717434">"ඇමතුම 2 එක් කරන්න"</string>
- <string name="set_call_active" msgid="248748409907478011">"ඇමතුම 2 සක්රිය ලෙස සකසන්න"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"ඇමතුම 2 විසන්ධි කරන්න"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS අරඹන්න (පසුබිමේ MT + යෙදුම අනුකරණය කරන්න)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"පිටතට යන ඇමතුම අරඹන්න"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"එන ඇමතුම අරඹන්න"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"අමතුම්කරුගේ id සකසා නැත"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"පිළිතුරු දෙන්න"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"විසන්ධි කරන්න"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"සවන් කඩ"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"ස්පීකරය"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"බ්ලූටූත්"</string>
+ <string name="start_stream" msgid="3567634786280097431">"ප්රවාහය අරඹන්න"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-sk/strings.xml b/testapps/transactionalVoipApp/res/values-sk/strings.xml
index 1d08d78..5e76289 100644
--- a/testapps/transactionalVoipApp/res/values-sk/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-sk/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Testovacia aktivita transakčného rozhrania API"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Transakčná aktivita počas hovoru"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Registrovať telefónny účet"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"odchádzajúci"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"prichádzajúci"</string>
- <string name="add_call_1" msgid="5825706540046010457">"pridať hovor 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"odpojiť hovor 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"pridať hovor 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"nastaviť hovor 2 ako aktívny"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"odpojiť hovor 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Spustiť FGS (simulácia MT a aplikácie na pozadí)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Začať odchádzajúci hovor"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Začať prichádzajúci hovor"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"identifikátor hovoru nie je nastavený"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"prijať"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"odpojiť"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Slúchadlo"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Reproduktor"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"spustiť streamovanie"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-sl/strings.xml b/testapps/transactionalVoipApp/res/values-sl/strings.xml
index c82c86d..435eac9 100644
--- a/testapps/transactionalVoipApp/res/values-sl/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-sl/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Preizkusna dejavnost transakcijskega API-ja"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Transakcijska dejavnost v klicu"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Registracija telefonskega računa"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"odhodni"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"dohodni"</string>
- <string name="add_call_1" msgid="5825706540046010457">"dodaj klic 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"prekinitev klica 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"dodaj klic 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"nastavi klic 2 kot aktiven"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"prekinitev klica 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Zaženi FGS (simuliraj strojni prevod + aplikacijo v ozadju)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Začni odhodni klic"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Začni dohodni klic"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"id klica ni nastavljen"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"Nastavi kot aktivno"</string>
+ <string name="answer" msgid="5423590397665409939">"sprejmi"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"Nastavi kot neaktivno"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"prekini klic"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Slušalka"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Zvočnik"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"začni pretočno predvajanje"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-sq/strings.xml b/testapps/transactionalVoipApp/res/values-sq/strings.xml
index 8a44cdd..3d18edf 100644
--- a/testapps/transactionalVoipApp/res/values-sq/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-sq/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Aktiviteti i testimit të API-së së transaksioneve"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Aktivitet transaksioni brenda telefonatës"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Regjistro llogarinë e telefonit"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"dalëse"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"hyrëse"</string>
- <string name="add_call_1" msgid="5825706540046010457">"shto telefonatën 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"shkëput telefonatën 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"shto telefonatën 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"caktoje telefonatën 2 si aktive"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"shkëput telefonatën 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Nis shërbimin FGS (simulo përkthimin kompjuterik dhe aplikacionin në sfond)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Nis një telefonatë dalëse"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Nis një telefonatë hyrëse"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"ID-ja e telefonatës nuk është caktuar"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"përgjigju"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"shkëput"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Receptori"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Altoparlanti"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"nis transmetimin"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-sr/strings.xml b/testapps/transactionalVoipApp/res/values-sr/strings.xml
index 8e66da7..df6a08b 100644
--- a/testapps/transactionalVoipApp/res/values-sr/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-sr/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Активност тестирања трансакционог API-ја"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Активност позива у вези са трансакцијама"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Региструј налог телефона"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"одлазни"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"долазни"</string>
- <string name="add_call_1" msgid="5825706540046010457">"додај 1. позив"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"прекини 1. позив"</string>
- <string name="add_call_2" msgid="6706005258041717434">"додај 2. позив"</string>
- <string name="set_call_active" msgid="248748409907478011">"подеси 2. позив као активан"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"прекини 2. позив"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Покрени FGS (симулирајте MT + апликацију у позадини)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Започните одлазни позив"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Започните долазни позив"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"ИД позива није подешен"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"одговори"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"прекини везу"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Слушалица"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Звучник"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"почните да стримујете"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-sv/strings.xml b/testapps/transactionalVoipApp/res/values-sv/strings.xml
index 586d1a4..51d300a 100644
--- a/testapps/transactionalVoipApp/res/values-sv/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-sv/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Aktiviteten Test av transaktions-API"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Transaktioner i samtalsaktivitet"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Registrera telefonkonto"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"utgående"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"inkommande"</string>
- <string name="add_call_1" msgid="5825706540046010457">"lägg till samtal 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"lägg på samtal 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"lägg till samtal 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"ställ in samtal 2 som aktivt"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"lägg på samtal 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Starta FGS (simulera MT + app i bakgrunden)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Starta utgående samtal"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Starta inkommande samtal"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"nummerpresentatör inte inställd"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"svara"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"koppla från"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Lur"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Högtalare"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"starta streaming"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-sw/strings.xml b/testapps/transactionalVoipApp/res/values-sw/strings.xml
index 11a5a77..3ad2501 100644
--- a/testapps/transactionalVoipApp/res/values-sw/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-sw/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Shughuli za jaribio la API ya Uthibitishaji"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Shughuli ya Muamala Kwenye Simu"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Sajili Akaunti ya Simu"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"simu unazopiga"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"simu zinazoingia"</string>
- <string name="add_call_1" msgid="5825706540046010457">"weka simu ya 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"kata simu ya 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"weka simu ya 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"piga simu ya 2"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"kata simu ya 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Anzisha FGS (kuiga Tafsiri ya Mashine na programu katika hali ya chinichini)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Anzisha Uigaji wa Simu Unayopiga"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Anzisha Uigaji wa Simu Uliyopigiwa"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"kitambulisho cha anayepiga hakijawekwa"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"jibu"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"ondoa"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Spika ya sikioni"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Spika"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"anzisha kutiririsha"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ta/strings.xml b/testapps/transactionalVoipApp/res/values-ta/strings.xml
index 73d9944..884291d 100644
--- a/testapps/transactionalVoipApp/res/values-ta/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ta/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Transactional API சோதனை செயல்பாடு"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"டிரான்சாக்ஷனல் இன் கால் ஆக்டிவிட்டி"</string>
<string name="register_phone_account" msgid="1920315963082350332">"மொபைல் கணக்கைப் பதிவுசெய்தல்"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"வெளிச்செல்லும் அழைப்பு"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"உள்வரும் அழைப்பு"</string>
- <string name="add_call_1" msgid="5825706540046010457">"அழைப்பு 1ஐச் சேர்த்தல்"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"அழைப்பு 1ஐத் துண்டித்தல்"</string>
- <string name="add_call_2" msgid="6706005258041717434">"அழைப்பு 2ஐச் சேர்த்தல்"</string>
- <string name="set_call_active" msgid="248748409907478011">"அழைப்பு 2 செயலில் உள்ளதாக அமைத்தல்"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"அழைப்பு 2ஐத் துண்டித்தல்"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGSஸைத் தொடங்கு (MT + ஆப்ஸைப் பின்னணியில் சிமுலேட் செய்)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"வெளிச்செல்லும் அழைப்பைத் தொடங்கு"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"உள்வரும் அழைப்பைத் தொடங்கு"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"அழைப்பு ஐடி அமைக்கப்படவில்லை"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"செயலில் அமை"</string>
+ <string name="answer" msgid="5423590397665409939">"பதில்"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"செயலற்ற நிலையில் அமை"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"துண்டி"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ஒலி கேட்கும் பகுதி"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"ஸ்பீக்கர்"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"புளூடூத்"</string>
+ <string name="start_stream" msgid="3567634786280097431">"ஸ்ட்ரீமிங்கைத் தொடங்கு"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-te/strings.xml b/testapps/transactionalVoipApp/res/values-te/strings.xml
index 708122a..b926d1a 100644
--- a/testapps/transactionalVoipApp/res/values-te/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-te/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"లావాదేవీల API టెస్ట్ యాక్టివిటీ"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"కాల్ యాక్టివిటీలో లావాదేవీ"</string>
<string name="register_phone_account" msgid="1920315963082350332">"ఫోన్ ఖాతాను రిజిస్టర్ చేయండి"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"అవుట్గోయింగ్"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"ఇన్కమింగ్"</string>
- <string name="add_call_1" msgid="5825706540046010457">"కాల్ 1ని జోడించండి"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"కాల్ 1ని డిస్కనెక్ట్ చేయండి"</string>
- <string name="add_call_2" msgid="6706005258041717434">"కాల్ 2ను జోడించండి"</string>
- <string name="set_call_active" msgid="248748409907478011">"కాల్ 2ను యాక్టివ్గా సెట్ చేయండి"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"కాల్ 2ను డిస్కనెక్ట్ చేయండి"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS (అనుకరణ MT + బ్యాక్గ్రౌండ్లో యాప్)ను ప్రారంభించండి"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"అవుట్గోయింగ్ కాల్ను ప్రారంభించండి"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"ఇన్కమింగ్ కాల్ను ప్రారంభించండి"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"కాల్ id సెట్ చేయబడలేదు"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"సమాధానం"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"డిస్కనెక్ట్ చేయండి"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ఇయర్పీస్"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"స్పీకర్"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"బ్లూటూత్"</string>
+ <string name="start_stream" msgid="3567634786280097431">"స్ట్రీమింగ్ను ప్రారంభించండి"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-th/strings.xml b/testapps/transactionalVoipApp/res/values-th/strings.xml
index 207f291..a1a9803 100644
--- a/testapps/transactionalVoipApp/res/values-th/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-th/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"กิจกรรมการทดสอบ API ธุรกรรม"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"กิจกรรมธุรกรรมระหว่างการโทร"</string>
<string name="register_phone_account" msgid="1920315963082350332">"ลงทะเบียนบัญชีของโทรศัพท์"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"สายโทรออก"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"สายเรียกเข้า"</string>
- <string name="add_call_1" msgid="5825706540046010457">"เพิ่มการโทร 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"ตัดสาย 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"เพิ่มการโทร 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"ตั้งค่าการโทร 2 เป็นใช้งานอยู่"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"ตัดสาย 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"เริ่ม FGS (จําลอง MT + แอปในพื้นหลัง)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"เริ่มสายโทรออก"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"เริ่มสายเรียกเข้า"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"ไม่ได้ตั้งค่าหมายเลขผู้โทร"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"ตั้งค่าเป็นใช้งานอยู่"</string>
+ <string name="answer" msgid="5423590397665409939">"คำตอบ"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"ตั้งค่าเป็นไม่ใช้งาน"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"ยกเลิกการเชื่อมต่อ"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"หูฟังโทรศัพท์"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"ลำโพง"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"บลูทูธ"</string>
+ <string name="start_stream" msgid="3567634786280097431">"เริ่มสตรีมมิง"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-tl/strings.xml b/testapps/transactionalVoipApp/res/values-tl/strings.xml
index 77f963b..d3399ff 100644
--- a/testapps/transactionalVoipApp/res/values-tl/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-tl/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Aktibidad ng pansubok na Transactional API"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Transaksyonal na In Call na Aktibidad"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Irehistro ang Phone Account"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"papalabas"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"incoming"</string>
- <string name="add_call_1" msgid="5825706540046010457">"magdagdag ng tawag 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"idiskonekta ang tawag 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"magdagdag ng tawag 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"itakdang aktibo ang tawag 2"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"idiskonekta ang tawag 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Simulan ang FGS (i-simulate ang MT + app sa background)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Magsimula ng Papalabas na Tawag"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Magsimula ng Papasok na Tawag"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"hindi naitakda ang call id"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"sagutin"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"idiskonekta"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Earpiece"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Speaker"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"simulan ang streaming"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-tr/strings.xml b/testapps/transactionalVoipApp/res/values-tr/strings.xml
index 4d31191..d9a94ab 100644
--- a/testapps/transactionalVoipApp/res/values-tr/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-tr/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Transactional API test etkinliği"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Görüşme İçin İşlem Etkinliği"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Telefon Hesabını Kaydet"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"giden"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"gelen"</string>
- <string name="add_call_1" msgid="5825706540046010457">"1. aramayı ekle"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"1. aramayı sonlandır"</string>
- <string name="add_call_2" msgid="6706005258041717434">"2. aramayı ekle"</string>
- <string name="set_call_active" msgid="248748409907478011">"2. aramayı etkinleştir"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"2. aramayı sonlandır"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Ön plan hizmetlerini (FGS) başlat (makine çevirisi + arka plandaki uygulamayı simüle et)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Giden Arama Başlat"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Gelen Arama Başlat"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"arama kimliği ayarlanmadı"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"yanıtla"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"bağlantıyı kes"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Kulaklık"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Hoparlör"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"yayın başlat"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-uk/strings.xml b/testapps/transactionalVoipApp/res/values-uk/strings.xml
index d1f5f1d..e08728c 100644
--- a/testapps/transactionalVoipApp/res/values-uk/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-uk/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Тестування API підтвердження"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Трансакції під час викликів"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Зареєструвати обліковий запис телефона"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"вихідні дзвінки"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"вхідні дзвінки"</string>
- <string name="add_call_1" msgid="5825706540046010457">"додати дзвінок 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"завершити дзвінок 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"додати дзвінок 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"активувати дзвінок 2"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"завершити дзвінок 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Запустити активний сервіс (симуляція МП + додаток у фоновому режимі)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Почати вихідний виклик"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Почати вхідний виклик"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"ідентифікатор виклику не налаштовано"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"позначити як активний"</string>
+ <string name="answer" msgid="5423590397665409939">"відповідь"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"позначити як неактивний"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"від’єднати"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Динамік"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Колонка"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"Почати трансляцію"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ur/strings.xml b/testapps/transactionalVoipApp/res/values-ur/strings.xml
index 8c14f04..e0e0c6e 100644
--- a/testapps/transactionalVoipApp/res/values-ur/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ur/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"ٹرانزیکشنل API ٹیسٹ کی سرگرمی"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"کال کی سرگرمی میں ٹرانزیکشنل"</string>
<string name="register_phone_account" msgid="1920315963082350332">"فون کے اکاؤنٹ کو رجسٹر کریں"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"آؤٹ گوئنگ"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"اِن کمنگ"</string>
- <string name="add_call_1" msgid="5825706540046010457">"کال 1 کو شامل کریں"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"کال 1 کو منقطع کریں"</string>
- <string name="add_call_2" msgid="6706005258041717434">"کال 2 کو شامل کریں"</string>
- <string name="set_call_active" msgid="248748409907478011">"کال 2 کو فعال کریں"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"کال 2 کو منقطع کریں"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS شروع کریں ( بیک گراؤنڈ میں MT + ایپ کی نقل کریں)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"آؤٹ گوئنگ کال شروع کریں"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"اِن کمنگ کال شروع کریں"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"کال ID سیٹ نہیں ہے"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"فعال پر سیٹ کریں"</string>
+ <string name="answer" msgid="5423590397665409939">"جواب"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"غیر فعال پر سیٹ کریں"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"غیر منسلک کریں"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ایئر پیس"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"اسپیکر"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"بلوٹوتھ"</string>
+ <string name="start_stream" msgid="3567634786280097431">"سلسلہ بندی شروع کریں"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-uz/strings.xml b/testapps/transactionalVoipApp/res/values-uz/strings.xml
index 4e212a1..5421322 100644
--- a/testapps/transactionalVoipApp/res/values-uz/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-uz/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Tranzaksiyaviy API sinovi faoliyati"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Chaqiruvda tranzaksiya faoliyati"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Telefon hisobini ro‘yxatdan o‘tkazish"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"chiquvchi"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"kiruvchi"</string>
- <string name="add_call_1" msgid="5825706540046010457">"chaqiruv qo‘shish 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"chaqiruvni uzish 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"chaqiruv qo‘shish 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"chaqiruv 2-ni faol qilish"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"chaqiruvni uzish 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"FGS boshlash (MT + fonda ilova simulyatsiyasi)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Chiquvchi chaqiruvni boshlash"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Kiruvchi chaqiruvni boshlash"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"chaqiruv id belgilanmagan"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"javob berish"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"uzish"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Quloq karnaychasi"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Karnay"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"strimingni boshlash"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-vi/strings.xml b/testapps/transactionalVoipApp/res/values-vi/strings.xml
index 2bcd65f..88362e4 100644
--- a/testapps/transactionalVoipApp/res/values-vi/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-vi/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Hoạt động kiểm tra cho API Xác nhận trao đổi"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Hoạt động giao dịch trong cuộc gọi"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Đăng ký tài khoản điện thoại"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"cuộc gọi đi"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"cuộc gọi đến"</string>
- <string name="add_call_1" msgid="5825706540046010457">"thêm cuộc gọi 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"ngắt cuộc gọi 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"thêm cuộc gọi 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"đặt cuộc gọi 2 ở trạng thái đang diễn ra"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"ngắt cuộc gọi 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Khởi động FGS (mô phỏng MT + ứng dụng trong nền)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Bắt đầu cuộc gọi đi"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Bắt đầu cuộc gọi đến"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"chưa đặt mã cuộc gọi"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"Đặt thành đang hoạt động"</string>
+ <string name="answer" msgid="5423590397665409939">"trả lời"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"Đặt thành không hoạt động"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"ngắt kết nối"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Loa tai nghe"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Loa"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"bắt đầu phát trực tuyến"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-zh-rCN/strings.xml b/testapps/transactionalVoipApp/res/values-zh-rCN/strings.xml
index 512fdea..4b816ba 100644
--- a/testapps/transactionalVoipApp/res/values-zh-rCN/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-zh-rCN/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"事务性 API 测试活动"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"通话活动中的事务"</string>
<string name="register_phone_account" msgid="1920315963082350332">"注册电话帐号"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"外拨电话"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"来电"</string>
- <string name="add_call_1" msgid="5825706540046010457">"添加通话 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"中断通话 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"添加通话 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"将通话 2 设置为通话中"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"中断通话 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"启动 FGS(在后台模拟 MT + 应用)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"开始去电"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"开始来电"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"未设置来电显示/本机号码"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"回复"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"断开连接"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"手机听筒"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"扬声器"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"蓝牙"</string>
+ <string name="start_stream" msgid="3567634786280097431">"开始直播"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-zh-rHK/strings.xml b/testapps/transactionalVoipApp/res/values-zh-rHK/strings.xml
index 7729a28..5b80831 100644
--- a/testapps/transactionalVoipApp/res/values-zh-rHK/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-zh-rHK/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Transactional API 測試活動"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"交易來電活動"</string>
<string name="register_phone_account" msgid="1920315963082350332">"註冊電話帳戶"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"撥出"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"來電"</string>
- <string name="add_call_1" msgid="5825706540046010457">"新增通話 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"中斷通話 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"新增通話 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"將通話 2 設為進行中"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"中斷通話 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"開始 FGS (模擬 MT + 背景應用程式)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"開始撥出電話"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"開始來電"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"未設定來電顯示"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"設為使用中"</string>
+ <string name="answer" msgid="5423590397665409939">"接聽"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"設為停用"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"解除連結"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"聽筒"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"喇叭"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"藍牙"</string>
+ <string name="start_stream" msgid="3567634786280097431">"開始串流播放"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-zh-rTW/strings.xml b/testapps/transactionalVoipApp/res/values-zh-rTW/strings.xml
index 90f8299..b8a2045 100644
--- a/testapps/transactionalVoipApp/res/values-zh-rTW/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-zh-rTW/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"交易 API 測試活動"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"通話活動交易資訊"</string>
<string name="register_phone_account" msgid="1920315963082350332">"註冊電話帳戶"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"撥出通話"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"來電"</string>
- <string name="add_call_1" msgid="5825706540046010457">"新增通話 1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"中斷通話 1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"新增通話 2"</string>
- <string name="set_call_active" msgid="248748409907478011">"將通話 2 設為啟用"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"中斷通話 2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"啟動 FGS (在背景模擬機器翻譯和應用程式)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"開始模擬撥出電話"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"開始模擬來電"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"未設定通話 ID"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"設為使用中"</string>
+ <string name="answer" msgid="5423590397665409939">"接聽"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"設為閒置"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"掛斷"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"耳機"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"喇叭"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"藍牙"</string>
+ <string name="start_stream" msgid="3567634786280097431">"開始串流播放"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values-zu/strings.xml b/testapps/transactionalVoipApp/res/values-zu/strings.xml
index 7689224..8e14895 100644
--- a/testapps/transactionalVoipApp/res/values-zu/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-zu/strings.xml
@@ -18,12 +18,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="2907804426411305091">"Umsebenzi wokuhlolwa kwe-Transactional API"</string>
+ <string name="in_call_activity_name" msgid="7545884666442897585">"Okwenziwayo Emsebenzini Wekholi"</string>
<string name="register_phone_account" msgid="1920315963082350332">"Bhalisa I-akhawunti Yefoni"</string>
- <string name="direction_outgoing" msgid="2535369086068422248">"okuphumayo"</string>
- <string name="direction_incoming" msgid="3279919900558410227">"okungenayo"</string>
- <string name="add_call_1" msgid="5825706540046010457">"engeza ikholi e-1"</string>
- <string name="disconnect_call_1" msgid="2687960802565131403">"Nqamula ikholi e-1"</string>
- <string name="add_call_2" msgid="6706005258041717434">"engeza ikholi yesi-2"</string>
- <string name="set_call_active" msgid="248748409907478011">"setha ikholi yesi-2 ukuthi isebenze"</string>
- <string name="disconnect_call_2" msgid="133113412102219516">"Nqamula ikholi yesi-2"</string>
+ <string name="start_foreground_service" msgid="8968755699895128574">"Qala ama-FGS (lingisa i-app ye-MT + ngemuva)"</string>
+ <string name="start_outgoing" msgid="1441644037370361864">"Qala ikholi ephumela ngaphandle"</string>
+ <string name="start_incoming" msgid="6444983300186361271">"Qala Ikholi Engenayo"</string>
+ <string name="get_call_id" msgid="5513943242738347108">"I-ID yekholi ayisethiwe"</string>
+ <string name="set_call_active" msgid="3365404393507589899">"I-setActive"</string>
+ <string name="answer" msgid="5423590397665409939">"impendulo"</string>
+ <string name="set_call_inactive" msgid="7106775211368705195">"I-setInactive"</string>
+ <string name="disconnect_call" msgid="1349412380315371385">"nqamula"</string>
+ <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Isipikha sendlebe"</string>
+ <string name="request_speaker_endpoint" msgid="1033259535289845405">"Isipikha"</string>
+ <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"I-Bluetooth"</string>
+ <string name="start_stream" msgid="3567634786280097431">"Qala ukusakaza-bukhoma"</string>
</resources>
diff --git a/testapps/transactionalVoipApp/res/values/strings.xml b/testapps/transactionalVoipApp/res/values/strings.xml
index 038adc1..23a5118 100644
--- a/testapps/transactionalVoipApp/res/values/strings.xml
+++ b/testapps/transactionalVoipApp/res/values/strings.xml
@@ -17,13 +17,26 @@
<resources>
<string name="app_name">Transactional API test Activity</string>
+ <string name="in_call_activity_name">Transactional In Call Activity</string>
+
+ <!-- Main Activity -->
<string name="register_phone_account">Register Phone Account</string>
- <string name="direction_outgoing">outgoing</string>
- <string name="direction_incoming">incoming</string>
- <string name="add_call_1">add call 1</string>
- <string name="disconnect_call_1">disconnect call 1</string>
- <string name="add_call_2">add call 2</string>
- <string name="set_call_active">set call 2 active</string>
- <string name="disconnect_call_2">disconnect call 2</string>
+ <string name="start_foreground_service">Start FGS (simulate MT + app in background)</string>
+ <string name="start_outgoing">Start Outgoing Call</string>
+ <string name="start_incoming">Start Incoming Call</string>
+
+ <!-- InCall Activity -->
+ <string name="get_call_id">call id not set</string>
+ <!-- control the call state -->
+ <string name="set_call_active">setActive</string>
+ <string name="answer">answer</string>
+ <string name="set_call_inactive">setInactive</string>
+ <string name="disconnect_call">disconnect</string>
+ <!-- control the call audio -->
+ <string name="request_earpiece_endpoint">Earpiece</string>
+ <string name="request_speaker_endpoint">Speaker</string>
+ <string name="request_bluetooth_endpoint">Bluetooth</string>
+ <!-- extra functionality -->
+ <string name="start_stream">start streaming</string>
</resources>
\ No newline at end of file
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/BackgroundIncomingCallService.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/BackgroundIncomingCallService.java
new file mode 100644
index 0000000..b503e94
--- /dev/null
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/BackgroundIncomingCallService.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.transactionalVoipApp;
+
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.util.Log;
+
+public class BackgroundIncomingCallService extends Service {
+ // finals
+ private static final String TAG = "BackgroundIncomingCallService";
+ // instance vars
+ private NotificationManager mNotificationManager;
+ private final IBinder mBinder = new LocalBinder();
+
+ @Override
+ public void onCreate() {
+ Log.i(TAG, "onCreate");
+ mNotificationManager = getSystemService(NotificationManager.class);
+ }
+
+ @Override
+ @StartResult
+ public int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {
+ Log.i(TAG, String.format("onStartCommand: intent=[%s]", intent));
+
+ // create the notification channel
+ if (mNotificationManager != null) {
+ mNotificationManager.createNotificationChannel(new NotificationChannel(
+ Utils.CHANNEL_ID, "incoming calls", NotificationManager.IMPORTANCE_DEFAULT));
+ }
+
+ // start the foreground service and post a notification
+ startForeground(98765, Utils.createCallStyleNotification(this),
+ FOREGROUND_SERVICE_TYPE_PHONE_CALL);
+
+ return Service.START_STICKY_COMPATIBILITY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.i(TAG, String.format("onBind: intent=[%s]", intent));
+ return mBinder;
+ }
+
+ /**
+ * Class used for the client Binder. Because we know this service always
+ * runs in the same process as its clients, we don't need to deal with IPC.
+ */
+ public class LocalBinder extends Binder {
+ BackgroundIncomingCallService getService() {
+ // Return this instance of LocalService so clients can call public methods
+ return BackgroundIncomingCallService.this;
+ }
+ }
+}
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java
new file mode 100644
index 0000000..4c9f52d
--- /dev/null
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.transactionalVoipApp;
+
+import static android.telecom.CallAttributes.AUDIO_CALL;
+import static android.telecom.CallAttributes.DIRECTION_INCOMING;
+import static android.telecom.CallAttributes.DIRECTION_OUTGOING;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.MediaPlayer;
+import android.net.StringNetworkSpecifier;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.OutcomeReceiver;
+import android.telecom.CallAttributes;
+import android.telecom.CallControl;
+import android.telecom.CallEndpoint;
+import android.telecom.CallException;
+import android.telecom.DisconnectCause;
+import android.telecom.TelecomManager;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+public class InCallActivity extends Activity {
+ private static final String TAG = "InCallActivity";
+ private final AudioManager.AudioRecordingCallback mAudioRecordingCallback =
+ Utils.getAudioRecordingCallback();
+ private static TelecomManager mTelecomManager;
+ private MyVoipCall mVoipCall;
+ private MediaPlayer mMediaPlayer;
+ private AudioRecord mAudioRecord;
+ private int mCallDirection = DIRECTION_INCOMING;
+ private TextView mCurrentEndpointTextView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Log.i(TAG, "#onCreate: in function");
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.in_call_activity);
+
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ mCallDirection = extras.getInt(Utils.sCALL_DIRECTION_KEY, DIRECTION_INCOMING);
+ }
+ mCurrentEndpointTextView = findViewById(R.id.current_endpoint);
+ mCurrentEndpointTextView.setText("Endpoint/Audio Route NOT ESTABLISHED");
+ updateCallId();
+ mTelecomManager = getSystemService(TelecomManager.class);
+ mMediaPlayer = Utils.createMediaPlayer(getApplicationContext());
+ mAudioRecord = Utils.createAudioRecord();
+ mAudioRecord.registerAudioRecordingCallback(Runnable::run, mAudioRecordingCallback);
+
+ if (mVoipCall == null) {
+ addCall();
+ }
+
+ findViewById(R.id.set_call_active_button).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ updateCurrentEndpoint();
+ if (canUseCallControl()) {
+ mVoipCall.mCallControl.setActive(Runnable::run,
+ Utils.getLoggableOutcomeReceiver("setActive"));
+ }
+ mAudioRecord.startRecording();
+ mMediaPlayer.start();
+ }
+ });
+
+
+ findViewById(R.id.answer_button).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ updateCurrentEndpoint();
+ if (canUseCallControl() && mCallDirection != DIRECTION_OUTGOING) {
+ mVoipCall.mCallControl.answer(AUDIO_CALL, Runnable::run,
+ Utils.getLoggableOutcomeReceiver("answer"));
+ mAudioRecord.startRecording();
+ mMediaPlayer.start();
+ }
+ }
+ });
+
+
+ findViewById(R.id.set_call_inactive_button).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (canUseCallControl()) {
+ mVoipCall.mCallControl.setInactive(Runnable::run,
+ Utils.getLoggableOutcomeReceiver("setInactive"));
+ }
+ mAudioRecord.stop();
+ mMediaPlayer.pause();
+ }
+ });
+
+ findViewById(R.id.disconnect_call_button).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ disconnectAndStopAudio();
+ finish();
+ }
+ });
+
+ findViewById(R.id.start_stream_button).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (canUseCallControl()) {
+ mVoipCall.mCallControl.startCallStreaming(Runnable::run,
+ Utils.getLoggableOutcomeReceiver("startCallStream"));
+ }
+ }
+ });
+
+ findViewById(R.id.request_earpiece).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (canUseCallControl() && mVoipCall.mEarpieceEndpoint != null) {
+ requestEndpointChange(mVoipCall.mEarpieceEndpoint,
+ "Request EARPIECE Endpoint:");
+ }
+ }
+ });
+
+ findViewById(R.id.request_speaker).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (canUseCallControl() && mVoipCall.mSpeakerEndpoint != null) {
+ requestEndpointChange(mVoipCall.mSpeakerEndpoint,
+ "Request SPEAKER Endpoint:");
+ }
+ }
+ });
+
+ findViewById(R.id.request_bluetooth).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (canUseCallControl() && mVoipCall.mBluetoothEndpoint != null) {
+ requestEndpointChange(mVoipCall.mBluetoothEndpoint,
+ "Request BLUETOOTH Endpoint:");
+ }
+ }
+ });
+ }
+
+ @Override
+ protected void onDestroy() {
+ disconnectAndStopAudio();
+ super.onDestroy();
+ }
+
+ private boolean canUseCallControl() {
+ return mVoipCall != null && mVoipCall.mCallControl != null;
+ }
+
+ private void updateCurrentEndpoint() {
+ if (mCurrentEndpointTextView != null) {
+ if (mVoipCall != null && mVoipCall.mCurrentEndpoint != null) {
+ mCurrentEndpointTextView.setText("CallEndpoint=[" +
+ mVoipCall.mCurrentEndpoint.getEndpointName() + "]");
+ }
+ }
+ }
+
+ private void updateCurrentEndpointWithOnResult(CallEndpoint endpoint) {
+ if (mCurrentEndpointTextView != null) {
+ if (mVoipCall != null && mVoipCall.mCurrentEndpoint != null) {
+ mCurrentEndpointTextView.setText("CallEndpoint=[" +
+ endpoint.getEndpointName() + "]");
+ }
+ }
+ }
+
+ private void updateCallId() {
+ TextView view = findViewById(R.id.getCallIdTextView);
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ if (canUseCallControl()) {
+ String id = mVoipCall.mCallControl.getCallId().toString();
+ sb.append(id);
+ } else {
+ sb.append("Error Getting Id");
+ }
+ sb.append("]");
+ view.setText(sb.toString());
+ }
+
+ private void addCall() {
+ mVoipCall = new MyVoipCall("123");
+
+ CallAttributes callAttributes =
+ new CallAttributes.Builder(
+ Utils.PHONE_ACCOUNT_HANDLE,
+ mCallDirection,
+ "Alan Turing",
+ Uri.parse("tel:6506959001")).build();
+
+ mTelecomManager.addCall(callAttributes, Runnable::run,
+ new OutcomeReceiver<CallControl, CallException>() {
+ @Override
+ public void onResult(CallControl callControl) {
+ Log.i(TAG, "addCall: onResult: callback fired");
+ mVoipCall.onAddCallControl(callControl);
+ updateCallId();
+ updateCurrentEndpoint();
+ }
+
+ @Override
+ public void onError(CallException exception) {
+
+ }
+ },
+ mVoipCall, mVoipCall);
+ }
+
+ private void disconnectAndStopAudio() {
+ if (mVoipCall != null) {
+ mVoipCall.mCallControl.disconnect(
+ new DisconnectCause(DisconnectCause.LOCAL),
+ Runnable::run,
+ Utils.getLoggableOutcomeReceiver("disconnect"));
+ }
+ mMediaPlayer.stop();
+ mAudioRecord.stop();
+ try {
+ mAudioRecord.unregisterAudioRecordingCallback(mAudioRecordingCallback);
+ } catch (IllegalArgumentException e) {
+ // pass through
+ }
+ }
+
+ private void requestEndpointChange(CallEndpoint endpoint, String tag) {
+ mVoipCall.mCallControl.requestCallEndpointChange(
+ endpoint,
+ Runnable::run,
+ new OutcomeReceiver<Void, CallException>() {
+ @Override
+ public void onResult(Void result) {
+ Log.i(TAG, String.format("success w/ %s", tag));
+ updateCurrentEndpointWithOnResult(endpoint);
+ }
+
+ @Override
+ public void onError(CallException e) {
+ Log.i(TAG, String.format("%s :failed to switch to endpoint=[%s],"
+ + " due to exception=[%s]", tag, endpoint, e.toString()));
+ }
+ });
+ }
+}
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java
index 690311e..e2b5b14 100644
--- a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java
@@ -24,6 +24,7 @@
import android.telecom.DisconnectCause;
import android.util.Log;
+import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
@@ -34,7 +35,12 @@
private static final String TAG = "MyVoipCall";
private final String mCallId;
- CallControl mCallControl;
+ public CallControl mCallControl;
+ public CallEndpoint mCurrentEndpoint;
+ public CallEndpoint mEarpieceEndpoint;
+ public CallEndpoint mSpeakerEndpoint;
+ public CallEndpoint mBluetoothEndpoint;
+ List<CallEndpoint> mAvailableEndpoint = new ArrayList<>();
MyVoipCall(String id) {
mCallId = id;
@@ -89,6 +95,7 @@
@Override
public void onCallEndpointChanged(@NonNull CallEndpoint newCallEndpoint) {
Log.i(TAG, String.format("onCallEndpointChanged: endpoint=[%s]", newCallEndpoint));
+ mCurrentEndpoint = newCallEndpoint;
}
@Override
@@ -97,7 +104,17 @@
Log.i(TAG, String.format("onAvailableCallEndpointsChanged: callId=[%s]", mCallId));
for (CallEndpoint endpoint : availableEndpoints) {
Log.i(TAG, String.format("endpoint=[%s]", endpoint));
+ if (endpoint != null && endpoint.getEndpointType() == CallEndpoint.TYPE_EARPIECE) {
+ mEarpieceEndpoint = endpoint;
+ }
+ if (endpoint != null && endpoint.getEndpointType() == CallEndpoint.TYPE_SPEAKER) {
+ mSpeakerEndpoint = endpoint;
+ }
+ if (endpoint != null && endpoint.getEndpointType() == CallEndpoint.TYPE_BLUETOOTH) {
+ mBluetoothEndpoint = endpoint;
+ }
}
+ mAvailableEndpoint = availableEndpoints;
}
@Override
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/Utils.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/Utils.java
new file mode 100644
index 0000000..98de790
--- /dev/null
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/Utils.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.transactionalVoipApp;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.AudioRecordingConfiguration;
+import android.media.MediaPlayer;
+import android.media.MediaRecorder;
+import android.os.OutcomeReceiver;
+import android.telecom.CallException;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.util.Log;
+
+import java.util.List;
+
+public class Utils {
+ public static final String TAG = "TransactionalAppUtils";
+ public static final String sEXTRAS_KEY = "ExtrasKey";
+ public static final String sCALL_DIRECTION_KEY = "CallDirectionKey";
+ public static final String CHANNEL_ID = "TelecomVoipAppChannelId";
+ private static final int SAMPLING_RATE_HZ = 44100;
+
+ public static final PhoneAccountHandle PHONE_ACCOUNT_HANDLE = new PhoneAccountHandle(
+ new ComponentName("com.android.server.telecom.transactionalVoipApp",
+ "com.android.server.telecom.transactionalVoipApp.VoipAppMainActivity"), "123");
+
+ public static final PhoneAccount PHONE_ACCOUNT =
+ PhoneAccount.builder(PHONE_ACCOUNT_HANDLE, "test label")
+ .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
+ PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS).build();
+
+
+ public static Notification createCallStyleNotification(Context context) {
+ Intent answerIntent = new Intent(context, InCallActivity.class);
+ Intent rejectIntent = new Intent(context, InCallActivity.class);
+
+ // Creating a pending intent and wrapping our intent
+ PendingIntent pendingAnswer = PendingIntent.getActivity(context, 0,
+ answerIntent, PendingIntent.FLAG_IMMUTABLE);
+ PendingIntent pendingReject = PendingIntent.getActivity(context, 0,
+ rejectIntent, PendingIntent.FLAG_IMMUTABLE);
+
+
+ Notification callStyleNotification = new Notification.Builder(context,
+ CHANNEL_ID)
+ .setContentText("Answer/Reject call")
+ .setContentTitle("Incoming call")
+ .setSmallIcon(R.drawable.ic_android_black_24dp)
+ .setStyle(Notification.CallStyle.forIncomingCall(
+ new Person.Builder().setName("Tom Stu").setImportant(true).build(),
+ pendingAnswer, pendingReject)
+ )
+ .setFullScreenIntent(pendingAnswer, true)
+ .build();
+
+ return callStyleNotification;
+ }
+
+ public static MediaPlayer createMediaPlayer(Context context) {
+ int audioToPlay = (Math.random() > 0.5f) ?
+ com.android.server.telecom.transactionalVoipApp.R.raw.sample_audio :
+ com.android.server.telecom.transactionalVoipApp.R.raw.sample_audio2;
+ MediaPlayer mediaPlayer = MediaPlayer.create(context, audioToPlay);
+ mediaPlayer.setLooping(true);
+ return mediaPlayer;
+ }
+
+ public static AudioRecord createAudioRecord() {
+ return new AudioRecord.Builder()
+ .setAudioFormat(new AudioFormat.Builder()
+ .setSampleRate(SAMPLING_RATE_HZ)
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
+ .setAudioSource(MediaRecorder.AudioSource.DEFAULT)
+ .setBufferSizeInBytes(
+ AudioRecord.getMinBufferSize(SAMPLING_RATE_HZ,
+ AudioFormat.CHANNEL_IN_MONO,
+ AudioFormat.ENCODING_PCM_16BIT) * 10)
+ .build();
+ }
+
+
+ public static AudioManager.AudioRecordingCallback getAudioRecordingCallback() {
+ return new AudioManager.AudioRecordingCallback() {
+ @Override
+ public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
+ super.onRecordingConfigChanged(configs);
+
+ for (AudioRecordingConfiguration config : configs) {
+ if (config != null) {
+ Log.i(TAG, String.format("onRecordingConfigChanged: random: "
+ + "isClientSilenced=[%b], config=[%s]",
+ config.isClientSilenced(), config));
+ }
+ }
+ }
+ };
+ }
+
+ public static OutcomeReceiver<Void, CallException> getLoggableOutcomeReceiver(String tag) {
+ return new OutcomeReceiver<Void, CallException>() {
+ @Override
+ public void onResult(Void result) {
+ Log.i(TAG, tag + " : onResult");
+ }
+
+ @Override
+ public void onError(CallException exception) {
+ Log.i(TAG, tag + " : onError");
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/VoipAppMainActivity.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/VoipAppMainActivity.java
index 4e1ec4c..ae7d9d0 100644
--- a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/VoipAppMainActivity.java
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/VoipAppMainActivity.java
@@ -20,7 +20,12 @@
import static android.telecom.CallAttributes.DIRECTION_OUTGOING;
import android.app.Activity;
-import android.content.ComponentName;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.OutcomeReceiver;
@@ -28,142 +33,115 @@
import android.telecom.CallControl;
import android.telecom.CallException;
import android.telecom.DisconnectCause;
-import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.util.Log;
import android.view.View;
import android.widget.ToggleButton;
public class VoipAppMainActivity extends Activity {
-
private static final String TAG = "VoipAppMainActivity";
+ private static final String ACT_STATE_TAG = "VoipActivityState";
private static TelecomManager mTelecomManager;
- private MyVoipCall mCall1;
- private MyVoipCall mCall2;
- private ToggleButton mCallDirectionButton;
-
- PhoneAccountHandle handle = new PhoneAccountHandle(
- new ComponentName("com.android.server.telecom.transactionalVoipApp",
- "com.android.server.telecom.transactionalVoipApp.VoipAppMainActivity"), "123");
-
- PhoneAccount mPhoneAccount = PhoneAccount.builder(handle, "test label")
- .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
- PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS).build();
+ NotificationManager mNotificationManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
+ Log.i(TAG, ACT_STATE_TAG + "onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
mTelecomManager = getSystemService(TelecomManager.class);
- mCallDirectionButton = findViewById(R.id.callDirectionButton);
+ mNotificationManager = getSystemService(NotificationManager.class);
+ // create a notification channel
+ if (mNotificationManager != null) {
+ mNotificationManager.createNotificationChannel(new NotificationChannel(
+ Utils.CHANNEL_ID, "new call channel",
+ NotificationManager.IMPORTANCE_DEFAULT));
+ }
// register account
findViewById(R.id.registerButton).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- mTelecomManager.registerPhoneAccount(mPhoneAccount);
+ mTelecomManager.registerPhoneAccount(Utils.PHONE_ACCOUNT);
}
});
- // call 1 buttons
- findViewById(R.id.add_call_1_button).setOnClickListener(new View.OnClickListener() {
+ // Start a foreground service that will post a notification within 10 seconds.
+ // This is helpful for debugging scenarios where the app is in the background and posting
+ // an incoming call notification.
+ findViewById(R.id.startForegroundService).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- Bundle extras = new Bundle();
- extras.putString("testKey", "testValue");
- mCall1 = new MyVoipCall("1");
- addCall(mCall1, true);
+ Intent startForegroundService = new Intent(getApplicationContext(),
+ BackgroundIncomingCallService.class);
+ getApplicationContext().startForegroundService(startForegroundService);
}
});
- findViewById(R.id.disconnect_call_1_button).setOnClickListener(new View.OnClickListener() {
+
+ // post a new call notification and start an InCall activity
+ findViewById(R.id.startOutgoingCall).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- disconnectCall(mCall1);
+ startInCallActivity(DIRECTION_OUTGOING);
}
});
-
- //call 2 buttons
- findViewById(R.id.add_call_2_button).setOnClickListener(new View.OnClickListener() {
+ // post a new call notification and start an InCall activity
+ findViewById(R.id.startIncomingCall).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- Bundle extras = new Bundle();
- extras.putString("call2extraKey", "call2Value");
- mCall2 = new MyVoipCall("2");
- addCall(mCall2, false);
+ startInCallActivity(DIRECTION_INCOMING);
}
});
- findViewById(R.id.set_call_2_active_button).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- setCallActive(mCall2);
- }
- });
-
- findViewById(R.id.disconnect_call_2_button).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- disconnectCall(mCall2);
- }
- });
}
- private void addCall(MyVoipCall call, boolean setActive) {
- int direction = (mCallDirectionButton.isChecked() ? DIRECTION_INCOMING
- : DIRECTION_OUTGOING);
-
- CallAttributes callAttributes = new CallAttributes.Builder(handle, direction, "Alan Turing",
- Uri.fromParts("tel", "abc", "123")).build();
-
- mTelecomManager.addCall(callAttributes, Runnable::run,
- new OutcomeReceiver<CallControl, CallException>() {
- @Override
- public void onResult(CallControl callControl) {
- Log.i(TAG, "addCall: onResult: callback fired");
- call.onAddCallControl(callControl);
- if (setActive) {
- setCallActive(call);
- }
- }
-
- @Override
- public void onError(CallException exception) {
-
- }
- },
- call, call);
+ private void startInCallActivity(int direction) {
+ mNotificationManager.notify(123456,
+ Utils.createCallStyleNotification(getApplicationContext()));
+ Bundle extras = new Bundle();
+ extras.putInt(Utils.sCALL_DIRECTION_KEY, direction);
+ Intent intent = new Intent(getApplicationContext(), InCallActivity.class);
+ intent.putExtra(Utils.sEXTRAS_KEY, extras);
+ startActivity(intent);
}
- private void setCallActive(MyVoipCall call) {
- call.mCallControl.setActive(Runnable::run, new OutcomeReceiver<Void, CallException>() {
- @Override
- public void onResult(Void result) {
- Log.i(TAG, "setCallActive: onResult");
- }
-
- @Override
- public void onError(CallException exception) {
- Log.i(TAG, "setCallActive: onError");
- }
- });
+ @Override
+ protected void onResume() {
+ Log.i(TAG, ACT_STATE_TAG + " onResume: When the activity enters the Resumed state,"
+ + " it comes to the foreground");
+ super.onResume();
}
- private void disconnectCall(MyVoipCall call) {
- call.mCallControl.disconnect(new DisconnectCause(DisconnectCause.LOCAL), Runnable::run,
- new OutcomeReceiver<Void, CallException>() {
- @Override
- public void onResult(Void result) {
- Log.i(TAG, "disconnectCall: onResult");
- }
+ @Override
+ protected void onPause() {
+ Log.i(TAG, ACT_STATE_TAG + " onPause: The system calls this method as the first"
+ + " indication that the user is leaving your activity. It indicates that the"
+ + " activity is no longer in the foreground, but it is still visible if the user"
+ + " is in multi-window mode");
+ super.onPause();
+ }
- @Override
- public void onError(CallException exception) {
- Log.i(TAG, "disconnectCall: onError");
- }
- });
+ @Override
+ protected void onStop() {
+ Log.i(TAG, ACT_STATE_TAG + "onStop: When your activity is no longer visible to"
+ + " the user, it enters the Stopped state,");
+ super.onStop();
+ }
+
+ @Override
+ protected void onRestart() {
+ Log.i(TAG, ACT_STATE_TAG + " onRestart: onStop has called onRestart and the "
+ + "activity comes back to interact with the user");
+ super.onRestart();
+ }
+
+ @Override
+ protected void onDestroy() {
+ Log.i(TAG, ACT_STATE_TAG + " onDestroy: is called before the activity is"
+ + " destroyed. ");
+ super.onDestroy();
}
}
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index c84db3b..4ca6030 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -44,6 +44,9 @@
<!-- Used to access PlatformCompat APIs -->
<uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
<uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
+
+ <!-- Used to register NotificationListenerService -->
+ <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<application android:label="@string/app_name"
android:debuggable="true">
diff --git a/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java b/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
index 93d1314..7e197fe 100644
--- a/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
@@ -21,11 +21,13 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.net.Uri;
+import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -37,6 +39,7 @@
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ClockProxy;
import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.EmergencyCallDiagnosticLogger;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.TelecomSystem;
@@ -85,6 +88,8 @@
@Mock private ConnectionServiceWrapper mMockConnectionService;
@Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
+ @Mock private EmergencyCallDiagnosticLogger mMockEmergencyCallDiagnosticLogger;
+
@Override
@Before
public void setUp() throws Exception {
@@ -116,7 +121,7 @@
doReturn(new ComponentName(mContext, CallTest.class))
.when(mMockConnectionService).getComponentName();
mCallAnomalyWatchdog = new CallAnomalyWatchdog(mTestScheduledExecutorService, mLock,
- mTimeouts, mMockClockProxy);
+ mTimeouts, mMockClockProxy, mMockEmergencyCallDiagnosticLogger);
mCallAnomalyWatchdog.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
}
@@ -755,6 +760,90 @@
}
/**
+ * Emulate the case where a new incoming call is created but the connection fails for a known
+ * reason before being added to CallsManager. In this case, the watchdog should stop tracking
+ * the call and not trigger an anomaly report.
+ */
+ @Test
+ public void testIncomingCallCreatedButNotAddedNoAnomalyReport() {
+ //The call is created:
+ Call call = getCall();
+ call.setState(CallState.NEW, "foo");
+ call.setIsCreateConnectionComplete(false);
+ mCallAnomalyWatchdog.onStartCreateConnection(call);
+
+ //The connection fails before being added to CallsManager for a known reason:
+ call.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+ // Move the clock forward:
+ when(mMockClockProxy.elapsedRealtime()).
+ thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+ mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+
+ //Ensure an anomaly report is not generated:
+ verify(mAnomalyReporterAdapter, never()).reportAnomaly(
+ CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
+ CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
+ }
+
+ /**
+ * Emulate the case where a new outgoing call is created but the connection fails for a known
+ * reason before being added to CallsManager. In this case, the watchdog should stop tracking
+ * the call and not trigger an anomaly report.
+ */
+ @Test
+ public void testOutgoingCallCreatedButNotAddedNoAnomalyReport() {
+ //The call is created:
+ Call call = getCall();
+ call.setCallDirection(Call.CALL_DIRECTION_OUTGOING);
+ call.setState(CallState.NEW, "foo");
+ call.setIsCreateConnectionComplete(false);
+ mCallAnomalyWatchdog.onStartCreateConnection(call);
+
+ //The connection fails before being added to CallsManager for a known reason.
+ call.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+ // Move the clock forward:
+ when(mMockClockProxy.elapsedRealtime()).
+ thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+ mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+
+ //Ensure an anomaly report is not generated:
+ verify(mAnomalyReporterAdapter, never()).reportAnomaly(
+ CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
+ CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
+ }
+
+ /**
+ * Emulate the case where a new incoming call is created but the connection fails for a known
+ * reason before being added to CallsManager and CallsManager notifies the watchdog by invoking
+ * {@link CallsManager.CallsManagerListener#onCreateConnectionFailed(Call)}.
+ * In this case, the watchdog should stop tracking the call and not trigger an anomaly report.
+ */
+ @Test
+ public void testCallCreatedButNotAddedPreventsAnomalyReport() {
+ //The call is created:
+ Call call = getCall();
+ call.setState(CallState.NEW, "foo");
+ call.setIsCreateConnectionComplete(false);
+ mCallAnomalyWatchdog.onStartCreateConnection(call);
+
+ //Telecom cancels the connection before adding it to CallsManager:
+ mCallAnomalyWatchdog.onCreateConnectionFailed(call);
+
+ // Move the clock forward:
+ when(mMockClockProxy.elapsedRealtime()).
+ thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+ mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+
+ //Ensure an anomaly report is not generated:
+ verify(mAnomalyReporterAdapter, never()).reportAnomaly(
+ CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
+ CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
+ }
+
+
+ /**
* @return an instance of {@link Call} for testing purposes.
*/
private Call getCall() {
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index f53c953..569c487 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -153,7 +153,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
// Since we don't know if we're on a platform with an earpiece or not, all we can do
// is ensure the stateMachine construction didn't fail. But at least we exercised the
@@ -172,7 +173,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
Set<Call> trackedCalls = new HashSet<>(Arrays.asList(fakeCall, fakeSelfManagedCall));
@@ -217,7 +219,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
@@ -260,7 +263,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
@@ -305,7 +309,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -349,7 +354,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
Collection<BluetoothDevice> availableDevices = Collections.singleton(bluetoothDevice1);
@@ -427,7 +433,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -463,7 +470,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
setInBandRing(false);
when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -518,7 +526,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
List<BluetoothDevice> availableDevices =
Arrays.asList(bluetoothDevice1, bluetoothDevice2, bluetoothDevice3);
@@ -568,7 +577,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
@@ -599,7 +609,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
@@ -633,7 +644,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
List<BluetoothDevice> availableDevices =
Arrays.asList(bluetoothDevice1, bluetoothDevice2);
@@ -748,7 +760,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.initialize();
assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
}
@@ -764,7 +777,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
@@ -801,7 +815,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
earpieceControl,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.initialize();
assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
}
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
index 5caa432..cf684de 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
@@ -269,7 +269,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
mParams.earpieceControl,
- mHandlerThread.getLooper());
+ mHandlerThread.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
setupMocksForParams(stateMachine, mParams);
@@ -365,7 +366,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
mParams.earpieceControl,
- mHandlerThread.getLooper());
+ mHandlerThread.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
// Set up bluetooth and speakerphone state
diff --git a/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
index 51d5960..01446d1 100644
--- a/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
@@ -354,4 +354,28 @@
assertEquals(REDIRECTED_GATEWAY_NUMBER_WITH_POST_DIAL,
gatewayInfoArgumentCaptor.getValue().getGatewayAddress());
}
+
+ @Test
+ public void testUnbindOnNullBind() throws Exception {
+ startProcessWithNoGateWayInfo();
+ // To make sure tests are not flaky, clean all the previous handler messages
+ waitForHandlerAction(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY);
+ enableUserDefinedCallRedirectionService();
+ disableCarrierCallRedirectionService();
+
+ mProcessor.performCallRedirection(UserHandle.CURRENT);
+
+ // Capture the binder
+ ArgumentCaptor<ServiceConnection> serviceConnectionCaptor = ArgumentCaptor.forClass(
+ ServiceConnection.class);
+ // Verify binding occurred
+ verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
+ serviceConnectionCaptor.capture(), anyInt(), eq(UserHandle.CURRENT));
+ // Simulate null return from onBind
+ serviceConnectionCaptor.getValue().onNullBinding(USER_DEFINED_SERVICE_TEST_COMPONENT_NAME);
+
+ // Verify service was unbound
+ verify(mContext, times(1)).
+ unbindService(any(ServiceConnection.class));
+ }
}
diff --git a/tests/src/com/android/server/telecom/tests/CallTest.java b/tests/src/com/android/server/telecom/tests/CallTest.java
index 6b817d8..997e7dd 100644
--- a/tests/src/com/android/server/telecom/tests/CallTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallTest.java
@@ -26,16 +26,11 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.content.ComponentName;
import android.content.Intent;
@@ -43,7 +38,7 @@
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Parcel;
+import android.telecom.CallAttributes;
import android.telecom.CallerInfo;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
@@ -56,12 +51,10 @@
import android.telecom.VideoProfile;
import android.telephony.CallQuality;
import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
import android.widget.Toast;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.android.internal.telecom.IVideoProvider;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallIdMapper;
import com.android.server.telecom.CallState;
@@ -69,8 +62,6 @@
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ClockProxy;
import com.android.server.telecom.ConnectionServiceWrapper;
-import com.android.server.telecom.InCallController;
-import com.android.server.telecom.InCallController.InCallServiceInfo;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.TelecomSystem;
@@ -640,8 +631,40 @@
verify(listener).onFailedUnknownCall(unknownCall);
}
+ /**
+ * ensure a Call object does not throw an NPE when the CallingPackageIdentity is not set and
+ * the correct values are returned when set
+ */
@Test
@SmallTest
+ public void testCallingPackageIdentity() {
+ final int packageUid = 123;
+ final int packagePid = 1;
+
+ Call call = createCall("1");
+
+ // assert default values for a Calls CallingPackageIdentity are -1 unless set via the setter
+ assertEquals(-1, call.getCallingPackageIdentity().mCallingPackageUid);
+ assertEquals(-1, call.getCallingPackageIdentity().mCallingPackagePid);
+
+ // set the Call objects CallingPackageIdentity via the setter and a bundle
+ Bundle extras = new Bundle();
+ extras.putInt(CallAttributes.CALLER_UID_KEY, packageUid);
+ extras.putInt(CallAttributes.CALLER_PID_KEY, packagePid);
+ // assert that the setter removed the extras
+ assertEquals(packageUid, extras.getInt(CallAttributes.CALLER_UID_KEY));
+ assertEquals(packagePid, extras.getInt(CallAttributes.CALLER_PID_KEY));
+ call.setCallingPackageIdentity(extras);
+ // assert that the setter removed the extras
+ assertEquals(0, extras.getInt(CallAttributes.CALLER_UID_KEY));
+ assertEquals(0, extras.getInt(CallAttributes.CALLER_PID_KEY));
+ // assert the properties are fetched correctly
+ assertEquals(packageUid, call.getCallingPackageIdentity().mCallingPackageUid);
+ assertEquals(packagePid, call.getCallingPackageIdentity().mCallingPackagePid);
+ }
+
+ @Test
+ @SmallTest
public void testOnConnectionEventNotifiesListener() {
Call.Listener listener = mock(Call.Listener.class);
Call call = createCall("1");
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 7544dc9..fc4e935 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -58,16 +58,19 @@
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.telecom.CallerInfo;
+import android.provider.BlockedNumberContract;
+import android.telecom.CallException;
import android.telecom.CallScreeningService;
+import android.telecom.CallerInfo;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
import android.telecom.GatewayInfo;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
-import android.telecom.CallException;
import android.telecom.VideoProfile;
+import android.telephony.CarrierConfigManager;
+import android.telephony.PhoneCapability;
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
@@ -81,9 +84,9 @@
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioModeStateMachine;
import com.android.server.telecom.CallAudioRouteStateMachine;
+import com.android.server.telecom.CallDiagnosticServiceController;
import com.android.server.telecom.CallEndpointController;
import com.android.server.telecom.CallEndpointControllerFactory;
-import com.android.server.telecom.CallDiagnosticServiceController;
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallerInfoLookupHelper;
import com.android.server.telecom.CallsManager;
@@ -92,6 +95,7 @@
import com.android.server.telecom.ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory;
import com.android.server.telecom.ConnectionServiceWrapper;
import com.android.server.telecom.DefaultDialerCache;
+import com.android.server.telecom.EmergencyCallDiagnosticLogger;
import com.android.server.telecom.EmergencyCallHelper;
import com.android.server.telecom.HandoverState;
import com.android.server.telecom.HeadsetMediaButton;
@@ -114,10 +118,12 @@
import com.android.server.telecom.WiredHeadsetManager;
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
+import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
import com.android.server.telecom.callfiltering.CallFilteringResult;
import com.android.server.telecom.ui.AudioProcessingNotification;
import com.android.server.telecom.ui.DisconnectedCallNotifier;
import com.android.server.telecom.ui.ToastFactory;
+import com.android.server.telecom.voip.TransactionManager;
import org.junit.After;
import org.junit.Before;
@@ -135,16 +141,15 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@RunWith(JUnit4.class)
public class CallsManagerTest extends TelecomTestCase {
private static final int TEST_TIMEOUT = 5000; // milliseconds
+ private static final long STATE_TIMEOUT = 5000L;
private static final int SECONDARY_USER_ID = 12;
private static final PhoneAccountHandle SIM_1_HANDLE = new PhoneAccountHandle(
ComponentName.unflattenFromString("com.foo/.Blah"), "Sim1");
@@ -235,8 +240,12 @@
@Mock private ToastFactory mToastFactory;
@Mock private Toast mToast;
@Mock private CallAnomalyWatchdog mCallAnomalyWatchdog;
+
+ @Mock private EmergencyCallDiagnosticLogger mEmergencyCallDiagnosticLogger;
@Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
@Mock private Ringer.AccessibilityManagerAdapter mAccessibilityManagerAdapter;
+ @Mock private BlockedNumbersAdapter mBlockedNumbersAdapter;
+ @Mock private PhoneCapability mPhoneCapability;
private CallsManager mCallsManager;
@@ -256,7 +265,7 @@
when(mCallEndpointControllerFactory.create(any(), any(), any())).thenReturn(
mCallEndpointController);
when(mCallAudioRouteStateMachineFactory.create(any(), any(), any(), any(), any(), any(),
- anyInt())).thenReturn(mCallAudioRouteStateMachine);
+ anyInt(), any())).thenReturn(mCallAudioRouteStateMachine);
when(mCallAudioModeStateMachineFactory.create(any(), any()))
.thenReturn(mCallAudioModeStateMachine);
when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
@@ -267,6 +276,9 @@
.thenReturn(mDisconnectedCallNotifier);
when(mTimeoutsAdapter.getCallDiagnosticServiceTimeoutMillis(any(ContentResolver.class)))
.thenReturn(2000L);
+ when(mTimeoutsAdapter.getNonVoipCallTransitoryStateTimeoutMillis())
+ .thenReturn(STATE_TIMEOUT);
+ when(mClockProxy.elapsedRealtime()).thenReturn(0L);
mCallsManager = new CallsManager(
mComponentContextFixture.getTestDouble().getApplicationContext(),
mLock,
@@ -299,9 +311,12 @@
mToastFactory,
mCallEndpointControllerFactory,
mCallAnomalyWatchdog,
+ mAccessibilityManagerAdapter,
// Just do async tasks synchronously to support testing.
command -> command.run(),
- mAccessibilityManagerAdapter);
+ mBlockedNumbersAdapter,
+ TransactionManager.getTestInstance(),
+ mEmergencyCallDiagnosticLogger);
when(mPhoneAccountRegistrar.getPhoneAccount(
eq(SELF_MANAGED_HANDLE), any())).thenReturn(SELF_MANAGED_ACCOUNT);
@@ -326,6 +341,26 @@
assertEquals(0, mCallsManager.constructPossiblePhoneAccounts(null, null, false, false).size());
}
+ private Call constructOngoingCall(String callId, PhoneAccountHandle phoneAccountHandle) {
+ Call ongoingCall = new Call(
+ callId,
+ mContext,
+ mCallsManager,
+ mLock,
+ null /* ConnectionServiceRepository */,
+ mPhoneNumberUtilsAdapter,
+ TEST_ADDRESS,
+ null /* GatewayInfo */,
+ null /* connectionManagerPhoneAccountHandle */,
+ phoneAccountHandle,
+ Call.CALL_DIRECTION_INCOMING,
+ false /* shouldAttachToExistingConnection*/,
+ false /* isConference */,
+ mClockProxy,
+ mToastFactory);
+ ongoingCall.setState(CallState.ACTIVE, "just cuz");
+ return ongoingCall;
+ }
/**
* Verify behavior for multisim devices where we want to ensure that the active sim is used for
* placing a new call.
@@ -336,23 +371,7 @@
public void testConstructPossiblePhoneAccountsMultiSimActive() throws Exception {
setupMsimAccounts();
- Call ongoingCall = new Call(
- "1", /* callId */
- mContext,
- mCallsManager,
- mLock,
- null /* ConnectionServiceRepository */,
- mPhoneNumberUtilsAdapter,
- TEST_ADDRESS,
- null /* GatewayInfo */,
- null /* connectionManagerPhoneAccountHandle */,
- SIM_2_HANDLE,
- Call.CALL_DIRECTION_INCOMING,
- false /* shouldAttachToExistingConnection*/,
- false /* isConference */,
- mClockProxy,
- mToastFactory);
- ongoingCall.setState(CallState.ACTIVE, "just cuz");
+ Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
mCallsManager.addCall(ongoingCall);
List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
@@ -375,6 +394,47 @@
assertEquals(2, phoneAccountHandles.size());
}
+ /**
+ * For DSDA-enabled multisim devices with an ongoing call, verify that both SIMs'
+ * PhoneAccountHandles are constructed while placing a new call.
+ * @throws Exception
+ */
+ @MediumTest
+ @Test
+ public void testConstructPossiblePhoneAccountsMultiSimActive_dsdaCallingPossible() throws
+ Exception {
+ setupMsimAccounts();
+ setMaxActiveVoiceSubscriptions(2);
+
+ Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
+ mCallsManager.addCall(ongoingCall);
+
+ List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+ TEST_ADDRESS, null, false, false);
+ assertEquals(2, phoneAccountHandles.size());
+ }
+
+ /**
+ * For DSDA-enabled multisim devices with an ongoing call, verify that only the active SIMs'
+ * PhoneAccountHandle is constructed while placing an emergency call.
+ * @throws Exception
+ */
+ @MediumTest
+ @Test
+ public void testConstructPossiblePhoneAccountsMultiSimActive_dsdaCallingPossible_emergencyCall()
+ throws Exception {
+ setupMsimAccounts();
+ setMaxActiveVoiceSubscriptions(2);
+
+ Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
+ mCallsManager.addCall(ongoingCall);
+
+ List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+ TEST_ADDRESS, null, false, true /* isEmergency */);
+ assertEquals(1, phoneAccountHandles.size());
+ assertEquals(SIM_2_HANDLE, phoneAccountHandles.get(0));
+ }
+
private void setupCallerInfoLookupHelper() {
doAnswer(invocation -> {
Uri handle = invocation.getArgument(0);
@@ -632,6 +692,54 @@
verify(heldCall).unhold(any());
}
+ /**
+ * Ensures we don't auto-unhold a call from a different app when we locally disconnect a call.
+ */
+ @SmallTest
+ @Test
+ public void testDontUnholdCallsBetweenConnectionServices() {
+ // GIVEN a CallsManager with ongoing call
+ Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+ when(ongoingCall.isDisconnectHandledViaFuture()).thenReturn(false);
+ doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+ doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+ when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+ // and a held call which has different ConnectionService
+ Call heldCall = addSpyCall(VOIP_1_HANDLE, CallState.ON_HOLD);
+
+ // Disconnect and cleanup the active ongoing call.
+ mCallsManager.disconnectCall(ongoingCall);
+ mCallsManager.markCallAsRemoved(ongoingCall);
+
+ // Should not unhold the held call since its in another app.
+ verify(heldCall, never()).unhold();
+ }
+
+ /**
+ * Ensures we do auto-unhold a call from the same app when we locally disconnect a call.
+ */
+ @SmallTest
+ @Test
+ public void testUnholdCallWhenDisconnectingInSameApp() {
+ // GIVEN a CallsManager with ongoing call
+ Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+ when(ongoingCall.isDisconnectHandledViaFuture()).thenReturn(false);
+ doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+ doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+ when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+ // and a held call which has same ConnectionService
+ Call heldCall = addSpyCall(SIM_1_HANDLE, CallState.ON_HOLD);
+
+ // Disconnect and cleanup the active ongoing call.
+ mCallsManager.disconnectCall(ongoingCall);
+ mCallsManager.markCallAsRemoved(ongoingCall);
+
+ // Should auto-unhold the held call since its in the same app.
+ verify(heldCall).unhold();
+ }
+
@SmallTest
@Test
public void testUnholdCallWhenOngoingEmergCallCanNotBeHeldAndFromDifferentConnectionService() {
@@ -1459,6 +1567,8 @@
.thenReturn(false);
newCall.setHandle(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED);
+ // Make sure enough time has passed that we'd drop the connecting call.
+ when(mClockProxy.elapsedRealtime()).thenReturn(STATE_TIMEOUT + 10L);
assertTrue(mCallsManager.makeRoomForOutgoingCall(newCall));
verify(mAnomalyReporterAdapter).reportAnomaly(
CallsManager.LIVE_CALL_STUCK_CONNECTING_ERROR_UUID,
@@ -1466,6 +1576,26 @@
verify(ongoingCall).disconnect(anyLong(), anyString());
}
+ /**
+ * Verifies that we won't auto-disconnect an outgoing CONNECTING call unless it has timed out.
+ */
+ @SmallTest
+ @Test
+ public void testDontDisconnectConnectingCallWhenNotTimedOut() {
+ mCallsManager.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
+ Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.CONNECTING);
+
+ Call newCall = createCall(SIM_1_HANDLE, CallState.NEW);
+ when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+ .thenReturn(false);
+ newCall.setHandle(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED);
+
+ // Make sure it has been a short time so we don't try to disconnect the call
+ when(mClockProxy.elapsedRealtime()).thenReturn(STATE_TIMEOUT / 2);
+ assertFalse(mCallsManager.makeRoomForOutgoingCall(newCall));
+ verify(ongoingCall, never()).disconnect(anyLong(), anyString());
+ }
+
@SmallTest
@Test
public void testMakeRoomForEmergencyCallHasOutgoingCall() {
@@ -1539,6 +1669,7 @@
Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.CONNECTING);
Call newCall = createCall(SIM_1_HANDLE, CallState.NEW);
+ when(mClockProxy.elapsedRealtime()).thenReturn(STATE_TIMEOUT + 10L);
assertTrue(mCallsManager.makeRoomForOutgoingCall(newCall));
verify(ongoingCall).disconnect(anyLong(), anyString());
}
@@ -1548,7 +1679,7 @@
public void testMakeRoomForOutgoingCallForSameCall() {
addSpyCall(SIM_2_HANDLE, CallState.CONNECTING);
Call ongoingCall2 = addSpyCall();
-
+ when(mClockProxy.elapsedRealtime()).thenReturn(STATE_TIMEOUT + 10L);
assertTrue(mCallsManager.makeRoomForOutgoingCall(ongoingCall2));
}
@@ -2000,6 +2131,79 @@
SELF_MANAGED_HANDLE.getUserHandle()));
}
+ /**
+ * Emulate the case where a new incoming call is created but the connection fails for a known
+ * reason before being added to CallsManager. In this case, the listeners should be notified
+ * properly.
+ */
+ @Test
+ public void testIncomingCallCreatedButNotAddedNotifyListener() {
+ //The call is created and a listener is added:
+ Call incomingCall = createCall(SIM_2_HANDLE, null, CallState.NEW);
+ CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+ mCallsManager.addListener(listener);
+
+ //The connection fails before being added to CallsManager for a known reason:
+ incomingCall.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+ //Ensure the listener is notified properly:
+ verify(listener).onCreateConnectionFailed(incomingCall);
+ }
+
+ /**
+ * Emulate the case where a new incoming call is created but the connection fails for a known
+ * reason after being added to CallsManager. Since the call was added to CallsManager, the
+ * listeners should not be notified via onCreateConnectionFailed().
+ */
+ @Test
+ public void testIncomingCallCreatedAndAddedDoNotNotifyListener() {
+ //The call is created and a listener is added:
+ Call incomingCall = createCall(SIM_2_HANDLE, null, CallState.NEW);
+ CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+ mCallsManager.addListener(listener);
+
+ //The call is added to CallsManager:
+ mCallsManager.addCall(incomingCall);
+
+ //The connection fails after being added to CallsManager for a known reason:
+ incomingCall.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+ //Since the call was added to CallsManager, onCreateConnectionFailed shouldn't be invoked:
+ verify(listener, never()).onCreateConnectionFailed(incomingCall);
+ }
+
+ /**
+ * Emulate the case where a new outgoing call is created but is aborted before being added to
+ * CallsManager since there are no available phone accounts. In this case, the listeners
+ * should be notified properly.
+ */
+ @Test
+ public void testAbortOutgoingCallNoPhoneAccountsNotifyListeners() throws Exception {
+ // Setup a new outgoing call and add a listener
+ Call newCall = addSpyCall(CallState.NEW);
+ CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+ mCallsManager.addListener(listener);
+
+ // Ensure contact info lookup succeeds but do not set the phone account info
+ doAnswer(invocation -> {
+ Uri handle = invocation.getArgument(0);
+ CallerInfo info = new CallerInfo();
+ CompletableFuture<Pair<Uri, CallerInfo>> callerInfoFuture = new CompletableFuture<>();
+ callerInfoFuture.complete(new Pair<>(handle, info));
+ return callerInfoFuture;
+ }).when(mCallerInfoLookupHelper).startLookup(any(Uri.class));
+
+ // Start the outgoing call
+ CompletableFuture<Call> callFuture = mCallsManager.startOutgoingCall(
+ newCall.getHandle(), newCall.getTargetPhoneAccount(), new Bundle(),
+ UserHandle.CURRENT, new Intent(), "com.test.stuff");
+ Call result = callFuture.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+ //Ensure the listener is notified properly:
+ verify(listener).onCreateConnectionFailed(any());
+ assertNull(result);
+ }
+
@Test
public void testIsInSelfManagedCallOnlySelfManaged() {
Call selfManagedCall = createCall(SELF_MANAGED_HANDLE, CallState.ACTIVE);
@@ -2637,6 +2841,61 @@
assertTrue(mCallsManager.hasSelfManagedCalls());
}
+ /**
+ * Verifies when {@link CallsManager} receives a carrier config change it will trigger an
+ * update of the emergency call notification.
+ * Note: this test mocks out {@link BlockedNumbersAdapter} so does not actually test posting of
+ * the notification. Notification posting in the actual implementation is covered by
+ * {@link BlockedNumbersUtilTests}.
+ */
+ @SmallTest
+ @Test
+ public void testUpdateEmergencyCallNotificationOnCarrierConfigChange() {
+ when(mBlockedNumbersAdapter.shouldShowEmergencyCallNotification(any(Context.class)))
+ .thenReturn(true);
+ mComponentContextFixture.getBroadcastReceivers().forEach(c -> c.onReceive(mContext,
+ new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)));
+ verify(mBlockedNumbersAdapter).updateEmergencyCallNotification(any(Context.class),
+ eq(true));
+
+ when(mBlockedNumbersAdapter.shouldShowEmergencyCallNotification(any(Context.class)))
+ .thenReturn(false);
+ mComponentContextFixture.getBroadcastReceivers().forEach(c -> c.onReceive(mContext,
+ new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)));
+ verify(mBlockedNumbersAdapter).updateEmergencyCallNotification(any(Context.class),
+ eq(false));
+ }
+
+ /**
+ * Verifies when {@link CallsManager} receives a signal from the blocked number provider that
+ * the call blocking enabled state changes, it will trigger an update of the emergency call
+ * notification.
+ * Note: this test mocks out {@link BlockedNumbersAdapter} so does not actually test posting of
+ * the notification. Notification posting in the actual implementation is covered by
+ * {@link BlockedNumbersUtilTests}.
+ */
+ @SmallTest
+ @Test
+ public void testUpdateEmergencyCallNotificationOnNotificationVisibilityChange() {
+ when(mBlockedNumbersAdapter.shouldShowEmergencyCallNotification(any(Context.class)))
+ .thenReturn(true);
+ mComponentContextFixture.getBroadcastReceivers().forEach(c -> c.onReceive(mContext,
+ new Intent(
+ BlockedNumberContract.SystemContract
+ .ACTION_BLOCK_SUPPRESSION_STATE_CHANGED)));
+ verify(mBlockedNumbersAdapter).updateEmergencyCallNotification(any(Context.class),
+ eq(true));
+
+ when(mBlockedNumbersAdapter.shouldShowEmergencyCallNotification(any(Context.class)))
+ .thenReturn(false);
+ mComponentContextFixture.getBroadcastReceivers().forEach(c -> c.onReceive(mContext,
+ new Intent(
+ BlockedNumberContract.SystemContract
+ .ACTION_BLOCK_SUPPRESSION_STATE_CHANGED)));
+ verify(mBlockedNumbersAdapter).updateEmergencyCallNotification(any(Context.class),
+ eq(false));
+ }
+
private Call addSpyCall() {
return addSpyCall(SIM_2_HANDLE, CallState.ACTIVE);
}
@@ -2729,4 +2988,10 @@
when(mPhoneAccountRegistrar.getSimPhoneAccountsOfCurrentUser()).thenReturn(
new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
}
+
+ private void setMaxActiveVoiceSubscriptions(int num) {
+ TelephonyManager mockTelephonyManager = mComponentContextFixture.getTelephonyManager();
+ when(mockTelephonyManager.getPhoneCapability()).thenReturn(mPhoneCapability);
+ when(mPhoneCapability.getMaxActiveVoiceSubscriptions()).thenReturn(num);
+ }
}
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index e3a1471..cc22de2 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -55,9 +55,13 @@
import android.location.LocationManager;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
+import android.os.BugreportManager;
import android.os.Bundle;
+import android.os.DropBoxManager;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IInterface;
+import android.os.Looper;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.UserHandle;
@@ -110,6 +114,7 @@
* property points to an application context implementing all the nontrivial functionality.
*/
public class ComponentContextFixture implements TestFixture<Context> {
+ private HandlerThread mHandlerThread;
public class FakeApplicationContext extends MockContext {
@Override
@@ -275,8 +280,12 @@
return Context.NOTIFICATION_SERVICE;
} else if (svcClass == AccessibilityManager.class) {
return Context.ACCESSIBILITY_SERVICE;
+ } else if (svcClass == DropBoxManager.class) {
+ return Context.DROPBOX_SERVICE;
+ } else if (svcClass == BugreportManager.class) {
+ return Context.BUGREPORT_SERVICE;
}
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(svcClass.getName());
}
@Override
@@ -305,6 +314,15 @@
}
@Override
+ public Looper getMainLooper() {
+ if (mHandlerThread == null) {
+ mHandlerThread = new HandlerThread(this.getClass().getSimpleName());
+ mHandlerThread.start();
+ }
+ return mHandlerThread.getLooper();
+ }
+
+ @Override
public ContentResolver getContentResolver() {
return new ContentResolver(mApplicationContextSpy) {
@Override
@@ -344,30 +362,34 @@
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
- // TODO -- this is called by WiredHeadsetManager!!!
+ mBroadcastReceivers.add(receiver);
return null;
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {
+ mBroadcastReceivers.add(receiver);
return null;
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
+ mBroadcastReceivers.add(receiver);
return null;
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler, int flags) {
+ mBroadcastReceivers.add(receiver);
return null;
}
@Override
public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle handle,
IntentFilter filter, String broadcastPermission, Handler scheduler) {
+ mBroadcastReceivers.add(receiver);
return null;
}
@@ -591,6 +613,7 @@
mock(PermissionCheckerManager.class);
private final PermissionInfo mPermissionInfo = mock(PermissionInfo.class);
private final SensorPrivacyManager mSensorPrivacyManager = mock(SensorPrivacyManager.class);
+ private final List<BroadcastReceiver> mBroadcastReceivers = new ArrayList<>();
private TelecomManager mTelecomManager = mock(TelecomManager.class);
@@ -795,6 +818,10 @@
return mNotificationManager;
}
+ public List<BroadcastReceiver> getBroadcastReceivers() {
+ return mBroadcastReceivers;
+ }
+
private void addService(String action, ComponentName name, IInterface service) {
mComponentNamesByAction.put(action, name);
mServiceByComponentName.put(name, service);
diff --git a/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
index f977ddb..dbcab66 100644
--- a/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
+++ b/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
@@ -517,6 +517,43 @@
}
/**
+ * Ensure that the call goes out on the PhoneAccount for the incoming call and not the
+ * Telephony preferred emergency account.
+ */
+ @SmallTest
+ @Test
+ public void testMTEmergencyCallMultiSimUserPreferred() throws Exception {
+ when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockCall.isTestEmergencyCall()).thenReturn(false);
+ when(mMockCall.isIncoming()).thenReturn(true);
+ ConnectionServiceWrapper service = makeConnectionServiceWrapper();
+ PhoneAccount emergencyPhoneAccount1 = makeEmergencyPhoneAccount("tel_emer1", 0, null);
+ mapToSubSlot(emergencyPhoneAccount1, 1 /*subId*/, 0 /*slotId*/);
+ setTargetPhoneAccount(mMockCall, emergencyPhoneAccount1.getAccountHandle());
+ phoneAccounts.add(emergencyPhoneAccount1);
+ // Make this the user preferred phone account
+ setTargetPhoneAccount(mMockCall, emergencyPhoneAccount1.getAccountHandle());
+ PhoneAccount emergencyPhoneAccount2 = makeEmergencyPhoneAccount("tel_emer2",
+ PhoneAccount.CAPABILITY_EMERGENCY_PREFERRED, null);
+ mapToSubSlot(emergencyPhoneAccount2, 2 /*subId*/, 1 /*slotId*/);
+ phoneAccounts.add(emergencyPhoneAccount2);
+ PhoneAccountHandle emergencyPhoneAccountHandle2 = emergencyPhoneAccount2.getAccountHandle();
+
+ mTestCreateConnectionProcessor.process();
+
+ verify(mMockCall).setConnectionManagerPhoneAccount(
+ eq(emergencyPhoneAccount1.getAccountHandle()));
+ // The account we're using to place the call should be the user preferred account
+ verify(mMockCall).setTargetPhoneAccount(eq(emergencyPhoneAccount1.getAccountHandle()));
+ verify(mMockCall).setConnectionService(eq(service));
+ verify(service).createConnection(eq(mMockCall), any(CreateConnectionResponse.class));
+ // Notify successful connection to call
+ CallIdMapper mockCallIdMapper = mock(CallIdMapper.class);
+ mTestCreateConnectionProcessor.handleCreateConnectionSuccess(mockCallIdMapper, null);
+ verify(mMockCreateConnectionResponse).handleCreateConnectionSuccess(mockCallIdMapper, null);
+ }
+
+ /**
* If the user preferred PhoneAccount is associated with an invalid slot, place on the other,
* valid slot.
*/
diff --git a/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java b/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java
new file mode 100644
index 0000000..3cb8196
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.tests;
+
+
+import static android.telephony.TelephonyManager.EmergencyCallDiagnosticParams;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.BugreportManager;
+import android.os.DropBoxManager;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.TelephonyManager;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallerInfoLookupHelper;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ClockProxy;
+import com.android.server.telecom.EmergencyCallDiagnosticLogger;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.PhoneNumberUtilsAdapter;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.ui.ToastFactory;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(JUnit4.class)
+public class EmergencyCallDiagnosticLoggerTest extends TelecomTestCase {
+
+ private static final ComponentName COMPONENT_NAME_1 = ComponentName
+ .unflattenFromString("com.foo/.Blah");
+ private static final PhoneAccountHandle SIM_1_HANDLE = new PhoneAccountHandle(
+ COMPONENT_NAME_1, "Sim1");
+ private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.
+ Builder(SIM_1_HANDLE, "Sim1")
+ .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
+ | PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .setIsEnabled(true)
+ .build();
+ private static final String DROP_BOX_TAG = "ecall_diagnostic_data";
+
+ private static final long EMERGENCY_CALL_ACTIVE_TIME_THRESHOLD_MILLIS = 100L;
+
+ private static final long EMERGENCY_CALL_TIME_BEFORE_USER_DISCONNECT_THRESHOLD_MILLIS = 120L;
+
+ private static final int DAYS_BACK_TO_SEARCH_EMERGENCY_DIAGNOSTIC_ENTRIES = 1;
+ private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {
+ };
+ EmergencyCallDiagnosticLogger mEmergencyCallDiagnosticLogger;
+ @Mock
+ private Timeouts.Adapter mTimeouts;
+ @Mock
+ private CallsManager mMockCallsManager;
+ @Mock
+ private CallerInfoLookupHelper mMockCallerInfoLookupHelper;
+ @Mock
+ private PhoneAccountRegistrar mMockPhoneAccountRegistrar;
+ @Mock
+ private ClockProxy mMockClockProxy;
+ @Mock
+ private ToastFactory mMockToastProxy;
+ @Mock
+ private PhoneNumberUtilsAdapter mMockPhoneNumberUtilsAdapter;
+
+ @Mock
+ private TelephonyManager mTm;
+ @Mock
+ private BugreportManager mBrm;
+ @Mock
+ private DropBoxManager mDbm;
+
+ @Mock
+ private ClockProxy mClockProxy;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ doReturn(mMockCallerInfoLookupHelper).when(mMockCallsManager).getCallerInfoLookupHelper();
+ doReturn(mMockPhoneAccountRegistrar).when(mMockCallsManager).getPhoneAccountRegistrar();
+ doReturn(SIM_1_ACCOUNT).when(mMockPhoneAccountRegistrar).getPhoneAccountUnchecked(
+ eq(SIM_1_HANDLE));
+ when(mTimeouts.getEmergencyCallActiveTimeThresholdMillis()).
+ thenReturn(EMERGENCY_CALL_ACTIVE_TIME_THRESHOLD_MILLIS);
+ when(mTimeouts.getEmergencyCallTimeBeforeUserDisconnectThresholdMillis()).
+ thenReturn(EMERGENCY_CALL_TIME_BEFORE_USER_DISCONNECT_THRESHOLD_MILLIS);
+ when(mTimeouts.getDaysBackToSearchEmergencyDiagnosticEntries()).
+ thenReturn(DAYS_BACK_TO_SEARCH_EMERGENCY_DIAGNOSTIC_ENTRIES);
+ when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
+
+ mEmergencyCallDiagnosticLogger = new EmergencyCallDiagnosticLogger(mTm, mBrm,
+ mTimeouts, mDbm, Runnable::run, mClockProxy);
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ //reset(mTm);
+ }
+
+ /**
+ * Helper function that creates the call being tested.
+ * Also invokes onStartCreateConnection
+ */
+ private Call createCall(boolean isEmergencyCall, int direction) {
+ Call call = getCall();
+ call.setCallDirection(direction);
+ call.setIsEmergencyCall(isEmergencyCall);
+ mEmergencyCallDiagnosticLogger.onStartCreateConnection(call);
+ return call;
+ }
+
+ /**
+ * @return an instance of {@link Call} for testing purposes.
+ */
+ private Call getCall() {
+ return new Call(
+ "1", /* callId */
+ mContext,
+ mMockCallsManager,
+ mLock,
+ null /* ConnectionServiceRepository */,
+ mMockPhoneNumberUtilsAdapter,
+ Uri.parse("tel:6505551212"),
+ null /* GatewayInfo */,
+ null /* connectionManagerPhoneAccountHandle */,
+ SIM_1_HANDLE,
+ Call.CALL_DIRECTION_OUTGOING,
+ false /* shouldAttachToExistingConnection*/,
+ false /* isConference */,
+ mMockClockProxy,
+ mMockToastProxy);
+ }
+
+ /**
+ * Test that only outgoing emergency calls are tracked
+ */
+ @Test
+ public void testNonEmergencyCallNotTracked() {
+ //should not be tracked
+ createCall(false, Call.CALL_DIRECTION_OUTGOING);
+ assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+
+ //should not be tracked (not in scope)
+ createCall(false, Call.CALL_DIRECTION_INCOMING);
+ assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+ }
+
+ /**
+ * Test that incoming emergency calls are not tracked (not in scope right now)
+ */
+ @Test
+ public void testIncomingEmergencyCallsNotTracked() {
+ //should not be tracked
+ createCall(true, Call.CALL_DIRECTION_INCOMING);
+ assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+ }
+
+
+ /**
+ * Test getDataCollectionTypes(reason)
+ */
+ @Test
+ public void testCollectionTypeForReasonDoesNotReturnUnreasonableValues() {
+ int reason = EmergencyCallDiagnosticLogger.REPORT_REASON_RANGE_START + 1;
+ while (reason < EmergencyCallDiagnosticLogger.REPORT_REASON_RANGE_END) {
+ List<Integer> ctypes = EmergencyCallDiagnosticLogger.getDataCollectionTypes(reason);
+ assertNotNull(ctypes);
+ Set<Integer> ctypesSet = new HashSet<>(ctypes);
+
+ //assert that list is not empty
+ assertNotEquals(0, ctypes.size());
+
+ //assert no repeated values
+ assertEquals(ctypes.size(), ctypesSet.size());
+
+ //if bugreport type is present, that should be the only collection type
+ if (ctypesSet.contains(EmergencyCallDiagnosticLogger.COLLECTION_TYPE_BUGREPORT)) {
+ assertEquals(1, ctypes.size());
+ }
+ reason++;
+ }
+ }
+
+
+ /**
+ * Test emergency call reported stuck
+ */
+ @Test
+ public void testStuckEmergencyCall() {
+ Call call = createCall(true, Call.CALL_DIRECTION_OUTGOING);
+ mEmergencyCallDiagnosticLogger.onCallAdded(call);
+ mEmergencyCallDiagnosticLogger.reportStuckCall(call);
+
+ //for stuck calls, we should always be persisting some data
+ ArgumentCaptor<EmergencyCallDiagnosticParams> captor =
+ ArgumentCaptor.forClass(EmergencyCallDiagnosticParams.class);
+ verify(mTm, times(1)).persistEmergencyCallDiagnosticData(eq(DROP_BOX_TAG),
+ captor.capture());
+ EmergencyCallDiagnosticParams dp = captor.getValue();
+
+ assertNotNull(dp);
+ assertTrue(
+ dp.isLogcatCollectionEnabled() || dp.isTelecomDumpSysCollectionEnabled()
+ || dp.isTelephonyDumpSysCollectionEnabled());
+
+ //tracking should end
+ assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+ }
+
+ @Test
+ public void testEmergencyCallNeverWentActiveWithNonLocalDisconnectCause() {
+ Call call = createCall(true, Call.CALL_DIRECTION_OUTGOING);
+ mEmergencyCallDiagnosticLogger.onCallAdded(call);
+
+ //call is tracked
+ assertEquals(1, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+
+ call.setDisconnectCause(new DisconnectCause(DisconnectCause.REJECTED));
+ mEmergencyCallDiagnosticLogger.onCallRemoved(call);
+
+ //for non-local disconnect of non-active call, we should always be persisting some data
+ ArgumentCaptor<TelephonyManager.EmergencyCallDiagnosticParams> captor =
+ ArgumentCaptor.forClass(
+ TelephonyManager.EmergencyCallDiagnosticParams.class);
+ verify(mTm, times(1)).persistEmergencyCallDiagnosticData(eq(DROP_BOX_TAG),
+ captor.capture());
+ TelephonyManager.EmergencyCallDiagnosticParams dp = captor.getValue();
+
+ assertNotNull(dp);
+ assertTrue(
+ dp.isLogcatCollectionEnabled() || dp.isTelecomDumpSysCollectionEnabled()
+ || dp.isTelephonyDumpSysCollectionEnabled());
+
+ //tracking should end
+ assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+ }
+
+ @Test
+ public void testEmergencyCallWentActiveForLongDuration_shouldNotCollectDiagnostics()
+ throws Exception {
+ Call call = createCall(true, Call.CALL_DIRECTION_OUTGOING);
+ mEmergencyCallDiagnosticLogger.onCallAdded(call);
+
+ //call went active
+ mEmergencyCallDiagnosticLogger.onCallStateChanged(call, CallState.DIALING,
+ CallState.ACTIVE);
+
+ //return large value for time when call is disconnected
+ when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis() + 10000L);
+
+ call.setDisconnectCause(new DisconnectCause(DisconnectCause.ERROR));
+ mEmergencyCallDiagnosticLogger.onCallRemoved(call);
+
+ //no diagnostic data should be persisted
+ verify(mTm, never()).persistEmergencyCallDiagnosticData(eq(DROP_BOX_TAG),
+ any());
+
+ //tracking should end
+ assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+ }
+
+}
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index f50753d..e61bc2a 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -35,7 +35,6 @@
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -53,10 +52,12 @@
import android.app.UiModeManager;
import android.content.AttributionSource;
import android.content.AttributionSourceState;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.PermissionChecker;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
@@ -64,9 +65,10 @@
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.compat.testing.PlatformCompatChangeRule;
-import android.os.Binder;
+import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -74,6 +76,7 @@
import android.os.Looper;
import android.os.Process;
import android.os.UserHandle;
+import android.os.UserManager;
import android.permission.PermissionCheckerManager;
import android.telecom.CallAudioState;
import android.telecom.InCallService;
@@ -122,14 +125,13 @@
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
+import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
-import libcore.junit.util.compat.CoreCompatChangeRule;
-
@RunWith(JUnit4.class)
public class InCallControllerTests extends TelecomTestCase {
@Mock CallsManager mMockCallsManager;
@@ -150,29 +152,31 @@
@Mock PermissionInfo mMockPermissionInfo;
@Mock InCallController.InCallServiceInfo mInCallServiceInfo;
@Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
+ @Mock UserManager mMockUserManager;
+ @Mock UserInfo mMockUserInfo;
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
- private static final int CURRENT_USER_ID = 900973;
+ private static final int CURRENT_USER_ID = 9;
private static final String DEF_PKG = "defpkg";
private static final String DEF_CLASS = "defcls";
- private static final int DEF_UID = 1;
+ private static final int DEF_UID = 900972;
private static final String SYS_PKG = "syspkg";
private static final String SYS_CLASS = "syscls";
- private static final int SYS_UID = 2;
+ private static final int SYS_UID = 900971;
private static final String COMPANION_PKG = "cpnpkg";
private static final String COMPANION_CLASS = "cpncls";
- private static final int COMPANION_UID = 3;
+ private static final int COMPANION_UID = 900970;
private static final String CAR_PKG = "carpkg";
private static final String CAR2_PKG = "carpkg2";
private static final String CAR_CLASS = "carcls";
private static final String CAR2_CLASS = "carcls";
- private static final int CAR_UID = 4;
- private static final int CAR2_UID = 5;
+ private static final int CAR_UID = 900969;
+ private static final int CAR2_UID = 900968;
private static final String NONUI_PKG = "nonui_pkg";
private static final String NONUI_CLASS = "nonui_cls";
- private static final int NONUI_UID = 6;
+ private static final int NONUI_UID = 900973;
private static final String APPOP_NONUI_PKG = "appop_nonui_pkg";
private static final String APPOP_NONUI_CLASS = "appop_nonui_cls";
private static final int APPOP_NONUI_UID = 7;
@@ -180,6 +184,7 @@
private static final PhoneAccountHandle PA_HANDLE =
new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"),
"pa_id_0", UserHandle.of(CURRENT_USER_ID));
+ private static final UserHandle DUMMY_USER_HANDLE = UserHandle.of(10);
private UserHandle mUserHandle = UserHandle.of(CURRENT_USER_ID);
private InCallController mInCallController;
@@ -187,6 +192,7 @@
private EmergencyCallHelper mEmergencyCallHelper;
private SystemStateHelper.SystemStateListener mSystemStateListener;
private CarModeTracker mCarModeTracker = spy(new CarModeTracker());
+ private BroadcastReceiver mRegisteredReceiver;
private final int serviceBindingFlags = Context.BIND_AUTO_CREATE
| Context.BIND_FOREGROUND_SERVICE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
@@ -199,6 +205,7 @@
MockitoAnnotations.initMocks(this);
when(mMockCall.getAnalytics()).thenReturn(new Analytics.CallInfo());
when(mMockCall.getUserHandleFromTargetPhoneAccount()).thenReturn(mUserHandle);
+ when(mMockCall.getId()).thenReturn("TC@1");
doReturn(mMockResources).when(mMockContext).getResources();
doReturn(mMockAppOpsManager).when(mMockContext).getSystemService(AppOpsManager.class);
doReturn(SYS_PKG).when(mMockResources).getString(
@@ -222,6 +229,12 @@
mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager,
mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter,
mEmergencyCallHelper, mCarModeTracker, mClockProxy);
+ // Capture the broadcast receiver registered.
+ doAnswer(invocation -> {
+ mRegisteredReceiver = invocation.getArgument(0);
+ return null;
+ }).when(mMockContext).registerReceiver(any(BroadcastReceiver.class),
+ any(IntentFilter.class));
ArgumentCaptor<SystemStateHelper.SystemStateListener> systemStateListenerArgumentCaptor
= ArgumentCaptor.forClass(SystemStateHelper.SystemStateListener.class);
@@ -281,6 +294,11 @@
.thenReturn(PackageManager.PERMISSION_DENIED);
when(mMockCallsManager.getAudioState()).thenReturn(new CallAudioState(false, 0, 0));
+
+ when(mMockContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mMockUserManager);
+ // Mock user info to allow binding on user stored in the phone account (mUserHandle).
+ when(mMockUserManager.getUserInfo(anyInt())).thenReturn(mMockUserInfo);
+ when(mMockUserInfo.isManagedProfile()).thenReturn(true);
}
@Override
@@ -425,60 +443,6 @@
@MediumTest
@Test
- public void testBindToService_OutgoingCall_FailToBind_AnomalyReported() throws Exception {
- mInCallController.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
- when(mMockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
- anyInt(), any(UserHandle.class))).thenReturn(false);
- Bundle callExtras = new Bundle();
- callExtras.putBoolean("whatever", true);
-
- when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
- when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
- when(mMockCall.isIncoming()).thenReturn(false);
- when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
- when(mMockCall.getIntentExtras()).thenReturn(callExtras);
- when(mMockCall.isExternalCall()).thenReturn(false);
- when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
- .thenReturn(300_000L);
-
- setupMockPackageManager(false /* default */, true /* system */, false /* external calls */);
- mInCallController.bindToServices(mMockCall);
-
- verify(mAnomalyReporterAdapter).reportAnomaly(InCallController.BIND_TO_IN_CALL_ERROR_UUID,
- InCallController.BIND_TO_IN_CALL_ERROR_MSG);
- }
-
- @MediumTest
- @Test
- public void testBindToService_OutgoingEmergCall_FailToBind_AnomalyReported() throws Exception {
- mInCallController.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
- when(mMockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
- anyInt(), any(UserHandle.class))).thenReturn(false);
- Bundle callExtras = new Bundle();
- callExtras.putBoolean("whatever", true);
-
- when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
- when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
- when(mMockCall.isEmergencyCall()).thenReturn(true);
- when(mMockCall.isIncoming()).thenReturn(false);
- when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
- when(mMockCall.getIntentExtras()).thenReturn(callExtras);
- when(mMockCall.isExternalCall()).thenReturn(false);
- when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
- .thenReturn(300_000L);
-
- setupMockPackageManager(false /* default */, true /* system */, false /* external calls */);
- mInCallController.bindToServices(mMockCall);
-
- verify(mAnomalyReporterAdapter).reportAnomaly(
- InCallController.BIND_TO_IN_CALL_EMERGENCY_ERROR_UUID,
- InCallController.BIND_TO_IN_CALL_EMERGENCY_ERROR_MSG);
- }
-
- @MediumTest
- @Test
public void testBindToService_DefaultDialer_NoEmergency() throws Exception {
Bundle callExtras = new Bundle();
callExtras.putBoolean("whatever", true);
@@ -539,6 +503,9 @@
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockContext.getSystemService(eq(UserManager.class)))
+ .thenReturn(mMockUserManager);
+ when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
when(mMockCall.isIncoming()).thenReturn(false);
when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mMockCall.getIntentExtras()).thenReturn(callExtras);
@@ -598,6 +565,60 @@
eq(Manifest.permission.ACCESS_FINE_LOCATION), eq(mUserHandle));
}
+ @MediumTest
+ @Test
+ public void
+ testBindToService_UserAssociatedWithCallIsInQuietMode_EmergCallInCallUi_BindsToPrimaryUser()
+ throws Exception {
+ when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockCall.getUserHandleFromTargetPhoneAccount()).thenReturn(DUMMY_USER_HANDLE);
+ when(mMockContext.getSystemService(eq(UserManager.class)))
+ .thenReturn(mMockUserManager);
+ when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(true);
+ setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+ setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
+
+ mInCallController.bindToServices(mMockCall);
+
+ ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockContext, times(1)).bindServiceAsUser(
+ bindIntentCaptor.capture(),
+ any(ServiceConnection.class),
+ eq(serviceBindingFlags),
+ eq(mUserHandle));
+ Intent bindIntent = bindIntentCaptor.getValue();
+ assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
+ }
+
+ @MediumTest
+ @Test
+ public void
+ testBindToService_UserAssociatedWithCallNotInQuietMode_EmergCallInCallUi_BindsToAssociatedUser()
+ throws Exception {
+ when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockCall.getUserHandleFromTargetPhoneAccount()).thenReturn(DUMMY_USER_HANDLE);
+ when(mMockContext.getSystemService(eq(UserManager.class)))
+ .thenReturn(mMockUserManager);
+ when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
+ setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+ setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
+
+ mInCallController.bindToServices(mMockCall);
+
+ ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockContext, times(1)).bindServiceAsUser(
+ bindIntentCaptor.capture(),
+ any(ServiceConnection.class),
+ eq(serviceBindingFlags),
+ eq(DUMMY_USER_HANDLE));
+ Intent bindIntent = bindIntentCaptor.getValue();
+ assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
+ }
+
/**
* This test verifies the behavior of Telecom when the system dialer crashes on binding and must
* be restarted. Specifically, it ensures when the system dialer crashes we revoke the runtime
@@ -614,6 +635,9 @@
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockContext.getSystemService(eq(UserManager.class)))
+ .thenReturn(mMockUserManager);
+ when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
when(mMockCall.isIncoming()).thenReturn(false);
when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mMockCall.getIntentExtras()).thenReturn(callExtras);
@@ -870,6 +894,114 @@
}
/**
+ * This test verifies the behavior of Telecom when the system dialer crashes on binding and must
+ * be restarted. Specifically, it ensures when the system dialer crashes we revoke the runtime
+ * location permission, and when it restarts we re-grant the permission.
+ * @throws Exception
+ */
+ @MediumTest
+ @Test
+ public void testBindToLateConnectionNonUiIcs() throws Exception {
+ Bundle callExtras = new Bundle();
+ callExtras.putBoolean("whatever", true);
+
+ // Make a basic call and bind to the default dialer.
+ when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
+ when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockContext.getSystemService(eq(UserManager.class)))
+ .thenReturn(mMockUserManager);
+ when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
+ when(mMockCall.isIncoming()).thenReturn(false);
+ when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+ when(mMockCall.getIntentExtras()).thenReturn(callExtras);
+ when(mMockCall.isExternalCall()).thenReturn(false);
+ when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID))
+ .thenReturn(DEF_PKG);
+ ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
+ ArgumentCaptor.forClass(ServiceConnection.class);
+ when(mMockContext.bindServiceAsUser(any(Intent.class), serviceConnectionCaptor.capture(),
+ eq(serviceBindingFlags),
+ eq(mUserHandle))).thenReturn(true);
+ when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
+ .thenReturn(300_000L);
+
+ // Setup package manager; there is a dialer and disable non-ui ICS
+ when(mMockPackageManager.queryIntentServicesAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(
+ Arrays.asList(
+ getDefResolveInfo(false /* externalCalls */, false /* selfMgd */),
+ getNonUiResolveinfo(false /* selfManaged */,
+ false /* isEnabled */)
+ )
+ );
+ when(mMockPackageManager
+ .getComponentEnabledSetting(new ComponentName(DEF_PKG, DEF_CLASS)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ when(mMockPackageManager
+ .getComponentEnabledSetting(new ComponentName(NONUI_PKG, NONUI_CLASS)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+
+ mInCallController.addCall(mMockCall);
+ mInCallController.bindToServices(mMockCall);
+
+ // There will be 4 calls for the various types of ICS.
+ verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
+ any(Intent.class),
+ eq(PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS),
+ eq(CURRENT_USER_ID));
+
+ // Verify bind to the dialer
+ ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockContext, times(1)).bindServiceAsUser(
+ bindIntentCaptor.capture(),
+ any(ServiceConnection.class),
+ eq(serviceBindingFlags),
+ eq(mUserHandle));
+
+ Intent bindIntent = bindIntentCaptor.getValue();
+ assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
+ assertEquals(SYS_PKG, bindIntent.getComponent().getPackageName());
+ assertEquals(SYS_CLASS, bindIntent.getComponent().getClassName());
+
+ // Setup mocks to enable nonui ICS
+ when(mMockPackageManager.queryIntentServicesAsUser(
+ any(Intent.class), anyInt(), anyInt())).thenReturn(
+ Arrays.asList(
+ getDefResolveInfo(false /* externalCalls */, false /* selfMgd */),
+ getNonUiResolveinfo(false /* selfManaged */,
+ true /* isEnabled */)
+ )
+ );
+ when(mMockPackageManager
+ .getComponentEnabledSetting(new ComponentName(NONUI_PKG, NONUI_CLASS)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+ // Emulate a late enable of the non-ui ICS
+ Intent packageUpdated = new Intent(Intent.ACTION_PACKAGE_CHANGED);
+ packageUpdated.setData(Uri.fromParts("package", NONUI_PKG, null));
+ packageUpdated.putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
+ new String[] {NONUI_CLASS});
+ packageUpdated.putExtra(Intent.EXTRA_UID, NONUI_UID);
+ mRegisteredReceiver.onReceive(mMockContext, packageUpdated);
+
+ // Now, we expect to auto-rebind to the system dialer (verify 2 times since this is the
+ // second binding).
+ verify(mMockContext, times(2)).bindServiceAsUser(
+ bindIntentCaptor.capture(),
+ any(ServiceConnection.class),
+ eq(serviceBindingFlags),
+ eq(mUserHandle));
+
+ // Unbind!
+ mInCallController.unbindFromServices(UserHandle.of(CURRENT_USER_ID));
+
+ // Make sure we unbound 2 times
+ verify(mMockContext, times(2)).unbindService(any(ServiceConnection.class));
+ }
+
+ /**
* Ensures that the {@link InCallController} will bind to an {@link InCallService} which
* supports external calls.
*/
@@ -1455,6 +1587,26 @@
android.telecom.Call.EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB));
}
+ @Test
+ public void testSecondaryUserCallBindToCurrentUser() throws Exception {
+ setupMocks(true /* isExternalCall */);
+ setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+ // Force the difference between the phone account user and current user. This is supposed to
+ // simulate a secondary user placing a call over an unassociated sim.
+ assertFalse(mUserHandle.equals(UserHandle.USER_CURRENT));
+ when(mMockUserInfo.isManagedProfile()).thenReturn(false);
+
+ mInCallController.bindToServices(mMockCall);
+
+ // Bind InCallService on UserHandle.CURRENT and not the user from the call (mUserHandle)
+ ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockContext, times(1)).bindServiceAsUser(
+ bindIntentCaptor.capture(),
+ any(ServiceConnection.class),
+ eq(serviceBindingFlags),
+ eq(UserHandle.CURRENT));
+ }
+
private void setupMocks(boolean isExternalCall) {
setupMocks(isExternalCall, false /* isSelfManagedCall */);
}
@@ -1549,14 +1701,14 @@
}};
}
- private ResolveInfo getNonUiResolveinfo(boolean supportsSelfManaged) {
+ private ResolveInfo getNonUiResolveinfo(boolean supportsSelfManaged, boolean isEnabled) {
return new ResolveInfo() {{
serviceInfo = new ServiceInfo();
serviceInfo.packageName = NONUI_PKG;
serviceInfo.name = NONUI_CLASS;
serviceInfo.applicationInfo = new ApplicationInfo();
serviceInfo.applicationInfo.uid = NONUI_UID;
- serviceInfo.enabled = true;
+ serviceInfo.enabled = isEnabled;
serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
serviceInfo.metaData = new Bundle();
if (supportsSelfManaged) {
@@ -1647,7 +1799,7 @@
} else {
// InCallController uses a blank package name when querying for non-ui incalls
if (useNonUiInCalls) {
- resolveInfo.add(getNonUiResolveinfo(includeSelfManagedCallsInNonUi));
+ resolveInfo.add(getNonUiResolveinfo(includeSelfManagedCallsInNonUi, true));
}
// InCallController uses a blank package name when querying for App Op non-ui incalls
if (useAppOpNonUiInCalls) {
diff --git a/tests/src/com/android/server/telecom/tests/MissedInformationTest.java b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
index f28a781..8ea2739 100644
--- a/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
+++ b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
@@ -16,6 +16,7 @@
package com.android.server.telecom.tests;
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
import static android.provider.CallLog.Calls.AUTO_MISSED_EMERGENCY_CALL;
import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_DIALING;
import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_RINGING;
@@ -117,6 +118,8 @@
mPackageManager = mContext.getPackageManager();
when(mPackageManager.getPackageUid(anyString(), eq(0))).thenReturn(Binder.getCallingUid());
mCountDownLatch = new CountDownLatch(1);
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .when(mContext).checkCallingPermission(MODIFY_PHONE_STATE);
}
@Override
@@ -356,6 +359,7 @@
doReturn(mNotificationManager).when(mSpyContext)
.getSystemService(Context.NOTIFICATION_SERVICE);
doReturn(false).when(mNotificationManager).matchesCallFilter(any(Bundle.class));
+ doReturn(false).when(mIncomingCall).wasDndCheckComputedForCall();
mCallsManager.getRinger().setNotificationManager(mNotificationManager);
CallFilteringResult result = new CallFilteringResult.Builder()
diff --git a/tests/src/com/android/server/telecom/tests/MmiUtilsTest.java b/tests/src/com/android/server/telecom/tests/MmiUtilsTest.java
new file mode 100644
index 0000000..ed74637
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/MmiUtilsTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.tests;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.net.Uri;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.MmiUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class MmiUtilsTest extends TelecomTestCase {
+
+ private static final String[] sDangerousDialStrings = {
+ "*21*1234567#", // fwd unconditionally to 1234567,
+ "*67*1234567#", // fwd to 1234567 when line is busy
+ "*61*1234567#", // fwd to 1234567 when no one picks up
+ "*62*1234567#", // fwd to 1234567 when out of range
+ "*004*1234567#", // fwd to 1234567 when busy, not pickup up, out of range
+ "*004*1234567#", // fwd to 1234567 conditionally
+ "**21*1234567#", // fwd unconditionally to 1234567
+
+ // north american vertical service codes
+
+ "*094565678", // Selective Call Blocking/Reporting
+ "*4278889", // Change Forward-To Number for Customer Programmable Call Forwarding Don't
+ // Answer
+ "*5644456", // Change Forward-To Number for ISDN Call Forwarding
+ "*6045677", // Selective Call Rejection Activation
+ "*635678", // Selective Call Forwarding Activation
+ "*64678899", // Selective Call Acceptance Activation
+ "*683456", // Call Forwarding Busy Line/Don't Answer Activation
+ "*721234", // Call Forwarding Activation
+ "*77", // Anonymous Call Rejection Activation
+ "*78", // Do Not Disturb Activation
+ };
+
+ private MmiUtils mMmiUtils = new MmiUtils();
+ private static final String[] sNonDangerousDialStrings = {"*6712345678", "*272", "*272911"};
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @SmallTest
+ @Test
+ public void testDangerousDialStringsDetected() throws Exception {
+ for (String s : sDangerousDialStrings) {
+ Uri.Builder b = new Uri.Builder();
+ b.scheme("tel").opaquePart(s);
+ assertTrue(mMmiUtils.isDangerousMmiOrVerticalCode(b.build()));
+ }
+ }
+
+ @SmallTest
+ @Test
+ public void testNonDangerousDialStringsNotDetected() throws Exception {
+ for (String s : sNonDangerousDialStrings) {
+ Uri.Builder b = new Uri.Builder();
+ b.scheme("tel").opaquePart(s);
+ assertFalse(mMmiUtils.isDangerousMmiOrVerticalCode(b.build()));
+ }
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
index 169aeb2..f2bcf18 100644
--- a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
+++ b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
@@ -56,6 +56,7 @@
import com.android.server.telecom.Call;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.DefaultDialerCache;
+import com.android.server.telecom.MmiUtils;
import com.android.server.telecom.NewOutgoingCallIntentBroadcaster;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.PhoneNumberUtilsAdapter;
@@ -93,6 +94,7 @@
@Mock private RoleManagerAdapter mRoleManagerAdapter;
@Mock private DefaultDialerCache mDefaultDialerCache;
+ @Mock private MmiUtils mMmiUtils;
private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter = new PhoneNumberUtilsAdapterImpl();
@Override
@@ -261,6 +263,58 @@
assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK, dialerIntent.getFlags());
}
+ @Test
+ public void testDangerousMmiCodeWithNonDefaultDialer() {
+ Uri handle = Uri.parse("tel:*21*1234567#");
+ doReturn(true).when(mMmiUtils).isDangerousMmiOrVerticalCode(handle);
+ Intent intent = new Intent(Intent.ACTION_CALL, handle);
+
+ String ui_package_string = "sample_string_1";
+ String dialer_default_class_string = "sample_string_2";
+ mComponentContextFixture.putResource(com.android.internal.R.string.config_defaultDialer,
+ ui_package_string);
+ mComponentContextFixture.putResource(R.string.dialer_default_class,
+ dialer_default_class_string);
+ when(mDefaultDialerCache.getSystemDialerApplication()).thenReturn(ui_package_string);
+ when(mDefaultDialerCache.getDialtactsSystemDialerComponent()).thenReturn(
+ new ComponentName(ui_package_string, dialer_default_class_string));
+
+ int result = processIntent(intent, false).disconnectCause;
+
+ assertEquals(DisconnectCause.OUTGOING_CANCELED, result);
+ verifyNoBroadcastSent();
+ verifyNoCallPlaced();
+
+ ArgumentCaptor<Intent> dialerIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext).startActivityAsUser(dialerIntentCaptor.capture(), any(UserHandle.class));
+ Intent dialerIntent = dialerIntentCaptor.getValue();
+ assertEquals(new ComponentName(ui_package_string, dialer_default_class_string),
+ dialerIntent.getComponent());
+ assertEquals(Intent.ACTION_DIAL, dialerIntent.getAction());
+ assertEquals(handle, dialerIntent.getData());
+ assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK, dialerIntent.getFlags());
+ }
+
+ @Test
+ public void testNonDangerousMmiCodeWithNonDefaultDialer() {
+ Uri handle = Uri.parse("tel:*12*1234567#");
+ doReturn(false).when(mMmiUtils).isDangerousMmiOrVerticalCode(handle);
+ Intent intent = new Intent(Intent.ACTION_CALL, handle);
+
+ String ui_package_string = "sample_string_1";
+ String dialer_default_class_string = "sample_string_2";
+ mComponentContextFixture.putResource(com.android.internal.R.string.config_defaultDialer,
+ ui_package_string);
+ mComponentContextFixture.putResource(R.string.dialer_default_class,
+ dialer_default_class_string);
+ when(mDefaultDialerCache.getSystemDialerApplication()).thenReturn(ui_package_string);
+ when(mDefaultDialerCache.getDialtactsSystemDialerComponent()).thenReturn(
+ new ComponentName(ui_package_string, dialer_default_class_string));
+
+ int result = processIntent(intent, false).disconnectCause;
+ assertEquals(DisconnectCause.NOT_DISCONNECTED, result);
+ }
+
@SmallTest
@Test
public void testActionCallEmergencyCall() {
@@ -488,7 +542,7 @@
boolean isDefaultPhoneApp) {
NewOutgoingCallIntentBroadcaster b = new NewOutgoingCallIntentBroadcaster(
mContext, mCallsManager, intent, mPhoneNumberUtilsAdapter,
- isDefaultPhoneApp, mDefaultDialerCache);
+ isDefaultPhoneApp, mDefaultDialerCache, mMmiUtils);
NewOutgoingCallIntentBroadcaster.CallDisposition cd = b.evaluateCall();
if (cd.disconnectCause == DisconnectCause.NOT_DISCONNECTED) {
b.processCall(mCall, cd);
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index c6cbbbb..e573bb8 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -23,8 +23,6 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyObject;
-import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
@@ -707,7 +705,7 @@
.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
.build();
- assertFalse(mRegistrar.hasTransactionalCallCapabilites(accountWithoutCapability));
+ assertFalse(mRegistrar.hasTransactionalCallCapabilities(accountWithoutCapability));
try {
mRegistrar.registerPhoneAccount(accountWithoutCapability);
@@ -730,7 +728,7 @@
PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS)
.build();
- assertTrue(mRegistrar.hasTransactionalCallCapabilites(accountWithCapability));
+ assertTrue(mRegistrar.hasTransactionalCallCapabilities(accountWithCapability));
try {
mRegistrar.registerPhoneAccount(accountWithCapability);
@@ -752,7 +750,7 @@
PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS)
.build();
- assertTrue(mRegistrar.hasTransactionalCallCapabilites(accountWithCapability));
+ assertTrue(mRegistrar.hasTransactionalCallCapabilities(accountWithCapability));
try {
// WHEN
@@ -1667,6 +1665,41 @@
}
}
+ /**
+ * Ensure an IllegalArgumentException is thrown when providing a SubscriptionAddress that
+ * exceeds the PhoneAccountRegistrar limit.
+ */
+ @Test
+ public void testLimitOnSubscriptionAddress() throws Exception {
+ String text = "a".repeat(100);
+ PhoneAccount.Builder builder = new PhoneAccount.Builder(makeQuickAccountHandle(TEST_ID),
+ TEST_LABEL).setSubscriptionAddress(Uri.fromParts(text, text, text));
+ try {
+ mRegistrar.enforceCharacterLimit(builder.build());
+ fail("failed to throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // pass test
+ }
+ }
+
+ /**
+ * PhoneAccounts with CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS do not require a
+ * ConnectionService. Ensure that such an account can be registered and fetched.
+ */
+ @Test
+ public void testFetchingTransactionalAccounts() {
+ PhoneAccount account = makeBuilderWithBindCapabilities(
+ makeQuickAccountHandle(TEST_ID)).build();
+
+ try {
+ assertEquals(0, mRegistrar.getAllPhoneAccounts(null, true).size());
+ registerAndEnableAccount(account);
+ assertEquals(1, mRegistrar.getAllPhoneAccounts(null, true).size());
+ } finally {
+ mRegistrar.unregisterPhoneAccount(account.getAccountHandle());
+ }
+ }
+
private static PhoneAccount.Builder makeBuilderWithBindCapabilities(PhoneAccountHandle handle) {
return new PhoneAccount.Builder(handle, TEST_LABEL)
.setCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS);
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index d397910..abbfe34 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -24,13 +24,16 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.NotificationManager;
@@ -42,15 +45,14 @@
import android.media.VolumeShaper;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Parcel;
import android.os.UserHandle;
+import android.os.UserManager;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.test.suitebuilder.annotation.SmallTest;
-import android.view.accessibility.AccessibilityManager;
import com.android.server.telecom.AsyncRingtonePlayer;
import com.android.server.telecom.Call;
@@ -69,83 +71,19 @@
import org.mockito.Mock;
import org.mockito.Spy;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.Objects;
import java.util.concurrent.CompletableFuture;
@RunWith(JUnit4.class)
public class RingerTest extends TelecomTestCase {
private static final Uri FAKE_RINGTONE_URI = Uri.parse("content://media/fake/audio/1729");
- private static class UriVibrationEffect extends VibrationEffect {
- final Uri mUri;
-
- private UriVibrationEffect(Uri uri) {
- mUri = uri;
- }
-
- @Override
- public VibrationEffect resolve(int defaultAmplitude) {
- return this;
- }
-
- @Override
- public VibrationEffect scale(float scaleFactor) {
- return this;
- }
-
- @Override
- public long[] computeCreateWaveformOffOnTimingsOrNull() {
- return null; // not needed
- }
-
- @Override
- public boolean areVibrationFeaturesSupported(Vibrator vibrator) {
- return true; // not needed
- }
-
- @Override
- public void validate() {
- // not needed
- }
-
- @Override
- public long getDuration() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- // not needed
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- UriVibrationEffect that = (UriVibrationEffect) o;
- return Objects.equals(mUri, that.mUri);
- }
- }
+ // Returned when the a URI-based VibrationEffect is attempted, to avoid depending on actual
+ // device configuration for ringtone URIs. The actual Uri can be verified via the
+ // VibrationEffectProxy mock invocation.
+ private static final VibrationEffect URI_VIBRATION_EFFECT =
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
@Mock InCallTonePlayer.Factory mockPlayerFactory;
@Mock SystemSettingsUtil mockSystemSettingsUtil;
- @Mock AsyncRingtonePlayer mockRingtonePlayer;
@Mock RingtoneFactory mockRingtoneFactory;
@Mock Vibrator mockVibrator;
@Mock InCallController mockInCallController;
@@ -162,6 +100,8 @@
new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"),
"pa_id");
+ boolean mIsHapticPlaybackSupported = true; // Note: initializeRinger() after changes.
+ AsyncRingtonePlayer asyncRingtonePlayer = new AsyncRingtonePlayer();
Ringer mRingerUnderTest;
AudioManager mockAudioManager;
CompletableFuture<Void> mRingCompletionFuture = new CompletableFuture<>();
@@ -171,25 +111,33 @@
public void setUp() throws Exception {
super.setUp();
mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
- doAnswer(invocation -> {
- Uri ringtoneUriForEffect = invocation.getArgument(0);
- return new UriVibrationEffect(ringtoneUriForEffect);
- }).when(spyVibrationEffectProxy).get(any(), any());
+ doReturn(URI_VIBRATION_EFFECT).when(spyVibrationEffectProxy).get(any(), any());
when(mockPlayerFactory.createPlayer(anyInt())).thenReturn(mockTonePlayer);
mockAudioManager = mContext.getSystemService(AudioManager.class);
when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
- when(mockSystemSettingsUtil.isHapticPlaybackSupported(any(Context.class))).thenReturn(true);
+ when(mockSystemSettingsUtil.isHapticPlaybackSupported(any(Context.class)))
+ .thenAnswer((invocation) -> mIsHapticPlaybackSupported);
mockNotificationManager =mContext.getSystemService(NotificationManager.class);
when(mockTonePlayer.startTone()).thenReturn(true);
when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
when(mockRingtoneFactory.hasHapticChannels(any(Ringtone.class))).thenReturn(false);
- mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
- mockRingtonePlayer, mockRingtoneFactory, mockVibrator, spyVibrationEffectProxy,
- mockInCallController, mockNotificationManager, mockAccessibilityManagerAdapter);
when(mockCall1.getState()).thenReturn(CallState.RINGING);
when(mockCall2.getState()).thenReturn(CallState.RINGING);
when(mockCall1.getUserHandleFromTargetPhoneAccount()).thenReturn(PA_HANDLE.getUserHandle());
when(mockCall2.getUserHandleFromTargetPhoneAccount()).thenReturn(PA_HANDLE.getUserHandle());
+
+ createRingerUnderTest();
+ }
+
+ /**
+ * (Re-)Creates the Ringer for the test. This needs to be called if changing final properties,
+ * like mIsHapticPlaybackSupported.
+ */
+ private void createRingerUnderTest() {
+ mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
+ asyncRingtonePlayer, mockRingtoneFactory, mockVibrator, spyVibrationEffectProxy,
+ mockInCallController, mockNotificationManager, mockAccessibilityManagerAdapter);
+ // This future is used to wait for AsyncRingtonePlayer to finish its part.
mRingerUnderTest.setBlockOnRingingFuture(mRingCompletionFuture);
}
@@ -201,33 +149,30 @@
@SmallTest
@Test
- public void testNoActionInTheaterMode() {
+ public void testNoActionInTheaterMode() throws Exception {
// Start call waiting to make sure that it doesn't stop when we start ringing
mRingerUnderTest.startCallWaiting(mockCall1);
when(mockSystemSettingsUtil.isTheaterModeOn(any(Context.class))).thenReturn(true);
- assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
- verify(mockRingtoneFactory, never())
- .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+ assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+ verifyZeroInteractions(mockRingtoneFactory);
verify(mockTonePlayer, never()).stopTone();
- verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
verify(mockVibrator, never())
.vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
}
@SmallTest
@Test
- public void testNoActionWithExternalRinger() {
+ public void testNoActionWithExternalRinger() throws Exception {
Bundle externalRingerExtra = new Bundle();
externalRingerExtra.putBoolean(TelecomManager.EXTRA_CALL_HAS_IN_BAND_RINGTONE, true);
when(mockCall1.getIntentExtras()).thenReturn(externalRingerExtra);
when(mockCall2.getIntentExtras()).thenReturn(externalRingerExtra);
// Start call waiting to make sure that it doesn't stop when we start ringing
mRingerUnderTest.startCallWaiting(mockCall1);
- assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
- verify(mockRingtoneFactory, never())
- .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+ assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+
+ verifyZeroInteractions(mockRingtoneFactory);
verify(mockTonePlayer, never()).stopTone();
- verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
verify(mockVibrator, never())
.vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
}
@@ -235,19 +180,15 @@
@SmallTest
@Test
public void testNoActionWhenDialerRings() throws Exception {
- ensureRingtoneMocked();
-
// Start call waiting to make sure that it doesn't stop when we start ringing
mRingerUnderTest.startCallWaiting(mockCall1);
when(mockInCallController.doesConnectedDialerSupportRinging(
any(UserHandle.class))).thenReturn(true);
ensureRingerIsNotAudible();
- assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
- verify(mockRingtoneFactory, never())
- .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
- mRingCompletionFuture.get();
+ assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+
+ verifyZeroInteractions(mockRingtoneFactory);
verify(mockTonePlayer, never()).stopTone();
- verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
verify(mockVibrator, never())
.vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
}
@@ -255,48 +196,46 @@
@SmallTest
@Test
public void testAudioFocusStillAcquiredWhenDialerRings() throws Exception {
- ensureRingtoneMocked();
// Start call waiting to make sure that it doesn't stop when we start ringing
mRingerUnderTest.startCallWaiting(mockCall1);
when(mockInCallController.doesConnectedDialerSupportRinging(
any(UserHandle.class))).thenReturn(true);
ensureRingerIsAudible();
- assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
- verify(mockRingtoneFactory, never())
- .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
- mRingCompletionFuture.get();
+ assertTrue(startRingingAndWaitForAsync(mockCall2, false));
+ verifyZeroInteractions(mockRingtoneFactory);
verify(mockTonePlayer, never()).stopTone();
- verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
verify(mockVibrator, never())
.vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
}
@SmallTest
@Test
- public void testNoActionWhenCallIsSelfManaged() {
+ public void testNoActionWhenCallIsSelfManaged() throws Exception {
// Start call waiting to make sure that it doesn't stop when we start ringing
mRingerUnderTest.startCallWaiting(mockCall1);
when(mockCall2.isSelfManaged()).thenReturn(true);
// We do want to acquire audio focus when self-managed
- assertTrue(mRingerUnderTest.startRinging(mockCall2, true));
+ assertTrue(startRingingAndWaitForAsync(mockCall2, true));
+
+ verifyZeroInteractions(mockRingtoneFactory);
verify(mockTonePlayer, never()).stopTone();
- verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
verify(mockVibrator, never())
.vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
}
@SmallTest
@Test
- public void testCallWaitingButNoRingForSpecificContacts() {
+ public void testCallWaitingButNoRingForSpecificContacts() throws Exception {
when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(false);
// Start call waiting to make sure that it does stop when we start ringing
mRingerUnderTest.startCallWaiting(mockCall1);
verify(mockTonePlayer).startTone();
- assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
+ assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+
+ verifyZeroInteractions(mockRingtoneFactory);
verify(mockTonePlayer).stopTone();
- verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
verify(mockVibrator, never())
.vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
}
@@ -311,12 +250,12 @@
enableVibrationWhenRinging();
// Pretend we're using audio coupled haptics.
setIsUsingHaptics(mockRingtone, true);
- assertTrue(mRingerUnderTest.startRinging(mockCall1, false));
- mRingCompletionFuture.get();
+ assertTrue(startRingingAndWaitForAsync(mockCall1, false));
verify(mockRingtoneFactory, times(1))
.getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+ verifyNoMoreInteractions(mockRingtoneFactory);
verify(mockTonePlayer).stopTone();
- verify(mockRingtonePlayer).play(any(Ringtone.class));
+ verify(mockRingtone).play();
verify(mockVibrator, never()).vibrate(any(VibrationEffect.class),
any(VibrationAttributes.class));
}
@@ -331,11 +270,16 @@
mRingerUnderTest.startCallWaiting(mockCall1);
when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
enableVibrationWhenRinging();
- assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
- mRingCompletionFuture.get();
+ // The ringtone isn't known to be null until the async portion after the call completes,
+ // so startRinging still returns true here as there should nominally be a ringtone.
+ // Notably, vibration still happens in this scenario.
+ assertTrue(startRingingAndWaitForAsync(mockCall2, false));
verify(mockTonePlayer).stopTone();
- // Ringtone does not exist, make sure it does not try to play it
- verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
+
+ // Just the one call to mockRingtoneFactory, which returned null.
+ verify(mockRingtoneFactory).getRingtone(
+ any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+ verifyNoMoreInteractions(mockRingtoneFactory);
// Play default vibration when future completes with no audio coupled haptics
verify(mockVibrator).vibrate(eq(mRingerUnderTest.mDefaultVibrationEffect),
@@ -353,14 +297,30 @@
when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
enableVibrationWhenRinging();
- assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
- mRingCompletionFuture.get();
+ assertFalse(startRingingAndWaitForAsync(mockCall2, false));
verify(mockTonePlayer).stopTone();
// Try to play a silent haptics ringtone
- verify(mockRingtonePlayer).play(any(Ringtone.class));
verify(mockRingtoneFactory, times(1)).getHapticOnlyRingtone();
- verify(mockRingtoneFactory, never())
- .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+ verifyNoMoreInteractions(mockRingtoneFactory);
+ verify(mockRingtone).play();
+
+ // Play default vibration when future completes with no audio coupled haptics
+ verify(mockVibrator).vibrate(eq(mRingerUnderTest.mDefaultVibrationEffect),
+ any(VibrationAttributes.class));
+ }
+
+ @SmallTest
+ @Test
+ public void testVibrateButNoRingForSilentRingtoneWithoutAudioHapticSupport() throws Exception {
+ mIsHapticPlaybackSupported = false;
+ createRingerUnderTest(); // Needed after changing haptic playback support.
+ mRingerUnderTest.startCallWaiting(mockCall1);
+ when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+ when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
+ enableVibrationWhenRinging();
+ assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+ verify(mockTonePlayer).stopTone();
+ verifyZeroInteractions(mockRingtoneFactory);
// Play default vibration when future completes with no audio coupled haptics
verify(mockVibrator).vibrate(eq(mRingerUnderTest.mDefaultVibrationEffect),
@@ -377,14 +337,13 @@
when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
setIsUsingHaptics(mockRingtone, true);
enableVibrationWhenRinging();
- assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
+ assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+
verify(mockRingtoneFactory, times(1)).getHapticOnlyRingtone();
- verify(mockRingtoneFactory, never())
- .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
- mRingCompletionFuture.get();
+ verifyNoMoreInteractions(mockRingtoneFactory);
verify(mockTonePlayer).stopTone();
// Try to play a silent haptics ringtone
- verify(mockRingtonePlayer).play(any(Ringtone.class));
+ verify(mockRingtone).play();
// Skip vibration for audio coupled haptics
verify(mockVibrator, never()).vibrate(any(VibrationEffect.class),
any(VibrationAttributes.class));
@@ -394,36 +353,34 @@
@Test
public void testCustomVibrationForRingtone() throws Exception {
mRingerUnderTest.startCallWaiting(mockCall1);
- Ringtone mockRingtone = mock(Ringtone.class);
+ Ringtone mockRingtone = ensureRingtoneMocked();
when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
- when(mockRingtoneFactory.getRingtone(any(Call.class), eq(null), anyBoolean()))
- .thenReturn(mockRingtone);
when(mockRingtone.getUri()).thenReturn(FAKE_RINGTONE_URI);
enableVibrationWhenRinging();
- assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
- mRingCompletionFuture.get();
+ assertTrue(startRingingAndWaitForAsync(mockCall2, false));
verify(mockTonePlayer).stopTone();
- verify(mockRingtonePlayer).play(any(Ringtone.class));
verify(mockRingtoneFactory, times(1))
- .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
- verify(mockVibrator).vibrate(eq(spyVibrationEffectProxy.get(FAKE_RINGTONE_URI, mContext)),
- any(VibrationAttributes.class));
+ .getRingtone(any(Call.class), isNull(), anyBoolean());
+ verifyNoMoreInteractions(mockRingtoneFactory);
+ verify(mockRingtone).play();
+ verify(spyVibrationEffectProxy).get(eq(FAKE_RINGTONE_URI), any(Context.class));
+ verify(mockVibrator).vibrate(eq(URI_VIBRATION_EFFECT), any(VibrationAttributes.class));
}
@SmallTest
@Test
public void testRingAndNoVibrate() throws Exception {
- ensureRingtoneMocked();
+ Ringtone mockRingtone = ensureRingtoneMocked();
mRingerUnderTest.startCallWaiting(mockCall1);
ensureRingerIsAudible();
enableVibrationOnlyWhenNotRinging();
- assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
- mRingCompletionFuture.get();
+ assertTrue(startRingingAndWaitForAsync(mockCall2, false));
verify(mockRingtoneFactory, times(1))
.getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+ verifyNoMoreInteractions(mockRingtoneFactory);
verify(mockTonePlayer).stopTone();
- verify(mockRingtonePlayer).play(any(Ringtone.class));
+ verify(mockRingtone).play();
verify(mockVibrator, never())
.vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
}
@@ -431,50 +388,31 @@
@SmallTest
@Test
public void testRingWithRampingRinger() throws Exception {
- ensureRingtoneMocked();
+ Ringtone mockRingtone = ensureRingtoneMocked();
mRingerUnderTest.startCallWaiting(mockCall1);
ensureRingerIsAudible();
enableRampingRinger();
enableVibrationWhenRinging();
- assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
+ assertTrue(startRingingAndWaitForAsync(mockCall2, false));
verify(mockRingtoneFactory, times(1))
.getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
- mRingCompletionFuture.get();
+ verifyNoMoreInteractions(mockRingtoneFactory);
verify(mockTonePlayer).stopTone();
- verify(mockRingtonePlayer).play(any(Ringtone.class));
+ verify(mockRingtone).play();
}
@SmallTest
@Test
- public void testSilentRingWithHfpStillAcquiresFocus1() throws Exception {
+ public void testSilentRingWithHfpStillAcquiresFocus() throws Exception {
mRingerUnderTest.startCallWaiting(mockCall1);
- ensureRingtoneMocked();
when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
enableVibrationOnlyWhenNotRinging();
- assertTrue(mRingerUnderTest.startRinging(mockCall2, true));
- mRingCompletionFuture.get();
+ assertTrue(startRingingAndWaitForAsync(mockCall2, true));
verify(mockTonePlayer).stopTone();
- verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
- verify(mockVibrator, never())
- .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
- }
-
- @SmallTest
- @Test
- public void testSilentRingWithHfpStillAcquiresFocus2() throws Exception {
- mRingerUnderTest.startCallWaiting(mockCall1);
- when(mockRingtoneFactory.getRingtone(
- any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean()))
- .thenReturn(null);
- when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
- when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
- enableVibrationOnlyWhenNotRinging();
- assertTrue(mRingerUnderTest.startRinging(mockCall2, true));
- mRingCompletionFuture.get();
- verify(mockTonePlayer).stopTone();
- verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
+ // Ringer not audible, so never tries to create a ringtone.
+ verifyZeroInteractions(mockRingtoneFactory);
verify(mockVibrator, never())
.vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
}
@@ -483,28 +421,17 @@
@Test
public void testRingAndVibrateForAllowedCallInDndMode() throws Exception {
mRingerUnderTest.startCallWaiting(mockCall1);
- Ringtone mockRingtone = mock(Ringtone.class);
+ Ringtone mockRingtone = ensureRingtoneMocked();
when(mockNotificationManager.getZenMode()).thenReturn(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- when(mockRingtoneFactory.getRingtone(any(Call.class), eq(null), anyBoolean()))
- .thenReturn(mockRingtone);
when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_SILENT);
when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(100);
enableVibrationWhenRinging();
- assertTrue(mRingerUnderTest.startRinging(mockCall2, true));
+ assertTrue(startRingingAndWaitForAsync(mockCall2, true));
verify(mockRingtoneFactory, times(1))
- .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
- mRingCompletionFuture.get();
+ .getRingtone(any(Call.class), isNull(), anyBoolean());
+ verifyNoMoreInteractions(mockRingtoneFactory);
verify(mockTonePlayer).stopTone();
- verify(mockRingtonePlayer).play(any(Ringtone.class));
- }
-
- private Ringtone ensureRingtoneMocked() {
- Ringtone mockRingtone = mock(Ringtone.class);
- when(mockRingtoneFactory.getRingtone(
- any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean()))
- .thenReturn(mockRingtone);
- when(mockRingtoneFactory.getHapticOnlyRingtone()).thenReturn(mockRingtone);
- return mockRingtone;
+ verify(mockRingtone).play();
}
/**
@@ -547,7 +474,7 @@
}
@Test
- public void testNoFlashNotificationWhenCallSuppressed() {
+ public void testNoFlashNotificationWhenCallSuppressed() throws Exception {
ensureRingtoneMocked();
// Start call waiting to make sure that it doesn't stop when we start ringing
mRingerUnderTest.startCallWaiting(mockCall1);
@@ -556,13 +483,13 @@
when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(false);
assertFalse(mRingerUnderTest.shouldRingForContact(mockCall2));
- assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
+ assertFalse(startRingingAndWaitForAsync(mockCall2, false));
verify(mockAccessibilityManagerAdapter, never())
.startFlashNotificationSequence(any(Context.class), anyInt());
}
@Test
- public void testStartFlashNotificationWhenRingStarts() {
+ public void testStartFlashNotificationWhenRingStarts() throws Exception {
ensureRingtoneMocked();
// Start call waiting to make sure that it doesn't stop when we start ringing
mRingerUnderTest.startCallWaiting(mockCall1);
@@ -571,15 +498,27 @@
when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
assertTrue(mRingerUnderTest.shouldRingForContact(mockCall2));
- assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
+ assertTrue(startRingingAndWaitForAsync(mockCall2, false));
verify(mockAccessibilityManagerAdapter, atLeastOnce())
.startFlashNotificationSequence(any(Context.class), anyInt());
}
@Test
- public void testStopFlashNotificationWhenRingStops() {
- ensureRingtoneMocked();
+ public void testStopFlashNotificationWhenRingStops() throws Exception {
+ Ringtone mockRingtone = mock(Ringtone.class);
+ when(mockRingtoneFactory.getRingtone(
+ any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean()))
+ .thenAnswer(x -> {
+ // Be slow to create ringtone.
+ try {
+ Thread.sleep(300);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ return mockRingtone;
+ });
// Start call waiting to make sure that it doesn't stop when we start ringing
+ enableVibrationWhenRinging();
mRingerUnderTest.startCallWaiting(mockCall1);
when(mockCall2.wasDndCheckComputedForCall()).thenReturn(false);
when(mockCall2.getHandle()).thenReturn(Uri.parse(""));
@@ -590,7 +529,36 @@
mRingerUnderTest.stopRinging();
verify(mockAccessibilityManagerAdapter, atLeastOnce())
.stopFlashNotificationSequence(any(Context.class));
+ mRingCompletionFuture.get(); // Don't leak async work.
+ verify(mockVibrator, never()) // cancelled before it started.
+ .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
+ }
+ @SmallTest
+ @Test
+ public void testNoRingingForQuietProfile() throws Exception {
+ UserManager um = mContext.getSystemService(UserManager.class);
+ when(um.isManagedProfile(PA_HANDLE.getUserHandle().getIdentifier())).thenReturn(true);
+ when(um.isQuietModeEnabled(PA_HANDLE.getUserHandle())).thenReturn(true);
+ // We don't want to acquire audio focus when self-managed
+ assertFalse(startRingingAndWaitForAsync(mockCall2, true));
+
+ verify(mockTonePlayer, never()).stopTone();
+ verifyZeroInteractions(mockRingtoneFactory);
+ verify(mockVibrator, never())
+ .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
+ }
+
+ /**
+ * Call startRinging and wait for its effects to have played out, to allow reliable assertions
+ * after it. The effects are generally "start playing ringtone" and "start vibration" - not
+ * waiting for anything open-ended.
+ */
+ private boolean startRingingAndWaitForAsync(Call mockCall2, boolean isHfpDeviceAttached)
+ throws Exception {
+ boolean result = mRingerUnderTest.startRinging(mockCall2, isHfpDeviceAttached);
+ mRingCompletionFuture.get();
+ return result;
}
private void ensureRingerIsAudible() {
@@ -618,10 +586,19 @@
}
private void setIsUsingHaptics(Ringtone mockRingtone, boolean useHaptics) {
- when(mockSystemSettingsUtil.isHapticPlaybackSupported(any(Context.class)))
- .thenReturn(useHaptics);
+ // Note: using haptics can also depend on mIsHapticPlaybackSupported. If changing
+ // that, the ringerUnderTest needs to be re-created.
when(mockSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled())
.thenReturn(useHaptics);
when(mockRingtone.hasHapticChannels()).thenReturn(useHaptics);
}
+
+ private Ringtone ensureRingtoneMocked() {
+ Ringtone mockRingtone = mock(Ringtone.class);
+ when(mockRingtoneFactory.getRingtone(
+ any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean()))
+ .thenReturn(mockRingtone);
+ when(mockRingtoneFactory.getHapticOnlyRingtone()).thenReturn(mockRingtone);
+ return mockRingtone;
+ }
}
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index 569d6b4..7b5afe6 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -33,6 +33,7 @@
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -292,6 +293,51 @@
assertEquals(SIP_PA_HANDLE_17, returnedHandleSip);
}
+ /**
+ * Clear the groupId from the PhoneAccount if a package does NOT have MODIFY_PHONE_STATE
+ */
+ @SmallTest
+ @Test
+ public void testGroupIdIsClearedWhenPermissionIsMissing() throws RemoteException {
+ // GIVEN
+ PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_CURRENT)
+ .setGroupId("testId")
+ .build();
+ // WHEN
+ doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+ eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class), anyBoolean());
+ doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+ doReturn(PackageManager.PERMISSION_DENIED)
+ .when(mContext).checkCallingPermission(MODIFY_PHONE_STATE);
+ // THEN
+ PhoneAccount account =
+ mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_CURRENT, PACKAGE_NAME);
+ assertEquals("***", account.getGroupId());
+ }
+
+ /**
+ * Ensure groupId is not cleared if a package has MODIFY_PHONE_STATE
+ */
+ @SmallTest
+ @Test
+ public void testGroupIdIsNotCleared() throws RemoteException {
+ // GIVEN
+ final String groupId = "testId";
+ PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_CURRENT)
+ .setGroupId(groupId)
+ .build();
+ // WHEN
+ doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+ eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class), anyBoolean());
+ doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .when(mContext).checkCallingPermission(MODIFY_PHONE_STATE);
+ // THEN
+ PhoneAccount account =
+ mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_CURRENT, DEFAULT_DIALER_PACKAGE);
+ assertEquals(groupId, account.getGroupId());
+ }
+
@SmallTest
@Test
public void testGetDefaultOutgoingPhoneAccountSucceedsIfCallerIsSimCallManager()
@@ -608,6 +654,8 @@
@SmallTest
@Test
public void testGetPhoneAccount() throws Exception {
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .when(mContext).checkCallingPermission(MODIFY_PHONE_STATE);
makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
assertEquals(TEL_PA_HANDLE_16, mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_16,
mContext.getPackageName()).getAccountHandle());
@@ -903,6 +951,26 @@
@SmallTest
@Test
+ public void testRegisterPhoneAccountImageIconCrossUser() throws RemoteException {
+ String packageNameToUse = "com.android.officialpackage";
+ PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
+ packageNameToUse, "cs"), "test", Binder.getCallingUserHandle());
+ Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+ PhoneAccount phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build();
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
+
+ // This should fail; security exception will be thrown.
+ registerPhoneAccountTestHelper(phoneAccount, false);
+
+ icon = Icon.createWithContentUri("content://0@media/external/images/media/");
+ phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build();
+ // This should succeed.
+ registerPhoneAccountTestHelper(phoneAccount, true);
+ }
+
+ @SmallTest
+ @Test
public void testUnregisterPhoneAccount() throws RemoteException {
String packageNameToUse = "com.android.officialpackage";
PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
@@ -1067,12 +1135,18 @@
}
}
+ /**
+ * Place a managed call with no PhoneAccount specified and ensure no security exception is
+ * thrown.
+ */
@SmallTest
@Test
public void testPlaceCallWithNonEmergencyPermission() throws Exception {
Uri handle = Uri.parse("tel:6505551234");
Bundle extras = createSampleExtras();
+ // We have passed in the DEFAULT_DIALER_PACKAGE for this test, so canCallPhone is always
+ // true.
when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
nullable(String.class), nullable(String.class)))
.thenReturn(AppOpsManager.MODE_ALLOWED);
@@ -1082,15 +1156,339 @@
.when(mContext).checkCallingPermission(CALL_PRIVILEGED);
mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null);
- placeCallTestHelper(handle, extras, true);
+ placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false,
+ /*shouldNonEmergencyBeAllowed*/ true);
}
+ /**
+ * Ensure that we get a SecurityException if the UID of the caller doesn't match the UID of the
+ * UID of the package name passed in.
+ */
+ @SmallTest
+ @Test
+ public void testPlaceCall_enforceCallingPackageFailure() throws Exception {
+ Uri handle = Uri.parse("tel:6505551234");
+ Bundle extras = createSampleExtras();
+ // callingPackage matches the PhoneAccountHandle, so this is an app with a self-managed
+ // ConnectionService.
+ extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+ // Return a non-matching UID for testing purposes.
+ when(mPackageManager.getPackageUid(anyString(), eq(0))).thenReturn(-1);
+ try {
+ mTSIBinder.placeCall(handle, extras, PACKAGE_NAME, null);
+ fail("Expected SecurityException because calling package doesn't match");
+ } catch(SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * In the case that there is a self-managed call request and MANAGE_OWN_CALLS is granted, ensure
+ * that placeCall does not generate a SecurityException.
+ */
+ @SmallTest
+ @Test
+ public void testPlaceCall_selfManaged_permissionGranted() throws Exception {
+ doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+ eq(DEFAULT_DIALER_PACKAGE), anyInt());
+ when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+ makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+ Uri handle = Uri.parse("tel:6505551234");
+ Bundle extras = createSampleExtras();
+ // callingPackage matches the PhoneAccountHandle, so this is an app with a self-managed
+ // ConnectionService.
+ extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+ // pass MANAGE_OWN_CALLS check, but do not have CALL_PHONE
+ doNothing().when(mContext).enforceCallingOrSelfPermission(
+ eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+ doReturn(PackageManager.PERMISSION_DENIED)
+ .when(mContext).checkCallingPermission(CALL_PHONE);
+ doReturn(PackageManager.PERMISSION_DENIED)
+ .when(mContext).checkCallingPermission(CALL_PRIVILEGED);
+
+ try {
+ mTSIBinder.placeCall(handle, extras, PACKAGE_NAME, null);
+ placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ true,
+ /*shouldNonEmergencyBeAllowed*/ false);
+ } catch(SecurityException e) {
+ fail("Unexpected SecurityException - MANAGE_OWN_CALLS is set");
+ }
+ }
+
+ /**
+ * In the case that the placeCall API is being used place a self-managed call
+ * (phone account is marked self-managed and the calling application owns that PhoneAccount),
+ * ensure that the call gets placed as not self-managed as to not disclose PA info.
+ */
+ @SmallTest
+ @Test
+ public void testPlaceCall_selfManaged_noPermission() throws Exception {
+ doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+ eq(DEFAULT_DIALER_PACKAGE), anyInt());
+ when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+ makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+ Uri handle = Uri.parse("tel:6505551234");
+ Bundle extras = createSampleExtras();
+ // callingPackage matches the PhoneAccountHandle, so this is an app with a self-managed
+ // ConnectionService.
+ extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+ doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+ eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+
+ try {
+ mTSIBinder.placeCall(handle, extras, PACKAGE_NAME, null);
+ fail("Expected SecurityException because MANAGE_OWN_CALLS is not set");
+ } catch(SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * In the case that there is a self-managed call request and the app doesn't own that
+ * PhoneAccount, we will need to check CALL_PHONE. If they do not have CALL_PHONE permission,
+ * we need to throw a security exception.
+ */
+ @SmallTest
+ @Test
+ public void testPlaceCall_selfManaged_permissionFail() throws Exception {
+ doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+ eq(DEFAULT_DIALER_PACKAGE), anyInt());
+ when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+ makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+ Uri handle = Uri.parse("tel:6505551234");
+ Bundle extras = createSampleExtras();
+ // callingPackage doesn't match the PhoneAccountHandle package
+ extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+ // pass MANAGE_OWN_CALLS check, but do not have CALL PHONE
+ doNothing().when(mContext).enforceCallingOrSelfPermission(
+ eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+ doThrow(new SecurityException())
+ .when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
+
+ try {
+ // Calling package is received and is not the same as PACKAGE_NAME
+ mTSIBinder.placeCall(handle, extras, PACKAGE_NAME + "2", null);
+ fail("Expected a SecurityException - CALL_PHONE was not granted");
+ } catch(SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * In the case that there is a self-managed call request and the app doesn't own that
+ * PhoneAccount, we will need to check CALL_PHONE. If they have the CALL_PHONE permission, but
+ * the app op has been denied, this should throw a security exception.
+ */
+ @SmallTest
+ @Test
+ public void testPlaceCall_selfManaged_appOpPermissionFail() throws Exception {
+ doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+ eq(DEFAULT_DIALER_PACKAGE), anyInt());
+ when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+ makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+ Uri handle = Uri.parse("tel:6505551234");
+ Bundle extras = createSampleExtras();
+ // callingPackage doesn't match the PhoneAccountHandle package.
+ extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+ // pass MANAGE_OWN_CALLS check, but do not have CALL PHONE
+ doNothing().when(mContext).enforceCallingOrSelfPermission(
+ eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+ doNothing().when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
+ when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+ nullable(String.class), nullable(String.class)))
+ .thenReturn(AppOpsManager.MODE_ERRORED);
+ try {
+ mTSIBinder.placeCall(handle, extras, PACKAGE_NAME + "2", null);
+ fail("Expected a SecurityException - CALL_PHONE app op is denied");
+ } catch(SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * In the case that there is a self-managed call request and the app doesn't own that
+ * PhoneAccount, we will need to check CALL_PHONE. If they have the correct permissions, the
+ * call will go through, however we will have removed the self-managed PhoneAccountHandle. The
+ * call will go through as a normal managed call request with no PhoneAccountHandle.
+ */
+ @SmallTest
+ @Test
+ public void testPlaceCall_selfManaged_differentCallingPackage() throws Exception {
+ doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+ eq(DEFAULT_DIALER_PACKAGE), anyInt());
+ when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+ makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+ Uri handle = Uri.parse("tel:6505551234");
+ Bundle extras = createSampleExtras();
+ // callingPackage doesn't match the PhoneAccountHandle package
+ extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+ // simulate default dialer so CALL_PHONE is granted.
+ when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+ nullable(String.class), nullable(String.class)))
+ .thenReturn(AppOpsManager.MODE_ALLOWED);
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .when(mContext).checkCallingPermission(CALL_PHONE);
+ doReturn(PackageManager.PERMISSION_DENIED)
+ .when(mContext).checkCallingPermission(CALL_PRIVILEGED);
+
+ // We expect the call to go through with no PhoneAccount specified, since the request
+ // contained a self-managed PhoneAccountHandle that didn't belong to this app.
+ Bundle expectedExtras = extras.deepCopy();
+ expectedExtras.remove(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
+ try {
+ mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null);
+ } catch (SecurityException e) {
+ fail("Unexpected SecurityException - CTS is default dialer and MANAGE_OWN_CALLS is not"
+ + " required. Exception: " + e);
+ }
+ placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false,
+ /*shouldNonEmergencyBeAllowed*/ true);
+ }
+
+ /**
+ * In the case that there is a managed call request and the app owns that
+ * PhoneAccount (but is not a self-managed), we will still need to check CALL_PHONE.
+ */
+ @SmallTest
+ @Test
+ public void testPlaceCall_samePackage_managedPhoneAccount_permissionFail() throws Exception {
+ doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+ eq(DEFAULT_DIALER_PACKAGE), anyInt());
+ when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+ makePhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+ Uri handle = Uri.parse("tel:6505551234");
+ Bundle extras = createSampleExtras();
+ // callingPackage doesn't match the PhoneAccountHandle package
+ extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+ // CALL_PHONE is not granted to the device.
+ doThrow(new SecurityException())
+ .when(mContext).enforceCallingOrSelfPermission(
+ eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+ doThrow(new SecurityException())
+ .when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
+
+ try {
+ mTSIBinder.placeCall(handle, extras, PACKAGE_NAME + "2", null);
+ fail("Expected a SecurityException - CALL_PHONE is not granted");
+ } catch(SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * In the case that there is a managed call request and the app owns that
+ * PhoneAccount (but is not a self-managed), we will still need to check CALL_PHONE.
+ */
+ @SmallTest
+ @Test
+ public void testPlaceCall_samePackage_managedPhoneAccount_AppOpFail() throws Exception {
+ doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+ eq(DEFAULT_DIALER_PACKAGE), anyInt());
+ when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+ makePhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+ Uri handle = Uri.parse("tel:6505551234");
+ Bundle extras = createSampleExtras();
+ // callingPackage matches the PhoneAccountHandle, but this is not a self managed phone
+ // account.
+ extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+ // CALL_PHONE is granted, but the app op is not
+ doThrow(new SecurityException())
+ .when(mContext).enforceCallingOrSelfPermission(
+ eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+ doNothing().when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
+ when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+ nullable(String.class), nullable(String.class)))
+ .thenReturn(AppOpsManager.MODE_ERRORED);
+
+ try {
+ mTSIBinder.placeCall(handle, extras, PACKAGE_NAME + "2", null);
+ fail("Expected a SecurityException - CALL_PHONE app op is denied");
+ } catch(SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Since this is a self-managed call being requested, so ensure we report the call as
+ * self-managed and without non-emergency permissions.
+ */
+ @SmallTest
+ @Test
+ public void testPlaceCall_selfManaged_nonEmergencyPermission() throws Exception {
+ doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+ eq(DEFAULT_DIALER_PACKAGE), anyInt());
+ when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+ makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+ Uri handle = Uri.parse("tel:6505551234");
+ Bundle extras = createSampleExtras();
+ // callingPackage matches the PhoneAccountHandle, so this is an app with a self-managed
+ // ConnectionService.
+ extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+ // enforceCallingOrSelfPermission is implicitly granted for MANAGE_OWN_CALLS here and
+ // CALL_PHONE is not required.
+ when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+ nullable(String.class), nullable(String.class)))
+ .thenReturn(AppOpsManager.MODE_IGNORED);
+ doReturn(PackageManager.PERMISSION_DENIED)
+ .when(mContext).checkCallingPermission(CALL_PHONE);
+ doReturn(PackageManager.PERMISSION_DENIED)
+ .when(mContext).checkCallingPermission(CALL_PRIVILEGED);
+
+ mTSIBinder.placeCall(handle, extras, PACKAGE_NAME, null);
+ placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ true,
+ /*shouldNonEmergencyBeAllowed*/ false);
+ }
+
+ /**
+ * Default dialer is calling placeCall and has CALL_PHONE granted, so non-emergency calls
+ * are allowed.
+ */
+ @SmallTest
+ @Test
+ public void testPlaceCall_managed_nonEmergencyGranted() throws Exception {
+ doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+ eq(DEFAULT_DIALER_PACKAGE), anyInt());
+ Uri handle = Uri.parse("tel:6505551234");
+ Bundle extras = createSampleExtras();
+ // callingPackage doesn't match the PhoneAccountHandle, so this app does not have a
+ // self-managed ConnectionService
+ extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+ // CALL_PHONE granted
+ when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+ nullable(String.class), nullable(String.class)))
+ .thenReturn(AppOpsManager.MODE_ALLOWED);
+ doReturn(PackageManager.PERMISSION_GRANTED)
+ .when(mContext).checkCallingPermission(CALL_PHONE);
+ doReturn(PackageManager.PERMISSION_DENIED)
+ .when(mContext).checkCallingPermission(CALL_PRIVILEGED);
+
+ mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null);
+ placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false,
+ /*shouldNonEmergencyBeAllowed*/ true);
+ }
+
+ /**
+ * The default dialer is requesting to place a call and CALL_PHONE is granted, however
+ * OP_CALL_PHONE app op is denied to that app, so non-emergency calls will be denied.
+ */
@SmallTest
@Test
public void testPlaceCallWithAppOpsOff() throws Exception {
Uri handle = Uri.parse("tel:6505551234");
Bundle extras = createSampleExtras();
+ // We have passed in the DEFAULT_DIALER_PACKAGE for this test, so canCallPhone is always
+ // true.
when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
nullable(String.class), nullable(String.class)))
.thenReturn(AppOpsManager.MODE_IGNORED);
@@ -1100,15 +1498,21 @@
.when(mContext).checkCallingPermission(CALL_PRIVILEGED);
mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null);
- placeCallTestHelper(handle, extras, false);
+ placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false,
+ /*shouldNonEmergencyBeAllowed*/ false);
}
+ /**
+ * The default dialer is requesting to place a call, however CALL_PHONE is denied to that app,
+ * so non-emergency calls will be denied.
+ */
@SmallTest
@Test
public void testPlaceCallWithNoCallingPermission() throws Exception {
Uri handle = Uri.parse("tel:6505551234");
Bundle extras = createSampleExtras();
+ // We are assumed to be default dialer in this test, so canCallPhone is always true.
when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
nullable(String.class), nullable(String.class)))
.thenReturn(AppOpsManager.MODE_ALLOWED);
@@ -1118,37 +1522,82 @@
.when(mContext).checkCallingPermission(CALL_PRIVILEGED);
mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null);
- placeCallTestHelper(handle, extras, false);
+ placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false,
+ /*shouldNonEmergencyBeAllowed*/ false);
}
+ /**
+ * Ensure the expected handle, extras, and non-emergency call permission checks have been
+ * correctly included in the ACTION_CALL intent as part of the
+ * {@link UserCallIntentProcessor#processIntent} method called during the placeCall procedure.
+ * @param expectedHandle Expected outgoing number handle
+ * @param expectedExtras Expected extras in the ACTION_CALL intent.
+ * @param shouldNonEmergencyBeAllowed true if non-emergency calls should be allowed, false if
+ * permission checks failed for non-emergency.
+ */
private void placeCallTestHelper(Uri expectedHandle, Bundle expectedExtras,
- boolean shouldNonEmergencyBeAllowed) {
+ boolean isSelfManagedExpected, boolean shouldNonEmergencyBeAllowed) {
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mUserCallIntentProcessor).processIntent(intentCaptor.capture(), anyString(),
- eq(shouldNonEmergencyBeAllowed), eq(true));
+ eq(isSelfManagedExpected), eq(shouldNonEmergencyBeAllowed), eq(true));
Intent capturedIntent = intentCaptor.getValue();
assertEquals(Intent.ACTION_CALL, capturedIntent.getAction());
assertEquals(expectedHandle, capturedIntent.getData());
assertTrue(areBundlesEqual(expectedExtras, capturedIntent.getExtras()));
}
+ /**
+ * Ensure that if the caller was never granted CALL_PHONE (and is not the default dialer), a
+ * SecurityException is thrown.
+ */
@SmallTest
@Test
public void testPlaceCallFailure() throws Exception {
Uri handle = Uri.parse("tel:6505551234");
Bundle extras = createSampleExtras();
+ // The app is not considered a privileged dialer and does not have the CALL_PHONE
+ // permission.
doThrow(new SecurityException())
.when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
try {
mTSIBinder.placeCall(handle, extras, "arbitrary_package_name", null);
+ fail("Expected SecurityException because CALL_PHONE was not granted to caller");
} catch (SecurityException e) {
// expected
}
verify(mUserCallIntentProcessor, never())
- .processIntent(any(Intent.class), anyString(), anyBoolean(), eq(true));
+ .processIntent(any(Intent.class), anyString(), eq(false), anyBoolean(), eq(true));
+ }
+
+ /**
+ * Ensure that if the caller was granted CALL_PHONE, but did not get the OP_CALL_PHONE app op
+ * (and is not the default dialer), a SecurityException is thrown.
+ */
+ @SmallTest
+ @Test
+ public void testPlaceCallAppOpFailure() throws Exception {
+ Uri handle = Uri.parse("tel:6505551234");
+ Bundle extras = createSampleExtras();
+
+ // The app is not considered a privileged dialer and does not have the OP_CALL_PHONE
+ // app op.
+ doNothing().when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
+ when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+ nullable(String.class), nullable(String.class)))
+ .thenReturn(AppOpsManager.MODE_IGNORED);
+
+ try {
+ mTSIBinder.placeCall(handle, extras, "arbitrary_package_name", null);
+ fail("Expected SecurityException because CALL_PHONE was not granted to caller");
+ } catch (SecurityException e) {
+ // expected
+ }
+
+ verify(mUserCallIntentProcessor, never())
+ .processIntent(any(Intent.class), anyString(), eq(false), anyBoolean(), eq(true));
}
@SmallTest
@@ -1654,6 +2103,8 @@
}
private static boolean areBundlesEqual(Bundle b1, Bundle b2) {
+ if (b1.keySet().size() != b2.keySet().size()) return false;
+
for (String key1 : b1.keySet()) {
if (!b1.get(key1).equals(b2.get(key1))) {
return false;
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 7e7235d..d013fae 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -66,7 +66,6 @@
import android.telecom.VideoProfile;
import android.telephony.TelephonyManager;
import android.telephony.TelephonyRegistryManager;
-import android.text.TextUtils;
import com.android.internal.telecom.IInCallAdapter;
import com.android.server.telecom.AsyncRingtonePlayer;
@@ -97,6 +96,7 @@
import com.android.server.telecom.Timeouts;
import com.android.server.telecom.WiredHeadsetManager;
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
+import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
import com.android.server.telecom.components.UserCallIntentProcessor;
import com.android.server.telecom.ui.IncomingCallNotifier;
@@ -113,6 +113,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
@@ -211,6 +212,8 @@
@Mock DeviceIdleControllerAdapter mDeviceIdleControllerAdapter;
@Mock Ringer.AccessibilityManagerAdapter mAccessibilityManagerAdapter;
+ @Mock
+ BlockedNumbersAdapter mBlockedNumbersAdapter;
final ComponentName mInCallServiceComponentNameX =
new ComponentName(
@@ -391,6 +394,7 @@
handlerThread.quitSafely();
}
handlerThreads.clear();
+ mTelecomSystem.getCallsManager().getVoipCallMonitor().stopMonitor();
}
waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
waitForHandlerAction(mHandlerThread.getThreadHandler(), TEST_TIMEOUT);
@@ -512,7 +516,8 @@
WiredHeadsetManager wiredHeadsetManager,
StatusBarNotifier statusBarNotifier,
CallAudioManager.AudioServiceFactory audioServiceFactory,
- int earpieceControl) {
+ int earpieceControl,
+ Executor asyncTaskExecutor) {
return new CallAudioRouteStateMachine(context,
callsManager,
bluetoothManager,
@@ -521,7 +526,8 @@
audioServiceFactory,
// Force enable an earpiece for the end-to-end tests
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mHandlerThread.getLooper());
+ mHandlerThread.getLooper(),
+ Runnable::run /* async tasks as now sync for testing! */);
}
},
new CallAudioModeStateMachine.Factory() {
@@ -540,7 +546,9 @@
ContactsAsyncHelper.ContentResolverAdapter adapter) {
return new ContactsAsyncHelper(adapter, mHandlerThread.getLooper());
}
- }, mDeviceIdleControllerAdapter, mAccessibilityManagerAdapter);
+ }, mDeviceIdleControllerAdapter, mAccessibilityManagerAdapter,
+ Runnable::run,
+ mBlockedNumbersAdapter);
mComponentContextFixture.setTelecomManager(new TelecomManager(
mComponentContextFixture.getTestDouble(),
@@ -757,7 +765,7 @@
final UserHandle userHandle = initiatingUser;
Context localAppContext = mComponentContextFixture.getTestDouble().getApplicationContext();
new UserCallIntentProcessor(localAppContext, userHandle).processIntent(
- actionCallIntent, null, true /* hasCallAppOp*/, false /* isLocal */);
+ actionCallIntent, null, false, true /* hasCallAppOp*/, false /* isLocal */);
// Wait for handler to start CallerInfo lookup.
waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
// Send the CallerInfo lookup reply.
diff --git a/tests/src/com/android/server/telecom/tests/TransactionTests.java b/tests/src/com/android/server/telecom/tests/TransactionTests.java
index 1e6734b..3fc87a9 100644
--- a/tests/src/com/android/server/telecom/tests/TransactionTests.java
+++ b/tests/src/com/android/server/telecom/tests/TransactionTests.java
@@ -47,13 +47,12 @@
import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.ui.ToastFactory;
-import com.android.server.telecom.voip.AnswerCallTransaction;
import com.android.server.telecom.voip.EndCallTransaction;
import com.android.server.telecom.voip.HoldCallTransaction;
import com.android.server.telecom.voip.IncomingCallTransaction;
import com.android.server.telecom.voip.OutgoingCallTransaction;
-import com.android.server.telecom.voip.HoldActiveCallForNewCallTransaction;
-import com.android.server.telecom.voip.RequestFocusTransaction;
+import com.android.server.telecom.voip.MaybeHoldCallForNewCallTransaction;
+import com.android.server.telecom.voip.RequestNewActiveCallTransaction;
import org.junit.After;
import org.junit.Before;
@@ -143,40 +142,56 @@
}
@Test
- public void testTransactionalRequestFocus() throws Exception {
+ public void testRequestNewCallFocusWithDialingCall() throws Exception {
// GIVEN
- RequestFocusTransaction transaction =
- new RequestFocusTransaction(mCallsManager, mMockCall1);
+ RequestNewActiveCallTransaction transaction =
+ new RequestNewActiveCallTransaction(mCallsManager, mMockCall1);
// WHEN
+ when(mMockCall1.getState()).thenReturn(CallState.DIALING);
transaction.processTransaction(null);
// THEN
verify(mCallsManager, times(1))
- .transactionRequestNewFocusCall(eq(mMockCall1), eq(CallState.ACTIVE),
- isA(OutcomeReceiver.class));
+ .requestNewCallFocusAndVerify(eq(mMockCall1), isA(OutcomeReceiver.class));
}
@Test
- public void testAnswerCallTransaction() throws Exception {
+ public void testRequestNewCallFocusWithRingingCall() throws Exception {
// GIVEN
- AnswerCallTransaction transaction =
- new AnswerCallTransaction(mCallsManager, mMockCall1, 0);
+ RequestNewActiveCallTransaction transaction =
+ new RequestNewActiveCallTransaction(mCallsManager, mMockCall1);
// WHEN
+ when(mMockCall1.getState()).thenReturn(CallState.RINGING);
transaction.processTransaction(null);
// THEN
verify(mCallsManager, times(1))
- .transactionRequestNewFocusCall(eq(mMockCall1), eq(CallState.ANSWERED),
- isA(OutcomeReceiver.class));
+ .requestNewCallFocusAndVerify(eq(mMockCall1), isA(OutcomeReceiver.class));
+ }
+
+ @Test
+ public void testRequestNewCallFocusFailure() throws Exception {
+ // GIVEN
+ RequestNewActiveCallTransaction transaction =
+ new RequestNewActiveCallTransaction(mCallsManager, mMockCall1);
+
+ // WHEN
+ when(mMockCall1.getState()).thenReturn(CallState.DISCONNECTING);
+ when(mCallsManager.getActiveCall()).thenReturn(null);
+ transaction.processTransaction(null);
+
+ // THEN
+ verify(mCallsManager, times(0))
+ .requestNewCallFocusAndVerify( eq(mMockCall1), isA(OutcomeReceiver.class));
}
@Test
public void testTransactionalHoldActiveCallForNewCall() throws Exception {
// GIVEN
- HoldActiveCallForNewCallTransaction transaction =
- new HoldActiveCallForNewCallTransaction(mCallsManager, mMockCall1);
+ MaybeHoldCallForNewCallTransaction transaction =
+ new MaybeHoldCallForNewCallTransaction(mCallsManager, mMockCall1);
// WHEN
transaction.processTransaction(null);
diff --git a/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java b/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java
index ae6e15f..98624d4 100644
--- a/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java
+++ b/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java
@@ -35,6 +35,7 @@
import com.android.internal.telecom.ICallEventCallback;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.TransactionalServiceRepository;
import com.android.server.telecom.TransactionalServiceWrapper;
import com.android.server.telecom.voip.EndCallTransaction;
@@ -68,6 +69,7 @@
@Mock private TransactionManager mTransactionManager;
@Mock private ICallEventCallback mCallEventCallback;
@Mock private TransactionalServiceRepository mRepository;
+ private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {};
@Override
@Before
@@ -76,6 +78,7 @@
MockitoAnnotations.initMocks(this);
Mockito.when(mMockCall1.getId()).thenReturn(CALL_ID_1);
Mockito.when(mMockCall2.getId()).thenReturn(CALL_ID_2);
+ Mockito.when(mCallsManager.getLock()).thenReturn(mLock);
mTransactionalServiceWrapper = new TransactionalServiceWrapper(mCallEventCallback,
mCallsManager, SERVICE_HANDLE, mMockCall1, mRepository);
diff --git a/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java b/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
new file mode 100644
index 0000000..346b3d8
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.telecom.PhoneAccountHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.voip.VoipCallMonitor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+@RunWith(JUnit4.class)
+public class VoipCallMonitorTest extends TelecomTestCase {
+ private VoipCallMonitor mMonitor;
+ private static final String PKG_NAME_1 = "telecom.voip.test1";
+ private static final String PKG_NAME_2 = "telecom.voip.test2";
+ private static final String CLS_NAME = "VoipActivity";
+ private static final String ID_1 = "id1";
+ private static final UserHandle USER_HANDLE_1 = new UserHandle(1);
+ private static final long TIMEOUT = 5000L;
+
+ @Mock private TelecomSystem.SyncRoot mLock;
+ @Mock private ActivityManagerInternal mActivityManagerInternal;
+ @Mock private NotificationListenerService mListenerService;
+
+ private final PhoneAccountHandle mHandle1User1 = new PhoneAccountHandle(
+ new ComponentName(PKG_NAME_1, CLS_NAME), ID_1, USER_HANDLE_1);
+ private final PhoneAccountHandle mHandle2User1 = new PhoneAccountHandle(
+ new ComponentName(PKG_NAME_2, CLS_NAME), ID_1, USER_HANDLE_1);
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mMonitor = new VoipCallMonitor(mContext, mLock);
+ mActivityManagerInternal = mock(ActivityManagerInternal.class);
+ mListenerService = mock(NotificationListenerService.class);
+ mMonitor.setActivityManagerInternal(mActivityManagerInternal);
+ mMonitor.setNotificationListenerService(mListenerService);
+ doNothing().when(mListenerService).registerAsSystemService(eq(mContext),
+ any(ComponentName.class), anyInt());
+ mMonitor.startMonitor();
+ }
+
+ @SmallTest
+ @Test
+ public void testStartMonitorForOneCall() {
+ Call call = createTestCall("testCall", mHandle1User1);
+ IBinder service = mock(IBinder.class);
+
+ ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class);
+ mMonitor.onCallAdded(call);
+ verify(mActivityManagerInternal, timeout(TIMEOUT)).startForegroundServiceDelegate(any(
+ ForegroundServiceDelegationOptions.class), captor.capture());
+ ServiceConnection conn = captor.getValue();
+ conn.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+ mMonitor.onCallRemoved(call);
+ verify(mActivityManagerInternal, timeout(TIMEOUT)).stopForegroundServiceDelegate(eq(conn));
+ }
+
+ @SmallTest
+ @Test
+ public void testMonitorForTwoCallsOnSameHandle() {
+ Call call1 = createTestCall("testCall1", mHandle1User1);
+ Call call2 = createTestCall("testCall2", mHandle1User1);
+ IBinder service = mock(IBinder.class);
+
+ ArgumentCaptor<ServiceConnection> captor1 =
+ ArgumentCaptor.forClass(ServiceConnection.class);
+ mMonitor.onCallAdded(call1);
+ verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+ .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+ captor1.capture());
+ ServiceConnection conn1 = captor1.getValue();
+ conn1.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+ ArgumentCaptor<ServiceConnection> captor2 =
+ ArgumentCaptor.forClass(ServiceConnection.class);
+ mMonitor.onCallAdded(call2);
+ verify(mActivityManagerInternal, timeout(TIMEOUT).times(2))
+ .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+ captor2.capture());
+ ServiceConnection conn2 = captor2.getValue();
+ conn2.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+ mMonitor.onCallRemoved(call1);
+ verify(mActivityManagerInternal, never()).stopForegroundServiceDelegate(
+ any(ServiceConnection.class));
+ mMonitor.onCallRemoved(call2);
+ verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+ .stopForegroundServiceDelegate(eq(conn2));
+ }
+
+ @SmallTest
+ @Test
+ public void testMonitorForTwoCallsOnDifferentHandle() {
+ Call call1 = createTestCall("testCall1", mHandle1User1);
+ Call call2 = createTestCall("testCall2", mHandle2User1);
+ IBinder service = mock(IBinder.class);
+
+ ArgumentCaptor<ServiceConnection> connCaptor1 = ArgumentCaptor.forClass(
+ ServiceConnection.class);
+ ArgumentCaptor<ForegroundServiceDelegationOptions> optionsCaptor1 =
+ ArgumentCaptor.forClass(ForegroundServiceDelegationOptions.class);
+ mMonitor.onCallAdded(call1);
+ verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+ .startForegroundServiceDelegate(optionsCaptor1.capture(), connCaptor1.capture());
+ ForegroundServiceDelegationOptions options1 = optionsCaptor1.getValue();
+ ServiceConnection conn1 = connCaptor1.getValue();
+ conn1.onServiceConnected(mHandle1User1.getComponentName(), service);
+ assertEquals(PKG_NAME_1, options1.getComponentName().getPackageName());
+
+ ArgumentCaptor<ServiceConnection> connCaptor2 = ArgumentCaptor.forClass(
+ ServiceConnection.class);
+ ArgumentCaptor<ForegroundServiceDelegationOptions> optionsCaptor2 =
+ ArgumentCaptor.forClass(ForegroundServiceDelegationOptions.class);
+ mMonitor.onCallAdded(call2);
+ verify(mActivityManagerInternal, timeout(TIMEOUT).times(2))
+ .startForegroundServiceDelegate(optionsCaptor2.capture(), connCaptor2.capture());
+ ForegroundServiceDelegationOptions options2 = optionsCaptor2.getValue();
+ ServiceConnection conn2 = connCaptor2.getValue();
+ conn2.onServiceConnected(mHandle2User1.getComponentName(), service);
+ assertEquals(PKG_NAME_2, options2.getComponentName().getPackageName());
+
+ mMonitor.onCallRemoved(call2);
+ verify(mActivityManagerInternal).stopForegroundServiceDelegate(eq(conn2));
+ mMonitor.onCallRemoved(call1);
+ verify(mActivityManagerInternal).stopForegroundServiceDelegate(eq(conn1));
+ }
+
+ @SmallTest
+ @Test
+ public void testStopDelegation() {
+ Call call1 = createTestCall("testCall1", mHandle1User1);
+ Call call2 = createTestCall("testCall2", mHandle1User1);
+ IBinder service = mock(IBinder.class);
+
+ ArgumentCaptor<ServiceConnection> captor1 =
+ ArgumentCaptor.forClass(ServiceConnection.class);
+ mMonitor.onCallAdded(call1);
+ verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+ .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+ captor1.capture());
+ ServiceConnection conn1 = captor1.getValue();
+ conn1.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+ ArgumentCaptor<ServiceConnection> captor2 =
+ ArgumentCaptor.forClass(ServiceConnection.class);
+ mMonitor.onCallAdded(call2);
+ verify(mActivityManagerInternal, timeout(TIMEOUT).times(2))
+ .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+ captor2.capture());
+ ServiceConnection conn2 = captor2.getValue();
+ conn2.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+ mMonitor.stopFGSDelegation(mHandle1User1);
+ verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+ .stopForegroundServiceDelegate(eq(conn2));
+ conn2.onServiceDisconnected(mHandle1User1.getComponentName());
+ mMonitor.onCallRemoved(call1);
+ verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+ .stopForegroundServiceDelegate(any(ServiceConnection.class));
+ }
+
+ private Call createTestCall(String id, PhoneAccountHandle handle) {
+ Call call = mock(Call.class);
+ when(call.getTargetPhoneAccount()).thenReturn(handle);
+ when(call.isTransactionalCall()).thenReturn(true);
+ when(call.getExtras()).thenReturn(new Bundle());
+ when(call.getId()).thenReturn(id);
+ when(call.getCallingPackageIdentity()).thenReturn( new Call.CallingPackageIdentity() );
+ return call;
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java b/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
index 58e4a77..e2c7b7b 100644
--- a/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
+++ b/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
@@ -25,6 +25,7 @@
import androidx.test.filters.SmallTest;
+import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.voip.ParallelTransaction;
import com.android.server.telecom.voip.SerialTransaction;
import com.android.server.telecom.voip.TransactionManager;
@@ -49,6 +50,7 @@
public class VoipCallTransactionTest extends TelecomTestCase {
private StringBuilder mLog;
private TransactionManager mTransactionManager;
+ private static final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
private class TestVoipCallTransaction extends VoipCallTransaction {
public static final int SUCCESS = 0;
@@ -60,7 +62,7 @@
private int mType;
public TestVoipCallTransaction(String name, long sleepTime, int type) {
- super();
+ super(mLock);
mName = name;
mSleepTime = sleepTime;
mType = type;
@@ -125,7 +127,8 @@
OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
resultFuture::complete;
String expectedLog = "t1 success;\nt2 success;\nt3 success;\n";
- mTransactionManager.addTransaction(new SerialTransaction(subTransactions), outcomeReceiver);
+ mTransactionManager.addTransaction(new SerialTransaction(subTransactions, mLock),
+ outcomeReceiver);
assertEquals(VoipCallTransactionResult.RESULT_SUCCEED,
resultFuture.get(5000L, TimeUnit.MILLISECONDS).getResult());
assertEquals(expectedLog, mLog.toString());
@@ -158,7 +161,7 @@
exceptionFuture.complete(e.getMessage());
}
};
- mTransactionManager.addTransaction(new SerialTransaction(subTransactions),
+ mTransactionManager.addTransaction(new SerialTransaction(subTransactions, mLock),
outcomeReceiver);
exceptionFuture.get(5000L, TimeUnit.MILLISECONDS);
String expectedLog = "t1 success;\nt2 failed;\n";
@@ -182,7 +185,7 @@
CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
resultFuture::complete;
- mTransactionManager.addTransaction(new ParallelTransaction(subTransactions),
+ mTransactionManager.addTransaction(new ParallelTransaction(subTransactions, mLock),
outcomeReceiver);
assertEquals(VoipCallTransactionResult.RESULT_SUCCEED,
resultFuture.get(5000L, TimeUnit.MILLISECONDS).getResult());
@@ -208,7 +211,7 @@
subTransactions.add(t3);
CompletableFuture<String> exceptionFuture = new CompletableFuture<>();
OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
- new OutcomeReceiver<VoipCallTransactionResult, CallException>() {
+ new OutcomeReceiver<>() {
@Override
public void onResult(VoipCallTransactionResult result) {
@@ -219,7 +222,7 @@
exceptionFuture.complete(e.getMessage());
}
};
- mTransactionManager.addTransaction(new ParallelTransaction(subTransactions),
+ mTransactionManager.addTransaction(new ParallelTransaction(subTransactions, mLock),
outcomeReceiver);
exceptionFuture.get(5000L, TimeUnit.MILLISECONDS);
assertTrue(mLog.toString().contains("t2 failed;\n"));
@@ -231,12 +234,20 @@
throws ExecutionException, InterruptedException, TimeoutException {
VoipCallTransaction t = new TestVoipCallTransaction("t", 10000L,
TestVoipCallTransaction.SUCCESS);
- CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
+ CompletableFuture<String> exceptionFuture = new CompletableFuture<>();
OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
- resultFuture::complete;
- mTransactionManager.addTransaction(t, outcomeReceiver);
- VoipCallTransactionResult result = resultFuture.get(7000L, TimeUnit.MILLISECONDS);
- assertEquals(VoipCallTransactionResult.RESULT_FAILED, result.getResult());
- assertTrue(result.getMessage().contains("timeout"));
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(VoipCallTransactionResult result) {
+
+ }
+
+ @Override
+ public void onError(CallException e) {
+ exceptionFuture.complete(e.getMessage());
+ }
+ }; mTransactionManager.addTransaction(t, outcomeReceiver);
+ String message = exceptionFuture.get(7000L, TimeUnit.MILLISECONDS);
+ assertTrue(message.contains("timeout"));
}
}