[automerger skipped] DO NOT MERGE am: d80cf1a7b7 am: bfa897ef51 -s ours
am skip reason: subject contains skip directive
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Telecomm/+/19748788
Change-Id: Id39a91daf3b381b9f3b65ebb551a95383044aa5e
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 629c95c..0d89b00 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,9 +1,24 @@
+genrule {
+ name: "statslog-telecom-java-gen",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --java $(out) --module telecom"
+ + " --javaPackage com.android.server.telecom --javaClass TelecomStatsLog",
+ out: ["com/android/server/telecom/TelecomStatsLog.java"],
+}
+
+filegroup {
+ name: "Telecom-srcs",
+ srcs: [
+ "src/**/*.java",
+ ":statslog-telecom-java-gen",
+ ],
+}
+
// Build the Telecom service.
android_app {
name: "Telecom",
- libs: ["telephony-common"],
srcs: [
- "src/**/*.java",
+ ":Telecom-srcs",
"proto/**/*.proto",
],
resource_dirs: ["res"],
@@ -26,17 +41,18 @@
static_libs: [
"android-ex-camera2",
"guava",
- "mockito-target-inline",
+ "mockito-target-extended",
"androidx.test.rules",
"platform-test-annotations",
"androidx.legacy_legacy-support-core-ui",
"androidx.legacy_legacy-support-core-utils",
"androidx.core_core",
"androidx.fragment_fragment",
+ "androidx.test.ext.junit"
],
srcs: [
"tests/src/**/*.java",
- "src/**/*.java",
+ ":Telecom-srcs",
"proto/**/*.proto",
],
proto: {
@@ -52,10 +68,12 @@
"android.test.mock",
"android.test.base",
"android.test.runner",
- "telephony-common",
],
- jni_libs: ["libdexmakerjvmtiagent"],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
aaptflags: [
"--auto-add-overlay",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 07ee6fa..7c57599 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -35,6 +35,7 @@
<uses-permission android:name="android.permission.BROADCAST_PHONE_ACCOUNT_REGISTRATION" />
<uses-permission android:name="android.permission.CALL_PRIVILEGED" />
<uses-permission android:name="android.permission.HANDLE_CALL_INTENT" />
+ <uses-permission android:name="android.permission.HANDLE_CAR_MODE_CHANGES" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
@@ -290,14 +291,6 @@
android:process=":ui">
</activity>
- <activity android:name=".ui.CallRedirectionConfirmDialogActivity"
- android:configChanges="orientation|screenSize|keyboardHidden"
- android:excludeFromRecents="true"
- android:launchMode="singleInstance"
- android:theme="@style/Theme.Telecomm.Transparent"
- android:process=":ui">
- </activity>
-
<activity android:name=".ui.CallRedirectionTimeoutDialogActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:excludeFromRecents="true"
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..9874044
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,2 @@
+[Hook Scripts]
+aosp_hook = ${REPO_ROOT}/packages/services/Telecomm/scripts/aosp_tag_preupload.py ${PREUPLOAD_COMMIT}
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..75feeb6
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,20 @@
+{
+ "presubmit": [
+ {
+ "name": "TelecomUnitTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "TeleServiceTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/proto/telecom.proto b/proto/telecom.proto
index 73eba87..411b8e2 100644
--- a/proto/telecom.proto
+++ b/proto/telecom.proto
@@ -177,6 +177,13 @@
// The type of the in-call service
optional InCallServiceType in_call_service_type = 2;
+
+ // The number of milliseconds that the in call service remained bound between binding and
+ // disconnection.
+ optional int64 bound_duration_millis = 3;
+
+ // True if the in call service has ever crashed during a call.
+ optional bool is_null_binding = 4;
}
// Information about each call.
diff --git a/res/raw/record.ogg b/res/raw/record.ogg
index a023e6d..732b42f 100644
--- a/res/raw/record.ogg
+++ b/res/raw/record.ogg
Binary files differ
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index fe0253d..2857be3 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Gemiste oproep van <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Bel terug"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Boodskap"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Oproep is ontkoppel"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Die oproep na <xliff:g id="CALLER">%s</xliff:g> is ontkoppel as gevolg van \'n noodoproep wat gemaak word."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Jou oproep is ontkoppel as gevolg van \'n noodoproep wat gemaak word."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Agtergrondoproep"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> het \'n oproep in die agtergrond geplaas. Hierdie program kan dalk toegang tot oudio kry en dit oor die oproep speel."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> het opgehou reageer"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Jou oproep het die foonprogram gebruik wat saam met jou toestel gekom het"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Oproep stilgemaak."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Luidsprekerfoon geaktiveer."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Kan nie nou praat nie. Hoe\'s dit?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Inkomende oproepe"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Gemiste oproepe"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Oproepblokkering"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Agtergrondoproepe"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Oproepe is ontkoppel"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Omgevalde foonprogramme"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"As jy hierdie oproep maak, sal dit jou <xliff:g id="OTHER_APP">%1$s</xliff:g>-oproep beëindig."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Kies hoe om hierdie oproep te maak"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Herlei oproep deur <xliff:g id="OTHER_APP">%1$s</xliff:g> te gebruik"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Noodoproep gemaak"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Oproepblokkering is gedeaktiveer sodat noodeenhede jou kan kontak."</string>
<string name="developer_title" msgid="9146088855661672353">"Telecom-ontwikkelaarkieslys"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Oproepe kan nie gedurende \'n noodoproep geneem word nie."</string>
</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index b712188..df8215c 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"ከ<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> ያመለጠ ጥሪ"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"የጀርባ ጥሪ"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"ገቢ ጥሪዎች"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"ያመለጡ ጥሪዎች"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"ጥሪን ማገድ"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 43cd1cf..7e204d0 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"مكالمة فائتة من <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"مكالمة في الخلفية"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"وضَع <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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"المكالمات الواردة"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"المكالمات الفائتة"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"حظر المكالمات"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index f2dc5e5..2f5de68 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>ৰ পৰা মিছ্ড কল"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"নেপথ্যৰ কল"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"অন্তৰ্গামী কল"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"মিছ্ড কল"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"কল অৱৰোধ"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index e7124e0..d79343f 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> tərəfindən zəng buraxılıb"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Geriyə zəng"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Mesaj"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Əlaqəsi kəsilmiş zəng"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Hazırda təcili zəng edildiyi üçün <xliff:g id="CALLER">%s</xliff:g> ilə zəng kəsilib."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Təcili zəng edildiyinə görə zənginizin əlaqəsi kəsildi."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Arxa fon zəngi"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> arxa fonda zəng edib. Bu tətbiq zəng ilə daxil ola və oxuda bilər."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> tətbiqində xəta baş verdi"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Zəng üçün cihazda əvvəlcədən quraşdırılan telefon tətbiqindən istifadə edildi"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Səssiz zəng edin."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Spikerfon aktivdir."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"İndi danışmaq olmur. Nə olub?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Gələn zənglər"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Buraxılmış zənglər"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Zəngi Bloklama"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Arxa fon zəngləri"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Əlaqəsi kəsilmiş zənglər"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Xəta baş verən telefon tətbiqləri"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Bu zəngin yerləşdirilməsi <xliff:g id="OTHER_APP">%1$s</xliff:g> zəngini sonlandıracaq."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Bu zəngi necə etməyi seçin"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g> istifadə edərək zəngi yönləndirin"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Təcili zəng edildi"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Təcili zənglərə cavab verənlərin Sizinlə əlaqə saxlamalarına icazə vermək üçün Zəngi Bloklama deaktiv edilib."</string>
<string name="developer_title" msgid="9146088855661672353">"Telecom Tərtibatçı Menyusu"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Təcili zəng zamanı zəng edilə bilməz."</string>
</resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index ce4a97e..eba8c5c 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Propušten poziv od: <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Uzvrati poziv"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Poruka"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Poziv je prekinut"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Poziv sa <xliff:g id="CALLER">%s</xliff:g> je prekinut jer se upućuje hitni poziv."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Poziv je prekinut jer se upućuje hitni poziv."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Poziv u pozadini"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Aplikacija <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> je uputila poziv u pozadini. Ona može da pristupa zvuku i pušta ga tokom poziva."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> više ne reaguje"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Poziv je koristio aplikaciju za telefoniranje koju ste dobili uz uređaj"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Zvuk poziva je isključen."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Spikerfon je omogućen."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"U gužvi sam. O čemu se radi?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Dolazni pozivi"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Propušteni pozivi"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Blokiranje poziva"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Pozivi u pozadini"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Prekinuti pozivi"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplikacije za telefoniranje koje su otkazale"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Ako uputite ovaj poziv, završićete <xliff:g id="OTHER_APP">%1$s</xliff:g> poziv."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Izaberite kako želite da uputite ovaj poziv"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Preusmeri poziv pomoću: <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Upućen je hitni poziv"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Blokiranje poziva je onemogućeno da bi hitne službe mogle da vas kontaktiraju."</string>
<string name="developer_title" msgid="9146088855661672353">"Meni za programere Telecom-a"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Za vreme hitnog poziva nije moguće preuzimati druge pozive."</string>
</resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index dae746a..b6dd707 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Прапушчаны выклік ад <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"Фонавы выклік"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Праграма \"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Уваходныя выклікі"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Прапушчаныя выклікі"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Блакіраванне выклікаў"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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">"Меню распрацоўшчыка Telecom"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Падчас экстраннага выкліку іншыя выклікі прымаць немагчыма."</string>
</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index d579297..7afddc2 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Пропуснато обаждане от <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"Обаждане: заден план"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Входящи обаждания"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Пропуснати обаждания"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Блокиране на обажданията"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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">"Меню за програмисти на Telecom"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"По време на спешно обаждане не могат да се поемат обаждания."</string>
</resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index adde38b..cf04d40 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> এর থেকে মিসড কল"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"ব্যাকগ্রাউন্ডের কল"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"ইনকামিং কল"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"মিস করা কল"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"কল ব্লক করা"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 43d59a1..1968357 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Propušteni poziv od kontakta <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Povr. poziv"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Poruka"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Poziv je prekinut"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Poziv upućen kontaktu <xliff:g id="CALLER">%s</xliff:g> je prekinut zbog upućivanja hitnog poziva."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Poziv je prekinut zbog upućivanja hitnog poziva."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Poziv u pozadini"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Aplikacija <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> je uputila poziv u pozadini. Ova aplikacija može pristupati zvuku i reproducirati ga tokom poziva."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"Aplikacija <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> je prestala reagirati"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Za poziv se koristila aplikacija za telefon koju ste dobili uz uređaj"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Zvuk poziva je isključen."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Zvučnik je omogućen."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Ne mogu sada pričati. O čemu se radi?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Dolazni pozivi"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Propušteni pozivi"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Blokiranje poziva"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Pozivi u pozadini"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Prekinuti pozivi"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Padovi aplikacija za telefon"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Upućivanje ovog poziva će prekinuti poziv: <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Odaberite kako želite uputiti ovaj poziv"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Preusmjeri poziv pomoću aplikacije <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Upućen je hitni poziv"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Blokiranje poziva je onemogućeno kako bi se omogućilo osobama koje reagiraju u hitnim slučajevima da vas kontaktiraju."</string>
<string name="developer_title" msgid="9146088855661672353">"Meni za programere iz telekoma"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Pozivi se ne mogu primati tokom hitnog poziva"</string>
</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index f87581f..69a7b6e 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Trucada perduda de <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Torna la trucada"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Missatge"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Trucada desconnectada"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"La trucada a <xliff:g id="CALLER">%s</xliff:g> s\'ha desconnectat perquè s\'ha fet una trucada d\'emergència."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"La teva trucada s\'ha desconnectat perquè s\'ha fet una trucada d\'emergència."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Trucada en segon pla"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ha fet una trucada en segon pla. És possible que aquesta aplicació estigui accedint a l\'àudio i reproduint-lo a través de la trucada."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> ha deixat de respondre"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"A la trucada s\'ha fet servir l\'aplicació de telèfon que hi ha al dispositiu"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Trucada silenciada."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Altaveu activat."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Ara no puc parlar. Què passa?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Trucades entrants"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Trucades perdudes"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Bloqueig de trucades"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Trucades en segon pla"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Trucades desconnectades"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplicacions del telèfon que han fallat"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"En fer aquesta trucada, finalitzarà la de l\'aplicació <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Tria com vols fer aquesta trucada"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Desvia la trucada amb <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"S\'ha fet una trucada d\'emergència"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"El bloqueig de trucades s\'ha desactivat perquè els serveis d\'emergència puguin contactar amb tu."</string>
<string name="developer_title" msgid="9146088855661672353">"Menú per a desenvolupadors de telecomunicacions"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"No es poden respondre trucades durant una trucada d\'emergència."</string>
</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 7451067..1730a30 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Zmeškaný hovor od volajícího <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>."</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Zavolat zpět"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Zpráva"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Hovor byl odpojen"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Hovor s volajícím <xliff:g id="CALLER">%s</xliff:g> byl odpojen, protože začalo být prováděno tísňové volání."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Váš hovor byl odpojen, protože bylo zahájeno tísňové volání."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Hovor na pozadí"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Aplikace <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> přesunula hovor na pozadí. Tato aplikace může v hovoru používat a přehrávat zvuk."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"Aplikace <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> přestala reagovat"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Při hovoru byla použita předinstalovaná telefonní aplikace"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Hovor ztlumen."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Reproduktor je zapnutý."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Teď nemůžu mluvit, o co jde?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Příchozí hovory"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Zmeškané hovory"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Blokování hovorů"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Hovory na pozadí"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Odpojené hovory"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplikace, které spadly"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Uskutečněním tohoto hovoru ukončíte hovor <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Vyberte, jak chcete tento hovor provést"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Přesměrovat hovor přes aplikaci <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Uskutečněno tísňové volání"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Blokování hovorů bylo vypnuto, aby vás mohli kontaktovat pracovníci tísňových služeb."</string>
<string name="developer_title" msgid="9146088855661672353">"Nabídka pro vývojáře Telecomu"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Během tísňového volání není možné přijímat hovory."</string>
</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 28b3b27..6a208c4 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Ubesvarede opkald fra <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Ring tilbage"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Besked"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Afbrudt opkald"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Opkaldet til <xliff:g id="CALLER">%s</xliff:g> er blevet afbrudt, fordi der foretages et nødopkald."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Dit opkald er blevet afbrudt, fordi der foretages et nødopkald."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Opkald i baggrunden"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> har foretaget et opkald i baggrunden. Denne app har muligvis adgang til og afspiller lyd i opkaldet."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> svarer ikke"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Dit opkald brugte den opkaldsapp, din enhed er født med"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Lyd slået fra opkald."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Højttalertelefon aktiveret."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Kan ikke tale nu. Hvad sker der?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Indgående opkald"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Ubesvarede opkald"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Opkaldsblokering"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Opkald i baggrunden"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Afbrudte opkald"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Opkaldsapps, der er gået ned"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Hvis du foretager dette opkald, afsluttes dit opkald i <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Vælg, hvordan du vil foretage dette opkald"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Omdiriger opkaldet ved hjælp af <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Foretagne nødopkald"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Opkaldsblokering er blevet deaktiveret for at give nødnumre mulighed for at kontakte dig."</string>
<string name="developer_title" msgid="9146088855661672353">"Udviklermenu for Telecom"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Du kan ikke besvare opkald, mens du er i et nødopkald."</string>
</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 00ae6a7..c434ecc 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Entgangener Anruf von <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Zurückrufen"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Nachricht"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Anruf beendet"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Der Anruf mit <xliff:g id="CALLER">%s</xliff:g> wurde beendet, weil ein Notruf getätigt wurde."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Der Anruf wurde beendet, weil ein Notruf getätigt wurde."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Anruf im Hintergrund"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> hat einen Anruf in den Hintergrund verschoben. Diese App greift möglicherweise auf den Anruf zu und spielt Audio darüber ab."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> reagiert nicht mehr"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Für den Anruf wurde die auf deinem Gerät vorinstallierte App verwendet"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Anruf stummgeschaltet"</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Freisprechfunktion aktiviert"</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Kann jetzt nicht sprechen. Was gibt\'s?"</string>
@@ -40,7 +47,7 @@
<string name="respond_via_sms_failure_format" msgid="5198680980054596391">"Fehler beim Senden der Nachricht an <xliff:g id="PHONE_NUMBER">%s</xliff:g>"</string>
<string name="enable_account_preference_title" msgid="6949224486748457976">"Anrufkonten"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="3424338207838851646">"Es sind nur Notrufe erlaubt."</string>
- <string name="outgoing_call_not_allowed_no_permission" msgid="8590468836581488679">"Diese App darf ohne die Berechtigung \"Standard-App für Telefonie\" keine ausgehenden Anrufe tätigen."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="8590468836581488679">"Diese App darf ohne die Berechtigung \"Telefon-App\" keine ausgehenden Anrufe tätigen."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="7665135102566099778">"Gib eine gültige Nummer ein."</string>
<string name="duplicate_video_call_not_allowed" msgid="5754746140185781159">"Der Anruf kann momentan nicht hinzugefügt werden."</string>
<string name="no_vm_number" msgid="2179959110602180844">"Fehlende Mailbox-Nummer"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Eingehende Anrufe"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Verpasste Anrufe"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Anrufblockierung"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Anrufe im Hintergrund"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Beendete Anrufe"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Abgestürzte Telefon-Apps"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Durch diesen Anruf wird der Anruf in <xliff:g id="OTHER_APP">%1$s</xliff:g> beendet."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Wie möchtest du anrufen?"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Mit <xliff:g id="OTHER_APP">%1$s</xliff:g> weiterleiten"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Notruf abgesetzt"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Die Anrufblockierung wurde deaktiviert, damit Ersthelfer und Rettungskräfte dich kontaktieren können."</string>
<string name="developer_title" msgid="9146088855661672353">"Telecom-Entwicklermenü"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Während eines Notrufs kannst du keine Anrufe annehmen."</string>
</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index d0568a4..ab523c1 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Αναπάντητη κλήση από <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"Κλήση στο παρασκήνιο"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Η εφαρμογή <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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Εισερχόμενες κλήσεις"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Αναπάντητες κλήσεις"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Φραγή κλήσεων"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 8bf839b..4de7584 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Missed call from <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Call back"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Message"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Disconnected call"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"The call to <xliff:g id="CALLER">%s</xliff:g> has been disconnected due to an emergency call being placed."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Your call has been disconnected due to an emergency call being placed."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Background call"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> has placed a call into the background. This app may be accessing and playing audio over the call."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> stopped responding"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Your call used the phone app that came with your device"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Call muted."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Speakerphone enabled."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Can\'t talk now. What\'s going on?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Incoming calls"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Missed calls"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Call Blocking"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Background calls"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Disconnected calls"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Crashed phone apps"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Placing this call will end your <xliff:g id="OTHER_APP">%1$s</xliff:g> call."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Choose how to make this call"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirect call using <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Emergency call made"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Call Blocking has been disabled to allow emergency responders to contact you."</string>
<string name="developer_title" msgid="9146088855661672353">"Telecom Developer Menu"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Calls can not be taken while in an emergency call."</string>
</resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index 8bf839b..4de7584 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Missed call from <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Call back"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Message"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Disconnected call"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"The call to <xliff:g id="CALLER">%s</xliff:g> has been disconnected due to an emergency call being placed."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Your call has been disconnected due to an emergency call being placed."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Background call"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> has placed a call into the background. This app may be accessing and playing audio over the call."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> stopped responding"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Your call used the phone app that came with your device"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Call muted."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Speakerphone enabled."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Can\'t talk now. What\'s going on?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Incoming calls"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Missed calls"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Call Blocking"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Background calls"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Disconnected calls"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Crashed phone apps"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Placing this call will end your <xliff:g id="OTHER_APP">%1$s</xliff:g> call."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Choose how to make this call"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirect call using <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Emergency call made"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Call Blocking has been disabled to allow emergency responders to contact you."</string>
<string name="developer_title" msgid="9146088855661672353">"Telecom Developer Menu"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Calls can not be taken while in an emergency call."</string>
</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 8bf839b..4de7584 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Missed call from <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Call back"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Message"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Disconnected call"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"The call to <xliff:g id="CALLER">%s</xliff:g> has been disconnected due to an emergency call being placed."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Your call has been disconnected due to an emergency call being placed."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Background call"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> has placed a call into the background. This app may be accessing and playing audio over the call."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> stopped responding"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Your call used the phone app that came with your device"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Call muted."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Speakerphone enabled."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Can\'t talk now. What\'s going on?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Incoming calls"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Missed calls"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Call Blocking"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Background calls"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Disconnected calls"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Crashed phone apps"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Placing this call will end your <xliff:g id="OTHER_APP">%1$s</xliff:g> call."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Choose how to make this call"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirect call using <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Emergency call made"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Call Blocking has been disabled to allow emergency responders to contact you."</string>
<string name="developer_title" msgid="9146088855661672353">"Telecom Developer Menu"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Calls can not be taken while in an emergency call."</string>
</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 8bf839b..4de7584 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Missed call from <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Call back"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Message"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Disconnected call"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"The call to <xliff:g id="CALLER">%s</xliff:g> has been disconnected due to an emergency call being placed."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Your call has been disconnected due to an emergency call being placed."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Background call"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> has placed a call into the background. This app may be accessing and playing audio over the call."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> stopped responding"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Your call used the phone app that came with your device"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Call muted."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Speakerphone enabled."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Can\'t talk now. What\'s going on?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Incoming calls"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Missed calls"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Call Blocking"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Background calls"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Disconnected calls"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Crashed phone apps"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Placing this call will end your <xliff:g id="OTHER_APP">%1$s</xliff:g> call."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Choose how to make this call"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirect call using <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Emergency call made"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Call Blocking has been disabled to allow emergency responders to contact you."</string>
<string name="developer_title" msgid="9146088855661672353">"Telecom Developer Menu"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Calls can not be taken while in an emergency call."</string>
</resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 71205ab..3b3a983 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Missed call from <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Call back"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Message"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Disconnected call"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"The call to <xliff:g id="CALLER">%s</xliff:g> has been disconnected due to an emergency call being placed."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Your call has been disconnected due to an emergency call being placed."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Background call"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> has placed a call into the background. This app may be accessing and playing audio over the call."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> stopped responding"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Your call used the phone app that came with your device"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Call muted."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Speakerphone enabled."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Can\'t talk now. What\'s up?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Incoming calls"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Missed calls"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Call Blocking"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Background calls"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Disconnected calls"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Crashed phone apps"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Placing this call will end your <xliff:g id="OTHER_APP">%1$s</xliff:g> call."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Choose how to place this call"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirect call using <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Emergency call made"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Call Blocking has been disabled to allow emergency responders to contact you."</string>
<string name="developer_title" msgid="9146088855661672353">"Telecom Developer Menu"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Calls can not be taken while in an emergency call."</string>
</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index d59d127..826b877 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Se perdieron las llamadas de <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Llamar"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Mensaje"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Se desconectó la llamada"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Se desconectó la llamada a <xliff:g id="CALLER">%s</xliff:g> porque se está realizando una llamada de emergencia."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Se desconectó tu llamada porque se está haciendo una llamada de emergencia."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Llamada en 2.° plano"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> movió una a segundo plano. Es posible que esta app acceda a la llamada y reproduzca audio."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> dejó de responder"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Tu llamada se hizo con la app de teléfono que venía en tu dispositivo"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Llamada silenciada"</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Altavoz habilitado"</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"No puedo hablar ahora. ¿Todo bien?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Llamadas entrantes"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Llamadas perdidas"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Bloqueo de llamadas"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Llamadas en segundo plano"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Llamadas desconectadas"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Apps de teléfono con fallas"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Si realizas esta llamada, finalizará la de <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Elige cómo quieres realizar esta llamada"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redireccionar la llamada mediante <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Se realizó una llamada de emergencia"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Se inhabilitó el bloqueo de llamadas para permitir que los servicios de emergencia se comuniquen contigo."</string>
<string name="developer_title" msgid="9146088855661672353">"Menú para desarrolladores de Telecom"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"No puedes contestar llamadas mientras estés en una llamada de emergencia."</string>
</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 391a5c2..e124de4 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Llamada perdida de <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Llamar"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Mensaje"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Llamada interrumpida"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Se ha interrumpido la llamada a <xliff:g id="CALLER">%s</xliff:g> debido a una llamada de emergencia."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Se ha interrumpido tu llamada debido a una llamada de emergencia."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Llamada en segundo plano"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ha llamado en segundo plano. Puede que esta aplicación acceda al audio y lo reproduzca durante la llamada."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> ha dejado de responder"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"La llamada se utilizó la aplicación para teléfonos que venía con tu dispositivo"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Llamada silenciada"</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Altavoz habilitado"</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"No puedo hablar. ¿Es importante?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Llamadas entrantes"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Llamadas perdidas"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Bloqueo de llamadas"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Llamadas en segundo plano"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Llamadas interrumpidas"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplicaciones para teléfonos con bloqueos"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Si haces esta llamada, se finalizará la de <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Elige cómo quieres hacer esta llamada"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirigir llamada con <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Se ha hecho una llamada de emergencia"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Se ha inhabilitado el bloqueo de llamadas para que los servicios de emergencia puedan ponerse en contacto contigo."</string>
<string name="developer_title" msgid="9146088855661672353">"Menú para desarrolladores de telecomunicaciones"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"No se pueden responder llamadas durante una llamada de emergencia."</string>
</resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 9128b1d..bb291ad 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Vastamata kõne helistajalt <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Helista tagasi"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Sõnum"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Kõne on katkestatud"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Kõne helistajaga <xliff:g id="CALLER">%s</xliff:g> on katkestatud, kuna alustati hädaabikõnet."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Teie kõne katkestati, kuna alustati hädaabikõnet."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Taustal olev kõne"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> lülitas kõne taustale. See rakendus võib helile juurde pääseda ja seda kõne ajal esitada."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> lõpetas reageerimise"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Teie kõne kasutas teie seadmega kaasas olnud telefonirakendust"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Kõne on summutatud."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Valjuhääldi on sisse lülitatud."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Ei saa praegu rääkida. Milles asi?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Sissetulevad kõned"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Vastamata kõned"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Kõnede blokeerimine"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Taustal olevad kõned"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Katkestatud kõned"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Kokkujooksnud telefonirakendused"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Selle kõne tegemisel lõpetatakse pooleliolev kõne rakenduses <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Valige, kuidas soovite helistada"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Suuna kõne ümber rakenduse <xliff:g id="OTHER_APP">%1$s</xliff:g> abil"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Tehti hädaabikõne"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Kõnede blokeerimine on keelatud, et lubada hädaabiteenustel teiega ühendust võtta."</string>
<string name="developer_title" msgid="9146088855661672353">"Teenuse Telecom arendaja menüü"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Hädaabikõne ajal ei saa kõnesid vastu võtta."</string>
</resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 5186c17..c8956cd 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Deitzaile honen dei galdua: <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Itzuli deia"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Mezua"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Deia deskonektatu egin da"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"<xliff:g id="CALLER">%s</xliff:g> deitzaileari egiten ari zinen deia deskonektatu da larrialdi-dei bat egiten ari zarelako."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Deia deskonektatu egin da, larrialdi-dei bat egiten ari zarelako."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Atzeko planoko deia"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> aplikazioak atzeko planoan jarri du deia. Baliteke aplikazioak audioa atzitzea eta erreproduzitzea deian zehar."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"Erantzuteari utzi dio <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> aplikazioak"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Gailuaren telefono-aplikazioarekin egin da deia"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Deiaren audioa desaktibatu da."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Bozgorailua gaitu da."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Ezin dut hitz egin. Arazoren bat al dago?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Jasotako deiak"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Dei galduak"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Deiak blokeatzeko aukera"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Atzeko planoko deiak"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Deskonektatutako deiak"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Huts egin duten telefonoko aplikazioak"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Dei hau egiten baduzu, amaitu egingo da <xliff:g id="OTHER_APP">%1$s</xliff:g> aplikazioko deia."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Aukeratu dei hau egiteko modua"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Birbideratu deia <xliff:g id="OTHER_APP">%1$s</xliff:g> aplikazioaren bidez"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Larrialdi-deia egin da"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Desgaitu da deiak blokeatzeko aukera, larrialdietako zerbitzuak zurekin harremanetan jarri ahal daitezen."</string>
<string name="developer_title" msgid="9146088855661672353">"Telekomunikazioen garatzaileen menua"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Ezin duzu hartu deirik larrialdi-dei bat abian den bitartean."</string>
</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index dbc4b71..c873159 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"تماس بی پاسخ از <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"تماس پسزمینه"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"تماسهای ورودی"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"تماسهای بیپاسخ"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"مسدود کردن تماس"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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">"منوی برنامهنویس Telecom"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"درحین برقراری تماسی اضطراری، نمیتوان به تماسها پاسخ داد."</string>
</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index a714eee..cf57d62 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Vastaamatta jäänyt puhelu numerosta <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Soita"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Viesti"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Puhelu katkaistu"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Puhelu vastaanottajalle <xliff:g id="CALLER">%s</xliff:g> on katkaistu hätäpuhelun soittamisen vuoksi."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Puhelusi on katkaistu hätäpuhelun soittamisen vuoksi."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Taustapuhelu"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> on asettanut puhelun taustalle. Tämä sovellus voi käyttää ja toistaa ääntä puhelun päällä."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> lakkasi vastaamasta"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Puhelu soitettiin laitteen mukana tulleella puhelinsovelluksella"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Puhelu mykistetty."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Kaiutin käytössä."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"En voi vastata. Mitä asiaa?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Saapuvat puhelut"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Vastaamattomat puhelut"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Puhelujen esto"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Taustapuhelut"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Katkaistut puhelut"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Kaatuneet puhelinsovellukset"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Tämän puhelun soittaminen päättää puhelun sovelluksessa <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Valitse, miten puhelu soitetaan"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Uudelleenohjaa puhelu sovelluksella <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Hätäpuhelu soitettu"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Puhelujen esto on poistettu käytöstä, jotta pelastusviranomaiset voivat soittaa puhelimeesi."</string>
<string name="developer_title" msgid="9146088855661672353">"Televiestinnän kehittäjävalikko"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Puheluita ei voi välittää hätäpuhelun aikana."</string>
</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 3ebecc8..74faab9 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Appel manqué de <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Rappeler"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Message"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Appel déconnecté"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"L\'appel à <xliff:g id="CALLER">%s</xliff:g> a été déconnecté en raison d\'un appel d\'urgence qui a été passé."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Votre appel a été déconnecté en raison d\'un appel d\'urgence en cours de lancement."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Appel en arrière-plan"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> a passé un appel en arrière-plan. Cette application peut accéder à l\'audio de l\'appel et faire jouer un contenu audio par l\'intermédiaire de l\'appel."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> a arrêté de répondre"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Votre appel a utilisé l\'application Téléphone intégrée à votre appareil"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Son coupé"</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Haut-parleur activé"</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Peux pas parler. Quoi de neuf?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Appels entrants"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Appels manqués"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Blocage des appels"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Appels en arrière-plan"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Appels déconnectés"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Applications téléphoniques qui ont planté"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Si vous passez cet appel, vous mettrez fin à l\'appel <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Choisissez comment passer cet appel"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Rediriger l\'appel en utilisant <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Appel d\'urgence effectué"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Le blocage des appels a été désactivé pour permettre aux intervenants d\'urgence de communiquer avec vous."</string>
<string name="developer_title" msgid="9146088855661672353">"Menu Telecom Developer"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Les appels ne peuvent pas être pris pendant un appel d\'urgence."</string>
</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index a6fb6ba..c2a95a2 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Appel manqué de <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Rappeler"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Message"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Appel interrompu"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"L\'appel avec <xliff:g id="CALLER">%s</xliff:g> a été interrompu en raison d\'un appel d\'urgence."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Votre appel a été interrompu en raison d\'un appel d\'urgence."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Appel en arrière-plan"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> a placé un appel en arrière-plan. Il est possible que cette application lise des fichiers audio pendant l\'appel."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> a cessé de répondre"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"L\'appel s\'est poursuivi dans l\'application d\'appel préinstallée sur votre appareil"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Son coupé"</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Haut-parleur activé"</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Je ne peux pas répondre. Ça va ?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Appels entrants"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Appels manqués"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Blocage d\'appels"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Appels en arrière-plan"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Appels interrompus"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Applications téléphoniques ayant planté"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Si vous passez cet appel, vous mettrez fin à celui qui est en cours dans l\'application <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Choisissez comment passer cet appel"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Rediriger l\'appel avec <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Appel d\'urgence"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Le blocage d\'appels a été désactivé pour que les services d\'urgence puissent vous contacter."</string>
<string name="developer_title" msgid="9146088855661672353">"Menu Telecom Developer"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Impossible de prendre un appel au cours d\'un appel d\'urgence."</string>
</resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 13775de..42c55d6 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Chamada perdida de <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Devolver chamada"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Mensaxe"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Chamada desconectada"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Desconectouse a chamada a <xliff:g id="CALLER">%s</xliff:g> porque se realizou unha chamada de emerxencia."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Desconectouse a chamada porque se realizou unha chamada de emerxencia."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Chamada seg. plano"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> chamou en segundo plano. Pode que esta aplicación acceda ao audio e o reproduza durante a chamada."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> deixou de responder"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Usouse a aplicación para teléfonos que ven co teu dispositivo na chamada"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Chamada silenciada"</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Altofalante activado"</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Non podo falar agora. Que pasa?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Chamadas entrantes"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Chamadas perdidas"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Bloqueo de chamadas"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Chamadas en segundo plano"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Chamadas desconectadas"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Fallaron as aplicacións de teléfono"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Ao facer esta chamada, finalizarase o túa chamada de <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Escolle como facer esta chamada"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirixir a chamada con <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Realizouse unha chamada de emerxencia"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Desactivouse o bloqueo de chamadas parar permitir que os servizos de emerxencias se poidan poñer en contacto contigo."</string>
<string name="developer_title" msgid="9146088855661672353">"Menú para programadores de telecomunicacións"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Non se pode responder ás chamadas durante unha chamada de emerxencia."</string>
</resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index b832aea..7455080 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> નો કૉલ ચૂકી ગયાં"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"બૅકગ્રાઉન્ડ કૉલ"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"ઇનકમિંગ કૉલ"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"છૂટેલા કૉલ"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"કૉલ બ્લૉક કરી રહ્યાં છીએ"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 634d39c..ed623b8 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> की कॉल छूटी"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"बैकग्राउंड कॉल"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"इनकमिंग कॉल"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"छूटे कॉल"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"कॉल पर रोक"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 9f91db8..ff38e54 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Propušten poziv kontakta <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Povratni poziv"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Poruka"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Prekinuti poziv"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Poziv upućen <xliff:g id="CALLER">%s</xliff:g> prekinut je zbog uspostavljanja hitnog poziva."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Poziv je prekinut zbog hitnog poziva."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Poziv u pozadini"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Aplikacija <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> prebacila je poziv u pozadinu. Ta aplikacija možda pristupa zvuku i reproducira ga putem poziva."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> više ne odgovara"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Vaš je poziv upotrijebio aplikaciju telefona koju ste dobili na uređaju"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Zvuk poziva isključen."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Zvučnik je omogućen."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Sada ne mogu razgovarati. Što ima?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Dolazni pozivi"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Propušteni pozivi"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Blokiranje poziva"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Pozivi u pozadini"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Prekinuti pozivi"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Rušenja aplikacija telefona"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Upućivanjem ovog poziva prekinut ćete poziv u aplikaciji <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Odaberite kako ćete uputiti poziv"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Preusmjeri poziv putem aplikacije <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Hitni je poziv upućen"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Blokiranje poziva onemogućeno je da bi vas mogli kontaktirati djelatnici hitnih službi."</string>
<string name="developer_title" msgid="9146088855661672353">"Izbornik Telecom Developer"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Pozivi se ne mogu primiti tijekom hitnog poziva."</string>
</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 2655975..55e5535 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Nem fogadott hívás: <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Visszahívás"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Üzenet"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Bontott hívás"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Az Ön és <xliff:g id="CALLER">%s</xliff:g> közötti hívást a szolgáltató egy segélyhívás kezdeményezése miatt bontotta."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Segélyhívást kezdeményeztek, ezért az Ön hívását megszakítottuk."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Háttérbeli hívás"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"A(z) <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> a háttérbe helyezett egy hívást. Ez az alkalmazás hozzáférhet a híváshoz, és hangot játszhat le benne."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"A(z) <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> lefagyott"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"A hívása a készülékhez mellékelt telefonalkalmazást használta"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Hívás némítva."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Kihangosítás engedélyezve."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Most nem alkalmas. Mi újság?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Beérkező hívások"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Nem fogadott hívások"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Hívásletiltás"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Háttérbeli hívások"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Bontott hívások"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Telefonalkalmazások összeomlása"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Ha hívást indít, azzal megszakítja a(z) <xliff:g id="OTHER_APP">%1$s</xliff:g>-hívást."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"A hívás módjának kiválasztása"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Hívás átirányítása a következővel: <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Segélyhívás indítva"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"A hívásletiltás ki van kapcsolva, hogy a segélyszolgálatok kapcsolatba léphessenek Önnel."</string>
<string name="developer_title" msgid="9146088855661672353">"Telekommunikációs fejlesztői menü"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Segélyhívás közben nem lehet hívást fogadni."</string>
</resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index f5125a9..6987dab 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Բաց թողնված զանգ <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>-ից"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"Ֆոնային զանգ"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Մուտքային զանգեր"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Բաց թողնված զանգեր"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Զանգերի արգելափակում"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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">"Telecom-ի մշակողի ընտրացանկ"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Շտապ կանչի ժամանակ այլ զանգեր չեք կարող ընդւնել։"</string>
</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 923e441..23ebb73 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Panggilan tak terjawab dari <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Telepon"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Pesan"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Panggilan terputus"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Panggilan kepada <xliff:g id="CALLER">%s</xliff:g> terputus karena ada panggilan darurat."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Panggilan Anda terputus karena ada panggilan darurat."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Panggilan latar belakang"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> telah menempatkan panggilan telepon ke latar belakang. Aplikasi ini mungkin mengakses dan memutar audio melalui panggilan telepon."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> berhenti merespons"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Panggilan Anda menggunakan aplikasi telepon bawaan perangkat Anda"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Panggilan disenyapkan."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Pengeras suara ponsel diaktifkan."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Tak bisa bicara sekarang. Ada apa?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Panggilan masuk"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Panggilan tak terjawab"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Pemblokiran Panggilan"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Panggilan telepon latar belakang"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Panggilan terputus"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplikasi telepon error"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Melakukan panggilan ini akan mengakhiri panggilan <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Pilih cara melakukan panggilan ini"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Alihkan panggilan menggunakan <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Panggilan darurat dibuat"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Pemblokiran Panggilan dinonaktifkan untuk mengizinkan penjawab darurat menghubungi Anda."</string>
<string name="developer_title" msgid="9146088855661672353">"Menu Developer Telecom"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Panggilan tidak dapat diterima saat sedang melakukan panggilan darurat."</string>
</resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index ccdd57f..442b9d5 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Ósvarað símtal frá <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Hringja til baka"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Skilaboð"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Aftengt símtal"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Símtalið til <xliff:g id="CALLER">%s</xliff:g> hefur verið aftengt vegna þess að neyðarsímtal var hringt."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Símtalið var aftengt vegna þess að neyðarsímtal var hringt."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Bakgrunnssímtal"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> hefur sett símtal í bakgrunn. Hugsanlega fær þetta forrit aðgang að hljóði yfir símtalið og spilar það."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> hætti að svara"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Símtalið þitt notaði símaforritið sem fylgdi tækinu"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Símtal þaggað."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Kveikt á hátalara."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Kemst ekki í símann. Eitthvað títt?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Móttekin símtöl"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Ósvöruð símtöl"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Lokað fyrir símtöl"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Bakgrunnssímtöl"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Aftengd símtöl"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Hrun í símaforritum"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Ef þú hringir mun þessu símtali í <xliff:g id="OTHER_APP">%1$s</xliff:g> ljúka."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Veldu hvernig hringt er"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Framsenda símtal með <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Neyðarsímtal var hringt"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Slökkt hefur verið á „Lokað fyrir símtöl“ svo neyðarþjónustuaðilar geti haft samband við þig."</string>
<string name="developer_title" msgid="9146088855661672353">"Forritaravalmynd fyrir fjarskipti"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Ekki er hægt að svara símtölum meðan á neyðarsímtali stendur."</string>
</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 18925ce..60f32d7 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Chiamata senza risposta da <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Richiama"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Messaggio"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Chiamata disconnessa"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"La chiamata a <xliff:g id="CALLER">%s</xliff:g> è stata disconnessa per dare priorità a una chiamata di emergenza."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"La tua chiamata è stata disconnessa per dare priorità a una chiamata di emergenza."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"In sottofondo"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ha spostato una chiamata in sottofondo. L\'app potrà accedere all\'audio e riprodurlo in sovrapposizione alla chiamata."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> non risponde più"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Per la tua chiamata è stata utilizzata l\'app per telefono integrata nel tuo dispositivo"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Chiamata disattivata."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Vivavoce attivo."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Non posso parlare ora. Che succede?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Chiamate in arrivo"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Chiamate perse"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Blocco delle chiamate"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Chiamate in sottofondo"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Chiamate disconnesse"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"App per telefono arrestate in modo anomalo"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Se effettui questa chiamata, la chiamata di <xliff:g id="OTHER_APP">%1$s</xliff:g> verrà terminata."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Scegli come effettuare questa chiamata"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Reindirizza la chiamata utilizzando <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Chiamata di emergenza effettuata"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Il blocco delle chiamate è stato disattivato per consentire ai servizi di emergenza di contattarti."</string>
<string name="developer_title" msgid="9146088855661672353">"Menu sviluppatore telecomunicazioni"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Impossibile accettare una chiamata durante una chiamata di emergenza."</string>
</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index b2ca40f..30a909b 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"שיחה שלא נענתה מאת <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"שיחה ברקע"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"האפליקציה <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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"שיחות נכנסות"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"שיחות שלא נענו"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"חסימת שיחות"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index f6f60c2..bd87d66 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>さんからの不在着信"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"バックグラウンド通話"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -50,9 +57,9 @@
<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_call_screening_dialog_title" msgid="5365787219927262408">"<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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"着信"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"不在着信"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"着信のブロック"</string>
+ <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_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>
@@ -102,11 +112,12 @@
<string name="phone_settings_private_num_summary_txt" msgid="6755758240544021037">"番号非通知の発信者をブロック"</string>
<string name="phone_settings_payphone_txt" msgid="5003987966052543965">"公衆電話"</string>
<string name="phone_settings_payphone_summary_txt" msgid="3936631076065563665">"公衆電話からの着信をブロック"</string>
- <string name="phone_settings_unknown_txt" msgid="3577926178354772728">"不明"</string>
+ <string name="phone_settings_unknown_txt" msgid="3577926178354772728">"不明な発信者"</string>
<string name="phone_settings_unknown_summary_txt" msgid="5446657192535779645">"不明な発信者からの着信をブロック"</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">"Telecom デベロッパー メニュー"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"緊急通報中は、他の電話を受けることができません。"</string>
</resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 6b19df5..be27e8b 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"გამოტოვებული ზარი <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>-ისგან"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"ზარი ფონში"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"შემომავალი ზარები"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"გამოტოვებული ზარები"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"ზარების დაბლოკვა"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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">"Telecom-ის დეველოპერის მენიუ"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"გადაუდებელი ზარის დროს ზარების მიღება შეუძლებელია."</string>
</resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 4c06e71..0bab244 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> қоңырауы қабылданбаған"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"Фондық қоңырау"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Кіріс қоңыраулары"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Қабылданбаған қоңыраулар"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Қоңырауды бөгеу"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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">"Telecom Developer мәзірі"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Құтқару қызметімен сөйлесіп жатқанда, басқа қоңырауларды қабылдай алмайсыз."</string>
</resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index b04294c..58b2abf 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"ខកខានទទួលពី <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"ការហៅនៅផ្ទៃខាងក្រោយ"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"ការហៅចូល"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"ការហៅដែលមិនបានទទួល"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"ការទប់ស្កាត់ការហៅ"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 043fc3b..6e0bb59 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> ಅವರಿಂದ ಮಿಸ್ಡ್ ಕಾಲ್"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"ಹಿನ್ನೆಲೆ ಕರೆ"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"ಒಳಬರುವ ಕರೆಗಳು"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"ಮಿಸ್ಡ್ ಕಾಲ್ಗಳು"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"ಕರೆ ನಿರ್ಬಂಧಿಸುವಿಕೆ"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index c0c2fad..1a29b2e 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>의 부재중 전화"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"백그라운드 통화"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"수신 전화"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"부재중 전화"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"통화 차단"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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">"Telecom 개발자 메뉴"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"긴급 전화 중에는 전화를 받을 수 없습니다."</string>
</resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 2334294..83e498e 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> дегенден кабыл алынбаган чалуу"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"Фондогу чалуу"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Кирүүчү чалуулар"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Кабыл алынбаган чалуулар"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Чалууну бөгөттөө"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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">"Telecom иштеп чыгуучусунун менюсу"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Шашылыш учурунда чалуулар кабыл алынбайт."</string>
</resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index f37832f..64142ca 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"ສາຍທີ່ບໍ່ໄດ້ຮັບຈາກ <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"ການໂທໃນພື້ນຫຼັງ"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"ສາຍໂທເຂົ້າ"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"ສາຍບໍ່ໄດ້ຮັບ"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"ການບລັອກສາຍ"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 6ce9204..29090d1 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"praleistas skambutis nuo <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Perskambinti"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Pranešimas"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Skambutis atjungtas"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Skambutis kontaktui <xliff:g id="CALLER">%s</xliff:g> buvo atjungtas dėl atliekamo skambučio pagalbos numeriu."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Skambutis buvo atjungtas dėl atliekamo skambučio pagalbos numeriu."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Skambutis fone"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Programa „<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>“ perkėlė skambutį į foną. Ši programa gali pasiekti ir leisti garsą vykstant skambučiui."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"Programa „<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g>“ nebereaguoja"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Atliekant skambutį buvo naudojama telefono programa, kuri buvo įrenginyje jį įsigijus"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Skambutis nutildytas."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Garsiakalbis įgalintas."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Dabar negaliu kalbėti. Kas nutiko?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Gaunamieji skambučiai"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Praleisti skambučiai"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Skambučių blokavimas"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Skambučiai fone"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Skambučiai atjungti"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Užstrigusios telefono programos"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Atliekant šį skambutį bus užbaigtas „<xliff:g id="OTHER_APP">%1$s</xliff:g>“ skambutis."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Pasirinkite, kaip norite skambinti"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Peradresuoti skambutį naudojant programą „<xliff:g id="OTHER_APP">%1$s</xliff:g>“"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Atliktas skambutis pagalbos numeriu"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Skambučių blokavimas išjungtas, kad pagalbos numeriu atsiliepusiems žmonėms būtų leidžiama su jumis susisiekti."</string>
<string name="developer_title" msgid="9146088855661672353">"Telekomunikacijų kūrėjų meniu"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Atliekant skambutį pagalbos numeriu negalima atsiliepti į kitus skambučius."</string>
</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index b73e67a..782777d 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Neatbildēts zvans no: <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Atzvanīt"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Ziņojums"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Pārtraukts zvans"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Zvans lietotājam <xliff:g id="CALLER">%s</xliff:g> ir pārtraukts, jo tiek veikts ārkārtas izsaukums."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Jūsu zvans ir pārtraukts, jo tiek veikts ārkārtas izsaukums."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Saruna fonā"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> pārcēla sarunu uz darbību fonā. Sarunas laikā šī lietotne var piekļūt audio saturam un to atskaņot."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> pārtrauca reaģēt"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Zvanam tika izmantota jūsu ierīcē iebūvētā tālruņa lietotne"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Zvana skaņa ir izslēgta."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Skaļrunis ir iespējots."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Nevaru runāt. Kas gadījās?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Ienākošie zvani"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Neatbildētie zvani"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Zvanu bloķēšana"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Sarunas fonā"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Pārtrauktie zvani"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Avarējušās tālruņa lietotnes"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Veicot šo zvanu, tiks beigts zvans lietotnē <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Izvēlieties, kā veikt šo zvanu"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Novirzīt zvanu, izmantojot lietotni <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Ārkārtas zvans ir veikts"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Zvanu bloķēšana ir atspējota, lai ļautu ar jums sazināties avārijas dienestu darbiniekiem."</string>
<string name="developer_title" msgid="9146088855661672353">"Telecom izstrādātāja izvēlne"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Ārkārtas izsaukuma laikā nevar pieņemt zvanus."</string>
</resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 3e0cd02..23c8033 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Пропуштен повик од <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"Повик во заднина"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Дојдовни повици"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Пропуштени повици"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Блокирање повици"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 3df14a5..5c64034 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> എന്നതിൽ നിന്നുള്ള മിസ്ഡ് കോൾ"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"പശ്ചാത്തല കോൾ"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"ഇൻകമിംഗ് കോളുകൾ"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"മിസ്ഡ് കോളുകൾ"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"കോൾ ബ്ലോക്ക് ചെയ്യൽ"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 09fdda9..c941fdd 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>-н аваагүй дуудлага"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"Арын дуудлага"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Ирж буй дуудлага"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Аваагүй дуудлага"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Дуудлага хориглох"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index e71383d..331cf5c 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> कडील सुटलेला कॉल"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"बॅकग्राउंड कॉल"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"येणारे कॉल"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"मिस्ड कॉल"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"कॉल ब्लॉक करणे"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 8a43aa6..b43b554 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Panggilan tidak dijawab daripada <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Panggil balik"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Mesej"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Panggilan diputuskan sambungan"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Panggilan kepada <xliff:g id="CALLER">%s</xliff:g> telah diputuskan sambungannya kerana panggilan kecemasan sedang dibuat."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Panggilan anda telah diputuskan sambungan kerana panggilan kecemasan sedang dibuat."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Panggilan latar blkg"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> telah meletakkan panggilan dalam latar belakang Apl ini mungkin mengakses dan memainkan audio mengatasi panggilan."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> telah berhenti memberikan respons"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Panggilan anda telah menggunakan aplikasi telefon yang disertakan bersama peranti anda"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Panggilan diredam."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Telefon pembesar suara didayakan."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Sedang sibuk. Ada apa?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Panggilan masuk"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Panggilan tidak dijawab"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Sekatan Panggilan"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Panggilan latar belakang"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Panggilan diputuskan sambungan"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplikasi telefon yang ranap"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Membuat panggilan ini akan menamatkan panggilan <xliff:g id="OTHER_APP">%1$s</xliff:g> anda."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Pilih cara untuk membuat panggilan ini"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Ubah hala panggilan menggunakan <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Panggilan kecemasan dibuat"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Sekatan Panggilan telah dilumpuhkan untuk membolehkan pasukan bantuan kecemasan menghubungi anda."</string>
<string name="developer_title" msgid="9146088855661672353">"Menu Pembangun Telekom"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Panggilan tidak boleh dijawab semasa dalam panggilan kecemasan."</string>
</resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 29f952e..c1b3124 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> က ဖုန်းခေါ်မှုကို မကိုင်မိပါ"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"နောက်ခံမှ ခေါ်ဆိုမှု"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"အဝင်ဖုန်းခေါ်ဆိုမှုများ"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"လွတ်သွားသော ဖုန်းခေါ်ဆိုမှုများ"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"ခေါ်ဆိုမှု ပိတ်ထားခြင်း"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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">"Telecom ဆော့ဖ်ဝဲအင်ဂျင်နီယာ မီနူး"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"အရေးပေါ်ခေါ်ဆိုမှု ပြုလုပ်နေစဉ် ဖုန်းမခေါ်နိုင်ပါ။"</string>
</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 72c362a..3f65c62 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Tapt anrop fra <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Ring tilbake"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Melding"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Koblet fra anropet"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Anropet til <xliff:g id="CALLER">%s</xliff:g> er koblet fra fordi et nødanrop ble utført."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Samtalen din ble brutt fordi et nødanrop ble utført."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Bakgrunnsanrop"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> har flyttet et anrop til bakgrunnen. Det kan hende denne appen bruker og spiller av lyd over anropet."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> sluttet å svare"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Anropet ble gjort med telefonappen som fulgte med enheten din"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Samtalelyd er kuttet."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Høyttaler er aktivert."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Kan ikke snakke nå. Hva skjer?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Innkommende anrop"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Tapte anrop"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Anropsblokkering"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Bakgrunnsanrop"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Frakoblede anrop"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Telefonapper som har krasjet"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Samtalen din i <xliff:g id="OTHER_APP">%1$s</xliff:g> avsluttes hvis du foretar dette anropet."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Velg hvordan du vil ringe"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Viderekoble anropet med <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Nødanrop utført"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Anropsblokkering er slått av for å gjøre det mulig for nødtjenester å kontakte deg."</string>
<string name="developer_title" msgid="9146088855661672353">"Meny for telekommunikasjonsutviklere"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Du kan ikke besvare anrop mens du har et pågående nødanrop."</string>
</resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 07452a7..54643b1 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>बाट छुटेका कल"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"पृष्ठभूमिको कल"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -39,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>
@@ -56,7 +63,7 @@
<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>
@@ -66,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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"आगमन कलहरू"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"छुटेका कलहरू"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"कलमाथि रोक लगाउने सुविधा"</string>
+ <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_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>
@@ -106,7 +116,8 @@
<string name="phone_settings_unknown_summary_txt" msgid="5446657192535779645">"अज्ञात कल गर्ने व्यक्तिहरूको कलमाथि रोक लगाउनुहोस्"</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="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>
</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 37977c2..166e95d 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Gemist gesprek van <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Terugbellen"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Bericht"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Beëindigd gesprek"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Het gesprek met <xliff:g id="CALLER">%s</xliff:g> is beëindigd omdat er een noodoproep is geplaatst."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Je gesprek is beëindigd omdat er een noodoproep is geplaatst."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Achtergrondgesprek"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> heeft een gesprek op de achtergrond geplaatst. Deze app kan audio openen en afspelen over het gesprek."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> reageert niet meer"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Voor je gesprek is de telefoon-app van je apparaat gebruikt"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Gesprek gedempt."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Luidspreker is aan."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Kan nu niet opnemen. Alles goed?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Inkomende gesprekken"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Gemiste gesprekken"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Gesprekken blokkeren"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Achtergrondgesprekken"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Beëindigde gesprekken"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Gecrashte telefoon-apps"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Als je dit gesprek start, wordt je <xliff:g id="OTHER_APP">%1$s</xliff:g>-gesprek beëindigd."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Kies hoe je dit gesprek wilt plaatsen"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Gesprek omleiden via <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Noodoproep geplaatst"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Gesprekken blokkeren is uitgeschakeld zodat nooddiensten je kunnen bereiken."</string>
<string name="developer_title" msgid="9146088855661672353">"Telecomontwikkelaarsmenu"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Gesprekken kunnen niet worden aangenomen tijdens een noodoproep."</string>
</resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 878a457..e0061a7 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>ଙ୍କ ଠାରୁ ମିସ୍-କଲ୍ ମିଳିଛି"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"ବ୍ୟାକ୍ଗ୍ରାଉଣ୍ଡ କଲ୍"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"ଇନ୍କମିଙ୍ଗ କଲ୍"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"ମିସ୍ଡ କଲ୍"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"କଲ୍ ବ୍ଲକିଂ"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 6e28d55..2b94316 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> ਵੱਲੋਂ ਮਿਸਡ ਕਾਲ"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"Background call"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> has placed a call into the background. This app may be accessing and playing audio over the call."</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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"ਇਨਕਮਿੰਗ ਕਾਲਾਂ"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"ਮਿਸ ਕਾਲਾਂ"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"ਕਾਲ ਬਲਾਕ ਕਰਨਾ"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index a060e1b..9cb1980 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Nieodebrane połączenie z <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Oddzwoń"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Wiadomość"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Przerwane połączenie"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Połączenie z rozmówcą <xliff:g id="CALLER">%s</xliff:g> zostało przerwane z powodu nawiązania połączenia alarmowego."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Połączenie zostało przerwane z powodu nawiązania połączenia alarmowego."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Połączenie w tle"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Aplikacja <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> umieściła połączenie w tle. Może ona uzyskiwać dostęp do dźwięku i odtwarzać go podczas połączenia."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"Aplikacja <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> nie odpowiada"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Połączenie zostało zrealizowane za pomocą aplikacji do obsługi telefonu zainstalowanej na urządzeniu"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Połączenie wyciszone."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Głośnik włączony."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Nie mogę rozmawiać. Co słychać?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Połączenia przychodzące"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Połączenia nieodebrane"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Blokowanie połączeń"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Połączenia w tle"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Przerwane połączenia"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplikacje telefoniczne po awarii"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Jeśli zadzwonisz, połączenie w aplikacji <xliff:g id="OTHER_APP">%1$s</xliff:g> zostanie zakończone."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Wybierz, jak chcesz zadzwonić"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Przekieruj połączenie za pomocą aplikacji <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Wykonano połączenie alarmowe"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Blokowanie połączeń zostało wyłączone, aby służby ratownicze mogły się z Tobą skontaktować."</string>
<string name="developer_title" msgid="9146088855661672353">"Menu programisty Telecom"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Nie można odbierać rozmów przy nawiązanym połączeniu alarmowym."</string>
</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 468a53d..bb5213e 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Chamada não atendida de <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Ligar de volta"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Mensagem"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Chamada desligada"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"A chamada para <xliff:g id="CALLER">%s</xliff:g> foi desligada porque foi efetuada uma chamada de emergência."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"A chamada foi desligada porque foi efetuada uma chamada de emergência."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Cham. segundo plano"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> colocou uma chamada em segundo plano. Esta app pode estar a aceder e a reproduzir áudio sobre a chamada."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> deixou de responder."</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"A chamada utilizou a app de telefone incluída com o dispositivo."</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Chamada sem som."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Alta voz ativada."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Não posso falar agora. Que se passa?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Chamadas recebidas"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Chamadas não atendidas"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Bloqueio de chamadas"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Chamadas em segundo plano"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Chamadas desligadas"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Apps Telefone com falhas"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Ao efetuar esta chamada, irá terminar a chamada na app <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Escolha como pretende efetuar esta chamada"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirecionar chamada através de <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Chamada de emergência efetuada"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"O bloqueio de chamadas foi desativado para permitir a receção de contactos de resposta a emergências."</string>
<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>
</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index ecfddd5..5d88619 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Chamada perdida de <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Retornar chamada"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Mensagem"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Chamada desconectada"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"A chamada para <xliff:g id="CALLER">%s</xliff:g> foi desconectada porque uma chamada de emergência está sendo realizada."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Sua chamada foi desconectada porque uma chamada de emergência está sendo feita."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Chamada em 2º plano"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"O app <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> colocou uma chamada em segundo plano. Talvez ele esteja acessando e tocando o áudio durante a chamada."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"O app <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> parou de responder"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"A chamada usou o aplicativo de telefone que veio com seu dispositivo"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Chamada sem áudio."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Viva-voz ativado."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Não posso falar agora. Manda um SMS, por favor?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Chamadas recebidas"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Chamadas perdidas"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Bloqueio de chamadas"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Chamadas em segundo plano"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Chamadas desconectadas"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Falha com os apps de telefone"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Se você ligar agora, sua chamada será encerrada no <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Escolha como fazer esta chamada"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirecionar a chamada usando o <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"A chamada de emergência foi feita"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"O bloqueio de chamadas foi desativado para permitir que a equipe de emergência entre em contato com você."</string>
<string name="developer_title" msgid="9146088855661672353">"Menu do desenvolvedor de telecomunicação"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Durante uma chamada de emergência, não é possível transferir chamadas para o dispositivo."</string>
</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 831cc46..a95d4a4 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Apel nepreluat de la <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Sunați"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Mesaj"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Apel deconectat"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Apelul către <xliff:g id="CALLER">%s</xliff:g> a fost deconectat deoarece a fost inițiat un apel de urgență."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Apelul a fost deconectat deoarece a fost inițiat un apel de urgență."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Apel în fundal"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> a inițiat un apel în fundal. Este posibil ca această aplicație să acceseze și să redea mesaje audio peste apel."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> nu mai răspunde"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Apelul dvs. a folosit aplicația Telefon instalată pe dispozitiv"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Apel cu sunet dezactivat."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Difuzor activat."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Nu pot acum. Despre ce e vorba?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Apeluri primite"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Apeluri nepreluate"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Blocarea apelurilor"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Apeluri în fundal"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Apeluri deconectate"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplicații pentru telefon blocate"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Dacă inițiați acest apel, cel din <xliff:g id="OTHER_APP">%1$s</xliff:g> va fi încheiat."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Alegeți cum vreți să inițiați apelul"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirecționați apelul folosind <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"S-a efectuat un apel de urgență."</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Blocarea apelurilor a fost dezactivată pentru a permite serviciilor de urgență să vă contacteze."</string>
<string name="developer_title" msgid="9146088855661672353">"Meniu pentru dezvoltatori de telecomunicații"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Nu puteți răspunde la apeluri în timpul unui apel de urgență."</string>
</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 0fb6e8f..a50907a 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Пропущенные вызовы от абонента <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"Фоновый вызов"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Приложение <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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Входящие вызовы"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Пропущенные вызовы"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Блокировка вызовов"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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">"Меню разработчика Telecom"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Невозможно принять вызов, когда уже выполняется экстренный вызов."</string>
</resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 1923458..b73272b 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> වෙතින් මඟ හැරුණු ඇමතුම්"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"පසුබිම් ඇමතුම"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"එන ඇමතුම්"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"මඟ හැරුණු ඇමතුම්"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"ඇමතුම් අවහිර කිරීම"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 62f273c..0d79f1a 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Zmeškaný hovor od volajúceho <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Zavolať"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Napísať"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Zrušený hovor"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Hovor kontaktu <xliff:g id="CALLER">%s</xliff:g> bol zrušený, aby mohlo prebehnúť tiesňové volanie."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Váš hovor bol zrušený, aby mohlo prebehnúť tiesňové volanie."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Hovor na pozadí"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Aplikácia <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> umiestnila hovor do pozadia. Táto aplikácia môže mať počas hovoru prístup k zvuku a prehrávať ho."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"Aplikácia <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> prestala reagovať"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Na hovor bola použitá telefónna aplikácia, ktorá bola vopred nainštalovaná vo vašom zariadení"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Zvuk hovoru bol vypnutý."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Reproduktor je povolený."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Teraz nemôžem hovoriť, o čo ide?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Prichádzajúce hovory"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Zmeškané hovory"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Blokovanie hovorov"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Hovory na pozadí"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Zrušené hovory"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Zrútené telefónne aplikácie"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Ak uskutočníte tento hovor, hovor cez <xliff:g id="OTHER_APP">%1$s</xliff:g> bude ukončený."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Vyberte, ako chcete tento hovor uskutočniť"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Presmerovať hovor cez aplikáciu <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Bolo uskutočnené tiesňové volanie"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Blokovanie hovorov bolo vypnuté, aby vás mohli kontaktovať pracovníci tiesňových služieb."</string>
<string name="developer_title" msgid="9146088855661672353">"Ponuka pre vývojárov Telecomu"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Počas tiesňového volania nie je možné prijímať hovory."</string>
</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 8c8c89f..5595c40 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Neodgovorjeni klic od <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Povratni klic"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Sporočilo"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Klic je prekinjen"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Klic za stik <xliff:g id="CALLER">%s</xliff:g> je prekinjen zaradi vzpostavljanja klica v sili."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Klic je prekinjen zaradi vzpostavljanja klica v sili."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Klic v ozadju"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Aplikacija <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> je klic premaknila v ozadje. Ta aplikacija morda dostopa do zvoka in ga predvaja prek klica."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"Aplikacija <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> se ne odziva več"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Klic je bil opravljen prek aplikacije za klicanje, ki jo je v napravo namestil proizvajalec"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Klic izključen."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Zvočnik omogočen."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Zdaj ne morem govoriti. Za kaj gre?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Dohodni klici"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Neodgovorjeni klici"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Blokiranje klicev"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Klici v ozadju"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Prekinjeni klici"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Zrušene aplikacije za klicanje"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Če opravite ta klic, bo končan klic prek aplikacije <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Izberite, kako želite opraviti klic"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Preusmeri klic z aplikacijo <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Opravljen je klic v sili"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Blokiranje klicev je onemogočeno, da lahko stik z vami vzpostavijo uslužbenci služb za klic v sili."</string>
<string name="developer_title" msgid="9146088855661672353">"Meni za razvijalce Telecom"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Med klicem v sili ni mogoče sprejeti klicev."</string>
</resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 3468da1..53f4efc 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Telefonatë e humbur nga <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Telefono"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Mesazh"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Telefonatë e shkëputur"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Telefonata me <xliff:g id="CALLER">%s</xliff:g> është shkëputur sepse është kryer një telefonatë urgjence."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Telefonata jote është shkëputur sepse është kryer një telefonatë urgjence."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Telefonatë në sfond"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> e ka vendosur një telefonatë në sfond. Ky aplikacion mund të ketë qasje dhe të luajë audio mbi telefonatë."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> nuk përgjigjet më"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Telefonata jote ka përdorur aplikacionin e telefonit që ke marrë me pajisjen tënde"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Telefonata kaloi në heshtje."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Altoparlanti u aktivizua."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Nuk flas dot tani. Si është puna?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Telefonatat hyrëse"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Telefonatat e humbura"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Bllokimi i telefonatave"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Telefonatat në sfond"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Telefonatat e shkëputura"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplikacionet e telefonit që kanë pësuar ndërprerje aksidentale"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Kryerja e kësaj telefonate do të mbyllë telefonatën tënde në <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Zgjidh se si do ta kryesh këtë telefonatë"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Ridrejtoje telefonatën duke përdorur <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Telefonata e urgjencës u krye"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Bllokimi i telefonatave është çaktivizuar për të lejuar që personat që përgjigjen në rast urgjence të kontaktojnë me ty."</string>
<string name="developer_title" msgid="9146088855661672353">"Menyja e zhvilluesit të telekomunikimit"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Nuk mund të marrësh telefonata kur je në një telefonatë urgjence."</string>
</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index dfe2e88..adfece0 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Пропуштен позив од: <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"Позив у позадини"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Апликација <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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Долазни позиви"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Пропуштени позиви"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Блокирање позива"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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">"Мени за програмере Telecom-а"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"За време хитног позива није могуће преузимати друге позиве."</string>
</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 896fb92..d59fc79 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Missat samtal från <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Ring upp"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Sms:a"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Samtalet kopplades från"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Samtalet <xliff:g id="CALLER">%s</xliff:g> kopplades från eftersom ett nödsamtal ringdes."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Samtalets kopplades bort på grund av ett nödsamtal."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Bakgrundssamtal"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> har ringt ett samtal i bakgrunden. Denna app kan få åtkomst till och spela upp ljud från samtalet."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> slutade svara"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Samtalet använde telefonappen som medföljer enheten"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Samtalets ljud avstängt."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Högtalartelefon aktiverad."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Kan inte prata nu. Läget?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Inkommande samtal"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Missade samtal"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Samtalsblockering"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Bakgrundssamtal"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Frånkopplade samtal"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Kraschade telefonappar"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Ringer du det här samtalet avslutas samtalet i <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Välj hur du vill ringa samtalet"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Omdirigera samtal med <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Nödsamtal ringt"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Samtalsblockering har inaktiverats för att tillåta att räddningstjänsten kontaktar dig."</string>
<string name="developer_title" msgid="9146088855661672353">"Meny för telekomutvecklare"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Det går inte att besvara samtal medan ett nödsamtal pågår."</string>
</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index d6d8adc..8b8a40c 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Ulikosa simu kutoka kwa <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Mpigie"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Ujumbe"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Simu imekatwa"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Simu uliyompigia <xliff:g id="CALLER">%s</xliff:g> imekatwa kwa sababu kuna simu ya dharura inayopigwa."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Simu yako imekatwa kwa sababu kuna simu ya dharura inayopigwa."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Simu ya chinichini"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> inapiga simu chinichini. Huenda programu hii inafikia na kucheza sauti huku simu ikiendelea."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> imeacha kufanya kazi"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Ulipiga simu kwa kutumia Programu ya simu iliyokuja na kifaa chako"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Simu imezimwa."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Spika za simu zimewezeshwa"</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Siwezi kuongea sasa. Kuna nini?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Simu zinazoingia"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Simu ambazo hukujibu"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Kuzuia Simu"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Simu za chinichini"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Simu zilizokatwa"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Programu za simu zilizoacha kufanya kazi"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Ukipiga simu hii, simu yako kwenye <xliff:g id="OTHER_APP">%1$s</xliff:g> itakatwa."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Chagua jinsi utakavyopiga simu hii"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Elekeza simu ukitumia <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Simu ya dharura imepigwa"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Kipengele cha Kuzuia Simu kimezimwa ili kuruhusu wapigaji simu za dharura kuwasiliana nawe."</string>
<string name="developer_title" msgid="9146088855661672353">"Menyu ya Msanidi programu wa Telecom"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Huwezi kupokea simu nyingine wakati unashiriki katika simu ya dharura."</string>
</resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 3ba8c2c..ee48214 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> இடமிருந்து தவறிய அழைப்பு"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"பின்னணி அழைப்பு"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"அழைப்பை பின்னணியில் செயல்படும் வகையில் <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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"உள்வரும் அழைப்புகள்"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"தவறிய அழைப்புகள்"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"அழைப்புத் தடுப்பு"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 7b21200..94a72e4 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> నుండి సమాధానం ఇవ్వని కాల్"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"నేపథ్యం కాల్"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"ఇన్కమింగ్ కాల్స్"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"సమాధానం ఇవ్వని కాల్స్"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"కాల్ బ్లాక్ చేయడం"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 7653a6f..615abb9 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"สายที่ไม่ได้รับจาก <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"การโทรในเบื้องหลัง"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"สายเรียกเข้า"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"สายที่ไม่ได้รับ"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"การบล็อกสาย"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 286eb82..998abef 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Hindi nasagot na tawag mula kay <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Tawagan"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Padalhan ng mensahe"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Nadiskonektang tawag"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Nadiskonekta ang tawag kay <xliff:g id="CALLER">%s</xliff:g> dahil sa ginagawang pang-emergency na tawag."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Nadiskonekta ang iyong tawag dahil sa ginagawang pang-emergency na tawag."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Tawag sa background"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Naglagay ng tawag ang <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> sa background. Posibleng ina-access at pine-play ng app na ito ang audio sa tawag."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"Huminto ang <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> sa pagtugon"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Ginamit ng tawag mo ang app na telepono na kasama sa iyong device"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Naka-mute ang tawag."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Pinapagana ang speakerphone."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Di masagot ngayon. Ano\'ng meron?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Mga papasok na tawag"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Mga hindi nasagot na tawag"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Pag-block ng Tawag"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Mga tawag sa background"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Nadiskonektang mga tawag"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Nag-crash na mga phone app"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Tatapusin ng pagtawag na ito ang iyong tawag sa <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Piliin kung paano gagawin ang tawag na ito"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"I-redirect ang tawag gamit ang <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Ginawang emergency na tawag"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Na-disable ang Pag-block ng Tawag para payagan ang mga tumutugon sa emergency na kontakin ka."</string>
<string name="developer_title" msgid="9146088855661672353">"Menu ng Telecom Developer"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Hindi puwedeng sumagot ng mga tawag habang nasa emergency na tawag."</string>
</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 86843c2..2e02f8c 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Cevapsız çağrı: <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Geri ara"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Mesaj gönder"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Aramanın bağlantısı kesildi"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Bir acil durum araması yapıldığı için <xliff:g id="CALLER">%s</xliff:g> ile olan görüşmenin bağlantısı kesildi."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Bir acil durum araması yapıldığı için görüşmenizin bağlantısı kesildi."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Arka plandaki arama"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> arka plana bir arama yerleştirdi. Bu uygulama arama üzerinden sese erişiyor ve ses çalıyor olabilir."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> yanıt vermeyi durdurdu"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Görüşmeniz için, cihazınızla gelen telefon uygulaması kullanıldı"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Çağrı sesi kapatıldı."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Hoparlör etkin."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Şimdi konuşamam. Konu nedir?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Gelen çağrılar"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Cevapsız çağrılar"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Çağrı Engelleme"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Arka plandaki aramalar"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Bağlantısı kesilen aramalar"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Kilitlenen telefon uygulamaları"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Bu çağrıyı yaptığınızda <xliff:g id="OTHER_APP">%1$s</xliff:g> çağrınız sona erecek."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Bu aramanın nasıl yapılacağını seçin"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g> uygulamasını kullanarak aramayı yönlendir"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Acil durum çağrısı yapıldı"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Acil durum müdahale ekibinin sizinle iletişime geçmesine olanak tanımak için Çağrı Engelleme devre dışı bırakıldı."</string>
<string name="developer_title" msgid="9146088855661672353">"Telekomünikasyon Geliştirici Menüsü"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Acil durum araması sırasında arama alınamaz."</string>
</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 0604c99..d86b3eb 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Пропущ. виклик від <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"У фоновий режим"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Додаток <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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Вхідні виклики"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Пропущені виклики"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Блокування викликів"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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">"Меню розробника Telecom"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Під час екстрених викликів не можна приймати інші."</string>
</resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 1c1074e..20cca1b 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> کی جانب سے چھوٹی ہوئی کال"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"پس منظر کی کال"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,11 +97,14 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"آنے والی کالیں"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"چھوٹی ہوئی کالیں"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"کال مسدود کرنا"</string>
+ <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_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>
@@ -108,5 +118,6 @@
<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="developer_title" msgid="9146088855661672353">"ٹیلی کام ڈویلپر مینو"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"ہنگامی کال کے دوران کالز نہیں لی جائیں گی۔"</string>
</resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index ad44f20..3271245 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> chaqiruvi javobsiz qoldi"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Telefon"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"SMS"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Chaqiruv tugatildi"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Favqulodda chaqiruv amalga oshirilayotgani uchun <xliff:g id="CALLER">%s</xliff:g> bilan suhbatingiz tugatildi."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Favqulodda chaqiruv amalga oshirilayotgani uchun joriy chaqiruvingiz to‘xtatildi."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Orqa fondagi chaqiruv"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ilovasi chaqiruvni orqa fonga joyladi. Bu ilova ovozli chaqiruvga kirishi yoki unda audio ijro etishi mumkin."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> javob bermayapti"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Chaqiruv qurilmangizga avvaldan o‘rnatilgan ilova orqali amalga oshirildi"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Qo‘ng‘iroq ovozi o‘chirildi."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Karnaychalar yoqildi."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Hozir gaplasholmayman. Tinchlikmi?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Kiruvchi chaqiruvlar"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Javobsiz chaqiruvlar"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Chaqiruvlarni bloklash"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Orqa fondagi chaqiruvlar"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Tugatilgan chaqiruvlar"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Ishdan chiqqan telefon ilovalari"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Bu qo‘ng‘iroqni amalga oshirsangiz, <xliff:g id="OTHER_APP">%1$s</xliff:g> qo‘ng‘irog‘i tugatiladi."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Telefon qilish usulini tanlang"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Chaqiruv <xliff:g id="OTHER_APP">%1$s</xliff:g> orqali qayta uzatilsin"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Favqulodda chaqiruv qilindi"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Sizga favqulodda chiqiruv qilish imkoni bo‘lishi uchun chaqiruvlarni bloklash funksiyasi o‘chirib qo‘yilgan."</string>
<string name="developer_title" msgid="9146088855661672353">"Telecom dasturchisi menyusi"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Favqulodda chaqiruv vaqtida boshqa chaqiruvlarni qabul qilish imkonsiz."</string>
</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index d1fbe28..c6fe44a 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Cuộc gọi nhỡ từ <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Gọi lại"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Tin nhắn"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Cuộc gọi bị ngắt kết nối"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Cuộc gọi đến <xliff:g id="CALLER">%s</xliff:g> đã bị ngắt kết nối do một cuộc gọi khẩn cấp được thực hiện."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Cuộc gọi của bạn đã bị ngắt kết nối do một cuộc gọi khẩn cấp đang được thực hiện."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Cuộc gọi trong nền"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> đã gọi điện ở chế độ nền. Ứng dụng này có thể đang truy cập và phát âm thanh qua cuộc gọi."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> đã dừng phản hồi"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Cuộc gọi của bạn đã dùng ứng dụng dành cho điện thoại đi kèm với thiết bị"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Đã tắt tiếng cuộc gọi."</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Đã bật loa ngoài."</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Giờ tôi không nói chuyện được. Có việc gì không?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Cuộc gọi đến"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Cuộc gọi nhỡ"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Chặn cuộc gọi"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Cuộc gọi trong nền"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Các cuộc gọi bị ngắt kết nối"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Các ứng dụng điện thoại bị lỗi"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Thực hiện cuộc gọi này sẽ kết thúc cuộc gọi <xliff:g id="OTHER_APP">%1$s</xliff:g> của bạn."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Chọn cách thực hiện cuộc gọi này"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Chuyển hướng cuộc gọi bằng cách sử dụng <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Đã thực hiện cuộc gọi khẩn cấp"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Đã tắt tính năng Chặn cuộc gọi để cho phép người trả lời khẩn cấp liên hệ với bạn."</string>
<string name="developer_title" msgid="9146088855661672353">"Menu nhà phát triển dịch vụ viễn thông"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Bạn không thể gọi điện trong khi thực hiện cuộc gọi khẩn cấp."</string>
</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index e8c2282..bbda818 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"来自<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>的未接电话"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"后台通话"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"来电"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"未接电话"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"来电屏蔽"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index f56c99c..5f1c946 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>打來的未接來電"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"背景通話"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"「<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"來電"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"未接來電"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"來電封鎖"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index dba94c5..7c98928 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"來自 <xliff:g id="MISSED_CALL_FROM">%s</xliff:g> 的未接來電"</string>
<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_audioProcessing_title" msgid="1619035039880584575">"背景通話"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"「<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="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>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"來電"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"未接來電"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"來電封鎖"</string>
+ <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_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>
@@ -109,4 +119,5 @@
<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>
</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 18b697a..30c147d 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -26,6 +26,13 @@
<string name="notification_missedCallTicker" msgid="6731461957487087769">"Uphuthelwe ikholi ephuma ku-<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="7900333283939789732">"Phinda ushaye"</string>
<string name="notification_missedCall_message" msgid="4054698824390076431">"Umlayezo"</string>
+ <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Ikholi enqanyuliwe"</string>
+ <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Ikholi eya ku-<xliff:g id="CALLER">%s</xliff:g> inqanyuliwe ngenxa yekholi yesimo esiphuthumayo efakwayo."</string>
+ <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Ikholi yakho inqanyuliwe ngenxa yekholi yesimo esiphuthumayo eyenziwe."</string>
+ <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Ikholi engemuva"</string>
+ <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> yenze ikholi ngemuva. Kungenzeka ukuthi lolu hlelo lokusebenza lufinyelela futhi ludlala okulalelwayo ngaphezu kwekholi."</string>
+ <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> iyeke ukuphendula"</string>
+ <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Ikholi yakho isebenzise uhlelo lokusebenza lefoni elize nedivayisi yakho"</string>
<string name="accessibility_call_muted" msgid="2968461092554300779">"Ikholu ithulisiwe"</string>
<string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Isipikha sefoni sinikwe amandla"</string>
<string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Angikwazi ukukhuluma okwamanje. Kwenzenjani?"</string>
@@ -90,6 +97,9 @@
<string name="notification_channel_incoming_call" msgid="5245550964701715662">"Amakholi angenayo"</string>
<string name="notification_channel_missed_call" msgid="7168893015283909012">"Amakholi akuphuthile"</string>
<string name="notification_channel_call_blocking" msgid="2028807677868598710">"Ukuvimbela ikholi"</string>
+ <string name="notification_channel_background_calls" msgid="7785659903711350506">"Amakholi angemuva"</string>
+ <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Amakholi anqanyuliwe"</string>
+ <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Izinhlelo zokusebenza ezikhubazekile zefoni"</string>
<string name="alert_outgoing_call" msgid="5319895109298927431">"Ukwenza le kholi kuzoqeda enye ikholi yakho ye-<xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Khetha ukuthi uyibeka kanjani le kholi"</string>
<string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Qondisa kabusha ikholi usebenzisa i-<xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,4 +119,5 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"Ikholi ephuthumayo yenziwe"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Ukuvimbela ikholi kukhutshaziwe ukuze kuvunyelwe abaphenduli besimo esiphuthumayo ukuthi baxhumane nawe."</string>
<string name="developer_title" msgid="9146088855661672353">"Imenyu yonjiniyela we-Telecom"</string>
+ <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Amakholi awakwazi ukuthathwa ngesikhathi ukukholi yesimo esiphuthumayo."</string>
</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 5b9636d..9cbbf46 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -53,11 +53,6 @@
Devices should overlay this value based on the type of vibration hardware they employ. -->
<bool name="use_simple_vibration_pattern">false</bool>
- <!-- When true, if Telecom is playing the ringtone, it will attempt to pause for some time
- between repeats of the ringtone.
- When false, the ringtone will be looping with no pause. -->
- <bool name="should_pause_between_ringtone_repeats">true</bool>
-
<!-- Threshold for the X+Y component of gravity needed for the device orientation to be
classified as being on a user's ear. -->
<item name="device_on_ear_xy_gravity_threshold" format="float" type="dimen">5.5</item>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 52b688c..b617ae1 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -24,6 +24,7 @@
<dimen name="blocked_numbers_large_padding">16dp</dimen>
<dimen name="blocked_numbers_extra_large_padding">32dp</dimen>
<dimen name="blocked_numbers_button_bottom_margin">20dp</dimen>
+ <dimen name="blocked_numbers_button_large_padding">15dp</dimen>
<dimen name="blocked_numbers_dialog_padding">24dp</dimen>
<dimen name="blocked_numbers_delete_icon_padding">12dp</dimen>
<dimen name="blocked_numbers_progress_bar_padding">100dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e4e588c..df08d7c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -48,6 +48,43 @@
[CHAR LIMIT=18] -->
<string name="notification_missedCall_message">Message</string>
+ <!-- Disconnected call notification label, used when the call has been automatically
+ disconnected by telecom to make room for a higher priority call, such as an emergency
+ call.-->
+ <string name="notification_disconnectedCall_title">Disconnected call</string>
+ <!-- Body of the notification presented when an ongoing call is disconnected in favor of placing
+ an emergency call. This is required by some carriers. [CHAR LIMIT=NONE] -->
+ <string name="notification_disconnectedCall_body">The call to <xliff:g id="caller">%s</xliff:g> has been disconnected due to an emergency call being placed.</string>
+ <!-- Body of the notification presented when an ongoing call is disconnected in favor of placing
+ an emergency call. This version is used when the call that was disconnected was also an
+ emergency call. In this case, we want to hide the exact number dialed in order to protect
+ user privacy. [CHAR LIMIT=NONE] -->
+ <string name="notification_disconnectedCall_generic_body">
+ Your call has been disconnected due to an emergency call being placed.
+ </string>
+
+ <!-- Title for the persistent notification presented when an app has requested that a call
+ be put into the background so that the app can access the audio from the call
+ [CHAR LIMIT=40] -->
+ <string name="notification_audioProcessing_title">Background call</string>
+ <!-- Body of the persistent notification presented when an app requests
+ that a call be put into the background so that the app can access the audio from the call.
+ [CHAR LIMIT=NONE] -->
+ <string name="notification_audioProcessing_body">
+ <xliff:g id="audio_processing_app_name">%s</xliff:g> has placed a call into the
+ background. This app may be accessing and playing audio over the call.
+ </string>
+
+ <!-- Crashed in call service notification label, used when the in call service has crashed and
+ the system fall back to use system dialer. [CHAR LIMIT=NONE] -->
+ <string name="notification_incallservice_not_responding_title">
+ <xliff:g id="in_call_service_app_name">%s</xliff:g> stopped responding
+ </string>
+ <!-- Body of the notification presented when an in call service crashed. [CHAR LIMIT=NONE] -->
+ <string name="notification_incallservice_not_responding_body">
+ Your call used the phone app that came with your device
+ </string>
+
<!-- Content description of the call muted notification icon for
accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_call_muted">Call muted.</string>
@@ -276,6 +313,12 @@
<string name="notification_channel_missed_call">Missed calls</string>
<!-- Notification channel name for a channel containing call blocking notifications. -->
<string name="notification_channel_call_blocking">Call Blocking</string>
+ <!-- Notification channel name for a channel containing background call notifications. -->
+ <string name="notification_channel_background_calls">Background calls</string>
+ <!-- Notification channel name for a channel containing disconnected call notifications. -->
+ <string name="notification_channel_disconnected_calls">Disconnected calls</string>
+ <!-- Notification channel name for a channel containing crashed phone apps service notifications. -->
+ <string name="notification_channel_in_call_service_crash">Crashed phone apps</string>
<!-- Alert dialog content used to inform the user that placing a new outgoing call will end the
ongoing call in the app "other_app". -->
@@ -321,6 +364,11 @@
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt">Call Blocking has been disabled to allow emergency responders to contact you.</string>
<!-- Window title used for the Telecom Developer Menu -->
<string name="developer_title">Telecom Developer Menu</string>
+ <!-- Some carriers allow an "external" call that has been placed on another device (another
+ tablet, phone, etc...) to be transferred to this device and taken as a call. In
+ the situation that we are already in an emergency call, notify the user that the call can not
+ be taken at this time.-->
+ <string name="toast_emergency_can_not_pull_call">Calls can not be taken while in an emergency call.</string>
<!-- Label for a switch in the Telecom Developer Menu which is used to enable the enhanced call
blocking functionality (for test purposes).
DO NOT TRANSLATE -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 187086b..5ba0a3f 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -68,7 +68,6 @@
<style name="BlockedNumbersButton" parent="BlockedNumbersTextPrimary2">
<item name="android:textColor">@color/theme_color</item>
- <item name="android:textAllCaps">true</item>
</style>
<style name="BlockedNumbersTextHead1"
@@ -83,6 +82,7 @@
<item name="android:textSize">@dimen/blocked_numbers_primary2_font_size</item>
<item name="android:fontFamily">sans-serif-regular</item>
<item name="android:lineSpacingExtra">@dimen/blocked_numbers_line_spacing</item>
+ <item name="android:capitalize">sentences</item>
</style>
<style name="BlockedNumbersTextSecondary">
@@ -90,5 +90,6 @@
<item name="android:textSize">@dimen/blocked_numbers_secondary_font_size</item>
<item name="android:fontFamily">sans-serif-regular</item>
<item name="android:lineSpacingExtra">@dimen/blocked_numbers_secondary_line_spacing</item>
+ <item name="android:capitalize">sentences</item>
</style>
</resources>
diff --git a/res/xml/activity_blocked_numbers.xml b/res/xml/activity_blocked_numbers.xml
index e137313..f884ec9 100644
--- a/res/xml/activity_blocked_numbers.xml
+++ b/res/xml/activity_blocked_numbers.xml
@@ -78,6 +78,8 @@
android:layout_height="wrap_content"
android:text="@string/block_number"
android:layout_marginBottom="@dimen/blocked_numbers_button_bottom_margin"
+ android:paddingTop="@dimen/blocked_numbers_button_large_padding"
+ android:paddingBottom="@dimen/blocked_numbers_button_large_padding"
style="@style/BlockedNumbersButton"
android:background="?android:attr/selectableItemBackgroundBorderless" />
diff --git a/res/xml/layout_blocked_number.xml b/res/xml/layout_blocked_number.xml
index 720d71a..3cdd771 100644
--- a/res/xml/layout_blocked_number.xml
+++ b/res/xml/layout_blocked_number.xml
@@ -29,6 +29,8 @@
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:paddingTop="@dimen/blocked_numbers_delete_icon_padding"
+ android:layout_alignParentLeft="true"
+ android:layout_toLeftOf="@+id/delete_blocked_number"
android:textDirection="ltr" />
<ImageView
diff --git a/scripts/aosp_tag_preupload.py b/scripts/aosp_tag_preupload.py
new file mode 100755
index 0000000..77a9714
--- /dev/null
+++ b/scripts/aosp_tag_preupload.py
@@ -0,0 +1,65 @@
+#!/usr/bin/python
+
+import re
+import subprocess
+import sys
+
+# Looks for a string of the form [aosp/branch-name]
+AOSP_BRANCH_REGEX = "\[aosp/[^\]]+\]"
+
+AOSP_COMMIT_TAG_REGEX = "AOSP:"
+AOSP_COMMIT_LINK_REGEX = "aosp/\d+"
+AOSP_INFEASIBLE_REGEX = "Infeasible[ ]?\S+"
+
+ERROR_MESSAGE = """
+The source of truth for this project is AOSP. If you are uploading something to
+a non-AOSP branch first, please provide a link in your commit message to the
+corresponding patch in AOSP. The link should be formatted as follows:
+
+ AOSP: aosp/<patch number>
+
+If it's infeasible for the change to be included in AOSP (for example, if a
+change contains confidential or security-sensitive information), please state
+that it's infeasible and provide reasoning as follows:
+
+ AOSP: Infeasible <your reasoning here>
+
+If you need to cherry-pick your change from an internal branch to AOSP before
+uploading, you can do so locally by adding the internal branch as a remote in
+AOSP:
+ git remote add goog-master /path/to/your/remote/branch/.git
+starting a new branch in AOSP:
+ repo start <your-branch-name>-cp
+then fetching and cherry-picking the change:
+ git fetch goog-master your-branch-name && git cherry-pick FETCH_HEAD
+"""
+
+def main():
+ if _is_in_aosp():
+ sys.exit(0)
+
+ commit_msg = subprocess.check_output(["git", "show",
+ sys.argv[1], "--no-notes"])
+ for commit_line in commit_msg.splitlines():
+ if re.search(AOSP_COMMIT_TAG_REGEX, commit_line, re.IGNORECASE):
+ _check_aosp_message(commit_line)
+
+ print(ERROR_MESSAGE)
+ sys.exit(0)
+
+def _is_in_aosp():
+ branch_info = subprocess.check_output(["git", "branch", "-vv"])
+ return re.search(AOSP_BRANCH_REGEX, branch_info) is not None
+
+def _check_aosp_message(aosp_line):
+ if re.search(AOSP_COMMIT_LINK_REGEX, aosp_line):
+ sys.exit(0)
+
+ if re.search(AOSP_INFEASIBLE_REGEX, aosp_line, re.IGNORECASE):
+ sys.exit(0)
+
+ print(ERROR_MESSAGE)
+ sys.exit(0)
+
+if __name__ == '__main__':
+ main()
diff --git a/src/com/android/server/telecom/Analytics.java b/src/com/android/server/telecom/Analytics.java
index 2997454..9c227a6 100644
--- a/src/com/android/server/telecom/Analytics.java
+++ b/src/com/android/server/telecom/Analytics.java
@@ -24,6 +24,7 @@
import android.telecom.Logging.EventManager;
import android.telecom.ParcelableCallAnalytics;
import android.telecom.TelecomAnalytics;
+import android.telecom.TelecomManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.util.Base64;
@@ -36,7 +37,6 @@
import java.io.PrintWriter;
import java.time.Instant;
import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -45,8 +45,6 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
-import java.util.PriorityQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.stream.Collectors;
@@ -201,7 +199,8 @@
public void addVideoEvent(int eventId, int videoState) {
}
- public void addInCallService(String serviceName, int type) {
+ public void addInCallService(String serviceName, int type, long boundDuration,
+ boolean isNullBinding) {
}
public void addCallProperties(int properties) {
@@ -370,10 +369,13 @@
}
@Override
- public void addInCallService(String serviceName, int type) {
+ public void addInCallService(String serviceName, int type, long boundDuration,
+ boolean isNullBinding) {
inCallServiceInfos.add(new TelecomLogClass.InCallServiceInfo()
.setInCallServiceName(serviceName)
- .setInCallServiceType(type));
+ .setInCallServiceType(type)
+ .setBoundDurationMillis(boundDuration)
+ .setIsNullBinding(isNullBinding));
}
@Override
@@ -533,6 +535,10 @@
s.append(service.getInCallServiceName());
s.append(" type: ");
s.append(service.getInCallServiceType());
+ s.append(" is crashed: ");
+ s.append(service.getIsNullBinding());
+ s.append(" service last time in ms: ");
+ s.append(service.getBoundDurationMillis());
s.append("\n");
}
s.append("]");
@@ -568,11 +574,11 @@
// Constants for call source
public static final int CALL_SOURCE_UNSPECIFIED =
- ParcelableCallAnalytics.CALL_SOURCE_UNSPECIFIED;
+ TelecomManager.CALL_SOURCE_UNSPECIFIED;
public static final int CALL_SOURCE_EMERGENCY_DIALPAD =
- ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_DIALPAD;
+ TelecomManager.CALL_SOURCE_EMERGENCY_DIALPAD;
public static final int CALL_SOURCE_EMERGENCY_SHORTCUT =
- ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_SHORTCUT;
+ TelecomManager.CALL_SOURCE_EMERGENCY_SHORTCUT;
// Constants for video events
public static final int SEND_LOCAL_SESSION_MODIFY_REQUEST =
diff --git a/src/com/android/server/telecom/AppLabelProxy.java b/src/com/android/server/telecom/AppLabelProxy.java
new file mode 100644
index 0000000..7c00f28
--- /dev/null
+++ b/src/com/android/server/telecom/AppLabelProxy.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.telecom.Log;
+
+/**
+ * Abstracts away dependency on the {@link PackageManager} required to fetch the label for an
+ * app.
+ */
+public interface AppLabelProxy {
+ String LOG_TAG = AppLabelProxy.class.getSimpleName();
+
+ class Util {
+ /**
+ * Default impl of getAppLabel.
+ * @param pm PackageManager instance
+ * @param packageName package name to look up.
+ */
+ public static CharSequence getAppLabel(PackageManager pm, String packageName) {
+ try {
+ ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+ CharSequence result = pm.getApplicationLabel(info);
+ Log.i(LOG_TAG, "package %s: name is %s", packageName, result);
+ return result;
+ } catch (PackageManager.NameNotFoundException nnfe) {
+ Log.w(LOG_TAG, "Could not determine app label. Package name is %s", packageName);
+ }
+
+ return null;
+ }
+ }
+
+ CharSequence getAppLabel(String packageName);
+}
diff --git a/src/com/android/server/telecom/AsyncRingtonePlayer.java b/src/com/android/server/telecom/AsyncRingtonePlayer.java
index 93041c8..bf2472f 100644
--- a/src/com/android/server/telecom/AsyncRingtonePlayer.java
+++ b/src/com/android/server/telecom/AsyncRingtonePlayer.java
@@ -43,10 +43,6 @@
// Message codes used with the ringtone thread.
private static final int EVENT_PLAY = 1;
private static final int EVENT_STOP = 2;
- private static final int EVENT_REPEAT = 3;
-
- // The interval in which to restart the ringer.
- private static final int RESTART_RINGER_MILLIS = 3000;
/** Handler running on the ringtone thread. */
private Handler mHandler;
@@ -60,26 +56,10 @@
*/
private CompletableFuture<Boolean> mHapticsFuture = null;
- /**
- * Determines if the {@link AsyncRingtonePlayer} should pause between repeats of the ringtone.
- * When {@code true}, the system will check if the ringtone has stopped every
- * {@link #RESTART_RINGER_MILLIS} and restart the ringtone if it has stopped. This does not
- * guarantee that there is {@link #RESTART_RINGER_MILLIS} between each repeat of the ringtone,
- * rather it ensures that for short ringtones, or ringtones which are not a multiple of
- * {@link #RESTART_RINGER_MILLIS} in duration that there will be some pause between repetitions.
- * When {@code false}, the ringtone will be looped continually with no attempt to pause between
- * repeats.
- */
- private boolean mShouldPauseBetweenRepeat = true;
-
public AsyncRingtonePlayer() {
// Empty
}
- public AsyncRingtonePlayer(boolean shouldPauseBetweenRepeat) {
- mShouldPauseBetweenRepeat = shouldPauseBetweenRepeat;
- }
-
/**
* Plays the appropriate ringtone for the specified call.
* If {@link VolumeShaper.Configuration} is specified, it is applied to the ringtone to change
@@ -156,9 +136,6 @@
case EVENT_PLAY:
handlePlay((SomeArgs) msg.obj);
break;
- case EVENT_REPEAT:
- handleRepeat();
- break;
case EVENT_STOP:
handleStop();
break;
@@ -242,43 +219,18 @@
}
}
- if (mShouldPauseBetweenRepeat) {
- // We're trying to pause between repeats, so the ringtone will not intentionally loop.
- // Instead, we'll use a handler message to perform repeats.
- handleRepeat();
- } else {
- mRingtone.setLooping(true);
- if (mRingtone.isPlaying()) {
- Log.d(this, "Ringtone already playing.");
- return;
- }
- mRingtone.play();
- Log.i(this, "Play ringtone, looping.");
+ mRingtone.setLooping(true);
+ if (mRingtone.isPlaying()) {
+ Log.d(this, "Ringtone already playing.");
+ return;
}
+ mRingtone.play();
+ Log.i(this, "Play ringtone, looping.");
} finally {
Log.cancelSubsession(session);
}
}
- private void handleRepeat() {
- if (mRingtone == null) {
- return;
- }
- if (mRingtone.isPlaying()) {
- Log.d(this, "Ringtone already playing.");
- } else {
- mRingtone.play();
- Log.i(this, "Repeat ringtone.");
- }
-
- // Repost event to restart ringer in {@link RESTART_RINGER_MILLIS}.
- synchronized(this) {
- if (!mHandler.hasMessages(EVENT_REPEAT)) {
- mHandler.sendEmptyMessageDelayed(EVENT_REPEAT, RESTART_RINGER_MILLIS);
- }
- }
- }
-
/**
* Stops the playback of the ringtone. Executes on the ringtone-thread.
*/
@@ -293,10 +245,6 @@
}
synchronized(this) {
- // At the time that STOP is handled, there should be no need for repeat messages in the
- // queue.
- mHandler.removeMessages(EVENT_REPEAT);
-
if (mHandler.hasMessages(EVENT_PLAY)) {
Log.v(this, "Keeping alive ringtone thread for subsequent play request.");
} else {
@@ -307,4 +255,8 @@
}
}
}
+
+ public boolean isPlaying() {
+ return mRingtone != null;
+ }
}
diff --git a/src/com/android/server/telecom/BluetoothAdapterProxy.java b/src/com/android/server/telecom/BluetoothAdapterProxy.java
index 72962b8..ee9cde3 100644
--- a/src/com/android/server/telecom/BluetoothAdapterProxy.java
+++ b/src/com/android/server/telecom/BluetoothAdapterProxy.java
@@ -17,6 +17,7 @@
package com.android.server.telecom;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
@@ -37,4 +38,15 @@
}
return mBluetoothAdapter.getProfileProxy(context, listener, profile);
}
+
+ public boolean setActiveDevice(BluetoothDevice device, int profiles) {
+ if (mBluetoothAdapter == null) {
+ return false;
+ }
+ if (device != null) {
+ return mBluetoothAdapter.setActiveDevice(device, profiles);
+ } else {
+ return mBluetoothAdapter.removeActiveDevice(profiles);
+ }
+ }
}
diff --git a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
index 33dddbd..f2ea950 100644
--- a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
+++ b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
@@ -78,12 +78,15 @@
// Add all held calls to a conference
private static final int CHLD_TYPE_ADDHELDTOCONF = 3;
+ // Indicates that no call is ringing
+ private static final int DEFAULT_RINGING_ADDRESS_TYPE = 128;
+
private int mNumActiveCalls = 0;
private int mNumHeldCalls = 0;
private int mNumChildrenOfActiveCall = 0;
private int mBluetoothCallState = CALL_STATE_IDLE;
- private String mRingingAddress = null;
- private int mRingingAddressType = 0;
+ private String mRingingAddress = "";
+ private int mRingingAddressType = DEFAULT_RINGING_ADDRESS_TYPE;
private Call mOldHeldCall = null;
private boolean mIsDisconnectedTonePlaying = false;
@@ -101,7 +104,7 @@
long token = Binder.clearCallingIdentity();
try {
Log.i(TAG, "BT - answering call");
- Call call = mCallsManager.getRingingCall();
+ Call call = mCallsManager.getRingingOrSimulatedRingingCall();
if (call != null) {
mCallsManager.answerCall(call, VideoProfile.STATE_AUDIO_ONLY);
return true;
@@ -173,7 +176,7 @@
return account.getLabel().toString();
} else {
// Finally, just get the network name from telephony.
- return TelephonyManager.from(mContext)
+ return mContext.getSystemService(TelephonyManager.class)
.getNetworkOperatorName();
}
} finally {
@@ -200,7 +203,8 @@
}
}
if (TextUtils.isEmpty(address)) {
- address = TelephonyManager.from(mContext).getLine1Number();
+ address = mContext.getSystemService(TelephonyManager.class)
+ .getLine1Number();
if (address == null) address = "";
}
return address;
@@ -493,7 +497,7 @@
private boolean processChld(int chld) {
Call activeCall = mCallsManager.getActiveCall();
- Call ringingCall = mCallsManager.getRingingCall();
+ Call ringingCall = mCallsManager.getRingingOrSimulatedRingingCall();
Call heldCall = mCallsManager.getHeldCall();
// TODO: Keeping as Log.i for now. Move to Log.d after L release if BT proves stable.
@@ -699,13 +703,13 @@
*/
private void updateHeadsetWithCallState(boolean force) {
Call activeCall = mCallsManager.getActiveCall();
- Call ringingCall = mCallsManager.getRingingCall();
+ Call ringingCall = mCallsManager.getRingingOrSimulatedRingingCall();
Call heldCall = mCallsManager.getHeldCall();
int bluetoothCallState = getBluetoothCallStateForUpdate();
String ringingAddress = null;
- int ringingAddressType = 128;
+ int ringingAddressType = DEFAULT_RINGING_ADDRESS_TYPE;
String ringingName = null;
if (ringingCall != null && ringingCall.getHandle() != null
&& !ringingCall.isSilentRingingRequested()) {
@@ -832,7 +836,7 @@
}
private int getBluetoothCallStateForUpdate() {
- Call ringingCall = mCallsManager.getRingingCall();
+ Call ringingCall = mCallsManager.getRingingOrSimulatedRingingCall();
Call dialingCall = mCallsManager.getOutgoingCall();
boolean hasOnlyDisconnectedCalls = mCallsManager.hasOnlyDisconnectedCalls();
@@ -862,6 +866,7 @@
case CallState.NEW:
case CallState.ABORTED:
case CallState.DISCONNECTED:
+ case CallState.AUDIO_PROCESSING:
return CALL_STATE_IDLE;
case CallState.ACTIVE:
@@ -885,6 +890,7 @@
case CallState.RINGING:
case CallState.ANSWERED:
+ case CallState.SIMULATED_RINGING:
if (call.isSilentRingingRequested()) {
return CALL_STATE_IDLE;
} else if (isForeground) {
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
old mode 100644
new mode 100755
index ecc6359..df6322c
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -16,6 +16,8 @@
package com.android.server.telecom;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -30,15 +32,18 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
+import android.os.UserHandle;
import android.provider.ContactsContract.Contacts;
import android.telecom.CallAudioState;
+import android.telecom.CallerInfo;
import android.telecom.Conference;
+import android.telecom.Connection;
import android.telecom.ConnectionService;
import android.telecom.DisconnectCause;
-import android.telecom.Connection;
import android.telecom.GatewayInfo;
import android.telecom.Log;
import android.telecom.Logging.EventManager;
+import android.telecom.ParcelableConference;
import android.telecom.ParcelableConnection;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -47,26 +52,26 @@
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
import android.text.TextUtils;
-import android.util.StatsLog;
-import android.os.UserHandle;
import android.widget.Toast;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telecom.IVideoProvider;
-import com.android.internal.telephony.CallerInfo;
-import com.android.internal.telephony.SmsApplication;
import com.android.internal.util.Preconditions;
+import com.android.server.telecom.ui.ToastFactory;
import java.io.IOException;
-import java.lang.String;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -126,15 +131,18 @@
void onExtrasRemoved(Call c, int source, List<String> keys);
void onHandleChanged(Call call);
void onCallerDisplayNameChanged(Call call);
+ void onCallDirectionChanged(Call call);
void onVideoStateChanged(Call call, int previousVideoState, int newVideoState);
void onTargetPhoneAccountChanged(Call call);
void onConnectionManagerPhoneAccountChanged(Call call);
void onPhoneAccountChanged(Call call);
void onConferenceableCallsChanged(Call call);
void onConferenceStateChanged(Call call, boolean isConference);
+ void onCdmaConferenceSwap(Call call);
boolean onCanceledViaNewOutgoingCallBroadcast(Call call, long disconnectionTimeout);
void onHoldToneRequested(Call call);
void onCallHoldFailed(Call call);
+ void onCallSwitchFailed(Call call);
void onConnectionEvent(Call call, String event, Bundle extras);
void onExternalCallChanged(Call call, boolean isExternalCall);
void onRttInitiationFailure(Call call, int reason);
@@ -191,6 +199,8 @@
@Override
public void onCallerDisplayNameChanged(Call call) {}
@Override
+ public void onCallDirectionChanged(Call call) {}
+ @Override
public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {}
@Override
public void onTargetPhoneAccountChanged(Call call) {}
@@ -203,6 +213,8 @@
@Override
public void onConferenceStateChanged(Call call, boolean isConference) {}
@Override
+ public void onCdmaConferenceSwap(Call call) {}
+ @Override
public boolean onCanceledViaNewOutgoingCallBroadcast(Call call, long disconnectionTimeout) {
return false;
}
@@ -211,6 +223,8 @@
@Override
public void onCallHoldFailed(Call call) {}
@Override
+ public void onCallSwitchFailed(Call call) {}
+ @Override
public void onConnectionEvent(Call call, String event, Bundle extras) {}
@Override
public void onExternalCallChanged(Call call, boolean isExternalCall) {}
@@ -248,7 +262,7 @@
/**
* One of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING, or CALL_DIRECTION_UNKNOWN
*/
- private final int mCallDirection;
+ private int mCallDirection;
/**
* The post-dial digits that were dialed after the network portion of the number
@@ -304,6 +318,8 @@
private PhoneAccountHandle mTargetPhoneAccountHandle;
+ private PhoneAccountHandle mRemotePhoneAccountHandle;
+
private UserHandle mInitiatingUser;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -316,11 +332,18 @@
/** The handle with which to establish this call. */
private Uri mHandle;
+ /** The participants with which to establish adhoc conference call */
+ private List<Uri> mParticipants;
/**
* The presentation requirements for the handle. See {@link TelecomManager} for valid values.
*/
private int mHandlePresentation;
+ /**
+ * The verification status for an incoming call's number.
+ */
+ private @Connection.VerificationStatus int mCallerNumberVerificationStatus;
+
/** The caller display name (CNAP) set by the connection service. */
private String mCallerDisplayName;
@@ -336,6 +359,10 @@
private boolean mIsEmergencyCall;
+ // The Call is considered an emergency call for testing, but will not actually connect to
+ // emergency services.
+ private boolean mIsTestEmergencyCall;
+
private boolean mSpeakerphoneOn;
private boolean mIsDisconnectingChildCall = false;
@@ -357,6 +384,13 @@
*/
private DisconnectCause mDisconnectCause = new DisconnectCause(DisconnectCause.UNKNOWN);
+ /**
+ * Override the disconnect cause set by the connection service. Used for audio processing and
+ * simulated ringing calls as well as the condition when an emergency call is ended due to
+ * an emergency call being placed.
+ */
+ private DisconnectCause mOverrideDisconnectCause = new DisconnectCause(DisconnectCause.UNKNOWN);
+
private Bundle mIntentExtras = new Bundle();
/**
@@ -423,6 +457,7 @@
private final Context mContext;
private final CallsManager mCallsManager;
private final ClockProxy mClockProxy;
+ private final ToastFactory mToastFactory;
private final TelecomSystem.SyncRoot mLock;
private final String mId;
private String mConnectionId;
@@ -431,6 +466,8 @@
private boolean mWasConferencePreviouslyMerged = false;
private boolean mWasHighDefAudio = false;
+ private boolean mWasWifi = false;
+ private boolean mWasVolte = false;
// For conferences which support merge/swap at their level, we retain a notion of an active
// call. This is used for BluetoothPhoneService. In order to support hold/merge, it must have
@@ -475,6 +512,15 @@
*/
private boolean mIsVideoCallingSupportedByPhoneAccount = false;
+ /**
+ * Indicates whether or not this call can be pulled if it is an external call. If true, respect
+ * the Connection Capability set by the ConnectionService. If false, override the capability
+ * set and always remove the ability to pull this external call.
+ *
+ * See {@link #setIsPullExternalCallSupported(boolean)}
+ */
+ private boolean mIsPullExternalCallSupported = true;
+
private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
/**
@@ -530,6 +576,12 @@
private Call mHandoverSourceCall = null;
/**
+ * The user-visible app name of the app that requested for this call to be put into the
+ * AUDIO_PROCESSING state. Used to display a notification to the user.
+ */
+ private CharSequence mAudioProcessingRequestingApp = null;
+
+ /**
* Indicates the current state of this call if it is in the process of a handover.
*/
private int mHandoverState = HandoverState.HANDOVER_NONE;
@@ -541,6 +593,24 @@
private boolean mIsUsingCallFiltering = false;
/**
+ * Indicates whether or not this call has been active before. This is helpful in detecting
+ * situations where we have moved into {@link CallState#SIMULATED_RINGING} or
+ * {@link CallState#AUDIO_PROCESSING} again after being active. If a call has moved into one
+ * of these states again after being active and the user dials an emergency call, we want to
+ * log these calls normally instead of considering them MISSED. If the emergency call was
+ * dialed during initial screening however, we want to treat those calls as MISSED (because the
+ * user never got the chance to explicitly reject).
+ */
+ private boolean mHasGoneActiveBefore = false;
+
+ /**
+ * Indicates the package name of the {@link android.telecom.CallScreeningService} which should
+ * be sent the {@link android.telecom.TelecomManager#ACTION_POST_CALL} intent upon disconnection
+ * of a call.
+ */
+ private String mPostCallPackageName;
+
+ /**
* Persists the specified parameters and initializes the new instance.
* @param context The context.
* @param repository The connection service repository.
@@ -570,16 +640,45 @@
int callDirection,
boolean shouldAttachToExistingConnection,
boolean isConference,
- ClockProxy clockProxy) {
+ ClockProxy clockProxy,
+ ToastFactory toastFactory) {
+ this(callId, context, callsManager, lock, repository, phoneNumberUtilsAdapter,
+ handle, null, gatewayInfo, connectionManagerPhoneAccountHandle,
+ targetPhoneAccountHandle, callDirection, shouldAttachToExistingConnection,
+ isConference, clockProxy, toastFactory);
+
+ }
+
+ public Call(
+ String callId,
+ Context context,
+ CallsManager callsManager,
+ TelecomSystem.SyncRoot lock,
+ ConnectionServiceRepository repository,
+ PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
+ Uri handle,
+ List<Uri> participants,
+ GatewayInfo gatewayInfo,
+ PhoneAccountHandle connectionManagerPhoneAccountHandle,
+ PhoneAccountHandle targetPhoneAccountHandle,
+ int callDirection,
+ boolean shouldAttachToExistingConnection,
+ boolean isConference,
+ ClockProxy clockProxy,
+ ToastFactory toastFactory) {
+
mId = callId;
mConnectionId = callId;
- mState = isConference ? CallState.ACTIVE : CallState.NEW;
+ mState = (isConference && callDirection != CALL_DIRECTION_INCOMING &&
+ callDirection != CALL_DIRECTION_OUTGOING) ?
+ CallState.ACTIVE : CallState.NEW;
mContext = context;
mCallsManager = callsManager;
mLock = lock;
mRepository = repository;
mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
setHandle(handle);
+ mParticipants = participants;
mPostDialDigits = handle != null
? PhoneNumberUtils.extractPostDialPortion(handle.getSchemeSpecificPart()) : "";
mGatewayInfo = gatewayInfo;
@@ -591,6 +690,7 @@
|| callDirection == CALL_DIRECTION_INCOMING;
maybeLoadCannedSmsResponses();
mClockProxy = clockProxy;
+ mToastFactory = toastFactory;
mCreationTimeMillis = mClockProxy.currentTimeMillis();
}
@@ -601,14 +701,14 @@
* @param handle The handle to dial.
* @param gatewayInfo Gateway information to use for the call.
* @param connectionManagerPhoneAccountHandle Account to use for the service managing the call.
-* This account must be one that was registered with the
-* {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
+ * This account must be one that was registered with the
+ * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
* @param targetPhoneAccountHandle Account information to use for the call. This account must be
-* one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
+ * one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
* @param callDirection one of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING,
-* or CALL_DIRECTION_UNKNOWN
+ * or CALL_DIRECTION_UNKNOWN
* @param shouldAttachToExistingConnection Set to true to attach the call to an existing
-* connection, regardless of whether it's incoming or outgoing.
+ * connection, regardless of whether it's incoming or outgoing.
* @param connectTimeMillis The connection time of the call.
* @param clockProxy
*/
@@ -628,11 +728,12 @@
boolean isConference,
long connectTimeMillis,
long connectElapsedTimeMillis,
- ClockProxy clockProxy) {
+ ClockProxy clockProxy,
+ ToastFactory toastFactory) {
this(callId, context, callsManager, lock, repository,
phoneNumberUtilsAdapter, handle, gatewayInfo,
connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, callDirection,
- shouldAttachToExistingConnection, isConference, clockProxy);
+ shouldAttachToExistingConnection, isConference, clockProxy, toastFactory);
mConnectTimeMillis = connectTimeMillis;
mConnectElapsedTimeMillis = connectElapsedTimeMillis;
@@ -717,21 +818,18 @@
/** {@inheritDoc} */
@Override
public String toString() {
- String component = null;
- if (mConnectionService != null && mConnectionService.getComponentName() != null) {
- component = mConnectionService.getComponentName().flattenToShortString();
- }
-
- return String.format(Locale.US, "[%s, %s, %s, %s, %s, childs(%d), has_parent(%b), %s, %s]",
+ return String.format(Locale.US, "[Call id=%s, state=%s, tpac=%s, cmgr=%s, handle=%s, "
+ + "vidst=%s, childs(%d), has_parent(%b), cap=%s, prop=%s]",
mId,
- CallState.toString(mState),
- component,
+ CallState.toString(getParcelableCallState()),
+ getTargetPhoneAccount(),
+ getConnectionManagerPhoneAccount(),
Log.piiHandle(mHandle),
getVideoStateDescription(getVideoState()),
getChildCalls().size(),
getParentCall() != null,
- Connection.capabilitiesToString(getConnectionCapabilities()),
- Connection.propertiesToString(getConnectionProperties()));
+ Connection.capabilitiesToStringShort(getConnectionCapabilities()),
+ Connection.propertiesToStringShort(getConnectionProperties()));
}
@Override
@@ -749,19 +847,63 @@
s.append(SimpleDateFormat.getDateTimeInstance().format(new Date(getCreationTimeMillis())));
s.append("]");
s.append(isIncoming() ? "(MT - incoming)" : "(MO - outgoing)");
- s.append("\n\tVia PhoneAccount: ");
+ s.append("\n\t");
+
PhoneAccountHandle targetPhoneAccountHandle = getTargetPhoneAccount();
+ PhoneAccountHandle remotePhoneAccountHandle = getRemotePhoneAccountHandle();
+ PhoneAccountHandle connectionMgrAccountHandle = getConnectionManagerPhoneAccount();
+ PhoneAccountHandle delegatePhoneAccountHandle = getDelegatePhoneAccountHandle();
+ boolean isTargetSameAsRemote = targetPhoneAccountHandle != null
+ && targetPhoneAccountHandle.equals(remotePhoneAccountHandle);
+ if (delegatePhoneAccountHandle.equals(targetPhoneAccountHandle)) {
+ s.append(">>>");
+ }
+ s.append("Target");
+ s.append(" PhoneAccount: ");
if (targetPhoneAccountHandle != null) {
s.append(targetPhoneAccountHandle);
s.append(" (");
s.append(getTargetPhoneAccountLabel());
s.append(")");
+ if (isTargetSameAsRemote) {
+ s.append("(remote)");
+ }
} else {
s.append("not set");
}
+ if (!isTargetSameAsRemote && remotePhoneAccountHandle != null) {
+ // This is a RARE case and will likely not be seen in practice but it is possible.
+ if (delegatePhoneAccountHandle.equals(remotePhoneAccountHandle)) {
+ s.append("\n\t>>>Remote PhoneAccount: ");
+ } else {
+ s.append("\n\tRemote PhoneAccount: ");
+ }
+ s.append(remotePhoneAccountHandle);
+ }
+ if (connectionMgrAccountHandle != null) {
+ if (delegatePhoneAccountHandle.equals(connectionMgrAccountHandle)) {
+ s.append("\n\t>>>Conn mgr: ");
+ } else {
+ s.append("\n\tConn mgr: ");
+ }
+ s.append(connectionMgrAccountHandle);
+ }
s.append("\n\tTo address: ");
s.append(Log.piiHandle(getHandle()));
+ if (isIncoming()) {
+ switch (mCallerNumberVerificationStatus) {
+ case Connection.VERIFICATION_STATUS_FAILED:
+ s.append(" Verstat: fail");
+ break;
+ case Connection.VERIFICATION_STATUS_NOT_VERIFIED:
+ s.append(" Verstat: not");
+ break;
+ case Connection.VERIFICATION_STATUS_PASSED:
+ s.append(" Verstat: pass");
+ break;
+ }
+ }
s.append(" Presentation: ");
switch (getHandlePresentation()) {
case TelecomManager.PRESENTATION_ALLOWED:
@@ -822,6 +964,20 @@
}
/**
+ * Similar to {@link #getState()}, except will return {@link CallState#DISCONNECTING} if the
+ * call is locally disconnecting. This is the call state which is reported to the
+ * {@link android.telecom.InCallService}s when a call is parcelled.
+ * @return The parcelable call state.
+ */
+ public int getParcelableCallState() {
+ if (isLocallyDisconnecting() &&
+ (mState != android.telecom.Call.STATE_DISCONNECTED)) {
+ return CallState.DISCONNECTING;
+ }
+ return mState;
+ }
+
+ /**
* Determines if this {@link Call} can receive call focus via the
* {@link ConnectionServiceFocusManager}.
* Only top-level calls and non-external calls are eligible.
@@ -924,6 +1080,7 @@
// We're clearly not disconnected, so reset the disconnected time.
mDisconnectTimeMillis = 0;
mDisconnectElapsedTimeMillis = 0;
+ mHasGoneActiveBefore = true;
} else if (mState == CallState.DISCONNECTED) {
mDisconnectTimeMillis = mClockProxy.currentTimeMillis();
mDisconnectElapsedTimeMillis = mClockProxy.elapsedRealtime();
@@ -979,8 +1136,8 @@
}
int statsdDisconnectCause = (newState == CallState.DISCONNECTED) ?
getDisconnectCause().getCode() : DisconnectCause.UNKNOWN;
- StatsLog.write(StatsLog.CALL_STATE_CHANGED, newState, statsdDisconnectCause,
- isSelfManaged(), isExternalCall());
+ TelecomStatsLog.write(TelecomStatsLog.CALL_STATE_CHANGED, newState,
+ statsdDisconnectCause, isSelfManaged(), isExternalCall());
}
return true;
}
@@ -1024,6 +1181,16 @@
return mHandle;
}
+ public List<Uri> getParticipants() {
+ return mParticipants;
+ }
+
+ public boolean isAdhocConferenceCall() {
+ return mIsConference &&
+ (mCallDirection == CALL_DIRECTION_OUTGOING ||
+ mCallDirection == CALL_DIRECTION_INCOMING);
+ }
+
public String getPostDialDigits() {
return mPostDialDigits;
}
@@ -1047,6 +1214,14 @@
return mHandlePresentation;
}
+ public void setCallerNumberVerificationStatus(
+ @Connection.VerificationStatus int callerNumberVerificationStatus) {
+ mCallerNumberVerificationStatus = callerNumberVerificationStatus;
+ }
+
+ public @Connection.VerificationStatus int getCallerNumberVerificationStatus() {
+ return mCallerNumberVerificationStatus;
+ }
void setHandle(Uri handle) {
setHandle(handle, TelecomManager.PRESENTATION_ALLOWED);
@@ -1071,11 +1246,20 @@
// Let's not allow resetting of the emergency flag. Once a call becomes an emergency
// call, it will remain so for the rest of it's lifetime.
if (!mIsEmergencyCall) {
- mIsEmergencyCall = mHandle != null &&
- mPhoneNumberUtilsAdapter.isLocalEmergencyNumber(mContext,
- mHandle.getSchemeSpecificPart());
+ try {
+ mIsEmergencyCall = mHandle != null &&
+ getTelephonyManager().isEmergencyNumber(
+ mHandle.getSchemeSpecificPart());
+ } catch (IllegalStateException ise) {
+ Log.e(this, ise, "setHandle: can't determine if number is emergency");
+ mIsEmergencyCall = false;
+ }
mAnalytics.setCallIsEmergency(mIsEmergencyCall);
}
+ if (!mIsTestEmergencyCall) {
+ mIsTestEmergencyCall = mHandle != null &&
+ isTestEmergencyCall(mHandle.getSchemeSpecificPart());
+ }
startCallerInfoLookup();
for (Listener l : mListeners) {
l.onHandleChanged(this);
@@ -1083,6 +1267,19 @@
}
}
+ private boolean isTestEmergencyCall(String number) {
+ try {
+ Map<Integer, List<EmergencyNumber>> eMap =
+ getTelephonyManager().getEmergencyNumberList();
+ return eMap.values().stream().flatMap(Collection::stream)
+ .anyMatch(eNumber ->
+ eNumber.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST) &&
+ number.equals(eNumber.getNumber()));
+ } catch (IllegalStateException ise) {
+ return false;
+ }
+ }
+
public String getCallerDisplayName() {
return mCallerDisplayName;
}
@@ -1103,11 +1300,11 @@
}
public String getName() {
- return mCallerInfo == null ? null : mCallerInfo.name;
+ return mCallerInfo == null ? null : mCallerInfo.getName();
}
public String getPhoneNumber() {
- return mCallerInfo == null ? null : mCallerInfo.phoneNumber;
+ return mCallerInfo == null ? null : mCallerInfo.getPhoneNumber();
}
public Bitmap getPhotoIcon() {
@@ -1119,16 +1316,33 @@
}
/**
- * @param disconnectCause The reason for the disconnection, represented by
- * {@link android.telecom.DisconnectCause}.
+ * @param cause The reason for the disconnection, represented by
+ * {@link android.telecom.DisconnectCause}.
*/
- public void setDisconnectCause(DisconnectCause disconnectCause) {
+ public void setDisconnectCause(DisconnectCause cause) {
// TODO: Consider combining this method with a setDisconnected() method that is totally
// separate from setState.
- mAnalytics.setCallDisconnectCause(disconnectCause);
- mDisconnectCause = disconnectCause;
+
+ if (mOverrideDisconnectCause.getCode() != DisconnectCause.UNKNOWN) {
+ cause = new DisconnectCause(mOverrideDisconnectCause.getCode(),
+ TextUtils.isEmpty(mOverrideDisconnectCause.getLabel()) ?
+ cause.getLabel() : mOverrideDisconnectCause.getLabel(),
+ (mOverrideDisconnectCause.getDescription() == null) ?
+ cause.getDescription() :mOverrideDisconnectCause.getDescription(),
+ TextUtils.isEmpty(mOverrideDisconnectCause.getReason()) ?
+ cause.getReason() : mOverrideDisconnectCause.getReason(),
+ (mOverrideDisconnectCause.getTone() == 0) ?
+ cause.getTone() : mOverrideDisconnectCause.getTone());
+ }
+ mAnalytics.setCallDisconnectCause(cause);
+ mDisconnectCause = cause;
}
+ public void setOverrideDisconnectCauseCode(DisconnectCause overrideDisconnectCause) {
+ mOverrideDisconnectCause = overrideDisconnectCause;
+ }
+
+
public DisconnectCause getDisconnectCause() {
return mDisconnectCause;
}
@@ -1143,6 +1357,15 @@
}
/**
+ * @return {@code true} if this an outgoing call to a test emergency number (and NOT to
+ * emergency services). Used for testing purposes to differentiate between a real and fake
+ * emergency call for safety reasons during testing.
+ */
+ public boolean isTestEmergencyCall() {
+ return mIsTestEmergencyCall;
+ }
+
+ /**
* @return {@code true} if the network has identified this call as an emergency call.
*/
public boolean isNetworkIdentifiedEmergencyCall() {
@@ -1185,6 +1408,45 @@
checkIfRttCapable();
}
+ /**
+ * @return the {@link PhoneAccountHandle} of the remote connection service which placing this
+ * call was delegated to, or {@code null} if a remote connection service was not used.
+ */
+ public @Nullable PhoneAccountHandle getRemotePhoneAccountHandle() {
+ return mRemotePhoneAccountHandle;
+ }
+
+ /**
+ * Sets the {@link PhoneAccountHandle} of the remote connection service which placing this
+ * call was delegated to.
+ * @param accountHandle The phone account handle.
+ */
+ public void setRemotePhoneAccountHandle(PhoneAccountHandle accountHandle) {
+ mRemotePhoneAccountHandle = accountHandle;
+ }
+
+ /**
+ * Determines which {@link PhoneAccountHandle} is actually placing a call.
+ * Where {@link #getRemotePhoneAccountHandle()} is non-null, the connection manager is placing
+ * the call via a remote connection service, so the remote connection service's phone account
+ * is the source.
+ * Where {@link #getConnectionManagerPhoneAccount()} is non-null and
+ * {@link #getRemotePhoneAccountHandle()} is null, the connection manager is placing the call
+ * itself (even if the target specifies something else).
+ * Finally, if neither of the above cases apply, the target phone account is the one actually
+ * placing the call.
+ * @return The {@link PhoneAccountHandle} which is actually placing a call.
+ */
+ public @NonNull PhoneAccountHandle getDelegatePhoneAccountHandle() {
+ if (mRemotePhoneAccountHandle != null) {
+ return mRemotePhoneAccountHandle;
+ }
+ if (mConnectionManagerPhoneAccountHandle != null) {
+ return mConnectionManagerPhoneAccountHandle;
+ }
+ return mTargetPhoneAccountHandle;
+ }
+
@VisibleForTesting
public PhoneAccountHandle getTargetPhoneAccount() {
return mTargetPhoneAccountHandle;
@@ -1295,6 +1557,26 @@
}
/**
+ * Determines if pulling this external call is supported. If it is supported, we will allow the
+ * {@link Connection#CAPABILITY_CAN_PULL_CALL} capability to be added to this call's
+ * capabilities. If it is not supported, we will strip this capability before sending this
+ * call's capabilities to the InCallService.
+ * @param isPullExternalCallSupported true, if pulling this external call is supported, false
+ * otherwise.
+ */
+ public void setIsPullExternalCallSupported(boolean isPullExternalCallSupported) {
+ if (!isExternalCall()) return;
+ if (isPullExternalCallSupported == mIsPullExternalCallSupported) return;
+
+ Log.i(this, "setCanPullExternalCall: canPull=%b", isPullExternalCallSupported);
+
+ mIsPullExternalCallSupported = isPullExternalCallSupported;
+
+ // Use mConnectionCapabilities here to get the unstripped capabilities.
+ setConnectionCapabilities(mConnectionCapabilities, true /* force */);
+ }
+
+ /**
* @return {@code true} if the {@link Call} locally supports video.
*/
public boolean isLocallyVideoCapable() {
@@ -1487,7 +1769,7 @@
mCreationTimeMillis = time;
}
- long getConnectTimeMillis() {
+ public long getConnectTimeMillis() {
return mConnectTimeMillis;
}
@@ -1500,7 +1782,7 @@
}
public int getConnectionCapabilities() {
- return mConnectionCapabilities;
+ return stripUnsupportedCapabilities(mConnectionCapabilities);
}
int getConnectionProperties() {
@@ -1515,29 +1797,39 @@
Log.v(this, "setConnectionCapabilities: %s", Connection.capabilitiesToString(
connectionCapabilities));
if (forceUpdate || mConnectionCapabilities != connectionCapabilities) {
- // If the phone account does not support video calling, and the connection capabilities
- // passed in indicate that the call supports video, remove those video capabilities.
- if (!isVideoCallingSupportedByPhoneAccount()
- && doesCallSupportVideo(connectionCapabilities)) {
- Log.w(this, "setConnectionCapabilities: attempt to set connection as video " +
- "capable when not supported by the phone account.");
- connectionCapabilities = removeVideoCapabilities(connectionCapabilities);
- }
int previousCapabilities = mConnectionCapabilities;
mConnectionCapabilities = connectionCapabilities;
for (Listener l : mListeners) {
l.onConnectionCapabilitiesChanged(this);
}
- int xorCaps = previousCapabilities ^ mConnectionCapabilities;
+ int strippedCaps = getConnectionCapabilities();
+ int xorCaps = previousCapabilities ^ strippedCaps;
Log.addEvent(this, LogUtils.Events.CAPABILITY_CHANGE,
"Current: [%s], Removed [%s], Added [%s]",
- Connection.capabilitiesToStringShort(mConnectionCapabilities),
+ Connection.capabilitiesToStringShort(strippedCaps),
Connection.capabilitiesToStringShort(previousCapabilities & xorCaps),
- Connection.capabilitiesToStringShort(mConnectionCapabilities & xorCaps));
+ Connection.capabilitiesToStringShort(strippedCaps & xorCaps));
}
}
+ /**
+ * For some states of Telecom, we need to modify this connection's capabilities:
+ * - A user should not be able to pull an external call during an emergency call, so
+ * CAPABILITY_CAN_PULL_CALL should be removed until the emergency call ends.
+ * @param capabilities The original capabilities.
+ * @return The stripped capabilities.
+ */
+ private int stripUnsupportedCapabilities(int capabilities) {
+ if (!mIsPullExternalCallSupported) {
+ if ((capabilities |= Connection.CAPABILITY_CAN_PULL_CALL) > 0) {
+ capabilities &= ~Connection.CAPABILITY_CAN_PULL_CALL;
+ Log.i(this, "stripCapabilitiesBasedOnState: CAPABILITY_CAN_PULL_CALL removed.");
+ }
+ }
+ return capabilities;
+ }
+
public void setConnectionProperties(int connectionProperties) {
Log.v(this, "setConnectionProperties: %s", Connection.propertiesToString(
connectionProperties));
@@ -1556,21 +1848,27 @@
mConnectionProperties = connectionProperties;
boolean didRttChange =
(changedProperties & Connection.PROPERTY_IS_RTT) == Connection.PROPERTY_IS_RTT;
- if (didRttChange && (mConnectionProperties & Connection.PROPERTY_IS_RTT) ==
- Connection.PROPERTY_IS_RTT) {
- createRttStreams();
- // Call startRtt to pass the RTT pipes down to the connection service.
- // They already turned on the RTT property so no request should be sent.
- mConnectionService.startRtt(this,
- getInCallToCsRttPipeForCs(), getCsToInCallRttPipeForCs());
- mWasEverRtt = true;
- if (isEmergencyCall()) {
- mCallsManager.setAudioRoute(CallAudioState.ROUTE_SPEAKER, null);
- mCallsManager.mute(false);
+ if (didRttChange) {
+ if ((mConnectionProperties & Connection.PROPERTY_IS_RTT) ==
+ Connection.PROPERTY_IS_RTT) {
+ createRttStreams();
+ // Call startRtt to pass the RTT pipes down to the connection service.
+ // They already turned on the RTT property so no request should be sent.
+ mConnectionService.startRtt(this,
+ getInCallToCsRttPipeForCs(), getCsToInCallRttPipeForCs());
+ mWasEverRtt = true;
+ if (isEmergencyCall()) {
+ mCallsManager.mute(false);
+ }
+ } else {
+ closeRttStreams();
+ mInCallToConnectionServiceStreams = null;
+ mConnectionServiceToInCallStreams = null;
}
}
mWasHighDefAudio = (connectionProperties & Connection.PROPERTY_HIGH_DEF_AUDIO) ==
Connection.PROPERTY_HIGH_DEF_AUDIO;
+ mWasWifi = (connectionProperties & Connection.PROPERTY_WIFI) > 0;
for (Listener l : mListeners) {
l.onConnectionPropertiesChanged(this, didRttChange);
}
@@ -1583,6 +1881,12 @@
Log.v(this, "setConnectionProperties: external call changed isExternal = %b",
isExternal);
Log.addEvent(this, LogUtils.Events.IS_EXTERNAL, isExternal);
+ if (isExternal) {
+ // If there is an ongoing emergency call, remove the ability for this call to
+ // be pulled.
+ boolean isInEmergencyCall = mCallsManager.isInEmergencyCall();
+ setIsPullExternalCallSupported(!isInEmergencyCall);
+ }
for (Listener l : mListeners) {
l.onExternalCallChanged(this, isExternal);
}
@@ -1735,6 +2039,39 @@
}
@Override
+ public void handleCreateConferenceSuccess(
+ CallIdMapper idMapper,
+ ParcelableConference conference) {
+ Log.v(this, "handleCreateConferenceSuccessful %s", conference);
+ setTargetPhoneAccount(conference.getPhoneAccount());
+ setHandle(conference.getHandle(), conference.getHandlePresentation());
+
+ setConnectionCapabilities(conference.getConnectionCapabilities());
+ setConnectionProperties(conference.getConnectionProperties());
+ setVideoProvider(conference.getVideoProvider());
+ setVideoState(conference.getVideoState());
+ setRingbackRequested(conference.isRingbackRequested());
+ setStatusHints(conference.getStatusHints());
+ putExtras(SOURCE_CONNECTION_SERVICE, conference.getExtras());
+
+ switch (mCallDirection) {
+ case CALL_DIRECTION_INCOMING:
+ // Listeners (just CallsManager for now) will be responsible for checking whether
+ // the call should be blocked.
+ for (Listener l : mListeners) {
+ l.onSuccessfulIncomingCall(this);
+ }
+ break;
+ case CALL_DIRECTION_OUTGOING:
+ for (Listener l : mListeners) {
+ l.onSuccessfulOutgoingCall(this,
+ getStateFromConnectionState(conference.getState()));
+ }
+ break;
+ }
+ }
+
+ @Override
public void handleCreateConnectionSuccess(
CallIdMapper idMapper,
ParcelableConnection connection) {
@@ -1761,6 +2098,8 @@
switch (mCallDirection) {
case CALL_DIRECTION_INCOMING:
+ setCallerNumberVerificationStatus(connection.getCallerNumberVerificationStatus());
+
// Listeners (just CallsManager for now) will be responsible for checking whether
// the call should be blocked.
for (Listener l : mListeners) {
@@ -1783,6 +2122,26 @@
}
@Override
+ public void handleCreateConferenceFailure(DisconnectCause disconnectCause) {
+ clearConnectionService();
+ setDisconnectCause(disconnectCause);
+ mCallsManager.markCallAsDisconnected(this, disconnectCause);
+
+ switch (mCallDirection) {
+ case CALL_DIRECTION_INCOMING:
+ for (Listener listener : mListeners) {
+ listener.onFailedIncomingCall(this);
+ }
+ break;
+ case CALL_DIRECTION_OUTGOING:
+ for (Listener listener : mListeners) {
+ listener.onFailedOutgoingCall(this, disconnectCause);
+ }
+ break;
+ }
+ }
+
+ @Override
public void handleCreateConnectionFailure(DisconnectCause disconnectCause) {
clearConnectionService();
setDisconnectCause(disconnectCause);
@@ -1895,6 +2254,14 @@
Log.v(this, "Aborting call %s", this);
abort(disconnectionTimeout);
} else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
+ if (mState == CallState.AUDIO_PROCESSING && !hasGoneActiveBefore()) {
+ setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.REJECTED));
+ } else if (mState == CallState.SIMULATED_RINGING) {
+ // This is the case where the dialer calls disconnect() because the call timed out
+ // or an emergency call was dialed while in this state.
+ // Override the disconnect cause to MISSED
+ setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.MISSED));
+ }
if (mConnectionService == null) {
Log.e(this, new Exception(), "disconnect() request on a call without a"
+ " connection service.");
@@ -1973,6 +2340,38 @@
}
/**
+ * Answers the call on the connectionservice side in order to start audio processing.
+ *
+ * This pathway keeps the call in the ANSWERED state until the connection service confirms the
+ * answer, at which point we'll set it to AUDIO_PROCESSING. However, to prevent any other
+ * components from seeing the churn between RINGING -> ANSWERED -> AUDIO_PROCESSING, we'll
+ * refrain from tracking this call in CallsManager until we've stabilized in AUDIO_PROCESSING
+ */
+ public void answerForAudioProcessing() {
+ if (mState != CallState.RINGING) {
+ Log.w(this, "Trying to audio-process a non-ringing call: id=%s", mId);
+ return;
+ }
+
+ if (mConnectionService != null) {
+ mConnectionService.answer(this, VideoProfile.STATE_AUDIO_ONLY);
+ } else {
+ Log.e(this, new NullPointerException(),
+ "answer call (audio processing) failed due to null CS callId=%s", getId());
+ }
+
+ Log.addEvent(this, LogUtils.Events.REQUEST_PICKUP_FOR_AUDIO_PROCESSING);
+ }
+
+ public void setAudioProcessingRequestingApp(CharSequence appName) {
+ mAudioProcessingRequestingApp = appName;
+ }
+
+ public CharSequence getAudioProcessingRequestingApp() {
+ return mAudioProcessingRequestingApp;
+ }
+
+ /**
* Deflects the call if it is ringing.
*
* @param address address to be deflected to.
@@ -2018,9 +2417,19 @@
*/
@VisibleForTesting
public void reject(boolean rejectWithMessage, String textMessage, String reason) {
- // Check to verify that the call is still in the ringing state. A call can change states
- // between the time the user hits 'reject' and Telecomm receives the command.
- if (isRinging("reject")) {
+ if (mState == CallState.SIMULATED_RINGING) {
+ // This handles the case where the user manually rejects a call that's in simulated
+ // ringing. Since the call is already active on the connectionservice side, we want to
+ // hangup, not reject.
+ setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.REJECTED));
+ if (mConnectionService != null) {
+ mConnectionService.disconnect(this);
+ } else {
+ Log.e(this, new NullPointerException(),
+ "reject call failed due to null CS callId=%s", getId());
+ }
+ Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, reason);
+ } else if (isRinging("reject")) {
// Ensure video state history tracks video state at time of rejection.
mVideoStateHistory |= mVideoState;
@@ -2035,6 +2444,77 @@
}
/**
+ * Reject this Telecom call with the user-indicated reason.
+ * @param rejectReason The user-indicated reason fore rejecting the call.
+ */
+ public void reject(@android.telecom.Call.RejectReason int rejectReason) {
+ if (mState == CallState.SIMULATED_RINGING) {
+ // This handles the case where the user manually rejects a call that's in simulated
+ // ringing. Since the call is already active on the connectionservice side, we want to
+ // hangup, not reject.
+ // Since its simulated reason we can't pass along the reject reason.
+ setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.REJECTED));
+ if (mConnectionService != null) {
+ mConnectionService.disconnect(this);
+ } else {
+ Log.e(this, new NullPointerException(),
+ "reject call failed due to null CS callId=%s", getId());
+ }
+ Log.addEvent(this, LogUtils.Events.REQUEST_REJECT);
+ } else if (isRinging("reject")) {
+ // Ensure video state history tracks video state at time of rejection.
+ mVideoStateHistory |= mVideoState;
+
+ if (mConnectionService != null) {
+ mConnectionService.rejectWithReason(this, rejectReason);
+ } else {
+ Log.e(this, new NullPointerException(),
+ "reject call failed due to null CS callId=%s", getId());
+ }
+ Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, rejectReason);
+ }
+ }
+
+ /**
+ * Transfers the call if it is active or held.
+ *
+ * @param number number to be transferred to.
+ * @param isConfirmationRequired whether for blind or assured transfer.
+ */
+ @VisibleForTesting
+ public void transfer(Uri number, boolean isConfirmationRequired) {
+ if (mState == CallState.ACTIVE || mState == CallState.ON_HOLD) {
+ if (mConnectionService != null) {
+ mConnectionService.transfer(this, number, isConfirmationRequired);
+ } else {
+ Log.e(this, new NullPointerException(),
+ "transfer call failed due to null CS callId=%s", getId());
+ }
+ Log.addEvent(this, LogUtils.Events.REQUEST_TRANSFER, Log.pii(number));
+ }
+ }
+
+ /**
+ * Transfers the call when this call is active and the other call is held.
+ * This is for Consultative call transfer.
+ *
+ * @param otherCall The other {@link Call} to which this call will be transferred.
+ */
+ @VisibleForTesting
+ public void transfer(Call otherCall) {
+ if (mState == CallState.ACTIVE &&
+ (otherCall != null && otherCall.getState() == CallState.ON_HOLD)) {
+ if (mConnectionService != null) {
+ mConnectionService.transfer(this, otherCall);
+ } else {
+ Log.e(this, new NullPointerException(),
+ "transfer call failed due to null CS callId=%s", getId());
+ }
+ Log.addEvent(this, LogUtils.Events.REQUEST_CONSULTATIVE_TRANSFER, otherCall);
+ }
+ }
+
+ /**
* Puts the call on hold if it is currently active.
*/
@VisibleForTesting
@@ -2089,7 +2569,8 @@
}
}
- boolean isActive() {
+ @VisibleForTesting
+ public boolean isActive() {
return mState == CallState.ACTIVE;
}
@@ -2122,6 +2603,25 @@
l.onExtrasChanged(this, source, extras);
}
+ // If mExtra shows that the call using Volte, record it with mWasVolte
+ if (mExtras.containsKey(TelecomManager.EXTRA_CALL_NETWORK_TYPE) &&
+ mExtras.get(TelecomManager.EXTRA_CALL_NETWORK_TYPE)
+ .equals(TelephonyManager.NETWORK_TYPE_LTE)) {
+ mWasVolte = true;
+ }
+
+ if (extras.containsKey(Connection.EXTRA_ORIGINAL_CONNECTION_ID)) {
+ setOriginalConnectionId(extras.getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID));
+ }
+
+ // The remote connection service API can track the phone account which was originally
+ // requested to create a connection via the remote connection service API; we store that so
+ // we have some visibility into how a call was actually placed.
+ if (mExtras.containsKey(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE)) {
+ setRemotePhoneAccountHandle(extras.getParcelable(
+ Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE));
+ }
+
// If the change originated from an InCallService, notify the connection service.
if (source == SOURCE_INCALL_SERVICE) {
if (mConnectionService != null) {
@@ -2193,7 +2693,7 @@
if (mCallerInfo == null || !mCallerInfo.contactExists) {
return getHandle();
}
- return Contacts.getLookupUri(mCallerInfo.contactIdOrZero, mCallerInfo.lookupKey);
+ return Contacts.getLookupUri(mCallerInfo.getContactId(), mCallerInfo.lookupKey);
}
Uri getRingtone() {
@@ -2271,6 +2771,18 @@
mConferenceLevelActiveCall = null;
break;
}
+ for (Listener l : mListeners) {
+ l.onCdmaConferenceSwap(this);
+ }
+ }
+ }
+
+ public void addConferenceParticipants(List<Uri> participants) {
+ if (mConnectionService == null) {
+ Log.w(this, "adding conference participants without a connection service.");
+ } else if (can(Connection.CAPABILITY_ADD_PARTICIPANT)) {
+ Log.addEvent(this, LogUtils.Events.ADD_PARTICIPANT);
+ mConnectionService.addConferenceParticipants(this, participants);
}
}
@@ -2291,6 +2803,7 @@
* device, so will issue a request to pull the call to the second device.
* <p>
* Requests to pull a call which is not external, or a call which is not pullable are ignored.
+ * If there is an ongoing emergency call, pull requests are also ignored.
*/
public void pullExternalCall() {
if (mConnectionService == null) {
@@ -2306,6 +2819,15 @@
Log.w(this, "pullExternalCall - call %s is external but cannot be pulled.", mId);
return;
}
+
+ if (mCallsManager.isInEmergencyCall()) {
+ Log.w(this, "pullExternalCall = pullExternalCall - call %s is external but can not be"
+ + " pulled while an emergency call is in progress.", mId);
+ mToastFactory.makeText(mContext, R.string.toast_emergency_can_not_pull_call,
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+
Log.addEvent(this, LogUtils.Events.REQUEST_PULL);
mConnectionService.pullExternalCall(this);
}
@@ -2340,8 +2862,9 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- Toast.makeText(mContext, "WARNING: Event-based handover APIs are deprecated "
- + "and will no longer function in Android Q.",
+ mToastFactory.makeText(mContext,
+ "WARNING: Event-based handover APIs are deprecated and will no"
+ + " longer function in Android Q.",
Toast.LENGTH_LONG).show();
}
});
@@ -2453,7 +2976,7 @@
* ensure the InCall UI is updated with the change in parent.
* @param parentCall The new parent for this call.
*/
- void setChildOf(Call parentCall) {
+ public void setChildOf(Call parentCall) {
if (parentCall != null && !parentCall.getChildCalls().contains(this)) {
parentCall.addChildCall(this);
}
@@ -2475,7 +2998,7 @@
@VisibleForTesting
public boolean can(int capability) {
- return (mConnectionCapabilities & capability) == capability;
+ return (getConnectionCapabilities() & capability) == capability;
}
@VisibleForTesting
@@ -2491,6 +3014,11 @@
mConferenceLevelActiveCall = call;
mChildCalls.add(call);
+ // When adding a child, we will potentially adjust the various times from the calls
+ // based on the children being added. This ensures the parent of the conference has a
+ // connect time reflective of all the children added.
+ maybeAdjustConnectTime(call);
+
Log.addEvent(this, LogUtils.Events.ADD_CHILD, call);
for (Listener l : mListeners) {
@@ -2499,6 +3027,30 @@
}
}
+ /**
+ * Potentially adjust the connect and creation time of this call based on another one.
+ * Ensures that if the other call has an earlier connect time that we adjust the connect time of
+ * this call to match.
+ * <p>
+ * This is important for conference calls; as we add children to the conference we need to
+ * ensure that earlier connect time is reflected on the conference. In the past this
+ * was just done in {@link ParcelableCallUtils} when parceling the calls to the UI, but that
+ * approach would not reflect the right time on the parent as children disconnect.
+ *
+ * @param call the call to potentially use to adjust connect time.
+ */
+ private void maybeAdjustConnectTime(@NonNull Call call) {
+ long childConnectTimeMillis = call.getConnectTimeMillis();
+ long currentConnectTimeMillis = getConnectTimeMillis();
+ // Conference calls typically have a 0 connect time, so we will replace the current connect
+ // time if its zero also.
+ if (childConnectTimeMillis != 0
+ && (currentConnectTimeMillis == 0
+ || childConnectTimeMillis < getConnectTimeMillis())) {
+ setConnectTimeMillis(childConnectTimeMillis);
+ }
+ }
+
private void removeChildCall(Call call) {
if (mChildCalls.remove(call)) {
Log.addEvent(this, LogUtils.Events.REMOVE_CHILD, call);
@@ -2549,8 +3101,8 @@
}
// Is there a valid SMS application on the phone?
- if (SmsApplication.getDefaultRespondViaMessageApplication(mContext,
- true /*updateIfNeeded*/) == null) {
+ if (mContext.getSystemService(TelephonyManager.class)
+ .getAndUpdateDefaultRespondViaMessageApplication() == null) {
return false;
}
@@ -2582,7 +3134,7 @@
* @return True if the call is ringing, else logs the action name.
*/
private boolean isRinging(String actionName) {
- if (mState == CallState.RINGING) {
+ if (mState == CallState.RINGING || mState == CallState.ANSWERED) {
return true;
}
@@ -2626,7 +3178,7 @@
mCallerInfo = callerInfo;
Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
- if (mCallerInfo.contactDisplayPhotoUri == null ||
+ if (mCallerInfo.getContactDisplayPhotoUri() == null ||
mCallerInfo.cachedPhotoIcon != null || mCallerInfo.cachedPhoto != null) {
for (Listener l : mListeners) {
l.onCallerInfoChanged(this);
@@ -3071,6 +3623,10 @@
for (Listener l : mListeners) {
l.onCallHoldFailed(this);
}
+ } else if (Connection.EVENT_CALL_SWITCH_FAILED.equals(event)) {
+ for (Listener l : mListeners) {
+ l.onCallSwitchFailed(this);
+ }
} else {
for (Listener l : mListeners) {
l.onConnectionEvent(this, event, extras);
@@ -3162,6 +3718,10 @@
}
}
+ private TelephonyManager getTelephonyManager() {
+ return mContext.getSystemService(TelephonyManager.class);
+ }
+
/**
* Sets whether this {@link Call} is a conference or not.
* @param isConference
@@ -3177,6 +3737,24 @@
}
/**
+ * Change the call direction. This is useful if it was not previously defined (for example in
+ * single caller emulation mode).
+ * @param callDirection The new direction of this call.
+ */
+ // Make sure the callDirection has been mapped to the Call definition correctly!
+ public void setCallDirection(int callDirection) {
+ if (mCallDirection != callDirection) {
+ Log.addEvent(this, LogUtils.Events.CALL_DIRECTION_CHANGED, "callDirection="
+ + callDirection);
+ mCallDirection = callDirection;
+ for (Listener l : mListeners) {
+ // Update InCallService directly, do not notify CallsManager.
+ l.onCallDirectionChanged(this);
+ }
+ }
+ }
+
+ /**
* Sets the video history based on the state and state transitions of the call. Always add the
* current video state to the video state history during a call transition except for the
* transitions DIALING->ACTIVE and RINGING->ANSWERED. In these cases, clear the history. If a
@@ -3201,11 +3779,41 @@
return mWasHighDefAudio;
}
+ /**
+ * Returns whether or not Wifi call was used.
+ *
+ * @return true if wifi call was used during this call.
+ */
+ boolean wasWifi() {
+ return mWasWifi;
+ }
+
public void setIsUsingCallFiltering(boolean isUsingCallFiltering) {
mIsUsingCallFiltering = isUsingCallFiltering;
}
/**
+ * Returns whether or not Volte call was used.
+ *
+ * @return true if Volte call was used during this call.
+ */
+ public boolean wasVolte() {
+ return mWasVolte;
+ }
+
+ /**
+ * In some cases, we need to know if this call has ever gone active (for example, the case
+ * when the call was put into the {@link CallState#AUDIO_PROCESSING} state after being active)
+ * for call logging purposes.
+ *
+ * @return {@code true} if this call has gone active before (even if it isn't now), false if it
+ * has never gone active.
+ */
+ public boolean hasGoneActiveBefore() {
+ return mHasGoneActiveBefore;
+ }
+
+ /**
* When upgrading a call to video via
* {@link VideoProviderProxy#onSendSessionModifyRequest(VideoProfile, VideoProfile)}, if the
* upgrade is from audio to video, potentially auto-engage the speakerphone.
@@ -3237,4 +3845,23 @@
}
return CALL_DIRECTION_UNDEFINED;
}
+
+ /**
+ * Set the package name of the {@link android.telecom.CallScreeningService} which should be sent
+ * the {@link android.telecom.TelecomManager#ACTION_POST_CALL} upon disconnection of a call.
+ * @param packageName post call screen service package name.
+ */
+ public void setPostCallPackageName(String packageName) {
+ mPostCallPackageName = packageName;
+ }
+
+ /**
+ * Return the package name of the {@link android.telecom.CallScreeningService} which should be
+ * sent the {@link android.telecom.TelecomManager#ACTION_POST_CALL} upon disconnection of a
+ * call.
+ * @return post call screen service package name.
+ */
+ public String getPostCallPackageName() {
+ return mPostCallPackageName;
+ }
}
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 3345fa4..a6509b4 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -26,6 +26,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.telecom.CallAudioModeStateMachine.MessageArgs.Builder;
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
import java.util.Collection;
@@ -44,6 +45,7 @@
private final LinkedHashSet<Call> mActiveDialingOrConnectingCalls;
private final LinkedHashSet<Call> mRingingCalls;
private final LinkedHashSet<Call> mHoldingCalls;
+ private final LinkedHashSet<Call> mAudioProcessingCalls;
private final Set<Call> mCalls;
private final SparseArray<LinkedHashSet<Call>> mCallStateToCalls;
@@ -69,9 +71,10 @@
RingbackPlayer ringbackPlayer,
BluetoothStateReceiver bluetoothStateReceiver,
DtmfLocalTonePlayer dtmfLocalTonePlayer) {
- mActiveDialingOrConnectingCalls = new LinkedHashSet<>();
- mRingingCalls = new LinkedHashSet<>();
- mHoldingCalls = new LinkedHashSet<>();
+ mActiveDialingOrConnectingCalls = new LinkedHashSet<>(1);
+ mRingingCalls = new LinkedHashSet<>(1);
+ mHoldingCalls = new LinkedHashSet<>(1);
+ mAudioProcessingCalls = new LinkedHashSet<>(1);
mCalls = new HashSet<>();
mCallStateToCalls = new SparseArray<LinkedHashSet<Call>>() {{
put(CallState.CONNECTING, mActiveDialingOrConnectingCalls);
@@ -80,6 +83,8 @@
put(CallState.PULLING, mActiveDialingOrConnectingCalls);
put(CallState.RINGING, mRingingCalls);
put(CallState.ON_HOLD, mHoldingCalls);
+ put(CallState.SIMULATED_RINGING, mRingingCalls);
+ put(CallState.AUDIO_PROCESSING, mAudioProcessingCalls);
}};
mCallAudioRouteStateMachine = callAudioRouteStateMachine;
@@ -110,6 +115,7 @@
if (newBinForCall != null) {
newBinForCall.add(call);
}
+ sendCallStatusToBluetoothStateReceiver();
updateForegroundCall();
if (shouldPlayDisconnectTone(oldState, newState)) {
@@ -153,9 +159,7 @@
}
updateForegroundCall();
mCalls.add(call);
- if (mCalls.size() == 1) {
- mBluetoothStateReceiver.setIsInCall(true);
- }
+ sendCallStatusToBluetoothStateReceiver();
onCallEnteringState(call, call.getState());
}
@@ -172,13 +176,17 @@
updateForegroundCall();
mCalls.remove(call);
- if (mCalls.size() == 0) {
- mBluetoothStateReceiver.setIsInCall(false);
- }
+ sendCallStatusToBluetoothStateReceiver();
onCallLeavingState(call, call.getState());
}
+ private void sendCallStatusToBluetoothStateReceiver() {
+ // We're in a call if there are calls in mCalls that are not in mAudioProcessingCalls.
+ boolean isInCall = !mAudioProcessingCalls.containsAll(mCalls);
+ mBluetoothStateReceiver.setIsInCall(isInCall);
+ }
+
/**
* Handles changes to the external state of a call. External calls which become regular calls
* should be tracked, and regular calls which become external should no longer be tracked.
@@ -355,7 +363,7 @@
@VisibleForTesting
public void toggleMute() {
// Don't mute if there are any emergency calls.
- if (mCallsManager.hasEmergencyCall()) {
+ if (mCallsManager.isInEmergencyCall()) {
Log.v(this, "ignoring toggleMute for emergency call");
return;
}
@@ -374,7 +382,7 @@
Log.v(this, "mute, shouldMute: %b", shouldMute);
// Don't mute if there are any emergency calls.
- if (mCallsManager.hasEmergencyCall()) {
+ if (mCallsManager.isInEmergencyCall()) {
shouldMute = false;
Log.v(this, "ignoring mute for emergency call");
}
@@ -415,7 +423,7 @@
CallAudioRouteStateMachine.NO_INCLUDE_BLUETOOTH_IN_BASELINE);
return;
default:
- Log.wtf(this, "Invalid route specified: %d", route);
+ Log.w(this, "InCallService requested an invalid audio route: %d", route);
}
}
@@ -441,6 +449,10 @@
}
}
+ public boolean isRingtonePlaying() {
+ return mRinger.isRinging();
+ }
+
@VisibleForTesting
public boolean startRinging() {
synchronized (mCallsManager.getLock()) {
@@ -478,6 +490,11 @@
CallAudioRouteStateMachine.SWITCH_FOCUS, focusState);
}
+ public void notifyAudioOperationsComplete() {
+ mCallAudioModeStateMachine.sendMessageWithArgs(
+ CallAudioModeStateMachine.AUDIO_OPERATIONS_COMPLETE, makeArgsForModeStateMachine());
+ }
+
@VisibleForTesting
public CallAudioRouteStateMachine getCallAudioRouteStateMachine() {
return mCallAudioRouteStateMachine;
@@ -521,6 +538,13 @@
pw.increaseIndent();
mCallAudioRouteStateMachine.dumpPendingMessages(pw);
pw.decreaseIndent();
+
+ pw.println("BluetoothDeviceManager:");
+ pw.increaseIndent();
+ if (mBluetoothStateReceiver.getBluetoothDeviceManager() != null) {
+ mBluetoothStateReceiver.getBluetoothDeviceManager().dump(pw);
+ }
+ pw.decreaseIndent();
}
@VisibleForTesting
@@ -544,6 +568,7 @@
onCallLeavingActiveDialingOrConnecting();
break;
case CallState.RINGING:
+ case CallState.SIMULATED_RINGING:
case CallState.ANSWERED:
onCallLeavingRinging();
break;
@@ -557,6 +582,9 @@
stopRingbackForCall(call);
onCallLeavingActiveDialingOrConnecting();
break;
+ case CallState.AUDIO_PROCESSING:
+ onCallLeavingAudioProcessing();
+ break;
}
}
@@ -567,6 +595,7 @@
onCallEnteringActiveDialingOrConnecting();
break;
case CallState.RINGING:
+ case CallState.SIMULATED_RINGING:
onCallEnteringRinging();
break;
case CallState.ON_HOLD:
@@ -584,6 +613,25 @@
onCallEnteringActiveDialingOrConnecting();
}
break;
+ case CallState.AUDIO_PROCESSING:
+ onCallEnteringAudioProcessing();
+ break;
+ }
+ }
+
+ private void onCallLeavingAudioProcessing() {
+ if (mAudioProcessingCalls.size() == 0) {
+ mCallAudioModeStateMachine.sendMessageWithArgs(
+ CallAudioModeStateMachine.NO_MORE_AUDIO_PROCESSING_CALLS,
+ makeArgsForModeStateMachine());
+ }
+ }
+
+ private void onCallEnteringAudioProcessing() {
+ if (mAudioProcessingCalls.size() == 1) {
+ mCallAudioModeStateMachine.sendMessageWithArgs(
+ CallAudioModeStateMachine.NEW_AUDIO_PROCESSING_CALL,
+ makeArgsForModeStateMachine());
}
}
@@ -665,13 +713,15 @@
@NonNull
private CallAudioModeStateMachine.MessageArgs makeArgsForModeStateMachine() {
- return new CallAudioModeStateMachine.MessageArgs(
- mActiveDialingOrConnectingCalls.size() > 0,
- mRingingCalls.size() > 0,
- mHoldingCalls.size() > 0,
- mIsTonePlaying,
- mForegroundCall != null && mForegroundCall.getIsVoipAudioMode(),
- Log.createSubsession());
+ return new Builder()
+ .setHasActiveOrDialingCalls(mActiveDialingOrConnectingCalls.size() > 0)
+ .setHasRingingCalls(mRingingCalls.size() > 0)
+ .setHasHoldingCalls(mHoldingCalls.size() > 0)
+ .setHasAudioProcessingCalls(mAudioProcessingCalls.size() > 0)
+ .setIsTonePlaying(mIsTonePlaying)
+ .setForegroundCallIsVoip(
+ mForegroundCall != null && mForegroundCall.getIsVoipAudioMode())
+ .setSession(Log.createSubsession()).build();
}
private HashSet<Call> getBinForCall(Call call) {
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 48d09fc..2aa9d5d 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -17,6 +17,7 @@
package com.android.server.telecom;
import android.media.AudioManager;
+import android.os.Looper;
import android.os.Message;
import android.telecom.Log;
import android.telecom.Logging.Runnable;
@@ -40,53 +41,108 @@
public boolean hasActiveOrDialingCalls;
public boolean hasRingingCalls;
public boolean hasHoldingCalls;
+ public boolean hasAudioProcessingCalls;
public boolean isTonePlaying;
public boolean foregroundCallIsVoip;
public Session session;
- public MessageArgs(boolean hasActiveOrDialingCalls, boolean hasRingingCalls,
- boolean hasHoldingCalls, boolean isTonePlaying, boolean foregroundCallIsVoip,
- Session session) {
+ private MessageArgs(boolean hasActiveOrDialingCalls, boolean hasRingingCalls,
+ boolean hasHoldingCalls, boolean hasAudioProcessingCalls, boolean isTonePlaying,
+ boolean foregroundCallIsVoip, Session session) {
this.hasActiveOrDialingCalls = hasActiveOrDialingCalls;
this.hasRingingCalls = hasRingingCalls;
this.hasHoldingCalls = hasHoldingCalls;
+ this.hasAudioProcessingCalls = hasAudioProcessingCalls;
this.isTonePlaying = isTonePlaying;
this.foregroundCallIsVoip = foregroundCallIsVoip;
this.session = session;
}
- public MessageArgs() {
- this.session = Log.createSubsession();
- }
-
@Override
public String toString() {
return "MessageArgs{" +
"hasActiveCalls=" + hasActiveOrDialingCalls +
", hasRingingCalls=" + hasRingingCalls +
", hasHoldingCalls=" + hasHoldingCalls +
+ ", hasAudioProcessingCalls=" + hasAudioProcessingCalls +
", isTonePlaying=" + isTonePlaying +
", foregroundCallIsVoip=" + foregroundCallIsVoip +
", session=" + session +
'}';
}
+
+ public static class Builder {
+ private boolean mHasActiveOrDialingCalls;
+ private boolean mHasRingingCalls;
+ private boolean mHasHoldingCalls;
+ private boolean mHasAudioProcessingCalls;
+ private boolean mIsTonePlaying;
+ private boolean mForegroundCallIsVoip;
+ private Session mSession;
+
+ public Builder setHasActiveOrDialingCalls(boolean hasActiveOrDialingCalls) {
+ mHasActiveOrDialingCalls = hasActiveOrDialingCalls;
+ return this;
+ }
+
+ public Builder setHasRingingCalls(boolean hasRingingCalls) {
+ mHasRingingCalls = hasRingingCalls;
+ return this;
+ }
+
+ public Builder setHasHoldingCalls(boolean hasHoldingCalls) {
+ mHasHoldingCalls = hasHoldingCalls;
+ return this;
+ }
+
+ public Builder setHasAudioProcessingCalls(boolean hasAudioProcessingCalls) {
+ mHasAudioProcessingCalls = hasAudioProcessingCalls;
+ return this;
+ }
+
+ public Builder setIsTonePlaying(boolean isTonePlaying) {
+ mIsTonePlaying = isTonePlaying;
+ return this;
+ }
+
+ public Builder setForegroundCallIsVoip(boolean foregroundCallIsVoip) {
+ mForegroundCallIsVoip = foregroundCallIsVoip;
+ return this;
+ }
+
+ public Builder setSession(Session session) {
+ mSession = session;
+ return this;
+ }
+
+ public MessageArgs build() {
+ return new MessageArgs(mHasActiveOrDialingCalls, mHasRingingCalls, mHasHoldingCalls,
+ mHasAudioProcessingCalls, mIsTonePlaying, mForegroundCallIsVoip, mSession);
+ }
+ }
}
+ // TODO: remove this and replace when the new audio mode gets pushed to AOSP.
+ public static final int NEW_AUDIO_MODE_FOR_AUDIO_PROCESSING = 4;
+
public static final int INITIALIZE = 1;
// These ENTER_*_FOCUS commands are for testing.
public static final int ENTER_CALL_FOCUS_FOR_TESTING = 2;
public static final int ENTER_COMMS_FOCUS_FOR_TESTING = 3;
public static final int ENTER_RING_FOCUS_FOR_TESTING = 4;
public static final int ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING = 5;
- public static final int ABANDON_FOCUS_FOR_TESTING = 6;
+ public static final int ENTER_AUDIO_PROCESSING_FOCUS_FOR_TESTING = 6;
+ public static final int ABANDON_FOCUS_FOR_TESTING = 7;
public static final int NO_MORE_ACTIVE_OR_DIALING_CALLS = 1001;
public static final int NO_MORE_RINGING_CALLS = 1002;
public static final int NO_MORE_HOLDING_CALLS = 1003;
+ public static final int NO_MORE_AUDIO_PROCESSING_CALLS = 1004;
public static final int NEW_ACTIVE_OR_DIALING_CALL = 2001;
public static final int NEW_RINGING_CALL = 2002;
public static final int NEW_HOLDING_CALL = 2003;
+ public static final int NEW_AUDIO_PROCESSING_CALL = 2004;
public static final int TONE_STARTED_PLAYING = 3001;
public static final int TONE_STOPPED_PLAYING = 3002;
@@ -95,30 +151,40 @@
public static final int RINGER_MODE_CHANGE = 5001;
+ // Used to indicate that Telecom is done doing things to the AudioManager and that it's safe
+ // to release focus for other apps to take over.
+ public static final int AUDIO_OPERATIONS_COMPLETE = 6001;
+
public static final int RUN_RUNNABLE = 9001;
private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
put(ENTER_CALL_FOCUS_FOR_TESTING, "ENTER_CALL_FOCUS_FOR_TESTING");
put(ENTER_COMMS_FOCUS_FOR_TESTING, "ENTER_COMMS_FOCUS_FOR_TESTING");
put(ENTER_RING_FOCUS_FOR_TESTING, "ENTER_RING_FOCUS_FOR_TESTING");
+ put(ENTER_AUDIO_PROCESSING_FOCUS_FOR_TESTING, "ENTER_AUDIO_PROCESSING_FOCUS_FOR_TESTING");
put(ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING, "ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING");
put(ABANDON_FOCUS_FOR_TESTING, "ABANDON_FOCUS_FOR_TESTING");
put(NO_MORE_ACTIVE_OR_DIALING_CALLS, "NO_MORE_ACTIVE_OR_DIALING_CALLS");
put(NO_MORE_RINGING_CALLS, "NO_MORE_RINGING_CALLS");
put(NO_MORE_HOLDING_CALLS, "NO_MORE_HOLDING_CALLS");
+ put(NO_MORE_AUDIO_PROCESSING_CALLS, "NO_MORE_AUDIO_PROCESSING_CALLS");
put(NEW_ACTIVE_OR_DIALING_CALL, "NEW_ACTIVE_OR_DIALING_CALL");
put(NEW_RINGING_CALL, "NEW_RINGING_CALL");
put(NEW_HOLDING_CALL, "NEW_HOLDING_CALL");
+ put(NEW_AUDIO_PROCESSING_CALL, "NEW_AUDIO_PROCESSING_CALL");
put(TONE_STARTED_PLAYING, "TONE_STARTED_PLAYING");
put(TONE_STOPPED_PLAYING, "TONE_STOPPED_PLAYING");
put(FOREGROUND_VOIP_MODE_CHANGE, "FOREGROUND_VOIP_MODE_CHANGE");
put(RINGER_MODE_CHANGE, "RINGER_MODE_CHANGE");
+ put(AUDIO_OPERATIONS_COMPLETE, "AUDIO_OPERATIONS_COMPLETE");
put(RUN_RUNNABLE, "RUN_RUNNABLE");
}};
public static final String TONE_HOLD_STATE_NAME = OtherFocusState.class.getSimpleName();
public static final String UNFOCUSED_STATE_NAME = UnfocusedState.class.getSimpleName();
+ public static final String AUDIO_PROCESSING_STATE_NAME =
+ AudioProcessingFocusState.class.getSimpleName();
public static final String CALL_STATE_NAME = SimCallFocusState.class.getSimpleName();
public static final String RING_STATE_NAME = RingingFocusState.class.getSimpleName();
public static final String COMMS_STATE_NAME = VoipCallFocusState.class.getSimpleName();
@@ -139,6 +205,9 @@
case ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING:
transitionTo(mOtherFocusState);
return HANDLED;
+ case ENTER_AUDIO_PROCESSING_FOCUS_FOR_TESTING:
+ transitionTo(mAudioProcessingFocusState);
+ return HANDLED;
case ABANDON_FOCUS_FOR_TESTING:
transitionTo(mUnfocusedState);
return HANDLED;
@@ -159,12 +228,11 @@
@Override
public void enter() {
if (mIsInitialized) {
- Log.i(LOG_TAG, "Abandoning audio focus: now UNFOCUSED");
- mAudioManager.abandonAudioFocusForCall();
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
-
- mMostRecentMode = AudioManager.MODE_NORMAL;
mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.NO_FOCUS);
+ mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ mMostRecentMode = AudioManager.MODE_NORMAL;
+ // Don't release focus here -- wait until we get a signal that any other audio
+ // operations triggered by this are done before releasing focus.
}
}
@@ -184,6 +252,9 @@
case NO_MORE_HOLDING_CALLS:
// Do nothing.
return HANDLED;
+ case NO_MORE_AUDIO_PROCESSING_CALLS:
+ // Do nothing.
+ return HANDLED;
case NEW_ACTIVE_OR_DIALING_CALL:
transitionTo(args.foregroundCallIsVoip
? mVoipCallFocusState : mSimCallFocusState);
@@ -191,6 +262,9 @@
case NEW_RINGING_CALL:
transitionTo(mRingingFocusState);
return HANDLED;
+ case NEW_AUDIO_PROCESSING_CALL:
+ transitionTo(mAudioProcessingFocusState);
+ return HANDLED;
case NEW_HOLDING_CALL:
// This really shouldn't happen, but transition to the focused state anyway.
Log.w(LOG_TAG, "Call was surprisingly put into hold from an unknown state." +
@@ -202,6 +276,75 @@
Log.w(LOG_TAG, "Tone started playing unexpectedly. Args are: \n"
+ args.toString());
return HANDLED;
+ case AUDIO_OPERATIONS_COMPLETE:
+ Log.i(LOG_TAG, "Abandoning audio focus: now UNFOCUSED");
+ mAudioManager.abandonAudioFocusForCall();
+ return HANDLED;
+ default:
+ // The forced focus switch commands are handled by BaseState.
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ private class AudioProcessingFocusState extends BaseState {
+ @Override
+ public void enter() {
+ if (mIsInitialized) {
+ mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.NO_FOCUS);
+ mAudioManager.setMode(NEW_AUDIO_MODE_FOR_AUDIO_PROCESSING);
+ mMostRecentMode = NEW_AUDIO_MODE_FOR_AUDIO_PROCESSING;
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (super.processMessage(msg) == HANDLED) {
+ return HANDLED;
+ }
+ MessageArgs args = (MessageArgs) msg.obj;
+ switch (msg.what) {
+ case NO_MORE_ACTIVE_OR_DIALING_CALLS:
+ // Do nothing.
+ return HANDLED;
+ case NO_MORE_RINGING_CALLS:
+ // Do nothing.
+ return HANDLED;
+ case NO_MORE_HOLDING_CALLS:
+ // Do nothing.
+ return HANDLED;
+ case NO_MORE_AUDIO_PROCESSING_CALLS:
+ BaseState destState = calculateProperStateFromArgs(args);
+ if (destState == this) {
+ Log.w(LOG_TAG, "Got spurious NO_MORE_AUDIO_PROCESSING_CALLS");
+ }
+ transitionTo(destState);
+ return HANDLED;
+ case NEW_ACTIVE_OR_DIALING_CALL:
+ transitionTo(args.foregroundCallIsVoip
+ ? mVoipCallFocusState : mSimCallFocusState);
+ return HANDLED;
+ case NEW_RINGING_CALL:
+ transitionTo(mRingingFocusState);
+ return HANDLED;
+ case NEW_HOLDING_CALL:
+ // This really shouldn't happen, but recalculate from args and do the transition
+ Log.w(LOG_TAG, "Call was surprisingly put into hold from an unknown state." +
+ " Args are: \n" + args.toString());
+ transitionTo(mOtherFocusState);
+ return HANDLED;
+ case NEW_AUDIO_PROCESSING_CALL:
+ // Can happen as a duplicate message
+ return HANDLED;
+ case TONE_STARTED_PLAYING:
+ // This shouldn't happen either, but perform the action anyway.
+ Log.w(LOG_TAG, "Tone started playing unexpectedly. Args are: \n"
+ + args.toString());
+ return HANDLED;
+ case AUDIO_OPERATIONS_COMPLETE:
+ Log.i(LOG_TAG, "Abandoning audio focus: now AUDIO_PROCESSING");
+ mAudioManager.abandonAudioFocusForCall();
+ return HANDLED;
default:
// The forced focus switch commands are handled by BaseState.
return NOT_HANDLED;
@@ -210,13 +353,28 @@
}
private class RingingFocusState extends BaseState {
+ // Keeps track of whether we're ringing with audio focus or if we've just entered the state
+ // without acquiring focus because of a silent ringtone or something.
+ private boolean mHasFocus = false;
+
private void tryStartRinging() {
+ if (mHasFocus && mCallAudioManager.isRingtonePlaying()) {
+ Log.i(LOG_TAG, "RingingFocusState#tryStartRinging -- audio focus previously"
+ + " acquired and ringtone already playing -- skipping.");
+ return;
+ }
+
if (mCallAudioManager.startRinging()) {
mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_RING,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
- mAudioManager.setMode(AudioManager.MODE_RINGTONE);
+ // Do not set MODE_RINGTONE if we were previously in the CALL_SCREENING mode -- this
+ // trips up the audio system.
+ if (mAudioManager.getMode() != AudioManager.MODE_CALL_SCREENING) {
+ mAudioManager.setMode(AudioManager.MODE_RINGTONE);
+ }
mCallAudioManager.setCallAudioRouteFocusState(
CallAudioRouteStateMachine.RINGING_FOCUS);
+ mHasFocus = true;
} else {
Log.i(LOG_TAG, "RINGING state, try start ringing but not acquiring audio focus");
}
@@ -233,6 +391,7 @@
public void exit() {
// Audio mode and audio stream will be set by the next state.
mCallAudioManager.stopRinging();
+ mHasFocus = false;
}
@Override
@@ -249,28 +408,27 @@
// Do nothing and keep ringing.
return HANDLED;
case NO_MORE_RINGING_CALLS:
- // If there are active or holding calls, switch to the appropriate focus.
- // Otherwise abandon focus.
- if (args.hasActiveOrDialingCalls) {
- if (args.foregroundCallIsVoip) {
- transitionTo(mVoipCallFocusState);
- } else {
- transitionTo(mSimCallFocusState);
- }
- } else if (args.hasHoldingCalls || args.isTonePlaying) {
- transitionTo(mOtherFocusState);
- } else {
- transitionTo(mUnfocusedState);
+ BaseState destState = calculateProperStateFromArgs(args);
+ if (destState == this) {
+ Log.w(LOG_TAG, "Got spurious NO_MORE_RINGING_CALLS");
}
+ transitionTo(destState);
return HANDLED;
case NEW_ACTIVE_OR_DIALING_CALL:
// If a call becomes active suddenly, give it priority over ringing.
transitionTo(args.foregroundCallIsVoip
? mVoipCallFocusState : mSimCallFocusState);
return HANDLED;
+ case NEW_AUDIO_PROCESSING_CALL:
+ // If we don't have any more ringing calls, transition to audio processing.
+ if (!args.hasRingingCalls) {
+ transitionTo(mAudioProcessingFocusState);
+ } else {
+ Log.w(LOG_TAG, "Got a audio processing call while there's still a call "
+ + "ringing");
+ }
case NEW_RINGING_CALL:
- Log.w(LOG_TAG, "Unexpected behavior! New ringing call appeared while in " +
- "ringing state.");
+ // Can happen as a duplicate message
return HANDLED;
case NEW_HOLDING_CALL:
// This really shouldn't happen, but transition to the focused state anyway.
@@ -283,6 +441,10 @@
tryStartRinging();
return HANDLED;
}
+ case AUDIO_OPERATIONS_COMPLETE:
+ Log.w(LOG_TAG, "Should not be seeing AUDIO_OPERATIONS_COMPLETE in a focused"
+ + " state");
+ return HANDLED;
default:
// The forced focus switch commands are handled by BaseState.
return NOT_HANDLED;
@@ -310,7 +472,7 @@
switch (msg.what) {
case NO_MORE_ACTIVE_OR_DIALING_CALLS:
// Switch to either ringing, holding, or inactive
- transitionTo(destinationStateAfterNoMoreActiveCalls(args));
+ transitionTo(calculateProperStateFromArgs(args));
return HANDLED;
case NO_MORE_RINGING_CALLS:
// Don't transition state, but stop any call-waiting tones that may have been
@@ -323,7 +485,7 @@
// indicating that a ringing call has disconnected while this state machine
// is in the SimCallFocusState.
if (!args.hasActiveOrDialingCalls) {
- transitionTo(destinationStateAfterNoMoreActiveCalls(args));
+ transitionTo(calculateProperStateFromArgs(args));
}
return HANDLED;
case NO_MORE_HOLDING_CALLS:
@@ -347,11 +509,23 @@
transitionTo(mVoipCallFocusState);
}
return HANDLED;
+ case NEW_AUDIO_PROCESSING_CALL:
+ // If we don't have any more active calls, transition to audio processing.
+ if (!args.hasActiveOrDialingCalls) {
+ transitionTo(mAudioProcessingFocusState);
+ } else {
+ Log.w(LOG_TAG, "Got a audio processing call while there's still a call "
+ + "active");
+ }
case FOREGROUND_VOIP_MODE_CHANGE:
if (args.foregroundCallIsVoip) {
transitionTo(mVoipCallFocusState);
}
return HANDLED;
+ case AUDIO_OPERATIONS_COMPLETE:
+ Log.w(LOG_TAG, "Should not be seeing AUDIO_OPERATIONS_COMPLETE in a focused"
+ + " state");
+ return HANDLED;
default:
// The forced focus switch commands are handled by BaseState.
return NOT_HANDLED;
@@ -379,7 +553,7 @@
switch (msg.what) {
case NO_MORE_ACTIVE_OR_DIALING_CALLS:
// Switch to either ringing, holding, or inactive
- transitionTo(destinationStateAfterNoMoreActiveCalls(args));
+ transitionTo(calculateProperStateFromArgs(args));
return HANDLED;
case NO_MORE_RINGING_CALLS:
// Don't transition state, but stop any call-waiting tones that may have been
@@ -409,11 +583,23 @@
transitionTo(mSimCallFocusState);
}
return HANDLED;
+ case NEW_AUDIO_PROCESSING_CALL:
+ // If we don't have any more active calls, transition to audio processing.
+ if (!args.hasActiveOrDialingCalls) {
+ transitionTo(mAudioProcessingFocusState);
+ } else {
+ Log.w(LOG_TAG, "Got a audio processing call while there's still a call "
+ + "active");
+ }
case FOREGROUND_VOIP_MODE_CHANGE:
if (!args.foregroundCallIsVoip) {
transitionTo(mSimCallFocusState);
}
return HANDLED;
+ case AUDIO_OPERATIONS_COMPLETE:
+ Log.w(LOG_TAG, "Should not be seeing AUDIO_OPERATIONS_COMPLETE in a focused"
+ + " state");
+ return HANDLED;
default:
// The forced focus switch commands are handled by BaseState.
return NOT_HANDLED;
@@ -475,7 +661,12 @@
mCallAudioManager.stopCallWaiting();
return HANDLED;
case TONE_STOPPED_PLAYING:
- transitionTo(destinationStateAfterNoMoreActiveCalls(args));
+ transitionTo(calculateProperStateFromArgs(args));
+ return HANDLED;
+ case AUDIO_OPERATIONS_COMPLETE:
+ Log.w(LOG_TAG, "Should not be seeing AUDIO_OPERATIONS_COMPLETE in a focused"
+ + " state");
+ return HANDLED;
default:
return NOT_HANDLED;
}
@@ -488,6 +679,7 @@
private final BaseState mRingingFocusState = new RingingFocusState();
private final BaseState mSimCallFocusState = new SimCallFocusState();
private final BaseState mVoipCallFocusState = new VoipCallFocusState();
+ private final BaseState mAudioProcessingFocusState = new AudioProcessingFocusState();
private final BaseState mOtherFocusState = new OtherFocusState();
private final AudioManager mAudioManager;
@@ -504,14 +696,39 @@
mSystemStateHelper = systemStateHelper;
mMostRecentMode = AudioManager.MODE_NORMAL;
+ createStates();
+ }
+
+ /**
+ * Used for testing
+ */
+ public CallAudioModeStateMachine(SystemStateHelper systemStateHelper,
+ AudioManager audioManager, Looper looper) {
+ super(CallAudioModeStateMachine.class.getSimpleName(), looper);
+ mAudioManager = audioManager;
+ mSystemStateHelper = systemStateHelper;
+ mMostRecentMode = AudioManager.MODE_NORMAL;
+
+ createStates();
+ }
+
+ private void createStates() {
addState(mUnfocusedState);
addState(mRingingFocusState);
addState(mSimCallFocusState);
addState(mVoipCallFocusState);
+ addState(mAudioProcessingFocusState);
addState(mOtherFocusState);
setInitialState(mUnfocusedState);
start();
- sendMessage(INITIALIZE, new MessageArgs());
+ sendMessage(INITIALIZE, new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(Log.createSubsession())
+ .build());
}
public void setCallAudioManager(CallAudioManager callAudioManager) {
@@ -551,15 +768,32 @@
Log.endSession();
}
- private BaseState destinationStateAfterNoMoreActiveCalls(MessageArgs args) {
- if (args.hasHoldingCalls) {
+ private BaseState calculateProperStateFromArgs(MessageArgs args) {
+ // If there are active, audio-processing, holding, or ringing calls,
+ // switch to the appropriate focus.
+ // Otherwise abandon focus.
+
+ // The order matters here. If there are active calls, holding focus for them takes priority.
+ // After that, we want to prioritize holding calls over ringing calls so that when a
+ // call-waiting call gets answered, there's no transition in and out of the ringing focus
+ // state. After that, we want tones since we actually hold focus during them, then the
+ // audio processing state because that will release focus.
+ if (args.hasActiveOrDialingCalls) {
+ if (args.foregroundCallIsVoip) {
+ return mVoipCallFocusState;
+ } else {
+ return mSimCallFocusState;
+ }
+ } else if (args.hasHoldingCalls) {
return mOtherFocusState;
} else if (args.hasRingingCalls) {
return mRingingFocusState;
} else if (args.isTonePlaying) {
return mOtherFocusState;
- } else {
- return mUnfocusedState;
+ } else if (args.hasAudioProcessingCalls) {
+ return mAudioProcessingFocusState;
}
+ return mUnfocusedState;
}
+
}
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index 44a3384..a562021 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -28,6 +28,7 @@
import android.media.AudioManager;
import android.media.IAudioService;
import android.os.Binder;
+import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -122,6 +123,11 @@
// Wired headset, earpiece, or speakerphone, in that order of precedence.
public static final int SWITCH_BASELINE_ROUTE = 1005;
+ // Messages denoting that the speakerphone was turned on/off. Used to update state when we
+ // weren't the ones who turned it on/off
+ public static final int SPEAKER_ON = 1006;
+ public static final int SPEAKER_OFF = 1007;
+
public static final int USER_SWITCH_EARPIECE = 1101;
public static final int USER_SWITCH_BLUETOOTH = 1102;
public static final int USER_SWITCH_HEADSET = 1103;
@@ -180,6 +186,8 @@
put(SWITCH_HEADSET, "SWITCH_HEADSET");
put(SWITCH_SPEAKER, "SWITCH_SPEAKER");
put(SWITCH_BASELINE_ROUTE, "SWITCH_BASELINE_ROUTE");
+ put(SPEAKER_ON, "SPEAKER_ON");
+ put(SPEAKER_OFF, "SPEAKER_OFF");
put(USER_SWITCH_EARPIECE, "USER_SWITCH_EARPIECE");
put(USER_SWITCH_BLUETOOTH, "USER_SWITCH_BLUETOOTH");
@@ -373,6 +381,7 @@
switch (msg.what) {
case SWITCH_EARPIECE:
case USER_SWITCH_EARPIECE:
+ case SPEAKER_OFF:
// Nothing to do here
return HANDLED;
case BT_AUDIO_CONNECTED:
@@ -404,11 +413,13 @@
return HANDLED;
case SWITCH_SPEAKER:
case USER_SWITCH_SPEAKER:
+ case SPEAKER_ON:
transitionTo(mActiveSpeakerRoute);
return HANDLED;
case SWITCH_FOCUS:
if (msg.arg1 == NO_FOCUS) {
reinitialize();
+ mCallAudioManager.notifyAudioOperationsComplete();
}
return HANDLED;
default:
@@ -448,6 +459,7 @@
switch (msg.what) {
case SWITCH_EARPIECE:
case USER_SWITCH_EARPIECE:
+ case SPEAKER_OFF:
// Nothing to do here
return HANDLED;
case BT_AUDIO_CONNECTED:
@@ -472,6 +484,7 @@
return HANDLED;
case SWITCH_SPEAKER:
case USER_SWITCH_SPEAKER:
+ case SPEAKER_ON:
transitionTo(mQuiescentSpeakerRoute);
return HANDLED;
case SWITCH_FOCUS:
@@ -593,15 +606,18 @@
return HANDLED;
case SWITCH_HEADSET:
case USER_SWITCH_HEADSET:
+ case SPEAKER_OFF:
// Nothing to do
return HANDLED;
case SWITCH_SPEAKER:
case USER_SWITCH_SPEAKER:
+ case SPEAKER_ON:
transitionTo(mActiveSpeakerRoute);
return HANDLED;
case SWITCH_FOCUS:
if (msg.arg1 == NO_FOCUS) {
reinitialize();
+ mCallAudioManager.notifyAudioOperationsComplete();
}
return HANDLED;
default:
@@ -661,10 +677,12 @@
return HANDLED;
case SWITCH_HEADSET:
case USER_SWITCH_HEADSET:
+ case SPEAKER_OFF:
// Nothing to do
return HANDLED;
case SWITCH_SPEAKER:
case USER_SWITCH_SPEAKER:
+ case SPEAKER_ON:
transitionTo(mQuiescentSpeakerRoute);
return HANDLED;
case SWITCH_FOCUS:
@@ -834,14 +852,18 @@
mHasUserExplicitlyLeftBluetooth = true;
// fall through
case SWITCH_SPEAKER:
+ case SPEAKER_ON:
setBluetoothOff();
transitionTo(mActiveSpeakerRoute);
return HANDLED;
+ case SPEAKER_OFF:
+ return HANDLED;
case SWITCH_FOCUS:
if (msg.arg1 == NO_FOCUS) {
// Only disconnect SCO audio here instead of routing away from BT entirely.
mBluetoothRouteManager.disconnectSco();
reinitialize();
+ mCallAudioManager.notifyAudioOperationsComplete();
} else if (msg.arg1 == RINGING_FOCUS
&& !mBluetoothRouteManager.isInbandRingingEnabled()) {
setBluetoothOff();
@@ -925,11 +947,15 @@
mHasUserExplicitlyLeftBluetooth = true;
// fall through
case SWITCH_SPEAKER:
+ case SPEAKER_ON:
transitionTo(mActiveSpeakerRoute);
return HANDLED;
+ case SPEAKER_OFF:
+ return HANDLED;
case SWITCH_FOCUS:
if (msg.arg1 == NO_FOCUS) {
reinitialize();
+ mCallAudioManager.notifyAudioOperationsComplete();
} else if (msg.arg1 == ACTIVE_FOCUS) {
setBluetoothOn(null);
}
@@ -986,6 +1012,7 @@
return HANDLED;
case SWITCH_BLUETOOTH:
case USER_SWITCH_BLUETOOTH:
+ case SPEAKER_OFF:
// Nothing to do
return HANDLED;
case SWITCH_HEADSET:
@@ -998,6 +1025,7 @@
return HANDLED;
case SWITCH_SPEAKER:
case USER_SWITCH_SPEAKER:
+ case SPEAKER_ON:
transitionTo(mQuiescentSpeakerRoute);
return HANDLED;
case SWITCH_FOCUS:
@@ -1142,9 +1170,16 @@
case USER_SWITCH_SPEAKER:
// Nothing to do
return HANDLED;
+ case SPEAKER_ON:
+ // Expected, since we just transitioned here
+ return HANDLED;
+ case SPEAKER_OFF:
+ sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
+ return HANDLED;
case SWITCH_FOCUS:
if (msg.arg1 == NO_FOCUS) {
reinitialize();
+ mCallAudioManager.notifyAudioOperationsComplete();
}
return HANDLED;
default:
@@ -1214,8 +1249,12 @@
return HANDLED;
case SWITCH_SPEAKER:
case USER_SWITCH_SPEAKER:
+ case SPEAKER_ON:
// Nothing to do
return HANDLED;
+ case SPEAKER_OFF:
+ sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
+ return HANDLED;
case SWITCH_FOCUS:
if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
transitionTo(mActiveSpeakerRoute);
@@ -1277,7 +1316,7 @@
Log.startSession("CARSM.mCR");
try {
if (AudioManager.ACTION_MICROPHONE_MUTE_CHANGED.equals(intent.getAction())) {
- if (mCallsManager.hasEmergencyCall()) {
+ if (mCallsManager.isInEmergencyCall()) {
Log.i(this, "Mute was externally changed when there's an emergency call. " +
"Forcing mute back off.");
sendInternalMessage(MUTE_OFF);
@@ -1293,6 +1332,28 @@
}
};
+ private final BroadcastReceiver mSpeakerPhoneChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.startSession("CARSM.mSPCR");
+ try {
+ if (AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED.equals(intent.getAction())) {
+ if (mAudioManager != null) {
+ if (mAudioManager.isSpeakerphoneOn()) {
+ sendInternalMessage(SPEAKER_ON);
+ } else {
+ sendInternalMessage(SPEAKER_OFF);
+ }
+ }
+ } else {
+ Log.w(this, "Received non-speakerphone-change intent");
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+ };
+
private final ActiveEarpieceRoute mActiveEarpieceRoute = new ActiveEarpieceRoute();
private final ActiveHeadsetRoute mActiveHeadsetRoute = new ActiveHeadsetRoute();
private final ActiveBluetoothRoute mActiveBluetoothRoute = new ActiveBluetoothRoute();
@@ -1320,7 +1381,7 @@
private final WiredHeadsetManager mWiredHeadsetManager;
private final StatusBarNotifier mStatusBarNotifier;
private final CallAudioManager.AudioServiceFactory mAudioServiceFactory;
- private final boolean mDoesDeviceSupportEarpieceRoute;
+ private boolean mDoesDeviceSupportEarpieceRoute;
private final TelecomSystem.SyncRoot mLock;
private boolean mHasUserExplicitlyLeftBluetooth = false;
@@ -1343,16 +1404,6 @@
CallAudioManager.AudioServiceFactory audioServiceFactory,
int earpieceControl) {
super(NAME);
- addState(mActiveEarpieceRoute);
- addState(mActiveHeadsetRoute);
- addState(mActiveBluetoothRoute);
- addState(mActiveSpeakerRoute);
- addState(mRingingBluetoothRoute);
- addState(mQuiescentEarpieceRoute);
- addState(mQuiescentHeadsetRoute);
- addState(mQuiescentBluetoothRoute);
- addState(mQuiescentSpeakerRoute);
-
mContext = context;
mCallsManager = callsManager;
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
@@ -1360,6 +1411,34 @@
mWiredHeadsetManager = wiredHeadsetManager;
mStatusBarNotifier = statusBarNotifier;
mAudioServiceFactory = audioServiceFactory;
+ mLock = callsManager.getLock();
+
+ createStates(earpieceControl);
+ }
+
+ /** Used for testing only */
+ public CallAudioRouteStateMachine(
+ Context context,
+ CallsManager callsManager,
+ BluetoothRouteManager bluetoothManager,
+ WiredHeadsetManager wiredHeadsetManager,
+ StatusBarNotifier statusBarNotifier,
+ CallAudioManager.AudioServiceFactory audioServiceFactory,
+ int earpieceControl, Looper looper) {
+ super(NAME, looper);
+ mContext = context;
+ mCallsManager = callsManager;
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mBluetoothRouteManager = bluetoothManager;
+ mWiredHeadsetManager = wiredHeadsetManager;
+ mStatusBarNotifier = statusBarNotifier;
+ mAudioServiceFactory = audioServiceFactory;
+ mLock = callsManager.getLock();
+
+ createStates(earpieceControl);
+ }
+
+ private void createStates(int earpieceControl) {
switch (earpieceControl) {
case EARPIECE_FORCE_DISABLED:
mDoesDeviceSupportEarpieceRoute = false;
@@ -1370,7 +1449,17 @@
default:
mDoesDeviceSupportEarpieceRoute = checkForEarpieceSupport();
}
- mLock = callsManager.getLock();
+
+ addState(mActiveEarpieceRoute);
+ addState(mActiveHeadsetRoute);
+ addState(mActiveBluetoothRoute);
+ addState(mActiveSpeakerRoute);
+ addState(mRingingBluetoothRoute);
+ addState(mQuiescentEarpieceRoute);
+ addState(mQuiescentHeadsetRoute);
+ addState(mQuiescentBluetoothRoute);
+ addState(mQuiescentSpeakerRoute);
+
mStateNameToRouteCode = new HashMap<>(8);
mStateNameToRouteCode.put(mQuiescentEarpieceRoute.getName(), ROUTE_EARPIECE);
@@ -1417,6 +1506,8 @@
mWasOnSpeaker = false;
mContext.registerReceiver(mMuteChangeReceiver,
new IntentFilter(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED));
+ mContext.registerReceiver(mSpeakerPhoneChangeReceiver,
+ new IntentFilter(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED));
mStatusBarNotifier.notifyMute(initState.isMuted());
mStatusBarNotifier.notifySpeakerphone(initState.getRoute() == CallAudioState.ROUTE_SPEAKER);
@@ -1502,8 +1593,12 @@
}
private void setSpeakerphoneOn(boolean on) {
- Log.i(this, "turning speaker phone %s", on);
- mAudioManager.setSpeakerphoneOn(on);
+ if (mAudioManager.isSpeakerphoneOn() != on) {
+ Log.i(this, "turning speaker phone %s", on);
+ mAudioManager.setSpeakerphoneOn(on);
+ } else {
+ Log.i(this, "Ignoring speakerphone request -- already %s", on);
+ }
mStatusBarNotifier.notifySpeakerphone(on);
}
@@ -1512,10 +1607,13 @@
BluetoothDevice connectedDevice =
mBluetoothRouteManager.getBluetoothAudioConnectedDevice();
if (address == null && connectedDevice != null) {
- // null means connect to any device, so don't bother reconnecting. Also, send a
- // message to ourselves telling us that BT audio is already connected.
- Log.i(this, "HFP audio already on. Skipping connecting.");
+ // null means connect to any device, so if we're already connected to some device,
+ // that means we can just tell ourselves that it's connected.
+ // Do still try to connect audio though, so that BluetoothRouteManager knows that
+ // there's an active call.
+ Log.i(this, "Bluetooth audio already on.");
sendInternalMessage(BT_AUDIO_CONNECTED);
+ mBluetoothRouteManager.connectBluetoothAudio(connectedDevice.getAddress());
return;
}
if (connectedDevice == null || !Objects.equals(address, connectedDevice.getAddress())) {
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index 4afa645..7305ab9 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -38,11 +38,16 @@
}
public static class AdapterImpl implements Adapter {
+ private final DefaultDialerCache mDefaultDialerCache;
+ public AdapterImpl(DefaultDialerCache cache) {
+ mDefaultDialerCache = cache;
+ }
+
@Override
public void processOutgoingCallIntent(Context context, CallsManager callsManager,
Intent intent, String callingPackage) {
CallIntentProcessor.processOutgoingCallIntent(context, callsManager, intent,
- callingPackage);
+ callingPackage, mDefaultDialerCache);
}
@Override
@@ -58,11 +63,6 @@
public static final String KEY_IS_UNKNOWN_CALL = "is_unknown_call";
public static final String KEY_IS_INCOMING_CALL = "is_incoming_call";
- /*
- * Whether or not the dialer initiating this outgoing call is the default dialer, or system
- * dialer and thus allowed to make emergency calls.
- */
- public static final String KEY_IS_PRIVILEGED_DIALER = "is_privileged_dialer";
/**
* The user initiating the outgoing call.
@@ -72,10 +72,13 @@
private final Context mContext;
private final CallsManager mCallsManager;
+ private final DefaultDialerCache mDefaultDialerCache;
- public CallIntentProcessor(Context context, CallsManager callsManager) {
+ public CallIntentProcessor(Context context, CallsManager callsManager,
+ DefaultDialerCache defaultDialerCache) {
this.mContext = context;
this.mCallsManager = callsManager;
+ this.mDefaultDialerCache = defaultDialerCache;
}
public void processIntent(Intent intent, String callingPackage) {
@@ -86,7 +89,8 @@
if (isUnknownCall) {
processUnknownCallIntent(mCallsManager, intent);
} else {
- processOutgoingCallIntent(mContext, mCallsManager, intent, callingPackage);
+ processOutgoingCallIntent(mContext, mCallsManager, intent, callingPackage,
+ mDefaultDialerCache);
}
Trace.endSection();
}
@@ -102,7 +106,8 @@
Context context,
CallsManager callsManager,
Intent intent,
- String callingPackage) {
+ String callingPackage,
+ DefaultDialerCache defaultDialerCache) {
Uri handle = intent.getData();
String scheme = handle.getScheme();
@@ -157,6 +162,20 @@
UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
+ boolean isPrivilegedDialer = defaultDialerCache.isDefaultOrSystemDialer(callingPackage,
+ initiatingUser.getIdentifier());
+
+ NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
+ context, callsManager, intent, callsManager.getPhoneNumberUtilsAdapter(),
+ isPrivilegedDialer, defaultDialerCache);
+
+ // If the broadcaster comes back with an immediate error, disconnect and show a dialog.
+ NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall();
+ if (disposition.disconnectCause != DisconnectCause.NOT_DISCONNECTED) {
+ showErrorDialog(context, disposition.disconnectCause);
+ return;
+ }
+
// Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
CompletableFuture<Call> callFuture = callsManager
.startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser,
@@ -167,7 +186,7 @@
if (call != null) {
Log.continueSession(logSubsession, "CIP.sNOCI");
try {
- sendNewOutgoingCallIntent(context, call, callsManager, intent);
+ broadcaster.processCall(call, disposition);
} finally {
Log.endSession();
}
@@ -175,29 +194,6 @@
});
}
- static void sendNewOutgoingCallIntent(Context context, Call call, CallsManager callsManager,
- Intent intent) {
- // Asynchronous calls should not usually be made inside a BroadcastReceiver because once
- // onReceive is complete, the BroadcastReceiver's process runs the risk of getting
- // killed if memory is scarce. However, this is OK here because the entire Telecom
- // process will be running throughout the duration of the phone call and should never
- // be killed.
- final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false);
-
- NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
- context, callsManager, call, intent, callsManager.getPhoneNumberUtilsAdapter(),
- isPrivilegedDialer);
-
- // If the broadcaster comes back with an immediate error, disconnect and show a dialog.
- NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall();
- if (disposition.disconnectCause != DisconnectCause.NOT_DISCONNECTED) {
- disconnectCallAndShowErrorDialog(context, call, disposition.disconnectCause);
- return;
- }
-
- broadcaster.processCall(disposition);
- }
-
/**
* If the call is initiated from managed profile but there is no work dialer installed, treat
* the call is initiated from its parent user.
@@ -269,9 +265,7 @@
callsManager.addNewUnknownCall(phoneAccountHandle, intent.getExtras());
}
- private static void disconnectCallAndShowErrorDialog(
- Context context, Call call, int errorCode) {
- call.disconnect();
+ private static void showErrorDialog(Context context, int errorCode) {
final Intent errorIntent = new Intent(context, ErrorDialogActivity.class);
int errorMessageId = -1;
switch (errorCode) {
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index 7c31bca..7a5af14 100755
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -41,8 +41,7 @@
// TODO: Needed for move to system service: import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.CallerInfo;
-import com.android.internal.telephony.SubscriptionController;
+import android.telecom.CallerInfo;
import com.android.server.telecom.callfiltering.CallFilteringResult;
import java.util.Arrays;
@@ -132,6 +131,11 @@
private static final String TAG = CallLogManager.class.getSimpleName();
+ // Copied from android.telephony.DisconnectCause.toString
+ // TODO: come up with a better way to indicate in a android.telecom.DisconnectCause that
+ // a conference was merged successfully
+ private static final String REASON_IMS_MERGED_SUCCESSFULLY = "IMS_MERGED_SUCCESSFULLY";
+
private final Context mContext;
private final CarrierConfigManager mCarrierConfigManager;
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
@@ -200,7 +204,11 @@
* should be logged in its PhoneAccount
*/
@VisibleForTesting
- public boolean shouldLogDisconnectedCall(Call call, int oldState, boolean isCallCancelled) {
+ public boolean shouldLogDisconnectedCall(Call call, int oldState, boolean isCallCanceled) {
+ boolean shouldCallSelfManagedLogged = call.isLoggedSelfManaged()
+ && (call.getHandoverState() == HandoverState.HANDOVER_NONE
+ || call.getHandoverState() == HandoverState.HANDOVER_COMPLETE);
+
// "Choose account" phase when disconnected
if (oldState == CallState.SELECT_PHONE_ACCOUNT) {
return false;
@@ -211,6 +219,14 @@
return false;
}
+ // A conference call which had no children should not be logged; this case will occur on IMS
+ // when no conference event package data is received. We will have logged the participants
+ // as they merge into the conference, so we should not log the conference itself.
+ if (call.isConference() && !call.hadChildren() &&
+ !call.hasProperty(Connection.PROPERTY_REMOTELY_HOSTED)) {
+ return false;
+ }
+
// A child call of a conference which was remotely hosted; these didn't originate on this
// device and should not be logged.
if (call.getParentCall() != null && call.hasProperty(Connection.PROPERTY_REMOTELY_HOSTED)) {
@@ -218,7 +234,7 @@
}
DisconnectCause cause = call.getDisconnectCause();
- if (isCallCancelled) {
+ if (isCallCanceled) {
// No log when disconnecting to simulate a single party conference.
if (cause != null
&& DisconnectCause.REASON_EMULATING_SINGLE_CALL.equals(cause.getReason())) {
@@ -227,8 +243,9 @@
// Explicitly canceled
// Conference children connections only have CAPABILITY_DISCONNECT_FROM_CONFERENCE.
// Log them when they are disconnected from conference.
- return Connection.can(call.getConnectionCapabilities(),
- Connection.CAPABILITY_DISCONNECT_FROM_CONFERENCE);
+ return (call.getConnectionCapabilities()
+ & Connection.CAPABILITY_DISCONNECT_FROM_CONFERENCE)
+ == Connection.CAPABILITY_DISCONNECT_FROM_CONFERENCE;
}
// An external call
if (call.isExternalCall()) {
@@ -236,9 +253,11 @@
}
// Call merged into conferences and marked with IMS_MERGED_SUCCESSFULLY.
- if (cause != null && android.telephony.DisconnectCause.toString(
- android.telephony.DisconnectCause.IMS_MERGED_SUCCESSFULLY)
- .equals(cause.getReason())) {
+ // Return false if the conference supports the participants packets for the carrier.
+ // Otherwise, fall through. Merged calls would be associated with disconnected
+ // connections because of special carrier requirements. Those calls don't look like
+ // merged, e.g. could be one active and the other on hold.
+ if (cause != null && REASON_IMS_MERGED_SUCCESSFULLY.equals(cause.getReason())) {
int subscriptionId = mPhoneAccountRegistrar
.getSubscriptionIdForPhoneAccount(call.getTargetPhoneAccount());
// By default, the conference should return a list of participants.
@@ -256,9 +275,6 @@
}
}
- boolean shouldCallSelfManagedLogged = call.isLoggedSelfManaged()
- && (call.getHandoverState() == HandoverState.HANDOVER_NONE
- || call.getHandoverState() == HandoverState.HANDOVER_COMPLETE);
// Call is NOT a self-managed call OR call is a self-managed call which has indicated it
// should be logged in its PhoneAccount
return !call.isSelfManaged() || shouldCallSelfManagedLogged;
@@ -295,7 +311,18 @@
*/
void logCall(Call call, int callLogType,
@Nullable LogCallCompletedListener logCallCompletedListener, CallFilteringResult result) {
- final long creationTime = call.getCreationTimeMillis();
+ long creationTime;
+
+ if (call.getConnectTimeMillis() != 0
+ && call.getConnectTimeMillis() < call.getCreationTimeMillis()) {
+ // If connected time is available, use connected time. The connected time might be
+ // earlier than created time since it might come from carrier sent special SMS to
+ // notifier user earlier missed call.
+ creationTime = call.getConnectTimeMillis();
+ } else {
+ creationTime = call.getCreationTimeMillis();
+ }
+
final long age = call.getAgeMillis();
final String logNumber = getLogNumber(call);
@@ -320,10 +347,11 @@
int callFeatures = getCallFeatures(call.getVideoStateHistory(),
call.getDisconnectCause().getCode() == DisconnectCause.CALL_PULLED,
- shouldSaveHdInfo(call, accountHandle),
- (call.getConnectionProperties() & Connection.PROPERTY_ASSISTED_DIALING_USED) ==
- Connection.PROPERTY_ASSISTED_DIALING_USED,
- call.wasEverRttCall());
+ call.wasHighDefAudio(), call.wasWifi(),
+ (call.getConnectionProperties() & Connection.PROPERTY_ASSISTED_DIALING) ==
+ Connection.PROPERTY_ASSISTED_DIALING,
+ call.wasEverRttCall(),
+ call.wasVolte());
if (callLogType == Calls.BLOCKED_TYPE) {
logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber,
@@ -443,11 +471,12 @@
* @param videoState The video state.
* @param isPulledCall {@code true} if this call was pulled to another device.
* @param isStoreHd {@code true} if this call was used HD.
+ * @param isWifi {@code true} if this call was used wifi.
* @param isUsingAssistedDialing {@code true} if this call used assisted dialing.
* @return The call features.
*/
private static int getCallFeatures(int videoState, boolean isPulledCall, boolean isStoreHd,
- boolean isUsingAssistedDialing, boolean isRtt) {
+ boolean isWifi, boolean isUsingAssistedDialing, boolean isRtt, boolean isVolte) {
int features = 0;
if (VideoProfile.isVideo(videoState)) {
features |= Calls.FEATURES_VIDEO;
@@ -458,31 +487,21 @@
if (isStoreHd) {
features |= Calls.FEATURES_HD_CALL;
}
+ if (isWifi) {
+ features |= Calls.FEATURES_WIFI;
+ }
if (isUsingAssistedDialing) {
features |= Calls.FEATURES_ASSISTED_DIALING_USED;
}
if (isRtt) {
features |= Calls.FEATURES_RTT;
}
+ if (isVolte) {
+ features |= Calls.FEATURES_VOLTE;
+ }
return features;
}
- private boolean shouldSaveHdInfo(Call call, PhoneAccountHandle accountHandle) {
- CarrierConfigManager configManager = (CarrierConfigManager) mContext.getSystemService(
- Context.CARRIER_CONFIG_SERVICE);
- PersistableBundle configBundle = null;
- if (configManager != null) {
- configBundle = configManager.getConfigForSubId(
- mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle));
- }
- if (configBundle != null && configBundle.getBoolean(
- CarrierConfigManager.KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL)
- && call.wasHighDefAudio()) {
- return true;
- }
- return false;
- }
-
/**
* Retrieve the phone number from the call, and then process it before returning the
* actual number that is to be logged.
diff --git a/src/com/android/server/telecom/CallRecordingTonePlayer.java b/src/com/android/server/telecom/CallRecordingTonePlayer.java
index 9b1c4a5..1b522bc 100644
--- a/src/com/android/server/telecom/CallRecordingTonePlayer.java
+++ b/src/com/android/server/telecom/CallRecordingTonePlayer.java
@@ -17,12 +17,15 @@
package com.android.server.telecom;
import android.content.Context;
+import android.media.AudioAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.AudioRecordingConfiguration;
import android.media.MediaPlayer;
import android.os.Handler;
import android.os.Looper;
+import android.os.Message;
+import android.provider.MediaStore;
import android.telecom.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -59,19 +62,71 @@
}
};
+ private class LoopingTonePlayer extends Handler {
+ private Runnable mPlayToneRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mRecordingTonePlayer != null) {
+ mRecordingTonePlayer.start();
+ postDelayed(this, mRepeatInterval);
+ }
+ }
+ };
+ private MediaPlayer mRecordingTonePlayer = null;
+
+ LoopingTonePlayer() {
+ // We're using the main looper here to avoid creating more threads and risking a thread
+ // leak. The actual playing of the tone doesn't take up much time on the calling
+ // thread, so it's okay to use the main thread for this.
+ super(Looper.getMainLooper());
+ }
+
+ private boolean start() {
+ if (mRecordingTonePlayer != null) {
+ Log.w(CallRecordingTonePlayer.this, "Can't start looping tone player more than"
+ + " once");
+ return false;
+ }
+ AudioDeviceInfo telephonyDevice = getTelephonyDevice(mAudioManager);
+ if (telephonyDevice != null) {
+ mRecordingTonePlayer = MediaPlayer.create(mContext, R.raw.record);
+ mRecordingTonePlayer.setPreferredDevice(telephonyDevice);
+ mRecordingTonePlayer.setVolume(0.1f);
+ AudioAttributes audioAttributes = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION).build();
+ mRecordingTonePlayer.setAudioAttributes(audioAttributes);
+
+ post(mPlayToneRunnable);
+ return true;
+ } else {
+ Log.w(this ,"startCallRecordingTone: can't find telephony audio device.");
+ return false;
+ }
+ }
+
+ private void stop() {
+ mRecordingTonePlayer.release();
+ mRecordingTonePlayer = null;
+ }
+ }
+
private final AudioManager mAudioManager;
private final Context mContext;
private final TelecomSystem.SyncRoot mLock;
private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
+ private final long mRepeatInterval;
private boolean mIsRecording = false;
- private MediaPlayer mRecordingTonePlayer = null;
+ private LoopingTonePlayer mLoopingTonePlayer;
private List<Call> mCalls = new ArrayList<>();
public CallRecordingTonePlayer(Context context, AudioManager audioManager,
+ Timeouts.Adapter timeouts,
TelecomSystem.SyncRoot lock) {
mContext = context;
mAudioManager = audioManager;
mLock = lock;
+ mRepeatInterval = timeouts.getCallRecordingToneRepeatIntervalMillis(
+ context.getContentResolver());
}
@Override
@@ -161,7 +216,7 @@
*/
private void maybeStartCallAudioTone() {
if (mIsRecording && hasActiveCall()) {
- startCallRecordingTone(mContext);
+ startCallRecordingTone();
}
}
@@ -229,23 +284,15 @@
* Begins playing the call recording tone to the remote end of the call.
* The call recording tone is played via the telephony audio output device; this means that it
* will only be audible to the remote end of the call, not the local side.
- *
- * @param context required for obtaining media player.
*/
- private void startCallRecordingTone(Context context) {
- if (mRecordingTonePlayer != null) {
+ private void startCallRecordingTone() {
+ if (mLoopingTonePlayer != null) {
+ Log.w(this, "Tone is already playing");
return;
}
- AudioDeviceInfo telephonyDevice = getTelephonyDevice(mAudioManager);
- if (telephonyDevice != null) {
- Log.i(this ,"startCallRecordingTone: playing call recording tone to remote end.");
- mRecordingTonePlayer = MediaPlayer.create(context, R.raw.record);
- mRecordingTonePlayer.setLooping(true);
- mRecordingTonePlayer.setPreferredDevice(telephonyDevice);
- mRecordingTonePlayer.setVolume(0.1f);
- mRecordingTonePlayer.start();
- } else {
- Log.w(this ,"startCallRecordingTone: can't find telephony audio device.");
+ mLoopingTonePlayer = new LoopingTonePlayer();
+ if (!mLoopingTonePlayer.start()) {
+ mLoopingTonePlayer = null;
}
}
@@ -253,10 +300,10 @@
* Attempts to stop the call recording tone if it is playing.
*/
private void stopCallRecordingTone() {
- if (mRecordingTonePlayer != null) {
- Log.i(this ,"stopCallRecordingTone: stopping call recording tone.");
- mRecordingTonePlayer.stop();
- mRecordingTonePlayer = null;
+ if (mLoopingTonePlayer != null) {
+ Log.i(this, "stopCallRecordingTone: stopping call recording tone.");
+ mLoopingTonePlayer.stop();
+ mLoopingTonePlayer = null;
}
}
diff --git a/src/com/android/server/telecom/CallScreeningServiceHelper.java b/src/com/android/server/telecom/CallScreeningServiceHelper.java
index a9341ab..f02b924 100644
--- a/src/com/android/server/telecom/CallScreeningServiceHelper.java
+++ b/src/com/android/server/telecom/CallScreeningServiceHelper.java
@@ -21,7 +21,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Handler;
import android.os.IBinder;
@@ -45,32 +44,39 @@
private static final String TAG = CallScreeningServiceHelper.class.getSimpleName();
/**
- * Abstracts away dependency on the {@link PackageManager} required to fetch the label for an
- * app.
- */
- public interface AppLabelProxy {
- CharSequence getAppLabel(String packageName);
- }
-
- /**
* Implementation of {@link CallScreeningService} adapter AIDL; provides a means for responses
* from the call screening service to be handled.
*/
private class CallScreeningAdapter extends ICallScreeningAdapter.Stub {
+ private ServiceConnection mServiceConnection;
+
+ public CallScreeningAdapter(ServiceConnection connection) {
+ mServiceConnection = connection;
+ }
+
@Override
public void allowCall(String s) throws RemoteException {
- // no-op; we don't allow this on outgoing calls.
+ unbindCallScreeningService();
}
@Override
public void silenceCall(String s) throws RemoteException {
- // no-op; we don't allow this on outgoing calls.
+ unbindCallScreeningService();
+ }
+
+ @Override
+ public void screenCallFurther(String callId) throws RemoteException {
+ unbindCallScreeningService();
}
@Override
public void disallowCall(String s, boolean b, boolean b1, boolean b2,
ComponentName componentName) throws RemoteException {
- // no-op; we don't allow this on outgoing calls.
+ unbindCallScreeningService();
+ }
+
+ private void unbindCallScreeningService() {
+ mContext.unbindService(mServiceConnection);
}
}
@@ -123,7 +129,7 @@
try {
try {
// Note: for outgoing calls, never include the restricted extras.
- screeningService.screenCall(new CallScreeningAdapter(),
+ screeningService.screenCall(new CallScreeningAdapter(this),
mParcelableCallUtilsConverter.toParcelableCallForScreening(mCall,
false /* areRestrictedExtrasIncluded */));
} catch (RemoteException e) {
diff --git a/src/com/android/server/telecom/CallState.java b/src/com/android/server/telecom/CallState.java
index 1e31732..0411ecc 100644
--- a/src/com/android/server/telecom/CallState.java
+++ b/src/com/android/server/telecom/CallState.java
@@ -118,7 +118,19 @@
* Indicates that an incoming call has been answered by the in-call UI, but Telephony hasn't yet
* set the call to active.
*/
- public static final int ANSWERED = 11;
+ public static final int ANSWERED = TelecomProtoEnums.ANSWERED; // = 11
+
+ /**
+ * Indicates that the call is undergoing audio processing by a different app in the background.
+ * @see android.telecom.Call#STATE_AUDIO_PROCESSING
+ */
+ public static final int AUDIO_PROCESSING = TelecomProtoEnums.AUDIO_PROCESSING; // = 12
+
+ /**
+ * Indicates that the call is in a fake ringing state.
+ * @see android.telecom.Call#STATE_SIMULATED_RINGING
+ */
+ public static final int SIMULATED_RINGING = TelecomProtoEnums.SIMULATED_RINGING; // = 13
public static String toString(int callState) {
switch (callState) {
@@ -146,6 +158,10 @@
return "PULLING";
case ANSWERED:
return "ANSWERED";
+ case AUDIO_PROCESSING:
+ return "AUDIO_PROCESSING";
+ case SIMULATED_RINGING:
+ return "SIMULATED_RINGING";
default:
return "UNKNOWN";
}
diff --git a/src/com/android/server/telecom/CallerInfoAsyncQueryFactory.java b/src/com/android/server/telecom/CallerInfoAsyncQueryFactory.java
index 500f16e..cfb9f6d 100644
--- a/src/com/android/server/telecom/CallerInfoAsyncQueryFactory.java
+++ b/src/com/android/server/telecom/CallerInfoAsyncQueryFactory.java
@@ -15,7 +15,7 @@
*/
package com.android.server.telecom;
-import com.android.internal.telephony.CallerInfoAsyncQuery;
+import android.telecom.CallerInfoAsyncQuery;
import android.content.Context;
diff --git a/src/com/android/server/telecom/CallerInfoLookupHelper.java b/src/com/android/server/telecom/CallerInfoLookupHelper.java
index a919921..e9f1c16 100644
--- a/src/com/android/server/telecom/CallerInfoLookupHelper.java
+++ b/src/com/android/server/telecom/CallerInfoLookupHelper.java
@@ -18,8 +18,6 @@
import android.annotation.Nullable;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
@@ -30,10 +28,9 @@
import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.CallerInfo;
-import com.android.internal.telephony.CallerInfoAsyncQuery;
+import android.telecom.CallerInfo;
+import android.telecom.CallerInfoAsyncQuery;
-import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -194,14 +191,14 @@
for (OnQueryCompleteListener l : info.listeners) {
l.onCallerInfoQueryComplete(handle, ci);
}
- if (ci.contactDisplayPhotoUri == null) {
+ if (ci.getContactDisplayPhotoUri() == null) {
Log.i(CallerInfoLookupHelper.this, "There is no photo for this " +
"contact, skipping photo query");
mQueryEntries.remove(handle);
} else {
info.callerInfo = ci;
info.imageQueryPending = true;
- startPhotoLookup(handle, ci.contactDisplayPhotoUri);
+ startPhotoLookup(handle, ci.getContactDisplayPhotoUri());
}
} else {
Log.i(CallerInfoLookupHelper.this, "CI query for handle %s has completed," +
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
old mode 100755
new mode 100644
index 26dd08a..5273675
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -16,37 +16,57 @@
package com.android.server.telecom;
+import static android.telecom.TelecomManager.ACTION_POST_CALL;
+import static android.telecom.TelecomManager.DURATION_LONG;
+import static android.telecom.TelecomManager.DURATION_MEDIUM;
+import static android.telecom.TelecomManager.DURATION_SHORT;
+import static android.telecom.TelecomManager.DURATION_VERY_SHORT;
+import static android.telecom.TelecomManager.EXTRA_CALL_DURATION;
+import static android.telecom.TelecomManager.EXTRA_DISCONNECT_CAUSE;
+import static android.telecom.TelecomManager.EXTRA_HANDLE;
+import static android.telecom.TelecomManager.MEDIUM_CALL_TIME_MS;
+import static android.telecom.TelecomManager.SHORT_CALL_TIME_MS;
+import static android.telecom.TelecomManager.VERY_SHORT_CALL_TIME_MS;
+
+import android.Manifest;
import android.annotation.NonNull;
import android.app.ActivityManager;
+import android.app.AlertDialog;
import android.app.KeyguardManager;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
import android.media.AudioManager;
import android.media.AudioSystem;
-import android.media.ToneGenerator;
import android.media.MediaPlayer;
+import android.media.ToneGenerator;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.SystemVibrator;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.BlockedNumberContract;
import android.provider.BlockedNumberContract.SystemContract;
import android.provider.CallLog.Calls;
import android.provider.Settings;
+import android.sysprop.TelephonyProperties;
import android.telecom.CallAudioState;
+import android.telecom.CallerInfo;
import android.telecom.Conference;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
@@ -66,29 +86,34 @@
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.widget.Button;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.AsyncEmergencyContactNotifier;
-import com.android.internal.telephony.CallerInfo;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
-import com.android.server.telecom.callfiltering.AsyncBlockCheckFilter;
import com.android.server.telecom.callfiltering.BlockCheckerAdapter;
+import com.android.server.telecom.callfiltering.BlockCheckerFilter;
import com.android.server.telecom.callfiltering.CallFilterResultCallback;
import com.android.server.telecom.callfiltering.CallFilteringResult;
-import com.android.server.telecom.callfiltering.CallScreeningServiceController;
-import com.android.server.telecom.callfiltering.DirectToVoicemailCallFilter;
+import com.android.server.telecom.callfiltering.CallFilteringResult.Builder;
+import com.android.server.telecom.callfiltering.CallScreeningServiceFilter;
+import com.android.server.telecom.callfiltering.DirectToVoicemailFilter;
import com.android.server.telecom.callfiltering.IncomingCallFilter;
+import com.android.server.telecom.callfiltering.IncomingCallFilterGraph;
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.ui.CallRedirectionConfirmDialogActivity;
+import com.android.server.telecom.ui.AudioProcessingNotification;
import com.android.server.telecom.ui.CallRedirectionTimeoutDialogActivity;
import com.android.server.telecom.ui.ConfirmCallDialogActivity;
+import com.android.server.telecom.ui.DisconnectedCallNotifier;
import com.android.server.telecom.ui.IncomingCallNotifier;
+import com.android.server.telecom.ui.ToastFactory;
import java.util.ArrayList;
import java.util.Arrays;
@@ -97,6 +122,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -105,6 +131,7 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -145,6 +172,7 @@
void onDisconnectedTonePlaying(boolean isTonePlaying);
void onConnectionTimeChanged(Call call);
void onConferenceStateChanged(Call call, boolean isConference);
+ void onCdmaConferenceSwap(Call call);
}
/** Interface used to define the action which is executed delay under some condition. */
@@ -197,7 +225,7 @@
*/
private static final int[] LIVE_CALL_STATES =
{CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
- CallState.PULLING, CallState.ACTIVE};
+ CallState.PULLING, CallState.ACTIVE, CallState.AUDIO_PROCESSING};
/**
* These states determine which calls will cause {@link TelecomManager#isInCall()} or
@@ -208,25 +236,27 @@
*/
public static final int[] ONGOING_CALL_STATES =
{CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING, CallState.PULLING, CallState.ACTIVE,
- CallState.ON_HOLD, CallState.RINGING, CallState.ANSWERED};
+ CallState.ON_HOLD, CallState.RINGING, CallState.SIMULATED_RINGING,
+ CallState.ANSWERED, CallState.AUDIO_PROCESSING};
private static final int[] ANY_CALL_STATE =
{CallState.NEW, CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
- CallState.RINGING, CallState.ACTIVE, CallState.ON_HOLD, CallState.DISCONNECTED,
- CallState.ABORTED, CallState.DISCONNECTING, CallState.PULLING,
- CallState.ANSWERED};
+ CallState.RINGING, CallState.SIMULATED_RINGING, CallState.ACTIVE,
+ CallState.ON_HOLD, CallState.DISCONNECTED, CallState.ABORTED,
+ CallState.DISCONNECTING, CallState.PULLING, CallState.ANSWERED,
+ CallState.AUDIO_PROCESSING};
public static final String TELECOM_CALL_ID_PREFIX = "TC@";
- // Maps call technologies in PhoneConstants to those in Analytics.
+ // Maps call technologies in TelephonyManager to those in Analytics.
private static final Map<Integer, Integer> sAnalyticsTechnologyMap;
static {
sAnalyticsTechnologyMap = new HashMap<>(5);
- sAnalyticsTechnologyMap.put(PhoneConstants.PHONE_TYPE_CDMA, Analytics.CDMA_PHONE);
- sAnalyticsTechnologyMap.put(PhoneConstants.PHONE_TYPE_GSM, Analytics.GSM_PHONE);
- sAnalyticsTechnologyMap.put(PhoneConstants.PHONE_TYPE_IMS, Analytics.IMS_PHONE);
- sAnalyticsTechnologyMap.put(PhoneConstants.PHONE_TYPE_SIP, Analytics.SIP_PHONE);
- sAnalyticsTechnologyMap.put(PhoneConstants.PHONE_TYPE_THIRD_PARTY,
+ sAnalyticsTechnologyMap.put(TelephonyManager.PHONE_TYPE_CDMA, Analytics.CDMA_PHONE);
+ sAnalyticsTechnologyMap.put(TelephonyManager.PHONE_TYPE_GSM, Analytics.GSM_PHONE);
+ sAnalyticsTechnologyMap.put(TelephonyManager.PHONE_TYPE_IMS, Analytics.IMS_PHONE);
+ sAnalyticsTechnologyMap.put(TelephonyManager.PHONE_TYPE_SIP, Analytics.SIP_PHONE);
+ sAnalyticsTechnologyMap.put(TelephonyManager.PHONE_TYPE_THIRD_PARTY,
Analytics.THIRD_PARTY_PHONE);
}
@@ -251,6 +281,13 @@
* Used by {@link #onCallRedirectionComplete}.
*/
private Call mPendingRedirectedOutgoingCall;
+
+ /**
+ * Cached call that's been answered but will be added to mCalls pending confirmation of active
+ * status from the connection service.
+ */
+ private Call mPendingAudioProcessingCall;
+
/**
* Cached latest pending redirected call information which require user-intervention in order
* to be placed. Used by {@link #onCallRedirectionComplete}.
@@ -311,12 +348,15 @@
private final TelecomSystem.SyncRoot mLock;
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final MissedCallNotifier mMissedCallNotifier;
+ private final DisconnectedCallNotifier mDisconnectedCallNotifier;
private IncomingCallNotifier mIncomingCallNotifier;
private final CallerInfoLookupHelper mCallerInfoLookupHelper;
+ private final IncomingCallFilter.Factory mIncomingCallFilterFactory;
private final DefaultDialerCache mDefaultDialerCache;
private final Timeouts.Adapter mTimeoutsAdapter;
private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
private final ClockProxy mClockProxy;
+ private final ToastFactory mToastFactory;
private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
private final Set<Call> mPendingCallsToDisconnect = new HashSet<>();
private final ConnectionServiceFocusManager mConnectionSvrFocusMgr;
@@ -344,10 +384,14 @@
private boolean mCanAddCall = true;
- private TelephonyManager.MultiSimVariants mRadioSimVariants = null;
+ private int mMaxNumberOfSimultaneouslyActiveSims = -1;
private Runnable mStopTone;
+ private LinkedList<HandlerThread> mGraphHandlerThreads;
+
+ private boolean mHasActiveRttCall = false;
+
/**
* Listener to PhoneAccountRegistrar events.
*/
@@ -415,6 +459,7 @@
TelecomSystem.SyncRoot lock,
CallerInfoLookupHelper callerInfoLookupHelper,
MissedCallNotifier missedCallNotifier,
+ DisconnectedCallNotifier.Factory disconnectedCallNotifierFactory,
PhoneAccountRegistrar phoneAccountRegistrar,
HeadsetMediaButtonFactory headsetMediaButtonFactory,
ProximitySensorManagerFactory proximitySensorManagerFactory,
@@ -432,17 +477,21 @@
EmergencyCallHelper emergencyCallHelper,
InCallTonePlayer.ToneGeneratorFactory toneGeneratorFactory,
ClockProxy clockProxy,
+ AudioProcessingNotification audioProcessingNotification,
BluetoothStateReceiver bluetoothStateReceiver,
CallAudioRouteStateMachine.Factory callAudioRouteStateMachineFactory,
CallAudioModeStateMachine.Factory callAudioModeStateMachineFactory,
InCallControllerFactory inCallControllerFactory,
- RoleManagerAdapter roleManagerAdapter) {
+ RoleManagerAdapter roleManagerAdapter,
+ IncomingCallFilter.Factory incomingCallFilterFactory,
+ ToastFactory toastFactory) {
mContext = context;
mLock = lock;
mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
mPhoneAccountRegistrar = phoneAccountRegistrar;
mPhoneAccountRegistrar.addListener(mPhoneAccountListener);
mMissedCallNotifier = missedCallNotifier;
+ mDisconnectedCallNotifier = disconnectedCallNotifierFactory.create(mContext, this);
StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this);
mWiredHeadsetManager = wiredHeadsetManager;
mSystemStateHelper = systemStateHelper;
@@ -452,6 +501,7 @@
mTimeoutsAdapter = timeoutsAdapter;
mEmergencyCallHelper = emergencyCallHelper;
mCallerInfoLookupHelper = callerInfoLookupHelper;
+ mIncomingCallFilterFactory = incomingCallFilterFactory;
mDtmfLocalTonePlayer =
new DtmfLocalTonePlayer(new DtmfLocalTonePlayer.ToneGeneratorProxy());
@@ -493,7 +543,8 @@
mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer,
ringtoneFactory, systemVibrator,
new Ringer.VibrationEffectProxy(), mInCallController);
- mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext, audioManager, mLock);
+ mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext, audioManager,
+ mTimeoutsAdapter, mLock);
mCallAudioManager = new CallAudioManager(callAudioRouteStateMachine,
this, callAudioModeStateMachineFactory.create(systemStateHelper,
(AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)),
@@ -510,6 +561,7 @@
new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, mLock, this);
mInCallWakeLockController = inCallWakeLockControllerFactory.create(context, this);
mClockProxy = clockProxy;
+ mToastFactory = toastFactory;
mRoleManagerAdapter = roleManagerAdapter;
mListeners.add(mInCallWakeLockController);
@@ -520,8 +572,10 @@
mListeners.add(mCallAudioManager);
mListeners.add(mCallRecordingTonePlayer);
mListeners.add(missedCallNotifier);
+ mListeners.add(mDisconnectedCallNotifier);
mListeners.add(mHeadsetMediaButton);
mListeners.add(mProximitySensorManager);
+ mListeners.add(audioProcessingNotification);
// There is no USER_SWITCHED broadcast for user 0, handle it here explicitly.
final UserManager userManager = UserManager.get(mContext);
@@ -534,6 +588,7 @@
CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
intentFilter.addAction(SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED);
context.registerReceiver(mReceiver, intentFilter);
+ mGraphHandlerThreads = new LinkedList<>();
}
public void setIncomingCallNotifier(IncomingCallNotifier incomingCallNotifier) {
@@ -567,6 +622,7 @@
@Override
public void onSuccessfulOutgoingCall(Call call, int callState) {
Log.v(this, "onSuccessfulOutgoingCall, %s", call);
+ call.setPostCallPackageName(getRoleManagerAdapter().getDefaultCallScreeningApp());
setCallState(call, callState, "successful outgoing call");
if (!mCalls.contains(call)) {
@@ -607,35 +663,74 @@
incomingCall.hasProperty(Connection.PROPERTY_EMERGENCY_CALLBACK_MODE),
incomingCall.isSelfManaged(),
extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING));
- onCallFilteringComplete(incomingCall, new CallFilteringResult(true, false, true, true));
+ onCallFilteringComplete(incomingCall, new Builder()
+ .setShouldAllowCall(true)
+ .setShouldReject(false)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(true)
+ .build());
incomingCall.setIsUsingCallFiltering(false);
return;
}
- incomingCall.setIsUsingCallFiltering(true);
- List<IncomingCallFilter.CallFilter> filters = new ArrayList<>();
- filters.add(new DirectToVoicemailCallFilter(mCallerInfoLookupHelper));
- filters.add(new AsyncBlockCheckFilter(mContext, new BlockCheckerAdapter(),
- mCallerInfoLookupHelper, null));
- filters.add(new CallScreeningServiceController(mContext, this, mPhoneAccountRegistrar,
- new ParcelableCallUtils.Converter(), mLock,
- new TelecomServiceImpl.SettingsSecureAdapterImpl(), mCallerInfoLookupHelper,
- new CallScreeningServiceHelper.AppLabelProxy() {
- @Override
- public CharSequence getAppLabel(String packageName) {
- PackageManager pm = mContext.getPackageManager();
- try {
- ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
- return pm.getApplicationLabel(info);
- } catch (PackageManager.NameNotFoundException nnfe) {
- Log.w(this, "Could not determine package name.");
- }
+ IncomingCallFilterGraph graph = setUpCallFilterGraph(incomingCall);
+ graph.performFiltering();
+ }
- return null;
- }
- }));
- new IncomingCallFilter(mContext, this, incomingCall, mLock,
- mTimeoutsAdapter, filters).performFiltering();
+ private IncomingCallFilterGraph setUpCallFilterGraph(Call incomingCall) {
+ incomingCall.setIsUsingCallFiltering(true);
+ String carrierPackageName = getCarrierPackageName();
+ String defaultDialerPackageName = TelecomManager.from(mContext).getDefaultDialerPackage();
+ String userChosenPackageName = getRoleManagerAdapter().getDefaultCallScreeningApp();
+ AppLabelProxy appLabelProxy = packageName -> AppLabelProxy.Util.getAppLabel(
+ mContext.getPackageManager(), packageName);
+ ParcelableCallUtils.Converter converter = new ParcelableCallUtils.Converter();
+
+ IncomingCallFilterGraph graph = new IncomingCallFilterGraph(incomingCall,
+ this::onCallFilteringComplete, mContext, mTimeoutsAdapter, mLock);
+ DirectToVoicemailFilter voicemailFilter = new DirectToVoicemailFilter(incomingCall,
+ mCallerInfoLookupHelper);
+ BlockCheckerFilter blockCheckerFilter = new BlockCheckerFilter(mContext, incomingCall,
+ mCallerInfoLookupHelper, new BlockCheckerAdapter());
+ CallScreeningServiceFilter carrierCallScreeningServiceFilter =
+ new CallScreeningServiceFilter(incomingCall, carrierPackageName,
+ CallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, this,
+ appLabelProxy, converter);
+ CallScreeningServiceFilter callScreeningServiceFilter;
+ if ((userChosenPackageName != null)
+ && (!userChosenPackageName.equals(defaultDialerPackageName))) {
+ callScreeningServiceFilter = new CallScreeningServiceFilter(incomingCall,
+ userChosenPackageName, CallScreeningServiceFilter.PACKAGE_TYPE_USER_CHOSEN,
+ mContext, this, appLabelProxy, converter);
+ } else {
+ callScreeningServiceFilter = new CallScreeningServiceFilter(incomingCall,
+ defaultDialerPackageName,
+ CallScreeningServiceFilter.PACKAGE_TYPE_DEFAULT_DIALER,
+ mContext, this, appLabelProxy, converter);
+ }
+ graph.addFilter(voicemailFilter);
+ graph.addFilter(blockCheckerFilter);
+ graph.addFilter(carrierCallScreeningServiceFilter);
+ graph.addFilter(callScreeningServiceFilter);
+ IncomingCallFilterGraph.addEdge(voicemailFilter, carrierCallScreeningServiceFilter);
+ IncomingCallFilterGraph.addEdge(blockCheckerFilter, carrierCallScreeningServiceFilter);
+ IncomingCallFilterGraph.addEdge(carrierCallScreeningServiceFilter,
+ callScreeningServiceFilter);
+ mGraphHandlerThreads.add(graph.getHandlerThread());
+ return graph;
+ }
+
+ private String getCarrierPackageName() {
+ ComponentName componentName = null;
+ CarrierConfigManager configManager = (CarrierConfigManager) mContext.getSystemService
+ (Context.CARRIER_CONFIG_SERVICE);
+ PersistableBundle configBundle = configManager.getConfig();
+ if (configBundle != null) {
+ componentName = ComponentName.unflattenFromString(configBundle.getString
+ (CarrierConfigManager.KEY_CARRIER_CALL_SCREENING_APP_STRING, ""));
+ }
+
+ return componentName != null ? componentName.getPackageName() : null;
}
@Override
@@ -643,6 +738,8 @@
// Only set the incoming call as ringing if it isn't already disconnected. It is possible
// that the connection service disconnected the call before it was even added to Telecom, in
// which case it makes no sense to set it back to a ringing state.
+ mGraphHandlerThreads.clear();
+
if (incomingCall.getState() != CallState.DISCONNECTED &&
incomingCall.getState() != CallState.DISCONNECTING) {
setCallState(incomingCall, CallState.RINGING,
@@ -653,6 +750,9 @@
}
if (result.shouldAllowCall) {
+ incomingCall.setPostCallPackageName(
+ getRoleManagerAdapter().getDefaultCallScreeningApp());
+
if (hasMaximumManagedRingingCalls(incomingCall)) {
if (shouldSilenceInsteadOfReject(incomingCall)) {
incomingCall.silence();
@@ -670,6 +770,10 @@
"dialing calls.");
rejectCallAndLog(incomingCall, result);
}
+ } else if (result.shouldScreenViaAudio) {
+ Log.i(this, "onCallFilteringCompleted: starting background audio processing");
+ answerCallForAudioProcessing(incomingCall);
+ incomingCall.setAudioProcessingRequestingApp(result.mCallScreeningAppName);
} else if (result.shouldSilence) {
Log.i(this, "onCallFilteringCompleted: setting the call to silent ringing state");
incomingCall.setSilentRingingRequested(true);
@@ -796,6 +900,13 @@
}
@Override
+ public void onConnectionPropertiesChanged(Call call, boolean didRttChange) {
+ if (didRttChange) {
+ updateHasActiveRttCall();
+ }
+ }
+
+ @Override
public void onParentChanged(Call call) {
// parent-child relationship affects which call should be foreground, so do an update.
updateCanAddCall();
@@ -823,6 +934,14 @@
}
@Override
+ public void onCdmaConferenceSwap(Call call) {
+ // SWAP was executed on a CDMA conference
+ for (CallsManagerListener listener : mListeners) {
+ listener.onCdmaConferenceSwap(call);
+ }
+ }
+
+ @Override
public void onIsVoipAudioModeChanged(Call call) {
for (CallsManagerListener listener : mListeners) {
listener.onIsVoipAudioModeChanged(call);
@@ -940,12 +1059,22 @@
@Override
public void onCallHoldFailed(Call call) {
- // Normally, we don't care whether a call hold has failed. However, if a call was held in
- // order to answer an incoming call, that incoming call needs to be brought out of the
- // ANSWERED state so that the user can try the operation again.
+ markAllAnsweredCallAsRinging(call, "hold");
+ }
+
+ @Override
+ public void onCallSwitchFailed(Call call) {
+ markAllAnsweredCallAsRinging(call, "switch");
+ }
+
+ private void markAllAnsweredCallAsRinging(Call call, String actionName) {
+ // Normally, we don't care whether a call hold or switch has failed.
+ // However, if a call was held or switched in order to answer an incoming call, that
+ // incoming call needs to be brought out of the ANSWERED state so that the user can
+ // try the operation again.
for (Call call1 : mCalls) {
if (call1 != call && call1.getState() == CallState.ANSWERED) {
- setCallState(call1, CallState.RINGING, "hold failed on other call");
+ setCallState(call1, CallState.RINGING, actionName + " failed on other call");
}
}
}
@@ -967,19 +1096,13 @@
return mEmergencyCallHelper;
}
- @VisibleForTesting
- public PhoneAccountRegistrar.Listener getPhoneAccountListener() {
- return mPhoneAccountListener;
+ public DefaultDialerCache getDefaultDialerCache() {
+ return mDefaultDialerCache;
}
@VisibleForTesting
- public boolean hasEmergencyCall() {
- for (Call call : mCalls) {
- if (call.isEmergencyCall()) {
- return true;
- }
- }
- return false;
+ public PhoneAccountRegistrar.Listener getPhoneAccountListener() {
+ return mPhoneAccountListener;
}
public boolean hasEmergencyRttCall() {
@@ -1031,10 +1154,16 @@
mListeners.add(listener);
}
- void removeListener(CallsManagerListener listener) {
+ @VisibleForTesting
+ public void removeListener(CallsManagerListener listener) {
mListeners.remove(listener);
}
+ void processIncomingConference(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+ Log.d(this, "processIncomingCallConference");
+ processIncomingCallIntent(phoneAccountHandle, extras, true);
+ }
+
/**
* Starts the process to attach the call to a connection service.
*
@@ -1043,6 +1172,11 @@
* @param extras The optional extras Bundle passed with the intent used for the incoming call.
*/
void processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+ processIncomingCallIntent(phoneAccountHandle, extras, false);
+ }
+
+ void processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras,
+ boolean isConference) {
Log.d(this, "processIncomingCallIntent");
boolean isHandover = extras.getBoolean(TelecomManager.EXTRA_IS_HANDOVER);
Uri handle = extras.getParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS);
@@ -1063,8 +1197,9 @@
phoneAccountHandle,
Call.CALL_DIRECTION_INCOMING /* callDirection */,
false /* forceAttachToExistingConnection */,
- false, /* isConference */
- mClockProxy);
+ isConference, /* isConference */
+ mClockProxy,
+ mToastFactory);
// 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, however
@@ -1095,7 +1230,10 @@
}
}
- if (extras.getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
+ Bundle phoneAccountExtras = phoneAccount.getExtras();
+ if (phoneAccountExtras != null
+ && phoneAccountExtras.getBoolean(
+ PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
Log.d(this, "processIncomingCallIntent: defaulting to voip mode for call %s",
call.getId());
call.setIsVoipAudioMode(true);
@@ -1130,6 +1268,11 @@
// TODO: Move this to be a part of addCall()
call.addListener(this);
+ if (extras.containsKey(TelecomManager.EXTRA_CALL_DISCONNECT_MESSAGE)) {
+ String disconnectMessage = extras.getString(TelecomManager.EXTRA_CALL_DISCONNECT_MESSAGE);
+ Log.i(this, "processIncomingCallIntent Disconnect message " + disconnectMessage);
+ }
+
boolean isHandoverAllowed = true;
if (isHandover) {
if (!isHandoverInProgress() &&
@@ -1176,7 +1319,22 @@
if (!isHandoverAllowed || (call.isSelfManaged() && !isIncomingCallPermitted(call,
call.getTargetPhoneAccount()))) {
- notifyCreateConnectionFailed(phoneAccountHandle, call);
+ if (isConference) {
+ notifyCreateConferenceFailed(phoneAccountHandle, call);
+ } else {
+ notifyCreateConnectionFailed(phoneAccountHandle, call);
+ }
+ } else if (isInEmergencyCall()) {
+ // The incoming call is implicitly being rejected so the user does not get any incoming
+ // call UI during an emergency call. In this case, log the call as missed instead of
+ // rejected since the user did not explicitly reject.
+ mCallLogManager.logCall(call, Calls.MISSED_TYPE,
+ true /*showNotificationForMissedCall*/, null /*CallFilteringResult*/);
+ if (isConference) {
+ notifyCreateConferenceFailed(phoneAccountHandle, call);
+ } else {
+ notifyCreateConnectionFailed(phoneAccountHandle, call);
+ }
} else {
call.startCreateConnection(mPhoneAccountRegistrar);
}
@@ -1201,7 +1359,8 @@
// to the existing connection instead of trying to create a new one.
true /* forceAttachToExistingConnection */,
false, /* isConference */
- mClockProxy);
+ mClockProxy,
+ mToastFactory);
call.initAnalytics();
setIntentExtrasAndStartTime(call, extras);
@@ -1263,7 +1422,18 @@
PhoneAccountHandle requestedAccountHandle,
Bundle extras, UserHandle initiatingUser, Intent originalIntent,
String callingPackage) {
+ final List<Uri> callee = new ArrayList<>();
+ callee.add(handle);
+ return startOutgoingCall(callee, requestedAccountHandle, extras, initiatingUser,
+ originalIntent, callingPackage, false);
+ }
+
+ private CompletableFuture<Call> startOutgoingCall(List<Uri> participants,
+ PhoneAccountHandle requestedAccountHandle,
+ Bundle extras, UserHandle initiatingUser, Intent originalIntent,
+ String callingPackage, boolean isConference) {
boolean isReusedCall;
+ Uri handle = isConference ? Uri.parse("tel:conf-factory") : participants.get(0);
Call call = reuseOutgoingCall(handle);
PhoneAccount account =
@@ -1279,13 +1449,15 @@
mConnectionServiceRepository,
mPhoneNumberUtilsAdapter,
handle,
+ isConference ? participants : null,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
null /* requestedAccountHandle */,
Call.CALL_DIRECTION_OUTGOING /* callDirection */,
false /* forceAttachToExistingConnection */,
- false, /* isConference */
- mClockProxy);
+ isConference, /* isConference */
+ mClockProxy,
+ mToastFactory);
call.initAnalytics(callingPackage);
// Ensure new calls related to self-managed calls/connections are set as such. This
@@ -1346,7 +1518,9 @@
CompletableFuture<List<PhoneAccountHandle>> accountsForCall =
CompletableFuture.completedFuture((Void) null).thenComposeAsync((x) ->
findOutgoingCallPhoneAccount(requestedAccountHandle, handle,
- VideoProfile.isVideo(finalVideoState), initiatingUser),
+ VideoProfile.isVideo(finalVideoState),
+ finalCall.isEmergencyCall(), initiatingUser,
+ isConference),
new LoggedHandlerExecutor(outgoingCallHandler, "CM.fOCP", mLock));
// This is a block of code that executes after the list of potential phone accts has been
@@ -1391,14 +1565,22 @@
CompletableFuture<Call> makeRoomForCall = setAccountHandle.thenComposeAsync(
potentialPhoneAccounts -> {
Log.i(CallsManager.this, "make room for outgoing call stage");
- boolean isPotentialInCallMMICode =
- isPotentialInCallMMICode(handle) && !isSelfManaged;
- // Do not support any more live calls. Our options are to move a call to hold,
- // disconnect a call, or cancel this call altogether. If a call is being reused,
- // then it has already passed the makeRoomForOutgoingCall check once and will
- // fail the second time due to the call transitioning into the CONNECTING state.
- if (!isPotentialInCallMMICode && (!isReusedCall
- && !makeRoomForOutgoingCall(finalCall, finalCall.isEmergencyCall()))) {
+ if (isPotentialInCallMMICode(handle) && !isSelfManaged) {
+ return CompletableFuture.completedFuture(finalCall);
+ }
+ // If a call is being reused, then it has already passed the
+ // makeRoomForOutgoingCall check once and will fail the second time due to the
+ // call transitioning into the CONNECTING state.
+ if (isReusedCall) {
+ return CompletableFuture.completedFuture(finalCall);
+ }
+
+ // If we can not supportany more active calls, our options are to move a call
+ // to hold, disconnect a call, or cancel this call altogether.
+ boolean isRoomForCall = finalCall.isEmergencyCall() ?
+ makeRoomForOutgoingEmergencyCall(finalCall) :
+ makeRoomForOutgoingCall(finalCall);
+ if (!isRoomForCall) {
Call foregroundCall = getForegroundCall();
Log.d(CallsManager.this, "No more room for outgoing call %s ", finalCall);
if (foregroundCall.isSelfManaged()) {
@@ -1414,8 +1596,13 @@
} else {
// If the ongoing call is a managed call, we will prevent the outgoing
// call from dialing.
- notifyCreateConnectionFailed(
- finalCall.getTargetPhoneAccount(), finalCall);
+ if (isConference) {
+ notifyCreateConferenceFailed(finalCall.getTargetPhoneAccount(),
+ finalCall);
+ } else {
+ notifyCreateConnectionFailed(
+ finalCall.getTargetPhoneAccount(), finalCall);
+ }
}
Log.i(CallsManager.this, "Aborting call since there's no room");
return CompletableFuture.completedFuture(null);
@@ -1498,8 +1685,16 @@
isInContacts);
// We only want to provide a CallScreeningService with a call if its not in
- // contacts.
- if (!isInContacts) {
+ // contacts or the package has READ_CONTACT permission.
+ PackageManager packageManager = mContext.getPackageManager();
+ int permission = packageManager.checkPermission(
+ Manifest.permission.READ_CONTACTS,
+ mRoleManagerAdapter.getDefaultCallScreeningApp());
+ Log.d(CallsManager.this,
+ "default call screening service package %s has permissions=%s",
+ mRoleManagerAdapter.getDefaultCallScreeningApp(),
+ permission == PackageManager.PERMISSION_GRANTED);
+ if ((!isInContacts) || (permission == PackageManager.PERMISSION_GRANTED)) {
bindForOutgoingCallerId(theCall);
}
}, new LoggedHandlerExecutor(outgoingCallHandler, "CM.pCSB", mLock));
@@ -1569,6 +1764,38 @@
return mLatestPostSelectionProcessingFuture;
}
+ public void startConference(List<Uri> participants, Bundle clientExtras, String callingPackage,
+ UserHandle initiatingUser) {
+
+ if (clientExtras == null) {
+ clientExtras = new Bundle();
+ }
+
+ PhoneAccountHandle phoneAccountHandle = clientExtras.getParcelable(
+ TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
+ CompletableFuture<Call> callFuture = startOutgoingCall(participants, phoneAccountHandle,
+ clientExtras, initiatingUser, null/* originalIntent */, callingPackage,
+ true/* isconference*/);
+
+ final boolean speakerphoneOn = clientExtras.getBoolean(
+ TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE);
+ final int videoState = clientExtras.getInt(
+ TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE);
+
+ final Session logSubsession = Log.createSubsession();
+ callFuture.thenAccept((call) -> {
+ if (call != null) {
+ Log.continueSession(logSubsession, "CM.pOGC");
+ try {
+ placeOutgoingCall(call, call.getHandle(), null/* gatewayInfo */,
+ speakerphoneOn, videoState);
+ } finally {
+ Log.endSession();
+ }
+ }
+ });
+ }
+
/**
* Performs call identification for an outgoing phone call.
* @param theCall The outgoing call to perform identification.
@@ -1585,19 +1812,10 @@
new ParcelableCallUtils.Converter(),
mCurrentUserHandle,
theCall,
- new CallScreeningServiceHelper.AppLabelProxy() {
+ new AppLabelProxy() {
@Override
public CharSequence getAppLabel(String packageName) {
- PackageManager pm = mContext.getPackageManager();
- try {
- ApplicationInfo info = pm.getApplicationInfo(
- packageName, 0);
- return pm.getApplicationLabel(info);
- } catch (PackageManager.NameNotFoundException nnfe) {
- Log.w(this, "Could not determine package name.");
- }
-
- return null;
+ return Util.getAppLabel(mContext.getPackageManager(), packageName);
}
}).process();
future.thenApply( v -> {
@@ -1623,13 +1841,22 @@
* @param handle The handle of the outgoing call; used to determine the SIP scheme when matching
* phone accounts.
* @param isVideo {@code true} if the call is a video call, {@code false} otherwise.
+ * @param isEmergency {@code true} if the call is an emergency call.
* @param initiatingUser The {@link UserHandle} the call is placed on.
* @return
*/
@VisibleForTesting
public CompletableFuture<List<PhoneAccountHandle>> findOutgoingCallPhoneAccount(
PhoneAccountHandle targetPhoneAccountHandle, Uri handle, boolean isVideo,
- UserHandle initiatingUser) {
+ boolean isEmergency, UserHandle initiatingUser) {
+ return findOutgoingCallPhoneAccount(targetPhoneAccountHandle, handle, isVideo,
+ isEmergency, initiatingUser, false/* isConference */);
+ }
+
+ public CompletableFuture<List<PhoneAccountHandle>> findOutgoingCallPhoneAccount(
+ PhoneAccountHandle targetPhoneAccountHandle, Uri handle, boolean isVideo,
+ boolean isEmergency, UserHandle initiatingUser, boolean isConference) {
+
if (isSelfManaged(targetPhoneAccountHandle, initiatingUser)) {
return CompletableFuture.completedFuture(Arrays.asList(targetPhoneAccountHandle));
}
@@ -1637,12 +1864,13 @@
List<PhoneAccountHandle> accounts;
// Try to find a potential phone account, taking into account whether this is a video
// call.
- accounts = constructPossiblePhoneAccounts(handle, initiatingUser, isVideo);
+ accounts = constructPossiblePhoneAccounts(handle, initiatingUser, isVideo, isEmergency,
+ isConference);
if (isVideo && accounts.size() == 0) {
// Placing a video call but no video capable accounts were found, so consider any
// call capable accounts (we can fallback to audio).
accounts = constructPossiblePhoneAccounts(handle, initiatingUser,
- false /* isVideo */);
+ false /* isVideo */, isEmergency /* isEmergency */, isConference);
}
Log.v(this, "findOutgoingCallPhoneAccount: accounts = " + accounts);
@@ -1667,7 +1895,8 @@
new CallerInfoLookupHelper.OnQueryCompleteListener() {
@Override
public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) {
- if (info.preferredPhoneAccountComponent != null &&
+ if (info != null &&
+ info.preferredPhoneAccountComponent != null &&
info.preferredPhoneAccountId != null &&
!info.preferredPhoneAccountId.isEmpty()) {
PhoneAccountHandle contactDefaultHandle = new PhoneAccountHandle(
@@ -1738,10 +1967,20 @@
}
}
+ boolean isPotentialEmergencyNumber;
+ try {
+ isPotentialEmergencyNumber =
+ handle != null && getTelephonyManager().isPotentialEmergencyNumber(
+ handle.getSchemeSpecificPart());
+ } catch (IllegalStateException ise) {
+ isPotentialEmergencyNumber = false;
+ }
+
if (shouldCancelCall) {
Log.w(this, "onCallRedirectionComplete: call is canceled");
endEarly = true;
disconnectReason = "Canceled from Call Redirection Service";
+
// Show UX when user-defined call redirection service does not response; the UX
// is not needed to show if the call is disconnected (e.g. by the user)
if (uiAction.equals(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT)
@@ -1762,8 +2001,7 @@
Log.w(this, "onCallRedirectionComplete: phoneAccountHandle is unavailable");
endEarly = true;
disconnectReason = "Unavailable phoneAccountHandle from Call Redirection Service";
- } else if (mPhoneNumberUtilsAdapter.isPotentialLocalEmergencyNumber(mContext,
- handle.getSchemeSpecificPart())) {
+ } else if (isPotentialEmergencyNumber) {
Log.w(this, "onCallRedirectionComplete: emergency number %s is redirected from Call"
+ " Redirection Service", handle.getSchemeSpecificPart());
endEarly = true;
@@ -1813,31 +2051,107 @@
+ "callId=%s, callRedirectionAppName=%s",
call.getId(), callRedirectionApp);
- Intent confirmIntent = new Intent(mContext,
- CallRedirectionConfirmDialogActivity.class);
- confirmIntent.putExtra(
- CallRedirectionConfirmDialogActivity.EXTRA_REDIRECTION_OUTGOING_CALL_ID,
- call.getId());
- confirmIntent.putExtra(CallRedirectionConfirmDialogActivity.EXTRA_REDIRECTION_APP_NAME,
+ showRedirectionDialog(call.getId(),
mRoleManagerAdapter.getApplicationLabelForPackageName(callRedirectionApp));
- confirmIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- // A small delay to start the activity after any Dialer's In Call UI starts
- mHandler.postDelayed(new Runnable("CM.oCRC", mLock) {
- @Override
- public void loggedRun() {
- mContext.startActivityAsUser(confirmIntent, UserHandle.CURRENT);
- }
- }.prepare(), 500 /* Milliseconds delay */);
-
} else {
call.setTargetPhoneAccount(phoneAccountHandle);
placeOutgoingCall(call, handle, gatewayInfo, speakerphoneOn, videoState);
}
}
+ /**
+ * Shows the call redirection confirmation dialog. This is explicitly done here instead of in
+ * an activity class such as {@link ConfirmCallDialogActivity}. This was originally done with
+ * an activity class, however due to the fact that the InCall UI is being spun up at the same
+ * time as the dialog activity, there is a potential race condition where the InCall UI will
+ * often be shown instead of the dialog. Activity manager chooses not to show the redirection
+ * dialog in that case since the new top activity from dialer is going to show.
+ * By showing the dialog here we're able to set the dialog's window type to
+ * {@link WindowManager.LayoutParams#TYPE_SYSTEM_ALERT} which guarantees it shows above other
+ * content on the screen.
+ * @param callId The ID of the call to show the redirection dialog for.
+ */
+ private void showRedirectionDialog(@NonNull String callId, @NonNull CharSequence appName) {
+ AlertDialog confirmDialog = new AlertDialog.Builder(mContext).create();
+ LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+ View dialogView = layoutInflater.inflate(R.layout.call_redirection_confirm_dialog, null);
+
+ Button buttonFirstLine = (Button) dialogView.findViewById(R.id.buttonFirstLine);
+ buttonFirstLine.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent proceedWithoutRedirectedCall = new Intent(
+ TelecomBroadcastIntentProcessor.ACTION_PLACE_UNREDIRECTED_CALL,
+ null, mContext,
+ TelecomBroadcastReceiver.class);
+ proceedWithoutRedirectedCall.putExtra(
+ TelecomBroadcastIntentProcessor.EXTRA_REDIRECTION_OUTGOING_CALL_ID,
+ callId);
+ mContext.sendBroadcast(proceedWithoutRedirectedCall);
+ confirmDialog.dismiss();
+ }
+ });
+
+ Button buttonSecondLine = (Button) dialogView.findViewById(R.id.buttonSecondLine);
+ buttonSecondLine.setText(mContext.getString(
+ R.string.alert_place_outgoing_call_with_redirection, appName));
+ buttonSecondLine.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent proceedWithRedirectedCall = new Intent(
+ TelecomBroadcastIntentProcessor.ACTION_PLACE_REDIRECTED_CALL, null,
+ mContext,
+ TelecomBroadcastReceiver.class);
+ proceedWithRedirectedCall.putExtra(
+ TelecomBroadcastIntentProcessor.EXTRA_REDIRECTION_OUTGOING_CALL_ID,
+ callId);
+ mContext.sendBroadcast(proceedWithRedirectedCall);
+ confirmDialog.dismiss();
+ }
+ });
+
+ Button buttonThirdLine = (Button) dialogView.findViewById(R.id.buttonThirdLine);
+ buttonThirdLine.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ cancelRedirection(callId);
+ confirmDialog.dismiss();
+ }
+ });
+
+ confirmDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ cancelRedirection(callId);
+ confirmDialog.dismiss();
+ }
+ });
+
+ confirmDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ confirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+
+ confirmDialog.setCancelable(false);
+ confirmDialog.setCanceledOnTouchOutside(false);
+ confirmDialog.setView(dialogView);
+
+ confirmDialog.show();
+ }
+
+ /**
+ * Signals to Telecom that redirection of the call is to be cancelled.
+ */
+ private void cancelRedirection(String callId) {
+ Intent cancelRedirectedCall = new Intent(
+ TelecomBroadcastIntentProcessor.ACTION_CANCEL_REDIRECTED_CALL,
+ null, mContext,
+ TelecomBroadcastReceiver.class);
+ cancelRedirectedCall.putExtra(
+ TelecomBroadcastIntentProcessor.EXTRA_REDIRECTION_OUTGOING_CALL_ID, callId);
+ mContext.sendBroadcastAsUser(cancelRedirectedCall, UserHandle.CURRENT);
+ }
+
public void processRedirectedOutgoingCallAfterUserInteraction(String callId, String action) {
- Log.i(this, "processRedirectedOutgoingCallAfterUserInteraction for Call ID %s", callId);
+ Log.i(this, "processRedirectedOutgoingCallAfterUserInteraction for Call ID %s, action=%s",
+ callId, action);
if (mPendingRedirectedOutgoingCall != null && mPendingRedirectedOutgoingCall.getId()
.equals(callId)) {
if (action.equals(TelecomBroadcastIntentProcessor.ACTION_PLACE_REDIRECTED_CALL)) {
@@ -1897,7 +2211,13 @@
// Auto-enable speakerphone if the originating intent specified to do so, if the call
// is a video call, of if using speaker when docked
- call.setStartWithSpeakerphoneOn(speakerphoneOn || useSpeakerForVideoCall
+ PhoneAccount account = mPhoneAccountRegistrar.getPhoneAccount(
+ call.getTargetPhoneAccount(), call.getInitiatingUser());
+ boolean allowVideo = false;
+ if (account != null) {
+ allowVideo = account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING);
+ }
+ call.setStartWithSpeakerphoneOn(speakerphoneOn || (useSpeakerForVideoCall && allowVideo)
|| (useSpeakerWhenDocked && useSpeakerForDock));
call.setVideoState(videoState);
@@ -1910,7 +2230,9 @@
}
if (call.isEmergencyCall()) {
- new AsyncEmergencyContactNotifier(mContext).execute();
+ Executors.defaultThreadFactory().newThread(() ->
+ BlockedNumberContract.SystemContract.notifyEmergencyContact(mContext))
+ .start();
}
final boolean requireCallCapableAccountByHandle = mContext.getResources().getBoolean(
@@ -1923,7 +2245,11 @@
// If the account has been set, proceed to place the outgoing call.
// Otherwise the connection will be initiated when the account is set by the user.
if (call.isSelfManaged() && !isOutgoingCallPermitted) {
- notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call);
+ if (call.isAdhocConferenceCall()) {
+ notifyCreateConferenceFailed(call.getTargetPhoneAccount(), call);
+ } else {
+ notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call);
+ }
} else {
if (call.isEmergencyCall()) {
// Drop any ongoing self-managed calls to make way for an emergency call.
@@ -1976,6 +2302,102 @@
}
}
+ private void answerCallForAudioProcessing(Call call) {
+ // We don't check whether the call has been added to the internal lists yet -- it's optional
+ // until the call is actually in the AUDIO_PROCESSING state.
+ Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
+ if (activeCall != null && activeCall != call) {
+ Log.w(this, "answerCallForAudioProcessing: another active call already exists. "
+ + "Ignoring request for audio processing and letting the incoming call "
+ + "through.");
+ // The call should already be in the RINGING state, so all we have to do is add the
+ // call to the internal tracker.
+ addCall(call);
+ return;
+ }
+ Log.d(this, "answerCallForAudioProcessing: Incoming call = %s", call);
+ mConnectionSvrFocusMgr.requestFocus(
+ call,
+ new RequestCallback(() -> {
+ synchronized (mLock) {
+ Log.d(this, "answering call %s for audio processing with cs focus", call);
+ call.answerForAudioProcessing();
+ // Skip setting the call state to ANSWERED -- that's only for calls that
+ // were answered by user intervention.
+ mPendingAudioProcessingCall = call;
+ }
+ }));
+
+ }
+
+ /**
+ * Instructs Telecom to bring a call into the AUDIO_PROCESSING state.
+ *
+ * Used by the background audio call screener (also the default dialer) to signal that
+ * they want to manually enter the AUDIO_PROCESSING state. The user will be aware that there is
+ * an ongoing call at this time.
+ *
+ * @param call The call to manipulate
+ */
+ public void enterBackgroundAudioProcessing(Call call, String requestingPackageName) {
+ if (!mCalls.contains(call)) {
+ Log.w(this, "Trying to exit audio processing on an untracked call");
+ return;
+ }
+
+ Call activeCall = getActiveCall();
+ if (activeCall != null && activeCall != call) {
+ Log.w(this, "Ignoring enter audio processing because there's already a call active");
+ return;
+ }
+
+ CharSequence requestingAppName = AppLabelProxy.Util.getAppLabel(
+ mContext.getPackageManager(), requestingPackageName);
+ if (requestingAppName == null) {
+ requestingAppName = requestingPackageName;
+ }
+
+ // We only want this to work on active or ringing calls
+ if (call.getState() == CallState.RINGING) {
+ // After the connection service sets up the call with the other end, it'll set the call
+ // state to AUDIO_PROCESSING
+ answerCallForAudioProcessing(call);
+ call.setAudioProcessingRequestingApp(requestingAppName);
+ } else if (call.getState() == CallState.ACTIVE) {
+ setCallState(call, CallState.AUDIO_PROCESSING,
+ "audio processing set by dialer request");
+ call.setAudioProcessingRequestingApp(requestingAppName);
+ }
+ }
+
+ /**
+ * Instructs Telecom to bring a call out of the AUDIO_PROCESSING state.
+ *
+ * Used by the background audio call screener (also the default dialer) to signal that it's
+ * finished doing its thing and the user should be made aware of the call.
+ *
+ * @param call The call to manipulate
+ * @param shouldRing if true, puts the call into SIMULATED_RINGING. Otherwise, makes the call
+ * active.
+ */
+ public void exitBackgroundAudioProcessing(Call call, boolean shouldRing) {
+ if (!mCalls.contains(call)) {
+ Log.w(this, "Trying to exit audio processing on an untracked call");
+ return;
+ }
+
+ Call activeCall = getActiveCall();
+ if (activeCall != null) {
+ Log.w(this, "Ignoring exit audio processing because there's already a call active");
+ }
+
+ if (shouldRing) {
+ setCallState(call, CallState.SIMULATED_RINGING, "exitBackgroundAudioProcessing");
+ } else {
+ setCallState(call, CallState.ACTIVE, "exitBackgroundAudioProcessing");
+ }
+ }
+
/**
* Instructs Telecom to deflect the specified call. Intended to be invoked by the in-call
* app through {@link InCallAdapter} after Telecom notifies it of an incoming call followed by
@@ -2024,9 +2446,9 @@
* @return {@code true} if the speakerphone should automatically be enabled.
*/
private static boolean isSpeakerEnabledForVideoCalls() {
- return (SystemProperties.getInt(TelephonyProperties.PROPERTY_VIDEOCALL_AUDIO_OUTPUT,
- PhoneConstants.AUDIO_OUTPUT_DEFAULT) ==
- PhoneConstants.AUDIO_OUTPUT_ENABLE_SPEAKER);
+ return TelephonyProperties.videocall_audio_output()
+ .orElse(TelecomManager.AUDIO_OUTPUT_DEFAULT)
+ == TelecomManager.AUDIO_OUTPUT_ENABLE_SPEAKER;
}
/**
@@ -2047,6 +2469,51 @@
}
/**
+ * Instructs Telecom to reject the specified call. Intended to be invoked by the in-call
+ * app through {@link InCallAdapter} after Telecom notifies it of an incoming call followed by
+ * the user opting to reject said call.
+ */
+ @VisibleForTesting
+ public void rejectCall(Call call, @android.telecom.Call.RejectReason int rejectReason) {
+ if (!mCalls.contains(call)) {
+ Log.i(this, "Request to reject a non-existent call %s", call);
+ } else {
+ for (CallsManagerListener listener : mListeners) {
+ listener.onIncomingCallRejected(call, false /* rejectWithMessage */,
+ null /* textMessage */);
+ }
+ call.reject(rejectReason);
+ }
+ }
+
+ /**
+ * Instructs Telecom to transfer the specified call. Intended to be invoked by the in-call
+ * app through {@link InCallAdapter} after the user opts to transfer the said call.
+ */
+ @VisibleForTesting
+ public void transferCall(Call call, Uri number, boolean isConfirmationRequired) {
+ if (!mCalls.contains(call)) {
+ Log.i(this, "transferCall - Request to transfer a non-existent call %s", call);
+ } else {
+ call.transfer(number, isConfirmationRequired);
+ }
+ }
+
+ /**
+ * Instructs Telecom to transfer the specified call to another ongoing call.
+ * Intended to be invoked by the in-call app through {@link InCallAdapter} after the user opts
+ * to transfer the said call (consultative transfer).
+ */
+ @VisibleForTesting
+ public void transferCall(Call call, Call otherCall) {
+ if (!mCalls.contains(call) || !mCalls.contains(otherCall)) {
+ Log.i(this, "transferCall - Non-existent call %s or %s", call, otherCall);
+ } else {
+ call.transfer(otherCall);
+ }
+ }
+
+ /**
* Instructs Telecom to play the specified DTMF tone within the specified call.
*
* @param digit The DTMF digit to play.
@@ -2102,7 +2569,11 @@
Log.w(this, "Unknown call (%s) asked to disconnect", call);
} else {
mLocallyDisconnectingCalls.add(call);
+ int previousState = call.getState();
call.disconnect();
+ for (CallsManagerListener listener : mListeners) {
+ listener.onCallStateChanged(call, previousState, call.getState());
+ }
// Cancel any of the outgoing call futures if they're still around.
if (mPendingCallConfirm != null && !mPendingCallConfirm.isDone()) {
mPendingCallConfirm.complete(null);
@@ -2165,6 +2636,11 @@
if (!mCalls.contains(call)) {
Log.w(this, "Unknown call (%s) asked to be removed from hold", call);
} else {
+ if (getOutgoingCall() != null) {
+ Log.w(this, "There is an outgoing call, so it is unable to unhold this call %s",
+ call);
+ return;
+ }
Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
String activeCallId = null;
if (activeCall != null && !activeCall.isLocallyDisconnecting()) {
@@ -2175,10 +2651,10 @@
Log.addEvent(call, LogUtils.Events.SWAP, "From " + activeCall.getId());
} else {
// This call does not support hold. If it is from a different connection
- // service, then disconnect it, otherwise invoke call.hold() and allow the
- // connection service to handle the situation.
- if (!PhoneAccountHandle.areFromSamePackage(activeCall.getTargetPhoneAccount(),
- call.getTargetPhoneAccount())) {
+ // service or connection manager, then disconnect it, otherwise invoke
+ // call.hold() and allow the connection service or connection manager to handle
+ // the situation.
+ if (!areFromSameSource(activeCall, call)) {
if (!activeCall.isEmergencyCall()) {
activeCall.disconnect("Swap to " + call.getId());
} else {
@@ -2222,25 +2698,32 @@
// then include only that SIM based PhoneAccount and any non-SIM PhoneAccounts, such as SIP.
@VisibleForTesting
public List<PhoneAccountHandle> constructPossiblePhoneAccounts(Uri handle, UserHandle user,
- boolean isVideo) {
+ boolean isVideo, boolean isEmergency) {
+ return constructPossiblePhoneAccounts(handle, user, isVideo, isEmergency, false);
+ }
+
+ public List<PhoneAccountHandle> constructPossiblePhoneAccounts(Uri handle, UserHandle user,
+ boolean isVideo, boolean isEmergency, boolean isConference) {
+
if (handle == null) {
return Collections.emptyList();
}
// If we're specifically looking for video capable accounts, then include that capability,
- // otherwise specify no additional capability constraints.
+ // otherwise specify no additional capability constraints. When handling the emergency call,
+ // it also needs to find the phone accounts excluded by CAPABILITY_EMERGENCY_CALLS_ONLY.
+ int capabilities = isVideo ? PhoneAccount.CAPABILITY_VIDEO_CALLING : 0;
+ capabilities |= isConference ? PhoneAccount.CAPABILITY_ADHOC_CONFERENCE_CALLING : 0;
List<PhoneAccountHandle> allAccounts =
mPhoneAccountRegistrar.getCallCapablePhoneAccounts(handle.getScheme(), false, user,
- isVideo ? PhoneAccount.CAPABILITY_VIDEO_CALLING : 0 /* any */);
- // First check the Radio SIM Technology
- if(mRadioSimVariants == null) {
- TelephonyManager tm = (TelephonyManager) mContext.getSystemService(
- Context.TELEPHONY_SERVICE);
- // Cache Sim Variants
- mRadioSimVariants = tm.getMultiSimConfiguration();
+ capabilities,
+ isEmergency ? 0 : PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY);
+ 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(mRadioSimVariants != TelephonyManager.MultiSimVariants.DSDA) {
+ // should be available if a call is already active on the SIM account.
+ if (mMaxNumberOfSimultaneouslyActiveSims == 1) {
List<PhoneAccountHandle> simAccounts =
mPhoneAccountRegistrar.getSimPhoneAccountsOfCurrentUser();
PhoneAccountHandle ongoingCallAccount = null;
@@ -2260,6 +2743,10 @@
return allAccounts;
}
+ private TelephonyManager getTelephonyManager() {
+ return mContext.getSystemService(TelephonyManager.class);
+ }
+
/**
* Informs listeners (notably {@link CallAudioManager} of a change to the call's external
* property.
@@ -2299,7 +2786,7 @@
/** Called by the in-call UI to change the mute state. */
void mute(boolean shouldMute) {
- if (hasEmergencyCall() && shouldMute) {
+ if (isInEmergencyCall() && shouldMute) {
Log.i(this, "Refusing to turn on mute because we're in an emergency call");
shouldMute = false;
}
@@ -2311,11 +2798,6 @@
* speaker phone.
*/
void setAudioRoute(int route, String bluetoothAddress) {
- if (hasEmergencyRttCall() && route != CallAudioState.ROUTE_SPEAKER) {
- Log.i(this, "In an emergency RTT call. Forcing route to speaker.");
- route = CallAudioState.ROUTE_SPEAKER;
- bluetoothAddress = null;
- }
mCallAudioManager.setAudioRoute(route, bluetoothAddress);
}
@@ -2418,11 +2900,11 @@
activeCall.hold();
return true;
} else if (supportsHold(activeCall)
- && PhoneAccountHandle.areFromSamePackage(activeCall.getTargetPhoneAccount(),
- call.getTargetPhoneAccount())) {
+ && areFromSameSource(activeCall, call)) {
- // Handle the case where the active call and the new call are from the same CS, and
- // the currently active call supports hold but cannot currently be held.
+ // Handle the case where the active call and the new call are from the same CS or
+ // connection manager, and the currently active call supports hold but cannot
+ // currently be held.
// In this case we'll look for the other held call for this connectionService and
// disconnect it prior to holding the active call.
// E.g.
@@ -2444,10 +2926,9 @@
return true;
} else {
// This call does not support hold. If it is from a different connection
- // service, then disconnect it, otherwise allow the connection service to
- // figure out the right states.
- if (!PhoneAccountHandle.areFromSamePackage(activeCall.getTargetPhoneAccount(),
- call.getTargetPhoneAccount())) {
+ // service or connection manager, then disconnect it, otherwise allow the connection
+ // service or connection manager to figure out the right states.
+ if (!areFromSameSource(activeCall, call)) {
Log.i(this, "holdActiveCallForNewCall: disconnecting %s so that %s can be "
+ "made active.", activeCall.getId(), call.getId());
if (!activeCall.isEmergencyCall()) {
@@ -2482,6 +2963,18 @@
CallState.ACTIVE,
"active set explicitly for self-managed")));
} else {
+ if (mPendingAudioProcessingCall == call) {
+ if (mCalls.contains(call)) {
+ setCallState(call, CallState.AUDIO_PROCESSING, "active set explicitly");
+ } else {
+ call.setState(CallState.AUDIO_PROCESSING, "active set explicitly and adding");
+ addCall(call);
+ }
+ // Clear mPendingAudioProcessingCall so that future attempts to mark the call as
+ // active (e.g. coming off of hold) don't put the call into audio processing instead
+ mPendingAudioProcessingCall = null;
+ return;
+ }
setCallState(call, CallState.ACTIVE, "active set explicitly");
maybeMoveToSpeakerPhone(call);
ensureCallAudible();
@@ -2500,8 +2993,21 @@
* @param disconnectCause The disconnect cause, see {@link android.telecom.DisconnectCause}.
*/
void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
+ int oldState = call.getState();
+ if (call.getState() == CallState.SIMULATED_RINGING
+ && disconnectCause.getCode() == DisconnectCause.REMOTE) {
+ // If the remote end hangs up while in SIMULATED_RINGING, the call should
+ // be marked as missed.
+ call.setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.MISSED));
+ }
call.setDisconnectCause(disconnectCause);
setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly");
+
+ if(oldState == CallState.NEW && disconnectCause.getCode() == DisconnectCause.MISSED) {
+ Log.i(this, "markCallAsDisconnected: logging missed call ");
+ mCallLogManager.logCall(call, Calls.MISSED_TYPE, true, null);
+ }
+
}
/**
@@ -2612,10 +3118,16 @@
return getFirstCallWithState(CallState.RINGING, CallState.ANSWERED) != null;
}
+ boolean hasRingingOrSimulatedRingingCall() {
+ return getFirstCallWithState(
+ CallState.SIMULATED_RINGING, CallState.RINGING, CallState.ANSWERED) != null;
+ }
+
@VisibleForTesting
public boolean onMediaButton(int type) {
if (hasAnyCalls()) {
- Call ringingCall = getFirstCallWithState(CallState.RINGING);
+ Call ringingCall = getFirstCallWithState(CallState.RINGING,
+ CallState.SIMULATED_RINGING);
if (HeadsetMediaButton.SHORT_PRESS == type) {
if (ringingCall == null) {
Call activeCall = getFirstCallWithState(CallState.ACTIVE);
@@ -2637,7 +3149,7 @@
return true;
}
} else {
- ringingCall.answer(VideoProfile.STATE_AUDIO_ONLY);
+ answerCall(ringingCall, VideoProfile.STATE_AUDIO_ONLY);
return true;
}
} else if (HeadsetMediaButton.LONG_PRESS == type) {
@@ -2714,8 +3226,9 @@
}
@VisibleForTesting
- public Call getRingingCall() {
- return getFirstCallWithState(CallState.RINGING, CallState.ANSWERED);
+ public Call getRingingOrSimulatedRingingCall() {
+ return getFirstCallWithState(CallState.RINGING,
+ CallState.ANSWERED, CallState.SIMULATED_RINGING);
}
public Call getActiveCall() {
@@ -2830,6 +3343,11 @@
Conference.CONNECT_TIME_NOT_SPECIFIED ? 0 :
parcelableConference.getConnectElapsedTimeMillis();
+ int callDirection = Call.getRemappedCallDirection(parcelableConference.getCallDirection());
+
+ PhoneAccountHandle connectionMgr =
+ mPhoneAccountRegistrar.getSimCallManagerFromHandle(phoneAccount,
+ mCurrentUserHandle);
Call call = new Call(
callId,
mContext,
@@ -2839,14 +3357,15 @@
mPhoneNumberUtilsAdapter,
null /* handle */,
null /* gatewayInfo */,
- null /* connectionManagerPhoneAccount */,
+ connectionMgr,
phoneAccount,
- Call.CALL_DIRECTION_UNDEFINED /* callDirection */,
+ callDirection,
false /* forceAttachToExistingConnection */,
true /* isConference */,
connectTime,
connectElapsedTime,
- mClockProxy);
+ mClockProxy,
+ mToastFactory);
setCallState(call, Call.getStateFromConnectionState(parcelableConference.getState()),
"new conference call");
@@ -2889,6 +3408,14 @@
}
/**
+ * Retrieves the {@link DisconnectedCallNotifier}
+ * @return The {@link DisconnectedCallNotifier}.
+ */
+ DisconnectedCallNotifier getDisconnectedCallNotifier() {
+ return mDisconnectedCallNotifier;
+ }
+
+ /**
* Retrieves the {@link MissedCallNotifier}
* @return The {@link MissedCallNotifier}.
*/
@@ -2945,6 +3472,8 @@
SystemClock.elapsedRealtime());
updateCanAddCall();
+ updateHasActiveRttCall();
+ updateExternalCallCanPullSupport();
// onCallAdded for calls which immediately take the foreground (like the first call).
for (CallsManagerListener listener : mListeners) {
if (LogUtils.SYSTRACE_DEBUG) {
@@ -2958,7 +3487,8 @@
Trace.endSection();
}
- private void removeCall(Call call) {
+ @VisibleForTesting
+ public void removeCall(Call call) {
Trace.beginSection("removeCall");
Log.v(this, "removeCall(%s)", call);
@@ -2974,10 +3504,11 @@
}
call.destroy();
-
+ updateExternalCallCanPullSupport();
// Only broadcast changes for calls that are being tracked.
if (shouldNotify) {
updateCanAddCall();
+ updateHasActiveRttCall();
for (CallsManagerListener listener : mListeners) {
if (LogUtils.SYSTRACE_DEBUG) {
Trace.beginSection(listener.getClass().toString() + " onCallRemoved");
@@ -2991,6 +3522,24 @@
Trace.endSection();
}
+ private void updateHasActiveRttCall() {
+ boolean hasActiveRttCall = hasActiveRttCall();
+ if (hasActiveRttCall != mHasActiveRttCall) {
+ Log.i(this, "updateHasActiveRttCall %s -> %s", mHasActiveRttCall, hasActiveRttCall);
+ AudioManager.setRttEnabled(hasActiveRttCall);
+ mHasActiveRttCall = hasActiveRttCall;
+ }
+ }
+
+ private boolean hasActiveRttCall() {
+ for (Call call : mCalls) {
+ if (call.isActive() && call.isRttCall()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Sets the specified state on the specified call.
*
@@ -3002,7 +3551,8 @@
return;
}
int oldState = call.getState();
- Log.i(this, "setCallState %s -> %s, call: %s", CallState.toString(oldState),
+ Log.i(this, "setCallState %s -> %s, call: %s",
+ CallState.toString(call.getParcelableCallState()),
CallState.toString(newState), call);
if (newState != oldState) {
// If the call switches to held state while a DTMF tone is playing, stop the tone to
@@ -3019,6 +3569,10 @@
// TODO: Define expected state transitions here, and log when an
// unexpected transition occurs.
if (call.setState(newState, tag)) {
+ if ((oldState != CallState.AUDIO_PROCESSING) &&
+ (newState == CallState.DISCONNECTED)) {
+ maybeSendPostCallScreenIntent(call);
+ }
maybeShowErrorDialogOnDisconnect(call);
Trace.beginSection("onCallStateChanged");
@@ -3028,6 +3582,7 @@
// Only broadcast state change for calls that are being tracked.
if (mCalls.contains(call)) {
updateCanAddCall();
+ updateHasActiveRttCall();
for (CallsManagerListener listener : mListeners) {
if (LogUtils.SYSTRACE_DEBUG) {
Trace.beginSection(listener.getClass().toString() +
@@ -3393,101 +3948,236 @@
&& incomingCall.getHandoverSourceCall() == null;
}
- private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) {
- if (hasMaximumLiveCalls(call)) {
- // NOTE: If the amount of live calls changes beyond 1, this logic will probably
- // have to change.
- Call liveCall = getFirstCallWithState(LIVE_CALL_STATES);
- Log.i(this, "makeRoomForOutgoingCall call = " + call + " livecall = " +
- liveCall);
-
- if (call == liveCall) {
- // If the call is already the foreground call, then we are golden.
- // This can happen after the user selects an account in the SELECT_PHONE_ACCOUNT
- // state since the call was already populated into the list.
- return true;
- }
-
- if (hasMaximumOutgoingCalls(call)) {
- Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES);
- if (isEmergency && !outgoingCall.isEmergencyCall()) {
- // Disconnect the current outgoing call if it's not an emergency call. If the
- // user tries to make two outgoing calls to different emergency call numbers,
- // we will try to connect the first outgoing call.
- call.getAnalytics().setCallIsAdditional(true);
- outgoingCall.getAnalytics().setCallIsInterrupted(true);
- outgoingCall.disconnect();
- return true;
+ @VisibleForTesting
+ public boolean makeRoomForOutgoingEmergencyCall(Call emergencyCall) {
+ // Always disconnect any ringing/incoming calls when an emergency call is placed to minimize
+ // distraction. This does not affect live call count.
+ if (hasRingingOrSimulatedRingingCall()) {
+ Call ringingCall = getRingingOrSimulatedRingingCall();
+ ringingCall.getAnalytics().setCallIsAdditional(true);
+ ringingCall.getAnalytics().setCallIsInterrupted(true);
+ if (ringingCall.getState() == CallState.SIMULATED_RINGING) {
+ if (!ringingCall.hasGoneActiveBefore()) {
+ // If this is an incoming call that is currently in SIMULATED_RINGING only
+ // after a call screen, disconnect to make room and mark as missed, since
+ // the user didn't get a chance to accept/reject.
+ ringingCall.disconnect("emergency call dialed during simulated ringing "
+ + "after screen.");
+ } else {
+ // If this is a simulated ringing call after being active and put in
+ // AUDIO_PROCESSING state again, disconnect normally.
+ ringingCall.reject(false, null, "emergency call dialed during simulated "
+ + "ringing.");
}
- if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) {
- // If there is an orphaned call in the {@link CallState#SELECT_PHONE_ACCOUNT}
- // state, just disconnect it since the user has explicitly started a new call.
- call.getAnalytics().setCallIsAdditional(true);
- outgoingCall.getAnalytics().setCallIsInterrupted(true);
- outgoingCall.disconnect();
- return true;
- }
- return false;
+ } else { // normal incoming ringing call.
+ // Hang up the ringing call to make room for the emergency call and mark as missed,
+ // since the user did not reject.
+ ringingCall.setOverrideDisconnectCauseCode(
+ new DisconnectCause(DisconnectCause.MISSED));
+ ringingCall.reject(false, null, "emergency call dialed during ringing.");
}
+ }
- // If we have the max number of held managed calls and we're placing an emergency call,
- // we'll disconnect the ongoing call if it cannot be held.
- if (hasMaximumManagedHoldingCalls(call) && isEmergency && !canHold(liveCall)) {
- call.getAnalytics().setCallIsAdditional(true);
- liveCall.getAnalytics().setCallIsInterrupted(true);
- liveCall.disconnect("disconnecting to make room for emergency call "
- + call.getId());
+ // There is already room!
+ if (!hasMaximumLiveCalls(emergencyCall)) return true;
+
+ Call liveCall = getFirstCallWithState(LIVE_CALL_STATES);
+ Log.i(this, "makeRoomForOutgoingEmergencyCall call = " + emergencyCall
+ + " livecall = " + liveCall);
+
+ if (emergencyCall == liveCall) {
+ // Not likely, but a good sanity check.
+ return true;
+ }
+
+ if (hasMaximumOutgoingCalls(emergencyCall)) {
+ Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES);
+ if (!outgoingCall.isEmergencyCall()) {
+ emergencyCall.getAnalytics().setCallIsAdditional(true);
+ outgoingCall.getAnalytics().setCallIsInterrupted(true);
+ outgoingCall.disconnect("Disconnecting dialing call in favor of new dialing"
+ + " emergency call.");
return true;
}
-
- // TODO: Remove once b/23035408 has been corrected.
- // If the live call is a conference, it will not have a target phone account set. This
- // means the check to see if the live call has the same target phone account as the new
- // call will not cause us to bail early. As a result, we'll end up holding the
- // ongoing conference call. However, the ConnectionService is already doing that. This
- // has caused problems with some carriers. As a workaround until b/23035408 is
- // corrected, we will try and get the target phone account for one of the conference's
- // children and use that instead.
- PhoneAccountHandle liveCallPhoneAccount = liveCall.getTargetPhoneAccount();
- if (liveCallPhoneAccount == null && liveCall.isConference() &&
- !liveCall.getChildCalls().isEmpty()) {
- liveCallPhoneAccount = getFirstChildPhoneAccount(liveCall);
- Log.i(this, "makeRoomForOutgoingCall: using child call PhoneAccount = " +
- liveCallPhoneAccount);
- }
-
- // First thing, if we are trying to make a call with the same phone account as the live
- // call, then allow it so that the connection service can make its own decision about
- // how to handle the new call relative to the current one.
- if (PhoneAccountHandle.areFromSamePackage(liveCallPhoneAccount,
- call.getTargetPhoneAccount())) {
- Log.i(this, "makeRoomForOutgoingCall: phoneAccount matches.");
- call.getAnalytics().setCallIsAdditional(true);
- liveCall.getAnalytics().setCallIsInterrupted(true);
- return true;
- } else if (call.getTargetPhoneAccount() == null) {
- // Without a phone account, we can't say reliably that the call will fail.
- // If the user chooses the same phone account as the live call, then it's
- // still possible that the call can be made (like with CDMA calls not supporting
- // hold but they still support adding a call by going immediately into conference
- // mode). Return true here and we'll run this code again after user chooses an
- // account.
+ if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) {
+ // Sanity check: if there is an orphaned emergency call in the
+ // {@link CallState#SELECT_PHONE_ACCOUNT} state, just disconnect it since the user
+ // has explicitly started a new call.
+ emergencyCall.getAnalytics().setCallIsAdditional(true);
+ outgoingCall.getAnalytics().setCallIsInterrupted(true);
+ outgoingCall.disconnect("Disconnecting call in SELECT_PHONE_ACCOUNT in favor"
+ + " of new outgoing call.");
return true;
}
-
- // Try to hold the live call before attempting the new outgoing call.
- if (canHold(liveCall)) {
- Log.i(this, "makeRoomForOutgoingCall: holding live call.");
- call.getAnalytics().setCallIsAdditional(true);
- liveCall.getAnalytics().setCallIsInterrupted(true);
- liveCall.hold("calling " + call.getId());
- return true;
- }
-
- // The live call cannot be held so we're out of luck here. There's no room.
+ // If the user tries to make two outgoing calls to different emergency call numbers,
+ // we will try to connect the first outgoing call and reject the second.
return false;
}
- return true;
+
+ if (liveCall.getState() == CallState.AUDIO_PROCESSING) {
+ emergencyCall.getAnalytics().setCallIsAdditional(true);
+ liveCall.getAnalytics().setCallIsInterrupted(true);
+ liveCall.disconnect("disconnecting audio processing call for emergency");
+ return true;
+ }
+
+ // If we have the max number of held managed calls and we're placing an emergency call,
+ // we'll disconnect the ongoing call if it cannot be held.
+ if (hasMaximumManagedHoldingCalls(emergencyCall) && !canHold(liveCall)) {
+ emergencyCall.getAnalytics().setCallIsAdditional(true);
+ liveCall.getAnalytics().setCallIsInterrupted(true);
+ // Disconnect the active call instead of the holding call because it is historically
+ // easier to do, rather than disconnect a held call.
+ liveCall.disconnect("disconnecting to make room for emergency call "
+ + emergencyCall.getId());
+ return true;
+ }
+
+ // TODO: Remove once b/23035408 has been corrected.
+ // If the live call is a conference, it will not have a target phone account set. This
+ // means the check to see if the live call has the same target phone account as the new
+ // call will not cause us to bail early. As a result, we'll end up holding the
+ // ongoing conference call. However, the ConnectionService is already doing that. This
+ // has caused problems with some carriers. As a workaround until b/23035408 is
+ // corrected, we will try and get the target phone account for one of the conference's
+ // children and use that instead.
+ PhoneAccountHandle liveCallPhoneAccount = liveCall.getTargetPhoneAccount();
+ if (liveCallPhoneAccount == null && liveCall.isConference() &&
+ !liveCall.getChildCalls().isEmpty()) {
+ liveCallPhoneAccount = getFirstChildPhoneAccount(liveCall);
+ Log.i(this, "makeRoomForOutgoingEmergencyCall: using child call PhoneAccount = " +
+ liveCallPhoneAccount);
+ }
+
+ // We may not know which PhoneAccount the emergency call will be placed on yet, but if
+ // the liveCall PhoneAccount does not support placing emergency calls, then we know it
+ // will not be that one and we do not want multiple PhoneAccounts active during an
+ // emergency call if possible. Disconnect the active call in favor of the emergency call
+ // instead of trying to hold.
+ if (liveCall.getTargetPhoneAccount() != null) {
+ PhoneAccount pa = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
+ liveCall.getTargetPhoneAccount());
+ if((pa.getCapabilities() & PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) == 0) {
+ liveCall.setOverrideDisconnectCauseCode(new DisconnectCause(
+ DisconnectCause.LOCAL, DisconnectCause.REASON_EMERGENCY_CALL_PLACED));
+ liveCall.disconnect("outgoing call does not support emergency calls, "
+ + "disconnecting.");
+ }
+ return true;
+ }
+
+ // First thing, if we are trying to make an emergency call with the same package name as
+ // the live call, then allow it so that the connection service can make its own decision
+ // about how to handle the new call relative to the current one.
+ // By default, for telephony, it will try to hold the existing call before placing the new
+ // emergency call except for if the carrier does not support holding calls for emergency.
+ // In this case, telephony will disconnect the call.
+ if (PhoneAccountHandle.areFromSamePackage(liveCallPhoneAccount,
+ emergencyCall.getTargetPhoneAccount())) {
+ Log.i(this, "makeRoomForOutgoingEmergencyCall: phoneAccount matches.");
+ emergencyCall.getAnalytics().setCallIsAdditional(true);
+ liveCall.getAnalytics().setCallIsInterrupted(true);
+ return true;
+ } else if (emergencyCall.getTargetPhoneAccount() == null) {
+ // Without a phone account, we can't say reliably that the call will fail.
+ // If the user chooses the same phone account as the live call, then it's
+ // still possible that the call can be made (like with CDMA calls not supporting
+ // hold but they still support adding a call by going immediately into conference
+ // mode). Return true here and we'll run this code again after user chooses an
+ // account.
+ return true;
+ }
+
+ // Hold the live call if possible before attempting the new outgoing emergency call.
+ if (canHold(liveCall)) {
+ Log.i(this, "makeRoomForOutgoingEmergencyCall: holding live call.");
+ emergencyCall.getAnalytics().setCallIsAdditional(true);
+ liveCall.getAnalytics().setCallIsInterrupted(true);
+ liveCall.hold("calling " + emergencyCall.getId());
+ return true;
+ }
+
+ // The live call cannot be held so we're out of luck here. There's no room.
+ return false;
+ }
+
+ private boolean makeRoomForOutgoingCall(Call call) {
+ // Already room!
+ if (!hasMaximumLiveCalls(call)) return true;
+
+ // NOTE: If the amount of live calls changes beyond 1, this logic will probably
+ // have to change.
+ Call liveCall = getFirstCallWithState(LIVE_CALL_STATES);
+ Log.i(this, "makeRoomForOutgoingCall call = " + call + " livecall = " +
+ liveCall);
+
+ if (call == liveCall) {
+ // If the call is already the foreground call, then we are golden.
+ // This can happen after the user selects an account in the SELECT_PHONE_ACCOUNT
+ // state since the call was already populated into the list.
+ return true;
+ }
+
+ if (hasMaximumOutgoingCalls(call)) {
+ Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES);
+ if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) {
+ // If there is an orphaned call in the {@link CallState#SELECT_PHONE_ACCOUNT}
+ // state, just disconnect it since the user has explicitly started a new call.
+ call.getAnalytics().setCallIsAdditional(true);
+ outgoingCall.getAnalytics().setCallIsInterrupted(true);
+ outgoingCall.disconnect("Disconnecting call in SELECT_PHONE_ACCOUNT in favor"
+ + " of new outgoing call.");
+ return true;
+ }
+ return false;
+ }
+
+ // TODO: Remove once b/23035408 has been corrected.
+ // If the live call is a conference, it will not have a target phone account set. This
+ // means the check to see if the live call has the same target phone account as the new
+ // call will not cause us to bail early. As a result, we'll end up holding the
+ // ongoing conference call. However, the ConnectionService is already doing that. This
+ // has caused problems with some carriers. As a workaround until b/23035408 is
+ // corrected, we will try and get the target phone account for one of the conference's
+ // children and use that instead.
+ PhoneAccountHandle liveCallPhoneAccount = liveCall.getTargetPhoneAccount();
+ if (liveCallPhoneAccount == null && liveCall.isConference() &&
+ !liveCall.getChildCalls().isEmpty()) {
+ liveCallPhoneAccount = getFirstChildPhoneAccount(liveCall);
+ Log.i(this, "makeRoomForOutgoingCall: using child call PhoneAccount = " +
+ liveCallPhoneAccount);
+ }
+
+ // First thing, if we are trying to make a call with the same phone account as the live
+ // call, then allow it so that the connection service can make its own decision about
+ // how to handle the new call relative to the current one.
+ if (PhoneAccountHandle.areFromSamePackage(liveCallPhoneAccount,
+ call.getTargetPhoneAccount())) {
+ Log.i(this, "makeRoomForOutgoingCall: phoneAccount matches.");
+ call.getAnalytics().setCallIsAdditional(true);
+ liveCall.getAnalytics().setCallIsInterrupted(true);
+ return true;
+ } else if (call.getTargetPhoneAccount() == null) {
+ // Without a phone account, we can't say reliably that the call will fail.
+ // If the user chooses the same phone account as the live call, then it's
+ // still possible that the call can be made (like with CDMA calls not supporting
+ // hold but they still support adding a call by going immediately into conference
+ // mode). Return true here and we'll run this code again after user chooses an
+ // account.
+ return true;
+ }
+
+ // Try to hold the live call before attempting the new outgoing call.
+ if (canHold(liveCall)) {
+ Log.i(this, "makeRoomForOutgoingCall: holding live call.");
+ call.getAnalytics().setCallIsAdditional(true);
+ liveCall.getAnalytics().setCallIsInterrupted(true);
+ liveCall.hold("calling " + call.getId());
+ return true;
+ }
+
+ // The live call cannot be held so we're out of luck here. There's no room.
+ return false;
}
/**
@@ -3553,6 +4243,10 @@
Call createCallForExistingConnection(String callId, ParcelableConnection connection) {
boolean isDowngradedConference = (connection.getConnectionProperties()
& Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE) != 0;
+
+ PhoneAccountHandle connectionMgr =
+ mPhoneAccountRegistrar.getSimCallManagerFromHandle(connection.getPhoneAccount(),
+ mCurrentUserHandle);
Call call = new Call(
callId,
mContext,
@@ -3562,14 +4256,15 @@
mPhoneNumberUtilsAdapter,
connection.getHandle() /* handle */,
null /* gatewayInfo */,
- null /* connectionManagerPhoneAccount */,
+ connectionMgr,
connection.getPhoneAccount(), /* targetPhoneAccountHandle */
Call.getRemappedCallDirection(connection.getCallDirection()) /* callDirection */,
false /* forceAttachToExistingConnection */,
isDowngradedConference /* isConference */,
connection.getConnectTimeMillis() /* connectTimeMillis */,
connection.getConnectElapsedTimeMillis(), /* connectElapsedTimeMillis */
- mClockProxy);
+ mClockProxy,
+ mToastFactory);
call.initAnalytics();
call.getAnalytics().setCreatedFromExistingConnection(true);
@@ -3583,13 +4278,8 @@
call.setCallerDisplayName(connection.getCallerDisplayName(),
connection.getCallerDisplayNamePresentation());
call.addListener(this);
+ call.putExtras(Call.SOURCE_CONNECTION_SERVICE, connection.getExtras());
- // In case this connection was added via a ConnectionManager, keep track of the original
- // Connection ID as created by the originating ConnectionService.
- Bundle extras = connection.getExtras();
- if (extras != null && extras.containsKey(Connection.EXTRA_ORIGINAL_CONNECTION_ID)) {
- call.setOriginalConnectionId(extras.getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID));
- }
Log.i(this, "createCallForExistingConnection: %s", connection);
Call parentCall = null;
if (!TextUtils.isEmpty(connection.getParentCallId())) {
@@ -3719,13 +4409,13 @@
if (phoneAccount == null) {
return false;
}
+ if (isInEmergencyCall()) return false;
if (!phoneAccount.isSelfManaged()) {
return !hasMaximumManagedRingingCalls(excludeCall) &&
!hasMaximumManagedHoldingCalls(excludeCall);
} else {
- return !hasEmergencyCall() &&
- !hasMaximumSelfManagedRingingCalls(excludeCall, phoneAccountHandle) &&
+ return !hasMaximumSelfManagedRingingCalls(excludeCall, phoneAccountHandle) &&
!hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle);
}
}
@@ -3756,7 +4446,7 @@
// 2. The outgoing call is an handover call or it not hit the self-managed call limit
// and the current active call can be held.
Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
- return !hasEmergencyCall() &&
+ return !isInEmergencyCall() &&
((excludeCall != null && excludeCall.getHandoverSourceCall() != null) ||
(!hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle) &&
(activeCall == null || canHold(activeCall))));
@@ -3915,6 +4605,18 @@
pw.println(mPendingCall.getId());
}
+ if (mPendingRedirectedOutgoingCallInfo.size() > 0) {
+ pw.print("mPendingRedirectedOutgoingCallInfo:");
+ pw.println(mPendingRedirectedOutgoingCallInfo.keySet().stream().collect(
+ Collectors.joining(", ")));
+ }
+
+ if (mPendingUnredirectedOutgoingCallInfo.size() > 0) {
+ pw.print("mPendingUnredirectedOutgoingCallInfo:");
+ pw.println(mPendingUnredirectedOutgoingCallInfo.keySet().stream().collect(
+ Collectors.joining(", ")));
+ }
+
if (mCallAudioManager != null) {
pw.println("mCallAudioManager:");
pw.increaseIndent();
@@ -4043,6 +4745,29 @@
/**
* Notifies the {@link android.telecom.ConnectionService} associated with a
+ * {@link PhoneAccountHandle} that the attempt to create a new connection has failed.
+ *
+ * @param phoneAccountHandle The {@link PhoneAccountHandle}.
+ * @param call The {@link Call} which could not be added.
+ */
+ private void notifyCreateConferenceFailed(PhoneAccountHandle phoneAccountHandle, Call call) {
+ if (phoneAccountHandle == null) {
+ return;
+ }
+ ConnectionServiceWrapper service = mConnectionServiceRepository.getService(
+ phoneAccountHandle.getComponentName(), phoneAccountHandle.getUserHandle());
+ if (service == null) {
+ Log.i(this, "Found no connection service.");
+ return;
+ } else {
+ call.setConnectionService(service);
+ service.createConferenceFailed(call);
+ }
+ }
+
+
+ /**
+ * Notifies the {@link android.telecom.ConnectionService} associated with a
* {@link PhoneAccountHandle} that the attempt to handover a call has failed.
*
* @param call The handover call
@@ -4077,110 +4802,6 @@
Log.addEvent(handoverFromCall, LogUtils.Events.HANDOVER_REQUEST, "legacy request denied");
}
- public Call createHandoverCall(Uri handle, PhoneAccountHandle handoverToHandle,
- Bundle extras, UserHandle initiatingUser) {
- boolean isReusedCall = true;
- Call call = reuseOutgoingCall(handle);
-
- PhoneAccount account =
- mPhoneAccountRegistrar.getPhoneAccount(handoverToHandle, initiatingUser);
- boolean isSelfManaged = account != null && account.isSelfManaged();
-
- // Create a call with original handle. The handle may be changed when the call is attached
- // to a connection service, but in most cases will remain the same.
- if (call == null) {
- call = new Call(getNextCallId(), mContext,
- this,
- mLock,
- mConnectionServiceRepository,
- mPhoneNumberUtilsAdapter,
- handle,
- null /* gatewayInfo */,
- null /* connectionManagerPhoneAccount */,
- null /* handoverToHandle */,
- Call.CALL_DIRECTION_OUTGOING /* callDirection */,
- false /* forceAttachToExistingConnection */,
- false, /* isConference */
- mClockProxy);
- call.initAnalytics(null);
-
- // 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,
- // however doing this now ensures the logs and any other logic will treat this call as
- // self-managed from the moment it is created.
- call.setIsSelfManaged(isSelfManaged);
- if (isSelfManaged) {
- // Self-managed calls will ALWAYS use voip audio mode.
- call.setIsVoipAudioMode(true);
- }
- call.setInitiatingUser(initiatingUser);
- isReusedCall = false;
- }
-
- int videoState = VideoProfile.STATE_AUDIO_ONLY;
- if (extras != null) {
- // Set the video state on the call early so that when it is added to the InCall UI the
- // UI knows to configure itself as a video call immediately.
- videoState = extras.getInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
- VideoProfile.STATE_AUDIO_ONLY);
-
- // If this is an emergency video call, we need to check if the phone account supports
- // emergency video calling.
- // Also, ensure we don't try to place an outgoing call with video if video is not
- // supported.
- if (VideoProfile.isVideo(videoState)) {
- if (call.isEmergencyCall() && account != null &&
- !account.hasCapabilities(PhoneAccount.CAPABILITY_EMERGENCY_VIDEO_CALLING)) {
- // Phone account doesn't support emergency video calling, so fallback to
- // audio-only now to prevent the InCall UI from setting up video surfaces
- // needlessly.
- Log.i(this, "startOutgoingCall - emergency video calls not supported; " +
- "falling back to audio-only");
- videoState = VideoProfile.STATE_AUDIO_ONLY;
- } else if (account != null &&
- !account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING)) {
- // Phone account doesn't support video calling, so fallback to audio-only.
- Log.i(this, "startOutgoingCall - video calls not supported; fallback to " +
- "audio-only.");
- videoState = VideoProfile.STATE_AUDIO_ONLY;
- }
- }
-
- call.setVideoState(videoState);
- }
-
- call.setTargetPhoneAccount(handoverToHandle);
-
- // If there's no more room for a handover, just fail.
- if ((!isReusedCall && !makeRoomForOutgoingCall(call, call.isEmergencyCall()))) {
- return null;
- }
-
- PhoneAccount accountToUse =
- mPhoneAccountRegistrar.getPhoneAccount(handoverToHandle, initiatingUser);
- if (accountToUse != null && accountToUse.getExtras() != null) {
- if (accountToUse.getExtras()
- .getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
- Log.d(this, "startOutgoingCall: defaulting to voip mode for call %s",
- call.getId());
- call.setIsVoipAudioMode(true);
- }
- }
-
- call.setState(
- CallState.CONNECTING,
- handoverToHandle == null ? "no-handle" : handoverToHandle.toString());
-
- setIntentExtrasAndStartTime(call, extras);
-
- if (!mCalls.contains(call)) {
- // We check if mCalls already contains the call because we could potentially be reusing
- // a call which was previously added (See {@link #reuseOutgoingCall}).
- addCall(call);
- }
-
- return call;
- }
/**
* Called in response to a {@link Call} receiving a {@link Call#handoverTo(PhoneAccountHandle,
* int, Bundle)} indicating the {@link android.telecom.InCallService} has requested a
@@ -4198,7 +4819,7 @@
int videoState, Bundle extras) {
// Send an error back if there are any ongoing emergency calls.
- if (hasEmergencyCall()) {
+ if (isInEmergencyCall()) {
handoverFromCall.onHandoverFailed(
android.telecom.Call.Callback.HANDOVER_FAILURE_ONGOING_EMERGENCY_CALL);
return;
@@ -4227,7 +4848,7 @@
handoverFromCall.getHandle(), null,
null, null,
Call.CALL_DIRECTION_OUTGOING, false,
- false, mClockProxy);
+ false, mClockProxy, mToastFactory);
call.initAnalytics();
// Set self-managed and voipAudioMode if destination is self-managed CS
@@ -4431,12 +5052,13 @@
Call.CALL_DIRECTION_INCOMING /* callDirection */,
false /* forceAttachToExistingConnection */,
false, /* isConference */
- mClockProxy);
+ mClockProxy,
+ mToastFactory);
if (fromCall == null || isHandoverInProgress() ||
!isHandoverFromPhoneAccountSupported(fromCall.getTargetPhoneAccount()) ||
!isHandoverToPhoneAccountSupported(destAcct) ||
- hasEmergencyCall()) {
+ isInEmergencyCall()) {
Log.w(this, "acceptHandover: Handover not supported");
notifyHandoverFailed(call,
android.telecom.Call.Callback.HANDOVER_FAILURE_NOT_SUPPORTED);
@@ -4513,8 +5135,10 @@
@Override
public void performAction() {
- Log.d(this, "perform set call state for %s, state = %s", mCall, mState);
- setCallState(mCall, mState, mTag);
+ synchronized (mLock) {
+ Log.d(this, "perform set call state for %s, state = %s", mCall, mState);
+ setCallState(mCall, mState, mTag);
+ }
}
}
@@ -4529,8 +5153,10 @@
@Override
public void performAction() {
- Log.d(this, "perform unhold call for %s", mCall);
- mCall.unhold("held " + mPreviouslyHeldCallId);
+ synchronized (mLock) {
+ Log.d(this, "perform unhold call for %s", mCall);
+ mCall.unhold("held " + mPreviouslyHeldCallId);
+ }
}
}
@@ -4545,19 +5171,32 @@
@Override
public void performAction() {
- Log.d(this, "perform answer call for %s, videoState = %d", mCall, mVideoState);
- for (CallsManagerListener listener : mListeners) {
- listener.onIncomingCallAnswered(mCall);
- }
+ synchronized (mLock) {
+ Log.d(this, "perform answer call for %s, videoState = %d", mCall, mVideoState);
+ for (CallsManagerListener listener : mListeners) {
+ listener.onIncomingCallAnswered(mCall);
+ }
- // We do not update the UI until we get confirmation of the answer() through
- // {@link #markCallAsActive}.
- mCall.answer(mVideoState);
- if (mCall.getState() == CallState.RINGING) {
- setCallState(mCall, CallState.ANSWERED, "answered");
- }
- if (isSpeakerphoneAutoEnabledForVideoCalls(mVideoState)) {
- mCall.setStartWithSpeakerphoneOn(true);
+ // We do not update the UI until we get confirmation of the answer() through
+ // {@link #markCallAsActive}.
+ if (mCall.getState() == CallState.RINGING) {
+ mCall.answer(mVideoState);
+ setCallState(mCall, CallState.ANSWERED, "answered");
+ } else if (mCall.getState() == CallState.SIMULATED_RINGING) {
+ // If the call's in simulated ringing, we don't have to wait for the CS --
+ // we can just declare it active.
+ setCallState(mCall, CallState.ACTIVE, "answering simulated ringing");
+ Log.addEvent(mCall, LogUtils.Events.REQUEST_SIMULATED_ACCEPT);
+ } else if (mCall.getState() == CallState.ANSWERED) {
+ // In certain circumstances, the connection service can lose track of a request
+ // to answer a call. Therefore, if the user presses answer again, still send it
+ // on down, but log a warning in the process and don't change the call state.
+ mCall.answer(mVideoState);
+ Log.w(this, "Duplicate answer request for call %s", mCall.getId());
+ }
+ if (isSpeakerphoneAutoEnabledForVideoCalls(mVideoState)) {
+ mCall.setStartWithSpeakerphoneOn(true);
+ }
}
}
}
@@ -4589,14 +5228,30 @@
}
}
+ public Context getContext() {
+ return mContext;
+ }
+
/**
* Determines if there is an ongoing emergency call. This can be either an outgoing emergency
* call, or a number which has been identified by the number as an emergency call.
* @return {@code true} if there is an ongoing emergency call, {@code false} otherwise.
*/
public boolean isInEmergencyCall() {
- return mCalls.stream().filter(c -> c.isEmergencyCall()
- || c.isNetworkIdentifiedEmergencyCall()).count() > 0;
+ return mCalls.stream().filter(c -> (c.isEmergencyCall()
+ || c.isNetworkIdentifiedEmergencyCall()) && !c.isDisconnected()).count() > 0;
+ }
+
+ /**
+ * Trigger a recalculation of support for CAPABILITY_CAN_PULL_CALL for external calls due to
+ * a possible emergency call being added/removed.
+ */
+ private void updateExternalCallCanPullSupport() {
+ boolean isInEmergencyCall = isInEmergencyCall();
+ // Remove the capability to pull an external call in the case that we are in an emergency
+ // call.
+ mCalls.stream().filter(Call::isExternalCall).forEach(
+ c->c.setIsPullExternalCallSupported(!isInEmergencyCall));
}
/**
@@ -4629,4 +5284,58 @@
.filter(c -> phoneAccount.getAccountHandle().equals(c.getTargetPhoneAccount()))
.forEach(c -> c.setVideoCallingSupportedByPhoneAccount(isVideoNowSupported));
}
+
+ /**
+ * Determines if two {@link Call} instances originated from either the same target
+ * {@link PhoneAccountHandle} or connection manager {@link PhoneAccountHandle}.
+ * @param call1 The first call
+ * @param call2 The second call
+ * @return {@code true} if both calls are from the same target or connection manager
+ * {@link PhoneAccountHandle}.
+ */
+ public static boolean areFromSameSource(@NonNull Call call1, @NonNull Call call2) {
+ PhoneAccountHandle call1ConnectionMgr = call1.getConnectionManagerPhoneAccount();
+ PhoneAccountHandle call2ConnectionMgr = call2.getConnectionManagerPhoneAccount();
+
+ if (call1ConnectionMgr != null && call2ConnectionMgr != null
+ && PhoneAccountHandle.areFromSamePackage(call1ConnectionMgr, call2ConnectionMgr)) {
+ // Both calls share the same connection manager package, so they are from the same
+ // source.
+ return true;
+ }
+
+ PhoneAccountHandle call1TargetAcct = call1.getTargetPhoneAccount();
+ PhoneAccountHandle call2TargetAcct = call2.getTargetPhoneAccount();
+ // Otherwise if the target phone account for both is the same package, they're the same
+ // source.
+ return PhoneAccountHandle.areFromSamePackage(call1TargetAcct, call2TargetAcct);
+ }
+
+ public LinkedList<HandlerThread> getGraphHandlerThreads() {
+ return mGraphHandlerThreads;
+ }
+
+ private void maybeSendPostCallScreenIntent(Call call) {
+ if (call.isEmergencyCall() || (call.isNetworkIdentifiedEmergencyCall()) ||
+ (call.getPostCallPackageName() == null)) {
+ return;
+ }
+
+ Intent intent = new Intent(ACTION_POST_CALL);
+ intent.setPackage(call.getPostCallPackageName());
+ intent.putExtra(EXTRA_HANDLE, call.getHandle());
+ intent.putExtra(EXTRA_DISCONNECT_CAUSE, call.getDisconnectCause().getCode());
+ long duration = call.getAgeMillis();
+ int durationCode = DURATION_VERY_SHORT;
+ if ((duration >= VERY_SHORT_CALL_TIME_MS) && (duration < SHORT_CALL_TIME_MS)) {
+ durationCode = DURATION_SHORT;
+ } else if ((duration >= SHORT_CALL_TIME_MS) && (duration < MEDIUM_CALL_TIME_MS)) {
+ durationCode = DURATION_MEDIUM;
+ } else if (duration >= MEDIUM_CALL_TIME_MS) {
+ durationCode = DURATION_LONG;
+ }
+ intent.putExtra(EXTRA_CALL_DURATION, durationCode);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivityAsUser(intent, mCurrentUserHandle);
+ }
}
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index 4fa2ee5..e0d2831 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -100,4 +100,8 @@
@Override
public void onConferenceStateChanged(Call call, boolean isConference) {
}
+
+ @Override
+ public void onCdmaConferenceSwap(Call call) {
+ }
}
diff --git a/src/com/android/server/telecom/CarModeTracker.java b/src/com/android/server/telecom/CarModeTracker.java
new file mode 100644
index 0000000..0ec4917
--- /dev/null
+++ b/src/com/android/server/telecom/CarModeTracker.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2019 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.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.UiModeManager;
+import android.telecom.Log;
+import android.util.LocalLog;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.PriorityQueue;
+import java.util.stream.Collectors;
+
+/**
+ * Tracks the package names of apps which enter end exit car mode.
+ */
+public class CarModeTracker {
+ /**
+ * Data class holding information about apps which have requested to enter car mode.
+ */
+ private class CarModeApp {
+ private @IntRange(from = 0) int mPriority;
+ private @NonNull String mPackageName;
+
+ public CarModeApp(int priority, @NonNull String packageName) {
+ mPriority = priority;
+ mPackageName = Objects.requireNonNull(packageName);
+ }
+
+ /**
+ * The priority at which the app requested to enter car mode.
+ * Will be the same as the one specified when {@link UiModeManager#enableCarMode(int, int)}
+ * was called, or {@link UiModeManager#DEFAULT_PRIORITY} if no priority was specifeid.
+ * @return The priority.
+ */
+ public int getPriority() {
+ return mPriority;
+ }
+
+ public void setPriority(int priority) {
+ mPriority = priority;
+ }
+
+ /**
+ * @return The package name of the app which requested to enter car mode.
+ */
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public void setPackageName(String packageName) {
+ mPackageName = packageName;
+ }
+ }
+
+ /**
+ * Comparator used to maintain the car mode priority queue ordering.
+ */
+ private class CarModeAppComparator implements Comparator<CarModeApp> {
+ @Override
+ public int compare(CarModeApp o1, CarModeApp o2) {
+ // highest priority takes precedence.
+ return Integer.compare(o2.getPriority(), o1.getPriority());
+ }
+ }
+
+ /**
+ * Priority list of apps which have entered or exited car mode, ordered with the highest
+ * priority app at the top of the queue. Where items have the same priority, they are ordered
+ * by insertion time.
+ */
+ private PriorityQueue<CarModeApp> mCarModeApps = new PriorityQueue<>(2,
+ new CarModeAppComparator());
+
+ private final LocalLog mCarModeChangeLog = new LocalLog(20);
+
+ /**
+ * Handles a request to enter car mode by a package name.
+ * @param priority The priority at which car mode is entered.
+ * @param packageName The package name of the app entering car mode.
+ */
+ public void handleEnterCarMode(@IntRange(from = 0) int priority, @NonNull String packageName) {
+ if (mCarModeApps.stream().anyMatch(c -> c.getPriority() == priority)) {
+ Log.w(this, "handleEnterCarMode: already in car mode at priority %d (apps: %s)",
+ priority, getCarModePriorityString());
+ return;
+ }
+
+ if (mCarModeApps.stream().anyMatch(c -> c.getPackageName().equals(packageName))) {
+ Log.w(this, "handleEnterCarMode: %s is already in car mode (apps: %s)",
+ packageName, getCarModePriorityString());
+ return;
+ }
+
+ Log.i(this, "handleEnterCarMode: packageName=%s, priority=%d", packageName, priority);
+ mCarModeChangeLog.log("enterCarMode: packageName=" + packageName + ", priority="
+ + priority);
+ mCarModeApps.add(new CarModeApp(priority, packageName));
+ }
+
+ /**
+ * Handles a request to exist car mode at a priority level.
+ * @param priority The priority level.
+ * @param packageName The packagename of the app requesting the change.
+ */
+ public void handleExitCarMode(@IntRange(from = 0) int priority, @NonNull String packageName) {
+ if (!mCarModeApps.stream().anyMatch(c -> c.getPriority() == priority)) {
+ Log.w(this, "handleExitCarMode: not in car mode at priority %d (apps=%s)",
+ priority, getCarModePriorityString());
+ return;
+ }
+
+ if (priority != UiModeManager.DEFAULT_PRIORITY && !mCarModeApps.stream().anyMatch(
+ c -> c.getPackageName().equals(packageName) && c.getPriority() == priority)) {
+ Log.w(this, "handleExitCarMode: %s didn't enter car mode at priority %d (apps=%s)",
+ packageName, priority, getCarModePriorityString());
+ return;
+ }
+
+ Log.i(this, "handleExitCarMode: packageName=%s, priority=%d", packageName, priority);
+ mCarModeChangeLog.log("exitCarMode: packageName=" + packageName + ", priority="
+ + priority);
+ mCarModeApps.removeIf(c -> c.getPriority() == priority);
+ }
+
+ /**
+ * Retrieves a list of the apps which are currently in car mode, ordered by priority such that
+ * the highest priority app is first.
+ * @return List of apps in car mode.
+ */
+ public @NonNull List<String> getCarModeApps() {
+ return mCarModeApps
+ .stream()
+ .sorted(mCarModeApps.comparator())
+ .map(cma -> cma.getPackageName())
+ .collect(Collectors.toList());
+ }
+
+ private @NonNull String getCarModePriorityString() {
+ return mCarModeApps
+ .stream()
+ .sorted(mCarModeApps.comparator())
+ .map(cma -> "[" + cma.getPriority() + ", " + cma.getPackageName() + "]")
+ .collect(Collectors.joining(", "));
+ }
+
+ /**
+ * Gets the app which is currently in car mode. This is the highest priority app which has
+ * entered car mode.
+ * @return The app which is in car mode.
+ */
+ public @Nullable String getCurrentCarModePackage() {
+ CarModeApp app = mCarModeApps.peek();
+ return app == null ? null : app.getPackageName();
+ }
+
+ /**
+ * @return {@code true} if the device is in car mode, {@code false} otherwise.
+ */
+ public boolean isInCarMode() {
+ return !mCarModeApps.isEmpty();
+ }
+
+ /**
+ * Dumps the state of the car mode tracker to the specified print writer.
+ * @param pw
+ */
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("CarModeTracker:");
+ pw.increaseIndent();
+
+ pw.println("Current car mode apps:");
+ pw.increaseIndent();
+ for (CarModeApp app : mCarModeApps) {
+ pw.print("[");
+ pw.print(app.getPriority());
+ pw.print("] ");
+ pw.println(app.getPackageName());
+ }
+ pw.decreaseIndent();
+
+ pw.println("Car mode history:");
+ pw.increaseIndent();
+ mCarModeChangeLog.dump(pw);
+ pw.decreaseIndent();
+
+ pw.decreaseIndent();
+ }
+}
diff --git a/src/com/android/server/telecom/ConnectionServiceFocusManager.java b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
index 9c0bfa2..fbb23f4 100644
--- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java
+++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
@@ -154,7 +154,7 @@
}
private static final int[] PRIORITY_FOCUS_CALL_STATE = new int[] {
- CallState.ACTIVE, CallState.CONNECTING, CallState.DIALING
+ CallState.ACTIVE, CallState.CONNECTING, CallState.DIALING, CallState.AUDIO_PROCESSING
};
private static final int MSG_REQUEST_FOCUS = 1;
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
old mode 100644
new mode 100755
index 4621558..01acdd1
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -16,9 +16,12 @@
package com.android.server.telecom;
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
+
import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -40,7 +43,6 @@
import android.telecom.StatusHints;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
-import android.telephony.TelephonyManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telecom.IConnectionService;
@@ -67,12 +69,15 @@
public class ConnectionServiceWrapper extends ServiceBinder implements
ConnectionServiceFocusManager.ConnectionServiceFocus {
+ private static final String TELECOM_ABBREVIATION = "cast";
+
private final class Adapter extends IConnectionServiceAdapter.Stub {
@Override
public void handleCreateConnectionComplete(String callId, ConnectionRequest request,
ParcelableConnection connection, Session.Info sessionInfo) {
- Log.startSession(sessionInfo, LogUtils.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE);
+ Log.startSession(sessionInfo, LogUtils.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE,
+ mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -99,8 +104,40 @@
}
@Override
+ public void handleCreateConferenceComplete(String callId, ConnectionRequest request,
+ ParcelableConference conference, Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, LogUtils.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE,
+ mPackageAbbreviation);
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ logIncoming("handleCreateConferenceComplete %s", callId);
+ ConnectionServiceWrapper.this
+ .handleCreateConferenceComplete(callId, request, conference);
+
+ if (mServiceInterface != null) {
+ logOutgoing("createConferenceComplete %s", callId);
+ try {
+ mServiceInterface.createConferenceComplete(callId,
+ Log.getExternalSession());
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ } catch (Throwable t) {
+ Log.e(ConnectionServiceWrapper.this, t, "");
+ throw t;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ Log.endSession();
+ }
+ }
+
+
+ @Override
public void setActive(String callId, Session.Info sessionInfo) {
- Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_ACTIVE);
+ Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_ACTIVE,
+ mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -123,7 +160,7 @@
@Override
public void setRinging(String callId, Session.Info sessionInfo) {
- Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_RINGING);
+ Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_RINGING, mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -146,7 +183,7 @@
@Override
public void resetConnectionTime(String callId, Session.Info sessionInfo) {
- Log.startSession(sessionInfo, "CSW.rCCT");
+ Log.startSession(sessionInfo, "CSW.rCCT", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -167,7 +204,7 @@
@Override
public void setVideoProvider(String callId, IVideoProvider videoProvider,
Session.Info sessionInfo) {
- Log.startSession(sessionInfo, "CSW.sVP");
+ Log.startSession(sessionInfo, "CSW.sVP", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -188,7 +225,7 @@
@Override
public void setDialing(String callId, Session.Info sessionInfo) {
- Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_DIALING);
+ Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_DIALING, mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -211,7 +248,7 @@
@Override
public void setPulling(String callId, Session.Info sessionInfo) {
- Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_PULLING);
+ Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_PULLING, mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -233,7 +270,8 @@
@Override
public void setDisconnected(String callId, DisconnectCause disconnectCause,
Session.Info sessionInfo) {
- Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_DISCONNECTED);
+ Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_DISCONNECTED,
+ mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -257,7 +295,7 @@
@Override
public void setOnHold(String callId, Session.Info sessionInfo) {
- Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_ON_HOLD);
+ Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_ON_HOLD, mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -281,7 +319,7 @@
@Override
public void setRingbackRequested(String callId, boolean ringback,
Session.Info sessionInfo) {
- Log.startSession(sessionInfo, "CSW.SRR");
+ Log.startSession(sessionInfo, "CSW.SRR", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -304,7 +342,7 @@
@Override
public void removeCall(String callId, Session.Info sessionInfo) {
- Log.startSession(sessionInfo, LogUtils.Sessions.CSW_REMOVE_CALL);
+ Log.startSession(sessionInfo, LogUtils.Sessions.CSW_REMOVE_CALL, mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -331,7 +369,7 @@
@Override
public void setConnectionCapabilities(String callId, int connectionCapabilities,
Session.Info sessionInfo) {
- Log.startSession(sessionInfo, "CSW.sCC");
+ Log.startSession(sessionInfo, "CSW.sCC", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -356,7 +394,7 @@
@Override
public void setConnectionProperties(String callId, int connectionProperties,
Session.Info sessionInfo) {
- Log.startSession("CSW.sCP");
+ Log.startSession("CSW.sCP", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -378,7 +416,8 @@
@Override
public void setIsConferenced(String callId, String conferenceCallId,
Session.Info sessionInfo) {
- Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_IS_CONFERENCED);
+ Log.startSession(sessionInfo, LogUtils.Sessions.CSW_SET_IS_CONFERENCED,
+ mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -407,7 +446,7 @@
@Override
public void setConferenceMergeFailed(String callId, Session.Info sessionInfo) {
- Log.startSession(sessionInfo, "CSW.sCMF");
+ Log.startSession(sessionInfo, "CSW.sCMF", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -434,7 +473,31 @@
@Override
public void addConferenceCall(String callId, ParcelableConference parcelableConference,
Session.Info sessionInfo) {
- Log.startSession(sessionInfo, LogUtils.Sessions.CSW_ADD_CONFERENCE_CALL);
+ Log.startSession(sessionInfo, LogUtils.Sessions.CSW_ADD_CONFERENCE_CALL,
+ mPackageAbbreviation);
+
+ if (parcelableConference.getConnectElapsedTimeMillis() != 0
+ && mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE)
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.w(this, "addConferenceCall from caller without permission!");
+ parcelableConference = new ParcelableConference.Builder(
+ parcelableConference.getPhoneAccount(),
+ parcelableConference.getState())
+ .setConnectionCapabilities(parcelableConference.getConnectionCapabilities())
+ .setConnectionProperties(parcelableConference.getConnectionProperties())
+ .setConnectionIds(parcelableConference.getConnectionIds())
+ .setVideoAttributes(parcelableConference.getVideoProvider(),
+ parcelableConference.getVideoState())
+ .setStatusHints(parcelableConference.getStatusHints())
+ .setExtras(parcelableConference.getExtras())
+ .setAddress(parcelableConference.getHandle(),
+ parcelableConference.getHandlePresentation())
+ // no caller display name set.
+ .setDisconnectCause(parcelableConference.getDisconnectCause())
+ .setRingbackRequested(parcelableConference.isRingbackRequested())
+ .build();
+ }
+
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -525,7 +588,7 @@
@Override
public void onPostDialWait(String callId, String remaining,
Session.Info sessionInfo) throws RemoteException {
- Log.startSession(sessionInfo, "CSW.oPDW");
+ Log.startSession(sessionInfo, "CSW.oPDW", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -549,7 +612,7 @@
@Override
public void onPostDialChar(String callId, char nextChar,
Session.Info sessionInfo) throws RemoteException {
- Log.startSession(sessionInfo, "CSW.oPDC");
+ Log.startSession(sessionInfo, "CSW.oPDC", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -574,7 +637,7 @@
public void queryRemoteConnectionServices(RemoteServiceCallback callback,
String callingPackage, Session.Info sessionInfo) {
final UserHandle callingUserHandle = Binder.getCallingUserHandle();
- Log.startSession(sessionInfo, "CSW.qRCS");
+ Log.startSession(sessionInfo, "CSW.qRCS", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -594,7 +657,7 @@
@Override
public void setVideoState(String callId, int videoState, Session.Info sessionInfo) {
- Log.startSession(sessionInfo, "CSW.sVS");
+ Log.startSession(sessionInfo, "CSW.sVS", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -615,7 +678,7 @@
@Override
public void setIsVoipAudioMode(String callId, boolean isVoip, Session.Info sessionInfo) {
- Log.startSession(sessionInfo, "CSW.sIVAM");
+ Log.startSession(sessionInfo, "CSW.sIVAM", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -637,7 +700,7 @@
@Override
public void setAudioRoute(String callId, int audioRoute,
String bluetoothAddress, Session.Info sessionInfo) {
- Log.startSession(sessionInfo, "CSW.sAR");
+ Log.startSession(sessionInfo, "CSW.sAR", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -657,7 +720,7 @@
@Override
public void setStatusHints(String callId, StatusHints statusHints,
Session.Info sessionInfo) {
- Log.startSession(sessionInfo, "CSW.sSH");
+ Log.startSession(sessionInfo, "CSW.sSH", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -678,7 +741,7 @@
@Override
public void putExtras(String callId, Bundle extras, Session.Info sessionInfo) {
- Log.startSession(sessionInfo, "CSW.pE");
+ Log.startSession(sessionInfo, "CSW.pE", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -699,7 +762,7 @@
@Override
public void removeExtras(String callId, List<String> keys, Session.Info sessionInfo) {
- Log.startSession(sessionInfo, "CSW.rE");
+ Log.startSession(sessionInfo, "CSW.rE", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -721,7 +784,8 @@
@Override
public void setAddress(String callId, Uri address, int presentation,
Session.Info sessionInfo) {
- Log.startSession(sessionInfo, "CSW.sA");
+ Log.startSession(sessionInfo, "CSW.sA", mPackageAbbreviation);
+
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -743,7 +807,7 @@
@Override
public void setCallerDisplayName(String callId, String callerDisplayName, int presentation,
Session.Info sessionInfo) {
- Log.startSession(sessionInfo, "CSW.sCDN");
+ Log.startSession(sessionInfo, "CSW.sCDN", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -766,7 +830,7 @@
@Override
public void setConferenceableConnections(String callId, List<String> conferenceableCallIds,
Session.Info sessionInfo) {
- Log.startSession(sessionInfo, "CSW.sCC");
+ Log.startSession(sessionInfo, "CSW.sCC", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -798,7 +862,7 @@
@Override
public void addExistingConnection(String callId, ParcelableConnection connection,
Session.Info sessionInfo) {
- Log.startSession(sessionInfo, "CSW.aEC");
+ Log.startSession(sessionInfo, "CSW.aEC", mPackageAbbreviation);
UserHandle userHandle = Binder.getCallingUserHandle();
// Check that the Calling Package matches PhoneAccountHandle's Component Package
PhoneAccountHandle callingPhoneAccountHandle = connection.getPhoneAccount();
@@ -806,6 +870,7 @@
mAppOpsManager.checkPackage(Binder.getCallingUid(),
callingPhoneAccountHandle.getComponentName().getPackageName());
}
+
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -813,8 +878,11 @@
// ParcelableConnection is in fact registered to Telecom and is being called
// from the correct user.
List<PhoneAccountHandle> accountHandles =
+ // Include CAPABILITY_EMERGENCY_CALLS_ONLY in this list in case we are adding
+ // an emergency call.
mPhoneAccountRegistrar.getCallCapablePhoneAccounts(null /*uriScheme*/,
- false /*includeDisabledAccounts*/, userHandle);
+ false /*includeDisabledAccounts*/, userHandle, 0 /*capabilities*/,
+ 0 /*excludedCapabilities*/);
PhoneAccountHandle phoneAccountHandle = null;
for (PhoneAccountHandle accountHandle : accountHandles) {
if(accountHandle.equals(callingPhoneAccountHandle)) {
@@ -883,7 +951,7 @@
@Override
public void onConnectionEvent(String callId, String event, Bundle extras,
Session.Info sessionInfo) {
- Log.startSession(sessionInfo, "CSW.oCE");
+ Log.startSession(sessionInfo, "CSW.oCE", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -911,7 +979,7 @@
@Override
public void onRttInitiationFailure(String callId, int reason, Session.Info sessionInfo)
throws RemoteException {
- Log.startSession(sessionInfo, "CSW.oRIF");
+ Log.startSession(sessionInfo, "CSW.oRIF", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -938,7 +1006,7 @@
@Override
public void onRemoteRttRequest(String callId, Session.Info sessionInfo)
throws RemoteException {
- Log.startSession(sessionInfo, "CSW.oRRR");
+ Log.startSession(sessionInfo, "CSW.oRRR", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -964,7 +1032,7 @@
mAppOpsManager.checkPackage(Binder.getCallingUid(),
pHandle.getComponentName().getPackageName());
}
- Log.startSession(sessionInfo, "CSW.oPAC");
+ Log.startSession(sessionInfo, "CSW.oPAC", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -985,7 +1053,7 @@
@Override
public void onConnectionServiceFocusReleased(Session.Info sessionInfo)
throws RemoteException {
- Log.startSession(sessionInfo, "CSW.oCSFR");
+ Log.startSession(sessionInfo, "CSW.oCSFR", mPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -1004,7 +1072,15 @@
@Override
public void setConferenceState(String callId, boolean isConference,
Session.Info sessionInfo) throws RemoteException {
- Log.startSession(sessionInfo, "CSW.sCS");
+ Log.startSession(sessionInfo, "CSW.sCS", mPackageAbbreviation);
+
+ if (mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE)
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.w(this, "setConferenceState from caller without permission.");
+ Log.endSession();
+ return;
+ }
+
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -1021,6 +1097,35 @@
Log.endSession();
}
}
+
+ @Override
+ public void setCallDirection(String callId, int direction, Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, "CSW.sCD", mPackageAbbreviation);
+
+ if (mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE)
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.w(this, "setCallDirection from caller without permission.");
+ Log.endSession();
+ return;
+ }
+
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ logIncoming("setCallDirection %s %d", callId, direction);
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setCallDirection(Call.getRemappedCallDirection(direction));
+ }
+ }
+ } catch (Throwable t) {
+ Log.e(ConnectionServiceWrapper.this, t, "");
+ throw t;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ Log.endSession();
+ }
+ }
}
private final Adapter mAdapter = new Adapter();
@@ -1033,6 +1138,7 @@
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final CallsManager mCallsManager;
private final AppOpsManager mAppOpsManager;
+ private final Context mContext;
private ConnectionServiceFocusManager.ConnectionServiceFocusListener mConnSvrFocusListener;
@@ -1063,6 +1169,7 @@
mPhoneAccountRegistrar = phoneAccountRegistrar;
mCallsManager = callsManager;
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+ mContext = context;
}
/** See {@link IConnectionService#addConnectionServiceAdapter}. */
@@ -1088,11 +1195,71 @@
}
/**
+ * Creates a conference for a new outgoing call or attach to an existing incoming call.
+ */
+ public void createConference(final Call call, final CreateConnectionResponse response) {
+ Log.d(this, "createConference(%s) via %s.", call, getComponentName());
+ BindCallback callback = new BindCallback() {
+ @Override
+ public void onSuccess() {
+ String callId = mCallIdMapper.getCallId(call);
+ mPendingResponses.put(callId, response);
+
+ Bundle extras = call.getIntentExtras();
+
+ Log.addEvent(call, LogUtils.Events.START_CONFERENCE,
+ Log.piiHandle(call.getHandle()));
+
+ ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
+ .setAccountHandle(call.getTargetPhoneAccount())
+ .setAddress(call.getHandle())
+ .setExtras(extras)
+ .setVideoState(call.getVideoState())
+ .setTelecomCallId(callId)
+ // For self-managed incoming calls, if there is another ongoing call Telecom
+ // is responsible for showing a UI to ask the user if they'd like to answer
+ // this new incoming call.
+ .setShouldShowIncomingCallUi(
+ !mCallsManager.shouldShowSystemIncomingCallUi(call))
+ .setRttPipeFromInCall(call.getInCallToCsRttPipeForCs())
+ .setRttPipeToInCall(call.getCsToInCallRttPipeForCs())
+ .setParticipants(call.getParticipants())
+ .setIsAdhocConferenceCall(call.isAdhocConferenceCall())
+ .build();
+
+ try {
+ mServiceInterface.createConference(
+ call.getConnectionManagerPhoneAccount(),
+ callId,
+ connectionRequest,
+ call.shouldAttachToExistingConnection(),
+ call.isUnknown(),
+ Log.getExternalSession(TELECOM_ABBREVIATION));
+
+ } catch (RemoteException e) {
+ Log.e(this, e, "Failure to createConference -- %s", getComponentName());
+ mPendingResponses.remove(callId).handleCreateConferenceFailure(
+ new DisconnectCause(DisconnectCause.ERROR, e.toString()));
+ }
+ }
+
+ @Override
+ public void onFailure() {
+ Log.e(this, new Exception(), "Failure to conference %s", getComponentName());
+ response.handleCreateConferenceFailure(new DisconnectCause(DisconnectCause.ERROR));
+ }
+ };
+
+ mBinder.bind(callback, call);
+
+ }
+
+ /**
* Creates a new connection for a new outgoing call or to attach to an existing incoming call.
*/
@VisibleForTesting
public void createConnection(final Call call, final CreateConnectionResponse response) {
- Log.d(this, "createConnection(%s) via %s.", call, getComponentName());
+ Log.i(this, "createConnection(%s) via %s.", call, getComponentName());
BindCallback callback = new BindCallback() {
@Override
public void onSuccess() {
@@ -1131,7 +1298,8 @@
}
Log.addEvent(call, LogUtils.Events.START_CONNECTION,
- Log.piiHandle(call.getHandle()));
+ Log.piiHandle(call.getHandle()) + " via:" +
+ getComponentName().getPackageName());
ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
.setAccountHandle(call.getTargetPhoneAccount())
@@ -1155,7 +1323,7 @@
connectionRequest,
call.shouldAttachToExistingConnection(),
call.isUnknown(),
- Log.getExternalSession());
+ Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException e) {
Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
@@ -1202,7 +1370,7 @@
callId,
false),
call.isIncoming(),
- Log.getExternalSession());
+ Log.getExternalSession(TELECOM_ABBREVIATION));
call.setDisconnectCause(new DisconnectCause(DisconnectCause.CANCELED));
call.disconnect();
} catch (RemoteException e) {
@@ -1220,6 +1388,53 @@
mBinder.bind(callback, call);
}
+ /**
+ * Notifies the {@link ConnectionService} associated with a {@link Call} that the request to
+ * create a conference has been denied or failed.
+ * @param call The call.
+ */
+ void createConferenceFailed(final Call call) {
+ Log.d(this, "createConferenceFailed(%s) via %s.", call, getComponentName());
+ BindCallback callback = new BindCallback() {
+ @Override
+ public void onSuccess() {
+ final String callId = mCallIdMapper.getCallId(call);
+ // If still bound, tell the connection service create connection has failed.
+ if (callId != null && isServiceValid("createConferenceFailed")) {
+ Log.addEvent(call, LogUtils.Events.CREATE_CONFERENCE_FAILED,
+ Log.piiHandle(call.getHandle()));
+ try {
+ logOutgoing("createConferenceFailed %s", callId);
+ mServiceInterface.createConferenceFailed(
+ call.getConnectionManagerPhoneAccount(),
+ callId,
+ new ConnectionRequest(
+ call.getTargetPhoneAccount(),
+ call.getHandle(),
+ call.getIntentExtras(),
+ call.getVideoState(),
+ callId,
+ false),
+ call.isIncoming(),
+ Log.getExternalSession(TELECOM_ABBREVIATION));
+ call.setDisconnectCause(new DisconnectCause(DisconnectCause.CANCELED));
+ call.disconnect();
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ @Override
+ public void onFailure() {
+ // Binding failed. Oh no.
+ Log.w(this, "onFailure - could not bind to CS for conf call %s", call.getId());
+ }
+ };
+
+ mBinder.bind(callback, call);
+ }
+
+
void handoverFailed(final Call call, final int reason) {
Log.d(this, "handoverFailed(%s) via %s.", call, getComponentName());
BindCallback callback = new BindCallback() {
@@ -1239,7 +1454,9 @@
call.getIntentExtras(),
call.getVideoState(),
callId,
- false), reason, Log.getExternalSession());
+ false),
+ reason,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException e) {
}
}
@@ -1267,7 +1484,7 @@
try {
mServiceInterface.handoverComplete(
callId,
- Log.getExternalSession());
+ Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException e) {
}
}
@@ -1293,7 +1510,7 @@
if (callId != null && isServiceValid("abort")) {
try {
logOutgoing("abort %s", callId);
- mServiceInterface.abort(callId, Log.getExternalSession());
+ mServiceInterface.abort(callId, Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException e) {
}
}
@@ -1307,7 +1524,7 @@
if (callId != null && isServiceValid("silence")) {
try {
logOutgoing("silence %s", callId);
- mServiceInterface.silence(callId, Log.getExternalSession());
+ mServiceInterface.silence(callId, Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException e) {
}
}
@@ -1319,7 +1536,7 @@
if (callId != null && isServiceValid("hold")) {
try {
logOutgoing("hold %s", callId);
- mServiceInterface.hold(callId, Log.getExternalSession());
+ mServiceInterface.hold(callId, Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException e) {
}
}
@@ -1331,7 +1548,7 @@
if (callId != null && isServiceValid("unhold")) {
try {
logOutgoing("unhold %s", callId);
- mServiceInterface.unhold(callId, Log.getExternalSession());
+ mServiceInterface.unhold(callId, Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException e) {
}
}
@@ -1345,7 +1562,7 @@
try {
logOutgoing("onCallAudioStateChanged %s %s", callId, audioState);
mServiceInterface.onCallAudioStateChanged(callId, audioState,
- Log.getExternalSession());
+ Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException e) {
}
}
@@ -1357,7 +1574,7 @@
if (callId != null && isServiceValid("disconnect")) {
try {
logOutgoing("disconnect %s", callId);
- mServiceInterface.disconnect(callId, Log.getExternalSession());
+ mServiceInterface.disconnect(callId, Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException e) {
}
}
@@ -1370,9 +1587,10 @@
try {
logOutgoing("answer %s %d", callId, videoState);
if (VideoProfile.isAudioOnly(videoState)) {
- mServiceInterface.answer(callId, Log.getExternalSession());
+ mServiceInterface.answer(callId, Log.getExternalSession(TELECOM_ABBREVIATION));
} else {
- mServiceInterface.answerVideo(callId, videoState, Log.getExternalSession());
+ mServiceInterface.answerVideo(callId, videoState,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
}
} catch (RemoteException e) {
}
@@ -1385,7 +1603,8 @@
if (callId != null && isServiceValid("deflect")) {
try {
logOutgoing("deflect %s", callId);
- mServiceInterface.deflect(callId, address, Log.getExternalSession());
+ mServiceInterface.deflect(callId, address,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException e) {
}
}
@@ -1400,22 +1619,65 @@
if (rejectWithMessage && call.can(
Connection.CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION)) {
- mServiceInterface.rejectWithMessage(callId, message, Log.getExternalSession());
+ mServiceInterface.rejectWithMessage(callId, message,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
} else {
- mServiceInterface.reject(callId, Log.getExternalSession());
+ mServiceInterface.reject(callId, Log.getExternalSession(TELECOM_ABBREVIATION));
}
} catch (RemoteException e) {
}
}
}
+ /** @see IConnectionService#reject(String, Session.Info) */
+ void rejectWithReason(Call call, @android.telecom.Call.RejectReason int rejectReason) {
+ final String callId = mCallIdMapper.getCallId(call);
+ if (callId != null && isServiceValid("rejectReason")) {
+ try {
+ logOutgoing("rejectReason %s, %d", callId, rejectReason);
+
+ mServiceInterface.rejectWithReason(callId, rejectReason,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /** @see IConnectionService#transfer(String, Uri , boolean, Session.Info) */
+ void transfer(Call call, Uri number, boolean isConfirmationRequired) {
+ final String callId = mCallIdMapper.getCallId(call);
+ if (callId != null && isServiceValid("transfer")) {
+ try {
+ logOutgoing("transfer %s", callId);
+ mServiceInterface.transfer(callId, number, isConfirmationRequired,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /** @see IConnectionService#consultativeTransfer(String, String, Session.Info) */
+ void transfer(Call call, Call otherCall) {
+ final String callId = mCallIdMapper.getCallId(call);
+ final String otherCallId = mCallIdMapper.getCallId(otherCall);
+ if (callId != null && otherCallId != null && isServiceValid("consultativeTransfer")) {
+ try {
+ logOutgoing("consultativeTransfer %s", callId);
+ mServiceInterface.consultativeTransfer(callId, otherCallId,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
/** @see IConnectionService#playDtmfTone(String, char, Session.Info) */
void playDtmfTone(Call call, char digit) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("playDtmfTone")) {
try {
logOutgoing("playDtmfTone %s %c", callId, digit);
- mServiceInterface.playDtmfTone(callId, digit, Log.getExternalSession());
+ mServiceInterface.playDtmfTone(callId, digit,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException e) {
}
}
@@ -1427,7 +1689,8 @@
if (callId != null && isServiceValid("stopDtmfTone")) {
try {
logOutgoing("stopDtmfTone %s", callId);
- mServiceInterface.stopDtmfTone(callId, Log.getExternalSession());
+ mServiceInterface.stopDtmfTone(callId,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException e) {
}
}
@@ -1474,7 +1737,8 @@
if (callId != null && isServiceValid("onPostDialContinue")) {
try {
logOutgoing("onPostDialContinue %s %b", callId, proceed);
- mServiceInterface.onPostDialContinue(callId, proceed, Log.getExternalSession());
+ mServiceInterface.onPostDialContinue(callId, proceed,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException ignored) {
}
}
@@ -1486,7 +1750,8 @@
if (callId != null && otherCallId != null && isServiceValid("conference")) {
try {
logOutgoing("conference %s %s", callId, otherCallId);
- mServiceInterface.conference(callId, otherCallId, Log.getExternalSession());
+ mServiceInterface.conference(callId, otherCallId,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException ignored) {
}
}
@@ -1497,7 +1762,8 @@
if (callId != null && isServiceValid("splitFromConference")) {
try {
logOutgoing("splitFromConference %s", callId);
- mServiceInterface.splitFromConference(callId, Log.getExternalSession());
+ mServiceInterface.splitFromConference(callId,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException ignored) {
}
}
@@ -1508,7 +1774,8 @@
if (callId != null && isServiceValid("mergeConference")) {
try {
logOutgoing("mergeConference %s", callId);
- mServiceInterface.mergeConference(callId, Log.getExternalSession());
+ mServiceInterface.mergeConference(callId,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException ignored) {
}
}
@@ -1519,18 +1786,33 @@
if (callId != null && isServiceValid("swapConference")) {
try {
logOutgoing("swapConference %s", callId);
- mServiceInterface.swapConference(callId, Log.getExternalSession());
+ mServiceInterface.swapConference(callId,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException ignored) {
}
}
}
- void pullExternalCall(Call call) {
+ void addConferenceParticipants(Call call, List<Uri> participants) {
+ final String callId = mCallIdMapper.getCallId(call);
+ if (callId != null && isServiceValid("addConferenceParticipants")) {
+ try {
+ logOutgoing("addConferenceParticipants %s", callId);
+ mServiceInterface.addConferenceParticipants(callId, participants,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public void pullExternalCall(Call call) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("pullExternalCall")) {
try {
logOutgoing("pullExternalCall %s", callId);
- mServiceInterface.pullExternalCall(callId, Log.getExternalSession());
+ mServiceInterface.pullExternalCall(callId,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException ignored) {
}
}
@@ -1541,7 +1823,8 @@
if (callId != null && isServiceValid("sendCallEvent")) {
try {
logOutgoing("sendCallEvent %s %s", callId, event);
- mServiceInterface.sendCallEvent(callId, event, extras, Log.getExternalSession());
+ mServiceInterface.sendCallEvent(callId, event, extras,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException ignored) {
}
}
@@ -1552,7 +1835,8 @@
if (callId != null && isServiceValid("onExtrasChanged")) {
try {
logOutgoing("onExtrasChanged %s %s", callId, extras);
- mServiceInterface.onExtrasChanged(callId, extras, Log.getExternalSession());
+ mServiceInterface.onExtrasChanged(callId, extras,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException ignored) {
}
}
@@ -1563,7 +1847,8 @@
if (callId != null && isServiceValid("startRtt")) {
try {
logOutgoing("startRtt: %s %s %s", callId, fromInCall, toInCall);
- mServiceInterface.startRtt(callId, fromInCall, toInCall, Log.getExternalSession());
+ mServiceInterface.startRtt(callId, fromInCall, toInCall,
+ Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException ignored) {
}
}
@@ -1574,7 +1859,7 @@
if (callId != null && isServiceValid("stopRtt")) {
try {
logOutgoing("stopRtt: %s", callId);
- mServiceInterface.stopRtt(callId, Log.getExternalSession());
+ mServiceInterface.stopRtt(callId, Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException ignored) {
}
}
@@ -1587,7 +1872,7 @@
try {
logOutgoing("respondToRttRequest: %s %s %s", callId, fromInCall, toInCall);
mServiceInterface.respondToRttUpgradeRequest(
- callId, fromInCall, toInCall, Log.getExternalSession());
+ callId, fromInCall, toInCall, Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException ignored) {
}
}
@@ -1626,7 +1911,8 @@
@Override
public void onSuccess() {
try {
- mServiceInterface.connectionServiceFocusLost(Log.getExternalSession());
+ mServiceInterface.connectionServiceFocusLost(
+ Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException ignored) {
Log.d(this, "failed to inform the focus lost event");
}
@@ -1644,7 +1930,8 @@
@Override
public void onSuccess() {
try {
- mServiceInterface.connectionServiceFocusGained(Log.getExternalSession());
+ mServiceInterface.connectionServiceFocusGained(
+ Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException ignored) {
Log.d(this, "failed to inform the focus gained event");
}
@@ -1673,6 +1960,11 @@
// A connection that begins in the DISCONNECTED state is an indication of
// failure to connect; we handle all failures uniformly
Call foundCall = mCallIdMapper.getCall(callId);
+
+ if (connection.getConnectTimeMillis() != 0) {
+ foundCall.setConnectTimeMillis(connection.getConnectTimeMillis());
+ }
+
if (foundCall != null) {
// The post-dial digits are created when the call is first created. Normally
// the ConnectionService is responsible for stripping them from the address, but
@@ -1690,6 +1982,26 @@
}
}
+ private void handleCreateConferenceComplete(
+ String callId,
+ ConnectionRequest request,
+ ParcelableConference conference) {
+ // TODO: Note we are not using parameter "request", which is a side effect of our tacit
+ // assumption that we have at most one outgoing conference attempt per ConnectionService.
+ // This may not continue to be the case.
+ if (conference.getState() == Connection.STATE_DISCONNECTED) {
+ // A conference that begins in the DISCONNECTED state is an indication of
+ // failure to connect; we handle all failures uniformly
+ removeCall(callId, conference.getDisconnectCause());
+ } else {
+ // Successful connection
+ if (mPendingResponses.containsKey(callId)) {
+ mPendingResponses.remove(callId)
+ .handleCreateConferenceSuccess(mCallIdMapper, conference);
+ }
+ }
+ }
+
/**
* Called when the associated connection service dies.
*/
@@ -1711,12 +2023,14 @@
}
private void logIncoming(String msg, Object... params) {
- Log.d(this, "ConnectionService -> Telecom[" + mComponentName.flattenToShortString() + "]: "
+ // Keep these as debug; the incoming logging is traced on a package level through the
+ // session logging.
+ Log.d(this, "CS -> TC[" + Log.getPackageAbbreviation(mComponentName) + "]: "
+ msg, params);
}
private void logOutgoing(String msg, Object... params) {
- Log.d(this, "Telecom -> ConnectionService[" + mComponentName.flattenToShortString() + "]: "
+ Log.d(this, "TC -> CS[" + Log.getPackageAbbreviation(mComponentName) + "]: "
+ msg, params);
}
@@ -1743,8 +2057,12 @@
}
ConnectionServiceWrapper service = mConnectionServiceRepository.getService(
handle.getComponentName(), handle.getUserHandle());
- if (service != null) {
+ if (service != null && service != this) {
simServices.add(service);
+ } else {
+ // This is unexpected, normally PhoneAccounts with CAPABILITY_CALL_PROVIDER are not
+ // also CAPABILITY_CONNECTION_MANAGER
+ Log.w(this, "call provider also detected as SIM call manager: " + service);
}
}
@@ -1761,11 +2079,6 @@
Log.i(this, "queryRemoteConnectionServices, simServices = %s", simServices);
for (ConnectionServiceWrapper simService : simServices) {
- if (simService == this) {
- // Only happens in the unlikely case that a SIM service is also a SIM call manager
- continue;
- }
-
final ConnectionServiceWrapper currentSimService = simService;
currentSimService.mBinder.bind(new BindCallback() {
diff --git a/src/com/android/server/telecom/ContactsAsyncHelper.java b/src/com/android/server/telecom/ContactsAsyncHelper.java
index 7fb6419..37ee941 100644
--- a/src/com/android/server/telecom/ContactsAsyncHelper.java
+++ b/src/com/android/server/telecom/ContactsAsyncHelper.java
@@ -40,6 +40,12 @@
public class ContactsAsyncHelper {
private static final String LOG_TAG = ContactsAsyncHelper.class.getSimpleName();
+ public static class Factory {
+ public ContactsAsyncHelper create(ContentResolverAdapter adapter) {
+ return new ContactsAsyncHelper(adapter);
+ }
+ }
+
/**
* Interface for a WorkerHandler result return.
*/
@@ -77,6 +83,11 @@
mContentResolverAdapter = contentResolverAdapter;
}
+ public ContactsAsyncHelper(ContentResolverAdapter contentResolverAdapter, Looper looper) {
+ mContentResolverAdapter = contentResolverAdapter;
+ mThreadHandler = new WorkerHandler(looper);
+ }
+
private static final class WorkerArgs {
public Context context;
public Uri displayPhotoUri;
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index 89200ad..0a8a6c4 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -17,8 +17,10 @@
package com.android.server.telecom;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.telecom.DisconnectCause;
import android.telecom.Log;
+import android.telecom.ParcelableConference;
import android.telecom.ParcelableConnection;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -103,7 +105,7 @@
if (manager == null) {
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
- return manager.getSubIdForPhoneAccount(account);
+ return manager.getSubscriptionId(account.getAccountHandle());
}
@Override
@@ -245,7 +247,11 @@
mCall.setConnectionService(mService);
setTimeoutIfNeeded(mService, attempt);
if (mCall.isIncoming()) {
- mService.createConnection(mCall, CreateConnectionProcessor.this);
+ if (mCall.isAdhocConferenceCall()) {
+ mService.createConference(mCall, CreateConnectionProcessor.this);
+ } else {
+ mService.createConnection(mCall, CreateConnectionProcessor.this);
+ }
} else {
// Start to create the connection for outgoing call after the ConnectionService
// of the call has gained the focus.
@@ -254,10 +260,16 @@
new CallsManager.RequestCallback(new CallsManager.PendingAction() {
@Override
public void performAction() {
- Log.d(this, "perform create connection");
- mService.createConnection(
- mCall,
- CreateConnectionProcessor.this);
+ if (mCall.isAdhocConferenceCall()) {
+ Log.d(this, "perform create conference");
+ mService.createConference(mCall,
+ CreateConnectionProcessor.this);
+ } else {
+ Log.d(this, "perform create connection");
+ mService.createConnection(
+ mCall,
+ CreateConnectionProcessor.this);
+ }
}
}));
@@ -267,7 +279,11 @@
Log.v(this, "attemptNextPhoneAccount, no more accounts, failing");
DisconnectCause disconnectCause = mLastErrorDisconnectCause != null ?
mLastErrorDisconnectCause : new DisconnectCause(DisconnectCause.ERROR);
- notifyCallConnectionFailure(disconnectCause);
+ if (mCall.isAdhocConferenceCall()) {
+ notifyConferenceCallFailure(disconnectCause);
+ } else {
+ notifyCallConnectionFailure(disconnectCause);
+ }
}
}
@@ -373,7 +389,8 @@
List<PhoneAccount> allAccounts = mPhoneAccountRegistrar
.getAllPhoneAccountsOfCurrentUser();
- if (allAccounts.isEmpty()) {
+ if (allAccounts.isEmpty() && mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY)) {
// If the list of phone accounts is empty at this point, it means Telephony hasn't
// registered any phone accounts yet. Add a fallback emergency phone account so
// that emergency calls can still go through. We create a new ArrayLists here just
@@ -383,6 +400,12 @@
allAccounts.add(TelephonyUtil.getDefaultEmergencyPhoneAccount());
}
+ // When testing emergency calls, we want the calls to go through to the test connection
+ // service, not the telephony ConnectionService.
+ if (mCall.isTestEmergencyCall()) {
+ allAccounts = mPhoneAccountRegistrar.filterRestrictedPhoneAccounts(allAccounts);
+ }
+
// Get user preferred PA if it exists.
PhoneAccount preferredPA = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
preferredPAH);
@@ -451,6 +474,16 @@
}
}
+ private void notifyConferenceCallFailure(DisconnectCause errorDisconnectCause) {
+ if (mCallResponse != null) {
+ clearTimeout();
+ mCallResponse.handleCreateConferenceFailure(errorDisconnectCause);
+ mCallResponse = null;
+ mCall.clearConnectionService();
+ }
+ }
+
+
@Override
public void handleCreateConnectionSuccess(
CallIdMapper idMapper,
@@ -469,6 +502,25 @@
}
}
+ @Override
+ public void handleCreateConferenceSuccess(
+ CallIdMapper idMapper,
+ ParcelableConference conference) {
+ if (mCallResponse == null) {
+ // Nobody is listening for this conference attempt any longer; ask the responsible
+ // ConnectionService to tear down any resources associated with the call
+ mService.abort(mCall);
+ } else {
+ // Success -- share the good news and remember that we are no longer interested
+ // in hearing about any more attempts
+ mCallResponse.handleCreateConferenceSuccess(idMapper, conference);
+ mCallResponse = null;
+ // If there's a timeout running then don't clear it. The timeout can be triggered
+ // after the call has successfully been created but before it has become active.
+ }
+ }
+
+
private boolean shouldFailCallIfConnectionManagerFails(DisconnectCause cause) {
// Connection Manager does not exist or does not match registered Connection Manager
// Since Connection manager is a proxy for SIM, fall back to SIM
@@ -516,6 +568,18 @@
attemptNextPhoneAccount();
}
+ @Override
+ public void handleCreateConferenceFailure(DisconnectCause errorDisconnectCause) {
+ // Failure of some sort; record the reasons for failure and try again if possible
+ Log.d(CreateConnectionProcessor.this, "Conference failed: (%s)", errorDisconnectCause);
+ if (shouldFailCallIfConnectionManagerFails(errorDisconnectCause)) {
+ notifyConferenceCallFailure(errorDisconnectCause);
+ return;
+ }
+ mLastErrorDisconnectCause = errorDisconnectCause;
+ attemptNextPhoneAccount();
+ }
+
public void sortSimPhoneAccountsForEmergency(List<PhoneAccount> accounts,
PhoneAccount userPreferredAccount) {
// Sort the accounts according to how we want to display them (ascending order).
diff --git a/src/com/android/server/telecom/CreateConnectionResponse.java b/src/com/android/server/telecom/CreateConnectionResponse.java
index 8e3d0cf..601a165 100644
--- a/src/com/android/server/telecom/CreateConnectionResponse.java
+++ b/src/com/android/server/telecom/CreateConnectionResponse.java
@@ -17,6 +17,7 @@
package com.android.server.telecom;
import android.telecom.DisconnectCause;
+import android.telecom.ParcelableConference;
import android.telecom.ParcelableConnection;
import com.android.internal.annotations.VisibleForTesting;
@@ -28,4 +29,7 @@
public interface CreateConnectionResponse {
void handleCreateConnectionSuccess(CallIdMapper idMapper, ParcelableConnection connection);
void handleCreateConnectionFailure(DisconnectCause disconnectCaused);
+
+ void handleCreateConferenceSuccess(CallIdMapper idMapper, ParcelableConference conference);
+ void handleCreateConferenceFailure(DisconnectCause disconnectCaused);
}
diff --git a/src/com/android/server/telecom/DefaultDialerCache.java b/src/com/android/server/telecom/DefaultDialerCache.java
index b72d860..c2b78ee 100644
--- a/src/com/android/server/telecom/DefaultDialerCache.java
+++ b/src/com/android/server/telecom/DefaultDialerCache.java
@@ -18,9 +18,11 @@
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
@@ -134,9 +136,10 @@
private final Context mContext;
private final DefaultDialerManagerAdapter mDefaultDialerManagerAdapter;
private final TelecomSystem.SyncRoot mLock;
- private final String mSystemDialerName;
+ private final ComponentName mSystemDialerComponentName;
private final RoleManagerAdapter mRoleManagerAdapter;
private SparseArray<String> mCurrentDefaultDialerPerUser = new SparseArray<>();
+ private ComponentName mOverrideSystemDialerComponentName;
public DefaultDialerCache(Context context,
DefaultDialerManagerAdapter defaultDialerManagerAdapter,
@@ -146,7 +149,11 @@
mDefaultDialerManagerAdapter = defaultDialerManagerAdapter;
mRoleManagerAdapter = roleManagerAdapter;
mLock = lock;
- mSystemDialerName = TelecomServiceImpl.getSystemDialerPackage(mContext);
+ Resources resources = mContext.getResources();
+ mSystemDialerComponentName = new ComponentName(resources.getString(
+ com.android.internal.R.string.config_defaultDialer),
+ resources.getString(R.string.incall_default_class));
+
IntentFilter packageIntentFilter = new IntentFilter();
packageIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
@@ -194,6 +201,22 @@
return getDefaultDialerApplication(mContext.getUserId());
}
+ public void setSystemDialerComponentName(ComponentName testComponentName) {
+ mOverrideSystemDialerComponentName = testComponentName;
+ }
+
+ public String getSystemDialerApplication() {
+ if (mOverrideSystemDialerComponentName != null) {
+ return mOverrideSystemDialerComponentName.getPackageName();
+ }
+ return mSystemDialerComponentName.getPackageName();
+ }
+
+ public ComponentName getSystemDialerComponent() {
+ if (mOverrideSystemDialerComponentName != null) return mOverrideSystemDialerComponentName;
+ return mSystemDialerComponentName;
+ }
+
public void observeDefaultDialerApplication(Executor executor, IntConsumer observer) {
mRoleManagerAdapter.observeDefaultDialerApp(executor, observer);
}
@@ -201,7 +224,7 @@
public boolean isDefaultOrSystemDialer(String packageName, int userId) {
String defaultDialer = getDefaultDialerApplication(userId);
return Objects.equals(packageName, defaultDialer)
- || Objects.equals(packageName, mSystemDialerName);
+ || Objects.equals(packageName, getSystemDialerApplication());
}
public boolean setDefaultDialer(String packageName, int userId) {
diff --git a/src/com/android/server/telecom/EmergencyCallHelper.java b/src/com/android/server/telecom/EmergencyCallHelper.java
index 7052597..5de4e5a 100644
--- a/src/com/android/server/telecom/EmergencyCallHelper.java
+++ b/src/com/android/server/telecom/EmergencyCallHelper.java
@@ -25,13 +25,13 @@
/**
* Helps with emergency calls by:
- * 1. granting temporary location permission to the default dialer service during emergency calls
+ * 1. granting temporary location permission to the system dialer service during emergency calls
* 2. keeping track of the time of the last emergency call
*/
@VisibleForTesting
public class EmergencyCallHelper {
private final Context mContext;
- private final String mDefaultDialerPackage;
+ private final DefaultDialerCache mDefaultDialerCache;
private final Timeouts.Adapter mTimeoutsAdapter;
private UserHandle mLocationPermissionGrantedToUser;
private boolean mHadFineLocation = false;
@@ -41,10 +41,10 @@
@VisibleForTesting
public EmergencyCallHelper(
Context context,
- String defaultDialerPackage,
+ DefaultDialerCache defaultDialerCache,
Timeouts.Adapter timeoutsAdapter) {
mContext = context;
- mDefaultDialerPackage = defaultDialerPackage;
+ mDefaultDialerCache = defaultDialerCache;
mTimeoutsAdapter = timeoutsAdapter;
}
@@ -94,48 +94,50 @@
}
private void grantLocationPermission(UserHandle userHandle) {
- Log.i(this, "Granting temporary location permission to " + mDefaultDialerPackage
+ String systemDialerPackage = mDefaultDialerCache.getSystemDialerApplication();
+ Log.i(this, "Granting temporary location permission to " + systemDialerPackage
+ ", user: " + userHandle);
try {
boolean hadBackgroundLocation = hasBackgroundLocationPermission();
boolean hadFineLocation = hasFineLocationPermission();
if (hadBackgroundLocation && hadFineLocation) {
- Log.i(this, "Skipping location grant because the default dialer already"
+ Log.i(this, "Skipping location grant because the system dialer already"
+ " holds sufficient permissions");
return;
}
if (!hadFineLocation) {
- mContext.getPackageManager().grantRuntimePermission(mDefaultDialerPackage,
+ mContext.getPackageManager().grantRuntimePermission(systemDialerPackage,
Manifest.permission.ACCESS_FINE_LOCATION, userHandle);
}
if (!hadBackgroundLocation) {
- mContext.getPackageManager().grantRuntimePermission(mDefaultDialerPackage,
+ mContext.getPackageManager().grantRuntimePermission(systemDialerPackage,
Manifest.permission.ACCESS_BACKGROUND_LOCATION, userHandle);
}
mHadFineLocation = hadFineLocation;
mHadBackgroundLocation = hadBackgroundLocation;
recordPermissionGrant(userHandle);
} catch (Exception e) {
- Log.e(this, e, "Failed to grant location permissions to " + mDefaultDialerPackage
+ Log.e(this, e, "Failed to grant location permissions to " + systemDialerPackage
+ ", user: " + userHandle);
}
}
private void revokeLocationPermission() {
- Log.i(this, "Revoking temporary location permission from " + mDefaultDialerPackage
+ String systemDialerPackage = mDefaultDialerCache.getSystemDialerApplication();
+ Log.i(this, "Revoking temporary location permission from " + systemDialerPackage
+ ", user: " + mLocationPermissionGrantedToUser);
UserHandle userHandle = mLocationPermissionGrantedToUser;
try {
if (!mHadFineLocation) {
- mContext.getPackageManager().revokeRuntimePermission(mDefaultDialerPackage,
+ mContext.getPackageManager().revokeRuntimePermission(systemDialerPackage,
Manifest.permission.ACCESS_FINE_LOCATION, userHandle);
}
if (!mHadBackgroundLocation) {
- mContext.getPackageManager().revokeRuntimePermission(mDefaultDialerPackage,
+ mContext.getPackageManager().revokeRuntimePermission(systemDialerPackage,
Manifest.permission.ACCESS_BACKGROUND_LOCATION, userHandle);
}
} catch (Exception e) {
- Log.e(this, e, "Failed to revoke location permission from " + mDefaultDialerPackage
+ Log.e(this, e, "Failed to revoke location permission from " + systemDialerPackage
+ ", user: " + userHandle);
}
clearPermissionGrant();
@@ -143,13 +145,15 @@
private boolean hasBackgroundLocationPermission() {
return mContext.getPackageManager().checkPermission(
- Manifest.permission.ACCESS_BACKGROUND_LOCATION, mDefaultDialerPackage)
+ Manifest.permission.ACCESS_BACKGROUND_LOCATION,
+ mDefaultDialerCache.getSystemDialerApplication())
== PackageManager.PERMISSION_GRANTED;
}
private boolean hasFineLocationPermission() {
return mContext.getPackageManager().checkPermission(
- Manifest.permission.ACCESS_FINE_LOCATION, mDefaultDialerPackage)
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ mDefaultDialerCache.getSystemDialerApplication())
== PackageManager.PERMISSION_GRANTED;
}
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
old mode 100644
new mode 100755
index 8de27be..0fda5f8
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -35,21 +35,23 @@
private final CallsManager mCallsManager;
private final CallIdMapper mCallIdMapper;
private final TelecomSystem.SyncRoot mLock;
- private final String mOwnerComponentName;
+ private final String mOwnerPackageName;
+ private final String mOwnerPackageAbbreviation;
/** Persists the specified parameters. */
public InCallAdapter(CallsManager callsManager, CallIdMapper callIdMapper,
- TelecomSystem.SyncRoot lock, String ownerComponentName) {
+ TelecomSystem.SyncRoot lock, String ownerPackageName) {
mCallsManager = callsManager;
mCallIdMapper = callIdMapper;
mLock = lock;
- mOwnerComponentName = ownerComponentName;
+ mOwnerPackageName = ownerPackageName;
+ mOwnerPackageAbbreviation = Log.getPackageAbbreviation(ownerPackageName);
}
@Override
public void answerCall(String callId, int videoState) {
try {
- Log.startSession(LogUtils.Sessions.ICA_ANSWER_CALL, mOwnerComponentName);
+ Log.startSession(LogUtils.Sessions.ICA_ANSWER_CALL, mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -72,7 +74,7 @@
@Override
public void deflectCall(String callId, Uri address) {
try {
- Log.startSession(LogUtils.Sessions.ICA_DEFLECT_CALL, mOwnerComponentName);
+ Log.startSession(LogUtils.Sessions.ICA_DEFLECT_CALL, mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -95,7 +97,7 @@
@Override
public void rejectCall(String callId, boolean rejectWithMessage, String textMessage) {
try {
- Log.startSession(LogUtils.Sessions.ICA_REJECT_CALL, mOwnerComponentName);
+ Log.startSession(LogUtils.Sessions.ICA_REJECT_CALL, mOwnerPackageAbbreviation);
int callingUid = Binder.getCallingUid();
long token = Binder.clearCallingIdentity();
@@ -126,9 +128,84 @@
}
@Override
+ public void rejectCallWithReason(String callId,
+ @android.telecom.Call.RejectReason int rejectReason) {
+ try {
+ Log.startSession(LogUtils.Sessions.ICA_REJECT_CALL, mOwnerPackageAbbreviation);
+
+ int callingUid = Binder.getCallingUid();
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ Log.d(this, "rejectCallWithReason(%s,%d)", callId, rejectReason);
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.rejectCall(call, rejectReason);
+ } else {
+ Log.w(this, "rejectCallWithReason, unknown call id: %s", callId);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ public void transferCall(String callId, Uri targetNumber, boolean isConfirmationRequired) {
+ try {
+ Log.startSession(LogUtils.Sessions.ICA_TRANSFER_CALL, mOwnerPackageAbbreviation);
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ Log.i(this, "transferCall - %s, %s, %b", callId, Log.pii(targetNumber),
+ isConfirmationRequired);
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.transferCall(call, targetNumber, isConfirmationRequired);
+ } else {
+ Log.w(this, "transferCall, unknown call id: %s", callId);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
+ public void consultativeTransfer(String callId, String otherCallId) {
+ try {
+ Log.startSession(LogUtils.Sessions.ICA_CONSULTATIVE_TRANSFER,
+ mOwnerPackageAbbreviation);
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ Log.i(this, "consultativeTransfer - %s, %s", callId, otherCallId);
+ Call call = mCallIdMapper.getCall(callId);
+ Call otherCall = mCallIdMapper.getCall(otherCallId);
+ if (call != null && otherCall != null) {
+ mCallsManager.transferCall(call, otherCall);
+ } else {
+ Log.w(this, "consultativeTransfer, unknown call id: %s or %s",
+ callId, otherCallId);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
public void playDtmfTone(String callId, char digit) {
try {
- Log.startSession("ICA.pDT", mOwnerComponentName);
+ Log.startSession("ICA.pDT", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -151,7 +228,7 @@
@Override
public void stopDtmfTone(String callId) {
try {
- Log.startSession("ICA.sDT", mOwnerComponentName);
+ Log.startSession("ICA.sDT", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -174,7 +251,7 @@
@Override
public void postDialContinue(String callId, boolean proceed) {
try {
- Log.startSession("ICA.pDC", mOwnerComponentName);
+ Log.startSession("ICA.pDC", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -197,7 +274,7 @@
@Override
public void disconnectCall(String callId) {
try {
- Log.startSession(LogUtils.Sessions.ICA_DISCONNECT_CALL, mOwnerComponentName);
+ Log.startSession(LogUtils.Sessions.ICA_DISCONNECT_CALL, mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -220,7 +297,7 @@
@Override
public void holdCall(String callId) {
try {
- Log.startSession(LogUtils.Sessions.ICA_HOLD_CALL, mOwnerComponentName);
+ Log.startSession(LogUtils.Sessions.ICA_HOLD_CALL, mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -242,7 +319,7 @@
@Override
public void unholdCall(String callId) {
try {
- Log.startSession(LogUtils.Sessions.ICA_UNHOLD_CALL, mOwnerComponentName);
+ Log.startSession(LogUtils.Sessions.ICA_UNHOLD_CALL, mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -265,7 +342,7 @@
public void phoneAccountSelected(String callId, PhoneAccountHandle accountHandle,
boolean setDefault) {
try {
- Log.startSession("ICA.pAS", mOwnerComponentName);
+ Log.startSession("ICA.pAS", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -287,7 +364,7 @@
@Override
public void mute(boolean shouldMute) {
try {
- Log.startSession(LogUtils.Sessions.ICA_MUTE, mOwnerComponentName);
+ Log.startSession(LogUtils.Sessions.ICA_MUTE, mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -304,7 +381,7 @@
@Override
public void setAudioRoute(int route, String bluetoothAddress) {
try {
- Log.startSession(LogUtils.Sessions.ICA_SET_AUDIO_ROUTE, mOwnerComponentName);
+ Log.startSession(LogUtils.Sessions.ICA_SET_AUDIO_ROUTE, mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -319,9 +396,51 @@
}
@Override
+ public void enterBackgroundAudioProcessing(String callId) {
+ try {
+ Log.startSession(LogUtils.Sessions.ICA_ENTER_AUDIO_PROCESSING,
+ mOwnerPackageAbbreviation);
+ // TODO: enforce the extra permission.
+ Binder.withCleanCallingIdentity(() -> {
+ synchronized (mLock) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.enterBackgroundAudioProcessing(call, mOwnerPackageName);
+ } else {
+ Log.w(this, "enterBackgroundAudioProcessing, unknown call id: %s", callId);
+ }
+ }
+ });
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
+ public void exitBackgroundAudioProcessing(String callId, boolean shouldRing) {
+ try {
+ Log.startSession(LogUtils.Sessions.ICA_EXIT_AUDIO_PROCESSING,
+ mOwnerPackageAbbreviation);
+ Binder.withCleanCallingIdentity(() -> {
+ synchronized (mLock) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.exitBackgroundAudioProcessing(call, shouldRing);
+ } else {
+ Log.w(InCallAdapter.this,
+ "exitBackgroundAudioProcessing, unknown call id: %s", callId);
+ }
+ }
+ });
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
public void conference(String callId, String otherCallId) {
try {
- Log.startSession(LogUtils.Sessions.ICA_CONFERENCE, mOwnerComponentName);
+ Log.startSession(LogUtils.Sessions.ICA_CONFERENCE, mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -344,7 +463,7 @@
@Override
public void splitFromConference(String callId) {
try {
- Log.startSession("ICA.sFC", mOwnerComponentName);
+ Log.startSession("ICA.sFC", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -366,7 +485,7 @@
@Override
public void mergeConference(String callId) {
try {
- Log.startSession("ICA.mC", mOwnerComponentName);
+ Log.startSession("ICA.mC", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -388,7 +507,7 @@
@Override
public void swapConference(String callId) {
try {
- Log.startSession("ICA.sC", mOwnerComponentName);
+ Log.startSession("ICA.sC", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -408,9 +527,32 @@
}
@Override
+ public void addConferenceParticipants(String callId, List<Uri> participants) {
+ try {
+ Log.startSession("ICA.aCP", mOwnerPackageAbbreviation);
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.addConferenceParticipants(participants);
+ } else {
+ Log.w(this, "addConferenceParticipants, unknown call id: %s", callId);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+
+ @Override
public void pullExternalCall(String callId) {
try {
- Log.startSession("ICA.pEC", mOwnerComponentName);
+ Log.startSession("ICA.pEC", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -432,7 +574,7 @@
@Override
public void sendCallEvent(String callId, String event, int targetSdkVer, Bundle extras) {
try {
- Log.startSession("ICA.sCE", mOwnerComponentName);
+ Log.startSession("ICA.sCE", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -454,7 +596,7 @@
@Override
public void putExtras(String callId, Bundle extras) {
try {
- Log.startSession("ICA.pE", mOwnerComponentName);
+ Log.startSession("ICA.pE", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -476,7 +618,7 @@
@Override
public void removeExtras(String callId, List<String> keys) {
try {
- Log.startSession("ICA.rE", mOwnerComponentName);
+ Log.startSession("ICA.rE", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -498,7 +640,7 @@
@Override
public void turnOnProximitySensor() {
try {
- Log.startSession("ICA.tOnPS", mOwnerComponentName);
+ Log.startSession("ICA.tOnPS", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -515,7 +657,7 @@
@Override
public void turnOffProximitySensor(boolean screenOnImmediately) {
try {
- Log.startSession("ICA.tOffPS", mOwnerComponentName);
+ Log.startSession("ICA.tOffPS", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -616,7 +758,7 @@
public void handoverTo(String callId, PhoneAccountHandle destAcct, int videoState,
Bundle extras) {
try {
- Log.startSession("ICA.hT", mOwnerComponentName);
+ Log.startSession("ICA.hT", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 2e3ee8d..228cb3d 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -17,6 +17,9 @@
package com.android.server.telecom;
import android.Manifest;
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -24,7 +27,6 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -47,8 +49,10 @@
import com.android.internal.telecom.IInCallService;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.telecom.SystemStateHelper.SystemStateListener;
+import com.android.server.telecom.ui.NotificationChannelManager;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
@@ -56,6 +60,7 @@
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
/**
* Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
@@ -63,6 +68,8 @@
* a binding to the {@link IInCallService} (implemented by the in-call app).
*/
public class InCallController extends CallsManagerListenerBase {
+ public static final int IN_CALL_SERVICE_NOTIFICATION_ID = 3;
+ public static final String NOTIFICATION_TAG = InCallController.class.getSimpleName();
public class InCallServiceConnection {
/**
@@ -81,7 +88,7 @@
public static final int CONNECTION_NOT_SUPPORTED = 3;
public class Listener {
- public void onDisconnect(InCallServiceConnection conn) {}
+ public void onDisconnect(InCallServiceConnection conn, Call call) {}
}
protected Listener mListener;
@@ -95,6 +102,7 @@
}
public InCallServiceInfo getInfo() { return null; }
public void dump(IndentingPrintWriter pw) {}
+ public Call mCall;
}
private class InCallServiceInfo {
@@ -102,6 +110,8 @@
private boolean mIsExternalCallsSupported;
private boolean mIsSelfManagedCallsSupported;
private final int mType;
+ private long mBindingStartTime;
+ private long mDisconnectTime;
public InCallServiceInfo(ComponentName componentName,
boolean isExternalCallsSupported,
@@ -129,6 +139,22 @@
return mType;
}
+ public long getBindingStartTime() {
+ return mBindingStartTime;
+ }
+
+ public long getDisconnectTime() {
+ return mDisconnectTime;
+ }
+
+ public void setBindingStartTime(long bindingStartTime) {
+ mBindingStartTime = bindingStartTime;
+ }
+
+ public void setDisconnectTime(long disconnectTime) {
+ mDisconnectTime = disconnectTime;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -168,7 +194,7 @@
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- Log.startSession("ICSBC.oSC");
+ Log.startSession("ICSBC.oSC", Log.getPackageAbbreviation(name));
synchronized (mLock) {
try {
Log.d(this, "onServiceConnected: %s %b %b", name, mIsBound, mIsConnected);
@@ -185,7 +211,7 @@
@Override
public void onServiceDisconnected(ComponentName name) {
- Log.startSession("ICSBC.oSD");
+ Log.startSession("ICSBC.oSD", Log.getPackageAbbreviation(name));
synchronized (mLock) {
try {
Log.d(this, "onDisconnected: %s", name);
@@ -196,11 +222,42 @@
}
}
}
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ Log.startSession("ICSBC.oNB", Log.getPackageAbbreviation(name));
+ synchronized (mLock) {
+ try {
+ Log.d(this, "onNullBinding: %s", name);
+ mIsNullBinding = true;
+ mIsBound = false;
+ onDisconnected();
+ } finally {
+ Log.endSession();
+ }
+ }
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ Log.startSession("ICSBC.oBD", Log.getPackageAbbreviation(name));
+ synchronized (mLock) {
+ try {
+ Log.d(this, "onBindingDied: %s", name);
+ mIsBound = false;
+ onDisconnected();
+ } finally {
+ Log.endSession();
+ }
+ }
+ }
};
private final InCallServiceInfo mInCallServiceInfo;
private boolean mIsConnected = false;
private boolean mIsBound = false;
+ private boolean mIsNullBinding = false;
+ private NotificationManager mNotificationManager;
public InCallServiceBindingConnection(InCallServiceInfo info) {
mInCallServiceInfo = info;
@@ -232,6 +289,7 @@
Log.i(this, "Attempting to bind to InCall %s, with %s", mInCallServiceInfo, intent);
mIsConnected = true;
+ mInCallServiceInfo.setBindingStartTime(mClockProxy.elapsedRealtime());
if (!mContext.bindServiceAsUser(intent, mServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
| Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
@@ -240,11 +298,10 @@
mIsConnected = false;
}
- if (call != null && mIsConnected) {
- call.getAnalytics().addInCallService(
- mInCallServiceInfo.getComponentName().flattenToShortString(),
- mInCallServiceInfo.getType());
+ if (mIsConnected && call != null) {
+ mCall = call;
}
+ Log.i(this, "mCall: %s, mIsConnected: %s", mCall, mIsConnected);
return mIsConnected ? CONNECTION_SUCCEEDED : CONNECTION_FAILED;
}
@@ -257,9 +314,27 @@
@Override
public void disconnect() {
if (mIsConnected) {
+ mInCallServiceInfo.setDisconnectTime(mClockProxy.elapsedRealtime());
+ Log.i(InCallController.this, "ICSBC#disconnect: unbinding after %s ms;"
+ + "%s. isCrashed: %s", mInCallServiceInfo.mDisconnectTime
+ - mInCallServiceInfo.mBindingStartTime,
+ mInCallServiceInfo, mIsNullBinding);
+ String packageName = mInCallServiceInfo.getComponentName().getPackageName();
mContext.unbindService(mServiceConnection);
mIsConnected = false;
+ if (mIsNullBinding) {
+ sendCrashedInCallServiceNotification(packageName);
+ }
+ if (mCall != null) {
+ mCall.getAnalytics().addInCallService(
+ mInCallServiceInfo.getComponentName().flattenToShortString(),
+ mInCallServiceInfo.getType(),
+ mInCallServiceInfo.getDisconnectTime()
+ - mInCallServiceInfo.getBindingStartTime(), mIsNullBinding);
+ }
} else {
+ Log.i(InCallController.this, "ICSBC#disconnect: already disconnected; %s",
+ mInCallServiceInfo);
Log.addEvent(null, LogUtils.Events.INFO, "Already disconnected, ignoring request.");
}
}
@@ -271,9 +346,13 @@
@Override
public void dump(IndentingPrintWriter pw) {
- pw.append("BindingConnection [");
- pw.append(mIsConnected ? "" : "not ").append("connected, ");
- pw.append(mIsBound ? "" : "not ").append("bound]\n");
+ pw.print("BindingConnection [");
+ pw.print(mIsConnected ? "" : "not ");
+ pw.print("connected, ");
+ pw.print(mIsBound ? "" : "not ");
+ pw.print("bound, ");
+ pw.print(mInCallServiceInfo);
+ pw.println("\n");
}
protected void onConnected(IBinder service) {
@@ -292,7 +371,7 @@
InCallController.this.onDisconnected(mInCallServiceInfo);
disconnect(); // Unbind explicitly if we get disconnected.
if (mListener != null) {
- mListener.onDisconnect(InCallServiceBindingConnection.this);
+ mListener.onDisconnect(InCallServiceBindingConnection.this, mCall);
}
}
}
@@ -309,7 +388,7 @@
private Listener mSubListener = new Listener() {
@Override
- public void onDisconnect(InCallServiceConnection subConnection) {
+ public void onDisconnect(InCallServiceConnection subConnection, Call call) {
if (subConnection == mSubConnection) {
if (mIsConnected && mIsProxying) {
// At this point we know that we need to be connected to the InCallService
@@ -317,7 +396,7 @@
// just died so we need to stop proxying and connect to the system in-call
// service instead.
mIsProxying = false;
- connect(null);
+ connect(call);
}
}
}
@@ -398,7 +477,7 @@
super.onDisconnected();
// We just disconnected. Check if we are expected to be connected, and reconnect.
if (shouldReconnect && !mIsProxying) {
- connect(null); // reconnect
+ connect(mCall); // reconnect
}
}
@@ -437,7 +516,7 @@
*/
private class CarSwappingInCallServiceConnection extends InCallServiceConnection {
private final InCallServiceConnection mDialerConnection;
- private final InCallServiceConnection mCarModeConnection;
+ private InCallServiceConnection mCarModeConnection;
private InCallServiceConnection mCurrentConnection;
private boolean mIsCarMode = false;
private boolean mIsConnected = false;
@@ -450,22 +529,84 @@
mCurrentConnection = getCurrentConnection();
}
- public synchronized void setCarMode(boolean isCarMode) {
- Log.i(this, "carmodechange: " + mIsCarMode + " => " + isCarMode);
+ /**
+ * Called when we move to a state where calls are present on the device. Chooses the
+ * {@link InCallService} to which we should connect.
+ * @param isCarMode {@code true} if device is in car mode, {@code false} otherwise.
+ */
+ public synchronized void chooseInitialInCallService(boolean isCarMode) {
+ Log.i(this, "chooseInitialInCallService: " + mIsCarMode + " => " + isCarMode);
if (isCarMode != mIsCarMode) {
mIsCarMode = isCarMode;
InCallServiceConnection newConnection = getCurrentConnection();
if (newConnection != mCurrentConnection) {
if (mIsConnected) {
mCurrentConnection.disconnect();
- int result = newConnection.connect(null);
- mIsConnected = result == CONNECTION_SUCCEEDED;
}
+ int result = newConnection.connect(null);
+ mIsConnected = result == CONNECTION_SUCCEEDED;
mCurrentConnection = newConnection;
}
}
}
+ /**
+ * Invoked when {@link CarModeTracker} has determined that the device is no longer in car
+ * mode (i.e. has no car mode {@link InCallService}).
+ *
+ * Switches back to the default dialer app.
+ */
+ public synchronized void disableCarMode() {
+ mIsCarMode = false;
+ if (mIsConnected) {
+ mCurrentConnection.disconnect();
+ }
+
+ mCurrentConnection = mDialerConnection;
+ int result = mDialerConnection.connect(null);
+ mIsConnected = result == CONNECTION_SUCCEEDED;
+ }
+
+ /**
+ * Changes the active {@link InCallService} to a car mode app. Called whenever the device
+ * changes to car mode or the currently active car mode app changes.
+ * @param packageName The package name of the car mode app.
+ */
+ public synchronized void changeCarModeApp(String packageName) {
+ Log.i(this, "changeCarModeApp: isCarModeNow=" + mIsCarMode);
+
+ InCallServiceInfo currentConnectionInfo = mCurrentConnection == null ? null
+ : mCurrentConnection.getInfo();
+ InCallServiceInfo carModeConnectionInfo =
+ getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
+
+ if (!Objects.equals(currentConnectionInfo, carModeConnectionInfo)) {
+ Log.i(this, "changeCarModeApp: " + currentConnectionInfo + " => "
+ + carModeConnectionInfo);
+ if (mIsConnected) {
+ mCurrentConnection.disconnect();
+ }
+
+ if (carModeConnectionInfo != null) {
+ // Valid car mode app.
+ mCarModeConnection = mCurrentConnection =
+ new InCallServiceBindingConnection(carModeConnectionInfo);
+ mIsCarMode = true;
+ } else {
+ // Invalid car mode app; don't expect this but should handle it gracefully.
+ mCarModeConnection = null;
+ mIsCarMode = false;
+ mCurrentConnection = mDialerConnection;
+ }
+
+ int result = mCurrentConnection.connect(null);
+ mIsConnected = result == CONNECTION_SUCCEEDED;
+ } else {
+ Log.i(this, "changeCarModeApp: unchanged; " + currentConnectionInfo + " => "
+ + carModeConnectionInfo);
+ }
+ }
+
@Override
public int connect(Call call) {
if (mIsConnected) {
@@ -485,6 +626,7 @@
@Override
public void disconnect() {
if (mIsConnected) {
+ Log.i(InCallController.this, "CSICSC: disconnect %s", mCurrentConnection);
mCurrentConnection.disconnect();
mIsConnected = false;
} else {
@@ -659,6 +801,11 @@
}
@Override
+ public void onCallDirectionChanged(Call call) {
+ updateCall(call);
+ }
+
+ @Override
public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {
updateCall(call);
}
@@ -700,14 +847,9 @@
}
};
- private final SystemStateListener mSystemStateListener = new SystemStateListener() {
- @Override
- public void onCarModeChanged(boolean isCarMode) {
- if (mInCallServiceConnection != null) {
- mInCallServiceConnection.setCarMode(shouldUseCarModeUI());
- }
- }
- };
+ private final SystemStateListener mSystemStateListener =
+ (priority, packageName, isCarMode) -> InCallController.this.handleCarModeChange(
+ priority, packageName, isCarMode);
private static final int IN_CALL_SERVICE_TYPE_INVALID = 0;
private static final int IN_CALL_SERVICE_TYPE_DIALER_UI = 1;
@@ -721,9 +863,6 @@
private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId);
- /** The {@link ComponentName} of the default InCall UI. */
- private final ComponentName mSystemInCallComponentName;
-
private final Context mContext;
private final TelecomSystem.SyncRoot mLock;
private final CallsManager mCallsManager;
@@ -731,17 +870,22 @@
private final Timeouts.Adapter mTimeoutsAdapter;
private final DefaultDialerCache mDefaultDialerCache;
private final EmergencyCallHelper mEmergencyCallHelper;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
private CarSwappingInCallServiceConnection mInCallServiceConnection;
private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections;
+ private final ClockProxy mClockProxy;
// Future that's in a completed state unless we're in the middle of binding to a service.
// The future will complete with true if binding succeeds, false if it timed out.
private CompletableFuture<Boolean> mBindingFuture = CompletableFuture.completedFuture(true);
+ private final CarModeTracker mCarModeTracker;
+
public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
SystemStateHelper systemStateHelper,
DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter,
- EmergencyCallHelper emergencyCallHelper) {
+ EmergencyCallHelper emergencyCallHelper, CarModeTracker carModeTracker,
+ ClockProxy clockProxy) {
mContext = context;
mLock = lock;
mCallsManager = callsManager;
@@ -749,13 +893,9 @@
mTimeoutsAdapter = timeoutsAdapter;
mDefaultDialerCache = defaultDialerCache;
mEmergencyCallHelper = emergencyCallHelper;
-
- Resources resources = mContext.getResources();
- mSystemInCallComponentName = new ComponentName(
- TelecomServiceImpl.getSystemDialerPackage(mContext),
- resources.getString(R.string.incall_default_class));
-
+ mCarModeTracker = carModeTracker;
mSystemStateHelper.addListener(mSystemStateListener);
+ mClockProxy = clockProxy;
}
@Override
@@ -802,7 +942,7 @@
info.isExternalCallsSupported(), includeRttCall,
info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI);
try {
- inCallService.addCall(parcelableCall);
+ inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall));
} catch (RemoteException ignored) {
}
}
@@ -817,8 +957,7 @@
/** Let's add a 2 second delay before we send unbind to the services to hopefully
* give them enough time to process all the pending messages.
*/
- Handler handler = new Handler(Looper.getMainLooper());
- handler.postDelayed(new Runnable("ICC.oCR", mLock) {
+ mHandler.postDelayed(new Runnable("ICC.oCR", mLock) {
@Override
public void loggedRun() {
// Check again to make sure there are no active calls.
@@ -867,7 +1006,7 @@
info.isExternalCallsSupported(), includeRttCall,
info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI);
try {
- inCallService.addCall(parcelableCall);
+ inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall));
} catch (RemoteException ignored) {
}
}
@@ -876,7 +1015,7 @@
// The call was regular but it is now external. We must now remove it from any
// InCallServices which do not support external calls.
// Remove the call by sending a call update indicating the call was disconnected.
- Log.i(this, "Removing external call %", call);
+ Log.i(this, "Removing external call %s", call);
for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
InCallServiceInfo info = entry.getKey();
if (info.isExternalCallsSupported()) {
@@ -899,7 +1038,8 @@
);
try {
- inCallService.updateCall(parcelableCall);
+ inCallService.updateCall(
+ sanitizeParcelableCallForService(info, parcelableCall));
} catch (RemoteException ignored) {
}
}
@@ -984,6 +1124,12 @@
updateCall(call);
}
+ @Override
+ public void onCdmaConferenceSwap(Call call) {
+ Log.d(this, "onCdmaConferenceSwap %s", call);
+ updateCall(call);
+ }
+
void bringToForeground(boolean showDialpad) {
if (!mInCallServices.isEmpty()) {
for (IInCallService inCallService : mInCallServices.values()) {
@@ -1106,21 +1252,22 @@
Log.i(this, "defaultDialer: " + defaultDialerComponentInfo);
if (defaultDialerComponentInfo != null &&
!defaultDialerComponentInfo.getComponentName().equals(
- mSystemInCallComponentName)) {
+ mDefaultDialerCache.getSystemDialerComponent())) {
dialerInCall = new InCallServiceBindingConnection(defaultDialerComponentInfo);
}
Log.i(this, "defaultDialer: " + dialerInCall);
InCallServiceInfo systemInCallInfo = getInCallServiceComponent(
- mSystemInCallComponentName, IN_CALL_SERVICE_TYPE_SYSTEM_UI);
+ mDefaultDialerCache.getSystemDialerComponent(), IN_CALL_SERVICE_TYPE_SYSTEM_UI);
EmergencyInCallServiceConnection systemInCall =
new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall);
- systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall());
+ systemInCall.setHasEmergency(mCallsManager.isInEmergencyCall());
InCallServiceConnection carModeInCall = null;
- InCallServiceInfo carModeComponentInfo = getCarModeComponent();
+ InCallServiceInfo carModeComponentInfo = getCurrentCarModeComponent();
if (carModeComponentInfo != null &&
- !carModeComponentInfo.getComponentName().equals(mSystemInCallComponentName)) {
+ !carModeComponentInfo.getComponentName().equals(
+ mDefaultDialerCache.getSystemDialerComponent())) {
carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo);
}
@@ -1128,7 +1275,7 @@
new CarSwappingInCallServiceConnection(systemInCall, carModeInCall);
}
- mInCallServiceConnection.setCarMode(shouldUseCarModeUI());
+ mInCallServiceConnection.chooseInitialInCallService(shouldUseCarModeUI());
// Actually try binding to the UI InCallService. If the response
if (mInCallServiceConnection.connect(call) ==
@@ -1170,16 +1317,25 @@
private InCallServiceInfo getDefaultDialerComponent() {
String packageName = mDefaultDialerCache.getDefaultDialerApplication(
mCallsManager.getCurrentUserHandle().getIdentifier());
+ String systemPackageName = mDefaultDialerCache.getSystemDialerApplication();
Log.d(this, "Default Dialer package: " + packageName);
- return getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DIALER_UI);
+ InCallServiceInfo defaultDialerComponent =
+ (systemPackageName != null && systemPackageName.equals(packageName))
+ ? getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_SYSTEM_UI)
+ : getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DIALER_UI);
+ /* TODO: in Android 12 re-enable this an InCallService is required by the dialer role.
+ if (packageName != null && defaultDialerComponent == null) {
+ // The in call service of default phone app is disabled, send notification.
+ sendCrashedInCallServiceNotification(packageName);
+ }
+ */
+ return defaultDialerComponent;
}
- private InCallServiceInfo getCarModeComponent() {
- // The signatures of getInCallServiceComponent differ in the types of the first parameter,
- // and passing in null is inherently ambiguous. (If no car mode component found)
- String defaultCarMode = mCallsManager.getRoleManagerAdapter().getCarModeDialerApp();
- return getInCallServiceComponent(defaultCarMode, IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
+ private InCallServiceInfo getCurrentCarModeComponent() {
+ return getInCallServiceComponent(mCarModeTracker.getCurrentCarModePackage(),
+ IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
}
private InCallServiceInfo getInCallServiceComponent(ComponentName componentName, int type) {
@@ -1261,7 +1417,7 @@
}
private boolean shouldUseCarModeUI() {
- return mSystemStateHelper.isCarMode();
+ return mCarModeTracker.isInCarMode();
}
/**
@@ -1280,34 +1436,33 @@
return IN_CALL_SERVICE_TYPE_INVALID;
}
- if (mSystemInCallComponentName.getPackageName().equals(serviceInfo.packageName) &&
- mSystemInCallComponentName.getClassName().equals(serviceInfo.name)) {
+ if (mDefaultDialerCache.getSystemDialerApplication().equals(serviceInfo.packageName) &&
+ mDefaultDialerCache.getSystemDialerComponent().getClassName()
+ .equals(serviceInfo.name)) {
return IN_CALL_SERVICE_TYPE_SYSTEM_UI;
}
// Check to see if the service holds permissions or metadata for third party apps.
boolean isUIService = serviceInfo.metaData != null &&
serviceInfo.metaData.getBoolean(TelecomManager.METADATA_IN_CALL_SERVICE_UI);
- boolean isThirdPartyCompanionApp = packageManager.checkPermission(
- Manifest.permission.CALL_COMPANION_APP,
- serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED &&
- !isUIService;
// Check to see if the service is a car-mode UI type by checking that it has the
// CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the
// car-mode UI metadata.
- boolean hasControlInCallPermission = packageManager.checkPermission(
- Manifest.permission.CONTROL_INCALL_EXPERIENCE,
- serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED;
+ // We check the permission grant on all of the packages contained in the InCallService's
+ // same UID to see if any of them have been granted the permission. This accomodates the
+ // CTS tests, which have some shared UID stuff going on in order to work. It also still
+ // obeys the permission model since a single APK typically normally only has a single UID.
+ String[] uidPackages = packageManager.getPackagesForUid(serviceInfo.applicationInfo.uid);
+ boolean hasControlInCallPermission = Arrays.stream(uidPackages).anyMatch(
+ p -> packageManager.checkPermission(
+ Manifest.permission.CONTROL_INCALL_EXPERIENCE,
+ p) == PackageManager.PERMISSION_GRANTED);
boolean isCarModeUIService = serviceInfo.metaData != null &&
serviceInfo.metaData.getBoolean(
TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false);
- if (isCarModeUIService) {
- // ThirdPartyInCallService shouldn't be used when role manager hasn't assigned any car
- // mode role holders, i.e. packageName is null.
- if (hasControlInCallPermission || (isThirdPartyCompanionApp && packageName != null)) {
- return IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
- }
+ if (isCarModeUIService && hasControlInCallPermission) {
+ return IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
}
// Check to see that it is the default dialer package
@@ -1320,14 +1475,8 @@
// Also allow any in-call service that has the control-experience permission (to ensure
// that it is a system app) and doesn't claim to show any UI.
- if (!isUIService && !isCarModeUIService) {
- if (hasControlInCallPermission && !isThirdPartyCompanionApp) {
- return IN_CALL_SERVICE_TYPE_NON_UI;
- }
- // Third party companion alls without CONTROL_INCALL_EXPERIENCE permission.
- if (!hasControlInCallPermission && isThirdPartyCompanionApp) {
- return IN_CALL_SERVICE_TYPE_COMPANION;
- }
+ if (!isUIService && !isCarModeUIService && hasControlInCallPermission) {
+ return IN_CALL_SERVICE_TYPE_NON_UI;
}
// Anything else that remains, we will not bind to.
@@ -1340,7 +1489,7 @@
private void adjustServiceBindingsForEmergency() {
// The connected UI is not the system UI, so lets check if we should switch them
// if there exists an emergency number.
- if (mCallsManager.hasEmergencyCall()) {
+ if (mCallsManager.isInEmergencyCall()) {
mInCallServiceConnection.setHasEmergency(true);
}
}
@@ -1355,7 +1504,6 @@
* @return True if we successfully connected.
*/
private boolean onConnected(InCallServiceInfo info, IBinder service) {
- Trace.beginSection("onConnected: " + info.getComponentName());
Log.i(this, "onConnected to %s", info.getComponentName());
IInCallService inCallService = IInCallService.Stub.asInterface(service);
@@ -1392,13 +1540,14 @@
// Track the call if we don't already know about it.
addCall(call);
numCallsSent += 1;
- inCallService.addCall(ParcelableCallUtils.toParcelableCall(
+ ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
call,
true /* includeVideoProvider */,
mCallsManager.getPhoneAccountRegistrar(),
info.isExternalCallsSupported(),
includeRttCall,
- info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI));
+ info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI);
+ inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall));
} catch (RemoteException ignored) {
}
}
@@ -1409,7 +1558,6 @@
}
mBindingFuture.complete(true);
Log.i(this, "%s calls sent to InCallService.", numCallsSent);
- Trace.endSection();
return true;
}
@@ -1468,7 +1616,8 @@
componentsUpdated.add(componentName);
try {
- inCallService.updateCall(parcelableCall);
+ inCallService.updateCall(
+ sanitizeParcelableCallForService(info, parcelableCall));
} catch (RemoteException ignored) {
}
}
@@ -1521,6 +1670,8 @@
mInCallServiceConnection.dump(pw);
}
pw.decreaseIndent();
+
+ mCarModeTracker.dump(pw);
}
/**
@@ -1597,4 +1748,91 @@
childCalls.addAll(parentCalls);
return childCalls;
}
+
+ private ParcelableCall sanitizeParcelableCallForService(
+ InCallServiceInfo info, ParcelableCall parcelableCall) {
+ ParcelableCall.ParcelableCallBuilder builder =
+ ParcelableCall.ParcelableCallBuilder.fromParcelableCall(parcelableCall);
+ // Check for contacts permission. If it's not there, remove the contactsDisplayName.
+ PackageManager pm = mContext.getPackageManager();
+ if (pm.checkPermission(Manifest.permission.READ_CONTACTS,
+ info.getComponentName().getPackageName()) != PackageManager.PERMISSION_GRANTED) {
+ builder.setContactDisplayName(null);
+ }
+
+ // TODO: move all the other service-specific sanitizations in here
+ return builder.createParcelableCall();
+ }
+
+ @VisibleForTesting
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ /**
+ * Determines if the specified package is a valid car mode {@link InCallService}.
+ * @param packageName The package name to check.
+ * @return {@code true} if the package has a valid car mode {@link InCallService} defined,
+ * {@code false} otherwise.
+ */
+ private boolean isCarModeInCallService(@NonNull String packageName) {
+ InCallServiceInfo info =
+ getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
+ return info != null && info.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
+ }
+
+ public void handleCarModeChange(int priority, String packageName, boolean isCarMode) {
+ Log.i(this, "handleCarModeChange: packageName=%s, priority=%d, isCarMode=%b",
+ packageName, priority, isCarMode);
+ if (!isCarModeInCallService(packageName)) {
+ Log.i(this, "handleCarModeChange: not a valid InCallService; packageName=%s",
+ packageName);
+ return;
+ }
+
+ if (isCarMode) {
+ mCarModeTracker.handleEnterCarMode(priority, packageName);
+ } else {
+ mCarModeTracker.handleExitCarMode(priority, packageName);
+ }
+
+ if (mInCallServiceConnection != null) {
+ Log.i(this, "handleCarModeChange: car mode apps: %s",
+ mCarModeTracker.getCarModeApps().stream().collect(Collectors.joining(", ")));
+ if (shouldUseCarModeUI()) {
+ mInCallServiceConnection.changeCarModeApp(
+ mCarModeTracker.getCurrentCarModePackage());
+ } else {
+ mInCallServiceConnection.disableCarMode();
+ }
+ }
+ }
+
+ private void sendCrashedInCallServiceNotification(String packageName) {
+ PackageManager packageManager = mContext.getPackageManager();
+ CharSequence appName;
+ try {
+ appName = packageManager.getApplicationLabel(
+ packageManager.getApplicationInfo(packageName, 0));
+ if (TextUtils.isEmpty(appName)) {
+ appName = packageName;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ appName = packageName;
+ }
+ NotificationManager notificationManager = (NotificationManager) mContext
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ Notification.Builder builder = new Notification.Builder(mContext,
+ NotificationChannelManager.CHANNEL_ID_IN_CALL_SERVICE_CRASH);
+ builder.setSmallIcon(R.drawable.ic_phone)
+ .setColor(mContext.getResources().getColor(R.color.theme_color))
+ .setContentTitle(
+ mContext.getString(
+ R.string.notification_incallservice_not_responding_title, appName))
+ .setStyle(new Notification.BigTextStyle()
+ .bigText(mContext.getText(
+ R.string.notification_incallservice_not_responding_body)));
+ notificationManager.notify(NOTIFICATION_TAG, IN_CALL_SERVICE_NOTIFICATION_ID,
+ builder.build());
+ }
}
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 5864ce0..4f0cf8d 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -502,7 +502,7 @@
public void loggedRun() {
if (sTonesPlaying == 0) {
Log.wtf(this, "Over-releasing focus for tone player.");
- } else if (--sTonesPlaying == 0) {
+ } else if (--sTonesPlaying == 0 && mCallAudioManager != null) {
mCallAudioManager.setIsTonePlaying(false);
}
}
diff --git a/src/com/android/server/telecom/InCallWakeLockController.java b/src/com/android/server/telecom/InCallWakeLockController.java
index ac93bb5..b37883d 100644
--- a/src/com/android/server/telecom/InCallWakeLockController.java
+++ b/src/com/android/server/telecom/InCallWakeLockController.java
@@ -62,7 +62,7 @@
private void handleWakeLock() {
// We grab a full lock as long as there exists a ringing call.
- Call ringingCall = mCallsManager.getRingingCall();
+ Call ringingCall = mCallsManager.getRingingOrSimulatedRingingCall();
if (ringingCall != null) {
mTelecomWakeLock.acquire();
Log.i(this, "Acquiring full wake lock");
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index 760d24e..5bb14e6 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -17,9 +17,13 @@
package com.android.server.telecom;
import android.content.Context;
+import android.os.SystemClock;
import android.telecom.Logging.EventManager;
import android.telecom.Logging.EventManager.TimedEventPair;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Temporary location of new Logging class
*/
@@ -31,15 +35,43 @@
public static final boolean SYSTRACE_DEBUG = false; /* STOP SHIP if true */
+ public static class EventTimer {
+ private long mLastElapsedMillis;
+ private Map<String, Long> mTimings = new HashMap<>();
+
+ public EventTimer() {
+ mLastElapsedMillis = SystemClock.elapsedRealtime();
+ }
+
+ public void record(String label) {
+ long newElapsedMillis = SystemClock.elapsedRealtime();
+ mTimings.put(label, newElapsedMillis - mLastElapsedMillis);
+ mLastElapsedMillis = newElapsedMillis;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (Map.Entry<String, Long> entry : mTimings.entrySet()) {
+ sb.append(entry.getKey()).append(": ").append(entry.getValue()).append(", ");
+ }
+ return sb.toString();
+ }
+ }
+
public static final class Sessions {
public static final String ICA_ANSWER_CALL = "ICA.aC";
public static final String ICA_DEFLECT_CALL = "ICA.defC";
public static final String ICA_REJECT_CALL = "ICA.rC";
+ public static final String ICA_TRANSFER_CALL = "ICA.tC";
+ public static final String ICA_CONSULTATIVE_TRANSFER = "ICA.cT";
public static final String ICA_DISCONNECT_CALL = "ICA.dC";
public static final String ICA_HOLD_CALL = "ICA.hC";
public static final String ICA_UNHOLD_CALL = "ICA.uC";
public static final String ICA_MUTE = "ICA.m";
public static final String ICA_SET_AUDIO_ROUTE = "ICA.sAR";
+ public static final String ICA_ENTER_AUDIO_PROCESSING = "ICA.enBAP";
+ public static final String ICA_EXIT_AUDIO_PROCESSING = "ICA.exBAP";
public static final String ICA_CONFERENCE = "ICA.c";
public static final String CSW_HANDLE_CREATE_CONNECTION_COMPLETE = "CSW.hCCC";
public static final String CSW_SET_ACTIVE = "CSW.sA";
@@ -73,8 +105,13 @@
public static final String REQUEST_UNHOLD = "REQUEST_UNHOLD";
public static final String REQUEST_DISCONNECT = "REQUEST_DISCONNECT";
public static final String REQUEST_ACCEPT = "REQUEST_ACCEPT";
+ public static final String REQUEST_SIMULATED_ACCEPT = "REQUEST_SIMULATED_ACCEPT";
+ public static final String REQUEST_PICKUP_FOR_AUDIO_PROCESSING =
+ "REQUEST_PICKUP_FOR_AUDIO_PROCESSING";
public static final String REQUEST_DEFLECT = "REQUEST_DEFLECT";
public static final String REQUEST_REJECT = "REQUEST_REJECT";
+ public static final String REQUEST_TRANSFER = "REQUEST_TRANSFER";
+ public static final String REQUEST_CONSULTATIVE_TRANSFER = "REQUEST_CONSULTATIVE_TRANSFER";
public static final String START_DTMF = "START_DTMF";
public static final String STOP_DTMF = "STOP_DTMF";
public static final String START_RINGER = "START_RINGER";
@@ -87,15 +124,19 @@
public static final String STOP_CALL_WAITING_TONE = "STOP_CALL_WAITING_TONE";
public static final String START_CONNECTION = "START_CONNECTION";
public static final String CREATE_CONNECTION_FAILED = "CREATE_CONNECTION_FAILED";
+ public static final String START_CONFERENCE = "START_CONFERENCE";
+ public static final String CREATE_CONFERENCE_FAILED = "CREATE_CONFERENCE_FAILED";
public static final String BIND_CS = "BIND_CS";
public static final String CS_BOUND = "CS_BOUND";
public static final String CONFERENCE_WITH = "CONF_WITH";
public static final String SPLIT_FROM_CONFERENCE = "CONF_SPLIT";
public static final String SWAP = "SWAP";
+ public static final String ADD_PARTICIPANT = "ADD_PARTICIPANT";
public static final String ADD_CHILD = "ADD_CHILD";
public static final String REMOVE_CHILD = "REMOVE_CHILD";
public static final String SET_PARENT = "SET_PARENT";
public static final String CONF_STATE_CHANGED = "CONF_STATE_CHANGED";
+ public static final String CALL_DIRECTION_CHANGED = "CALL_DIRECTION_CHANGED";
public static final String MUTE = "MUTE";
public static final String UNMUTE = "UNMUTE";
public static final String AUDIO_ROUTE = "AUDIO_ROUTE";
@@ -109,6 +150,7 @@
public static final String BIND_SCREENING = "BIND_SCREENING";
public static final String SCREENING_BOUND = "SCREENING_BOUND";
public static final String SCREENING_SENT = "SCREENING_SENT";
+ public static final String SCREENING_SKIPPED = "SCREENING_SKIPPED";
public static final String CONTROLLER_SCREENING_COMPLETED =
"CONTROLLER_SCREENING_COMPLETED";
public static final String SCREENING_COMPLETED = "SCREENING_COMPLETED";
diff --git a/src/com/android/server/telecom/MissedCallNotifier.java b/src/com/android/server/telecom/MissedCallNotifier.java
index 500122b..0e5a287 100644
--- a/src/com/android/server/telecom/MissedCallNotifier.java
+++ b/src/com/android/server/telecom/MissedCallNotifier.java
@@ -20,7 +20,7 @@
import android.os.UserHandle;
import android.telecom.PhoneAccountHandle;
-import com.android.internal.telephony.CallerInfo;
+import android.telecom.CallerInfo;
/**
* Creates a notification for calls that the user missed (neither answered nor rejected).
@@ -75,11 +75,11 @@
}
public String getPhoneNumber() {
- return mCallerInfo == null ? null : mCallerInfo.phoneNumber;
+ return mCallerInfo == null ? null : mCallerInfo.getPhoneNumber();
}
public String getName() {
- return mCallerInfo == null ? null : mCallerInfo.name;
+ return mCallerInfo == null ? null : mCallerInfo.getName();
}
}
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index 7a641af..f0efe92 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -21,6 +21,7 @@
import android.app.Activity;
import android.app.BroadcastOptions;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -35,6 +36,7 @@
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telephony.DisconnectCause;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
@@ -71,11 +73,12 @@
public static final String EXTRA_GATEWAY_URI = "com.android.phone.extra.GATEWAY_URI";
private final CallsManager mCallsManager;
- private final Call mCall;
+ private Call mCall;
private final Intent mIntent;
private final Context mContext;
private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
private final TelecomSystem.SyncRoot mLock;
+ private final DefaultDialerCache mDefaultDialerCache;
/*
* Whether or not the outgoing call intent originated from the default phone application. If
@@ -97,16 +100,16 @@
}
@VisibleForTesting
- public NewOutgoingCallIntentBroadcaster(Context context, CallsManager callsManager, Call call,
+ public NewOutgoingCallIntentBroadcaster(Context context, CallsManager callsManager,
Intent intent, PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
- boolean isDefaultPhoneApp) {
+ boolean isDefaultPhoneApp, DefaultDialerCache defaultDialerCache) {
mContext = context;
mCallsManager = callsManager;
- mCall = call;
mIntent = intent;
mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
mIsDefaultOrSystemPhoneApp = isDefaultPhoneApp;
mLock = mCallsManager.getLock();
+ mDefaultDialerCache = defaultDialerCache;
}
/**
@@ -137,8 +140,7 @@
disconnectTimeout = getDisconnectTimeoutFromApp(
getResultExtras(false), disconnectTimeout);
endEarly = true;
- } else if (mPhoneNumberUtilsAdapter.isPotentialLocalEmergencyNumber(
- mContext, resultNumber)) {
+ } else if (isPotentialEmergencyNumber(resultNumber)) {
Log.w(this, "Cannot modify outgoing call to emergency number %s.",
resultNumber);
disconnectTimeout = 0;
@@ -328,7 +330,8 @@
return number;
}
- public void processCall(CallDisposition disposition) {
+ public void processCall(Call call, CallDisposition disposition) {
+ mCall = call;
if (disposition.callImmediately) {
boolean speakerphoneOn = mIntent.getBooleanExtra(
TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false);
@@ -492,10 +495,9 @@
private void launchSystemDialer(Uri handle) {
Intent systemDialerIntent = new Intent();
- final Resources resources = mContext.getResources();
- systemDialerIntent.setClassName(
- TelecomServiceImpl.getSystemDialerPackage(mContext),
- resources.getString(R.string.dialer_default_class));
+ systemDialerIntent.setComponent(
+ new ComponentName(mDefaultDialerCache.getSystemDialerApplication(),
+ mContext.getResources().getString(R.string.dialer_default_class)));
systemDialerIntent.setAction(Intent.ACTION_DIAL);
systemDialerIntent.setData(handle);
systemDialerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -519,8 +521,14 @@
*/
private boolean isPotentialEmergencyNumber(String number) {
Log.v(this, "Checking restrictions for number : %s", Log.pii(number));
- return (number != null)
- && mPhoneNumberUtilsAdapter.isPotentialLocalEmergencyNumber(mContext, number);
+ if (number == null) return false;
+ try {
+ return mContext.getSystemService(TelephonyManager.class).isPotentialEmergencyNumber(
+ number);
+ } catch (Exception e) {
+ Log.e(this, e, "isPotentialEmergencyNumber: Telephony threw an exception.");
+ return false;
+ }
}
/**
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index 6a66ccf..5c821a4 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -181,22 +181,12 @@
parentCallId = parentCall.getId();
}
- long connectTimeMillis = call.getConnectTimeMillis();
List<Call> childCalls = call.getChildCalls();
List<String> childCallIds = new ArrayList<>();
if (!childCalls.isEmpty()) {
- long childConnectTimeMillis = Long.MAX_VALUE;
for (Call child : childCalls) {
- if (child.getConnectTimeMillis() > 0) {
- childConnectTimeMillis = Math.min(child.getConnectTimeMillis(),
- childConnectTimeMillis);
- }
childCallIds.add(child.getId());
}
-
- if (childConnectTimeMillis != Long.MAX_VALUE) {
- connectTimeMillis = childConnectTimeMillis;
- }
}
Uri handle = call.getHandlePresentation() == TelecomManager.PRESENTATION_ALLOWED ?
@@ -220,6 +210,11 @@
callDirection = DIRECTION_OUTGOING;
}
+ String activeChildCallId = null;
+ if (call.getConferenceLevelActiveCall() != null) {
+ activeChildCallId = call.getConferenceLevelActiveCall().getId();
+ }
+
Bundle extras;
if (isForSystemDialer) {
extras = call.getExtras();
@@ -227,34 +222,38 @@
extras = sanitizeExtras(call.getExtras());
}
- return new ParcelableCall(
- call.getId(),
- state,
- call.getDisconnectCause(),
- call.getCannedSmsResponses(),
- capabilities,
- properties,
- supportedAudioRoutes,
- connectTimeMillis,
- handle,
- call.getHandlePresentation(),
- callerDisplayName,
- call.getCallerDisplayNamePresentation(),
- call.getGatewayInfo(),
- call.getTargetPhoneAccount(),
- includeVideoProvider,
- includeVideoProvider ? call.getVideoProvider() : null,
- includeRttCall,
- rttCall,
- parentCallId,
- childCallIds,
- call.getStatusHints(),
- call.getVideoState(),
- conferenceableCallIds,
- call.getIntentExtras(),
- extras,
- call.getCreationTimeMillis(),
- callDirection);
+ return new ParcelableCall.ParcelableCallBuilder()
+ .setId(call.getId())
+ .setState(state)
+ .setDisconnectCause(call.getDisconnectCause())
+ .setCannedSmsResponses(call.getCannedSmsResponses())
+ .setCapabilities(capabilities)
+ .setProperties(properties)
+ .setSupportedAudioRoutes(supportedAudioRoutes)
+ .setConnectTimeMillis(call.getConnectTimeMillis())
+ .setHandle(handle)
+ .setHandlePresentation(call.getHandlePresentation())
+ .setCallerDisplayName(callerDisplayName)
+ .setCallerDisplayNamePresentation(call.getCallerDisplayNamePresentation())
+ .setGatewayInfo(call.getGatewayInfo())
+ .setAccountHandle(call.getTargetPhoneAccount())
+ .setIsVideoCallProviderChanged(includeVideoProvider)
+ .setVideoCallProvider(includeVideoProvider ? call.getVideoProvider() : null)
+ .setIsRttCallChanged(includeRttCall)
+ .setRttCall(rttCall)
+ .setParentCallId(parentCallId)
+ .setChildCallIds(childCallIds)
+ .setStatusHints(call.getStatusHints())
+ .setVideoState(call.getVideoState())
+ .setConferenceableCallIds(conferenceableCallIds)
+ .setIntentExtras(call.getIntentExtras())
+ .setExtras(extras)
+ .setCreationTimeMillis(call.getCreationTimeMillis())
+ .setCallDirection(callDirection)
+ .setCallerNumberVerificationStatus(call.getCallerNumberVerificationStatus())
+ .setContactDisplayName(call.getName())
+ .setActiveChildCallId(activeChildCallId)
+ .createParcelableCall();
}
/**
@@ -267,6 +266,7 @@
* <li>Connection time</li>
* <li>Handle (phone number)</li>
* <li>Handle (phone number) presentation</li>
+ * <li>Caller number verification status (verstat)</li>
* </ul>
* All other fields are nulled or set to 0 values.
* Where the call screening service is part of the system dialer, the
@@ -297,34 +297,38 @@
callExtras = new Bundle();
}
- return new ParcelableCall(
- call.getId(),
- getParcelableState(call, false /* supportsExternalCalls */),
- new DisconnectCause(DisconnectCause.UNKNOWN),
- null, /* cannedSmsResponses */
- 0, /* capabilities */
- 0, /* properties */
- 0, /* supportedAudioRoutes */
- call.getConnectTimeMillis(),
- handle,
- call.getHandlePresentation(),
- null, /* callerDisplayName */
- 0 /* callerDisplayNamePresentation */,
- null, /* gatewayInfo */
- null, /* targetPhoneAccount */
- false, /* includeVideoProvider */
- null, /* videoProvider */
- false, /* includeRttCall */
- null, /* rttCall */
- null, /* parentCallId */
- null, /* childCallIds */
- null, /* statusHints */
- 0, /* videoState */
- Collections.emptyList(), /* conferenceableCallIds */
- null, /* intentExtras */
- callExtras, /* callExtras */
- call.getCreationTimeMillis(),
- callDirection);
+ return new ParcelableCall.ParcelableCallBuilder()
+ .setId(call.getId())
+ .setState(getParcelableState(call, false /* supportsExternalCalls */))
+ .setDisconnectCause(new DisconnectCause(DisconnectCause.UNKNOWN))
+ .setCannedSmsResponses(null)
+ .setCapabilities(0)
+ .setProperties(0)
+ .setSupportedAudioRoutes(0)
+ .setConnectTimeMillis(call.getConnectTimeMillis())
+ .setHandle(handle)
+ .setHandlePresentation(call.getHandlePresentation())
+ .setCallerDisplayName(null)
+ .setCallerDisplayNamePresentation(0)
+ .setGatewayInfo(null)
+ .setAccountHandle(null)
+ .setIsVideoCallProviderChanged(false)
+ .setVideoCallProvider(null)
+ .setIsRttCallChanged(false)
+ .setRttCall(null)
+ .setParentCallId(null)
+ .setChildCallIds(null)
+ .setStatusHints(null)
+ .setVideoState(0)
+ .setConferenceableCallIds(Collections.emptyList())
+ .setIntentExtras(null)
+ .setExtras(callExtras)
+ .setCreationTimeMillis(call.getCreationTimeMillis())
+ .setCallDirection(callDirection)
+ .setCallerNumberVerificationStatus(call.getCallerNumberVerificationStatus())
+ .setContactDisplayName(null)
+ .setActiveChildCallId(null)
+ .createParcelableCall();
}
/**
@@ -377,7 +381,7 @@
private static int getParcelableState(Call call, boolean supportsExternalCalls) {
int state = CallState.NEW;
- switch (call.getState()) {
+ switch (call.getParcelableCallState()) {
case CallState.ABORTED:
case CallState.DISCONNECTED:
state = android.telecom.Call.STATE_DISCONNECTED;
@@ -421,14 +425,14 @@
case CallState.SELECT_PHONE_ACCOUNT:
state = android.telecom.Call.STATE_SELECT_PHONE_ACCOUNT;
break;
+ case CallState.AUDIO_PROCESSING:
+ state = android.telecom.Call.STATE_AUDIO_PROCESSING;
+ break;
+ case CallState.SIMULATED_RINGING:
+ state = android.telecom.Call.STATE_SIMULATED_RINGING;
+ break;
}
- // If we are marked as 'locally disconnecting' then mark ourselves as disconnecting instead.
- // Unless we're disconnect*ED*, in which case leave it at that.
- if (call.isLocallyDisconnecting() &&
- (state != android.telecom.Call.STATE_DISCONNECTED)) {
- state = android.telecom.Call.STATE_DISCONNECTING;
- }
return state;
}
@@ -494,7 +498,16 @@
android.telecom.Call.Details.CAPABILITY_CAN_PULL_CALL,
Connection.CAPABILITY_SUPPORT_DEFLECT,
- android.telecom.Call.Details.CAPABILITY_SUPPORT_DEFLECT
+ android.telecom.Call.Details.CAPABILITY_SUPPORT_DEFLECT,
+
+ Connection.CAPABILITY_ADD_PARTICIPANT,
+ android.telecom.Call.Details.CAPABILITY_ADD_PARTICIPANT,
+
+ Connection.CAPABILITY_TRANSFER,
+ android.telecom.Call.Details.CAPABILITY_TRANSFER,
+
+ Connection.CAPABILITY_TRANSFER_CONSULTATIVE,
+ android.telecom.Call.Details.CAPABILITY_TRANSFER_CONSULTATIVE
};
private static int convertConnectionToCallCapabilities(int connectionCapabilities) {
@@ -531,14 +544,17 @@
Connection.PROPERTY_SELF_MANAGED,
android.telecom.Call.Details.PROPERTY_SELF_MANAGED,
- Connection.PROPERTY_ASSISTED_DIALING_USED,
- android.telecom.Call.Details.PROPERTY_ASSISTED_DIALING_USED,
+ Connection.PROPERTY_ASSISTED_DIALING,
+ android.telecom.Call.Details.PROPERTY_ASSISTED_DIALING,
Connection.PROPERTY_IS_RTT,
android.telecom.Call.Details.PROPERTY_RTT,
Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL,
- android.telecom.Call.Details.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL
+ android.telecom.Call.Details.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL,
+
+ Connection.PROPERTY_IS_ADHOC_CONFERENCE,
+ android.telecom.Call.Details.PROPERTY_IS_ADHOC_CONFERENCE
};
private static int convertConnectionToCallProperties(int connectionProperties) {
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index 16eaa97..0e06fba 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -130,14 +130,6 @@
PhoneAccount phoneAccount) {}
}
- /**
- * Abstracts away dependency on the {@link PackageManager} required to fetch the label for an
- * app.
- */
- public interface AppLabelProxy {
- CharSequence getAppLabel(String packageName);
- }
-
public static final String FILE_NAME = "phone-account-registrar-state.xml";
@VisibleForTesting
public static final int EXPECTED_STATE_VERSION = 9;
@@ -155,6 +147,7 @@
private final AppLabelProxy mAppLabelProxy;
private State mState;
private UserHandle mCurrentUserHandle;
+ private String mTestPhoneAccountPackageNameFilter;
private interface PhoneAccountRegistrarWriteLock {}
private final PhoneAccountRegistrarWriteLock mWriteLock =
new PhoneAccountRegistrarWriteLock() {};
@@ -195,7 +188,7 @@
if (account != null && account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
TelephonyManager tm =
(TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- return tm.getSubIdForPhoneAccount(account);
+ return tm.getSubscriptionId(accountHandle);
}
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
@@ -356,7 +349,7 @@
if (newSubId != currentVoiceSubId) {
Log.i(this, "setUserSelectedOutgoingPhoneAccount: update voice sub; "
+ "account=%s, subId=%d", accountHandle, newSubId);
- mSubscriptionManager.setDefaultVoiceSubId(newSubId);
+ mSubscriptionManager.setDefaultVoiceSubscriptionId(newSubId);
}
} else {
Log.i(this, "setUserSelectedOutgoingPhoneAccount: %s is not a sub", accountHandle);
@@ -459,6 +452,34 @@
}
/**
+ * Sets a filter for which {@link PhoneAccount}s will be returned from
+ * {@link #filterRestrictedPhoneAccounts(List)}. If non-null, only {@link PhoneAccount}s
+ * with the package name packageNameFilter will be returned. If null, no filter is set.
+ * @param packageNameFilter The package name that will be used to filter only
+ * {@link PhoneAccount}s with the same package name.
+ */
+ public void setTestPhoneAccountPackageNameFilter(String packageNameFilter) {
+ mTestPhoneAccountPackageNameFilter = packageNameFilter;
+ Log.i(this, "filter set for PhoneAccounts, packageName=" + packageNameFilter);
+ }
+
+ /**
+ * Filter the given {@link List<PhoneAccount>} and keep only {@link PhoneAccount}s that have the
+ * #mTestPhoneAccountPackageNameFilter.
+ * @param accounts List of {@link PhoneAccount}s to filter.
+ * @return new list of filtered {@link PhoneAccount}s.
+ */
+ public List<PhoneAccount> filterRestrictedPhoneAccounts(List<PhoneAccount> accounts) {
+ if (TextUtils.isEmpty(mTestPhoneAccountPackageNameFilter)) {
+ return new ArrayList<>(accounts);
+ }
+ // Remove all PhoneAccounts that do not have the same package name as the filter.
+ return accounts.stream().filter(account -> mTestPhoneAccountPackageNameFilter.equals(
+ account.getAccountHandle().getComponentName().getPackageName()))
+ .collect(Collectors.toList());
+ }
+
+ /**
* If it is a outgoing call, sim call manager associated with the target phone account of the
* call is returned (if one exists).
* Otherwise, we return the sim call manager of the user associated with the
@@ -488,6 +509,14 @@
*/
public PhoneAccountHandle getSimCallManagerFromHandle(PhoneAccountHandle targetPhoneAccount,
UserHandle userHandle) {
+ // First, check if the specified target phone account handle is a connection manager; if
+ // it is, then just return it.
+ PhoneAccount phoneAccount = getPhoneAccountUnchecked(targetPhoneAccount);
+ if (phoneAccount != null
+ && phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) {
+ return targetPhoneAccount;
+ }
+
int subId = getSubscriptionIdForPhoneAccount(targetPhoneAccount);
if (SubscriptionManager.isValidSubscriptionId(subId)
&& subId != SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
@@ -650,7 +679,7 @@
public List<PhoneAccountHandle> getCallCapablePhoneAccounts(
String uriScheme, boolean includeDisabledAccounts, UserHandle userHandle) {
return getCallCapablePhoneAccounts(uriScheme, includeDisabledAccounts, userHandle,
- 0 /* capabilities */);
+ 0 /* capabilities */, PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY);
}
/**
@@ -667,10 +696,10 @@
*/
public List<PhoneAccountHandle> getCallCapablePhoneAccounts(
String uriScheme, boolean includeDisabledAccounts, UserHandle userHandle,
- int capabilities) {
+ int capabilities, int excludedCapabilities) {
return getPhoneAccountHandles(
PhoneAccount.CAPABILITY_CALL_PROVIDER | capabilities,
- PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY /*excludedCapabilities*/,
+ excludedCapabilities /*excludedCapabilities*/,
uriScheme, null, includeDisabledAccounts, userHandle);
}
@@ -1221,6 +1250,9 @@
pw.println(phoneAccount);
}
pw.decreaseIndent();
+ pw.increaseIndent();
+ pw.println("test emergency PhoneAccount filter: " + mTestPhoneAccountPackageNameFilter);
+ pw.decreaseIndent();
}
}
diff --git a/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java b/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java
index aa568a9..28d832d 100644
--- a/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java
+++ b/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java
@@ -24,8 +24,6 @@
* refactoring.
*/
public interface PhoneNumberUtilsAdapter {
- boolean isLocalEmergencyNumber(Context context, String number);
- boolean isPotentialLocalEmergencyNumber(Context context, String number);
boolean isUriNumber(String number);
boolean isSamePhoneNumber(String number1, String number2);
String getNumberFromIntent(Intent intent, Context context);
diff --git a/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java b/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java
index 0b19f09..517857b 100644
--- a/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java
+++ b/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java
@@ -23,28 +23,6 @@
public class PhoneNumberUtilsAdapterImpl implements PhoneNumberUtilsAdapter {
@Override
- public boolean isLocalEmergencyNumber(Context context, String number) {
- try {
- TelephonyManager tm = (TelephonyManager) context.getSystemService(
- Context.TELEPHONY_SERVICE);
- return tm.isEmergencyNumber(number);
- } catch (IllegalStateException ise) {
- return false;
- }
- }
-
- @Override
- public boolean isPotentialLocalEmergencyNumber(Context context, String number) {
- try {
- TelephonyManager tm = (TelephonyManager) context.getSystemService(
- Context.TELEPHONY_SERVICE);
- return tm.isPotentialEmergencyNumber(number);
- } catch (IllegalStateException ise) {
- return false;
- }
- }
-
- @Override
public boolean isUriNumber(String number) {
return PhoneNumberUtils.isUriNumber(number);
}
@@ -68,4 +46,4 @@
public String stripSeparators(String number) {
return PhoneNumberUtils.stripSeparators(number);
}
-}
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/PhoneStateBroadcaster.java b/src/com/android/server/telecom/PhoneStateBroadcaster.java
index d574589..f2531ed 100644
--- a/src/com/android/server/telecom/PhoneStateBroadcaster.java
+++ b/src/com/android/server/telecom/PhoneStateBroadcaster.java
@@ -16,12 +16,9 @@
package com.android.server.telecom;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.telecom.Log;
import android.telephony.TelephonyManager;
-
-import com.android.internal.telephony.ITelephonyRegistry;
+import android.telephony.TelephonyRegistryManager;
/**
* Send a {@link TelephonyManager#ACTION_PHONE_STATE_CHANGED} broadcast when the call state
@@ -30,13 +27,12 @@
final class PhoneStateBroadcaster extends CallsManagerListenerBase {
private final CallsManager mCallsManager;
- private final ITelephonyRegistry mRegistry;
+ private final TelephonyRegistryManager mRegistry;
private int mCurrentState = TelephonyManager.CALL_STATE_IDLE;
public PhoneStateBroadcaster(CallsManager callsManager) {
mCallsManager = callsManager;
- mRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
- "telephony.registry"));
+ mRegistry = callsManager.getContext().getSystemService(TelephonyRegistryManager.class);
if (mRegistry == null) {
Log.w(this, "TelephonyRegistry is null");
}
@@ -85,7 +81,7 @@
// Note: CallsManager#hasRingingCall() and CallsManager#getFirstCallWithState(..) do not
// consider external calls, so an external call is going to cause the state to be idle.
int callState = TelephonyManager.CALL_STATE_IDLE;
- if (mCallsManager.hasRingingCall()) {
+ if (mCallsManager.hasRingingOrSimulatedRingingCall()) {
callState = TelephonyManager.CALL_STATE_RINGING;
} else if (mCallsManager.getFirstCallWithState(CallState.DIALING, CallState.PULLING,
CallState.ACTIVE, CallState.ON_HOLD) != null) {
@@ -112,13 +108,9 @@
callHandle = call.getHandle().getSchemeSpecificPart();
}
- try {
- if (mRegistry != null) {
- mRegistry.notifyCallState(phoneState, callHandle);
- Log.i(this, "Broadcasted state change: %s", mCurrentState);
- }
- } catch (RemoteException e) {
- Log.w(this, "RemoteException when notifying TelephonyRegistry of call state change.");
+ if (mRegistry != null) {
+ mRegistry.notifyCallStateChangedForAllSubscriptions(phoneState, callHandle);
+ Log.i(this, "Broadcasted state change: %s", mCurrentState);
}
}
}
diff --git a/src/com/android/server/telecom/RespondViaSmsManager.java b/src/com/android/server/telecom/RespondViaSmsManager.java
index ac9ee3c..00d94f0 100644
--- a/src/com/android/server/telecom/RespondViaSmsManager.java
+++ b/src/com/android/server/telecom/RespondViaSmsManager.java
@@ -211,8 +211,9 @@
!TextUtils.isEmpty(contactName) ? contactName : phoneNumber,
messageParts.size());
context.registerReceiver(receiver, new IntentFilter(ACTION_MESSAGE_SENT));
- smsManager.sendMultipartTextMessageExternal(phoneNumber, null, messageParts,
- sentIntents/*sentIntent*/, null /*deliveryIntent*/, context.getOpPackageName());
+ smsManager.sendMultipartTextMessage(phoneNumber, null, messageParts,
+ sentIntents/*sentIntent*/, null /*deliveryIntent*/, context.getOpPackageName(),
+ context.getAttributionTag());
} catch (IllegalArgumentException e) {
Log.w(RespondViaSmsManager.this, "Couldn't send SMS message: " +
e.getMessage());
diff --git a/src/com/android/server/telecom/RespondViaSmsSettings.java b/src/com/android/server/telecom/RespondViaSmsSettings.java
index 3bee5f7..6d7c5c6 100644
--- a/src/com/android/server/telecom/RespondViaSmsSettings.java
+++ b/src/com/android/server/telecom/RespondViaSmsSettings.java
@@ -18,16 +18,18 @@
import android.app.ActionBar;
import android.app.Activity;
-import android.content.Context;
+import android.app.AlertDialog;
import android.content.SharedPreferences;
-import android.telecom.Log;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
-import android.view.Menu;
+import android.telecom.Log;
+import android.text.Editable;
+import android.text.TextWatcher;
import android.view.MenuItem;
+import android.widget.Button;
// TODO: This class is newly copied into Telecom (com.android.server.telecom) from it previous
// location in Telephony (com.android.phone). User's preferences stored in the old location
@@ -103,11 +105,15 @@
// (Watch out: onPreferenceChange() is called *before* the
// Preference itself gets updated, so we need to use newValue here
// rather than pref.getText().)
- pref.setTitle((String) newValue);
+ // If the newValue is an empty string, skip this to avoid setting an empty response.
+ // TODO: Show a popup to inform user that response didn't set because it's empty.
+ if (((String) newValue).length() != 0) {
+ pref.setTitle((String) newValue);
- // Save the new preference value.
- SharedPreferences.Editor editor = mPrefs.edit();
- editor.putString(pref.getKey(), (String) newValue).commit();
+ // Save the new preference value.
+ SharedPreferences.Editor editor = mPrefs.edit();
+ editor.putString(pref.getKey(), (String) newValue).commit();
+ }
// If the user just reset the quick response to its original text, clear the pref.
QuickResponseUtils.maybeResetQuickResponses(this, mPrefs);
@@ -141,6 +147,33 @@
EditTextPreference pref = (EditTextPreference) preference;
pref.setText(mPrefs.getString(pref.getKey(), pref.getText()));
pref.setTitle(pref.getText());
+ pref.getEditText().addTextChangedListener(new TextWatcher() {
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ try {
+ Button button = ((AlertDialog) pref.getDialog())
+ .getButton(AlertDialog.BUTTON_POSITIVE);
+ if (s.toString().length() == 0) {
+ button.setEnabled(false);
+ } else {
+ button.setEnabled(true);
+ }
+ } catch (NullPointerException e) {
+ Log.d(this, e.toString());
+ }
+ }
+ });
pref.setOnPreferenceChangeListener(this);
}
}
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index d62a580..a769a94 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -32,6 +32,7 @@
import android.os.Vibrator;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.LogUtils.EventTimer;
import java.util.ArrayList;
import java.util.Arrays;
@@ -73,6 +74,9 @@
private static final int[] PULSE_AMPLITUDE;
+ private static final int RAMPING_RINGER_VIBRATION_DURATION = 5000;
+ private static final int RAMPING_RINGER_DURATION = 10000;
+
static {
// construct complete pulse pattern
PULSE_PATTERN = new long[PULSE_PRIMING_PATTERN.length + PULSE_RAMPING_PATTERN.length];
@@ -110,13 +114,6 @@
private static final int REPEAT_SIMPLE_VIBRATION_AT = 1;
- private static final int DEFAULT_RAMPING_RINGER_DURATION = 10000; // 10 seconds
-
- private int mRampingRingerDuration = -1; // ramping ringer duration in millisecond
-
- // vibration duration before ramping ringer in second
- private int mRampingRingerVibrationDuration = 0;
-
private static final float EPSILON = 1e-6f;
private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
@@ -210,7 +207,8 @@
return false;
}
- if (foregroundCall.getState() != CallState.RINGING) {
+ 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",
@@ -220,14 +218,32 @@
AudioManager audioManager =
(AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ LogUtils.EventTimer timer = new EventTimer();
boolean isVolumeOverZero = audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0;
+ timer.record("isVolumeOverZero");
boolean shouldRingForContact = shouldRingForContact(foregroundCall.getContactUri());
+ timer.record("shouldRingForContact");
boolean isRingtonePresent = !(mRingtoneFactory.getRingtone(foregroundCall) == null);
+ timer.record("getRingtone");
boolean isSelfManaged = foregroundCall.isSelfManaged();
+ timer.record("isSelfManaged");
boolean isSilentRingingRequested = foregroundCall.isSilentRingingRequested();
+ timer.record("isSilentRingRequested");
boolean isRingerAudible = isVolumeOverZero && shouldRingForContact && isRingtonePresent;
+ timer.record("isRingerAudible");
boolean hasExternalRinger = hasExternalRinger(foregroundCall);
+ timer.record("hasExternalRinger");
+ // Don't do call waiting operations or vibration unless these are false.
+ boolean isTheaterModeOn = mSystemSettingsUtil.isTheaterModeOn(mContext);
+ timer.record("isTheaterModeOn");
+ boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging();
+ timer.record("letDialerHandleRinging");
+
+ Log.i(this, "startRinging timings: " + timer);
+ boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged ||
+ hasExternalRinger || isSilentRingingRequested;
+
// Acquire audio focus under any of the following conditions:
// 1. Should ring for contact and there's an HFP device attached
// 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone
@@ -236,12 +252,6 @@
boolean shouldAcquireAudioFocus =
isRingerAudible || (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged;
- // Don't do call waiting operations or vibration unless these are false.
- boolean isTheaterModeOn = mSystemSettingsUtil.isTheaterModeOn(mContext);
- boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging();
- boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged ||
- hasExternalRinger || isSilentRingingRequested;
-
if (endEarly) {
if (letDialerHandleRinging) {
Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Dialer handles");
@@ -273,36 +283,18 @@
// 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 (mSystemSettingsUtil.applyRampingRinger(mContext)
- && mSystemSettingsUtil.enableRampingRingerFromDeviceConfig()) {
+ if (mSystemSettingsUtil.applyRampingRinger(mContext)) {
Log.i(this, "start ramping ringer.");
- // configure vibration effect for ramping ringer.
- int previousRampingRingerVibrationDuration = mRampingRingerVibrationDuration;
- // get vibration duration in millisecond and round down to second.
- mRampingRingerVibrationDuration =
- mSystemSettingsUtil.getRampingRingerVibrationDuration() >= 0
- ? mSystemSettingsUtil.getRampingRingerVibrationDuration() / 1000
- : 0;
if (mSystemSettingsUtil.enableAudioCoupledVibrationForRampingRinger()) {
effect = getVibrationEffectForCall(mRingtoneFactory, foregroundCall);
} else {
effect = mDefaultVibrationEffect;
}
-
- // configure volume shaper for ramping ringer
- int previousRampingRingerDuration = mRampingRingerDuration;
- mRampingRingerDuration =
- mSystemSettingsUtil.getRampingRingerDuration() > 0
- ? mSystemSettingsUtil.getRampingRingerDuration()
- : DEFAULT_RAMPING_RINGER_DURATION;
- if (mRampingRingerDuration != previousRampingRingerDuration
- || mRampingRingerVibrationDuration != previousRampingRingerVibrationDuration
- || mVolumeShaperConfig == null) {
- float silencePoint = (float) (mRampingRingerVibrationDuration * 1000)
- / (float) (mRampingRingerVibrationDuration * 1000 + mRampingRingerDuration);
+ if (mVolumeShaperConfig == null) {
+ float silencePoint = (float) (RAMPING_RINGER_VIBRATION_DURATION)
+ / (float) (RAMPING_RINGER_VIBRATION_DURATION + RAMPING_RINGER_DURATION);
mVolumeShaperConfig = new VolumeShaper.Configuration.Builder()
- .setDuration(mRampingRingerVibrationDuration * 1000
- + mRampingRingerDuration)
+ .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)
@@ -333,7 +325,6 @@
maybeStartVibration(foregroundCall, shouldRingForContact, effect,
isVibratorEnabled, isRingerAudible);
} else if (mSystemSettingsUtil.applyRampingRinger(mContext)
- && mSystemSettingsUtil.enableRampingRingerFromDeviceConfig()
&& !mSystemSettingsUtil.enableAudioCoupledVibrationForRampingRinger()) {
Log.i(this, "startRinging: apply ramping ringer vibration");
maybeStartVibration(foregroundCall, shouldRingForContact, effect,
@@ -363,15 +354,14 @@
if (isVibrationEnabled
&& !mIsVibrating && shouldRingForContact) {
if (mSystemSettingsUtil.applyRampingRinger(mContext)
- && mSystemSettingsUtil.enableRampingRingerFromDeviceConfig()
&& isRingerAudible) {
Log.i(this, "start vibration for ramping ringer.");
- mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
mIsVibrating = true;
+ mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
} else {
Log.i(this, "start normal vibration.");
- mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
mIsVibrating = true;
+ mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
}
} else if (mIsVibrating) {
Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION, "already vibrating");
@@ -468,6 +458,10 @@
}
}
+ public boolean isRinging() {
+ return mRingtonePlayer.isPlaying();
+ }
+
private boolean shouldRingForContact(Uri contactUri) {
final NotificationManager manager =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -521,7 +515,6 @@
return false;
}
return mSystemSettingsUtil.canVibrateWhenRinging(context)
- || (mSystemSettingsUtil.applyRampingRinger(context)
- && mSystemSettingsUtil.enableRampingRingerFromDeviceConfig());
+ || mSystemSettingsUtil.applyRampingRinger(context);
}
}
diff --git a/src/com/android/server/telecom/RingtoneFactory.java b/src/com/android/server/telecom/RingtoneFactory.java
index 4ea5008..d3d2d0e 100644
--- a/src/com/android/server/telecom/RingtoneFactory.java
+++ b/src/com/android/server/telecom/RingtoneFactory.java
@@ -21,7 +21,6 @@
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.media.AudioAttributes;
-import android.media.AudioManager;
import android.media.RingtoneManager;
import android.media.Ringtone;
import android.media.VolumeShaper;
@@ -31,11 +30,10 @@
import android.provider.Settings;
import android.telecom.Log;
-import android.telecom.PhoneAccount;
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.CallerInfo;
+import android.telecom.CallerInfo;
import java.util.List;
diff --git a/src/com/android/server/telecom/RoleManagerAdapter.java b/src/com/android/server/telecom/RoleManagerAdapter.java
index a04ae57..ba82a06 100644
--- a/src/com/android/server/telecom/RoleManagerAdapter.java
+++ b/src/com/android/server/telecom/RoleManagerAdapter.java
@@ -102,18 +102,6 @@
void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded);
/**
- * @return Package name of the car more app or {@code null} if there are no apps that match.
- */
- String getCarModeDialerApp();
-
- /**
- * Override the automotive app with another value. Used for testing purposes only.
- * @param packageName Package name of the automotive app. Where
- * {@code null}, the override is removed.
- */
- void setTestAutoModeApp(String packageName);
-
- /**
* Using role manager needs to know the current user handle. Need to make sure the role manager
* adapter can pass this to role manager. As it changes, we'll pass it in.
* @param currentUserHandle The new user handle.
diff --git a/src/com/android/server/telecom/RoleManagerAdapterImpl.java b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
index 6f3fddd..eb216d0 100644
--- a/src/com/android/server/telecom/RoleManagerAdapterImpl.java
+++ b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
@@ -38,7 +38,6 @@
private String mOverrideDefaultCallRedirectionApp = null;
private String mOverrideDefaultCallScreeningApp = null;
- private String mOverrideDefaultCarModeApp = null;
private String mOverrideDefaultDialerApp = null;
private List<String> mOverrideCallCompanionApps = new ArrayList<>();
private Context mContext;
@@ -112,19 +111,6 @@
}
@Override
- public String getCarModeDialerApp() {
- if (mOverrideDefaultCarModeApp != null) {
- return mOverrideDefaultCarModeApp;
- }
- return getRoleManagerCarModeDialerApp();
- }
-
- @Override
- public void setTestAutoModeApp(String packageName) {
- mOverrideDefaultCarModeApp = packageName;
- }
-
- @Override
public void setCurrentUserHandle(UserHandle currentUserHandle) {
mCurrentUserHandle = currentUserHandle;
}
@@ -147,11 +133,6 @@
return roleHolders.get(0);
}
- // TODO in R: query and return car mode apps
- private String getRoleManagerCarModeDialerApp() {
- return null;
- }
-
// TODO in R: Use companion app manager
private List<String> getRoleManagerCallCompanionApps() {
return new ArrayList<>();
@@ -213,15 +194,6 @@
}
pw.println();
- pw.print("DefaultCarModeDialerApp: ");
- if (mOverrideDefaultCarModeApp != null) {
- pw.print("(override ");
- pw.print(mOverrideDefaultCarModeApp);
- pw.print(") ");
- pw.print(getRoleManagerCarModeDialerApp());
- }
- pw.println();
-
pw.print("DefaultCallCompanionApps: ");
if (mOverrideCallCompanionApps != null) {
pw.print("(override ");
diff --git a/src/com/android/server/telecom/ServiceBinder.java b/src/com/android/server/telecom/ServiceBinder.java
index bd992e5..7274993 100644
--- a/src/com/android/server/telecom/ServiceBinder.java
+++ b/src/com/android/server/telecom/ServiceBinder.java
@@ -120,7 +120,8 @@
public void binderDied() {
try {
synchronized (mLock) {
- Log.startSession("SDR.bD");
+ Log.startSession("SDR.bD",
+ Log.getPackageAbbreviation(mComponentName));
Log.i(this, "binderDied: ConnectionService %s died.", mComponentName);
logServiceDisconnected("binderDied");
handleDisconnect();
@@ -144,7 +145,7 @@
@Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
try {
- Log.startSession("SBC.oSC");
+ Log.startSession("SBC.oSC", Log.getPackageAbbreviation(componentName));
synchronized (mLock) {
Log.i(this, "Service bound %s", componentName);
@@ -181,7 +182,7 @@
@Override
public void onServiceDisconnected(ComponentName componentName) {
try {
- Log.startSession("SBC.oSD");
+ Log.startSession("SBC.oSD", Log.getPackageAbbreviation(componentName));
synchronized (mLock) {
logServiceDisconnected("onServiceDisconnected");
handleDisconnect();
@@ -235,6 +236,11 @@
/** The component name of the service to bind to. */
protected final ComponentName mComponentName;
+ /**
+ * Abbreviated form of the package name from {@link #mComponentName}; used for session logging.
+ */
+ protected final String mPackageAbbreviation;
+
/** The set of callbacks waiting for notification of the binding's success or failure. */
private final Set<BindCallback> mCallbacks = new ArraySet<>();
@@ -284,6 +290,7 @@
mLock = lock;
mServiceAction = serviceAction;
mComponentName = componentName;
+ mPackageAbbreviation = Log.getPackageAbbreviation(componentName);
mUserHandle = userHandle;
}
diff --git a/src/com/android/server/telecom/SystemSettingsUtil.java b/src/com/android/server/telecom/SystemSettingsUtil.java
index 62e1a68..f104f27 100644
--- a/src/com/android/server/telecom/SystemSettingsUtil.java
+++ b/src/com/android/server/telecom/SystemSettingsUtil.java
@@ -30,16 +30,6 @@
@VisibleForTesting
public class SystemSettingsUtil {
- /** Flag for ringer ramping time in milliseconds. */
- private static final String RAMPING_RINGER_DURATION_MILLIS = "ramping_ringer_duration";
-
- /** Flag for vibration time in milliseconds before ramping ringer starts. */
- private static final String RAMPING_RINGER_VIBRATION_DURATION =
- "ramping_ringer_vibration_duration";
-
- /** Flag for whether or not to apply ramping ringer on incoming phone calls. */
- private static final String RAMPING_RINGER_ENABLED = "ramping_ringer_enabled";
-
/** Flag for whether or not to support audio coupled haptics in ramping ringer. */
private static final String RAMPING_RINGER_AUDIO_COUPLED_VIBRATION_ENABLED =
"ramping_ringer_audio_coupled_vibration_enabled";
@@ -69,26 +59,11 @@
Settings.Global.APPLY_RAMPING_RINGER, 0) == 1;
}
- public boolean enableRampingRingerFromDeviceConfig() {
- return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TELEPHONY, RAMPING_RINGER_ENABLED,
- false);
- }
-
public boolean enableAudioCoupledVibrationForRampingRinger() {
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TELEPHONY,
RAMPING_RINGER_AUDIO_COUPLED_VIBRATION_ENABLED, false);
}
- public int getRampingRingerDuration() {
- return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TELEPHONY,
- RAMPING_RINGER_DURATION_MILLIS, -1);
- }
-
- public int getRampingRingerVibrationDuration() {
- return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TELEPHONY,
- RAMPING_RINGER_VIBRATION_DURATION, 0);
- }
-
public boolean isHapticPlaybackSupported(Context context) {
return context.getSystemService(AudioManager.class).isHapticPlaybackSupported();
}
diff --git a/src/com/android/server/telecom/SystemStateHelper.java b/src/com/android/server/telecom/SystemStateHelper.java
index 69a46c6..073b081 100644
--- a/src/com/android/server/telecom/SystemStateHelper.java
+++ b/src/com/android/server/telecom/SystemStateHelper.java
@@ -39,7 +39,15 @@
*/
public class SystemStateHelper {
public static interface SystemStateListener {
- public void onCarModeChanged(boolean isCarMode);
+ /**
+ * Listener method to inform interested parties when a package name requests to enter or
+ * exit car mode.
+ * @param priority the priority of the enter/exit request.
+ * @param packageName the package name of the requester.
+ * @param isCarMode {@code true} if the package is entering car mode, {@code false}
+ * otherwise.
+ */
+ void onCarModeChanged(int priority, String packageName, boolean isCarMode);
}
private final Context mContext;
@@ -49,10 +57,22 @@
Log.startSession("SSP.oR");
try {
String action = intent.getAction();
- if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(action)) {
- onEnterCarMode();
- } else if (UiModeManager.ACTION_EXIT_CAR_MODE.equals(action)) {
- onExitCarMode();
+ if (UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED.equals(action)) {
+ int priority = intent.getIntExtra(UiModeManager.EXTRA_PRIORITY,
+ UiModeManager.DEFAULT_PRIORITY);
+ String callingPackage = intent.getStringExtra(
+ UiModeManager.EXTRA_CALLING_PACKAGE);
+ Log.i(SystemStateHelper.this, "ENTER_CAR_MODE_PRIVILEGED; priority=%d, pkg=%s",
+ priority, callingPackage);
+ onEnterCarMode(priority, callingPackage);
+ } else if (UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED.equals(action)) {
+ int priority = intent.getIntExtra(UiModeManager.EXTRA_PRIORITY,
+ UiModeManager.DEFAULT_PRIORITY);
+ String callingPackage = intent.getStringExtra(
+ UiModeManager.EXTRA_CALLING_PACKAGE);
+ Log.i(SystemStateHelper.this, "EXIT_CAR_MODE_PRIVILEGED; priority=%d, pkg=%s",
+ priority, callingPackage);
+ onExitCarMode(priority, callingPackage);
} else {
Log.w(this, "Unexpected intent received: %s", intent.getAction());
}
@@ -68,8 +88,9 @@
public SystemStateHelper(Context context) {
mContext = context;
- IntentFilter intentFilter = new IntentFilter(UiModeManager.ACTION_ENTER_CAR_MODE);
- intentFilter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE);
+ IntentFilter intentFilter = new IntentFilter(
+ UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED);
+ intentFilter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED);
mContext.registerReceiver(mBroadcastReceiver, intentFilter);
Log.i(this, "Registering car mode receiver: %s", intentFilter);
@@ -170,25 +191,19 @@
}
}
- private void onEnterCarMode() {
- if (!mIsCarMode) {
- Log.i(this, "Entering carmode");
- mIsCarMode = true;
- notifyCarMode();
- }
- }
-
- private void onExitCarMode() {
- if (mIsCarMode) {
- Log.i(this, "Exiting carmode");
- mIsCarMode = false;
- notifyCarMode();
- }
- }
-
- private void notifyCarMode() {
+ private void onEnterCarMode(int priority, String packageName) {
+ Log.i(this, "Entering carmode");
+ mIsCarMode = getSystemCarMode();
for (SystemStateListener listener : mListeners) {
- listener.onCarModeChanged(mIsCarMode);
+ listener.onCarModeChanged(priority, packageName, true /* isCarMode */);
+ }
+ }
+
+ private void onExitCarMode(int priority, String packageName) {
+ Log.i(this, "Exiting carmode");
+ mIsCarMode = getSystemCarMode();
+ for (SystemStateListener listener : mListeners) {
+ listener.onCarModeChanged(priority, packageName, false /* isCarMode */);
}
}
diff --git a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
index ca44cd4..e1f2d08 100644
--- a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
+++ b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
@@ -18,11 +18,16 @@
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.os.UserHandle;
import android.telecom.Log;
+import android.widget.Toast;
-import com.android.server.telecom.ui.CallRedirectionConfirmDialogActivity;
import com.android.server.telecom.ui.ConfirmCallDialogActivity;
+import com.android.server.telecom.ui.DisconnectedCallNotifier;
+
+import java.util.List;
public final class TelecomBroadcastIntentProcessor {
/** The action used to send SMS response for the missed call notification. */
@@ -33,6 +38,14 @@
public static final String ACTION_CALL_BACK_FROM_NOTIFICATION =
"com.android.server.telecom.ACTION_CALL_BACK_FROM_NOTIFICATION";
+ /** The action used to send SMS response for the disconnected call notification. */
+ public static final String ACTION_DISCONNECTED_SEND_SMS_FROM_NOTIFICATION =
+ "com.android.server.telecom.ACTION_DISCONNECTED_SEND_SMS_FROM_NOTIFICATION";
+
+ /** The action used to call a handle back for the disconnected call notification. */
+ public static final String ACTION_DISCONNECTED_CALL_BACK_FROM_NOTIFICATION =
+ "com.android.server.telecom.ACTION_DISCONNECTED_CALL_BACK_FROM_NOTIFICATION";
+
/** The action used to clear missed calls. */
public static final String ACTION_CLEAR_MISSED_CALLS =
"com.android.server.telecom.ACTION_CLEAR_MISSED_CALLS";
@@ -66,27 +79,31 @@
"com.android.server.telecom.CANCEL_CALL";
/**
- * The action used to proceed with a redirected call being confirmed via
- * {@link com.android.server.telecom.ui.CallRedirectionConfirmDialogActivity}.
+ * The action used to proceed with a redirected call being confirmed via the call redirection
+ * confirmation dialog.
*/
public static final String ACTION_PLACE_REDIRECTED_CALL =
"com.android.server.telecom.PROCEED_WITH_REDIRECTED_CALL";
/**
- * The action used to confirm to proceed the call without redirection via
- * {@link com.android.server.telecom.ui.CallRedirectionConfirmDialogActivity}.
+ * The action used to confirm to proceed the call without redirection via the call redirection
+ * confirmation dialog.
*/
public static final String ACTION_PLACE_UNREDIRECTED_CALL =
"com.android.server.telecom.PROCEED_WITH_UNREDIRECTED_CALL";
/**
- * The action used to cancel a redirected call being confirmed via
- * {@link com.android.server.telecom.ui.CallRedirectionConfirmDialogActivity}.
+ * The action used to cancel a redirected call being confirmed via the call redirection
+ * confirmation dialog.
*/
public static final String ACTION_CANCEL_REDIRECTED_CALL =
"com.android.server.telecom.CANCEL_REDIRECTED_CALL";
public static final String EXTRA_USERHANDLE = "userhandle";
+ public static final String EXTRA_REDIRECTION_OUTGOING_CALL_ID =
+ "android.telecom.extra.REDIRECTION_OUTGOING_CALL_ID";
+ public static final String EXTRA_REDIRECTION_APP_NAME =
+ "android.telecom.extra.REDIRECTION_APP_NAME";
private final Context mContext;
private final CallsManager mCallsManager;
@@ -116,26 +133,45 @@
// Close the notification shade and the notification itself.
closeSystemDialogs(mContext);
missedCallNotifier.clearMissedCalls(userHandle);
-
- Intent callIntent = new Intent(Intent.ACTION_SENDTO, intent.getData());
- callIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivityAsUser(callIntent, userHandle);
+ sendSmsIntent(intent, userHandle);
// Call back recent caller from the missed call notification.
} else if (ACTION_CALL_BACK_FROM_NOTIFICATION.equals(action)) {
// Close the notification shade and the notification itself.
closeSystemDialogs(mContext);
missedCallNotifier.clearMissedCalls(userHandle);
-
- Intent callIntent = new Intent(Intent.ACTION_CALL, intent.getData());
- callIntent.setFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- mContext.startActivityAsUser(callIntent, userHandle);
+ sendCallBackIntent(intent, userHandle);
// Clear the missed call notification and call log entries.
} else if (ACTION_CLEAR_MISSED_CALLS.equals(action)) {
missedCallNotifier.clearMissedCalls(userHandle);
}
+ } else if(ACTION_DISCONNECTED_SEND_SMS_FROM_NOTIFICATION.equals(action) ||
+ ACTION_DISCONNECTED_CALL_BACK_FROM_NOTIFICATION.equals(action)) {
+ Log.v(this, "Action received: %s.", action);
+ UserHandle userHandle = intent.getParcelableExtra(EXTRA_USERHANDLE);
+ if (userHandle == null) {
+ Log.d(this, "disconnect user handle can't be null, not processing the broadcast");
+ return;
+ }
+
+ DisconnectedCallNotifier disconnectedCallNotifier =
+ mCallsManager.getDisconnectedCallNotifier();
+
+ // Send an SMS from the disconnected call notification.
+ if (ACTION_DISCONNECTED_SEND_SMS_FROM_NOTIFICATION.equals(action)) {
+ // Close the notification shade and the notification itself.
+ closeSystemDialogs(mContext);
+ disconnectedCallNotifier.clearNotification(userHandle);
+ sendSmsIntent(intent, userHandle);
+
+ // Call back recent caller from the disconnected call notification.
+ } else if (ACTION_DISCONNECTED_CALL_BACK_FROM_NOTIFICATION.equals(action)) {
+ // Close the notification shade and the notification itself.
+ closeSystemDialogs(mContext);
+ disconnectedCallNotifier.clearNotification(userHandle);
+ sendCallBackIntent(intent, userHandle);
+ }
} else if (ACTION_ANSWER_FROM_NOTIFICATION.equals(action)) {
Log.startSession("TBIP.aAFM");
try {
@@ -181,8 +217,7 @@
Log.startSession("TBIP.aPRC");
try {
mCallsManager.processRedirectedOutgoingCallAfterUserInteraction(
- intent.getStringExtra(CallRedirectionConfirmDialogActivity
- .EXTRA_REDIRECTION_OUTGOING_CALL_ID),
+ intent.getStringExtra(EXTRA_REDIRECTION_OUTGOING_CALL_ID),
ACTION_PLACE_REDIRECTED_CALL);
} finally {
Log.endSession();
@@ -191,8 +226,7 @@
Log.startSession("TBIP.aPUC");
try {
mCallsManager.processRedirectedOutgoingCallAfterUserInteraction(
- intent.getStringExtra(CallRedirectionConfirmDialogActivity
- .EXTRA_REDIRECTION_OUTGOING_CALL_ID),
+ intent.getStringExtra(EXTRA_REDIRECTION_OUTGOING_CALL_ID),
ACTION_PLACE_UNREDIRECTED_CALL);
} finally {
Log.endSession();
@@ -201,8 +235,7 @@
Log.startSession("TBIP.aCRC");
try {
mCallsManager.processRedirectedOutgoingCallAfterUserInteraction(
- intent.getStringExtra(CallRedirectionConfirmDialogActivity
- .EXTRA_REDIRECTION_OUTGOING_CALL_ID),
+ intent.getStringExtra(EXTRA_REDIRECTION_OUTGOING_CALL_ID),
ACTION_CANCEL_REDIRECTED_CALL);
} finally {
Log.endSession();
@@ -217,4 +250,25 @@
Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
context.sendBroadcastAsUser(intent, UserHandle.ALL);
}
+
+ private void sendSmsIntent(Intent intent, UserHandle userHandle) {
+ Intent callIntent = new Intent(Intent.ACTION_SENDTO, intent.getData());
+ callIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ PackageManager packageManager = mContext.getPackageManager();
+ List<ResolveInfo> activities = packageManager.queryIntentActivitiesAsUser(
+ callIntent, PackageManager.MATCH_DEFAULT_ONLY, userHandle.getIdentifier());
+ if (activities.size() > 0) {
+ mContext.startActivityAsUser(callIntent, userHandle);
+ } else {
+ Toast.makeText(mContext, com.android.internal.R.string.noApplications,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void sendCallBackIntent(Intent intent, UserHandle userHandle) {
+ Intent callIntent = new Intent(Intent.ACTION_CALL, intent.getData());
+ callIntent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ mContext.startActivityAsUser(callIntent, userHandle);
+ }
}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index d3bbab2..f286f2f 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -20,8 +20,10 @@
import static android.Manifest.permission.CALL_PRIVILEGED;
import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.MODIFY_PHONE_STATE;
+import static android.Manifest.permission.READ_PHONE_NUMBERS;
import static android.Manifest.permission.READ_PHONE_STATE;
import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
+import static android.Manifest.permission.READ_SMS;
import static android.Manifest.permission.REGISTER_SIM_SUBSCRIPTION;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
@@ -34,7 +36,6 @@
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.res.Resources;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.net.Uri;
@@ -43,6 +44,7 @@
import android.os.Bundle;
import android.os.Process;
import android.os.UserHandle;
+import android.provider.BlockedNumberContract;
import android.provider.Settings;
import android.telecom.Log;
import android.telecom.PhoneAccount;
@@ -56,6 +58,7 @@
import android.util.EventLog;
import com.android.internal.telecom.ITelecomService;
+import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.telecom.components.UserCallIntentProcessorFactory;
import com.android.server.telecom.settings.BlockedNumbersActivity;
@@ -110,18 +113,15 @@
private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() {
@Override
public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme,
- String callingPackage) {
+ String callingPackage, String callingFeatureId) {
try {
Log.startSession("TSI.gDOPA");
synchronized (mLock) {
- if (!canReadPhoneState(callingPackage, "getDefaultOutgoingPhoneAccount")) {
- return null;
- }
-
+ PhoneAccountHandle phoneAccountHandle = null;
final UserHandle callingUserHandle = Binder.getCallingUserHandle();
long token = Binder.clearCallingIdentity();
try {
- return mPhoneAccountRegistrar
+ phoneAccountHandle = mPhoneAccountRegistrar
.getOutgoingPhoneAccountForScheme(uriScheme, callingUserHandle);
} catch (Exception e) {
Log.e(this, e, "getDefaultOutgoingPhoneAccount");
@@ -129,6 +129,14 @@
} finally {
Binder.restoreCallingIdentity(token);
}
+ if (isCallerSimCallManager(phoneAccountHandle)
+ || canReadPhoneState(
+ callingPackage,
+ callingFeatureId,
+ "getDefaultOutgoingPhoneAccount")) {
+ return phoneAccountHandle;
+ }
+ return null;
}
} finally {
Log.endSession();
@@ -181,10 +189,16 @@
@Override
public ParceledListSlice<PhoneAccountHandle> getCallCapablePhoneAccounts(
- boolean includeDisabledAccounts, String callingPackage) {
+ boolean includeDisabledAccounts, String callingPackage, String callingFeatureId) {
try {
Log.startSession("TSI.gCCPA");
- if (!canReadPhoneState(callingPackage, "getDefaultOutgoingPhoneAccount")) {
+ if (includeDisabledAccounts &&
+ !canReadPrivilegedPhoneState(
+ callingPackage, "getCallCapablePhoneAccounts")) {
+ return ParceledListSlice.emptyList();
+ }
+ if (!canReadPhoneState(callingPackage, callingFeatureId,
+ "getCallCapablePhoneAccounts")) {
return ParceledListSlice.emptyList();
}
synchronized (mLock) {
@@ -208,10 +222,11 @@
@Override
public ParceledListSlice<PhoneAccountHandle> getSelfManagedPhoneAccounts(
- String callingPackage) {
+ String callingPackage, String callingFeatureId) {
try {
Log.startSession("TSI.gSMPA");
- if (!canReadPhoneState(callingPackage, "Requires READ_PHONE_STATE permission.")) {
+ if (!canReadPhoneState(callingPackage, callingFeatureId,
+ "Requires READ_PHONE_STATE permission.")) {
throw new SecurityException("Requires READ_PHONE_STATE permission.");
}
synchronized (mLock) {
@@ -232,11 +247,10 @@
}
}
-
@Override
public ParceledListSlice<PhoneAccountHandle> getPhoneAccountsSupportingScheme(
String uriScheme, String callingPackage) {
- try {
+ try {
Log.startSession("TSI.gPASS");
try {
enforceModifyPermission(
@@ -471,8 +485,8 @@
try {
Log.startSession("TSI.rPA");
synchronized (mLock) {
- if (!mContext.getApplicationContext().getResources().getBoolean(
- com.android.internal.R.bool.config_voice_capable)) {
+ if (!((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
+ .isVoiceCapable()) {
Log.w(this,
"registerPhoneAccount not allowed on non-voice capable device.");
return;
@@ -511,6 +525,18 @@
if (callingUid != Process.SHELL_UID) {
enforceUserHandleMatchesCaller(account.getAccountHandle());
}
+
+ if (TextUtils.isEmpty(account.getGroupId())
+ && mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE)
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.w(this, "registerPhoneAccount - attempt to set a"
+ + " group from a non-system caller.");
+ // Not permitted to set group, so null it out.
+ account = new PhoneAccount.Builder(account)
+ .setGroupId(null)
+ .build();
+ }
+
final long token = Binder.clearCallingIdentity();
try {
mPhoneAccountRegistrar.registerPhoneAccount(account);
@@ -572,11 +598,11 @@
*/
@Override
public boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number,
- String callingPackage) {
+ String callingPackage, String callingFeatureId) {
try {
Log.startSession("TSI.iVMN");
synchronized (mLock) {
- if (!canReadPhoneState(callingPackage, "isVoiceMailNumber")) {
+ if (!canReadPhoneState(callingPackage, callingFeatureId, "isVoiceMailNumber")) {
return false;
}
final UserHandle callingUserHandle = Binder.getCallingUserHandle();
@@ -604,31 +630,32 @@
* @see android.telecom.TelecomManager#getVoiceMailNumber
*/
@Override
- public String getVoiceMailNumber(PhoneAccountHandle accountHandle, String callingPackage) {
+ public String getVoiceMailNumber(PhoneAccountHandle accountHandle, String callingPackage,
+ String callingFeatureId) {
try {
Log.startSession("TSI.gVMN");
- synchronized (mLock) {
- if (!canReadPhoneState(callingPackage, "getVoiceMailNumber")) {
+ if (!canReadPhoneState(callingPackage, callingFeatureId, "getVoiceMailNumber")) {
+ return null;
+ }
+ try {
+ final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ if (!isPhoneAccountHandleVisibleToCallingUser(accountHandle,
+ callingUserHandle)) {
+ Log.d(this, "%s is not visible for the calling user [gVMN]",
+ accountHandle);
return null;
}
- try {
- final UserHandle callingUserHandle = Binder.getCallingUserHandle();
- if (!isPhoneAccountHandleVisibleToCallingUser(accountHandle,
- callingUserHandle)) {
- Log.d(this, "%s is not visible for the calling user [gVMN]",
- accountHandle);
- return null;
- }
- int subId = mSubscriptionManagerAdapter.getDefaultVoiceSubId();
+ int subId = mSubscriptionManagerAdapter.getDefaultVoiceSubId();
+ synchronized (mLock) {
if (accountHandle != null) {
subId = mPhoneAccountRegistrar
.getSubscriptionIdForPhoneAccount(accountHandle);
}
- return getTelephonyManager().getVoiceMailNumber(subId);
- } catch (Exception e) {
- Log.e(this, e, "getSubscriptionIdForPhoneAccount");
- throw e;
}
+ return getTelephonyManager(subId).getVoiceMailNumber();
+ } catch (Exception e) {
+ Log.e(this, e, "getSubscriptionIdForPhoneAccount");
+ throw e;
}
} finally {
Log.endSession();
@@ -639,32 +666,34 @@
* @see android.telecom.TelecomManager#getLine1Number
*/
@Override
- public String getLine1Number(PhoneAccountHandle accountHandle, String callingPackage) {
+ public String getLine1Number(PhoneAccountHandle accountHandle, String callingPackage,
+ String callingFeatureId) {
try {
Log.startSession("getL1N");
- if (!canReadPhoneState(callingPackage, "getLine1Number")) {
+ if (!canReadPhoneNumbers(callingPackage, callingFeatureId, "getLine1Number")) {
return null;
}
- synchronized (mLock) {
- final UserHandle callingUserHandle = Binder.getCallingUserHandle();
- if (!isPhoneAccountHandleVisibleToCallingUser(accountHandle,
- callingUserHandle)) {
- Log.d(this, "%s is not visible for the calling user [gL1N]", accountHandle);
- return null;
- }
+ final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ if (!isPhoneAccountHandleVisibleToCallingUser(accountHandle,
+ callingUserHandle)) {
+ Log.d(this, "%s is not visible for the calling user [gL1N]", accountHandle);
+ return null;
+ }
- long token = Binder.clearCallingIdentity();
- try {
- int subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(
+ long token = Binder.clearCallingIdentity();
+ try {
+ int subId;
+ synchronized (mLock) {
+ subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(
accountHandle);
- return getTelephonyManager().getLine1Number(subId);
- } catch (Exception e) {
- Log.e(this, e, "getSubscriptionIdForPhoneAccount");
- throw e;
- } finally {
- Binder.restoreCallingIdentity(token);
}
+ return getTelephonyManager(subId).getLine1Number();
+ } catch (Exception e) {
+ Log.e(this, e, "getSubscriptionIdForPhoneAccount");
+ throw e;
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
} finally {
Log.endSession();
@@ -704,11 +733,7 @@
public ComponentName getDefaultPhoneApp() {
try {
Log.startSession("TSI.gDPA");
- // No need to synchronize
- Resources resources = mContext.getResources();
- return new ComponentName(
- TelecomServiceImpl.getSystemDialerPackage(mContext),
- resources.getString(R.string.dialer_default_class));
+ return mDefaultDialerCache.getSystemDialerComponent();
} finally {
Log.endSession();
}
@@ -737,13 +762,56 @@
}
/**
+ * @param userId user id to get the default dialer package for
+ * @return the package name of the current user-selected default dialer. If no default
+ * has been selected, the package name of the system dialer is returned. If
+ * neither exists, then {@code null} is returned.
+ * @see android.telecom.TelecomManager#getDefaultDialerPackage
+ */
+ @Override
+ public String getDefaultDialerPackageForUser(int userId) {
+ try {
+ Log.startSession("TSI.gDDPU");
+ mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
+ "READ_PRIVILEGED_PHONE_STATE permission required.");
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return mDefaultDialerCache.getDefaultDialerApplication(userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ /**
* @see android.telecom.TelecomManager#getSystemDialerPackage
*/
@Override
public String getSystemDialerPackage() {
try {
Log.startSession("TSI.gSDP");
- return TelecomServiceImpl.getSystemDialerPackage(mContext);
+ return mDefaultDialerCache.getSystemDialerApplication();
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ public void setSystemDialer(ComponentName testComponentName) {
+ try {
+ Log.startSession("TSI.sSD");
+ enforceModifyPermission();
+ enforceShellOnly(Binder.getCallingUid(), "setSystemDialer");
+ synchronized (mLock) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mDefaultDialerCache.setSystemDialerComponentName(testComponentName);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
} finally {
Log.endSession();
}
@@ -753,10 +821,10 @@
* @see android.telecom.TelecomManager#isInCall
*/
@Override
- public boolean isInCall(String callingPackage) {
+ public boolean isInCall(String callingPackage, String callingFeatureId) {
try {
Log.startSession("TSI.iIC");
- if (!canReadPhoneState(callingPackage, "isInCall")) {
+ if (!canReadPhoneState(callingPackage, callingFeatureId, "isInCall")) {
return false;
}
@@ -772,10 +840,10 @@
* @see android.telecom.TelecomManager#isInManagedCall
*/
@Override
- public boolean isInManagedCall(String callingPackage) {
+ public boolean isInManagedCall(String callingPackage, String callingFeatureId) {
try {
Log.startSession("TSI.iIMC");
- if (!canReadPhoneState(callingPackage, "isInManagedCall")) {
+ if (!canReadPhoneState(callingPackage, callingFeatureId, "isInManagedCall")) {
throw new SecurityException("Only the default dialer or caller with " +
"READ_PHONE_STATE permission can use this method.");
}
@@ -811,7 +879,7 @@
// current state as tracked by PhoneStateBroadcaster, any failure to properly
// track the current call state there could result in the wrong ringing state
// being reported by this API.
- return mCallsManager.hasRingingCall();
+ return mCallsManager.hasRingingOrSimulatedRingingCall();
}
} finally {
Log.endSession();
@@ -906,10 +974,11 @@
* @see android.telecom.TelecomManager#showInCallScreen
*/
@Override
- public void showInCallScreen(boolean showDialpad, String callingPackage) {
+ public void showInCallScreen(boolean showDialpad, String callingPackage,
+ String callingFeatureId) {
try {
Log.startSession("TSI.sICS");
- if (!canReadPhoneState(callingPackage, "showInCallScreen")) {
+ if (!canReadPhoneState(callingPackage, callingFeatureId, "showInCallScreen")) {
return;
}
@@ -955,21 +1024,21 @@
public boolean handlePinMmi(String dialString, String callingPackage) {
try {
Log.startSession("TSI.hPM");
- synchronized (mLock) {
- enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
+ enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
- // Switch identity so that TelephonyManager checks Telecom's permissions
- // instead.
- long token = Binder.clearCallingIdentity();
- boolean retval = false;
- try {
- retval = getTelephonyManager().handlePinMmi(dialString);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
-
- return retval;
+ // Switch identity so that TelephonyManager checks Telecom's permissions
+ // instead.
+ long token = Binder.clearCallingIdentity();
+ boolean retval = false;
+ try {
+ retval = getTelephonyManager(
+ SubscriptionManager.getDefaultVoiceSubscriptionId())
+ .handlePinMmi(dialString);
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
+
+ return retval;
}finally {
Log.endSession();
}
@@ -983,29 +1052,34 @@
String dialString, String callingPackage) {
try {
Log.startSession("TSI.hPMFPA");
- synchronized (mLock) {
- enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
- UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
+ UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ synchronized (mLock) {
if (!isPhoneAccountHandleVisibleToCallingUser(accountHandle,
callingUserHandle)) {
- Log.d(this, "%s is not visible for the calling user [hMMI]", accountHandle);
+ Log.d(this, "%s is not visible for the calling user [hMMI]",
+ accountHandle);
return false;
}
-
- // Switch identity so that TelephonyManager checks Telecom's permissions
- // instead.
- long token = Binder.clearCallingIdentity();
- boolean retval = false;
- try {
- int subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(
- accountHandle);
- retval = getTelephonyManager().handlePinMmiForSubscriber(subId, dialString);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- return retval;
}
+
+ // Switch identity so that TelephonyManager checks Telecom's permissions
+ // instead.
+ long token = Binder.clearCallingIdentity();
+ boolean retval = false;
+ int subId;
+ try {
+ synchronized (mLock) {
+ subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(
+ accountHandle);
+ }
+ retval = getTelephonyManager(subId)
+ .handlePinMmiForSubscriber(subId, dialString);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return retval;
}finally {
Log.endSession();
}
@@ -1019,28 +1093,28 @@
String callingPackage) {
try {
Log.startSession("TSI.aAUFPA");
+ enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
synchronized (mLock) {
- enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
if (!isPhoneAccountHandleVisibleToCallingUser(accountHandle,
Binder.getCallingUserHandle())) {
Log.d(this, "%s is not visible for the calling user [gA4PA]",
accountHandle);
return null;
}
- // Switch identity so that TelephonyManager checks Telecom's permissions
- // instead.
- long token = Binder.clearCallingIdentity();
- String retval = "content://icc/adn/";
- try {
- long subId = mPhoneAccountRegistrar
- .getSubscriptionIdForPhoneAccount(accountHandle);
- retval = retval + "subId/" + subId;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
-
- return Uri.parse(retval);
}
+ // Switch identity so that TelephonyManager checks Telecom's permissions
+ // instead.
+ long token = Binder.clearCallingIdentity();
+ String retval = "content://icc/adn/";
+ try {
+ long subId = mPhoneAccountRegistrar
+ .getSubscriptionIdForPhoneAccount(accountHandle);
+ retval = retval + "subId/" + subId;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ return Uri.parse(retval);
} finally {
Log.endSession();
}
@@ -1050,10 +1124,10 @@
* @see android.telecom.TelecomManager#isTtySupported
*/
@Override
- public boolean isTtySupported(String callingPackage) {
+ public boolean isTtySupported(String callingPackage, String callingFeatureId) {
try {
Log.startSession("TSI.iTS");
- if (!canReadPhoneState(callingPackage, "isTtySupported")) {
+ if (!canReadPhoneState(callingPackage, callingFeatureId, "isTtySupported")) {
throw new SecurityException("Only default dialer or an app with" +
"READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE can call this api");
}
@@ -1070,10 +1144,10 @@
* @see android.telecom.TelecomManager#getCurrentTtyMode
*/
@Override
- public int getCurrentTtyMode(String callingPackage) {
+ public int getCurrentTtyMode(String callingPackage, String callingFeatureId) {
try {
Log.startSession("TSI.gCTM");
- if (!canReadPhoneState(callingPackage, "getCurrentTtyMode")) {
+ if (!canReadPhoneState(callingPackage, callingFeatureId, "getCurrentTtyMode")) {
return TelecomManager.TTY_MODE_OFF;
}
@@ -1148,6 +1222,54 @@
}
/**
+ * @see android.telecom.TelecomManager#addNewIncomingConference
+ */
+ @Override
+ public void addNewIncomingConference(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+ try {
+ Log.startSession("TSI.aNIC");
+ synchronized (mLock) {
+ Log.i(this, "Adding new incoming conference with phoneAccountHandle %s",
+ phoneAccountHandle);
+ if (phoneAccountHandle != null &&
+ phoneAccountHandle.getComponentName() != null) {
+ if (isCallerSimCallManager(phoneAccountHandle)
+ && TelephonyUtil.isPstnComponentName(
+ phoneAccountHandle.getComponentName())) {
+ Log.v(this, "Allowing call manager to add incoming conference" +
+ " with PSTN handle");
+ } else {
+ mAppOpsManager.checkPackage(
+ Binder.getCallingUid(),
+ phoneAccountHandle.getComponentName().getPackageName());
+ // Make sure it doesn't cross the UserHandle boundary
+ enforceUserHandleMatchesCaller(phoneAccountHandle);
+ enforcePhoneAccountIsRegisteredEnabled(phoneAccountHandle,
+ Binder.getCallingUserHandle());
+ if (isSelfManagedConnectionService(phoneAccountHandle)) {
+ throw new SecurityException("Self-Managed ConnectionServices cannot add "
+ + "adhoc conference calls");
+ }
+ }
+ long token = Binder.clearCallingIdentity();
+ try {
+ mCallsManager.processIncomingConference(
+ phoneAccountHandle, extras);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ } else {
+ Log.w(this, "Null phoneAccountHandle. Ignoring request to add new" +
+ " incoming conference");
+ }
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+
+ /**
* @see android.telecom.TelecomManager#acceptHandover
*/
@Override
@@ -1250,10 +1372,30 @@
}
/**
+ * @see android.telecom.TelecomManager#startConference.
+ */
+ @Override
+ public void startConference(List<Uri> participants, Bundle extras,
+ String callingPackage) {
+ try {
+ Log.startSession("TSI.sC");
+ if (!canCallPhone(callingPackage, "startConference")) {
+ throw new SecurityException("Package " + callingPackage + " is not allowed"
+ + " to start conference call");
+ }
+ mCallsManager.startConference(participants, extras, callingPackage,
+ Binder.getCallingUserHandle());
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ /**
* @see android.telecom.TelecomManager#placeCall
*/
@Override
- public void placeCall(Uri handle, Bundle extras, String callingPackage) {
+ public void placeCall(Uri handle, Bundle extras, String callingPackage,
+ String callingFeatureId) {
try {
Log.startSession("TSI.pC");
enforceCallingPackage(callingPackage);
@@ -1275,14 +1417,14 @@
if (!callingPackage.equals(
phoneAccountHandle.getComponentName().getPackageName())
- && !canCallPhone(callingPackage,
+ && !canCallPhone(callingPackage, callingFeatureId,
"CALL_PHONE permission required to place calls.")) {
// The caller is not allowed to place calls, so we want to ensure that it
// can only place calls through itself.
throw new SecurityException("Self-managed ConnectionServices can only "
+ "place calls through their own ConnectionService.");
}
- } else if (!canCallPhone(callingPackage, "placeCall")) {
+ } else if (!canCallPhone(callingPackage, callingFeatureId, "placeCall")) {
throw new SecurityException("Package " + callingPackage
+ " is not allowed to place phone calls");
}
@@ -1295,7 +1437,8 @@
// by {@link UserCallIntentProcessor}.
final boolean hasCallAppOp = mAppOpsManager.noteOp(AppOpsManager.OP_CALL_PHONE,
- Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED;
+ Binder.getCallingUid(), callingPackage, callingFeatureId, null)
+ == AppOpsManager.MODE_ALLOWED;
final boolean hasCallPermission = mContext.checkCallingPermission(CALL_PHONE) ==
PackageManager.PERMISSION_GRANTED;
@@ -1374,6 +1517,28 @@
}
@Override
+ public void stopBlockSuppression() {
+ try {
+ Log.startSession("TSI.sBS");
+ enforceModifyPermission();
+ if (Binder.getCallingUid() != Process.SHELL_UID
+ && Binder.getCallingUid() != Process.ROOT_UID) {
+ throw new SecurityException("Shell-only API.");
+ }
+ synchronized (mLock) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ BlockedNumberContract.SystemContract.endBlockSuppression(mContext);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
public TelecomAnalytics dumpCallAnalytics() {
try {
Log.startSession("TSI.dCA");
@@ -1443,6 +1608,24 @@
return BlockedNumbersActivity.getIntentForStartingActivity();
}
+
+ @Override
+ public Intent createLaunchEmergencyDialerIntent(String number) {
+ String packageName = mContext.getApplicationContext().getString(
+ com.android.internal.R.string.config_emergency_dialer_package);
+ Intent intent = new Intent(Intent.ACTION_DIAL_EMERGENCY)
+ .setPackage(packageName);
+ ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, 0 /* flags*/);
+ if (resolveInfo == null) {
+ // No matching activity from config, fallback to default platform implementation
+ intent.setPackage(null);
+ }
+ if (!TextUtils.isEmpty(number) && TextUtils.isDigitsOnly(number)) {
+ intent.setData(Uri.parse("tel:" + number));
+ }
+ return intent;
+ }
+
/**
* @see android.telecom.TelecomManager#isIncomingCallPermitted(PhoneAccountHandle)
*/
@@ -1509,6 +1692,26 @@
}
}
+ @Override
+ public void setTestEmergencyPhoneAccountPackageNameFilter(String packageName) {
+ try {
+ Log.startSession("TSI.sTPAPNF");
+ enforceModifyPermission();
+ enforceShellOnly(Binder.getCallingUid(),
+ "setTestEmergencyPhoneAccountPackageNameFilter");
+ synchronized (mLock) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mPhoneAccountRegistrar.setTestPhoneAccountPackageNameFilter(packageName);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
/**
* See {@link TelecomManager#isInEmergencyCall()}
*/
@@ -1533,10 +1736,10 @@
}
/**
- * See {@link TelecomManager#handleCallIntent(Intent)} ()}
+ * See {@link TelecomManager#handleCallIntent(Intent, String)}
*/
@Override
- public void handleCallIntent(Intent intent) {
+ public void handleCallIntent(Intent intent, String callingPackage) {
try {
Log.startSession("TSI.hCI");
synchronized (mLock) {
@@ -1547,7 +1750,7 @@
try {
Log.i(this, "handleCallIntent: handling call intent");
mCallIntentProcessorAdapter.processOutgoingCallIntent(mContext,
- mCallsManager, intent, null /* callingPackage */);
+ mCallsManager, intent, callingPackage);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1606,9 +1809,7 @@
try {
Log.startSession("TSI.aORTCCA");
enforceModifyPermission();
- if (!Build.IS_USERDEBUG) {
- throw new SecurityException("Test-only API.");
- }
+ enforceShellOnly(Binder.getCallingUid(), "addOrRemoveTestCallCompanionApp");
synchronized (mLock) {
long token = Binder.clearCallingIdentity();
try {
@@ -1624,27 +1825,6 @@
}
@Override
- public void setTestAutoModeApp(String packageName) {
- try {
- Log.startSession("TSI.sTAMA");
- enforceModifyPermission();
- if (!Build.IS_USERDEBUG) {
- throw new SecurityException("Test-only API.");
- }
- synchronized (mLock) {
- long token = Binder.clearCallingIdentity();
- try {
- mCallsManager.getRoleManagerAdapter().setTestAutoModeApp(packageName);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- } finally {
- Log.endSession();
- }
- }
-
- @Override
public void setTestPhoneAcctSuggestionComponent(String flattenedComponentName) {
try {
Log.startSession("TSI.sPASA");
@@ -1772,10 +1952,6 @@
});
}
- public static String getSystemDialerPackage(Context context) {
- return context.getResources().getString(com.android.internal.R.string.config_defaultDialer);
- }
-
public ITelecomService.Stub getBinder() {
return mBinderImpl;
}
@@ -1786,7 +1962,10 @@
private boolean isPhoneAccountHandleVisibleToCallingUser(
PhoneAccountHandle phoneAccountUserHandle, UserHandle callingUser) {
- return mPhoneAccountRegistrar.getPhoneAccount(phoneAccountUserHandle, callingUser) != null;
+ synchronized (mLock) {
+ return mPhoneAccountRegistrar.getPhoneAccount(phoneAccountUserHandle, callingUser)
+ != null;
+ }
}
private boolean isCallerSystemApp() {
@@ -1813,12 +1992,12 @@
}
private void acceptRingingCallInternal(int videoState) {
- Call call = mCallsManager.getFirstCallWithState(CallState.RINGING);
+ Call call = mCallsManager.getFirstCallWithState(CallState.RINGING, CallState.SIMULATED_RINGING);
if (call != null) {
if (videoState == DEFAULT_VIDEO_STATE || !isValidAcceptVideoState(videoState)) {
videoState = call.getVideoState();
}
- call.answer(videoState);
+ mCallsManager.answerCall(call, videoState);
}
}
@@ -1832,6 +2011,7 @@
CallState.DIALING,
CallState.PULLING,
CallState.RINGING,
+ CallState.SIMULATED_RINGING,
CallState.ON_HOLD);
}
@@ -1841,10 +2021,11 @@
return false;
}
- if (call.getState() == CallState.RINGING) {
- call.reject(false /* rejectWithMessage */, null, callingPackage);
+ if (call.getState() == CallState.RINGING
+ || call.getState() == CallState.SIMULATED_RINGING) {
+ mCallsManager.rejectCall(call, false /* rejectWithMessage */, null);
} else {
- call.disconnect(0 /* disconnectionTimeout */, callingPackage);
+ mCallsManager.disconnectCall(call);
}
return true;
}
@@ -1961,7 +2142,17 @@
}
}
- private boolean canReadPhoneState(String callingPackage, String message) {
+ // to be used for TestApi methods that can only be called with SHELL UID.
+ private void enforceShellOnly(int callingUid, String message) {
+ if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
+ return; // okay
+ }
+
+ throw new SecurityException(message + ": Only shell user can call it");
+ }
+
+ private boolean canReadPhoneState(String callingPackage, String callingFeatureId,
+ String message) {
// The system/default dialer can always read phone state - so that emergency calls will
// still work.
if (isPrivilegedDialerCalling(callingPackage)) {
@@ -1978,11 +2169,75 @@
mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, message);
// Some apps that have the permission can be restricted via app ops.
- return mAppOpsManager.noteOp(AppOpsManager.OP_READ_PHONE_STATE,
- Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED;
+ return mAppOpsManager.noteOp(AppOpsManager.OP_READ_PHONE_STATE, Binder.getCallingUid(),
+ callingPackage, callingFeatureId, message) == AppOpsManager.MODE_ALLOWED;
}
}
+ private boolean canReadPhoneNumbers(String callingPackage, String callingFeatureId,
+ String message) {
+ boolean targetSdkPreR = false;
+ int uid = Binder.getCallingUid();
+ try {
+ ApplicationInfo applicationInfo = mPackageManager.getApplicationInfoAsUser(
+ callingPackage, 0, UserHandle.getUserHandleForUid(Binder.getCallingUid()));
+ targetSdkPreR = applicationInfo != null
+ && applicationInfo.targetSdkVersion < Build.VERSION_CODES.R;
+ } catch (PackageManager.NameNotFoundException e) {
+ // In the case that the PackageManager cannot find the specified calling package apply
+ // the more restrictive target R+ requirements.
+ }
+ // Apps targeting pre-R can access phone numbers via READ_PHONE_STATE
+ if (targetSdkPreR) {
+ try {
+ return canReadPhoneState(callingPackage, callingFeatureId, message);
+ } catch (SecurityException e) {
+ // Apps targeting pre-R can still access phone numbers via the additional checks
+ // below.
+ }
+ } else {
+ // The system/default dialer can always read phone state - so that emergency calls will
+ // still work.
+ if (isPrivilegedDialerCalling(callingPackage)) {
+ return true;
+ }
+ if (mContext.checkCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ }
+ if (mContext.checkCallingOrSelfPermission(READ_PHONE_NUMBERS)
+ == PackageManager.PERMISSION_GRANTED && mAppOpsManager.noteOpNoThrow(
+ AppOpsManager.OPSTR_READ_PHONE_NUMBERS, uid, callingPackage, callingFeatureId,
+ message) == AppOpsManager.MODE_ALLOWED) {
+ return true;
+ }
+ if (mContext.checkCallingOrSelfPermission(READ_SMS) == PackageManager.PERMISSION_GRANTED
+ && mAppOpsManager.noteOpNoThrow(AppOpsManager.OPSTR_READ_SMS, uid, callingPackage,
+ callingFeatureId, message) == AppOpsManager.MODE_ALLOWED) {
+ return true;
+ }
+ // The default SMS app with the WRITE_SMS appop granted can access phone numbers.
+ if (mAppOpsManager.noteOpNoThrow(AppOpsManager.OPSTR_WRITE_SMS, uid, callingPackage,
+ callingFeatureId, message) == AppOpsManager.MODE_ALLOWED) {
+ return true;
+ }
+ throw new SecurityException("Package " + callingPackage
+ + " does not meet the requirements to access the phone number");
+ }
+
+
+ private boolean canReadPrivilegedPhoneState(String callingPackage, String message) {
+ // The system/default dialer can always read phone state - so that emergency calls will
+ // still work.
+ if (isPrivilegedDialerCalling(callingPackage)) {
+ return true;
+ }
+
+ mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, message);
+ return true;
+ }
+
private boolean isDialerOrPrivileged(String callingPackage, String message) {
// The system/default dialer can always read phone state - so that emergency calls will
// still work.
@@ -2006,6 +2261,10 @@
}
private boolean canCallPhone(String callingPackage, String message) {
+ return canCallPhone(callingPackage, null /* featureId */, message);
+ }
+
+ private boolean canCallPhone(String callingPackage, String callingFeatureId, String message) {
// The system/default dialer can always read phone state - so that emergency calls will
// still work.
if (isPrivilegedDialerCalling(callingPackage)) {
@@ -2017,7 +2276,8 @@
// Some apps that have the permission can be restricted via app ops.
return mAppOpsManager.noteOp(AppOpsManager.OP_CALL_PHONE,
- Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED;
+ Binder.getCallingUid(), callingPackage, callingFeatureId, message)
+ == AppOpsManager.MODE_ALLOWED;
}
private boolean isCallerSimCallManager(PhoneAccountHandle targetPhoneAccount) {
@@ -2056,8 +2316,9 @@
}
}
- private TelephonyManager getTelephonyManager() {
- return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ private TelephonyManager getTelephonyManager(int subId) {
+ return ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
+ .createForSubscriptionId(subId);
}
/**
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index e772eb4..807cc2d 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -20,14 +20,19 @@
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.IncomingCallFilter;
import com.android.server.telecom.components.UserCallIntentProcessor;
import com.android.server.telecom.components.UserCallIntentProcessorFactory;
+import com.android.server.telecom.ui.AudioProcessingNotification;
+import com.android.server.telecom.ui.DisconnectedCallNotifier;
import com.android.server.telecom.ui.IncomingCallNotifier;
import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
import com.android.server.telecom.BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory;
import com.android.server.telecom.CallAudioManager.AudioServiceFactory;
import com.android.server.telecom.DefaultDialerCache.DefaultDialerManagerAdapter;
+import com.android.server.telecom.ui.ToastFactory;
+import android.app.ActivityManager;
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -40,6 +45,7 @@
import android.os.UserManager;
import android.telecom.Log;
import android.telecom.PhoneAccountHandle;
+import android.widget.Toast;
import java.io.FileNotFoundException;
import java.io.InputStream;
@@ -197,8 +203,11 @@
IncomingCallNotifier incomingCallNotifier,
InCallTonePlayer.ToneGeneratorFactory toneGeneratorFactory,
CallAudioRouteStateMachine.Factory callAudioRouteStateMachineFactory,
+ CallAudioModeStateMachine.Factory callAudioModeStateMachineFactory,
ClockProxy clockProxy,
- RoleManagerAdapter roleManagerAdapter) {
+ RoleManagerAdapter roleManagerAdapter,
+ IncomingCallFilter.Factory incomingCallFilterFactory,
+ ContactsAsyncHelper.Factory contactsAsyncHelperFactory) {
mContext = context.getApplicationContext();
LogUtils.initLogging(mContext);
DefaultDialerManagerAdapter defaultDialerAdapter =
@@ -209,21 +218,10 @@
Log.startSession("TS.init");
mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext, defaultDialerCache,
- new PhoneAccountRegistrar.AppLabelProxy() {
- @Override
- public CharSequence getAppLabel(String packageName) {
- PackageManager pm = mContext.getPackageManager();
- try {
- ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
- return pm.getApplicationLabel(info);
- } catch (PackageManager.NameNotFoundException nnfe) {
- Log.w(this, "Could not determine package name.");
- }
+ packageName -> AppLabelProxy.Util.getAppLabel(
+ mContext.getPackageManager(), packageName));
- return null;
- }
- });
- mContactsAsyncHelper = new ContactsAsyncHelper(
+ mContactsAsyncHelper = contactsAsyncHelperFactory.create(
new ContactsAsyncHelper.ContentResolverAdapter() {
@Override
public InputStream openInputStream(Context context, Uri uri)
@@ -244,13 +242,15 @@
mMissedCallNotifier = missedCallNotifierImplFactory
.makeMissedCallNotifierImpl(mContext, mPhoneAccountRegistrar, defaultDialerCache);
+ DisconnectedCallNotifier.Factory disconnectedCallNotifierFactory =
+ new DisconnectedCallNotifier.Default();
CallerInfoLookupHelper callerInfoLookupHelper =
new CallerInfoLookupHelper(context, callerInfoAsyncQueryFactory,
mContactsAsyncHelper, mLock);
EmergencyCallHelper emergencyCallHelper = new EmergencyCallHelper(mContext,
- TelecomServiceImpl.getSystemDialerPackage(mContext), timeoutsAdapter);
+ defaultDialerCache, timeoutsAdapter);
InCallControllerFactory inCallControllerFactory = new InCallControllerFactory() {
@Override
@@ -259,7 +259,24 @@
DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter,
EmergencyCallHelper emergencyCallHelper) {
return new InCallController(context, lock, callsManager, systemStateProvider,
- defaultDialerCache, timeoutsAdapter, emergencyCallHelper);
+ defaultDialerCache, timeoutsAdapter, emergencyCallHelper,
+ new CarModeTracker(), clockProxy);
+ }
+ };
+
+ AudioProcessingNotification audioProcessingNotification =
+ new AudioProcessingNotification(mContext);
+
+ ToastFactory toastFactory = new ToastFactory() {
+ @Override
+ public Toast makeText(Context context, int resId, int duration) {
+ return Toast.makeText(context, context.getMainLooper(), context.getString(resId),
+ duration);
+ }
+
+ @Override
+ public Toast makeText(Context context, CharSequence text, int duration) {
+ return Toast.makeText(context, context.getMainLooper(), text, duration);
}
};
@@ -268,6 +285,7 @@
mLock,
callerInfoLookupHelper,
mMissedCallNotifier,
+ disconnectedCallNotifierFactory,
mPhoneAccountRegistrar,
headsetMediaButtonFactory,
proximitySensorManagerFactory,
@@ -284,11 +302,14 @@
emergencyCallHelper,
toneGeneratorFactory,
clockProxy,
+ audioProcessingNotification,
bluetoothStateReceiver,
callAudioRouteStateMachineFactory,
- new CallAudioModeStateMachine.Factory(),
+ callAudioModeStateMachineFactory,
inCallControllerFactory,
- roleManagerAdapter);
+ roleManagerAdapter,
+ incomingCallFilterFactory,
+ toastFactory);
mIncomingCallNotifier = incomingCallNotifier;
incomingCallNotifier.setCallsManagerProxy(new IncomingCallNotifier.CallsManagerProxy() {
@@ -316,13 +337,23 @@
mRespondViaSmsManager = new RespondViaSmsManager(mCallsManager, mLock);
mCallsManager.setRespondViaSmsManager(mRespondViaSmsManager);
- mContext.registerReceiver(mUserSwitchedReceiver, USER_SWITCHED_FILTER);
- mContext.registerReceiver(mUserStartingReceiver, USER_STARTING_FILTER);
- mContext.registerReceiver(mBootCompletedReceiver, BOOT_COMPLETE_FILTER);
+ mContext.registerReceiverAsUser(mUserSwitchedReceiver, UserHandle.ALL,
+ USER_SWITCHED_FILTER, null, null);
+ mContext.registerReceiverAsUser(mUserStartingReceiver, UserHandle.ALL,
+ USER_STARTING_FILTER, null, null);
+ mContext.registerReceiverAsUser(mBootCompletedReceiver, UserHandle.ALL,
+ BOOT_COMPLETE_FILTER, null, null);
+
+ // Set current user explicitly since USER_SWITCHED_FILTER intent can be missed at startup
+ synchronized(mLock) {
+ UserHandle currentUserHandle = UserHandle.of(ActivityManager.getCurrentUser());
+ mPhoneAccountRegistrar.setCurrentUserHandle(currentUserHandle);
+ mCallsManager.onUserSwitch(currentUserHandle);
+ }
mBluetoothPhoneServiceImpl = bluetoothPhoneServiceImplFactory.makeBluetoothPhoneServiceImpl(
mContext, mLock, mCallsManager, mPhoneAccountRegistrar);
- mCallIntentProcessor = new CallIntentProcessor(mContext, mCallsManager);
+ mCallIntentProcessor = new CallIntentProcessor(mContext, mCallsManager, defaultDialerCache);
mTelecomBroadcastIntentProcessor = new TelecomBroadcastIntentProcessor(
mContext, mCallsManager);
@@ -335,7 +366,7 @@
final UserManager userManager = UserManager.get(mContext);
mTelecomServiceImpl = new TelecomServiceImpl(
mContext, mCallsManager, mPhoneAccountRegistrar,
- new CallIntentProcessor.AdapterImpl(),
+ new CallIntentProcessor.AdapterImpl(defaultDialerCache),
new UserCallIntentProcessorFactory() {
@Override
public UserCallIntentProcessor create(Context context, UserHandle userHandle) {
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index 37f9363..a701b88 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -29,7 +29,8 @@
*/
public final class Timeouts {
public static class Adapter {
- public Adapter() { }
+ public Adapter() {
+ }
public long getCallScreeningTimeoutMillis(ContentResolver cr) {
return Timeouts.getCallScreeningTimeoutMillis(cr);
@@ -62,20 +63,25 @@
public long getPhoneAccountSuggestionServiceTimeout(ContentResolver cr) {
return Timeouts.getPhoneAccountSuggestionServiceTimeout(cr);
}
+
+ public long getCallRecordingToneRepeatIntervalMillis(ContentResolver cr) {
+ return Timeouts.getCallRecordingToneRepeatIntervalMillis(cr);
+ }
}
/** A prefix to use for all keys so to not clobber the global namespace. */
private static final String PREFIX = "telecom.";
- private Timeouts() {}
+ private Timeouts() {
+ }
/**
* Returns the timeout value from Settings or the default value if it hasn't been changed. This
* method is safe to call from any thread, including the UI thread.
*
* @param contentResolver The content resolved.
- * @param key Settings key to retrieve.
- * @param defaultValue Default value, in milliseconds.
+ * @param key Settings key to retrieve.
+ * @param defaultValue Default value, in milliseconds.
* @return The timeout value from Settings or the default value if it hasn't been changed.
*/
private static long get(ContentResolver contentResolver, String key, long defaultValue) {
@@ -176,8 +182,8 @@
* as potential emergency callbacks.
*/
public static long getEmergencyCallbackWindowMillis(ContentResolver contentResolver) {
- return get(contentResolver, "emergency_callback_window_millis",
- TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES));
+ return get(contentResolver, "emergency_callback_window_millis",
+ TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES));
}
/**
@@ -187,7 +193,7 @@
*/
public static long getUserDefinedCallRedirectionTimeoutMillis(ContentResolver contentResolver) {
return get(contentResolver, "user_defined_call_redirection_timeout",
- 5000L /* 5 seconds */);
+ 5000L /* 5 seconds */);
}
/**
@@ -198,4 +204,11 @@
public static long getCarrierCallRedirectionTimeoutMillis(ContentResolver contentResolver) {
return get(contentResolver, "carrier_call_redirection_timeout", 5000L /* 5 seconds */);
}
+
+ /**
+ * Returns the number of milliseconds between two plays of the call recording tone.
+ */
+ public static long getCallRecordingToneRepeatIntervalMillis(ContentResolver contentResolver) {
+ return get(contentResolver, "call_recording_tone_repeat_interval", 15000L /* 15 seconds */);
+ }
}
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index 23a086b..7fd600c 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -16,13 +16,16 @@
package com.android.server.telecom.bluetooth;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.telecom.Log;
+import android.util.LocalLog;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.telecom.BluetoothAdapterProxy;
import com.android.server.telecom.BluetoothHeadsetProxy;
@@ -43,18 +46,21 @@
Log.startSession("BMSL.oSC");
try {
synchronized (mLock) {
+ String logString;
if (profile == BluetoothProfile.HEADSET) {
mBluetoothHeadsetService =
new BluetoothHeadsetProxy((BluetoothHeadset) proxy);
- Log.i(this, "- Got BluetoothHeadset: " + mBluetoothHeadsetService);
+ logString = "Got BluetoothHeadset: " + mBluetoothHeadsetService;
} else if (profile == BluetoothProfile.HEARING_AID) {
mBluetoothHearingAidService = (BluetoothHearingAid) proxy;
- Log.i(this, "- Got BluetoothHearingAid: "
- + mBluetoothHearingAidService);
+ logString = "Got BluetoothHearingAid: "
+ + mBluetoothHearingAidService;
} else {
- Log.w(this, "Connected to non-requested bluetooth service." +
- " Not changing bluetooth headset.");
+ logString = "Connected to non-requested bluetooth service." +
+ " Not changing bluetooth headset.";
}
+ Log.i(BluetoothDeviceManager.this, logString);
+ mLocalLog.log(logString);
}
} finally {
Log.endSession();
@@ -67,23 +73,25 @@
try {
synchronized (mLock) {
LinkedHashMap<String, BluetoothDevice> lostServiceDevices;
+ String logString;
if (profile == BluetoothProfile.HEADSET) {
mBluetoothHeadsetService = null;
- Log.i(BluetoothDeviceManager.this,
- "Lost BluetoothHeadset service. " +
- "Removing all tracked devices.");
lostServiceDevices = mHfpDevicesByAddress;
mBluetoothRouteManager.onActiveDeviceChanged(null, false);
+ logString = "Lost BluetoothHeadset service. " +
+ "Removing all tracked devices";
} else if (profile == BluetoothProfile.HEARING_AID) {
mBluetoothHearingAidService = null;
- Log.i(BluetoothDeviceManager.this,
- "Lost BluetoothHearingAid service. " +
- "Removing all tracked devices.");
+ logString = "Lost BluetoothHearingAid service. " +
+ "Removing all tracked devices.";
lostServiceDevices = mHearingAidDevicesByAddress;
mBluetoothRouteManager.onActiveDeviceChanged(null, true);
} else {
return;
}
+ Log.i(BluetoothDeviceManager.this, logString);
+ mLocalLog.log(logString);
+
List<BluetoothDevice> devicesToRemove = new LinkedList<>(
lostServiceDevices.values());
lostServiceDevices.clear();
@@ -103,6 +111,7 @@
new LinkedHashMap<>();
private final LinkedHashMap<BluetoothDevice, Long> mHearingAidDeviceSyncIds =
new LinkedHashMap<>();
+ private final LocalLog mLocalLog = new LocalLog(20);
// This lock only protects internal state -- it doesn't lock on anything going into Telecom.
private final Object mLock = new Object();
@@ -111,9 +120,11 @@
private BluetoothHeadsetProxy mBluetoothHeadsetService;
private BluetoothHearingAid mBluetoothHearingAidService;
private BluetoothDevice mBluetoothHearingAidActiveDeviceCache;
+ private BluetoothAdapterProxy mBluetoothAdapterProxy;
public BluetoothDeviceManager(Context context, BluetoothAdapterProxy bluetoothAdapter) {
if (bluetoothAdapter != null) {
+ mBluetoothAdapterProxy = bluetoothAdapter;
bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
BluetoothProfile.HEADSET);
bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
@@ -188,6 +199,8 @@
}
void onDeviceConnected(BluetoothDevice device, boolean isHearingAid) {
+ mLocalLog.log("Device connected -- address: " + device.getAddress() + " isHeadingAid: "
+ + isHearingAid);
synchronized (mLock) {
LinkedHashMap<String, BluetoothDevice> targetDeviceMap;
if (isHearingAid) {
@@ -213,6 +226,8 @@
}
void onDeviceDisconnected(BluetoothDevice device, boolean isHearingAid) {
+ mLocalLog.log("Device disconnected -- address: " + device.getAddress() + " isHeadingAid: "
+ + isHearingAid);
synchronized (mLock) {
LinkedHashMap<String, BluetoothDevice> targetDeviceMap;
if (isHearingAid) {
@@ -234,7 +249,8 @@
} else {
for (BluetoothDevice device : mBluetoothHearingAidService.getActiveDevices()) {
if (device != null) {
- mBluetoothHearingAidService.setActiveDevice(null);
+ mBluetoothAdapterProxy.setActiveDevice(null,
+ BluetoothAdapter.ACTIVE_DEVICE_ALL);
}
}
}
@@ -257,15 +273,17 @@
Log.w(this, "Attempting to turn on audio when the hearing aid service is null");
return false;
}
- return mBluetoothHearingAidService.setActiveDevice(
- mHearingAidDevicesByAddress.get(address));
+ return mBluetoothAdapterProxy.setActiveDevice(
+ mHearingAidDevicesByAddress.get(address),
+ BluetoothAdapter.ACTIVE_DEVICE_ALL);
} else if (mHfpDevicesByAddress.containsKey(address)) {
BluetoothDevice device = mHfpDevicesByAddress.get(address);
if (mBluetoothHeadsetService == null) {
Log.w(this, "Attempting to turn on audio when the headset service is null");
return false;
}
- boolean success = mBluetoothHeadsetService.setActiveDevice(device);
+ boolean success = mBluetoothAdapterProxy.setActiveDevice(device,
+ BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL);
if (!success) {
Log.w(this, "Couldn't set active device to %s", address);
return false;
@@ -292,9 +310,14 @@
public void restoreHearingAidDevice() {
if (mBluetoothHearingAidActiveDeviceCache != null && mBluetoothHearingAidService != null) {
- mBluetoothHearingAidService.setActiveDevice(mBluetoothHearingAidActiveDeviceCache);
+ mBluetoothAdapterProxy.setActiveDevice(
+ mBluetoothHearingAidActiveDeviceCache,
+ BluetoothAdapter.ACTIVE_DEVICE_ALL);
mBluetoothHearingAidActiveDeviceCache = null;
}
}
+ public void dump(IndentingPrintWriter pw) {
+ mLocalLog.dump(pw);
+ }
}
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index fe34be3..7211990 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -645,6 +645,16 @@
Log.i(this, "No device with address %s available. Using %s instead.",
address, actualAddress);
}
+
+ BluetoothDevice alreadyConnectedDevice = getBluetoothAudioConnectedDevice();
+ if (alreadyConnectedDevice != null && alreadyConnectedDevice.getAddress().equals(
+ actualAddress)) {
+ Log.i(this, "trying to connect to already connected device -- skipping connection"
+ + " and going into the actual connected state.");
+ transitionToActualState();
+ return null;
+ }
+
if (!mDeviceManager.connectAudio(actualAddress)) {
boolean shouldRetry = retryCount < MAX_CONNECTION_RETRIES;
Log.w(LOG_TAG, "Could not connect to %s. Will %s", actualAddress,
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index 9e64b56..8a14cbd 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -146,12 +146,20 @@
if (device == null) {
mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args);
} else {
+ if (!mIsInCall) {
+ Log.i(LOG_TAG, "Ignoring hearing aid audio on since we're not in a call");
+ return;
+ }
args.arg2 = device.getAddress();
mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args);
}
}
}
+ public BluetoothDeviceManager getBluetoothDeviceManager() {
+ return mBluetoothDeviceManager;
+ }
+
public BluetoothStateReceiver(BluetoothDeviceManager deviceManager,
BluetoothRouteManager routeManager) {
mBluetoothDeviceManager = deviceManager;
diff --git a/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java b/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
deleted file mode 100644
index 0abd15d..0000000
--- a/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2016 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;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.provider.BlockedNumberContract;
-import android.provider.CallLog;
-import android.telecom.Log;
-import android.telecom.Logging.Session;
-import android.telecom.TelecomManager;
-
-import com.android.internal.telephony.CallerInfo;
-import com.android.server.telecom.Call;
-import com.android.server.telecom.CallerInfoLookupHelper;
-import com.android.server.telecom.LogUtils;
-import com.android.server.telecom.settings.BlockedNumbersUtil;
-
-/**
- * An {@link AsyncTask} that checks if a call needs to be blocked.
- * <p> An {@link AsyncTask} is used to perform the block check to avoid blocking the main thread.
- * The block check itself is performed in the {@link AsyncTask#doInBackground(Object[])}.
- */
-public class AsyncBlockCheckFilter extends AsyncTask<String, Void, Boolean>
- implements IncomingCallFilter.CallFilter {
- private final Context mContext;
- private final BlockCheckerAdapter mBlockCheckerAdapter;
- private final CallBlockListener mCallBlockListener;
- private Call mIncomingCall;
- private Session mBackgroundTaskSubsession;
- private Session mPostExecuteSubsession;
- private CallFilterResultCallback mCallback;
- private CallerInfoLookupHelper mCallerInfoLookupHelper;
- private int mBlockStatus = BlockedNumberContract.STATUS_NOT_BLOCKED;
-
- public AsyncBlockCheckFilter(Context context, BlockCheckerAdapter blockCheckerAdapter,
- CallerInfoLookupHelper callerInfoLookupHelper, CallBlockListener callBlockListener) {
- mContext = context;
- mBlockCheckerAdapter = blockCheckerAdapter;
- mCallerInfoLookupHelper = callerInfoLookupHelper;
- mCallBlockListener = callBlockListener;
- }
-
- @Override
- public void startFilterLookup(Call call, CallFilterResultCallback callback) {
- mCallback = callback;
- mIncomingCall = call;
- String number = call.getHandle() == null ?
- null : call.getHandle().getSchemeSpecificPart();
- if (BlockedNumbersUtil.isEnhancedCallBlockingEnabledByPlatform(mContext)) {
- int presentation = mIncomingCall.getHandlePresentation();
- if (presentation == TelecomManager.PRESENTATION_ALLOWED) {
- mCallerInfoLookupHelper.startLookup(call.getHandle(),
- new CallerInfoLookupHelper.OnQueryCompleteListener() {
- @Override
- public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) {
- boolean contactExists = info == null ? false : info.contactExists;
- execute(number, String.valueOf(presentation),
- String.valueOf(contactExists));
- }
-
- @Override
- public void onContactPhotoQueryComplete(Uri handle, CallerInfo info) {
- // ignore
- }
- });
- } else {
- this.execute(number, String.valueOf(presentation));
- }
- } else {
- this.execute(number);
- }
- }
-
- @Override
- protected void onPreExecute() {
- mBackgroundTaskSubsession = Log.createSubsession();
- mPostExecuteSubsession = Log.createSubsession();
- }
-
- @Override
- protected Boolean doInBackground(String... params) {
- try {
- Log.continueSession(mBackgroundTaskSubsession, "ABCF.dIB");
- Log.addEvent(mIncomingCall, LogUtils.Events.BLOCK_CHECK_INITIATED);
- Bundle extras = new Bundle();
- if (params.length > 1) {
- extras.putInt(BlockedNumberContract.EXTRA_CALL_PRESENTATION,
- Integer.valueOf(params[1]));
- }
- if (params.length > 2) {
- extras.putBoolean(BlockedNumberContract.EXTRA_CONTACT_EXIST,
- Boolean.valueOf(params[2]));
- }
- mBlockStatus = mBlockCheckerAdapter.getBlockStatus(mContext, params[0], extras);
- return mBlockStatus != BlockedNumberContract.STATUS_NOT_BLOCKED;
- } finally {
- Log.endSession();
- }
- }
-
- @Override
- protected void onPostExecute(Boolean isBlocked) {
- Log.continueSession(mPostExecuteSubsession, "ABCF.oPE");
- try {
- CallFilteringResult result;
- if (isBlocked) {
- result = new CallFilteringResult(
- false, // shouldAllowCall
- true, //shouldReject
- true, //shouldAddToCallLog
- false, // shouldShowNotification
- convertBlockStatusToReason(), //callBlockReason
- null, //callScreeningAppName
- null //callScreeningComponentName
- );
- if (mCallBlockListener != null) {
- String number = mIncomingCall.getHandle() == null ? null
- : mIncomingCall.getHandle().getSchemeSpecificPart();
- mCallBlockListener.onCallBlocked(mBlockStatus, number,
- mIncomingCall.getInitiatingUser());
- }
- } else {
- result = new CallFilteringResult(
- true, // shouldAllowCall
- false, // shouldReject
- true, // shouldAddToCallLog
- true // shouldShowNotification
- );
- }
- Log.addEvent(mIncomingCall, LogUtils.Events.BLOCK_CHECK_FINISHED,
- BlockedNumberContract.SystemContract.blockStatusToString(mBlockStatus) + " "
- + result);
- mCallback.onCallFilteringComplete(mIncomingCall, result);
- } finally {
- Log.endSession();
- }
- }
-
- private int convertBlockStatusToReason() {
- switch (mBlockStatus) {
- case BlockedNumberContract.STATUS_BLOCKED_IN_LIST:
- return CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER;
-
- case BlockedNumberContract.STATUS_BLOCKED_UNKNOWN_NUMBER:
- return CallLog.Calls.BLOCK_REASON_UNKNOWN_NUMBER;
-
- case BlockedNumberContract.STATUS_BLOCKED_RESTRICTED:
- return CallLog.Calls.BLOCK_REASON_RESTRICTED_NUMBER;
-
- case BlockedNumberContract.STATUS_BLOCKED_PAYPHONE:
- return CallLog.Calls.BLOCK_REASON_PAY_PHONE;
-
- case BlockedNumberContract.STATUS_BLOCKED_NOT_IN_CONTACTS:
- return CallLog.Calls.BLOCK_REASON_NOT_IN_CONTACTS;
-
- default:
- Log.w(AsyncBlockCheckFilter.class.getSimpleName(),
- "There's no call log block reason can be converted");
- return CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER;
- }
- }
-}
diff --git a/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java b/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java
index 4a5ac60..a83f314 100644
--- a/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java
+++ b/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java
@@ -18,27 +18,49 @@
import android.content.Context;
import android.os.Bundle;
-
-import com.android.internal.telephony.BlockChecker;
+import android.provider.BlockedNumberContract;
+import android.telecom.Log;
public class BlockCheckerAdapter {
+ private static final String TAG = BlockCheckerAdapter.class.getSimpleName();
+
public BlockCheckerAdapter() { }
/**
- * Check whether the number is blocked.
+ * Returns the call blocking status for the {@code phoneNumber}.
+ * <p>
+ * This method catches all underlying exceptions to ensure that this method never throws any
+ * exception.
*
* @param context the context of the caller.
- * @param number the number to check.
+ * @param phoneNumber the number to check.
* @param extras the extra attribute of the number.
* @return result code indicating if the number should be blocked, and if so why.
- * Valid values are: {@link android.provider.BlockedNumberContract#STATUS_NOT_BLOCKED},
- * {@link android.provider.BlockedNumberContract#STATUS_BLOCKED_IN_LIST},
- * {@link android.provider.BlockedNumberContract#STATUS_BLOCKED_NOT_IN_CONTACTS},
- * {@link android.provider.BlockedNumberContract#STATUS_BLOCKED_PAYPHONE},
- * {@link android.provider.BlockedNumberContract#STATUS_BLOCKED_RESTRICTED},
- * {@link android.provider.BlockedNumberContract#STATUS_BLOCKED_UNKNOWN_NUMBER}.
+ * Valid values are: {@link BlockedNumberContract#STATUS_NOT_BLOCKED},
+ * {@link BlockedNumberContract#STATUS_BLOCKED_IN_LIST},
+ * {@link BlockedNumberContract#STATUS_BLOCKED_NOT_IN_CONTACTS},
+ * {@link BlockedNumberContract#STATUS_BLOCKED_PAYPHONE},
+ * {@link BlockedNumberContract#STATUS_BLOCKED_RESTRICTED},
+ * {@link BlockedNumberContract#STATUS_BLOCKED_UNKNOWN_NUMBER}.
*/
- public int getBlockStatus(Context context, String number, Bundle extras) {
- return BlockChecker.getBlockStatus(context, number, extras);
+ public int getBlockStatus(Context context, String phoneNumber, Bundle extras) {
+ int blockStatus = BlockedNumberContract.STATUS_NOT_BLOCKED;
+ long startTimeNano = System.nanoTime();
+
+ try {
+ blockStatus = BlockedNumberContract.SystemContract.shouldSystemBlockNumber(
+ context, phoneNumber, extras);
+ if (blockStatus != BlockedNumberContract.STATUS_NOT_BLOCKED) {
+ Log.d(TAG, phoneNumber + " is blocked.");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, e, "Exception checking for blocked number");
+ }
+
+ int durationMillis = (int) ((System.nanoTime() - startTimeNano) / 1000000);
+ if (durationMillis > 500 || Log.isLoggable(android.util.Log.DEBUG)) {
+ Log.d(TAG, "Blocked number lookup took: " + durationMillis + " ms.");
+ }
+ return blockStatus;
}
}
diff --git a/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java b/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java
new file mode 100644
index 0000000..daf6be0
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2019 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;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.provider.BlockedNumberContract;
+import android.provider.CallLog;
+import android.telecom.CallerInfo;
+import android.telecom.Log;
+import android.telecom.TelecomManager;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallerInfoLookupHelper;
+import com.android.server.telecom.LogUtils;
+import com.android.server.telecom.LoggedHandlerExecutor;
+import com.android.server.telecom.settings.BlockedNumbersUtil;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+public class BlockCheckerFilter extends CallFilter {
+ private final Call mCall;
+ private final Context mContext;
+ private final CallerInfoLookupHelper mCallerInfoLookupHelper;
+ private final BlockCheckerAdapter mBlockCheckerAdapter;
+ private final String TAG = "BlockCheckerFilter";
+ private boolean mContactExists;
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+
+ public static final long CALLER_INFO_QUERY_TIMEOUT = 5000;
+
+ public BlockCheckerFilter(Context context, Call call,
+ CallerInfoLookupHelper callerInfoLookupHelper,
+ BlockCheckerAdapter blockCheckerAdapter) {
+ mCall = call;
+ mContext = context;
+ mCallerInfoLookupHelper = callerInfoLookupHelper;
+ mBlockCheckerAdapter = blockCheckerAdapter;
+ mContactExists = false;
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ }
+
+ @Override
+ public CompletionStage<CallFilteringResult> startFilterLookup(CallFilteringResult result) {
+ Log.addEvent(mCall, LogUtils.Events.BLOCK_CHECK_INITIATED);
+ CompletableFuture<CallFilteringResult> resultFuture = new CompletableFuture<>();
+ Bundle extras = new Bundle();
+ if (BlockedNumbersUtil.isEnhancedCallBlockingEnabledByPlatform(mContext)) {
+ int presentation = mCall.getHandlePresentation();
+ extras.putInt(BlockedNumberContract.EXTRA_CALL_PRESENTATION, presentation);
+ if (presentation == TelecomManager.PRESENTATION_ALLOWED) {
+ mCallerInfoLookupHelper.startLookup(mCall.getHandle(),
+ new CallerInfoLookupHelper.OnQueryCompleteListener() {
+ @Override
+ public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) {
+ if (info != null && info.contactExists) {
+ mContactExists = true;
+ }
+ getBlockStatus(resultFuture);
+ }
+
+ @Override
+ public void onContactPhotoQueryComplete(Uri handle, CallerInfo info) {
+ // Ignore
+ }
+ });
+ } else {
+ getBlockStatus(resultFuture);
+ }
+ } else {
+ getBlockStatus(resultFuture);
+ }
+ return resultFuture;
+ }
+
+ private void getBlockStatus(
+ CompletableFuture<CallFilteringResult> resultFuture) {
+ // Set extras
+ Bundle extras = new Bundle();
+ if (BlockedNumbersUtil.isEnhancedCallBlockingEnabledByPlatform(mContext)) {
+ int presentation = mCall.getHandlePresentation();
+ extras.putInt(BlockedNumberContract.EXTRA_CALL_PRESENTATION, presentation);
+ if (presentation == TelecomManager.PRESENTATION_ALLOWED) {
+ extras.putBoolean(BlockedNumberContract.EXTRA_CONTACT_EXIST, mContactExists);
+ }
+ }
+
+ // Set number
+ final String number = mCall.getHandle() == null ? null :
+ mCall.getHandle().getSchemeSpecificPart();
+
+ CompletableFuture.supplyAsync(
+ () -> mBlockCheckerAdapter.getBlockStatus(mContext, number, extras),
+ new LoggedHandlerExecutor(mHandler, "BCF.gBS", null))
+ .thenApplyAsync((x) -> completeResult(resultFuture, x),
+ new LoggedHandlerExecutor(mHandler, "BCF.gBS", null));
+ }
+
+ private int completeResult(CompletableFuture<CallFilteringResult> resultFuture,
+ int blockStatus) {
+ CallFilteringResult result;
+ if (blockStatus != BlockedNumberContract.STATUS_NOT_BLOCKED) {
+ result = new CallFilteringResult.Builder()
+ .setShouldAllowCall(false)
+ .setShouldReject(true)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(false)
+ .setCallBlockReason(getBlockReason(blockStatus))
+ .setCallScreeningAppName(null)
+ .setCallScreeningComponentName(null)
+ .setContactExists(mContactExists)
+ .build();
+ } else {
+ result = new CallFilteringResult.Builder()
+ .setShouldAllowCall(true)
+ .setShouldReject(false)
+ .setShouldSilence(false)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(true)
+ .setContactExists(mContactExists)
+ .build();
+ }
+ Log.addEvent(mCall, LogUtils.Events.BLOCK_CHECK_FINISHED,
+ BlockedNumberContract.SystemContract.blockStatusToString(blockStatus) + " "
+ + result);
+ resultFuture.complete(result);
+ mHandlerThread.quitSafely();
+ return blockStatus;
+ }
+
+ private int getBlockReason(int blockStatus) {
+ switch (blockStatus) {
+ case BlockedNumberContract.STATUS_BLOCKED_IN_LIST:
+ return CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER;
+
+ case BlockedNumberContract.STATUS_BLOCKED_UNKNOWN_NUMBER:
+ return CallLog.Calls.BLOCK_REASON_UNKNOWN_NUMBER;
+
+ case BlockedNumberContract.STATUS_BLOCKED_RESTRICTED:
+ return CallLog.Calls.BLOCK_REASON_RESTRICTED_NUMBER;
+
+ case BlockedNumberContract.STATUS_BLOCKED_PAYPHONE:
+ return CallLog.Calls.BLOCK_REASON_PAY_PHONE;
+
+ case BlockedNumberContract.STATUS_BLOCKED_NOT_IN_CONTACTS:
+ return CallLog.Calls.BLOCK_REASON_NOT_IN_CONTACTS;
+
+ default:
+ Log.w(this,
+ "There's no call log block reason can be converted");
+ return CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER;
+ }
+ }
+}
diff --git a/src/com/android/server/telecom/callfiltering/CallFilter.java b/src/com/android/server/telecom/callfiltering/CallFilter.java
new file mode 100644
index 0000000..4b79439
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/CallFilter.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 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 java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+public class CallFilter {
+ private List<CallFilter> mDependencies;
+ private List<CallFilter> mFollowings;
+ private int mIndegree;
+ public CallFilteringResult mPriorStageResult;
+ public CallFilteringResult result;
+ private CompletableFuture<CallFilteringResult> mResultFuture;
+
+ public CallFilter() {
+ mDependencies = new ArrayList<>();
+ mFollowings = new ArrayList<>();
+ mPriorStageResult = null;
+ }
+
+ public CompletionStage<CallFilteringResult> startFilterLookup(
+ CallFilteringResult priorStageResult) {
+ return CompletableFuture.completedFuture(priorStageResult);
+ }
+
+ List<CallFilter> getDependencies() {
+ return mDependencies;
+ }
+
+ void addDependency(CallFilter filter) {
+ synchronized (this) {
+ mDependencies.add(filter);
+ mIndegree = mDependencies.size();
+ }
+ }
+
+ List<CallFilter> getFollowings() {
+ return mFollowings;
+ }
+
+ void addFollowings(CallFilter filter) {
+ mFollowings.add(filter);
+ }
+
+ int decrementAndGetIndegree() {
+ synchronized (this) {
+ mIndegree--;
+ return mIndegree;
+ }
+ }
+
+ public CallFilteringResult getResult() {
+ if (result == null) {
+ throw new NullPointerException("Result of this filter is null. This filter hasn't "
+ + "finished performing");
+ } else {
+ return result;
+ }
+ }
+}
diff --git a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
index af3ee1e..d95d578 100644
--- a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
+++ b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
@@ -20,65 +20,125 @@
import android.provider.CallLog.Calls;
import android.text.TextUtils;
+import java.util.Objects;
+
public class CallFilteringResult {
+ public static class Builder {
+ private boolean mShouldAllowCall;
+ private boolean mShouldReject;
+ private boolean mShouldAddToCallLog;
+ private boolean mShouldShowNotification;
+ private boolean mShouldSilence = false;
+ private boolean mShouldScreenViaAudio = false;
+ private boolean mContactExists = false;
+ private int mCallBlockReason = Calls.BLOCK_REASON_NOT_BLOCKED;
+ private CharSequence mCallScreeningAppName = null;
+ private String mCallScreeningComponentName = null;
+
+ public Builder setShouldAllowCall(boolean shouldAllowCall) {
+ mShouldAllowCall = shouldAllowCall;
+ return this;
+ }
+
+ public Builder setShouldReject(boolean shouldReject) {
+ mShouldReject = shouldReject;
+ return this;
+ }
+
+ public Builder setShouldAddToCallLog(boolean shouldAddToCallLog) {
+ mShouldAddToCallLog = shouldAddToCallLog;
+ return this;
+ }
+
+ public Builder setShouldShowNotification(boolean shouldShowNotification) {
+ mShouldShowNotification = shouldShowNotification;
+ return this;
+ }
+
+ public Builder setShouldSilence(boolean shouldSilence) {
+ mShouldSilence = shouldSilence;
+ return this;
+ }
+
+ public Builder setCallBlockReason(int callBlockReason) {
+ mCallBlockReason = callBlockReason;
+ return this;
+ }
+
+ public Builder setShouldScreenViaAudio(boolean shouldScreenViaAudio) {
+ mShouldScreenViaAudio = shouldScreenViaAudio;
+ return this;
+ }
+
+ public Builder setCallScreeningAppName(CharSequence callScreeningAppName) {
+ mCallScreeningAppName = callScreeningAppName;
+ return this;
+ }
+
+ public Builder setCallScreeningComponentName(String callScreeningComponentName) {
+ mCallScreeningComponentName = callScreeningComponentName;
+ return this;
+ }
+
+ public Builder setContactExists(boolean contactExists) {
+ mContactExists = contactExists;
+ return this;
+ }
+
+ public static Builder from(CallFilteringResult result) {
+ return new Builder()
+ .setShouldAllowCall(result.shouldAllowCall)
+ .setShouldReject(result.shouldReject)
+ .setShouldAddToCallLog(result.shouldAddToCallLog)
+ .setShouldShowNotification(result.shouldShowNotification)
+ .setShouldSilence(result.shouldSilence)
+ .setCallBlockReason(result.mCallBlockReason)
+ .setShouldScreenViaAudio(result.shouldScreenViaAudio)
+ .setCallScreeningAppName(result.mCallScreeningAppName)
+ .setCallScreeningComponentName(result.mCallScreeningComponentName)
+ .setContactExists(result.contactExists);
+ }
+
+ public CallFilteringResult build() {
+ return new CallFilteringResult(mShouldAllowCall, mShouldReject, mShouldSilence,
+ mShouldAddToCallLog, mShouldShowNotification, mCallBlockReason,
+ mCallScreeningAppName, mCallScreeningComponentName, mShouldScreenViaAudio,
+ mContactExists);
+ }
+ }
+
public boolean shouldAllowCall;
public boolean shouldReject;
public boolean shouldSilence;
public boolean shouldAddToCallLog;
+ public boolean shouldScreenViaAudio = false;
public boolean shouldShowNotification;
- public int mCallBlockReason = CallLog.Calls.BLOCK_REASON_NOT_BLOCKED;
- public CharSequence mCallScreeningAppName = null;
- public String mCallScreeningComponentName = null;
+ public int mCallBlockReason;
+ public CharSequence mCallScreeningAppName;
+ public String mCallScreeningComponentName;
+ public boolean contactExists;
- public CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
- shouldAddToCallLog, boolean shouldShowNotification) {
- this.shouldAllowCall = shouldAllowCall;
- this.shouldReject = shouldReject;
- this.shouldSilence = false;
- this.shouldAddToCallLog = shouldAddToCallLog;
- this.shouldShowNotification = shouldShowNotification;
- }
-
- public CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
- shouldAddToCallLog, boolean shouldShowNotification, int callBlockReason,
- CharSequence callScreeningAppName, String callScreeningComponentName) {
- this.shouldAllowCall = shouldAllowCall;
- this.shouldReject = shouldReject;
- this.shouldSilence = false;
- this.shouldAddToCallLog = shouldAddToCallLog;
- this.shouldShowNotification = shouldShowNotification;
- this.mCallBlockReason = callBlockReason;
- this.mCallScreeningAppName = callScreeningAppName;
- this.mCallScreeningComponentName = callScreeningComponentName;
- }
-
- public CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
- shouldSilence, boolean shouldAddToCallLog, boolean shouldShowNotification) {
- this.shouldAllowCall = shouldAllowCall;
- this.shouldReject = shouldReject;
- this.shouldSilence = shouldSilence;
- this.shouldAddToCallLog = shouldAddToCallLog;
- this.shouldShowNotification = shouldShowNotification;
- }
-
- public CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
+ private CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
shouldSilence, boolean shouldAddToCallLog, boolean shouldShowNotification, int
- callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName) {
+ callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName,
+ boolean shouldScreenViaAudio, boolean contactExists) {
this.shouldAllowCall = shouldAllowCall;
this.shouldReject = shouldReject;
this.shouldSilence = shouldSilence;
this.shouldAddToCallLog = shouldAddToCallLog;
this.shouldShowNotification = shouldShowNotification;
+ this.shouldScreenViaAudio = shouldScreenViaAudio;
this.mCallBlockReason = callBlockReason;
this.mCallScreeningAppName = callScreeningAppName;
this.mCallScreeningComponentName = callScreeningComponentName;
+ this.contactExists = contactExists;
}
/**
* Combine this CallFilteringResult with another, returning a CallFilteringResult with the more
* restrictive properties of the two. Where there are multiple call filtering components which
- * block a call, the first filter from {@link AsyncBlockCheckFilter},
- * {@link DirectToVoicemailCallFilter}, {@link CallScreeningServiceFilter} which blocked a call
+ * block a call, the first filter from {@link BlockCheckerFilter},
+ * {@link DirectToVoicemailFilter}, {@link CallScreeningServiceFilter} which blocked a call
* shall be used to populate the call block reason, component name, etc.
*/
public CallFilteringResult combine(CallFilteringResult other) {
@@ -109,12 +169,23 @@
other.mCallScreeningAppName, other.mCallScreeningComponentName);
}
- return new CallFilteringResult(
- shouldAllowCall && other.shouldAllowCall,
- shouldReject || other.shouldReject,
- shouldSilence || other.shouldSilence,
- shouldAddToCallLog && other.shouldAddToCallLog,
- shouldShowNotification && other.shouldShowNotification);
+ if (shouldScreenViaAudio) {
+ return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_NOT_BLOCKED,
+ mCallScreeningAppName, mCallScreeningComponentName);
+ } else if (other.shouldScreenViaAudio) {
+ return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_NOT_BLOCKED,
+ other.mCallScreeningAppName, other.mCallScreeningComponentName);
+ }
+
+ return new Builder()
+ .setShouldAllowCall(shouldAllowCall && other.shouldAllowCall)
+ .setShouldReject(shouldReject || other.shouldReject)
+ .setShouldSilence(shouldSilence || other.shouldSilence)
+ .setShouldAddToCallLog(shouldAddToCallLog && other.shouldAddToCallLog)
+ .setShouldShowNotification(shouldShowNotification && other.shouldShowNotification)
+ .setShouldScreenViaAudio(shouldScreenViaAudio || other.shouldScreenViaAudio)
+ .setContactExists(contactExists || other.contactExists)
+ .build();
}
private boolean isBlockedByProvider(int blockReason) {
@@ -131,15 +202,18 @@
private CallFilteringResult getCombinedCallFilteringResult(CallFilteringResult other,
int callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName) {
- return new CallFilteringResult(
- shouldAllowCall && other.shouldAllowCall,
- shouldReject || other.shouldReject,
- shouldSilence|| other.shouldSilence,
- shouldAddToCallLog && other.shouldAddToCallLog,
- shouldShowNotification && other.shouldShowNotification,
- callBlockReason,
- callScreeningAppName,
- callScreeningComponentName);
+ return new Builder()
+ .setShouldAllowCall(shouldAllowCall && other.shouldAllowCall)
+ .setShouldReject(shouldReject || other.shouldReject)
+ .setShouldSilence(shouldSilence || other.shouldSilence)
+ .setShouldAddToCallLog(shouldAddToCallLog && other.shouldAddToCallLog)
+ .setShouldShowNotification(shouldShowNotification && other.shouldShowNotification)
+ .setShouldScreenViaAudio(shouldScreenViaAudio || other.shouldScreenViaAudio)
+ .setCallBlockReason(callBlockReason)
+ .setCallScreeningAppName(callScreeningAppName)
+ .setCallScreeningComponentName(callScreeningComponentName)
+ .setContactExists(contactExists || other.contactExists)
+ .build();
}
@@ -156,22 +230,13 @@
if (shouldAddToCallLog != that.shouldAddToCallLog) return false;
if (shouldShowNotification != that.shouldShowNotification) return false;
if (mCallBlockReason != that.mCallBlockReason) return false;
+ if (contactExists != that.contactExists) return false;
- if ((TextUtils.isEmpty(mCallScreeningAppName) &&
- TextUtils.isEmpty(that.mCallScreeningAppName)) &&
- (TextUtils.isEmpty(mCallScreeningComponentName) &&
- TextUtils.isEmpty(that.mCallScreeningComponentName))) {
- return true;
- } else if (!TextUtils.isEmpty(mCallScreeningAppName) &&
- !TextUtils.isEmpty(that.mCallScreeningAppName) &&
- mCallScreeningAppName.equals(that.mCallScreeningAppName) &&
- !TextUtils.isEmpty(mCallScreeningComponentName) &&
- !TextUtils.isEmpty(that.mCallScreeningComponentName) &&
- mCallScreeningComponentName.equals(that.mCallScreeningComponentName)) {
- return true;
+ if (!Objects.equals(mCallScreeningAppName, that.mCallScreeningAppName)) return false;
+ if (!Objects.equals(mCallScreeningComponentName, that.mCallScreeningComponentName)) {
+ return false;
}
-
- return false;
+ return true;
}
@Override
@@ -198,6 +263,10 @@
sb.append("Ignore");
}
+ if (shouldScreenViaAudio) {
+ sb.append(", audio processing");
+ }
+
if (shouldAddToCallLog) {
sb.append(", logged");
}
@@ -206,6 +275,10 @@
sb.append(", notified");
}
+ if (contactExists) {
+ sb.append(", contact exists");
+ }
+
if (mCallBlockReason != 0) {
sb.append(", mCallBlockReason = ");
sb.append(mCallBlockReason);
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceController.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceController.java
deleted file mode 100644
index 291fef8..0000000
--- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceController.java
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright (C) 2018 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.ComponentName;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.PersistableBundle;
-import android.provider.CallLog;
-import android.telecom.Log;
-import android.telecom.Logging.Runnable;
-import android.telecom.TelecomManager;
-import android.telephony.CarrierConfigManager;
-import android.text.TextUtils;
-
-import com.android.internal.telephony.CallerInfo;
-import com.android.server.telecom.Call;
-import com.android.server.telecom.CallScreeningServiceHelper;
-import com.android.server.telecom.CallerInfoLookupHelper;
-import com.android.server.telecom.CallsManager;
-import com.android.server.telecom.LogUtils;
-import com.android.server.telecom.ParcelableCallUtils;
-import com.android.server.telecom.PhoneAccountRegistrar;
-import com.android.server.telecom.TelecomServiceImpl;
-import com.android.server.telecom.TelecomSystem;
-
-/**
- * This class supports binding to the various {@link android.telecom.CallScreeningService}:
- * carrier, default dialer and user chosen. Carrier's CallScreeningService implementation will be
- * bound first, and then default dialer's and user chosen's. If Carrier's CallScreeningService
- * blocks a call, no further CallScreeningService after it will be bound.
- */
-public class CallScreeningServiceController implements IncomingCallFilter.CallFilter,
- CallScreeningServiceFilter.CallScreeningFilterResultCallback {
-
- private final Context mContext;
- private final CallsManager mCallsManager;
- private final PhoneAccountRegistrar mPhoneAccountRegistrar;
- private final ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
- private final TelecomSystem.SyncRoot mTelecomLock;
- private final TelecomServiceImpl.SettingsSecureAdapter mSettingsSecureAdapter;
- private final CallerInfoLookupHelper mCallerInfoLookupHelper;
- private final CallScreeningServiceHelper.AppLabelProxy mAppLabelProxy;
-
- private final int CARRIER_CALL_FILTERING_TIMED_OUT = 2000; // 2 seconds
- private final int CALL_FILTERING_TIMED_OUT = 4500; // 4.5 seconds
-
- private final Handler mHandler = new Handler(Looper.getMainLooper());
-
- private Call mCall;
- private CallFilterResultCallback mCallback;
-
- private CallFilteringResult mResult = new CallFilteringResult(
- true, // shouldAllowCall
- false, // shouldReject
- true, // shouldAddToCallLog
- true // shouldShowNotification
- );
-
- private boolean mIsFinished;
- private boolean mIsCarrierFinished;
- private boolean mIsDefaultDialerFinished;
- private boolean mIsUserChosenFinished;
-
- public CallScreeningServiceController(
- Context context,
- CallsManager callsManager,
- PhoneAccountRegistrar phoneAccountRegistrar,
- ParcelableCallUtils.Converter parcelableCallUtilsConverter,
- TelecomSystem.SyncRoot lock,
- TelecomServiceImpl.SettingsSecureAdapter settingsSecureAdapter,
- CallerInfoLookupHelper callerInfoLookupHelper,
- CallScreeningServiceHelper.AppLabelProxy appLabelProxy) {
- mContext = context;
- mCallsManager = callsManager;
- mPhoneAccountRegistrar = phoneAccountRegistrar;
- mParcelableCallUtilsConverter = parcelableCallUtilsConverter;
- mTelecomLock = lock;
- mSettingsSecureAdapter = settingsSecureAdapter;
- mCallerInfoLookupHelper = callerInfoLookupHelper;
- mAppLabelProxy = appLabelProxy;
- }
-
- @Override
- public void startFilterLookup(Call call, CallFilterResultCallback callBack) {
- mCall = call;
- mCallback = callBack;
- mIsFinished = false;
- mIsCarrierFinished = false;
- mIsDefaultDialerFinished = false;
- mIsUserChosenFinished = false;
-
- bindCarrierService();
-
- // Call screening filtering timed out
- mHandler.postDelayed(new Runnable("ICF.pFTO", mTelecomLock) {
- @Override
- public void loggedRun() {
- if (!mIsFinished) {
- Log.i(CallScreeningServiceController.this, "Call screening has timed out.");
- finishCallScreening();
- }
- }
- }.prepare(), CALL_FILTERING_TIMED_OUT);
- }
-
- @Override
- public void onCallScreeningFilterComplete(Call call, CallFilteringResult result,
- String packageName) {
- synchronized (mTelecomLock) {
- mResult = result.combine(mResult);
- if (!TextUtils.isEmpty(packageName) && packageName.equals(getCarrierPackageName())) {
- mIsCarrierFinished = true;
- if (result.mCallBlockReason == CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE) {
- finishCallScreening();
- } else {
- checkContactExistsAndBindService();
- }
- } else if (!TextUtils.isEmpty(packageName) &&
- packageName.equals(getDefaultDialerPackageName())) {
- // Default dialer defined CallScreeningService cannot skip the call log.
- mResult.shouldAddToCallLog = true;
- mIsDefaultDialerFinished = true;
- if (result.mCallBlockReason == CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE ||
- mIsUserChosenFinished) {
- finishCallScreening();
- }
- } else if (!TextUtils.isEmpty(packageName) &&
- packageName.equals(getUserChosenPackageName())) {
- // User defined CallScreeningService cannot skip the call log.
- mResult.shouldAddToCallLog = true;
- mIsUserChosenFinished = true;
- if (mIsDefaultDialerFinished) {
- finishCallScreening();
- }
- }
- }
- }
-
- private void bindCarrierService() {
- String carrierPackageName = getCarrierPackageName();
- if (TextUtils.isEmpty(carrierPackageName)) {
- mIsCarrierFinished = true;
- bindDefaultDialerAndUserChosenService();
- } else {
- createCallScreeningServiceFilter().startCallScreeningFilter(mCall, this,
- carrierPackageName, mAppLabelProxy.getAppLabel(carrierPackageName),
- CallScreeningServiceFilter.CALL_SCREENING_FILTER_TYPE_CARRIER);
- }
-
- // Carrier filtering timed out
- mHandler.postDelayed(new Runnable("ICF.pFTO", mTelecomLock) {
- @Override
- public void loggedRun() {
- if (!mIsCarrierFinished) {
- mIsCarrierFinished = true;
- checkContactExistsAndBindService();
- }
- }
- }.prepare(), CARRIER_CALL_FILTERING_TIMED_OUT);
- }
-
- private void bindDefaultDialerAndUserChosenService() {
- if (mIsCarrierFinished) {
- String dialerPackageName = getDefaultDialerPackageName();
- String systemDialerPackageName = getSystemDialerPackageName();
- if (TextUtils.isEmpty(dialerPackageName)) {
- mIsDefaultDialerFinished = true;
- } else {
- int dialerType = dialerPackageName.equals(systemDialerPackageName) ?
- CallScreeningServiceFilter.CALL_SCREENING_FILTER_TYPE_SYSTEM_DIALER :
- CallScreeningServiceFilter.CALL_SCREENING_FILTER_TYPE_DEFAULT_DIALER;
- createCallScreeningServiceFilter().startCallScreeningFilter(mCall,
- CallScreeningServiceController.this, dialerPackageName,
- mAppLabelProxy.getAppLabel(dialerPackageName), dialerType);
- }
-
- String userChosenPackageName = getUserChosenPackageName();
- if (TextUtils.isEmpty(userChosenPackageName)) {
- mIsUserChosenFinished = true;
- } else {
- createCallScreeningServiceFilter().startCallScreeningFilter(mCall,
- CallScreeningServiceController.this, userChosenPackageName,
- mAppLabelProxy.getAppLabel(userChosenPackageName),
- CallScreeningServiceFilter.CALL_SCREENING_FILTER_TYPE_USER_SELECTED);
- }
-
- if (mIsDefaultDialerFinished && mIsUserChosenFinished) {
- finishCallScreening();
- }
- }
- }
-
- private CallScreeningServiceFilter createCallScreeningServiceFilter() {
- return new CallScreeningServiceFilter(
- mContext,
- mCallsManager,
- mPhoneAccountRegistrar,
- mParcelableCallUtilsConverter,
- mTelecomLock,
- mSettingsSecureAdapter);
- }
-
- private void checkContactExistsAndBindService() {
- mCallerInfoLookupHelper.startLookup(mCall.getHandle(),
- new CallerInfoLookupHelper.OnQueryCompleteListener() {
- @Override
- public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) {
- boolean contactExists = info != null && info.contactExists;
- Log.i(CallScreeningServiceController.this, "Contact exists: " +
- contactExists);
- if (!contactExists) {
- bindDefaultDialerAndUserChosenService();
- } else {
- finishCallScreening();
- }
- }
-
- @Override
- public void onContactPhotoQueryComplete(Uri handle, CallerInfo
- info) {
- // ignore
- }
- });
- }
-
- private void finishCallScreening() {
- Log.addEvent(mCall, LogUtils.Events.CONTROLLER_SCREENING_COMPLETED, mResult);
- mCallback.onCallFilteringComplete(mCall, mResult);
- mIsFinished = true;
- }
-
- private String getCarrierPackageName() {
- ComponentName componentName = null;
- CarrierConfigManager configManager = (CarrierConfigManager) mContext.getSystemService
- (Context.CARRIER_CONFIG_SERVICE);
- PersistableBundle configBundle = configManager.getConfig();
- if (configBundle != null) {
- componentName = ComponentName.unflattenFromString(configBundle.getString
- (CarrierConfigManager.KEY_CARRIER_CALL_SCREENING_APP_STRING, ""));
- }
-
- return componentName != null ? componentName.getPackageName() : null;
- }
-
- private String getDefaultDialerPackageName() {
- return TelecomManager.from(mContext).getDefaultDialerPackage();
- }
-
- private String getSystemDialerPackageName() {
- return TelecomManager.from(mContext).getSystemDialerPackage();
- }
-
- private String getUserChosenPackageName() {
- return mCallsManager.getRoleManagerAdapter().getDefaultCallScreeningApp();
- }
-}
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
index c8ec18a..1e52c5a 100644
--- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -11,322 +11,299 @@
* 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
+ * limitations under the License.
*/
package com.android.server.telecom.callfiltering;
+import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.IBinder;
-import android.os.PersistableBundle;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.provider.CallLog;
-import android.provider.Settings;
import android.telecom.Log;
import android.telecom.TelecomManager;
-import android.telephony.CarrierConfigManager;
-import android.text.TextUtils;
import com.android.internal.telecom.ICallScreeningAdapter;
import com.android.internal.telecom.ICallScreeningService;
+import com.android.server.telecom.AppLabelProxy;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallScreeningServiceHelper;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.LogUtils;
import com.android.server.telecom.ParcelableCallUtils;
-import com.android.server.telecom.PhoneAccountRegistrar;
-import com.android.server.telecom.TelecomServiceImpl.SettingsSecureAdapter;
-import com.android.server.telecom.TelecomSystem;
-/**
- * Binds to {@link ICallScreeningService} to allow call blocking. A single instance of this class
- * handles a single call.
- */
-public class CallScreeningServiceFilter {
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
- public static final int CALL_SCREENING_FILTER_TYPE_USER_SELECTED = 1;
- public static final int CALL_SCREENING_FILTER_TYPE_DEFAULT_DIALER = 2;
- public static final int CALL_SCREENING_FILTER_TYPE_SYSTEM_DIALER = 3;
- public static final int CALL_SCREENING_FILTER_TYPE_CARRIER = 4;
+public class CallScreeningServiceFilter extends CallFilter {
+ public static final int PACKAGE_TYPE_CARRIER = 0;
+ public static final int PACKAGE_TYPE_DEFAULT_DIALER = 1;
+ public static final int PACKAGE_TYPE_USER_CHOSEN = 2;
+ public static final long CALL_SCREENING_FILTER_TIMEOUT = 5000;
- public interface CallScreeningFilterResultCallback {
- void onCallScreeningFilterComplete(Call call, CallFilteringResult result, String
- packageName);
- }
-
- private class CallScreeningServiceConnection implements ServiceConnection {
- @Override
- public void onServiceConnected(ComponentName componentName, IBinder service) {
- Log.startSession("CSCR.oSC");
- try {
- synchronized (mTelecomLock) {
- Log.addEvent(mCall, LogUtils.Events.SCREENING_BOUND, componentName);
- if (!mHasFinished) {
- onServiceBound(ICallScreeningService.Stub.asInterface(service));
- }
- }
- } finally {
- Log.endSession();
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName componentName) {
- Log.startSession("CSCR.oSD");
- try {
- synchronized (mTelecomLock) {
- finishCallScreening();
- }
- } finally {
- Log.endSession();
- }
- }
- }
+ private final Call mCall;
+ private final String mPackageName;
+ private final int mPackagetype;
+ private PackageManager mPackageManager;
+ private Context mContext;
+ private CallScreeningServiceConnection mConnection;
+ private final CallsManager mCallsManager;
+ private CharSequence mAppName;
+ private final ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
private class CallScreeningAdapter extends ICallScreeningAdapter.Stub {
+ private CompletableFuture<CallFilteringResult> mResultFuture;
+
+ public CallScreeningAdapter(CompletableFuture<CallFilteringResult> resultFuture) {
+ mResultFuture = resultFuture;
+ }
+
@Override
public void allowCall(String callId) {
- Log.startSession("CSCR.aC");
- long token = Binder.clearCallingIdentity();
+ Long token = Binder.clearCallingIdentity();
+ Log.startSession("NCSSF.aC");
try {
- synchronized (mTelecomLock) {
- Log.d(this, "allowCall(%s)", callId);
- if (mCall != null && mCall.getId().equals(callId)) {
- mResult = new CallFilteringResult(
- true, // shouldAllowCall
- false, //shouldReject
- false, //shouldSilence
- true, //shouldAddToCallLog
- true // shouldShowNotification
- );
- } else {
- Log.w(this, "allowCall, unknown call id: %s", callId);
- }
- finishCallScreening();
+ if (mCall == null || (!mCall.getId().equals(callId))) {
+ Log.w(this, "allowCall, unknown call id: %s", callId);
}
+ Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, mPriorStageResult);
+ mResultFuture.complete(mPriorStageResult);
} finally {
+ unbindCallScreeningService();
Binder.restoreCallingIdentity(token);
Log.endSession();
}
}
@Override
- public void disallowCall(
- String callId,
- boolean shouldReject,
- boolean shouldAddToCallLog,
- boolean shouldShowNotification,
+ public void disallowCall(String callId, boolean shouldReject,
+ boolean shouldAddToCallLog, boolean shouldShowNotification,
ComponentName componentName) {
- Log.startSession("CSCR.dC");
long token = Binder.clearCallingIdentity();
+ Log.startSession("NCSSF.dC");
try {
- synchronized (mTelecomLock) {
- boolean isServiceRequestingLogging = isLoggable(componentName,
- shouldAddToCallLog);
- Log.i(this, "disallowCall(%s), shouldReject: %b, shouldAddToCallLog: %b, "
- + "shouldShowNotification: %b", callId, shouldReject,
- isServiceRequestingLogging, shouldShowNotification);
- if (mCall != null && mCall.getId().equals(callId)) {
- mResult = new CallFilteringResult(
- false, // shouldAllowCall
- shouldReject, //shouldReject
- false, // shouldSilenceCall
- isServiceRequestingLogging, //shouldAddToCallLog
- shouldShowNotification, // shouldShowNotification
- CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
- mAppName, //callScreeningAppName
- componentName.flattenToString() //callScreeningComponentName
- );
- } else {
- Log.w(this, "disallowCall, unknown call id: %s", callId);
- }
- finishCallScreening();
+ if (mCall != null && mCall.getId().equals(callId)) {
+ CallFilteringResult result = new CallFilteringResult.Builder()
+ .setShouldAllowCall(false)
+ .setShouldReject(shouldReject)
+ .setShouldSilence(false)
+ .setShouldAddToCallLog(shouldAddToCallLog
+ || packageTypeShouldAdd(mPackagetype))
+ .setShouldShowNotification(shouldShowNotification)
+ .setCallBlockReason(CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
+ .setCallScreeningAppName(mAppName)
+ .setCallScreeningComponentName(componentName.flattenToString())
+ .setContactExists(mPriorStageResult.contactExists)
+ .build();
+ Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, result);
+ mResultFuture.complete(result);
+ } else {
+ Log.w(this, "disallowCall, unknown call id: %s", callId);
+ mResultFuture.complete(mPriorStageResult);
}
} finally {
- Binder.restoreCallingIdentity(token);
+ unbindCallScreeningService();
Log.endSession();
+ Binder.restoreCallingIdentity(token);
}
}
@Override
public void silenceCall(String callId) {
- Log.startSession("CSCR.sC");
long token = Binder.clearCallingIdentity();
+ Log.startSession("NCSSF.sC");
try {
- synchronized (mTelecomLock) {
- Log.d(this, "silenceCall(%s)", callId);
- if (mCall != null && mCall.getId().equals(callId)) {
- mResult = new CallFilteringResult(
- true, // shouldAllowCall
- false, //shouldReject
- true, //shouldSilence
- true, //shouldAddToCallLog
- true // shouldShowNotification
- );
- } else {
- Log.w(this, "silenceCall, unknown call id: %s", callId);
- }
- finishCallScreening();
+ if (mCall != null && mCall.getId().equals(callId)) {
+ CallFilteringResult result = new CallFilteringResult.Builder()
+ .setShouldAllowCall(true)
+ .setShouldReject(false)
+ .setShouldSilence(true)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(true)
+ .setContactExists(mPriorStageResult.contactExists)
+ .build();
+ Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, result);
+ mResultFuture.complete(result);
+ } else {
+ Log.w(this, "silenceCall, unknown call id: %s", callId);
+ mResultFuture.complete(mPriorStageResult);
}
} finally {
- Binder.restoreCallingIdentity(token);
+ unbindCallScreeningService();
Log.endSession();
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void screenCallFurther(String callId) {
+ if (mPackagetype != PACKAGE_TYPE_DEFAULT_DIALER) {
+ throw new SecurityException("Only the default/system dialer may request screen via"
+ + "background call audio");
+ }
+ // TODO: add permission check for the additional role-based permission
+ long token = Binder.clearCallingIdentity();
+ Log.startSession("NCSSF.sCF");
+
+ try {
+ if (mCall != null && mCall.getId().equals(callId)) {
+ CallFilteringResult result = new CallFilteringResult.Builder()
+ .setShouldAllowCall(true)
+ .setShouldReject(false)
+ .setShouldSilence(false)
+ .setShouldScreenViaAudio(true)
+ .setCallScreeningAppName(mAppName)
+ .setContactExists(mPriorStageResult.contactExists)
+ .build();
+ Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, result);
+ mResultFuture.complete(result);
+ } else {
+ Log.w(this, "screenCallFurther, unknown call id: %s", callId);
+ mResultFuture.complete(mPriorStageResult);
+ }
+ } finally {
+ unbindCallScreeningService();
+ Log.endSession();
+ Binder.restoreCallingIdentity(token);
}
}
}
- private final Context mContext;
- private final CallsManager mCallsManager;
- private final ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
- private final TelecomSystem.SyncRoot mTelecomLock;
- private final SettingsSecureAdapter mSettingsSecureAdapter;
+ private class CallScreeningServiceConnection implements ServiceConnection {
+ private CompletableFuture<CallFilteringResult> mResultFuture;
- private Call mCall;
- private CallScreeningFilterResultCallback mCallback;
- private ICallScreeningService mService;
- private ServiceConnection mConnection;
- private String mPackageName;
- private CharSequence mAppName;
- private boolean mHasFinished = false;
- private int mCallScreeningServiceType;
+ public CallScreeningServiceConnection(CompletableFuture<CallFilteringResult> resultFuture) {
+ mResultFuture = resultFuture;
+ }
- private CallFilteringResult mResult = new CallFilteringResult(
- true, // shouldAllowCall
- false, //shouldReject
- true, //shouldAddToCallLog
- true // shouldShowNotification
- );
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder service) {
+ ICallScreeningService callScreeningService =
+ ICallScreeningService.Stub.asInterface(service);
+ try {
+ callScreeningService.screenCall(new CallScreeningAdapter(mResultFuture),
+ mParcelableCallUtilsConverter.
+ toParcelableCallForScreening(mCall, isSystemDialer()));
+ } catch (RemoteException e) {
+ Log.e(this, e, "Failed to set the call screening adapter");
+ mResultFuture.complete(mPriorStageResult);
+ }
+ Log.addEvent(mCall, LogUtils.Events.SCREENING_BOUND, componentName);
+ Log.i(this, "Binding completed.");
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) {
+ mResultFuture.complete(mPriorStageResult);
+ Log.i(this, "Service disconnected.");
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ mResultFuture.complete(mPriorStageResult);
+ Log.i(this, "Binding died.");
+ }
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ mResultFuture.complete(mPriorStageResult);
+ Log.i(this, "Null binding.");
+ unbindCallScreeningService();
+ }
+ }
public CallScreeningServiceFilter(
+ Call call,
+ String packageName,
+ int packageType,
Context context,
CallsManager callsManager,
- PhoneAccountRegistrar phoneAccountRegistrar,
- ParcelableCallUtils.Converter parcelableCallUtilsConverter,
- TelecomSystem.SyncRoot lock,
- SettingsSecureAdapter settingsSecureAdapter) {
- mContext = context;
- mCallsManager = callsManager;
- mParcelableCallUtilsConverter = parcelableCallUtilsConverter;
- mTelecomLock = lock;
- mSettingsSecureAdapter = settingsSecureAdapter;
- }
-
- public void startCallScreeningFilter(Call call,
- CallScreeningFilterResultCallback callback,
- String packageName,
- CharSequence appName,
- int callScreeningServiceType) {
- if (mHasFinished) {
- Log.w(this, "Attempting to reuse CallScreeningServiceFilter. Ignoring.");
- return;
- }
- Log.addEvent(call, LogUtils.Events.SCREENING_SENT, packageName);
+ AppLabelProxy appLabelProxy,
+ ParcelableCallUtils.Converter parcelableCallUtilsConverter) {
+ super();
mCall = call;
- mCallback = callback;
mPackageName = packageName;
- mAppName = appName;
- mCallScreeningServiceType = callScreeningServiceType;
+ mPackagetype = packageType;
+ mContext = context;
+ mPackageManager = mContext.getPackageManager();
+ mCallsManager = callsManager;
+ mAppName = appLabelProxy.getAppLabel(mPackageName);
+ mParcelableCallUtilsConverter = parcelableCallUtilsConverter;
+ }
- mConnection = new CallScreeningServiceConnection();
+ @Override
+ public CompletionStage<CallFilteringResult> startFilterLookup(
+ CallFilteringResult priorStageResult) {
+ mPriorStageResult = priorStageResult;
+ if (mPackageName == null) {
+ return CompletableFuture.completedFuture(priorStageResult);
+ }
+
+ if (!priorStageResult.shouldAllowCall) {
+ // Call already blocked by other filters, no need to bind to call screening service.
+ return CompletableFuture.completedFuture(priorStageResult);
+ }
+
+ if (priorStageResult.contactExists && (!hasReadContactsPermission())) {
+ // Binding to the call screening service will be skipped if it does NOT hold
+ // READ_CONTACTS permission and the number is in the user’s contacts
+ return CompletableFuture.completedFuture(priorStageResult);
+ }
+
+ CompletableFuture<CallFilteringResult> resultFuture = new CompletableFuture<>();
+
+ bindCallScreeningService(resultFuture);
+ return resultFuture;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + ": " + mPackageName;
+ }
+
+ private boolean hasReadContactsPermission() {
+ int permission = PackageManager.PERMISSION_DENIED;
+ if (mPackagetype == PACKAGE_TYPE_CARRIER || mPackagetype == PACKAGE_TYPE_DEFAULT_DIALER) {
+ permission = PackageManager.PERMISSION_GRANTED;
+ } else if (mPackageManager != null) {
+ permission = mPackageManager.checkPermission(Manifest.permission.READ_CONTACTS,
+ mPackageName);
+ }
+ return permission == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private void bindCallScreeningService(
+ CompletableFuture<CallFilteringResult> resultFuture) {
+ mConnection = new CallScreeningServiceConnection(resultFuture);
if (!CallScreeningServiceHelper.bindCallScreeningService(mContext,
- mCallsManager.getCurrentUserHandle(),
- mPackageName,
- mConnection)) {
- Log.i(this, "Could not bind to call screening service");
- finishCallScreening();
+ mCallsManager.getCurrentUserHandle(), mPackageName, mConnection)) {
+ Log.i(this, "Call screening service binding failed.");
+ resultFuture.complete(mPriorStageResult);
}
}
- private void finishCallScreening() {
- if (!mHasFinished) {
- Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, mResult);
- mCallback.onCallScreeningFilterComplete(mCall, mResult, mPackageName);
+ public void unbindCallScreeningService() {
+ if (mConnection != null) {
+ mContext.unbindService(mConnection);
+ }
+ mConnection = null;
+ }
- if (mConnection != null) {
- // We still need to call unbind even if the service disconnected.
- try {
- mContext.unbindService(mConnection);
- } catch (IllegalArgumentException ie) {
- Log.e(this, ie, "Unbind error");
- }
- mConnection = null;
- }
- mService = null;
- mHasFinished = true;
+ private boolean isSystemDialer() {
+ if (mPackagetype != PACKAGE_TYPE_DEFAULT_DIALER) {
+ return false;
+ } else {
+ return mPackageName.equals(
+ mContext.getSystemService(TelecomManager.class).getSystemDialerPackage());
}
}
- private void onServiceBound(ICallScreeningService service) {
- mService = service;
- try {
- boolean isSystemDialer =
- mCallScreeningServiceType
- == CallScreeningServiceFilter.CALL_SCREENING_FILTER_TYPE_SYSTEM_DIALER;
- // Important: Only send a minimal subset of the call to the screening service.
- // We will send some of the call extras to the call screening service which the system
- // dialer implements.
- mService.screenCall(new CallScreeningAdapter(),
- mParcelableCallUtilsConverter.toParcelableCallForScreening(mCall,
- isSystemDialer));
- } catch (RemoteException e) {
- Log.e(this, e, "Failed to set the call screening adapter.");
- finishCallScreening();
- }
- }
-
- private boolean isLoggable(ComponentName componentName, boolean shouldAddToCallLog) {
- if (isCarrierCallScreeningApp(componentName)) {
- return shouldAddToCallLog;
- } else if (isDefaultDialer(componentName) || isUserChosenCallScreeningApp(componentName)) {
- return true;
- }
-
- return shouldAddToCallLog;
- }
-
- private boolean isCarrierCallScreeningApp(ComponentName componentName) {
- String carrierCallScreeningApp = null;
- CarrierConfigManager configManager = (CarrierConfigManager) mContext
- .getSystemService(Context.CARRIER_CONFIG_SERVICE);
- PersistableBundle configBundle = configManager.getConfig();
- if (configBundle != null) {
- carrierCallScreeningApp = configBundle
- .getString(CarrierConfigManager.KEY_CARRIER_CALL_SCREENING_APP_STRING);
- }
-
- if (!TextUtils.isEmpty(carrierCallScreeningApp) && carrierCallScreeningApp
- .equals(componentName.flattenToString())) {
- return true;
- }
-
- return false;
- }
-
- private boolean isDefaultDialer(ComponentName componentName) {
- String defaultDialer = TelecomManager.from(mContext).getDefaultDialerPackage();
-
- if (!TextUtils.isEmpty(defaultDialer) && defaultDialer
- .equals(componentName.getPackageName())) {
- return true;
- }
-
- return false;
- }
-
- private boolean isUserChosenCallScreeningApp(ComponentName componentName) {
- String defaultCallScreeningApplication = mSettingsSecureAdapter
- .getStringForUser(mContext.getContentResolver(),
- Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT, UserHandle.USER_CURRENT);
-
- if (!TextUtils.isEmpty(defaultCallScreeningApplication) && defaultCallScreeningApplication
- .equals(componentName.flattenToString())) {
- return true;
- }
-
- return false;
+ private boolean packageTypeShouldAdd(int packageType) {
+ return packageType != PACKAGE_TYPE_CARRIER;
}
}
diff --git a/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java b/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
deleted file mode 100644
index 3a8ff7d..0000000
--- a/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2016 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.net.Uri;
-import android.provider.CallLog;
-import android.telecom.Log;
-
-import com.android.internal.telephony.CallerInfo;
-import com.android.server.telecom.Call;
-import com.android.server.telecom.CallerInfoLookupHelper;
-import com.android.server.telecom.LogUtils;
-
-import java.util.Objects;
-
-public class DirectToVoicemailCallFilter implements IncomingCallFilter.CallFilter {
- private final CallerInfoLookupHelper mCallerInfoLookupHelper;
-
- public DirectToVoicemailCallFilter(CallerInfoLookupHelper callerInfoLookupHelper) {
- mCallerInfoLookupHelper = callerInfoLookupHelper;
- }
-
- @Override
- public void startFilterLookup(final Call call, CallFilterResultCallback callback) {
- Log.addEvent(call, LogUtils.Events.DIRECT_TO_VM_INITIATED);
- final Uri callHandle = call.getHandle();
-
- mCallerInfoLookupHelper.startLookup(callHandle,
- new CallerInfoLookupHelper.OnQueryCompleteListener() {
- @Override
- public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) {
- CallFilteringResult result;
- if ((handle != null) && Objects.equals(callHandle, handle)) {
- if (info != null && info.shouldSendToVoicemail) {
- result = new CallFilteringResult(
- false, // shouldAllowCall
- true, // shouldReject
- true, // shouldAddToCallLog
- true, // shouldShowNotification
- CallLog.Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL,
- //callBlockReason
- null, //callScreeningAppName
- null // callScreeningComponentName
- );
- } else {
- result = new CallFilteringResult(
- true, // shouldAllowCall
- false, // shouldReject
- true, // shouldAddToCallLog
- true // shouldShowNotification
- );
- }
- Log.addEvent(call, LogUtils.Events.DIRECT_TO_VM_FINISHED, result);
- callback.onCallFilteringComplete(call, result);
- } else {
- result = new CallFilteringResult(
- true, // shouldAllowCall
- false, // shouldReject
- true, // shouldAddToCallLog
- true // shouldShowNotification
- );
- Log.addEvent(call, LogUtils.Events.DIRECT_TO_VM_FINISHED, result);
- Log.w(this, "CallerInfo lookup returned with a different handle than " +
- "what was passed in. Was %s, should be %s", handle, callHandle);
- callback.onCallFilteringComplete(call, result);
- }
- }
-
- @Override
- public void onContactPhotoQueryComplete(Uri handle, CallerInfo info) {
- // ignore
- }
- });
- }
-}
diff --git a/src/com/android/server/telecom/callfiltering/DirectToVoicemailFilter.java b/src/com/android/server/telecom/callfiltering/DirectToVoicemailFilter.java
new file mode 100644
index 0000000..ab24d94
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/DirectToVoicemailFilter.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2019 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.net.Uri;
+import android.provider.CallLog;
+import android.telecom.CallerInfo;
+import android.telecom.Log;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallerInfoLookupHelper;
+import com.android.server.telecom.LogUtils;
+
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+public class DirectToVoicemailFilter extends CallFilter {
+ private final Call mCall;
+ private final CallerInfoLookupHelper mCallerInfoLookupHelper;
+
+ public DirectToVoicemailFilter(Call call, CallerInfoLookupHelper callerInfoLookupHelper) {
+ mCall = call;
+ mCallerInfoLookupHelper = callerInfoLookupHelper;
+ }
+
+ @Override
+ public CompletionStage<CallFilteringResult> startFilterLookup(CallFilteringResult result) {
+ Log.addEvent(mCall, LogUtils.Events.DIRECT_TO_VM_INITIATED);
+ CompletableFuture<CallFilteringResult> resultFuture = new CompletableFuture<>();
+ mCallerInfoLookupHelper.startLookup(mCall.getHandle(),
+ new CallerInfoLookupHelper.OnQueryCompleteListener() {
+ @Override
+ public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) {
+ if ((handle != null) && Objects.equals(mCall.getHandle(), handle)
+ && (info != null)) {
+ resultFuture.complete(new CallFilteringResult.Builder()
+ .setShouldAllowCall(!info.shouldSendToVoicemail)
+ .setShouldReject(info.shouldSendToVoicemail)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(true)
+ .setCallBlockReason(info.shouldSendToVoicemail ?
+ CallLog.Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL
+ : CallLog.Calls.BLOCK_REASON_NOT_BLOCKED)
+ .setContactExists(info.contactExists)
+ .build());
+ } else {
+ if (info == null) {
+ Log.w(this, "CallerInfo lookup returned a null " +
+ "CallerInfo.");
+ } else {
+ Log.w(this, "CallerInfo lookup returned with "
+ + "handle %s, should be %s", handle,
+ mCall.getHandle());
+ }
+ resultFuture.complete(IncomingCallFilterGraph.DEFAULT_RESULT);
+ }
+ Log.addEvent(mCall, LogUtils.Events.DIRECT_TO_VM_FINISHED);
+ }
+
+ @Override
+ public void onContactPhotoQueryComplete(Uri handle, CallerInfo info) {
+ // Ignore
+ }
+ });
+ return resultFuture;
+ }
+}
diff --git a/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java b/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java
index 1c947d1..860de1f 100644
--- a/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java
+++ b/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java
@@ -27,36 +27,46 @@
import com.android.server.telecom.LogUtils;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.callfiltering.CallFilteringResult.Builder;
import java.util.List;
public class IncomingCallFilter implements CallFilterResultCallback {
+ public static class Factory {
+ public IncomingCallFilter create(Context context, CallFilterResultCallback listener,
+ Call call, TelecomSystem.SyncRoot lock, Timeouts.Adapter timeoutsAdapter,
+ List<CallFilter> filters) {
+ return new IncomingCallFilter(context, listener, call, lock, timeoutsAdapter, filters,
+ new Handler(Looper.getMainLooper()));
+ }
+ }
+
public interface CallFilter {
void startFilterLookup(Call call, CallFilterResultCallback listener);
}
private final TelecomSystem.SyncRoot mTelecomLock;
private final Context mContext;
- private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final Handler mHandler;
private final List<CallFilter> mFilters;
private final Call mCall;
private final CallFilterResultCallback mListener;
private final Timeouts.Adapter mTimeoutsAdapter;
- private CallFilteringResult mResult = new CallFilteringResult(
- true, // shouldAllowCall
- false, // shouldReject
- true, // shouldAddToCallLog
- true // shouldShowNotification
- );
+ private CallFilteringResult mResult = new Builder()
+ .setShouldAllowCall(true)
+ .setShouldReject(false)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(true)
+ .build();
private boolean mIsPending = true;
private int mNumPendingFilters;
public IncomingCallFilter(Context context, CallFilterResultCallback listener, Call call,
TelecomSystem.SyncRoot lock, Timeouts.Adapter timeoutsAdapter,
- List<CallFilter> filters) {
+ List<CallFilter> filters, Handler handler) {
mContext = context;
mListener = listener;
mCall = call;
@@ -64,6 +74,7 @@
mFilters = filters;
mNumPendingFilters = filters.size();
mTimeoutsAdapter = timeoutsAdapter;
+ mHandler = handler;
}
public void performFiltering() {
diff --git a/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java b/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java
new file mode 100644
index 0000000..1543270
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2019 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;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.telecom.Log;
+import android.telecom.Logging.Runnable;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.LoggedHandlerExecutor;
+import com.android.server.telecom.LogUtils;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+public class IncomingCallFilterGraph {
+ //TODO: Add logging for control flow.
+ public static final String TAG = "IncomingCallFilterGraph";
+ public static final CallFilteringResult DEFAULT_RESULT =
+ new CallFilteringResult.Builder()
+ .setShouldAllowCall(true)
+ .setShouldReject(false)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(true)
+ .build();
+
+ private final CallFilterResultCallback mListener;
+ private final Call mCall;
+ private final Handler mHandler;
+ private final HandlerThread mHandlerThread;
+ private final TelecomSystem.SyncRoot mLock;
+ private List<CallFilter> mFiltersList;
+ private CallFilter mDummyComplete;
+ private boolean mFinished;
+ private CallFilteringResult mCurrentResult;
+ private Context mContext;
+ private Timeouts.Adapter mTimeoutsAdapter;
+
+ private class PostFilterTask {
+ private final CallFilter mFilter;
+
+ public PostFilterTask(final CallFilter filter) {
+ mFilter = filter;
+ }
+
+ public CallFilteringResult whenDone(CallFilteringResult result) {
+ Log.i(TAG, "Filter %s done, result: %s.", mFilter, result);
+ mFilter.result = result;
+ for (CallFilter filter : mFilter.getFollowings()) {
+ if (filter.decrementAndGetIndegree() == 0) {
+ scheduleFilter(filter);
+ }
+ }
+ if (mFilter.equals(mDummyComplete)) {
+ synchronized (mLock) {
+ mFinished = true;
+ mListener.onCallFilteringComplete(mCall, result);
+ Log.addEvent(mCall, LogUtils.Events.FILTERING_COMPLETED, result);
+ }
+ mHandlerThread.quit();
+ }
+ return result;
+ }
+ }
+
+ public IncomingCallFilterGraph(Call call, CallFilterResultCallback listener, Context context,
+ Timeouts.Adapter timeoutsAdapter, TelecomSystem.SyncRoot lock) {
+ mListener = listener;
+ mCall = call;
+ mFiltersList = new ArrayList<>();
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mLock = lock;
+ mFinished = false;
+ mContext = context;
+ mTimeoutsAdapter = timeoutsAdapter;
+ mCurrentResult = DEFAULT_RESULT;
+ }
+
+ public void addFilter(CallFilter filter) {
+ mFiltersList.add(filter);
+ }
+
+ public void performFiltering() {
+ Log.addEvent(mCall, LogUtils.Events.FILTERING_INITIATED);
+ CallFilter dummyStart = new CallFilter();
+ mDummyComplete = new CallFilter();
+
+ for (CallFilter filter : mFiltersList) {
+ addEdge(dummyStart, filter);
+ }
+ for (CallFilter filter : mFiltersList) {
+ addEdge(filter, mDummyComplete);
+ }
+ addEdge(dummyStart, mDummyComplete);
+
+ scheduleFilter(dummyStart);
+ mHandler.postDelayed(new Runnable("ICFG.pF", mLock) {
+ @Override
+ public void loggedRun() {
+ if (!mFinished) {
+ Log.i(this, "Graph timed out when performing filtering.");
+ Log.addEvent(mCall, LogUtils.Events.FILTERING_TIMED_OUT);
+ mListener.onCallFilteringComplete(mCall, mCurrentResult);
+ mFinished = true;
+ mHandlerThread.quit();
+ }
+ for (CallFilter filter : mFiltersList) {
+ // unbind timed out call screening service
+ if (filter instanceof CallScreeningServiceFilter) {
+ ((CallScreeningServiceFilter) filter).unbindCallScreeningService();
+ }
+ }
+ }
+ }.prepare(), mTimeoutsAdapter.getCallScreeningTimeoutMillis(mContext.getContentResolver()));
+ }
+
+ private void scheduleFilter(CallFilter filter) {
+ CallFilteringResult result = new CallFilteringResult.Builder()
+ .setShouldAllowCall(true)
+ .setShouldReject(false)
+ .setShouldSilence(false)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(true)
+ .build();
+ for (CallFilter dependencyFilter : filter.getDependencies()) {
+ result = result.combine(dependencyFilter.getResult());
+ }
+ mCurrentResult = result;
+ final CallFilteringResult input = result;
+
+ CompletableFuture<CallFilteringResult> startFuture =
+ CompletableFuture.completedFuture(input);
+ PostFilterTask postFilterTask = new PostFilterTask(filter);
+
+ // TODO: improve these filter logging names to be more reflective of the filters that are
+ // executing
+ startFuture.thenComposeAsync(filter::startFilterLookup,
+ new LoggedHandlerExecutor(mHandler, "ICFG.sF", null))
+ .thenApplyAsync(postFilterTask::whenDone,
+ new LoggedHandlerExecutor(mHandler, "ICFG.sF", null));
+ Log.i(TAG, "Filter %s scheduled.", filter);
+ }
+
+ public static void addEdge(CallFilter before, CallFilter after) {
+ before.addFollowings(after);
+ after.addDependency(before);
+ }
+
+ public HandlerThread getHandlerThread() {
+ return mHandlerThread;
+ }
+}
diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
index 7a54118..e93ef22 100644
--- a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
+++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
@@ -196,7 +196,7 @@
synchronized (mTelecomLock) {
mRedirectionGatewayInfo = mCallRedirectionProcessorHelper
.getGatewayInfoFromGatewayUri(mComponentName.getPackageName(),
- gatewayUri, mDestinationUri);
+ gatewayUri, mDestinationUri, mPostDialDigits);
mPhoneAccountHandle = targetPhoneAccount;
// If carrier redirects call, we should skip to notify users about
// the user-defined call redirection service.
@@ -247,6 +247,12 @@
private Uri mProcessedDestinationUri;
/**
+ * The post dial digits which were removed from {@link #mDestinationUri} when determining
+ * {@link #mProcessedDestinationUri}.
+ */
+ private String mPostDialDigits;
+
+ /**
* Indicates if Telecom should cancel the call when the whole call redirection finishes.
*/
private boolean mShouldCancelCall = false;
@@ -293,6 +299,7 @@
context, callsManager, phoneAccountRegistrar);
mProcessedDestinationUri = mCallRedirectionProcessorHelper.formatNumberForRedirection(
mDestinationUri);
+ mPostDialDigits = mCallRedirectionProcessorHelper.getPostDialDigits(mDestinationUri);
}
@Override
diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java
index 12c8c57..9771d65 100644
--- a/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java
+++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java
@@ -129,6 +129,18 @@
return removePostDialDigits(formatNumberToE164(handle));
}
+ /**
+ * Extras the post dial digits from a given handle.
+ * @param handle The handle
+ * @return The post dial digits.
+ */
+ public String getPostDialDigits(Uri handle) {
+ if (handle == null) {
+ return "";
+ }
+ return PhoneNumberUtils.extractPostDialPortion(handle.getSchemeSpecificPart());
+ }
+
protected Uri formatNumberToE164(Uri handle) {
String number = handle.getSchemeSpecificPart();
@@ -151,7 +163,7 @@
String number = handle.getSchemeSpecificPart();
// Extract the post dial portion
- number = PhoneNumberUtils.extractNetworkPortionAlt(number);
+ number = PhoneNumberUtils.extractNetworkPortion(number);
Log.i(this, "removePostDialDigits, number after being extracted post dial digits: "
+ Log.pii(number));
// if there is a problem with parsing the phone number, removePostDialDigits will return
@@ -163,10 +175,17 @@
}
}
- protected GatewayInfo getGatewayInfoFromGatewayUri(
- String gatewayPackageName, Uri gatewayUri, Uri destinationUri) {
+ public static GatewayInfo getGatewayInfoFromGatewayUri(
+ String gatewayPackageName, Uri gatewayUri, Uri destinationUri, String postdialDigits) {
if (!TextUtils.isEmpty(gatewayPackageName) && gatewayUri != null) {
- return new GatewayInfo(gatewayPackageName, gatewayUri, destinationUri);
+ Uri gatewayWithPostdial = gatewayUri;
+ if (gatewayUri != null && !TextUtils.isEmpty(postdialDigits)) {
+ gatewayWithPostdial = new Uri.Builder()
+ .scheme(gatewayUri.getScheme())
+ .encodedOpaquePart(gatewayUri.getSchemeSpecificPart() + postdialDigits)
+ .build();
+ }
+ return new GatewayInfo(gatewayPackageName, gatewayWithPostdial, destinationUri);
}
return null;
}
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 956274a..e20da80 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -24,25 +24,25 @@
import android.media.IAudioService;
import android.media.ToneGenerator;
import android.os.IBinder;
-import android.os.Looper;
import android.os.PowerManager;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.telecom.Log;
-import com.android.internal.telephony.CallerInfoAsyncQuery;
+import android.telecom.CallerInfoAsyncQuery;
import com.android.server.telecom.AsyncRingtonePlayer;
import com.android.server.telecom.BluetoothAdapterProxy;
import com.android.server.telecom.BluetoothPhoneServiceImpl;
+import com.android.server.telecom.CallAudioModeStateMachine;
import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.CallerInfoAsyncQueryFactory;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ClockProxy;
import com.android.server.telecom.ConnectionServiceFocusManager;
+import com.android.server.telecom.ContactsAsyncHelper;
import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.HeadsetMediaButton;
import com.android.server.telecom.HeadsetMediaButtonFactory;
-import com.android.server.telecom.InCallTonePlayer;
import com.android.server.telecom.InCallWakeLockControllerFactory;
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.PhoneAccountRegistrar;
@@ -51,11 +51,11 @@
import com.android.server.telecom.InCallWakeLockController;
import com.android.server.telecom.ProximitySensorManager;
import com.android.server.telecom.R;
-import com.android.server.telecom.RoleManagerAdapter;
import com.android.server.telecom.RoleManagerAdapterImpl;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.TelecomWakeLock;
import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.callfiltering.IncomingCallFilter;
import com.android.server.telecom.ui.IncomingCallNotifier;
import com.android.server.telecom.ui.MissedCallNotifierImpl;
import com.android.server.telecom.ui.NotificationChannelManager;
@@ -90,8 +90,6 @@
new NotificationChannelManager();
notificationChannelManager.createChannels(context);
- boolean shouldPauseBetweenRingtoneRepeat = context.getResources().getBoolean(
- R.bool.should_pause_between_ringtone_repeats);
TelecomSystem.setInstance(
new TelecomSystem(
context,
@@ -173,11 +171,12 @@
},
ConnectionServiceFocusManager::new,
new Timeouts.Adapter(),
- new AsyncRingtonePlayer(shouldPauseBetweenRingtoneRepeat),
+ new AsyncRingtonePlayer(),
new PhoneNumberUtilsAdapterImpl(),
new IncomingCallNotifier(context),
ToneGenerator::new,
new CallAudioRouteStateMachine.Factory(),
+ new CallAudioModeStateMachine.Factory(),
new ClockProxy() {
@Override
public long currentTimeMillis() {
@@ -190,7 +189,9 @@
}
},
new RoleManagerAdapterImpl(context,
- (RoleManager) context.getSystemService(Context.ROLE_SERVICE))));
+ (RoleManager) context.getSystemService(Context.ROLE_SERVICE)),
+ new IncomingCallFilter.Factory(),
+ new ContactsAsyncHelper.Factory()));
}
if (BluetoothAdapter.getDefaultAdapter() != null) {
context.startService(new Intent(context, BluetoothPhoneService.class));
diff --git a/src/com/android/server/telecom/components/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
index ae4a7d8..75c1996 100644
--- a/src/com/android/server/telecom/components/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -22,13 +22,12 @@
import android.net.Uri;
import android.os.UserHandle;
import android.os.UserManager;
-import android.telecom.DefaultDialerManager;
import android.telecom.Log;
import android.telecom.PhoneAccount;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
+import android.telephony.TelephonyManager;
import com.android.server.telecom.CallIntentProcessor;
import com.android.server.telecom.R;
@@ -153,39 +152,20 @@
VideoProfile.STATE_AUDIO_ONLY);
Log.d(this, "processOutgoingCallIntent videoState = " + videoState);
- intent.putExtra(CallIntentProcessor.KEY_IS_PRIVILEGED_DIALER,
- isDefaultOrSystemDialer(callingPackageName));
-
// Save the user handle of current user before forwarding the intent to primary user.
intent.putExtra(CallIntentProcessor.KEY_INITIATING_USER, mUserHandle);
sendIntentToDestination(intent, isLocalInvocation, callingPackageName);
}
- private boolean isDefaultOrSystemDialer(String callingPackageName) {
- if (TextUtils.isEmpty(callingPackageName)) {
- return false;
- }
-
- final String defaultDialer = DefaultDialerManager.getDefaultDialerApplication(mContext,
- mUserHandle.getIdentifier());
- if (TextUtils.equals(defaultDialer, callingPackageName)) {
- return true;
- }
-
- final TelecomManager telecomManager =
- (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
- return TextUtils.equals(telecomManager.getSystemDialerPackage(), callingPackageName);
- }
-
/**
* Returns whether the device is voice-capable (e.g. a phone vs a tablet).
*
* @return {@code True} if the device is voice-capable.
*/
private boolean isVoiceCapable() {
- return mContext.getApplicationContext().getResources().getBoolean(
- com.android.internal.R.bool.config_voice_capable);
+ return ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
+ .isVoiceCapable();
}
/**
@@ -211,7 +191,7 @@
// process; we need to trampoline to TelecomSystem in the system server process.
Log.i(this, "sendIntentToDestination: trampoline to Telecom.");
TelecomManager tm = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
- tm.handleCallIntent(intent);
+ tm.handleCallIntent(intent, callingPackage);
}
return true;
}
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
index 1593e23..3549db5 100644
--- a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
+++ b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.app.ActionBar;
import android.app.AlertDialog;
+import android.app.Dialog;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
@@ -34,9 +35,11 @@
import android.database.Cursor;
import android.os.Bundle;
import android.provider.BlockedNumberContract;
+import android.provider.ContactsContract;
import android.telephony.PhoneNumberFormattingTextWatcher;
import android.telephony.PhoneNumberUtils;
import android.text.Editable;
+import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.LayoutInflater;
@@ -54,6 +57,7 @@
import com.android.server.telecom.R;
+
/**
* Activity to manage blocked numbers using {@link BlockedNumberContract}.
*/
@@ -79,6 +83,7 @@
private ProgressBar mProgressBar;
private RelativeLayout mButterBar;
@Nullable private Button mBlockButton;
+ @Nullable private Button mBlockButtonNegative;
private TextView mReEnableButton;
private BroadcastReceiver mBlockingStatusReceiver;
@@ -260,6 +265,10 @@
public void onShow(DialogInterface dialog) {
mBlockButton = ((AlertDialog) dialog)
.getButton(AlertDialog.BUTTON_POSITIVE);
+ mBlockButtonNegative = ((AlertDialog) dialog)
+ .getButton(AlertDialog.BUTTON_NEGATIVE);
+ mBlockButton.setAllCaps(false);
+ mBlockButtonNegative.setAllCaps(false);
mBlockButton.setEnabled(false);
// show keyboard
InputMethodManager inputMethodManager =
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersAdapter.java b/src/com/android/server/telecom/settings/BlockedNumbersAdapter.java
index 1278a4b..df68f6e 100644
--- a/src/com/android/server/telecom/settings/BlockedNumbersAdapter.java
+++ b/src/com/android/server/telecom/settings/BlockedNumbersAdapter.java
@@ -28,6 +28,7 @@
import android.view.View;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
+
import com.android.server.telecom.R;
public class BlockedNumbersAdapter extends SimpleCursorAdapter {
@@ -42,10 +43,18 @@
final String rawNumber = cursor.getString(cursor.getColumnIndex(
BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER));
final String formattedNumber = BlockedNumbersUtil.formatNumber(rawNumber);
- TextView numberView = (TextView) view.findViewById(R.id.blocked_number);
- Spannable numberSpannable = new SpannableString(formattedNumber);
- PhoneNumberUtils.addTtsSpan(numberSpannable, 0, numberSpannable.length());
- numberView.setText(numberSpannable);
+ TextView textView = (TextView) view.findViewById(R.id.blocked_number);
+
+ if (formattedNumber != null
+ && formattedNumber.contains("@") || formattedNumber.contains("%40")) {
+ // An email address
+ textView.setText(formattedNumber);
+ } else {
+ // A phone number
+ Spannable numberSpannable = new SpannableString(formattedNumber);
+ PhoneNumberUtils.addTtsSpan(numberSpannable, 0, numberSpannable.length());
+ textView.setText(numberSpannable);
+ }
View deleteButton = view.findViewById(R.id.delete_blocked_number);
deleteButton.setOnClickListener(new View.OnClickListener() {
@@ -63,7 +72,7 @@
Spannable messageSpannable = new SpannableString(message);
PhoneNumberUtils.addTtsSpan(messageSpannable, startingPosition,
startingPosition + formattedNumber.length());
- new AlertDialog.Builder(context)
+ AlertDialog dialog = new AlertDialog.Builder(context)
.setMessage(messageSpannable)
.setPositiveButton(R.string.unblock_button,
new DialogInterface.OnClickListener() {
@@ -79,8 +88,15 @@
}
}
)
- .create()
- .show();
+ .create();
+ dialog.setOnShowListener(new AlertDialog.OnShowListener() {
+ @Override
+ public void onShow(DialogInterface dialog) {
+ ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE).setAllCaps(false);
+ ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_NEGATIVE).setAllCaps(false);
+ }
+ });
+ dialog.show();
}
private void deleteBlockedNumber(Context context, String number) {
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersUtil.java b/src/com/android/server/telecom/settings/BlockedNumbersUtil.java
index 5acfe64..67634e4 100644
--- a/src/com/android/server/telecom/settings/BlockedNumbersUtil.java
+++ b/src/com/android/server/telecom/settings/BlockedNumbersUtil.java
@@ -108,7 +108,7 @@
.setContentText(message)
.setContentIntent(pendingIntent)
.setShowWhen(true)
- .setChannel(NotificationChannelManager.CHANNEL_ID_CALL_BLOCKING)
+ .setChannelId(NotificationChannelManager.CHANNEL_ID_CALL_BLOCKING)
.build();
notification.flags |= Notification.FLAG_NO_CLEAR;
diff --git a/src/com/android/server/telecom/settings/EnableAccountPreferenceFragment.java b/src/com/android/server/telecom/settings/EnableAccountPreferenceFragment.java
index 83eb113..c2a0500 100644
--- a/src/com/android/server/telecom/settings/EnableAccountPreferenceFragment.java
+++ b/src/com/android/server/telecom/settings/EnableAccountPreferenceFragment.java
@@ -19,9 +19,11 @@
import android.content.Context;
import android.graphics.drawable.Icon;
import android.os.Bundle;
+import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
+import android.telecom.Log;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
@@ -52,14 +54,16 @@
setIcon(icon.loadDrawable(context));
}
setChecked(account.isEnabled());
+ setOnPreferenceChangeListener(this::onPreferenceChange);
}
- /** ${inheritDoc} */
- @Override
- protected void onClick() {
- super.onClick();
+ private boolean onPreferenceChange(Preference preference, Object newValue) {
+ Log.d(this, "onPreferenceChange: key = %s", preference.getKey());
+ Log.d(this, " preference = '%s'", preference);
+ Log.d(this, " newValue = '%b'", newValue);
- mTelecomManager.enablePhoneAccount(mAccount.getAccountHandle(), isChecked());
+ mTelecomManager.enablePhoneAccount(mAccount.getAccountHandle(), (boolean) newValue);
+ return true;
}
}
diff --git a/src/com/android/server/telecom/ui/AudioProcessingNotification.java b/src/com/android/server/telecom/ui/AudioProcessingNotification.java
new file mode 100644
index 0000000..7a61460
--- /dev/null
+++ b/src/com/android/server/telecom/ui/AudioProcessingNotification.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2019, 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.ui;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.telecom.Log;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManagerListenerBase;
+import com.android.server.telecom.R;
+
+/**
+ * Displays a persistent notification whenever there's a call in the AUDIO_PROCESSING state so that
+ * the user is aware that there's some app
+ */
+public class AudioProcessingNotification extends CallsManagerListenerBase {
+
+ private static final int AUDIO_PROCESSING_NOTIFICATION_ID = 2;
+ private static final String NOTIFICATION_TAG =
+ AudioProcessingNotification.class.getSimpleName();
+
+ private final Context mContext;
+ private final NotificationManager mNotificationManager;
+ private Call mCallInAudioProcessing;
+
+ public AudioProcessingNotification(Context context) {
+ mContext = context;
+ mNotificationManager =
+ (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ @Override
+ public void onCallStateChanged(Call call, int oldState, int newState) {
+ if (newState == CallState.AUDIO_PROCESSING && oldState != CallState.AUDIO_PROCESSING) {
+ showAudioProcessingNotification(call);
+ } else if (oldState == CallState.AUDIO_PROCESSING
+ && newState != CallState.AUDIO_PROCESSING) {
+ cancelAudioProcessingNotification();
+ }
+ }
+
+ @Override
+ public void onCallAdded(Call call) {
+ if (call.getState() == CallState.AUDIO_PROCESSING) {
+ showAudioProcessingNotification(call);
+ }
+ }
+
+ @Override
+ public void onCallRemoved(Call call) {
+ if (call == mCallInAudioProcessing) {
+ cancelAudioProcessingNotification();
+ }
+ }
+
+ /**
+ * Create a system notification for the audio processing call.
+ *
+ * @param call The missed call.
+ */
+ private void showAudioProcessingNotification(Call call) {
+ Log.i(this, "showAudioProcessingNotification");
+ mCallInAudioProcessing = call;
+
+ Notification.Builder builder = new Notification.Builder(mContext,
+ NotificationChannelManager.CHANNEL_ID_AUDIO_PROCESSING);
+ builder.setSmallIcon(R.drawable.ic_phone)
+ .setColor(mContext.getResources().getColor(R.color.theme_color))
+ .setContentTitle(mContext.getText(R.string.notification_audioProcessing_title))
+ .setStyle(new Notification.BigTextStyle()
+ .bigText(mContext.getString(
+ R.string.notification_audioProcessing_body,
+ call.getAudioProcessingRequestingApp())))
+ .setOngoing(true);
+
+ Notification notification = builder.build();
+
+ mNotificationManager.notify(
+ NOTIFICATION_TAG, AUDIO_PROCESSING_NOTIFICATION_ID, notification);
+ }
+
+ /** Cancels the audio processing notification. */
+ private void cancelAudioProcessingNotification() {
+ mNotificationManager.cancel(NOTIFICATION_TAG, AUDIO_PROCESSING_NOTIFICATION_ID);
+ }
+}
diff --git a/src/com/android/server/telecom/ui/CallRedirectionConfirmDialogActivity.java b/src/com/android/server/telecom/ui/CallRedirectionConfirmDialogActivity.java
deleted file mode 100644
index 0d3599e..0000000
--- a/src/com/android/server/telecom/ui/CallRedirectionConfirmDialogActivity.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2019 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.ui;
-
-import com.android.server.telecom.R;
-import com.android.server.telecom.TelecomBroadcastIntentProcessor;
-import com.android.server.telecom.components.TelecomBroadcastReceiver;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.os.Bundle;
-import android.telecom.Log;
-import android.view.LayoutInflater;
-import android.view.View.OnClickListener;
-import android.view.View;
-import android.widget.Button;
-
-/**
- * Dialog activity used when there is an ongoing call redirected by the call redirection service.
- * The dialog prompts the user to see if they want to place the redirected outgoing call.
- */
-public class CallRedirectionConfirmDialogActivity extends Activity {
- public static final String EXTRA_REDIRECTION_OUTGOING_CALL_ID =
- "android.telecom.extra.REDIRECTION_OUTGOING_CALL_ID";
- public static final String EXTRA_REDIRECTION_APP_NAME =
- "android.telecom.extra.REDIRECTION_APP_NAME";
-
- private String mCallId;
- private AlertDialog mConfirmDialog;
- /**
- * Tracks whether the activity has stopped due to a loss of focus (e.g. use hitting the home
- * button) or whether its going to stop because a button in the dialog was pressed.
- */
- private boolean mHasLostFocus = true;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Log.i(this, "CallRedirectionConfirmDialogActivity onCreate.");
- final CharSequence redirectionAppName = getIntent().getStringExtra(
- EXTRA_REDIRECTION_APP_NAME);
- mCallId = getIntent().getStringExtra(EXTRA_REDIRECTION_OUTGOING_CALL_ID);
- showDialog(redirectionAppName);
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- if (mHasLostFocus) {
- Log.i(this, "onStop: dialog lost focus; canceling redirection for call %s", mCallId);
- mConfirmDialog.dismiss();
- cancelRedirection();
- }
- }
-
- private void showDialog(final CharSequence redirectionAppName) {
- Log.i(this, "showDialog: confirming redirection with %s", redirectionAppName);
-
- mConfirmDialog = new AlertDialog.Builder(this).create();
- LayoutInflater layoutInflater = LayoutInflater.from(this);
- View dialogView = layoutInflater.inflate(R.layout.call_redirection_confirm_dialog, null);
-
- Button buttonFirstLine = (Button) dialogView.findViewById(R.id.buttonFirstLine);
- buttonFirstLine.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent proceedWithoutRedirectedCall = new Intent(
- TelecomBroadcastIntentProcessor.ACTION_PLACE_UNREDIRECTED_CALL,
- null, CallRedirectionConfirmDialogActivity.this,
- TelecomBroadcastReceiver.class);
- proceedWithoutRedirectedCall.putExtra(EXTRA_REDIRECTION_OUTGOING_CALL_ID, mCallId);
- sendBroadcast(proceedWithoutRedirectedCall);
- mConfirmDialog.dismiss();
- mHasLostFocus = false;
- finish();
- }
- });
-
- Button buttonSecondLine = (Button) dialogView.findViewById(R.id.buttonSecondLine);
- buttonSecondLine.setText(getString(R.string.alert_place_outgoing_call_with_redirection,
- redirectionAppName));
- buttonSecondLine.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent proceedWithRedirectedCall = new Intent(
- TelecomBroadcastIntentProcessor
- .ACTION_PLACE_REDIRECTED_CALL, null,
- CallRedirectionConfirmDialogActivity.this,
- TelecomBroadcastReceiver.class);
- proceedWithRedirectedCall.putExtra(EXTRA_REDIRECTION_OUTGOING_CALL_ID, mCallId);
- sendBroadcast(proceedWithRedirectedCall);
- mConfirmDialog.dismiss();
- mHasLostFocus = false;
- finish();
- }
- });
-
- Button buttonThirdLine = (Button) dialogView.findViewById(R.id.buttonThirdLine);
- buttonThirdLine.setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- Intent cancelRedirectedCall = new Intent(
- TelecomBroadcastIntentProcessor.ACTION_CANCEL_REDIRECTED_CALL,
- null, CallRedirectionConfirmDialogActivity.this,
- TelecomBroadcastReceiver.class);
- cancelRedirectedCall.putExtra(EXTRA_REDIRECTION_OUTGOING_CALL_ID, mCallId);
- sendBroadcast(cancelRedirectedCall);
- mConfirmDialog.dismiss();
- mHasLostFocus = false;
- finish();
- }
- });
-
- mConfirmDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- cancelRedirection();
- dialog.dismiss();
- mHasLostFocus = false;
- finish();
- }
- });
-
- mConfirmDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
-
- mConfirmDialog.setCancelable(false);
- mConfirmDialog.setCanceledOnTouchOutside(false);
- mConfirmDialog.setView(dialogView);
-
- mConfirmDialog.show();
- }
-
- /**
- * Signals to Telecom that redirection of the call is to be cancelled.
- */
- private void cancelRedirection() {
- Intent cancelRedirectedCall = new Intent(
- TelecomBroadcastIntentProcessor.ACTION_CANCEL_REDIRECTED_CALL,
- null, CallRedirectionConfirmDialogActivity.this,
- TelecomBroadcastReceiver.class);
- cancelRedirectedCall.putExtra(EXTRA_REDIRECTION_OUTGOING_CALL_ID, mCallId);
- sendBroadcast(cancelRedirectedCall);
- }
-}
diff --git a/src/com/android/server/telecom/ui/DisconnectedCallNotifier.java b/src/com/android/server/telecom/ui/DisconnectedCallNotifier.java
new file mode 100644
index 0000000..3f54689
--- /dev/null
+++ b/src/com/android/server/telecom/ui/DisconnectedCallNotifier.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2019 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.ui;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.TaskStackBuilder;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.provider.CallLog;
+import android.telecom.DisconnectCause;
+import android.telecom.Log;
+import android.telecom.PhoneAccount;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.text.BidiFormatter;
+import android.text.TextDirectionHeuristics;
+import android.text.TextUtils;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.CallsManagerListenerBase;
+import com.android.server.telecom.Constants;
+import com.android.server.telecom.R;
+import com.android.server.telecom.TelecomBroadcastIntentProcessor;
+import com.android.server.telecom.components.TelecomBroadcastReceiver;
+
+import java.util.Locale;
+
+/**
+ * Handles notifications generated by Telecom for the case that a call was disconnected in order to
+ * connect another "higher priority" emergency call and gives the user the choice to call or
+ * message that user back after, similar to the missed call notifier.
+ */
+public class DisconnectedCallNotifier extends CallsManagerListenerBase {
+
+ public interface Factory {
+ DisconnectedCallNotifier create(Context context, CallsManager manager);
+ }
+
+ public static class Default implements Factory {
+
+ @Override
+ public DisconnectedCallNotifier create(Context context, CallsManager manager) {
+ return new DisconnectedCallNotifier(context, manager);
+ }
+ }
+
+ private static class CallInfo {
+ public final UserHandle userHandle;
+ public final Uri handle;
+ public final long endTimeMs;
+ public final Bitmap callerInfoIcon;
+ public final Drawable callerInfoPhoto;
+ public final String callerInfoName;
+ public final boolean isEmergency;
+
+ public CallInfo(UserHandle userHandle, Uri handle, long endTimeMs, Bitmap callerInfoIcon,
+ Drawable callerInfoPhoto, String callerInfoName, boolean isEmergency) {
+ this.userHandle = userHandle;
+ this.handle = handle;
+ this.endTimeMs = endTimeMs;
+ this.callerInfoIcon = callerInfoIcon;
+ this.callerInfoPhoto = callerInfoPhoto;
+ this.callerInfoName = callerInfoName;
+ this.isEmergency = isEmergency;
+ }
+
+ @Override
+ public String toString() {
+ return "CallInfo{" +
+ "userHandle=" + userHandle +
+ ", handle=" + handle +
+ ", isEmergency=" + isEmergency +
+ ", endTimeMs=" + endTimeMs +
+ ", callerInfoIcon=" + callerInfoIcon +
+ ", callerInfoPhoto=" + callerInfoPhoto +
+ ", callerInfoName='" + callerInfoName + '\'' +
+ '}';
+ }
+ }
+
+ private static final String NOTIFICATION_TAG =
+ DisconnectedCallNotifier.class.getSimpleName();
+ private static final int DISCONNECTED_CALL_NOTIFICATION_ID = 1;
+
+ private final Context mContext;
+ private final CallsManager mCallsManager;
+ private final NotificationManager mNotificationManager;
+ // The pending info to display to the user after they have ended the emergency call.
+ private CallInfo mPendingCallNotification;
+
+ public DisconnectedCallNotifier(Context context, CallsManager callsManager) {
+ mContext = context;
+ mNotificationManager =
+ (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ mCallsManager = callsManager;
+ }
+
+ @Override
+ public void onCallRemoved(Call call) {
+ // Wait until the emergency call is ended before showing the notification.
+ if (mCallsManager.getCalls().isEmpty() && mPendingCallNotification != null) {
+ showDisconnectedNotification(mPendingCallNotification);
+ mPendingCallNotification = null;
+ }
+ }
+
+ @Override
+ public void onCallStateChanged(Call call, int oldState, int newState) {
+ DisconnectCause cause = call.getDisconnectCause();
+ if (cause == null) {
+ Log.w(this, "onCallStateChanged: unexpected null disconnect cause.");
+ return;
+ }
+ // Call disconnected in favor of an emergency call. Place the call into a pending queue.
+ if ((newState == CallState.DISCONNECTED) && (cause.getCode() == DisconnectCause.LOCAL) &&
+ DisconnectCause.REASON_EMERGENCY_CALL_PLACED.equals(cause.getReason())) {
+ // Clear any existing notification.
+ clearNotification(mCallsManager.getCurrentUserHandle());
+ UserHandle userHandle = call.getTargetPhoneAccount() != null ?
+ call.getTargetPhoneAccount().getUserHandle() : call.getInitiatingUser();
+ // As a last resort, use the current user to display the notification.
+ if (userHandle == null) userHandle = mCallsManager.getCurrentUserHandle();
+ mPendingCallNotification = new CallInfo(userHandle, call.getHandle(),
+ call.getCreationTimeMillis() + call.getAgeMillis(), call.getPhotoIcon(),
+ call.getPhoto(), call.getName(), call.isEmergencyCall());
+ }
+ }
+
+ private void showDisconnectedNotification(@NonNull CallInfo call) {
+ Log.i(this, "showDisconnectedNotification: userHandle=%d", call.userHandle.getIdentifier());
+
+ final int titleResId = R.string.notification_disconnectedCall_title;
+ final CharSequence expandedText = call.isEmergency
+ ? mContext.getText(R.string.notification_disconnectedCall_generic_body)
+ : mContext.getString(R.string.notification_disconnectedCall_body,
+ getNameForCallNotification(call));
+
+ // Create a public viewable version of the notification, suitable for display when sensitive
+ // notification content is hidden.
+ // We use user's context here to make sure notification is badged if it is a managed user.
+ Context contextForUser = getContextForUser(call.userHandle);
+ Notification.Builder publicBuilder = new Notification.Builder(contextForUser,
+ NotificationChannelManager.CHANNEL_ID_DISCONNECTED_CALLS);
+ publicBuilder.setSmallIcon(android.R.drawable.stat_notify_error)
+ .setColor(mContext.getResources().getColor(R.color.theme_color, null /*theme*/))
+ // Set when the call was disconnected.
+ .setWhen(call.endTimeMs)
+ .setShowWhen(true)
+ // Show "Phone" for notification title.
+ .setContentTitle(mContext.getText(R.string.userCallActivityLabel))
+ // Notification details shows that there are disconnected call(s), but does not
+ // reveal the caller information.
+ .setContentText(mContext.getText(titleResId))
+ .setAutoCancel(true);
+
+ if (!call.isEmergency) {
+ publicBuilder.setContentIntent(createCallLogPendingIntent(call.userHandle));
+ }
+
+ // Create the notification suitable for display when sensitive information is showing.
+ Notification.Builder builder = new Notification.Builder(contextForUser,
+ NotificationChannelManager.CHANNEL_ID_DISCONNECTED_CALLS);
+ builder.setSmallIcon(android.R.drawable.stat_notify_error)
+ .setColor(mContext.getResources().getColor(R.color.theme_color, null /*theme*/))
+ .setWhen(call.endTimeMs)
+ .setShowWhen(true)
+ .setContentTitle(mContext.getText(titleResId))
+ //Only show expanded text for sensitive information
+ .setStyle(new Notification.BigTextStyle().bigText(expandedText))
+ .setAutoCancel(true)
+ // Include a public version of the notification to be shown when the call
+ // notification is shown on the user's lock screen and they have chosen to hide
+ // sensitive notification information.
+ .setPublicVersion(publicBuilder.build())
+ .setChannelId(NotificationChannelManager.CHANNEL_ID_DISCONNECTED_CALLS);
+
+ if (!call.isEmergency) {
+ builder.setContentIntent(createCallLogPendingIntent(call.userHandle));
+ }
+
+ String handle = call.handle != null ? call.handle.getSchemeSpecificPart() : null;
+
+ if (!TextUtils.isEmpty(handle)
+ && !TextUtils.equals(handle, mContext.getString(R.string.handle_restricted))
+ && !call.isEmergency) {
+ builder.addAction(new Notification.Action.Builder(
+ Icon.createWithResource(contextForUser, R.drawable.ic_phone_24dp),
+ // Reuse missed call "Call back"
+ mContext.getString(R.string.notification_missedCall_call_back),
+ createCallBackPendingIntent(call.handle, call.userHandle)).build());
+
+ if (canRespondViaSms(call)) {
+ builder.addAction(new Notification.Action.Builder(
+ Icon.createWithResource(contextForUser, R.drawable.ic_message_24dp),
+ // Reuse missed call "Call back"
+ mContext.getString(R.string.notification_missedCall_message),
+ createSendSmsFromNotificationPendingIntent(call.handle,
+ call.userHandle)).build());
+ }
+ }
+
+ if (call.callerInfoIcon != null) {
+ builder.setLargeIcon(call.callerInfoIcon);
+ } else {
+ if (call.callerInfoPhoto instanceof BitmapDrawable) {
+ builder.setLargeIcon(((BitmapDrawable) call.callerInfoPhoto).getBitmap());
+ }
+ }
+
+ Notification notification = builder.build();
+
+ Log.i(this, "Adding missed call notification for %s.", Log.pii(call.handle));
+ long token = Binder.clearCallingIdentity();
+ try {
+ // TODO: Only support one notification right now, so if multiple are hung up, we only
+ // show the last one. Support multiple in the future.
+ mNotificationManager.notifyAsUser(NOTIFICATION_TAG, DISCONNECTED_CALL_NOTIFICATION_ID,
+ notification, call.userHandle);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Returns the name to use in the call notification.
+ */
+ private String getNameForCallNotification(@NonNull CallInfo call) {
+ String number = call.handle != null ? call.handle.getSchemeSpecificPart() : null;
+
+ if (!TextUtils.isEmpty(number)) {
+ String formattedNumber = PhoneNumberUtils.formatNumber(number,
+ getCurrentCountryIso(mContext));
+
+ // The formatted number will be null if there was a problem formatting it, but we can
+ // default to using the unformatted number instead (e.g. a SIP URI may not be able to
+ // be formatted.
+ if (!TextUtils.isEmpty(formattedNumber)) {
+ number = formattedNumber;
+ }
+ }
+
+ if (!TextUtils.isEmpty(call.callerInfoName) && TextUtils.isGraphic(call.callerInfoName)) {
+ return call.callerInfoName;
+ }
+ if (!TextUtils.isEmpty(number)) {
+ // A handle should always be displayed LTR using {@link BidiFormatter} regardless of the
+ // content of the rest of the notification.
+ // TODO: Does this apply to SIP addresses?
+ BidiFormatter bidiFormatter = BidiFormatter.getInstance();
+ return bidiFormatter.unicodeWrap(number, TextDirectionHeuristics.LTR);
+ } else {
+ // Use "unknown" if the call is unidentifiable.
+ return mContext.getString(R.string.unknown);
+ }
+ }
+
+ /**
+ * @return The ISO 3166-1 two letters country code of the country the user is in based on the
+ * network location. If the network location does not exist, fall back to the locale
+ * setting.
+ */
+ private String getCurrentCountryIso(Context context) {
+ // Without framework function calls, this seems to be the most accurate location service
+ // we can rely on.
+ final TelephonyManager telephonyManager =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ String countryIso = telephonyManager.getNetworkCountryIso().toUpperCase();
+
+ if (countryIso == null) {
+ countryIso = Locale.getDefault().getCountry();
+ Log.w(this, "No CountryDetector; falling back to countryIso based on locale: "
+ + countryIso);
+ }
+ return countryIso;
+ }
+
+ private Context getContextForUser(UserHandle user) {
+ try {
+ return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Default to mContext, not finding the package system is running as is unlikely.
+ return mContext;
+ }
+ }
+
+ /**
+ * Creates an intent to be invoked when the user opts to "call back" from the disconnected call
+ * notification.
+ *
+ * @param handle The handle to call back.
+ */
+ private PendingIntent createCallBackPendingIntent(Uri handle, UserHandle userHandle) {
+ return createTelecomPendingIntent(
+ TelecomBroadcastIntentProcessor.ACTION_DISCONNECTED_CALL_BACK_FROM_NOTIFICATION,
+ handle, userHandle);
+ }
+
+ /**
+ * Creates generic pending intent from the specified parameters to be received by
+ * {@link TelecomBroadcastIntentProcessor}.
+ *
+ * @param action The intent action.
+ * @param data The intent data.
+ */
+ private PendingIntent createTelecomPendingIntent(String action, Uri data,
+ UserHandle userHandle) {
+ Intent intent = new Intent(action, data, mContext, TelecomBroadcastReceiver.class);
+ intent.putExtra(TelecomBroadcastIntentProcessor.EXTRA_USERHANDLE, userHandle);
+ return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ private boolean canRespondViaSms(@NonNull CallInfo call) {
+ // Only allow respond-via-sms for "tel:" calls.
+ return call.handle != null &&
+ PhoneAccount.SCHEME_TEL.equals(call.handle.getScheme());
+ }
+
+ /**
+ * Creates a new pending intent that sends the user to the call log.
+ *
+ * @return The pending intent.
+ */
+ private PendingIntent createCallLogPendingIntent(UserHandle userHandle) {
+ Intent intent = new Intent(Intent.ACTION_VIEW, null);
+ intent.setType(CallLog.Calls.CONTENT_TYPE);
+
+ TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(mContext);
+ taskStackBuilder.addNextIntent(intent);
+
+ return taskStackBuilder.getPendingIntent(0, 0, null, userHandle);
+ }
+
+ /**
+ * Creates an intent to be invoked when the user opts to "send sms" from the missed call
+ * notification.
+ */
+ private PendingIntent createSendSmsFromNotificationPendingIntent(Uri handle,
+ UserHandle userHandle) {
+ return createTelecomPendingIntent(
+ TelecomBroadcastIntentProcessor.ACTION_DISCONNECTED_SEND_SMS_FROM_NOTIFICATION,
+ Uri.fromParts(Constants.SCHEME_SMSTO, handle.getSchemeSpecificPart(), null),
+ userHandle);
+ }
+
+ /**
+ * Clear any of the active notifications.
+ * @param userHandle The user to clear the notifications for.
+ */
+ public void clearNotification(UserHandle userHandle) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mNotificationManager.cancelAsUser(NOTIFICATION_TAG, DISCONNECTED_CALL_NOTIFICATION_ID,
+ userHandle);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+}
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index 7fee263..a0eca8f 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -62,7 +62,7 @@
import android.text.TextDirectionHeuristics;
import android.text.TextUtils;
-import com.android.internal.telephony.CallerInfo;
+import android.telecom.CallerInfo;
import java.lang.Override;
import java.lang.String;
@@ -472,7 +472,7 @@
TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(mContext);
taskStackBuilder.addNextIntent(intent);
- return taskStackBuilder.getPendingIntent(0, 0, null, userHandle);
+ return taskStackBuilder.getPendingIntent(0, PendingIntent.FLAG_IMMUTABLE, null, userHandle);
}
/**
@@ -601,7 +601,7 @@
return;
}
if (info == null ||
- info.contactDisplayPhotoUri == null) {
+ info.getContactDisplayPhotoUri() == null) {
// If there is no photo or if the caller info is
// null, just show the notification.
CallInfo callInfo = callInfoFactory.makeCallInfo(
diff --git a/src/com/android/server/telecom/ui/NotificationChannelManager.java b/src/com/android/server/telecom/ui/NotificationChannelManager.java
index 70d4d0d..58794a6 100644
--- a/src/com/android/server/telecom/ui/NotificationChannelManager.java
+++ b/src/com/android/server/telecom/ui/NotificationChannelManager.java
@@ -37,6 +37,9 @@
public static final String CHANNEL_ID_MISSED_CALLS = "TelecomMissedCalls";
public static final String CHANNEL_ID_INCOMING_CALLS = "TelecomIncomingCalls";
public static final String CHANNEL_ID_CALL_BLOCKING = "TelecomCallBlocking";
+ public static final String CHANNEL_ID_AUDIO_PROCESSING = "TelecomBackgroundAudioProcessing";
+ public static final String CHANNEL_ID_DISCONNECTED_CALLS = "TelecomDisconnectedCalls";
+ public static final String CHANNEL_ID_IN_CALL_SERVICE_CRASH = "TelecomInCallServiceCrash";
private BroadcastReceiver mLocaleChangeReceiver = new BroadcastReceiver() {
@Override
@@ -57,6 +60,9 @@
createOrUpdateChannel(context, CHANNEL_ID_MISSED_CALLS);
createOrUpdateChannel(context, CHANNEL_ID_INCOMING_CALLS);
createOrUpdateChannel(context, CHANNEL_ID_CALL_BLOCKING);
+ createOrUpdateChannel(context, CHANNEL_ID_AUDIO_PROCESSING);
+ createOrUpdateChannel(context, CHANNEL_ID_DISCONNECTED_CALLS);
+ createOrUpdateChannel(context, CHANNEL_ID_IN_CALL_SERVICE_CRASH);
}
private void createOrUpdateChannel(Context context, String channelId) {
@@ -98,6 +104,29 @@
vibration = false;
sound = null;
break;
+ case CHANNEL_ID_AUDIO_PROCESSING:
+ name = context.getText(R.string.notification_channel_background_calls);
+ importance = NotificationManager.IMPORTANCE_LOW;
+ canShowBadge = false;
+ lights = false;
+ vibration = false;
+ sound = null;
+ break;
+ case CHANNEL_ID_DISCONNECTED_CALLS:
+ name = context.getText(R.string.notification_channel_disconnected_calls);
+ importance = NotificationManager.IMPORTANCE_DEFAULT;
+ canShowBadge = true;
+ lights = true;
+ vibration = true;
+ sound = silentRingtone;
+ break;
+ case CHANNEL_ID_IN_CALL_SERVICE_CRASH:
+ name = context.getText(R.string.notification_channel_in_call_service_crash);
+ importance = NotificationManager.IMPORTANCE_DEFAULT;
+ canShowBadge = true;
+ lights = true;
+ vibration = true;
+ sound = null;
}
NotificationChannel channel = new NotificationChannel(channelId, name, importance);
diff --git a/src/com/android/server/telecom/ui/ToastFactory.java b/src/com/android/server/telecom/ui/ToastFactory.java
new file mode 100644
index 0000000..75b69da
--- /dev/null
+++ b/src/com/android/server/telecom/ui/ToastFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 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.ui;
+
+import android.annotation.StringRes;
+import android.content.Context;
+import android.widget.Toast;
+
+public interface ToastFactory {
+ Toast makeText(Context context, @StringRes int resId, int duration);
+ Toast makeText(Context context, CharSequence text, int duration);
+}
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index e339356..4238191 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -20,7 +20,7 @@
<uses-sdk
android:minSdkVersion="28"
- android:targetSdkVersion="28" />
+ android:targetSdkVersion="30" />
<uses-permission android:name="android.permission.ACCEPT_HANDOVER" />
<uses-permission android:name="android.permission.BLUETOOTH" />
@@ -273,5 +273,13 @@
android:excludeFromRecents="true"
android:launchMode="singleInstance">
</activity>
+
+ <activity android:name=".PostCallActivity"
+ android:label="@string/postCallActivityLabel">
+ <intent-filter>
+ <action android:name="android.telecom.action.POST_CALL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/testapps/carmodedialer/Android.bp b/testapps/carmodedialer/Android.bp
new file mode 100644
index 0000000..7179b1f
--- /dev/null
+++ b/testapps/carmodedialer/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2019 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.
+//
+
+android_test {
+ name: "TelecomCarModeApp",
+ static_libs: [
+ "androidx.legacy_legacy-support-v4",
+ "guava",
+ ],
+ srcs: ["src/**/*.java"],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/testapps/carmodedialer/AndroidManifest.xml b/testapps/carmodedialer/AndroidManifest.xml
new file mode 100644
index 0000000..7f55f7e
--- /dev/null
+++ b/testapps/carmodedialer/AndroidManifest.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ coreApp="true"
+ package="com.android.server.telecom.carmodedialer">
+
+ <uses-sdk
+ android:minSdkVersion="28"
+ android:targetSdkVersion="29" />
+
+ <uses-permission android:name="android.permission.ACCEPT_HANDOVER" />
+ <uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.CALL_PHONE" />
+ <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
+ <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.READ_CALL_LOG" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
+
+ <application android:label="Telecom CarMode">
+ <uses-library android:name="android.test.runner" />
+
+ <service android:name="com.android.server.telecom.carmodedialer.CarModeInCallServiceImpl"
+ android:permission="android.permission.BIND_INCALL_SERVICE" >
+ <meta-data android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI"
+ android:value="true"/>
+ <intent-filter>
+ <action android:name="android.telecom.InCallService"/>
+ </intent-filter>
+ </service>
+
+ <activity android:name="com.android.server.telecom.carmodedialer.CarModeInCallUI"
+ android:label="CarMode Dialer"
+ android:launchMode="singleInstance">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="com.android.server.telecom.carmodedialer.CarModeDialerActivity"
+ android:label="CarMode Dialer">
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:mimeType="vnd.android.cursor.item/phone" />
+ <data android:mimeType="vnd.android.cursor.item/person" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="voicemail" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="tel" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/testapps/carmodedialer/res/drawable-hdpi/ic_android_black_24dp.png b/testapps/carmodedialer/res/drawable-hdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..ed3ee45
--- /dev/null
+++ b/testapps/carmodedialer/res/drawable-hdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/carmodedialer/res/drawable-mdpi/ic_android_black_24dp.png b/testapps/carmodedialer/res/drawable-mdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..a4add51
--- /dev/null
+++ b/testapps/carmodedialer/res/drawable-mdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/carmodedialer/res/drawable-xhdpi/ic_android_black_24dp.png b/testapps/carmodedialer/res/drawable-xhdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..41558f2
--- /dev/null
+++ b/testapps/carmodedialer/res/drawable-xhdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/carmodedialer/res/drawable-xhdpi/stat_sys_phone_call.png b/testapps/carmodedialer/res/drawable-xhdpi/stat_sys_phone_call.png
new file mode 100644
index 0000000..1bb4340
--- /dev/null
+++ b/testapps/carmodedialer/res/drawable-xhdpi/stat_sys_phone_call.png
Binary files differ
diff --git a/testapps/carmodedialer/res/drawable-xxhdpi/ic_android_black_24dp.png b/testapps/carmodedialer/res/drawable-xxhdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..6006b12
--- /dev/null
+++ b/testapps/carmodedialer/res/drawable-xxhdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/carmodedialer/res/drawable-xxxhdpi/ic_android_black_24dp.png b/testapps/carmodedialer/res/drawable-xxxhdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..4f935bf
--- /dev/null
+++ b/testapps/carmodedialer/res/drawable-xxxhdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/carmodedialer/res/layout/call_list_item.xml b/testapps/carmodedialer/res/layout/call_list_item.xml
new file mode 100644
index 0000000..9a77ceb
--- /dev/null
+++ b/testapps/carmodedialer/res/layout/call_list_item.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+ <TextView
+ android:id="@+id/phoneNumber"
+ android:layout_gravity="left"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="25dp"
+ android:text="TextView" />
+ <TextView
+ android:id="@+id/callState"
+ android:layout_gravity="left"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="25dp"
+ android:text="TextView" />
+ <TextView
+ android:id="@+id/duration"
+ android:layout_gravity="right"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="25dp"
+ android:text="TextView" />
+</LinearLayout>
diff --git a/testapps/carmodedialer/res/layout/incall_screen.xml b/testapps/carmodedialer/res/layout/incall_screen.xml
new file mode 100644
index 0000000..bef6915
--- /dev/null
+++ b/testapps/carmodedialer/res/layout/incall_screen.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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" >
+ <ListView
+ android:id="@+id/callListView"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:divider="#FFCC00"
+ android:dividerHeight="4px">
+ </ListView>
+ <GridLayout
+ android:columnCount="3"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button
+ android:id="@+id/end_call_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/endCallButton" />
+ <Button
+ android:id="@+id/mute_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/muteButton" />
+ <Button
+ android:id="@+id/hold_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/holdButton"/>
+ <Button
+ android:id="@+id/rtt_iface_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/rttIfaceButton"/>
+ <Button
+ android:id="@+id/answer_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/answerCallButton"/>
+ <Button
+ android:id="@+id/start_rtt_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/startRttButton"/>
+ <Button
+ android:id="@+id/accept_rtt_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/acceptRttButton"/>
+ <Button
+ android:id="@+id/request_handover_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/handoverButton"/>
+ </GridLayout>
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Spinner
+ android:id="@+id/available_bt_devices"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ <Button
+ android:id="@+id/set_bt_device_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/setBtDeviceButton"/>
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button
+ android:id="@+id/earpiece_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/earpieceButton"/>
+ <Button
+ android:id="@+id/speaker_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/speakerButton"/>
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/current_route_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/currentRouteLabel"/>
+ <TextView
+ android:id="@+id/current_audio_route"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="10dp"/>
+ </LinearLayout>
+</LinearLayout>
diff --git a/testapps/carmodedialer/res/layout/testdialer_main.xml b/testapps/carmodedialer/res/layout/testdialer_main.xml
new file mode 100644
index 0000000..c332d19
--- /dev/null
+++ b/testapps/carmodedialer/res/layout/testdialer_main.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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/appLabel"
+ android:layout_gravity="left"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="25dp"
+ android:text="Telecom CarMode Dialer" />
+ <EditText
+ android:id="@+id/number"
+ android:inputType="number"
+ android:layout_width="200dp"
+ android:layout_height="wrap_content" />
+ <Button
+ android:id="@+id/place_call_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/placeCallButton" />
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="15dp"
+ android:text="Priority:" />
+ <EditText
+ android:id="@+id/priority"
+ android:inputType="number"
+ android:text="100"
+ android:layout_width="50dp"
+ android:layout_height="wrap_content" />
+ <Button
+ android:id="@+id/enable_car_mode"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Enable Car mode" />
+ <Button
+ android:id="@+id/disable_car_mode"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Disable Car mode" />
+ </LinearLayout>
+ <Button
+ android:id="@+id/toggle_incallservice"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Toggle InCallService" />
+</LinearLayout>
diff --git a/testapps/carmodedialer/res/values/donottranslate_strings.xml b/testapps/carmodedialer/res/values/donottranslate_strings.xml
new file mode 100644
index 0000000..3b5fa65
--- /dev/null
+++ b/testapps/carmodedialer/res/values/donottranslate_strings.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <!-- Application label -->
+ <string name="app_name">TelecommTests</string>
+
+ <!-- String for the TestCallActivity -->
+ <string name="testCallActivityLabel">Test Connection Service App</string>
+
+ <!-- String for the CarModeDialerActivity -->
+ <string name="testDialerActivityLabel">Test Dialer</string>
+
+ <!-- String for button in CarModeDialerActivity that reassigns the default Dialer -->
+ <string name="defaultDialerButton">Default dialer request</string>
+
+ <!-- String for button in CarModeDialerActivity that places a test call -->
+ <string name="placeCallButton">Place call</string>
+
+ <!-- String for button in CarModeDialerActivity that performs voicemail requests to verify
+ voicemail permissions -->
+ <string name="testVoicemailButton">Exercise voicemail permissions</string>
+
+ <!-- String for button in CarModeDialerActivity that tries to exercise the
+ TelecomManager.cancelMissedCallNotifications() functionality -->
+ <string name="cancelMissedButton">Cancel missed calls</string>
+
+ <string name="endCallButton">End Call</string>
+
+ <string name="answerCallButton">Answer</string>
+
+ <string name="startCallWithRtt">Start call with RTT</string>
+
+ <string name="rttIfaceButton">RTT</string>
+
+ <string name="endRttButton">End RTT</string>
+
+ <string name="startRttButton">Start RTT</string>
+
+ <string name="acceptRttButton">Accept RTT request</string>
+
+ <string name="muteButton">Mute</string>
+
+ <string name="holdButton">Hold</string>
+
+ <string name="handoverButton">Handover</string>
+
+ <string name="inCallUiAppLabel">Test InCall UI</string>
+
+ <string name="UssdUiAppLabel">Test Ussd UI</string>
+
+ <string name="placeUssdButton">Send USSD</string>
+
+ <string name="KeyUiAppLabel">Get Key UI</string>
+
+ <string name="getKeyButton">Get Key Json</string>
+
+ <string name="earpieceButton">Earpiece/Wired</string>
+
+ <string name="speakerButton">Speakerphone</string>
+
+ <string name="setBtDeviceButton">Set BT device</string>
+
+ <string name="currentRouteLabel">Current audio route</string>
+ <!-- String for button in SelfManagedCallingActivity. -->
+ <string name="checkIfPermittedBeforeCallingButton">Check if calls permitted before calling</string>
+
+ <string name="selfManagedCallingActivityLabel">Self-Managed Sample</string>
+
+ <string name="outgoingCallNotPermitted">Outgoing call not permitted.</string>
+
+ <string name="outgoingCallNotPermittedCS">Outgoing call not permitted (CS Reported).</string>
+
+ <string name="incomingCallNotPermitted">Incoming call not permitted.</string>
+
+ <string name="incomingCallNotPermittedCS">Incoming call not permitted (CS Reported).</string>
+
+ <string name="rttUiLabel">Test RTT UI</string>
+
+ <string-array name="rtt_mode_array">
+ <item>Full</item>
+ <item>HCO</item>
+ <item>VCO</item>
+ </string-array>
+
+ <string-array name="rtt_reply_one_liners">
+ <item>To RTT or not to RTT, that is the question...</item>
+ <item>Making TTY great again!</item>
+ <item>I would be more comfortable with real "Thyme" chatting. I don\'t know how to end
+ this pun</item>
+ <item>お疲れ様でした</item>
+ <item>The FCC has mandated that I respond... I will do so begrudgingly</item>
+ <item>😂😂😂💯</item>
+ </string-array>
+</resources>
diff --git a/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CallListAdapter.java b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CallListAdapter.java
new file mode 100644
index 0000000..8ba59a7
--- /dev/null
+++ b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CallListAdapter.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2019 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.carmodedialer;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.telecom.Call;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+public class CallListAdapter extends BaseAdapter {
+ private static final String TAG = "CallListAdapter";
+
+ private final CarModeCallList.Listener mListener = new CarModeCallList.Listener() {
+ @Override
+ public void onCallAdded(Call call) {
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public void onCallRemoved(Call call) {
+ notifyDataSetChanged();
+ if (mCallList.size() == 0) {
+ mCallList.removeListener(this);
+ }
+ }
+ };
+
+ private final LayoutInflater mLayoutInflater;
+ private final CarModeCallList mCallList;
+ private final Handler mHandler = new Handler();
+ private final Runnable mSecondsRunnable = new Runnable() {
+ @Override
+ public void run() {
+ notifyDataSetChanged();
+ if (mCallList.size() > 0) {
+ mHandler.postDelayed(this, 1000);
+ }
+ }
+ };
+
+ public CallListAdapter(Context context) {
+ mLayoutInflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mCallList = CarModeCallList.getInstance();
+ mCallList.addListener(mListener);
+ mHandler.postDelayed(mSecondsRunnable, 1000);
+ }
+
+
+ @Override
+ public int getCount() {
+ Log.i(TAG, "size reporting: " + mCallList.size());
+ return mCallList.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return position;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(final int position, View convertView, ViewGroup parent) {
+ Log.i(TAG, "getView: " + position);
+ if (convertView == null) {
+ convertView = mLayoutInflater.inflate(R.layout.call_list_item, parent, false);
+ }
+
+ TextView phoneNumber = convertView.findViewById(R.id.phoneNumber);
+ TextView duration = convertView.findViewById(R.id.duration);
+ TextView state = convertView.findViewById(R.id.callState);
+
+ Call call = mCallList.getCall(position);
+ Uri handle = call.getDetails().getHandle();
+ phoneNumber.setText(handle == null ? "No number" : handle.getSchemeSpecificPart());
+
+ long durationMs = System.currentTimeMillis() - call.getDetails().getConnectTimeMillis();
+ duration.setText((durationMs / 1000) + " secs");
+
+ state.setText(getStateString(call));
+
+ Log.i(TAG, "Call found: " + ((handle == null) ? "null" : handle.getSchemeSpecificPart())
+ + ", " + durationMs);
+ Log.i(TAG, "Call extras: " + extrasToString(call.getDetails().getExtras()));
+ Log.i(TAG, "Call intent extras: " + extrasToString(call.getDetails().getIntentExtras()));
+
+ return convertView;
+ }
+
+ private String extrasToString(Bundle bundle) {
+ StringBuilder sb = new StringBuilder("[");
+ for (String key : bundle.keySet()) {
+ sb.append(key);
+ sb.append(": ");
+ sb.append(bundle.get(key));
+ sb.append("\n");
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ private static String getStateString(Call call) {
+ switch (call.getState()) {
+ case Call.STATE_ACTIVE:
+ return "active";
+ case Call.STATE_CONNECTING:
+ return "connecting";
+ case Call.STATE_DIALING:
+ return "dialing";
+ case Call.STATE_DISCONNECTED:
+ return "disconnected";
+ case Call.STATE_DISCONNECTING:
+ return "disconnecting";
+ case Call.STATE_HOLDING:
+ return "on hold";
+ case Call.STATE_NEW:
+ return "new";
+ case Call.STATE_RINGING:
+ return "ringing";
+ case Call.STATE_SELECT_PHONE_ACCOUNT:
+ return "select phone account";
+ default:
+ return "unknown";
+ }
+ }
+}
diff --git a/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeCallList.java b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeCallList.java
new file mode 100644
index 0000000..5fba5bd
--- /dev/null
+++ b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeCallList.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2019 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.carmodedialer;
+
+import android.content.Context;
+import android.telecom.Call;
+import android.telecom.InCallService;
+import android.telecom.VideoProfile;
+import android.telecom.VideoProfile.CameraCapabilities;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Maintains a list of calls received via the {@link TestInCallServiceImpl}.
+ */
+public class CarModeCallList extends Call.Callback {
+
+ public static abstract class Listener {
+ public void onCallAdded(Call call) {}
+ public void onCallRemoved(Call call) {}
+ public void onRttStarted(Call call) {}
+ public void onRttStopped(Call call) {}
+ public void onRttInitiationFailed(Call call, int reason) {}
+ public void onRttRequest(Call call, int id) {}
+ }
+
+ private static final com.android.server.telecom.carmodedialer.CarModeCallList
+ INSTANCE = new com.android.server.telecom.carmodedialer.CarModeCallList();
+ private static final String TAG = "TestCallList";
+
+ private class TestVideoCallListener extends InCallService.VideoCall.Callback {
+ private Call mCall;
+
+ public TestVideoCallListener(Call call) {
+ mCall = call;
+ }
+
+ @Override
+ public void onSessionModifyRequestReceived(VideoProfile videoProfile) {
+ Log.v(TAG,
+ "onSessionModifyRequestReceived: videoState = " + videoProfile.getVideoState()
+ + " call = " + mCall);
+ }
+
+ @Override
+ public void onSessionModifyResponseReceived(int status, VideoProfile requestedProfile,
+ VideoProfile responseProfile) {
+ Log.v(TAG,
+ "onSessionModifyResponseReceived: status = " + status + " videoState = "
+ + responseProfile.getVideoState()
+ + " call = " + mCall);
+ }
+
+ @Override
+ public void onCallSessionEvent(int event) {
+
+ }
+
+ @Override
+ public void onPeerDimensionsChanged(int width, int height) {
+
+ }
+
+ @Override
+ public void onVideoQualityChanged(int videoQuality) {
+ Log.v(TAG,
+ "onVideoQualityChanged: videoQuality = " + videoQuality + " call = " + mCall);
+ }
+
+ @Override
+ public void onCallDataUsageChanged(long dataUsage) {
+
+ }
+
+ @Override
+ public void onCameraCapabilitiesChanged(CameraCapabilities cameraCapabilities) {
+
+ }
+ }
+
+ // The calls the call list knows about.
+ private List<Call> mCalls = new LinkedList<Call>();
+ private Map<Call, TestVideoCallListener> mVideoCallListeners =
+ new ArrayMap<Call, TestVideoCallListener>();
+ private Set<Listener> mListeners = new ArraySet<Listener>();
+ private Context mContext;
+ private int mLastRttRequestId = -1;
+
+ /**
+ * Singleton accessor.
+ */
+ public static com.android.server.telecom.carmodedialer.CarModeCallList getInstance() {
+ return INSTANCE;
+ }
+
+ public void addListener(Listener listener) {
+ if (listener != null) {
+ mListeners.add(listener);
+ }
+ }
+
+ public boolean removeListener(Listener listener) {
+ return mListeners.remove(listener);
+ }
+
+ public Call getCall(int position) {
+ return mCalls.get(position);
+ }
+
+ public void addCall(Call call) {
+ if (mCalls.contains(call)) {
+ Log.e(TAG, "addCall: Call already added.");
+ return;
+ }
+ Log.i(TAG, "addCall: " + call + " " + System.identityHashCode(this));
+ mCalls.add(call);
+ call.registerCallback(this);
+
+ for (Listener l : mListeners) {
+ l.onCallAdded(call);
+ }
+ }
+
+ public void removeCall(Call call) {
+ if (!mCalls.contains(call)) {
+ Log.e(TAG, "removeCall: Call cannot be removed -- doesn't exist.");
+ return;
+ }
+ Log.i(TAG, "removeCall: " + call);
+ mCalls.remove(call);
+ call.unregisterCallback(this);
+
+ for (Listener l : mListeners) {
+ if (l != null) {
+ l.onCallRemoved(call);
+ }
+ }
+ }
+
+ public void clearCalls() {
+ for (Call call : new LinkedList<Call>(mCalls)) {
+ removeCall(call);
+ }
+
+ for (Call call : mVideoCallListeners.keySet()) {
+ if (call.getVideoCall() != null) {
+ call.getVideoCall().destroy();
+ }
+ }
+ mVideoCallListeners.clear();
+ }
+
+ public int size() {
+ return mCalls.size();
+ }
+
+ public int getLastRttRequestId() {
+ return mLastRttRequestId;
+ }
+
+ /**
+ * For any video calls tracked, sends an upgrade to video request.
+ */
+ public void sendUpgradeToVideoRequest(int videoState) {
+ Log.v(TAG, "sendUpgradeToVideoRequest : videoState = " + videoState);
+
+ for (Call call : mCalls) {
+ InCallService.VideoCall videoCall = call.getVideoCall();
+ Log.v(TAG, "sendUpgradeToVideoRequest: checkCall "+call);
+ if (videoCall == null) {
+ continue;
+ }
+
+ Log.v(TAG, "send upgrade to video request for call: " + call);
+ videoCall.sendSessionModifyRequest(new VideoProfile(videoState));
+ }
+ }
+
+ /**
+ * For any video calls which are active, sends an upgrade to video response with the specified
+ * video state.
+ *
+ * @param videoState The video state to respond with.
+ */
+ public void sendUpgradeToVideoResponse(int videoState) {
+ Log.v(TAG, "sendUpgradeToVideoResponse : videoState = " + videoState);
+
+ for (Call call : mCalls) {
+ InCallService.VideoCall videoCall = call.getVideoCall();
+ if (videoCall == null) {
+ continue;
+ }
+
+ Log.v(TAG, "send upgrade to video response for call: " + call);
+ videoCall.sendSessionModifyResponse(new VideoProfile(videoState));
+ }
+ }
+
+ @Override
+ public void onVideoCallChanged(Call call, InCallService.VideoCall videoCall) {
+ Log.v(TAG, "onVideoCallChanged: call = " + call + " " + System.identityHashCode(this));
+ if (videoCall != null) {
+ if (!mVideoCallListeners.containsKey(call)) {
+ TestVideoCallListener listener = new TestVideoCallListener(call);
+ videoCall.registerCallback(listener);
+ mVideoCallListeners.put(call, listener);
+ Log.v(TAG, "onVideoCallChanged: added new listener");
+ }
+ }
+ }
+
+ @Override
+ public void onRttStatusChanged(Call call, boolean enabled, Call.RttCall rttCall) {
+ Log.v(TAG, "onRttStatusChanged: call = " + call + " " + System.identityHashCode(this));
+ if (enabled) {
+ for (Listener l : mListeners) {
+ l.onRttStarted(call);
+ }
+ } else {
+ for (Listener l : mListeners) {
+ l.onRttStopped(call);
+ }
+ }
+ }
+
+ @Override
+ public void onRttInitiationFailure(Call call, int reason) {
+ for (Listener l : mListeners) {
+ l.onRttInitiationFailed(call, reason);
+ }
+ }
+
+ @Override
+ public void onRttRequest(Call call, int id) {
+ mLastRttRequestId = id;
+ for (Listener l : mListeners) {
+ l.onRttRequest(call, id);
+ }
+ }
+}
diff --git a/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeDialerActivity.java b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeDialerActivity.java
new file mode 100644
index 0000000..e46d64f
--- /dev/null
+++ b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeDialerActivity.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2019 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.carmodedialer;
+
+import android.app.Activity;
+import android.app.UiModeManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.telecom.PhoneAccount;
+import android.telecom.TelecomManager;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.EditText;
+import android.widget.Toast;
+
+public class CarModeDialerActivity extends Activity {
+ private static final int REQUEST_CODE_SET_DEFAULT_DIALER = 1;
+
+ private EditText mNumberView;
+ private EditText mPriorityView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.testdialer_main);
+
+ findViewById(R.id.place_call_button).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ placeCall();
+ }
+ });
+
+ mNumberView = (EditText) findViewById(R.id.number);
+ findViewById(R.id.enable_car_mode).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ enableCarMode();
+ }
+ });
+ findViewById(R.id.disable_car_mode).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ disableCarMode();
+ }
+ });
+ findViewById(R.id.toggle_incallservice).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ toggleInCallService();
+ }
+ });
+
+ mPriorityView = findViewById(R.id.priority);
+
+ updateMutableUi();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_CODE_SET_DEFAULT_DIALER) {
+ if (resultCode == RESULT_OK) {
+ showToast("User accepted request to become default dialer");
+ } else if (resultCode == RESULT_CANCELED) {
+ showToast("User declined request to become default dialer");
+ }
+ }
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ updateMutableUi();
+ }
+
+ private void updateMutableUi() {
+ Intent intent = getIntent();
+ if (intent != null) {
+ mNumberView.setText(intent.getDataString());
+ }
+ }
+
+ private void placeCall() {
+ final TelecomManager telecomManager =
+ (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
+ telecomManager.placeCall(Uri.fromParts(PhoneAccount.SCHEME_TEL,
+ mNumberView.getText().toString(), null), createCallIntentExtras());
+ }
+
+ private void showToast(String message) {
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
+ }
+
+ private Bundle createCallIntentExtras() {
+ Bundle extras = new Bundle();
+ extras.putString("com.android.server.telecom.carmodedialer.CALL_EXTRAS", "Tyler was here");
+
+ Bundle intentExtras = new Bundle();
+ intentExtras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
+ return intentExtras;
+ }
+
+ private void enableCarMode() {
+ int priority;
+ try {
+ priority = Integer.parseInt(mPriorityView.getText().toString());
+ } catch (NumberFormatException nfe) {
+ Toast.makeText(this, "Invalid priority; not enabling car mode.",
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+ UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE);
+ uiModeManager.enableCarMode(priority, 0);
+ Toast.makeText(this, "Enabling car mode with priority " + priority,
+ Toast.LENGTH_LONG).show();
+ }
+
+ private void disableCarMode() {
+ UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE);
+ uiModeManager.disableCarMode(0);
+ Toast.makeText(this, "Disabling car mode", Toast.LENGTH_LONG).show();
+ }
+
+ private void toggleInCallService() {
+ ComponentName uiComponent = new ComponentName(
+ com.android.server.telecom.carmodedialer.CarModeInCallServiceImpl.class.getPackage().getName(),
+ com.android.server.telecom.carmodedialer.CarModeInCallServiceImpl.class.getName());
+ boolean isEnabled = getPackageManager().getComponentEnabledSetting(uiComponent)
+ == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+ getPackageManager().setComponentEnabledSetting(uiComponent,
+ isEnabled ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP);
+ isEnabled = getPackageManager().getComponentEnabledSetting(uiComponent)
+ == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+ Toast.makeText(this, "Is UI enabled? " + isEnabled, Toast.LENGTH_LONG).show();
+ }
+}
diff --git a/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeInCallServiceImpl.java b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeInCallServiceImpl.java
new file mode 100644
index 0000000..2879bde
--- /dev/null
+++ b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeInCallServiceImpl.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 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.carmodedialer;
+
+import android.content.Intent;
+import android.telecom.Call;
+import android.telecom.CallAudioState;
+import android.telecom.InCallService;
+import android.telecom.Phone;
+import android.util.Log;
+
+import java.lang.Override;
+import java.lang.String;
+
+/**
+ * Test In-Call service implementation. Logs incoming events. Mainly used to test binding to
+ * multiple {@link InCallService} implementations.
+ */
+public class CarModeInCallServiceImpl extends InCallService {
+ private static final String TAG = "TestInCallServiceImpl";
+ public static com.android.server.telecom.carmodedialer.CarModeInCallServiceImpl sInstance;
+
+ private Phone mPhone;
+
+ private Phone.Listener mPhoneListener = new Phone.Listener() {
+ @Override
+ public void onCallAdded(Phone phone, Call call) {
+ Log.i(TAG, "onCallAdded: " + call.toString());
+ CarModeCallList callList = CarModeCallList.getInstance();
+ callList.addCall(call);
+
+ if (callList.size() == 1) {
+ startInCallUI();
+ }
+ }
+
+ @Override
+ public void onCallRemoved(Phone phone, Call call) {
+ Log.i(TAG, "onCallRemoved: "+call.toString());
+ CarModeCallList.getInstance().removeCall(call);
+ }
+ };
+
+ @Override
+ public void onPhoneCreated(Phone phone) {
+ Log.i(TAG, "onPhoneCreated");
+ mPhone = phone;
+ mPhone.addListener(mPhoneListener);
+ CarModeCallList.getInstance().clearCalls();
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ Log.i(TAG, "onPhoneDestroyed");
+ mPhone.removeListener(mPhoneListener);
+ mPhone = null;
+ CarModeCallList.getInstance().clearCalls();
+ sInstance = null;
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ public void onCallAudioStateChanged(CallAudioState cas) {
+ if (CarModeInCallUI.sInstance != null) {
+ CarModeInCallUI.sInstance.updateCallAudioState(cas);
+ }
+ }
+
+ private void startInCallUI() {
+ sInstance = this;
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClass(this, CarModeInCallUI.class);
+ startActivity(intent);
+ }
+}
diff --git a/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeInCallUI.java b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeInCallUI.java
new file mode 100644
index 0000000..6d06862
--- /dev/null
+++ b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/CarModeInCallUI.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2019 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.carmodedialer;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothDevice;
+import android.os.Bundle;
+import android.telecom.Call;
+import android.telecom.CallAudioState;
+import android.telecom.VideoProfile;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.Collection;
+
+public class CarModeInCallUI extends Activity {
+ private class BluetoothDeviceAdapter extends ArrayAdapter<BluetoothDevice> {
+ public BluetoothDeviceAdapter() {
+ super(com.android.server.telecom.carmodedialer.CarModeInCallUI.this, android.R.layout.simple_spinner_item);
+ setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ BluetoothDevice info = getItem(position);
+ TextView result = new TextView(com.android.server.telecom.carmodedialer.CarModeInCallUI.this);
+ result.setText(info.getName());
+ return result;
+ }
+
+ public void update(Collection<BluetoothDevice> devices) {
+ clear();
+ addAll(devices);
+ }
+ }
+
+ public static com.android.server.telecom.carmodedialer.CarModeInCallUI sInstance;
+ private ListView mListView;
+ private CarModeCallList mCallList;
+ private Spinner mBtDeviceList;
+ private BluetoothDeviceAdapter mBluetoothDeviceAdapter;
+ private TextView mCurrentRouteDisplay;
+
+ /** ${inheritDoc} */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ sInstance = this;
+
+ setContentView(R.layout.incall_screen);
+
+ mListView = (ListView) findViewById(R.id.callListView);
+ mListView.setAdapter(new CallListAdapter(this));
+ mListView.setVisibility(View.VISIBLE);
+
+ mCallList = CarModeCallList.getInstance();
+ mCallList.addListener(new CarModeCallList.Listener() {
+ @Override
+ public void onCallRemoved(Call call) {
+ if (mCallList.size() == 0) {
+ Log.i(CarModeInCallUI.class.getSimpleName(), "Ending the incall UI");
+ finish();
+ }
+ }
+
+ @Override
+ public void onRttStarted(Call call) {
+ Toast.makeText(CarModeInCallUI.this, "RTT now enabled", Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onRttStopped(Call call) {
+ Toast.makeText(CarModeInCallUI.this, "RTT now disabled", Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onRttInitiationFailed(Call call, int reason) {
+ Toast.makeText(CarModeInCallUI.this, String.format("RTT failed to init: %d", reason),
+ Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onRttRequest(Call call, int id) {
+ Toast.makeText(CarModeInCallUI.this, String.format("RTT request: %d", id),
+ Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ View endCallButton = findViewById(R.id.end_call_button);
+ View holdButton = findViewById(R.id.hold_button);
+ View muteButton = findViewById(R.id.mute_button);
+ View answerButton = findViewById(R.id.answer_button);
+ View setBtDeviceButton = findViewById(R.id.set_bt_device_button);
+ View earpieceButton = findViewById(R.id.earpiece_button);
+ View speakerButton = findViewById(R.id.speaker_button);
+ mBtDeviceList = findViewById(R.id.available_bt_devices);
+ mBluetoothDeviceAdapter = new BluetoothDeviceAdapter();
+ mBtDeviceList.setAdapter(mBluetoothDeviceAdapter);
+ mCurrentRouteDisplay = findViewById(R.id.current_audio_route);
+
+ endCallButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Call call = mCallList.getCall(0);
+ if (call != null) {
+ call.disconnect();
+ }
+ }
+ });
+ holdButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Call call = mCallList.getCall(0);
+ if (call != null) {
+ if (call.getState() == Call.STATE_HOLDING) {
+ call.unhold();
+ } else {
+ call.hold();
+ }
+ }
+ }
+ });
+ muteButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Call call = mCallList.getCall(0);
+ if (call != null) {
+
+ }
+ }
+ });
+
+ answerButton.setOnClickListener(view -> {
+ Call call = mCallList.getCall(0);
+ if (call.getState() == Call.STATE_RINGING) {
+ call.answer(VideoProfile.STATE_AUDIO_ONLY);
+ }
+ });
+
+ earpieceButton.setOnClickListener(view -> {
+ CarModeInCallServiceImpl.sInstance.setAudioRoute(CallAudioState.ROUTE_WIRED_OR_EARPIECE);
+ });
+
+ speakerButton.setOnClickListener(view -> {
+ CarModeInCallServiceImpl.sInstance.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
+ });
+
+ setBtDeviceButton.setOnClickListener(view -> {
+ if (mBtDeviceList.getSelectedItem() != null
+ && CarModeInCallServiceImpl.sInstance != null) {
+ CarModeInCallServiceImpl.sInstance.requestBluetoothAudio(
+ (BluetoothDevice) mBtDeviceList.getSelectedItem());
+ }
+ });
+
+ }
+
+ public void updateCallAudioState(CallAudioState cas) {
+ mBluetoothDeviceAdapter.update(cas.getSupportedBluetoothDevices());
+ String routeText;
+ switch (cas.getRoute()) {
+ case CallAudioState.ROUTE_EARPIECE:
+ routeText = "Earpiece";
+ break;
+ case CallAudioState.ROUTE_SPEAKER:
+ routeText = "Speaker";
+ break;
+ case CallAudioState.ROUTE_WIRED_HEADSET:
+ routeText = "Wired";
+ break;
+ case CallAudioState.ROUTE_BLUETOOTH:
+ BluetoothDevice activeDevice = cas.getActiveBluetoothDevice();
+ routeText = activeDevice == null ? "null bt" : activeDevice.getName();
+ break;
+ default:
+ routeText = "unknown: " + cas.getRoute();
+ }
+ mCurrentRouteDisplay.setText(routeText);
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ protected void onDestroy() {
+ sInstance = null;
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+}
diff --git a/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/TestInCallServiceBroadcastReceiver.java b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/TestInCallServiceBroadcastReceiver.java
new file mode 100644
index 0000000..6b2b009
--- /dev/null
+++ b/testapps/carmodedialer/src/com/android/server/telecom/carmodedialer/TestInCallServiceBroadcastReceiver.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 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.carmodedialer;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.telecom.TelecomManager;
+import android.util.Log;
+
+/**
+ * Test in call service broadcast receiver.
+ */
+public class TestInCallServiceBroadcastReceiver extends BroadcastReceiver {
+ private static final String TAG = "TestInCallServiceBR";
+
+ /**
+ * Sends an upgrade to video request for any live calls.
+ */
+ public static final String ACTION_SEND_UPDATE_REQUEST_FROM_TEST_INCALL_SERVICE =
+ "android.server.telecom.testapps.ACTION_SEND_UPDATE_REQUEST_FROM_TEST_INCALL_SERVICE";
+
+ /**
+ * Sends an a response to an upgrade to video request.
+ */
+ public static final String ACTION_SEND_UPGRADE_RESPONSE =
+ "android.server.telecom.testapps.ACTION_SEND_UPGRADE_RESPONSE";
+
+ /**
+ * Handles broadcasts directed at the {@link CarModeInCallServiceImpl}.
+ *
+ * @param context The Context in which the receiver is running.
+ * @param intent The Intent being received.
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ Log.v(TAG, "onReceive: " + action);
+
+ if (ACTION_SEND_UPDATE_REQUEST_FROM_TEST_INCALL_SERVICE.equals(action)) {
+ final int videoState = Integer.parseInt(intent.getData().getSchemeSpecificPart());
+ CarModeCallList.getInstance().sendUpgradeToVideoRequest(videoState);
+ } else if (ACTION_SEND_UPGRADE_RESPONSE.equals(action)) {
+ final int videoState = Integer.parseInt(intent.getData().getSchemeSpecificPart());
+ CarModeCallList.getInstance().sendUpgradeToVideoResponse(videoState);
+ } else if (TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED.equals(action)) {
+ Log.i(TAG, "onReceive: registered " + intent.getExtras().get(
+ TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE));
+ } else if (TelecomManager.ACTION_PHONE_ACCOUNT_UNREGISTERED.equals(action)) {
+ Log.i(TAG, "onReceive: unregistered " + intent.getExtras().get(
+ TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE));
+ }
+ }
+}
diff --git a/testapps/res/layout/incall_screen.xml b/testapps/res/layout/incall_screen.xml
index 452cb2b..f8f919b 100644
--- a/testapps/res/layout/incall_screen.xml
+++ b/testapps/res/layout/incall_screen.xml
@@ -71,6 +71,21 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/handoverButton"/>
+ <Button
+ android:id="@+id/exit_audio_processing_ring_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/exit_audio_processing_ring_button"/>
+ <Button
+ android:id="@+id/exit_audio_processing_noring_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/exit_audio_processing_noring_button"/>
+ <Button
+ android:id="@+id/reject_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/reject_button"/>
</GridLayout>
<LinearLayout
android:layout_width="wrap_content"
@@ -116,4 +131,14 @@
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"/>
</LinearLayout>
+ <Button
+ android:id="@+id/disable_incallservice"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Disable InCallService" />
+ <Button
+ android:id="@+id/enable_incallservice"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="Enable InCallService" />
</LinearLayout>
diff --git a/testapps/res/layout/testdialer_main.xml b/testapps/res/layout/testdialer_main.xml
index 9da3789..3f397b8 100644
--- a/testapps/res/layout/testdialer_main.xml
+++ b/testapps/res/layout/testdialer_main.xml
@@ -59,4 +59,41 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/startCallWithRtt"/>
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="15dp"
+ android:text="Priority:" />
+ <EditText
+ android:id="@+id/priority"
+ android:inputType="number"
+ android:text="100"
+ android:layout_width="50dp"
+ android:layout_height="wrap_content" />
+ <Button
+ android:id="@+id/enable_car_mode"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Enable Car mode" />
+ <Button
+ android:id="@+id/disable_car_mode"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Disable Car mode" />
+ </LinearLayout>
+ <Button
+ android:id="@+id/toggle_incallservice"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Toggle InCallService" />
+
+ <Button
+ android:id="@+id/send_contact_discovery_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/send_contact_discovery" />
</LinearLayout>
diff --git a/testapps/res/values/donottranslate_strings.xml b/testapps/res/values/donottranslate_strings.xml
index abd2949..7331d5a 100644
--- a/testapps/res/values/donottranslate_strings.xml
+++ b/testapps/res/values/donottranslate_strings.xml
@@ -21,20 +21,20 @@
<!-- String for the TestCallActivity -->
<string name="testCallActivityLabel">Test Connection Service App</string>
- <!-- String for the TestDialerActivity -->
+ <!-- String for the CarModeDialerActivity -->
<string name="testDialerActivityLabel">Test Dialer</string>
- <!-- String for button in TestDialerActivity that reassigns the default Dialer -->
+ <!-- String for button in CarModeDialerActivity that reassigns the default Dialer -->
<string name="defaultDialerButton">Default dialer request</string>
- <!-- String for button in TestDialerActivity that places a test call -->
+ <!-- String for button in CarModeDialerActivity that places a test call -->
<string name="placeCallButton">Place call</string>
- <!-- String for button in TestDialerActivity that performs voicemail requests to verify
+ <!-- String for button in CarModeDialerActivity that performs voicemail requests to verify
voicemail permissions -->
<string name="testVoicemailButton">Exercise voicemail permissions</string>
- <!-- String for button in TestDialerActivity that tries to exercise the
+ <!-- String for button in CarModeDialerActivity that tries to exercise the
TelecomManager.cancelMissedCallNotifications() functionality -->
<string name="cancelMissedButton">Cancel missed calls</string>
@@ -56,6 +56,12 @@
<string name="holdButton">Hold</string>
+ <string name="exit_audio_processing_noring_button">Exit AP (no ring)</string>
+
+ <string name="exit_audio_processing_ring_button">Exit AP (ring)</string>
+
+ <string name="reject_button">Reject</string>
+
<string name="handoverButton">Handover</string>
<string name="inCallUiAppLabel">Test InCall UI</string>
@@ -90,6 +96,8 @@
<string name="rttUiLabel">Test RTT UI</string>
+ <string name="postCallActivityLabel">Test Post Call Screen</string>
+
<string-array name="rtt_mode_array">
<item>Full</item>
<item>HCO</item>
@@ -105,4 +113,6 @@
<item>The FCC has mandated that I respond... I will do so begrudgingly</item>
<item>😂😂😂💯</item>
</string-array>
+
+ <string name="send_contact_discovery">Send Contact Discovery Intent</string>
</resources>
diff --git a/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java b/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java
index 4de6eed..5fa13e3 100644
--- a/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java
+++ b/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java
@@ -150,6 +150,10 @@
return "ringing";
case Call.STATE_SELECT_PHONE_ACCOUNT:
return "select phone account";
+ case Call.STATE_AUDIO_PROCESSING:
+ return "audio processing";
+ case Call.STATE_SIMULATED_RINGING:
+ return "simulated ringing";
default:
return "unknown";
}
diff --git a/testapps/src/com/android/server/telecom/testapps/CallScreeningActivity.java b/testapps/src/com/android/server/telecom/testapps/CallScreeningActivity.java
deleted file mode 100644
index 05ba500..0000000
--- a/testapps/src/com/android/server/telecom/testapps/CallScreeningActivity.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2018 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.testapps;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.telecom.CallScreeningService;
-import android.view.WindowManager;
-
-public class CallScreeningActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- AlertDialog alertDialog = new AlertDialog.Builder(this)
- .setTitle("Test Call Screening")
- .setMessage("Allow the call?")
- .setNegativeButton("Block", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (TestCallScreeningService.getInstance() != null) {
- TestCallScreeningService.getInstance().blockCall();
- }
- finish();
- }
- })
- .setPositiveButton("Allow", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (TestCallScreeningService.getInstance() != null) {
- TestCallScreeningService.getInstance().allowCall();
- }
- finish();
- }
- }).create();
- alertDialog.show();
- }
-}
diff --git a/testapps/src/com/android/server/telecom/testapps/PostCallActivity.java b/testapps/src/com/android/server/telecom/testapps/PostCallActivity.java
new file mode 100644
index 0000000..101a68e
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/PostCallActivity.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 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.testapps;
+
+import static android.telecom.TelecomManager.EXTRA_CALL_DURATION;
+import static android.telecom.TelecomManager.EXTRA_DISCONNECT_CAUSE;
+import static android.telecom.TelecomManager.EXTRA_HANDLE;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telecom.Log;
+
+public class PostCallActivity extends Activity {
+
+ public static final String ACTION_POST_CALL = "android.telecom.action.POST_CALL";
+ public static final int DEFAULT_DISCONNECT_CAUSE = -1;
+ public static final int DEFAULT_DURATION = -1;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ final Intent intent = getIntent();
+ final String action = intent != null ? intent.getAction() : null;
+ Log.i(this, "action: %s", action);
+ if (ACTION_POST_CALL.equals(action)) {
+ Log.i(this, "extra handle: " +
+ intent.getParcelableExtra(EXTRA_HANDLE));
+ Log.i(this, "extra disconnect cause: " +
+ intent.getIntExtra(EXTRA_DISCONNECT_CAUSE, DEFAULT_DISCONNECT_CAUSE));
+ Log.i(this, "extra duration: " +
+ intent.getIntExtra(EXTRA_CALL_DURATION, DEFAULT_DURATION));
+ }
+ }
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/TestCallScreeningService.java b/testapps/src/com/android/server/telecom/testapps/TestCallScreeningService.java
index 5f7f4d5..a975219 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestCallScreeningService.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestCallScreeningService.java
@@ -16,12 +16,16 @@
package com.android.server.telecom.testapps;
-import android.content.Intent;
-import android.graphics.drawable.Icon;
+import android.os.SystemProperties;
import android.telecom.Call;
import android.telecom.CallScreeningService;
import android.telecom.Log;
+/**
+ * To use this while testing, use:
+ * adb shell setprop com.android.server.telecom.testapps.callscreeningresult n,
+ * where n is one of the codes defined below.
+ */
public class TestCallScreeningService extends CallScreeningService {
private Call.Details mDetails;
private static TestCallScreeningService sTestCallScreeningService;
@@ -30,6 +34,13 @@
return sTestCallScreeningService;
}
+ private static final int ALLOW_CALL = 0;
+ private static final int BLOCK_CALL = 1;
+ private static final int SCREEN_CALL_FURTHER = 2;
+
+ private static final String SCREENING_RESULT_KEY =
+ TestCallScreeningService.class.getPackage().getName() + ".callscreeningresult";
+
/**
* Handles request from the system to screen an incoming call.
* @param callDetails Information about a new incoming call, see {@link Call.Details}.
@@ -40,10 +51,21 @@
sTestCallScreeningService = this;
mDetails = callDetails;
+
if (callDetails.getCallDirection() == Call.Details.DIRECTION_INCOMING) {
- Intent errorIntent = new Intent(this, CallScreeningActivity.class);
- errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(errorIntent);
+ Log.i(this, "%s = %d", SCREENING_RESULT_KEY,
+ SystemProperties.getInt(SCREENING_RESULT_KEY, 0));
+ switch (SystemProperties.getInt(SCREENING_RESULT_KEY, 0)) {
+ case ALLOW_CALL:
+ allowCall();
+ break;
+ case BLOCK_CALL:
+ blockCall();
+ break;
+ case SCREEN_CALL_FURTHER:
+ screenCallFurther();
+ break;
+ }
}
}
@@ -68,4 +90,16 @@
.build();
respondToCall(mDetails, response);
}
+
+ void screenCallFurther() {
+ CallScreeningService.CallResponse
+ response = new CallScreeningService.CallResponse.Builder()
+ .setDisallowCall(false)
+ .setRejectCall(false)
+ .setSkipCallLog(false)
+ .setSkipNotification(false)
+ .setShouldScreenCallViaAudioProcessing(true)
+ .build();
+ respondToCall(mDetails, response);
+ }
}
diff --git a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
index c438c4a..f6fa116 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
@@ -79,17 +79,6 @@
private final class TestConference extends Conference {
- private final Connection.Listener mConnectionListener = new Connection.Listener() {
- @Override
- public void onDestroyed(Connection c) {
- removeConnection(c);
- if (getConnections().size() == 0) {
- setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
- destroy();
- }
- }
- };
-
public TestConference(Connection a, Connection b) {
super(null);
setConnectionCapabilities(
@@ -100,9 +89,6 @@
addConnection(a);
addConnection(b);
- a.addConnectionListener(mConnectionListener);
- b.addConnectionListener(mConnectionListener);
-
a.setConference(this);
b.setConference(this);
@@ -122,7 +108,6 @@
if (getConnections().contains(connection)) {
connection.setConference(null);
removeConnection(connection);
- connection.removeConnectionListener(mConnectionListener);
}
}
@@ -197,7 +182,9 @@
setConnectionProperties(properties);
if (isIncoming) {
- putExtra(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
+ Bundle newExtras = (getExtras() == null) ? new Bundle() : getExtras();
+ newExtras.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
+ putExtras(newExtras);
}
LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(
mHangupReceiver, new IntentFilter(TestCallActivity.ACTION_HANGUP_CALLS));
diff --git a/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java b/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
index c7eccf7..1e06387 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
@@ -1,17 +1,28 @@
package com.android.server.telecom.testapps;
+import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
+
import android.app.Activity;
+import android.app.UiModeManager;
+import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
+import android.os.PersistableBundle;
import android.provider.CallLog.Calls;
+import android.provider.Settings;
import android.telecom.PhoneAccount;
import android.telecom.TelecomManager;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.ImsRcsManager;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
+import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;
@@ -21,6 +32,7 @@
private EditText mNumberView;
private CheckBox mRttCheckbox;
+ private EditText mPriorityView;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -56,6 +68,29 @@
mNumberView = (EditText) findViewById(R.id.number);
mRttCheckbox = (CheckBox) findViewById(R.id.call_with_rtt_checkbox);
+ findViewById(R.id.enable_car_mode).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ enableCarMode();
+ }
+ });
+ findViewById(R.id.disable_car_mode).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ disableCarMode();
+ }
+ });
+ findViewById(R.id.toggle_incallservice).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ toggleInCallService();
+ }
+ });
+
+ Button discoveryButton = findViewById(R.id.send_contact_discovery_button);
+ discoveryButton.setOnClickListener(v -> sendContactDiscoveryIntent());
+
+ mPriorityView = findViewById(R.id.priority);
updateMutableUi();
}
@@ -140,4 +175,46 @@
Log.i("Santos xtr", intentExtras.toString());
return intentExtras;
}
+
+ private void enableCarMode() {
+ int priority;
+ try {
+ priority = Integer.parseInt(mPriorityView.getText().toString());
+ } catch (NumberFormatException nfe) {
+ Toast.makeText(this, "Invalid priority; not enabling car mode.",
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+ UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE);
+ uiModeManager.enableCarMode(priority, 0);
+ Toast.makeText(this, "Enabling car mode with priority " + priority,
+ Toast.LENGTH_LONG).show();
+ }
+
+ private void disableCarMode() {
+ UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE);
+ uiModeManager.disableCarMode(0);
+ Toast.makeText(this, "Disabling car mode", Toast.LENGTH_LONG).show();
+ }
+
+ private void toggleInCallService() {
+ ComponentName uiComponent = new ComponentName(
+ TestInCallServiceImpl.class.getPackage().getName(),
+ TestInCallServiceImpl.class.getName());
+ boolean isEnabled = getPackageManager().getComponentEnabledSetting(uiComponent)
+ == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+ getPackageManager().setComponentEnabledSetting(uiComponent,
+ isEnabled ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP);
+ isEnabled = getPackageManager().getComponentEnabledSetting(uiComponent)
+ == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+ Toast.makeText(this, "Is UI enabled? " + isEnabled, Toast.LENGTH_LONG).show();
+ }
+
+ private void sendContactDiscoveryIntent() {
+ Intent intent = new Intent(ImsRcsManager.ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN);
+ intent.putExtra(Settings.EXTRA_SUB_ID, SubscriptionManager.getDefaultSubscriptionId());
+ startActivity(intent);
+ }
}
diff --git a/testapps/src/com/android/server/telecom/testapps/TestInCallServiceImpl.java b/testapps/src/com/android/server/telecom/testapps/TestInCallServiceImpl.java
index 78b8bcc..094f0a5 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestInCallServiceImpl.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestInCallServiceImpl.java
@@ -16,7 +16,6 @@
package com.android.server.telecom.testapps;
-import android.content.Context;
import android.content.Intent;
import android.telecom.Call;
import android.telecom.CallAudioState;
@@ -24,9 +23,6 @@
import android.telecom.Phone;
import android.util.Log;
-import java.lang.Override;
-import java.lang.String;
-
/**
* Test In-Call service implementation. Logs incoming events. Mainly used to test binding to
* multiple {@link InCallService} implementations.
diff --git a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
index 2a5b33a..3f3a2c8 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
@@ -20,6 +20,7 @@
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.telecom.Call;
import android.telecom.CallAudioState;
@@ -125,6 +126,10 @@
View setBtDeviceButton = findViewById(R.id.set_bt_device_button);
View earpieceButton = findViewById(R.id.earpiece_button);
View speakerButton = findViewById(R.id.speaker_button);
+ View exitAudioProcessingRingButton = findViewById(R.id.exit_audio_processing_ring_button);
+ View exitAudioProcessingNoRingButton =
+ findViewById(R.id.exit_audio_processing_noring_button);
+ View rejectButton = findViewById(R.id.reject_button);
mBtDeviceList = findViewById(R.id.available_bt_devices);
mBluetoothDeviceAdapter = new BluetoothDeviceAdapter();
mBtDeviceList.setAdapter(mBluetoothDeviceAdapter);
@@ -173,7 +178,8 @@
answerButton.setOnClickListener(view -> {
Call call = mCallList.getCall(0);
- if (call.getState() == Call.STATE_RINGING) {
+ if (call.getState() == Call.STATE_RINGING
+ || call.getState() == Call.STATE_SIMULATED_RINGING) {
call.answer(VideoProfile.STATE_AUDIO_ONLY);
}
});
@@ -213,6 +219,35 @@
call.handoverTo(getHandoverToPhoneAccountHandle(), VideoProfile.STATE_BIDIRECTIONAL,
null);
});
+
+ exitAudioProcessingRingButton.setOnClickListener((v) -> {
+ Call call = mCallList.getCall(0);
+ call.exitBackgroundAudioProcessing(true);
+ });
+
+ exitAudioProcessingNoRingButton.setOnClickListener((v) -> {
+ Call call = mCallList.getCall(0);
+ call.exitBackgroundAudioProcessing(false);
+ });
+
+ rejectButton.setOnClickListener((v) -> {
+ Call call = mCallList.getCall(0);
+ call.reject(false, null);
+ });
+
+ findViewById(R.id.disable_incallservice).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ disableInCallService();
+ }
+ });
+
+ findViewById(R.id.enable_incallservice).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ enableInCallService();
+ }
+ });
}
public void updateCallAudioState(CallAudioState cas) {
@@ -265,4 +300,26 @@
SelfManagedCallList.class.getPackage().getName(),
SelfManagedConnectionService.class.getName()), "1");
}
+
+ public void disableInCallService() {
+ ComponentName uiComponent = new ComponentName(
+ TestInCallServiceImpl.class.getPackage().getName(),
+ TestInCallServiceImpl.class.getName());
+ getPackageManager().setComponentEnabledSetting(uiComponent,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0);
+ boolean isEnabled = getPackageManager().getComponentEnabledSetting(uiComponent)
+ == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+ Toast.makeText(this, "Is UI enabled? " + isEnabled, Toast.LENGTH_LONG).show();
+ }
+
+ public void enableInCallService() {
+ ComponentName uiComponent = new ComponentName(
+ TestInCallServiceImpl.class.getPackage().getName(),
+ TestInCallServiceImpl.class.getName());
+ getPackageManager().setComponentEnabledSetting(uiComponent,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
+ boolean isEnabled = getPackageManager().getComponentEnabledSetting(uiComponent)
+ == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+ Toast.makeText(this, "Is UI enabled? " + isEnabled, Toast.LENGTH_LONG).show();
+ }
}
diff --git a/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java b/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java
deleted file mode 100644
index 91ddf5a..0000000
--- a/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2016 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.provider.BlockedNumberContract.STATUS_BLOCKED_IN_LIST;
-import static android.provider.BlockedNumberContract.STATUS_NOT_BLOCKED;
-
-import android.content.Context;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.PersistableBundle;
-import android.provider.CallLog;
-import android.telecom.TelecomManager;
-import android.telephony.CarrierConfigManager;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.server.telecom.Call;
-import com.android.server.telecom.CallerInfoLookupHelper;
-import com.android.server.telecom.callfiltering.AsyncBlockCheckFilter;
-import com.android.server.telecom.callfiltering.BlockCheckerAdapter;
-import com.android.server.telecom.callfiltering.CallFilterResultCallback;
-import com.android.server.telecom.callfiltering.CallFilteringResult;
-
-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.concurrent.CountDownLatch;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-@RunWith(JUnit4.class)
-public class AsyncBlockCheckFilterTest extends TelecomTestCase {
- @Mock private BlockCheckerAdapter mBlockCheckerAdapter;
- @Mock private Call mCall;
- @Mock private CallFilterResultCallback mCallback;
- @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
- @Mock private CarrierConfigManager mCarrierConfigManager;
-
- private AsyncBlockCheckFilter mFilter;
- private static final CallFilteringResult BLOCK_RESULT = new CallFilteringResult(
- false, // shouldAllowCall
- true, //shouldReject
- true, //shouldAddToCallLog
- false, // shouldShowNotification
- CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER, //blockReason
- null, // callScreeningAppName
- null //callScreeningComponentName
-
- );
-
- private static final CallFilteringResult PASS_RESULT = new CallFilteringResult(
- true, // shouldAllowCall
- false, // shouldReject
- true, // shouldAddToCallLog
- true // shouldShowNotification
- );
-
- private static final Uri TEST_HANDLE = Uri.parse("tel:1235551234");
- private static final int TEST_TIMEOUT = 1000;
-
- @Override
- @Before
- public void setUp() throws Exception {
- super.setUp();
- when(mCall.getHandle()).thenReturn(TEST_HANDLE);
- mFilter = new AsyncBlockCheckFilter(mContext, mBlockCheckerAdapter,
- mCallerInfoLookupHelper, null);
- }
-
- @SmallTest
- @Test
- public void testBlockNumber() {
- final CountDownLatch latch = new CountDownLatch(1);
- doAnswer(invocation -> {
- latch.countDown();
- return STATUS_BLOCKED_IN_LIST;
- }).when(mBlockCheckerAdapter)
- .getBlockStatus(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
- any(Bundle.class));
-
- setEnhancedBlockingEnabled(false);
- mFilter.startFilterLookup(mCall, mCallback);
-
- waitOnLatch(latch);
- verify(mCallback, timeout(TEST_TIMEOUT))
- .onCallFilteringComplete(eq(mCall), eq(BLOCK_RESULT));
- }
-
- @SmallTest
- @Test
- public void testBlockNumber_enhancedBlockingEnabled() {
- final CountDownLatch latch = new CountDownLatch(1);
- doAnswer(invocation -> {
- latch.countDown();
- return STATUS_BLOCKED_IN_LIST;
- }).when(mBlockCheckerAdapter)
- .getBlockStatus(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
- any(Bundle.class));
-
- setEnhancedBlockingEnabled(true);
- CallerInfoLookupHelper.OnQueryCompleteListener queryListener = verifyEnhancedLookupStart();
- queryListener.onCallerInfoQueryComplete(TEST_HANDLE, null);
-
- waitOnLatch(latch);
- verify(mCallback, timeout(TEST_TIMEOUT))
- .onCallFilteringComplete(eq(mCall), eq(BLOCK_RESULT));
- }
-
- @SmallTest
- @Test
- public void testDontBlockNumber() {
- final CountDownLatch latch = new CountDownLatch(1);
- doAnswer(invocation -> {
- latch.countDown();
- return STATUS_NOT_BLOCKED;
- }).when(mBlockCheckerAdapter)
- .getBlockStatus(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
- any(Bundle.class));
-
- setEnhancedBlockingEnabled(false);
- mFilter.startFilterLookup(mCall, mCallback);
-
- waitOnLatch(latch);
- verify(mCallback, timeout(TEST_TIMEOUT))
- .onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
- }
-
- @SmallTest
- @Test
- public void testDontBlockNumber_enhancedBlockingEnabled() {
- final CountDownLatch latch = new CountDownLatch(1);
- doAnswer(invocation -> {
- latch.countDown();
- return STATUS_NOT_BLOCKED;
- }).when(mBlockCheckerAdapter)
- .getBlockStatus(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
- any(Bundle.class));
-
- setEnhancedBlockingEnabled(true);
- CallerInfoLookupHelper.OnQueryCompleteListener queryListener = verifyEnhancedLookupStart();
- queryListener.onCallerInfoQueryComplete(TEST_HANDLE, null);
-
- waitOnLatch(latch);
- verify(mCallback, timeout(TEST_TIMEOUT))
- .onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
- }
-
- private void setEnhancedBlockingEnabled(Boolean value) {
- PersistableBundle bundle = new PersistableBundle();
- bundle.putBoolean(CarrierConfigManager.KEY_SUPPORT_ENHANCED_CALL_BLOCKING_BOOL,
- value);
- when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
- .thenReturn(mCarrierConfigManager);
- when(mCarrierConfigManager.getConfig()).thenReturn(bundle);
- }
-
- private CallerInfoLookupHelper.OnQueryCompleteListener verifyEnhancedLookupStart() {
- // The enhanced lookup will only be excuted when enhanced blocking enabled and the
- // presentation is PRESENTATION_ALLOWED.
- when(mCall.getHandlePresentation()).thenReturn(TelecomManager.PRESENTATION_ALLOWED);
- mFilter.startFilterLookup(mCall, mCallback);
- ArgumentCaptor<CallerInfoLookupHelper.OnQueryCompleteListener> captor =
- ArgumentCaptor.forClass(CallerInfoLookupHelper.OnQueryCompleteListener.class);
- verify(mCallerInfoLookupHelper).startLookup(eq(TEST_HANDLE), captor.capture());
- return captor.getValue();
- }
-
- private void waitOnLatch(CountDownLatch latch) {
- while (latch.getCount() > 0) {
- try {
- latch.await();
- } catch (InterruptedException e) {
- // do nothing
- }
- }
- }
-}
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 95ca3f3..6fd53eb 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -38,8 +38,6 @@
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
import android.os.Process;
import android.provider.BlockedNumberContract;
import android.telecom.Call;
@@ -59,7 +57,7 @@
import androidx.test.filters.FlakyTest;
import com.android.internal.telecom.IInCallAdapter;
-import com.android.internal.telephony.CallerInfo;
+import android.telecom.CallerInfo;
import com.google.common.base.Predicate;
@@ -105,8 +103,10 @@
mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
- assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
- assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+ assertEquals(Call.STATE_DISCONNECTING,
+ mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+ assertEquals(Call.STATE_DISCONNECTING,
+ mInCallServiceFixtureY.getCall(ids.mCallId).getState());
when(mClockProxy.currentTimeMillis()).thenReturn(TEST_DISCONNECT_TIME);
when(mClockProxy.elapsedRealtime()).thenReturn(TEST_DISCONNECT_ELAPSED_TIME);
@@ -261,12 +261,15 @@
IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
- assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
- assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+ assertEquals(Call.STATE_DISCONNECTING,
+ mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+ assertEquals(Call.STATE_DISCONNECTING,
+ mInCallServiceFixtureY.getCall(ids.mCallId).getState());
when(mClockProxy.currentTimeMillis()).thenReturn(TEST_DISCONNECT_TIME);
when(mClockProxy.elapsedRealtime()).thenReturn(TEST_DISCONNECT_ELAPSED_TIME);
mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
+
assertEquals(Call.STATE_DISCONNECTED,
mInCallServiceFixtureX.getCall(ids.mCallId).getState());
assertEquals(Call.STATE_DISCONNECTED,
@@ -306,7 +309,8 @@
mTelecomSystem.getTelecomServiceImpl().getBinder()
.addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras);
- waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+ TEST_TIMEOUT);
ArgumentCaptor<ConnectionRequest> connectionRequestCaptor
= ArgumentCaptor.forClass(ConnectionRequest.class);
verify(mConnectionServiceFixtureA.getTestDouble())
@@ -333,15 +337,18 @@
String callId = startOutgoingPhoneCallWithNoPhoneAccount("650-555-1212",
mConnectionServiceFixtureA);
mTelecomSystem.getCallsManager().getLatestPreAccountSelectionFuture().join();
- waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+ TEST_TIMEOUT);
assertEquals(Call.STATE_SELECT_PHONE_ACCOUNT,
mInCallServiceFixtureX.getCall(callId).getState());
assertEquals(Call.STATE_SELECT_PHONE_ACCOUNT,
mInCallServiceFixtureY.getCall(callId).getState());
mInCallServiceFixtureX.mInCallAdapter.phoneAccountSelected(callId,
mPhoneAccountA0.getAccountHandle(), false);
- waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
- waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+ TEST_TIMEOUT);
+ waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+ TEST_TIMEOUT);
verifyAndProcessOutgoingCallBroadcast(mPhoneAccountA0.getAccountHandle());
IdPair ids = outgoingCallPhoneAccountSelected(mPhoneAccountA0.getAccountHandle(),
@@ -356,6 +363,7 @@
mInCallServiceFixtureY.getCall(ids.mCallId).getState());
}
+ @FlakyTest
@LargeTest
@Test
public void testIncomingCallFromContactWithSendToVoicemailIsRejected() throws Exception {
@@ -366,17 +374,22 @@
mTelecomSystem.getTelecomServiceImpl().getBinder()
.addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras);
- waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+ TEST_TIMEOUT);
verify(mConnectionServiceFixtureA.getTestDouble())
.createConnection(any(PhoneAccountHandle.class), anyString(),
any(ConnectionRequest.class), eq(true), eq(false), any());
- waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+ TEST_TIMEOUT);
assertEquals(1, mCallerInfoAsyncQueryFactoryFixture.mRequests.size());
+
+ CallerInfo sendToVoicemailCallerInfo = new CallerInfo();
+ sendToVoicemailCallerInfo.shouldSendToVoicemail = true;
+ sendToVoicemailCallerInfo.contactExists = true;
+ mCallerInfoAsyncQueryFactoryFixture.setResponse(sendToVoicemailCallerInfo);
for (CallerInfoAsyncQueryFactoryFixture.Request request :
mCallerInfoAsyncQueryFactoryFixture.mRequests) {
- CallerInfo sendToVoicemailCallerInfo = new CallerInfo();
- sendToVoicemailCallerInfo.shouldSendToVoicemail = true;
request.replyWithCallerInfo(sendToVoicemailCallerInfo);
}
@@ -411,12 +424,14 @@
mTelecomSystem.getTelecomServiceImpl().getBinder()
.addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras);
- waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+ TEST_TIMEOUT);
verify(mConnectionServiceFixtureA.getTestDouble())
.createConnection(any(PhoneAccountHandle.class), anyString(),
any(ConnectionRequest.class), eq(true), eq(false), any());
- waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+ TEST_TIMEOUT);
// Never reply to the caller info lookup.
assertEquals(1, mCallerInfoAsyncQueryFactoryFixture.mRequests.size());
@@ -460,12 +475,14 @@
mTelecomSystem.getTelecomServiceImpl().getBinder()
.addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras);
- waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+ TEST_TIMEOUT);
verify(mConnectionServiceFixtureA.getTestDouble())
.createConnection(any(PhoneAccountHandle.class), anyString(),
any(ConnectionRequest.class), eq(true), eq(false), any());
- waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+ TEST_TIMEOUT);
assertEquals(1, mCallerInfoAsyncQueryFactoryFixture.mRequests.size());
for (CallerInfoAsyncQueryFactoryFixture.Request request :
mCallerInfoAsyncQueryFactoryFixture.mRequests) {
@@ -554,10 +571,12 @@
// TODO: We have to use the same PhoneAccount for both; see http://b/18461539
IdPair outgoing = startAndMakeActiveOutgoingCall("650-555-1212",
mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
- waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+ TEST_TIMEOUT);
IdPair incoming = startAndMakeActiveIncomingCall("650-555-2323",
mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
- waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+ TEST_TIMEOUT);
verify(mConnectionServiceFixtureA.getTestDouble())
.hold(eq(outgoing.mConnectionId), any());
mConnectionServiceFixtureA.mConnectionById.get(outgoing.mConnectionId).state =
@@ -601,7 +620,7 @@
waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
.getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
// setSpeakerPhoneOn(false) gets called once during the call initiation phase
- verify(audioManager, timeout(TEST_TIMEOUT).atLeast(2))
+ verify(audioManager, timeout(TEST_TIMEOUT).atLeast(1))
.setSpeakerphoneOn(false);
mConnectionServiceFixtureA.
@@ -786,7 +805,8 @@
@Override
public Bundle answer(InvocationOnMock invocation) throws Throwable {
Bundle bundle = new Bundle();
- bundle.putBoolean(BlockedNumberContract.RES_NUMBER_IS_BLOCKED, true);
+ bundle.putInt(BlockedNumberContract.RES_BLOCK_STATUS,
+ BlockedNumberContract.STATUS_BLOCKED_IN_LIST);
return bundle;
}
});
@@ -795,6 +815,8 @@
private void blockNumberWithAnswer(String phoneNumber, Answer answer) throws Exception {
when(getBlockedNumberProvider().call(
anyString(),
+ nullable(String.class),
+ anyString(),
eq(BlockedNumberContract.SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER),
eq(phoneNumber),
nullable(Bundle.class))).thenAnswer(answer);
@@ -1051,7 +1073,8 @@
// Route self-managed call to speaker.
connection.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
- waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+ TEST_TIMEOUT);
// Place an emergency call.
startAndMakeDialingEmergencyCall("650-555-1212", mPhoneAccountE0.getAccountHandle(),
diff --git a/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java b/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java
new file mode 100644
index 0000000..7df4f29
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2019 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.provider.BlockedNumberContract.STATUS_BLOCKED_IN_LIST;
+import static android.provider.BlockedNumberContract.STATUS_NOT_BLOCKED;
+
+import static junit.framework.TestCase.assertEquals;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.provider.CallLog;
+import android.telecom.CallerInfo;
+import android.telecom.TelecomManager;
+import android.telephony.CarrierConfigManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallerInfoLookupHelper;
+import com.android.server.telecom.callfiltering.BlockCheckerAdapter;
+import com.android.server.telecom.callfiltering.BlockCheckerFilter;
+import com.android.server.telecom.callfiltering.CallFilteringResult;
+
+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.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public class BlockCheckerFilterTest extends TelecomTestCase {
+ @Mock Call mCall;
+ @Mock private BlockCheckerAdapter mBlockCheckerAdapter;
+ @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
+ @Mock private CarrierConfigManager mCarrierConfigManager;
+
+ private BlockCheckerFilter mFilter;
+ private static final CallFilteringResult PASS_RESULT = new CallFilteringResult.Builder()
+ .setShouldAllowCall(true)
+ .setShouldReject(false)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(true)
+ .build();
+ private static final CallFilteringResult BLOCK_RESULT = new CallFilteringResult.Builder()
+ .setShouldAllowCall(false)
+ .setShouldReject(true)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(false)
+ .setCallBlockReason(CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER)
+ .setCallScreeningAppName(null)
+ .setCallScreeningComponentName(null)
+ .build();
+
+ private static final Uri TEST_HANDLE = Uri.parse("tel:1235551234");
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ when(mCall.getHandle()).thenReturn(TEST_HANDLE);
+ mFilter = new BlockCheckerFilter(mContext, mCall, mCallerInfoLookupHelper,
+ mBlockCheckerAdapter);
+ }
+
+ @SmallTest
+ @Test
+ public void testBlockNumber() throws Exception {
+ when(mBlockCheckerAdapter.getBlockStatus(any(Context.class),
+ eq(TEST_HANDLE.getSchemeSpecificPart()), any(Bundle.class)))
+ .thenReturn(STATUS_BLOCKED_IN_LIST);
+
+ setEnhancedBlockingEnabled(false);
+ assertEquals(BLOCK_RESULT, mFilter.startFilterLookup(PASS_RESULT).toCompletableFuture()
+ .get(BlockCheckerFilter.CALLER_INFO_QUERY_TIMEOUT, TimeUnit.MILLISECONDS));
+ }
+
+ @SmallTest
+ @Test
+ public void testBlockNumberWhenEnhancedBlockingEnabled() throws Exception {
+ when(mBlockCheckerAdapter.getBlockStatus(any(Context.class),
+ eq(TEST_HANDLE.getSchemeSpecificPart()), any(Bundle.class)))
+ .thenReturn(STATUS_BLOCKED_IN_LIST);
+
+ setEnhancedBlockingEnabled(true);
+ CompletionStage<CallFilteringResult> resultFuture = mFilter.startFilterLookup(PASS_RESULT);
+ verifyEnhancedLookupStart();
+ assertEquals(BLOCK_RESULT, resultFuture.toCompletableFuture()
+ .get(BlockCheckerFilter.CALLER_INFO_QUERY_TIMEOUT, TimeUnit.MILLISECONDS));
+ }
+
+ @SmallTest
+ @Test
+ public void testDontBlockNumber() throws Exception {
+ when(mBlockCheckerAdapter.getBlockStatus(any(Context.class),
+ eq(TEST_HANDLE.getSchemeSpecificPart()), any(Bundle.class)))
+ .thenReturn(STATUS_NOT_BLOCKED);
+
+ setEnhancedBlockingEnabled(false);
+ assertEquals(PASS_RESULT, mFilter.startFilterLookup(PASS_RESULT).toCompletableFuture()
+ .get(BlockCheckerFilter.CALLER_INFO_QUERY_TIMEOUT, TimeUnit.MILLISECONDS));
+ }
+
+ @SmallTest
+ @Test
+ public void testDontBlockNumberWhenEnhancedBlockingEnabled() throws Exception {
+ when(mBlockCheckerAdapter.getBlockStatus(any(Context.class),
+ eq(TEST_HANDLE.getSchemeSpecificPart()), any(Bundle.class)))
+ .thenReturn(STATUS_NOT_BLOCKED);
+
+ setEnhancedBlockingEnabled(true);
+ CompletionStage<CallFilteringResult> resultFuture = mFilter.startFilterLookup(PASS_RESULT);
+ verifyEnhancedLookupStart();
+ assertEquals(PASS_RESULT, resultFuture.toCompletableFuture()
+ .get(BlockCheckerFilter.CALLER_INFO_QUERY_TIMEOUT, TimeUnit.MILLISECONDS));
+ }
+
+ private void setEnhancedBlockingEnabled(boolean value) {
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putBoolean(CarrierConfigManager.KEY_SUPPORT_ENHANCED_CALL_BLOCKING_BOOL, value);
+ when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
+ .thenReturn(mCarrierConfigManager);
+ when(mCarrierConfigManager.getConfig()).thenReturn(bundle);
+ if (value) {
+ when(mCall.getHandlePresentation()).thenReturn(TelecomManager.PRESENTATION_ALLOWED);
+ }
+ }
+
+ private void verifyEnhancedLookupStart() {
+ ArgumentCaptor<CallerInfoLookupHelper.OnQueryCompleteListener> captor =
+ ArgumentCaptor.forClass(CallerInfoLookupHelper.OnQueryCompleteListener.class);
+ verify(mCallerInfoLookupHelper, timeout(BlockCheckerFilter.CALLER_INFO_QUERY_TIMEOUT))
+ .startLookup(eq(TEST_HANDLE), captor.capture());
+ captor.getValue().onCallerInfoQueryComplete(TEST_HANDLE, null);
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
index 2129ffa..bfa7a75 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
@@ -16,6 +16,7 @@
package com.android.server.telecom.tests;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
@@ -31,6 +32,7 @@
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -58,7 +60,7 @@
BluetoothDeviceManager mBluetoothDeviceManager;
BluetoothProfile.ServiceListener serviceListenerUnderTest;
- BroadcastReceiver receiverUnderTest;
+ BluetoothStateReceiver receiverUnderTest;
private BluetoothDevice device1;
private BluetoothDevice device2;
@@ -89,13 +91,18 @@
serviceCaptor.capture(), eq(BluetoothProfile.HEADSET));
serviceListenerUnderTest = serviceCaptor.getValue();
- receiverUnderTest = new BluetoothStateReceiver(mBluetoothDeviceManager,
- null /* route mgr not needed here */);
+ receiverUnderTest = new BluetoothStateReceiver(mBluetoothDeviceManager, mRouteManager);
mBluetoothDeviceManager.setHeadsetServiceForTesting(mHeadsetProxy);
mBluetoothDeviceManager.setHearingAidServiceForTesting(mBluetoothHearingAid);
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
@SmallTest
@Test
public void testSingleDeviceConnectAndDisconnect() {
@@ -191,15 +198,28 @@
@SmallTest
@Test
+ public void testHearingAidChangesIgnoredWhenNotInCall() {
+ receiverUnderTest.setIsInCall(false);
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true));
+ Intent activeDeviceChangedIntent =
+ new Intent(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
+ activeDeviceChangedIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device2);
+ receiverUnderTest.onReceive(mContext, activeDeviceChangedIntent);
+
+ verify(mRouteManager).onActiveDeviceChanged(device2, true);
+ verify(mRouteManager, never()).sendMessage(BluetoothRouteManager.BT_AUDIO_IS_ON);
+ }
+
+ @SmallTest
+ @Test
public void testConnectDisconnectAudioHeadset() {
receiverUnderTest.onReceive(mContext,
buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
- when(mHeadsetProxy.setActiveDevice(nullable(BluetoothDevice.class))).thenReturn(true);
+ when(mAdapterProxy.setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
mBluetoothDeviceManager.connectAudio(device1.getAddress());
- verify(mHeadsetProxy).setActiveDevice(device1);
- verify(mHeadsetProxy).connectAudio();
- verify(mBluetoothHearingAid, never()).setActiveDevice(nullable(BluetoothDevice.class));
-
+ verify(mAdapterProxy).setActiveDevice(device1, BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL);
+ verify(mAdapterProxy, never()).setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_ALL));
mBluetoothDeviceManager.disconnectAudio();
verify(mHeadsetProxy).disconnectAudio();
}
@@ -210,14 +230,14 @@
receiverUnderTest.onReceive(mContext,
buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true));
mBluetoothDeviceManager.connectAudio(device2.getAddress());
- verify(mBluetoothHearingAid).setActiveDevice(device2);
+ verify(mAdapterProxy).setActiveDevice(device2, BluetoothAdapter.ACTIVE_DEVICE_ALL);
verify(mHeadsetProxy, never()).connectAudio();
- verify(mHeadsetProxy, never()).setActiveDevice(nullable(BluetoothDevice.class));
+ verify(mAdapterProxy, never()).setActiveDevice(nullable(BluetoothDevice.class), eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
when(mBluetoothHearingAid.getActiveDevices()).thenReturn(Arrays.asList(device2, null));
mBluetoothDeviceManager.disconnectAudio();
- verify(mBluetoothHearingAid).setActiveDevice(null);
+ verify(mAdapterProxy).setActiveDevice(null, BluetoothAdapter.ACTIVE_DEVICE_ALL);
verify(mHeadsetProxy).disconnectAudio();
}
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java b/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
index 882f437..532cc7e 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
@@ -113,7 +113,7 @@
doNothing().when(mMockCallsManager).addListener(any(
CallsManager.CallsManagerListener.class));
doReturn(null).when(mMockCallsManager).getActiveCall();
- doReturn(null).when(mMockCallsManager).getRingingCall();
+ doReturn(null).when(mMockCallsManager).getRingingOrSimulatedRingingCall();
doReturn(null).when(mMockCallsManager).getHeldCall();
doReturn(null).when(mMockCallsManager).getOutgoingCall();
doReturn(0).when(mMockCallsManager).getNumHeldCalls();
@@ -147,7 +147,7 @@
@SmallTest
@Test
public void testHeadsetAnswerCallNull() throws Exception {
- when(mMockCallsManager.getRingingCall()).thenReturn(null);
+ when(mMockCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(null);
boolean callAnswered = mBluetoothPhoneService.mBinder.answerCall();
@@ -246,7 +246,8 @@
nullable(PhoneAccountHandle.class))).thenReturn(null);
when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(
nullable(PhoneAccountHandle.class))).thenReturn(null);
- when(TelephonyManager.from(mContext).getLine1Number()).thenReturn(fakeNumber);
+ when(mComponentContextFixture.getTelephonyManager().getLine1Number())
+ .thenReturn(fakeNumber);
String subscriberNumber = mBluetoothPhoneService.mBinder.getSubscriberNumber();
@@ -282,7 +283,7 @@
when(silentRingingCall.isConference()).thenReturn(false);
when(silentRingingCall.getHandle()).thenReturn(Uri.parse("tel:555-000"));
when(mMockCallsManager.getCalls()).thenReturn(calls);
- when(mMockCallsManager.getRingingCall()).thenReturn(silentRingingCall);
+ when(mMockCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(silentRingingCall);
mBluetoothPhoneService.mBinder.listCurrentCalls();
@@ -819,8 +820,8 @@
mBluetoothPhoneService.mCallsManagerListener.onCallAdded(ringingCall);
- verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_IDLE),
- eq(""), eq(128), nullable(String.class));
+ verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
+ anyString(), anyInt(), nullable(String.class));
}
@MediumTest
@@ -876,6 +877,64 @@
@MediumTest
@Test
+ public void testOnCallAddedAudioProcessing() throws Exception {
+ Call call = mock(Call.class);
+ when(call.getState()).thenReturn(CallState.AUDIO_PROCESSING);
+ mBluetoothPhoneService.mCallsManagerListener.onCallAdded(call);
+
+ verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
+ anyString(), anyInt(), nullable(String.class));
+ }
+
+ @MediumTest
+ @Test
+ public void testOnCallStateChangedRingingToAudioProcessing() throws Exception {
+ Call ringingCall = createRingingCall();
+ when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:555000"));
+
+ mBluetoothPhoneService.mCallsManagerListener.onCallAdded(ringingCall);
+
+ verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
+ eq("555000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
+
+ when(ringingCall.getState()).thenReturn(CallState.AUDIO_PROCESSING);
+ when(mMockCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(null);
+
+ mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(ringingCall,
+ CallState.RINGING, CallState.AUDIO_PROCESSING);
+
+ verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_IDLE),
+ eq(""), eq(128), nullable(String.class));
+ }
+
+ @MediumTest
+ @Test
+ public void testOnCallStateChangedAudioProcessingToSimulatedRinging() throws Exception {
+ Call ringingCall = createRingingCall();
+ when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:555-0000"));
+
+ mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(ringingCall,
+ CallState.AUDIO_PROCESSING, CallState.SIMULATED_RINGING);
+
+ verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
+ eq("555-0000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
+ }
+
+ @MediumTest
+ @Test
+ public void testOnCallStateChangedAudioProcessingToActive() throws Exception {
+ Call activeCall = createActiveCall();
+ when(activeCall.getState()).thenReturn(CallState.ACTIVE);
+
+ mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(activeCall,
+ CallState.AUDIO_PROCESSING, CallState.ACTIVE);
+
+ verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(0), eq(CALL_STATE_IDLE),
+ eq(""), eq(128), nullable(String.class));
+ }
+
+ @MediumTest
+ @Test
public void testOnCallStateChangedDialing() throws Exception {
Call activeCall = createActiveCall();
@@ -931,7 +990,7 @@
eq("555-0000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
//Switch to active
- doReturn(null).when(mMockCallsManager).getRingingCall();
+ doReturn(null).when(mMockCallsManager).getRingingOrSimulatedRingingCall();
when(mMockCallsManager.getActiveCall()).thenReturn(ringingCall);
mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(ringingCall,
@@ -1021,7 +1080,7 @@
private Call createRingingCall() {
Call call = mock(Call.class);
- when(mMockCallsManager.getRingingCall()).thenReturn(call);
+ when(mMockCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(call);
return call;
}
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
index 93c2909..b20ecfb 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
@@ -31,6 +31,7 @@
import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -71,6 +72,12 @@
super.setUp();
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
@SmallTest
@Test
public void testConnectHfpRetryWhileNotConnected() {
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
index 2584f33..b36d74b 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
@@ -30,6 +30,7 @@
import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -246,6 +247,12 @@
super.setUp();
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
public BluetoothRouteTransitionTests(BluetoothRouteTestParameters params) {
mParams = params;
}
@@ -339,6 +346,8 @@
when(mDeviceManager.getConnectedDevices()).thenReturn(Arrays.asList(devices));
when(mHeadsetProxy.getConnectedDevices()).thenReturn(Arrays.asList(devices));
when(mHeadsetProxy.getActiveDevice()).thenReturn(activeDevice);
+ when(mHeadsetProxy.getAudioState(nullable(BluetoothDevice.class)))
+ .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
if (audioOnDevice != null) {
when(mHeadsetProxy.getActiveDevice()).thenReturn(audioOnDevice);
when(mHeadsetProxy.getAudioState(audioOnDevice))
@@ -627,6 +636,22 @@
.setExpectedBluetoothInteraction(NONE)
.setExpectedFinalStateName(BluetoothRouteManager.AUDIO_OFF_STATE_NAME)
.build());
+
+ result.add(new BluetoothRouteTestParametersBuilder()
+ .setName("connect BT to an already active device when in audio off.")
+ .setInitialBluetoothState(BluetoothRouteManager.AUDIO_OFF_STATE_NAME)
+ .setAudioOnDevice(DEVICE2)
+ .setActiveDevice(DEVICE2)
+ .setConnectedDevices(DEVICE2, DEVICE3)
+ .setHearingAidBtDevices(Collections.singletonList(DEVICE2))
+ .setMessageType(BluetoothRouteManager.CONNECT_HFP)
+ .setMessageDevice(DEVICE2)
+ .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
+ .setExpectedBluetoothInteraction(NONE)
+ .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
+ + ":" + DEVICE2)
+ .build());
+
return result;
}
}
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
index 3f70453..7af29aa 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
@@ -19,21 +19,25 @@
import android.media.ToneGenerator;
import android.telecom.DisconnectCause;
import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
import android.util.SparseArray;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallAudioModeStateMachine;
+import com.android.server.telecom.CallAudioModeStateMachine.MessageArgs;
import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.DtmfLocalTonePlayer;
import com.android.server.telecom.InCallTonePlayer;
+import com.android.server.telecom.CallAudioModeStateMachine.MessageArgs.Builder;
import com.android.server.telecom.RingbackPlayer;
import com.android.server.telecom.Ringer;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -46,13 +50,16 @@
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -95,17 +102,22 @@
mDtmfLocalTonePlayer);
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
@MediumTest
@Test
public void testUnmuteOfSecondIncomingCall() {
// Start with a single incoming call.
Call call = createIncomingCall();
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
when(call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO))
.thenReturn(false);
when(call.getId()).thenReturn("1");
- ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
- ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
// Answer the incoming call
mCallAudioManager.onIncomingCallAnswered(call);
when(call.getState()).thenReturn(CallState.ACTIVE);
@@ -113,14 +125,15 @@
verify(mCallAudioModeStateMachine).sendMessageWithArgs(
eq(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS), captor.capture());
CallAudioModeStateMachine.MessageArgs correctArgs =
- new CallAudioModeStateMachine.MessageArgs(
- true, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- );
+ new Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setHasAudioProcessingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build();
assertMessageArgEquality(correctArgs, captor.getValue());
verify(mCallAudioModeStateMachine).sendMessageWithArgs(
eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
@@ -163,11 +176,10 @@
@Test
public void testSingleIncomingCallFlowWithoutMTSpeedUp() {
Call call = createIncomingCall();
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
when(call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO))
.thenReturn(false);
- ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
- ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
// Answer the incoming call
mCallAudioManager.onIncomingCallAnswered(call);
when(call.getState()).thenReturn(CallState.ACTIVE);
@@ -175,14 +187,15 @@
verify(mCallAudioModeStateMachine).sendMessageWithArgs(
eq(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS), captor.capture());
CallAudioModeStateMachine.MessageArgs correctArgs =
- new CallAudioModeStateMachine.MessageArgs(
- true, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- );
+ new Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setHasAudioProcessingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build();
assertMessageArgEquality(correctArgs, captor.getValue());
verify(mCallAudioModeStateMachine).sendMessageWithArgs(
eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
@@ -199,12 +212,11 @@
@Test
public void testSingleIncomingCallFlowWithMTSpeedUp() {
Call call = createIncomingCall();
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
when(call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO))
.thenReturn(true);
when(call.getState()).thenReturn(CallState.ANSWERED);
- ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
- ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
// Answer the incoming call
mCallAudioManager.onCallStateChanged(call, CallState.RINGING, CallState.ANSWERED);
verify(mCallAudioModeStateMachine).sendMessageWithArgs(
@@ -212,14 +224,15 @@
verify(mCallAudioModeStateMachine).sendMessageWithArgs(
eq(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS), captor.capture());
CallAudioModeStateMachine.MessageArgs correctArgs =
- new CallAudioModeStateMachine.MessageArgs(
- true, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- );
+ new Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setHasAudioProcessingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build();
assertMessageArgEquality(correctArgs, captor.getValue());
assertMessageArgEquality(correctArgs, captor.getValue());
when(call.getState()).thenReturn(CallState.ACTIVE);
@@ -236,25 +249,25 @@
@Test
public void testSingleOutgoingCall() {
Call call = mock(Call.class);
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
when(call.getState()).thenReturn(CallState.CONNECTING);
mCallAudioManager.onCallAdded(call);
assertEquals(call, mCallAudioManager.getForegroundCall());
- ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
- ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
verify(mCallAudioModeStateMachine).sendMessageWithArgs(
eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
CallAudioModeStateMachine.MessageArgs expectedArgs =
- new CallAudioModeStateMachine.MessageArgs(
- true, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- );
+ new Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setHasAudioProcessingCalls(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build();
assertMessageArgEquality(expectedArgs, captor.getValue());
when(call.getState()).thenReturn(CallState.DIALING);
@@ -281,6 +294,347 @@
verifyProperCleanup();
}
+ @SmallTest
+ @Test
+ public void testNewCallGoesToAudioProcessing() {
+ Call call = mock(Call.class);
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
+ when(call.getState()).thenReturn(CallState.NEW);
+
+ // Make sure nothing happens when we add the NEW call
+ mCallAudioManager.onCallAdded(call);
+
+ verify(mCallAudioRouteStateMachine, never()).sendMessageWithSessionInfo(anyInt());
+ verify(mCallAudioModeStateMachine, never()).sendMessageWithArgs(
+ anyInt(), nullable(MessageArgs.class));
+
+ // Transition the call to AUDIO_PROCESSING and see what happens
+ when(call.getState()).thenReturn(CallState.AUDIO_PROCESSING);
+ mCallAudioManager.onCallStateChanged(call, CallState.NEW, CallState.AUDIO_PROCESSING);
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_AUDIO_PROCESSING_CALL), captor.capture());
+
+ CallAudioModeStateMachine.MessageArgs expectedArgs =
+ new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setHasAudioProcessingCalls(true)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build();
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+ }
+
+ @SmallTest
+ @Test
+ public void testRingingCallGoesToAudioProcessing() {
+ Call call = mock(Call.class);
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
+ when(call.getState()).thenReturn(CallState.RINGING);
+
+ // Make sure appropriate messages are sent when we add a RINGING call
+ mCallAudioManager.onCallAdded(call);
+
+ assertEquals(call, mCallAudioManager.getForegroundCall());
+ verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_RINGING_CALL), captor.capture());
+ CallAudioModeStateMachine.MessageArgs expectedArgs =
+ new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(true)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setHasAudioProcessingCalls(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build();
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+
+ // Transition the call to AUDIO_PROCESSING and see what happens
+ when(call.getState()).thenReturn(CallState.AUDIO_PROCESSING);
+ mCallAudioManager.onCallStateChanged(call, CallState.RINGING, CallState.AUDIO_PROCESSING);
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_AUDIO_PROCESSING_CALL), captor.capture());
+
+ CallAudioModeStateMachine.MessageArgs expectedArgs2 =
+ new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setHasAudioProcessingCalls(true)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build();
+ assertMessageArgEquality(expectedArgs2, captor.getValue());
+
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS), captor.capture());
+ assertMessageArgEquality(expectedArgs2, captor.getValue());
+ }
+
+ @SmallTest
+ @Test
+ public void testActiveCallGoesToAudioProcessing() {
+ Call call = mock(Call.class);
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
+ when(call.getState()).thenReturn(CallState.ACTIVE);
+
+ // Make sure appropriate messages are sent when we add an active call
+ mCallAudioManager.onCallAdded(call);
+
+ assertEquals(call, mCallAudioManager.getForegroundCall());
+ verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+ CallAudioModeStateMachine.MessageArgs expectedArgs =
+ new Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setHasAudioProcessingCalls(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build();
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+
+ // Transition the call to AUDIO_PROCESSING and see what happens
+ when(call.getState()).thenReturn(CallState.AUDIO_PROCESSING);
+ mCallAudioManager.onCallStateChanged(call, CallState.ACTIVE, CallState.AUDIO_PROCESSING);
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_AUDIO_PROCESSING_CALL), captor.capture());
+
+ CallAudioModeStateMachine.MessageArgs expectedArgs2 =
+ new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setHasAudioProcessingCalls(true)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build();
+ assertMessageArgEquality(expectedArgs2, captor.getValue());
+
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS), captor.capture());
+ assertMessageArgEquality(expectedArgs2, captor.getValue());
+ }
+
+ @SmallTest
+ @Test
+ public void testAudioProcessingCallDisconnects() {
+ Call call = createAudioProcessingCall();
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
+
+ when(call.getState()).thenReturn(CallState.DISCONNECTED);
+ when(call.getDisconnectCause()).thenReturn(new DisconnectCause(DisconnectCause.LOCAL,
+ "", "", "", ToneGenerator.TONE_UNKNOWN));
+
+ mCallAudioManager.onCallStateChanged(call, CallState.AUDIO_PROCESSING,
+ CallState.DISCONNECTED);
+ verify(mPlayerFactory, never()).createPlayer(anyInt());
+ CallAudioModeStateMachine.MessageArgs expectedArgs2 = new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setHasAudioProcessingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build();
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NO_MORE_AUDIO_PROCESSING_CALLS), captor.capture());
+ assertMessageArgEquality(expectedArgs2, captor.getValue());
+ verify(mCallAudioModeStateMachine, never()).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.TONE_STARTED_PLAYING), nullable(MessageArgs.class));
+
+ mCallAudioManager.onCallRemoved(call);
+ verifyProperCleanup();
+ }
+
+ @SmallTest
+ @Test
+ public void testAudioProcessingCallDoesSimulatedRing() {
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
+
+ Call call = createAudioProcessingCall();
+
+ when(call.getState()).thenReturn(CallState.SIMULATED_RINGING);
+
+ mCallAudioManager.onCallStateChanged(call, CallState.AUDIO_PROCESSING,
+ CallState.SIMULATED_RINGING);
+ verify(mPlayerFactory, never()).createPlayer(anyInt());
+ CallAudioModeStateMachine.MessageArgs expectedArgs = new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(true)
+ .setHasHoldingCalls(false)
+ .setHasAudioProcessingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build();
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NO_MORE_AUDIO_PROCESSING_CALLS), captor.capture());
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_RINGING_CALL), captor.capture());
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+ }
+
+ @SmallTest
+ @Test
+ public void testAudioProcessingCallGoesActive() {
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
+
+ Call call = createAudioProcessingCall();
+
+ when(call.getState()).thenReturn(CallState.ACTIVE);
+
+ mCallAudioManager.onCallStateChanged(call, CallState.AUDIO_PROCESSING,
+ CallState.ACTIVE);
+ verify(mPlayerFactory, never()).createPlayer(anyInt());
+ CallAudioModeStateMachine.MessageArgs expectedArgs = new Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setHasAudioProcessingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build();
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NO_MORE_AUDIO_PROCESSING_CALLS), captor.capture());
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+ }
+
+ @SmallTest
+ @Test
+ public void testSimulatedRingingCallGoesActive() {
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
+
+ Call call = createSimulatedRingingCall();
+
+ when(call.getState()).thenReturn(CallState.ACTIVE);
+
+ mCallAudioManager.onCallStateChanged(call, CallState.SIMULATED_RINGING,
+ CallState.ACTIVE);
+ verify(mPlayerFactory, never()).createPlayer(anyInt());
+ CallAudioModeStateMachine.MessageArgs expectedArgs = new Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setHasAudioProcessingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build();
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS), captor.capture());
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+ }
+
+ private Call createAudioProcessingCall() {
+ Call call = mock(Call.class);
+ when(call.getState()).thenReturn(CallState.AUDIO_PROCESSING);
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
+
+ // Set up an AUDIO_PROCESSING call
+ mCallAudioManager.onCallAdded(call);
+
+ assertNull(mCallAudioManager.getForegroundCall());
+
+ verify(mCallAudioRouteStateMachine, never()).sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_AUDIO_PROCESSING_CALL), captor.capture());
+ CallAudioModeStateMachine.MessageArgs expectedArgs =
+ new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setHasAudioProcessingCalls(true)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build();
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+
+ return call;
+ }
+
+ @SmallTest
+ @Test
+ public void testSimulatedRingingCallDisconnects() {
+ Call call = createSimulatedRingingCall();
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
+
+ when(call.getState()).thenReturn(CallState.DISCONNECTED);
+ when(call.getDisconnectCause()).thenReturn(new DisconnectCause(DisconnectCause.LOCAL,
+ "", "", "", ToneGenerator.TONE_UNKNOWN));
+
+ mCallAudioManager.onCallStateChanged(call, CallState.SIMULATED_RINGING,
+ CallState.DISCONNECTED);
+ verify(mPlayerFactory, never()).createPlayer(anyInt());
+ CallAudioModeStateMachine.MessageArgs expectedArgs2 = new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setHasAudioProcessingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build();
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS), captor.capture());
+ assertMessageArgEquality(expectedArgs2, captor.getValue());
+ verify(mCallAudioModeStateMachine, never()).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.TONE_STARTED_PLAYING), nullable(MessageArgs.class));
+
+ mCallAudioManager.onCallRemoved(call);
+ verifyProperCleanup();
+ }
+
+ private Call createSimulatedRingingCall() {
+ Call call = mock(Call.class);
+ when(call.getState()).thenReturn(CallState.SIMULATED_RINGING);
+ ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
+
+ mCallAudioManager.onCallAdded(call);
+
+ assertEquals(call, mCallAudioManager.getForegroundCall());
+
+ verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NEW_RINGING_CALL), captor.capture());
+ CallAudioModeStateMachine.MessageArgs expectedArgs =
+ new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(true)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setHasAudioProcessingCalls(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build();
+ assertMessageArgEquality(expectedArgs, captor.getValue());
+
+ return call;
+ }
+
private Call createIncomingCall() {
Call call = mock(Call.class);
when(call.getState()).thenReturn(CallState.RINGING);
@@ -293,14 +647,15 @@
CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
verify(mCallAudioModeStateMachine).sendMessageWithArgs(
eq(CallAudioModeStateMachine.NEW_RINGING_CALL), captor.capture());
- assertMessageArgEquality(new CallAudioModeStateMachine.MessageArgs(
- false, // hasActiveOrDialingCalls
- true, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ), captor.getValue());
+ assertMessageArgEquality(new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(true)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
+ captor.getValue());
return call;
}
@@ -316,14 +671,14 @@
mCallAudioManager.onCallStateChanged(call, CallState.ACTIVE, CallState.DISCONNECTED);
verify(mPlayerFactory).createPlayer(InCallTonePlayer.TONE_CALL_ENDED);
- correctArgs = new CallAudioModeStateMachine.MessageArgs(
- false, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- true, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- );
+ correctArgs = new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(true)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build();
verify(mCallAudioModeStateMachine).sendMessageWithArgs(
eq(CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS), captor.capture());
assertMessageArgEquality(correctArgs, captor.getValue());
@@ -336,15 +691,14 @@
ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
mCallAudioManager.setIsTonePlaying(false);
- CallAudioModeStateMachine.MessageArgs correctArgs =
- new CallAudioModeStateMachine.MessageArgs(
- false, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- );
+ CallAudioModeStateMachine.MessageArgs correctArgs = new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build();
verify(mCallAudioModeStateMachine).sendMessageWithArgs(
eq(CallAudioModeStateMachine.TONE_STOPPED_PLAYING), captor.capture());
assertMessageArgEquality(correctArgs, captor.getValue());
@@ -358,6 +712,10 @@
}
}
+ private ArgumentCaptor<MessageArgs> makeNewCaptor() {
+ return ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
+ }
+
private void assertMessageArgEquality(CallAudioModeStateMachine.MessageArgs expected,
CallAudioModeStateMachine.MessageArgs actual) {
assertEquals(expected.hasActiveOrDialingCalls, actual.hasActiveOrDialingCalls);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
index 56f585f..38f58fd 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
@@ -17,13 +17,16 @@
package com.android.server.telecom.tests;
import android.media.AudioManager;
+import android.os.HandlerThread;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioModeStateMachine;
import com.android.server.telecom.CallAudioRouteStateMachine;
+import com.android.server.telecom.CallAudioModeStateMachine.MessageArgs.Builder;
import com.android.server.telecom.SystemStateHelper;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -47,17 +50,29 @@
@Mock private AudioManager mAudioManager;
@Mock private CallAudioManager mCallAudioManager;
+ private HandlerThread mTestThread;
+
@Override
@Before
public void setUp() throws Exception {
+ mTestThread = new HandlerThread("CallAudioModeStateMachineTest");
+ mTestThread.start();
super.setUp();
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ mTestThread.quit();
+ mTestThread.join();
+ super.tearDown();
+ }
+
@SmallTest
@Test
public void testNoFocusWhenRingerSilenced() throws Throwable {
CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
- mAudioManager);
+ mAudioManager, mTestThread.getLooper());
sm.setCallAudioManager(mCallAudioManager);
sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -65,15 +80,14 @@
resetMocks();
when(mCallAudioManager.startRinging()).thenReturn(false);
- sm.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL,
- new CallAudioModeStateMachine.MessageArgs(
- false, // hasActiveOrDialingCalls
- true, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ));
+ sm.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL, new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(true)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build());
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
assertEquals(CallAudioModeStateMachine.RING_STATE_NAME, sm.getCurrentStateName());
@@ -90,32 +104,30 @@
@Test
public void testNoRingWhenDeviceIsAtEar() {
CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
- mAudioManager);
+ mAudioManager, mTestThread.getLooper());
sm.setCallAudioManager(mCallAudioManager);
sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
- sm.sendMessage(CallAudioModeStateMachine.NEW_HOLDING_CALL,
- new CallAudioModeStateMachine.MessageArgs(
- false, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- true, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ));
+ sm.sendMessage(CallAudioModeStateMachine.NEW_HOLDING_CALL, new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(true)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build());
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
assertEquals(CallAudioModeStateMachine.TONE_HOLD_STATE_NAME, sm.getCurrentStateName());
when(mSystemStateHelper.isDeviceAtEar()).thenReturn(true);
resetMocks();
- sm.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL,
- new CallAudioModeStateMachine.MessageArgs(
- false, // hasActiveOrDialingCalls
- true, // hasRingingCalls
- true, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ));
+ sm.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL, new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(true)
+ .setHasHoldingCalls(true)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build());
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
verify(mAudioManager, never()).requestAudioFocusForCall(anyInt(), anyInt());
@@ -128,7 +140,7 @@
@Test
public void testRegainFocusWhenHfpIsConnectedSilenced() throws Throwable {
CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
- mAudioManager);
+ mAudioManager, mTestThread.getLooper());
sm.setCallAudioManager(mCallAudioManager);
sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -136,15 +148,14 @@
resetMocks();
when(mCallAudioManager.startRinging()).thenReturn(false);
- sm.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL,
- new CallAudioModeStateMachine.MessageArgs(
- false, // hasActiveOrDialingCalls
- true, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ));
+ sm.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL, new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(true)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build());
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
assertEquals(CallAudioModeStateMachine.RING_STATE_NAME, sm.getCurrentStateName());
@@ -169,6 +180,81 @@
CallAudioRouteStateMachine.RINGING_FOCUS);
}
+ @SmallTest
+ @Test
+ public void testDoNotRingTwiceWhenHfpConnected() {
+ CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
+ mAudioManager, mTestThread.getLooper());
+ sm.setCallAudioManager(mCallAudioManager);
+ sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+ resetMocks();
+ when(mCallAudioManager.startRinging()).thenReturn(true);
+
+ sm.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL, new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(true)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build());
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+ assertEquals(CallAudioModeStateMachine.RING_STATE_NAME, sm.getCurrentStateName());
+
+ verify(mAudioManager).requestAudioFocusForCall(AudioManager.STREAM_RING,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ verify(mAudioManager).setMode(AudioManager.MODE_RINGTONE);
+ verify(mCallAudioManager).setCallAudioRouteFocusState(
+ CallAudioRouteStateMachine.RINGING_FOCUS);
+
+ when(mCallAudioManager.isRingtonePlaying()).thenReturn(true);
+ sm.sendMessage(CallAudioModeStateMachine.RINGER_MODE_CHANGE);
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+ // Make sure we don't try and start ringing again.
+ verify(mCallAudioManager, times(1)).startRinging();
+ }
+
+ @SmallTest
+ @Test
+ public void testStartRingingAfterHfpConnectedIfNotAlreadyPlaying() {
+ CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
+ mAudioManager, mTestThread.getLooper());
+ sm.setCallAudioManager(mCallAudioManager);
+ sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+ resetMocks();
+ when(mCallAudioManager.startRinging()).thenReturn(true);
+
+ sm.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL, new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(true)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build());
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+ assertEquals(CallAudioModeStateMachine.RING_STATE_NAME, sm.getCurrentStateName());
+
+ verify(mAudioManager).requestAudioFocusForCall(AudioManager.STREAM_RING,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ verify(mAudioManager).setMode(AudioManager.MODE_RINGTONE);
+ verify(mCallAudioManager).setCallAudioRouteFocusState(
+ CallAudioRouteStateMachine.RINGING_FOCUS);
+
+ when(mCallAudioManager.isRingtonePlaying()).thenReturn(false);
+ sm.sendMessage(CallAudioModeStateMachine.RINGER_MODE_CHANGE);
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+ // Make sure we do try and start ringing again, since the ringtone wasn't already playing.
+ verify(mCallAudioManager, times(2)).startRinging();
+ }
private void resetMocks() {
clearInvocations(mCallAudioManager, mAudioManager);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java
index ff5986e..c7e5aa9 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java
@@ -16,14 +16,16 @@
package com.android.server.telecom.tests;
-import android.content.Context;
import android.media.AudioManager;
+import android.os.HandlerThread;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioModeStateMachine;
+import com.android.server.telecom.CallAudioModeStateMachine.MessageArgs;
import com.android.server.telecom.SystemStateHelper;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -102,13 +104,24 @@
@Mock private AudioManager mAudioManager;
@Mock private CallAudioManager mCallAudioManager;
private final ModeTestParameters mParams;
+ private HandlerThread mTestThread;
@Override
@Before
public void setUp() throws Exception {
+ mTestThread = new HandlerThread("CallAudioModeStateMachineTest");
+ mTestThread.start();
super.setUp();
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ mTestThread.quit();
+ mTestThread.join();
+ super.tearDown();
+ }
+
public CallAudioModeTransitionTests(ModeTestParameters params) {
mParams = params;
}
@@ -117,16 +130,27 @@
@SmallTest
public void modeTransitionTest() {
CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
- mAudioManager);
+ mAudioManager, mTestThread.getLooper());
sm.setCallAudioManager(mCallAudioManager);
sm.sendMessage(mParams.initialAudioState);
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
resetMocks();
when(mCallAudioManager.startRinging()).thenReturn(true);
+ if (mParams.initialAudioState
+ == CallAudioModeStateMachine.ENTER_AUDIO_PROCESSING_FOCUS_FOR_TESTING) {
+ when(mAudioManager.getMode())
+ .thenReturn(CallAudioModeStateMachine.NEW_AUDIO_MODE_FOR_AUDIO_PROCESSING);
+ }
sm.sendMessage(mParams.messageType, mParams.externalState);
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+ if (mParams.expectedFocus == FOCUS_OFF
+ && mParams.messageType != CallAudioModeStateMachine.AUDIO_OPERATIONS_COMPLETE) {
+ // If we expect the focus to turn off, we need to signal operations complete first
+ sm.sendMessage(CallAudioModeStateMachine.AUDIO_OPERATIONS_COMPLETE);
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+ }
assertEquals(mParams.expectedFinalStateName, sm.getCurrentStateName());
@@ -178,8 +202,6 @@
verify(mCallAudioManager).stopCallWaiting();
break;
}
-
- sm.quitNow();
}
@Parameterized.Parameters(name = "{0}")
@@ -189,14 +211,14 @@
"New active/dialing call with no other calls when unfocused",
CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- true, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
FOCUS_VOICE, // expectedFocus
AudioManager.MODE_IN_CALL, // expectedMode
@@ -208,14 +230,14 @@
"New active/dialing voip call with no other calls when unfocused",
CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- true, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- true, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(true)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.COMMS_STATE_NAME, // expectedFinalStateName
FOCUS_VOICE, // expectedFocus
AudioManager.MODE_IN_COMMUNICATION, // expectedMode
@@ -227,14 +249,14 @@
"New ringing call with no other calls when unfocused",
CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NEW_RINGING_CALL, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- false, // hasActiveOrDialingCalls
- true, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(true)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.RING_STATE_NAME, // expectedFinalStateName
FOCUS_RING, // expectedFocus
AudioManager.MODE_RINGTONE, // expectedMode
@@ -246,14 +268,14 @@
"New ringing call coming in on top of active/dialing call",
CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NEW_RINGING_CALL, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- true, // hasActiveOrDialingCalls
- true, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(true)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
NO_CHANGE, // expectedFocus
NO_CHANGE, // expectedMode
@@ -265,14 +287,14 @@
"Ringing call becomes active, part 1",
CallAudioModeStateMachine.ENTER_RING_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- true, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
FOCUS_VOICE, // expectedFocus
AudioManager.MODE_IN_CALL, // expectedMode
@@ -284,14 +306,14 @@
"Ringing call becomes active, part 2",
CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- true, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
NO_CHANGE, // expectedFocus
NO_CHANGE, // expectedMode
@@ -303,14 +325,14 @@
"Active call disconnects, but tone is playing",
CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- false, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- true, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(true)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.TONE_HOLD_STATE_NAME, // expectedFinalStateName
FOCUS_VOICE, // expectedFocus
AudioManager.MODE_IN_CALL, // expectedMode
@@ -322,14 +344,14 @@
"Tone stops playing, with no active calls",
CallAudioModeStateMachine.ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.TONE_STOPPED_PLAYING, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- false, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.UNFOCUSED_STATE_NAME, // expectedFinalStateName
FOCUS_OFF, // expectedFocus
AudioManager.MODE_NORMAL, // expectedMode
@@ -341,14 +363,14 @@
"Ringing call disconnects",
CallAudioModeStateMachine.ENTER_RING_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- false, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.UNFOCUSED_STATE_NAME, // expectedFinalStateName
FOCUS_OFF, // expectedFocus
AudioManager.MODE_NORMAL, // expectedMode
@@ -360,14 +382,14 @@
"Call-waiting call disconnects",
CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- true, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- true, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(true)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
FOCUS_NO_CHANGE, // expectedFocus
NO_CHANGE, // expectedMode
@@ -379,14 +401,14 @@
"Call is placed on hold - 1",
CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- false, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- true, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(true)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.TONE_HOLD_STATE_NAME, // expectedFinalStateName
FOCUS_VOICE, // expectedFocus
AudioManager.MODE_IN_CALL, // expectedMode
@@ -398,14 +420,14 @@
"Call is placed on hold - 2",
CallAudioModeStateMachine.ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NEW_HOLDING_CALL, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- false, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- true, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(true)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.TONE_HOLD_STATE_NAME, // expectedFinalStateName
FOCUS_NO_CHANGE, // expectedFocus
NO_CHANGE, // expectedMode
@@ -417,14 +439,14 @@
"Swap between voip and sim calls - 1",
CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NEW_HOLDING_CALL, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- false, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- true, // hasHoldingCalls
- false, // isTonePlaying
- true, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(true)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(true)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.COMMS_STATE_NAME, // expectedFinalStateName
FOCUS_VOICE, // expectedFocus
AudioManager.MODE_IN_COMMUNICATION, // expectedMode
@@ -436,14 +458,14 @@
"Swap between voip and sim calls - 2",
CallAudioModeStateMachine.ENTER_COMMS_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NEW_HOLDING_CALL, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- false, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- true, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(true)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
FOCUS_VOICE, // expectedFocus
AudioManager.MODE_IN_CALL, // expectedMode
@@ -455,14 +477,14 @@
"Swap between voip and sim calls - 3",
CallAudioModeStateMachine.ENTER_COMMS_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- false, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- true, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(true)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
FOCUS_VOICE, // expectedFocus
AudioManager.MODE_IN_CALL, // expectedMode
@@ -474,14 +496,14 @@
"Swap between voip and sim calls - 4",
CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NEW_HOLDING_CALL, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- false, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- true, // hasHoldingCalls
- false, // isTonePlaying
- true, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(true)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(true)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.COMMS_STATE_NAME, // expectedFinalStateName
FOCUS_VOICE, // expectedFocus
AudioManager.MODE_IN_COMMUNICATION, // expectedMode
@@ -493,14 +515,14 @@
"Call is taken off hold - 1",
CallAudioModeStateMachine.ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- true, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
FOCUS_VOICE, // expectedFocus
AudioManager.MODE_IN_CALL, // expectedMode
@@ -512,14 +534,14 @@
"Call is taken off hold - 2",
CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- true, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
FOCUS_NO_CHANGE, // expectedFocus
NO_CHANGE, // expectedMode
@@ -531,14 +553,14 @@
"Active call disconnects while there's a call-waiting call",
CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- false, // hasActiveOrDialingCalls
- true, // hasRingingCalls
- false, // hasHoldingCalls
- true, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(true)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(true)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.RING_STATE_NAME, // expectedFinalStateName
FOCUS_RING, // expectedFocus
AudioManager.MODE_RINGTONE, // expectedMode
@@ -550,14 +572,14 @@
"New dialing call when there's a call on hold",
CallAudioModeStateMachine.ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- true, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- true, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(true)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
FOCUS_VOICE, // expectedFocus
AudioManager.MODE_IN_CALL, // expectedMode
@@ -569,14 +591,14 @@
"Ringing call disconnects with a holding call in the background",
CallAudioModeStateMachine.ENTER_RING_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- false, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- true, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(true)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.TONE_HOLD_STATE_NAME, // expectedFinalStateName
FOCUS_VOICE, // expectedFocus
AudioManager.MODE_NORMAL, // expectedMode -- we're expecting this because
@@ -589,14 +611,14 @@
"Foreground call transitions from sim to voip",
CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- true, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- true, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(true)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.COMMS_STATE_NAME, // expectedFinalStateName
FOCUS_VOICE, // expectedFocus
AudioManager.MODE_IN_COMMUNICATION, // expectedMode
@@ -608,14 +630,14 @@
"Foreground call transitions from voip to sim",
CallAudioModeStateMachine.ENTER_COMMS_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- true, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- false, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
FOCUS_VOICE, // expectedFocus
AudioManager.MODE_IN_CALL, // expectedMode
@@ -628,14 +650,14 @@
"foreground",
CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- true, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- true, // isTonePlaying
- false, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(true)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
FOCUS_NO_CHANGE, // expectedFocus
NO_CHANGE, // expectedMode
@@ -648,14 +670,14 @@
"foreground",
CallAudioModeStateMachine.ENTER_COMMS_FOCUS_FOR_TESTING, // initialAudioState
CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, // messageType
- new CallAudioModeStateMachine.MessageArgs(
- true, // hasActiveOrDialingCalls
- false, // hasRingingCalls
- false, // hasHoldingCalls
- true, // isTonePlaying
- true, // foregroundCallIsVoip
- null // session
- ),
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(true)
+ .setForegroundCallIsVoip(true)
+ .setSession(null)
+ .build(),
CallAudioModeStateMachine.COMMS_STATE_NAME, // expectedFinalStateName
FOCUS_NO_CHANGE, // expectedFocus
NO_CHANGE, // expectedMode
@@ -663,6 +685,228 @@
OFF // expectedCallWaitingInteraction
));
+ result.add(new ModeTestParameters(
+ "Call enters audio processing state from call screening service",
+ CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING, // initialAudioState
+ CallAudioModeStateMachine.NEW_AUDIO_PROCESSING_CALL, // messageType
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setHasAudioProcessingCalls(true)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
+ CallAudioModeStateMachine.AUDIO_PROCESSING_STATE_NAME, // expectedFinalStateName
+ FOCUS_NO_CHANGE, // expectedFocus
+ CallAudioModeStateMachine.NEW_AUDIO_MODE_FOR_AUDIO_PROCESSING, // expectedMode
+ NO_CHANGE, // expectedRingingInteraction
+ NO_CHANGE // expectedCallWaitingInteraction
+ ));
+
+ result.add(new ModeTestParameters(
+ "Call enters audio processing state by manual intervention from ringing state, 1",
+ CallAudioModeStateMachine.ENTER_RING_FOCUS_FOR_TESTING, // initialAudioState
+ CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, // messageType
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setHasAudioProcessingCalls(true)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
+ CallAudioModeStateMachine.AUDIO_PROCESSING_STATE_NAME, // expectedFinalStateName
+ FOCUS_OFF, // expectedFocus
+ CallAudioModeStateMachine.NEW_AUDIO_MODE_FOR_AUDIO_PROCESSING, // expectedMode
+ OFF, // expectedRingingInteraction
+ NO_CHANGE // expectedCallWaitingInteraction
+ ));
+
+ result.add(new ModeTestParameters(
+ "Call enters audio processing state by manual intervention from ringing state, 2",
+ CallAudioModeStateMachine.ENTER_RING_FOCUS_FOR_TESTING, // initialAudioState
+ CallAudioModeStateMachine.NEW_AUDIO_PROCESSING_CALL, // messageType
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setHasAudioProcessingCalls(true)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
+ CallAudioModeStateMachine.AUDIO_PROCESSING_STATE_NAME, // expectedFinalStateName
+ FOCUS_OFF, // expectedFocus
+ CallAudioModeStateMachine.NEW_AUDIO_MODE_FOR_AUDIO_PROCESSING, // expectedMode
+ OFF, // expectedRingingInteraction
+ NO_CHANGE // expectedCallWaitingInteraction
+ ));
+
+ result.add(new ModeTestParameters(
+ "Call enters audio processing state from active call, 1",
+ CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
+ CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS, // messageType
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setHasAudioProcessingCalls(true)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
+ CallAudioModeStateMachine.AUDIO_PROCESSING_STATE_NAME, // expectedFinalStateName
+ FOCUS_OFF, // expectedFocus
+ CallAudioModeStateMachine.NEW_AUDIO_MODE_FOR_AUDIO_PROCESSING, // expectedMode
+ NO_CHANGE, // expectedRingingInteraction
+ NO_CHANGE // expectedCallWaitingInteraction
+ ));
+
+ result.add(new ModeTestParameters(
+ "Call enters audio processing state from active call, 2",
+ CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
+ CallAudioModeStateMachine.NEW_AUDIO_PROCESSING_CALL, // messageType
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setHasAudioProcessingCalls(true)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
+ CallAudioModeStateMachine.AUDIO_PROCESSING_STATE_NAME, // expectedFinalStateName
+ FOCUS_OFF, // expectedFocus
+ CallAudioModeStateMachine.NEW_AUDIO_MODE_FOR_AUDIO_PROCESSING, // expectedMode
+ NO_CHANGE, // expectedRingingInteraction
+ NO_CHANGE // expectedCallWaitingInteraction
+ ));
+
+ result.add(new ModeTestParameters(
+ "Call in audio processing gets hanged up",
+ CallAudioModeStateMachine.ENTER_AUDIO_PROCESSING_FOCUS_FOR_TESTING, // initialAudioS
+ CallAudioModeStateMachine.NO_MORE_AUDIO_PROCESSING_CALLS, // messageType
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setHasAudioProcessingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
+ CallAudioModeStateMachine.UNFOCUSED_STATE_NAME, // expectedFinalStateName
+ NO_CHANGE, // expectedFocus
+ AudioManager.MODE_NORMAL, // expectedMode
+ NO_CHANGE, // expectedRingingInteraction
+ NO_CHANGE // expectedCallWaitingInteraction
+ ));
+
+ result.add(new ModeTestParameters(
+ "Notify user of a call in audio processing by simulating ringing, 1",
+ CallAudioModeStateMachine.ENTER_AUDIO_PROCESSING_FOCUS_FOR_TESTING, // initialAudioS
+ CallAudioModeStateMachine.NO_MORE_AUDIO_PROCESSING_CALLS, // messageType
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(true)
+ .setHasHoldingCalls(false)
+ .setHasAudioProcessingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
+ CallAudioModeStateMachine.RING_STATE_NAME, // expectedFinalStateName
+ FOCUS_RING, // expectedFocus
+ NO_CHANGE, // expectedMode
+ ON, // expectedRingingInteraction
+ // We expect a call to stopCallWaiting because it happens whenever the ringer starts
+ OFF // expectedCallWaitingInteraction
+ ));
+
+ result.add(new ModeTestParameters(
+ "Notify user of a call in audio processing by simulating ringing, 2",
+ CallAudioModeStateMachine.ENTER_AUDIO_PROCESSING_FOCUS_FOR_TESTING, // initialAudioS
+ CallAudioModeStateMachine.NEW_RINGING_CALL, // messageType
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(true)
+ .setHasHoldingCalls(false)
+ .setHasAudioProcessingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
+ CallAudioModeStateMachine.RING_STATE_NAME, // expectedFinalStateName
+ FOCUS_RING, // expectedFocus
+ NO_CHANGE, // expectedMode
+ ON, // expectedRingingInteraction
+ // We expect a call to stopCallWaiting because it happens whenever the ringer starts
+ OFF // expectedCallWaitingInteraction
+ ));
+
+ result.add(new ModeTestParameters(
+ "Audio processing call gets set to active manually",
+ CallAudioModeStateMachine.ENTER_AUDIO_PROCESSING_FOCUS_FOR_TESTING, // initialAudioS
+ CallAudioModeStateMachine.NO_MORE_AUDIO_PROCESSING_CALLS, // messageType
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(true)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setHasAudioProcessingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
+ CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
+ FOCUS_VOICE, // expectedFocus
+ AudioManager.MODE_IN_CALL, // expectedMode
+ NO_CHANGE, // expectedRingingInteraction
+ NO_CHANGE // expectedCallWaitingInteraction
+ ));
+
+ result.add(new ModeTestParameters(
+ "No change to focus without signaling audio ops complete",
+ CallAudioModeStateMachine.ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING, // initialAudioS
+ CallAudioModeStateMachine.TONE_STOPPED_PLAYING, // messageType
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setHasAudioProcessingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
+ CallAudioModeStateMachine.UNFOCUSED_STATE_NAME, // expectedFinalStateName
+ FOCUS_NO_CHANGE, // expectedFocus
+ AudioManager.MODE_NORMAL, // expectedMode
+ NO_CHANGE, // expectedRingingInteraction
+ NO_CHANGE // expectedCallWaitingInteraction
+ ));
+
+ result.add(new ModeTestParameters(
+ "Abandon focus once audio ops are complete",
+ CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING, // initialAudioS
+ CallAudioModeStateMachine.AUDIO_OPERATIONS_COMPLETE, // messageType
+ new MessageArgs.Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(false)
+ .setHasHoldingCalls(false)
+ .setHasAudioProcessingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build(),
+ CallAudioModeStateMachine.UNFOCUSED_STATE_NAME, // expectedFinalStateName
+ FOCUS_OFF, // expectedFocus
+ NO_CHANGE, // expectedMode
+ NO_CHANGE, // expectedRingingInteraction
+ NO_CHANGE // expectedCallWaitingInteraction
+ ));
+
return result;
}
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index 7f289b8..24476f0 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.media.AudioManager;
import android.media.IAudioService;
+import android.os.HandlerThread;
import android.telecom.CallAudioState;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
@@ -34,6 +35,7 @@
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.WiredHeadsetManager;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -52,9 +54,11 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
@@ -66,7 +70,7 @@
import static org.mockito.Mockito.when;
@RunWith(JUnit4.class)
-public class CallAudioRouteStateMachineTest extends TelecomSystemTest {
+public class CallAudioRouteStateMachineTest extends TelecomTestCase {
private static final BluetoothDevice bluetoothDevice1 =
BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:01");
@@ -88,12 +92,15 @@
private static final int TEST_TIMEOUT = 500;
private AudioManager mockAudioManager;
private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
+ private HandlerThread mThreadHandler;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
MockitoAnnotations.initMocks(this);
+ mThreadHandler = new HandlerThread("CallAudioRouteStateMachineTest");
+ mThreadHandler.start();
mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
mockAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
@@ -115,6 +122,14 @@
any(CallAudioState.class));
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ mThreadHandler.quit();
+ mThreadHandler.join();
+ super.tearDown();
+ }
+
@SmallTest
@Test
public void testEarpieceAutodetect() {
@@ -125,7 +140,8 @@
mockWiredHeadsetManager,
mockStatusBarNotifier,
mAudioServiceFactory,
- CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT);
+ CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
+ mThreadHandler.getLooper());
// 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
@@ -143,7 +159,8 @@
mockWiredHeadsetManager,
mockStatusBarNotifier,
mAudioServiceFactory,
- CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mThreadHandler.getLooper());
when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
@@ -187,7 +204,8 @@
mockWiredHeadsetManager,
mockStatusBarNotifier,
mAudioServiceFactory,
- CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mThreadHandler.getLooper());
stateMachine.setCallAudioManager(mockCallAudioManager);
when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -230,7 +248,8 @@
mockWiredHeadsetManager,
mockStatusBarNotifier,
mAudioServiceFactory,
- CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mThreadHandler.getLooper());
stateMachine.setCallAudioManager(mockCallAudioManager);
Collection<BluetoothDevice> availableDevices = Collections.singleton(bluetoothDevice1);
@@ -303,7 +322,8 @@
mockWiredHeadsetManager,
mockStatusBarNotifier,
mAudioServiceFactory,
- CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mThreadHandler.getLooper());
stateMachine.setCallAudioManager(mockCallAudioManager);
when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -338,7 +358,8 @@
mockWiredHeadsetManager,
mockStatusBarNotifier,
mAudioServiceFactory,
- CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mThreadHandler.getLooper());
stateMachine.setCallAudioManager(mockCallAudioManager);
setInBandRing(false);
when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -392,7 +413,8 @@
mockWiredHeadsetManager,
mockStatusBarNotifier,
mAudioServiceFactory,
- CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mThreadHandler.getLooper());
stateMachine.setCallAudioManager(mockCallAudioManager);
List<BluetoothDevice> availableDevices =
Arrays.asList(bluetoothDevice1, bluetoothDevice2, bluetoothDevice3);
@@ -441,7 +463,8 @@
mockWiredHeadsetManager,
mockStatusBarNotifier,
mAudioServiceFactory,
- CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mThreadHandler.getLooper());
stateMachine.setCallAudioManager(mockCallAudioManager);
List<BluetoothDevice> availableDevices =
Arrays.asList(bluetoothDevice1, bluetoothDevice2);
@@ -465,9 +488,10 @@
CallAudioRouteStateMachine.ACTIVE_FOCUS);
waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
- // Make sure that we've successfully switched to the active BT route without actually
- // calling connectAudio.
- verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(nullable(String.class));
+ // Make sure that we've successfully switched to the active BT route and that we've
+ // called connectAudio on the right device.
+ verify(mockBluetoothRouteManager, atLeastOnce())
+ .connectBluetoothAudio(eq(bluetoothDevice1.getAddress()));
assertTrue(stateMachine.isInActiveState());
}
@@ -554,7 +578,8 @@
mockWiredHeadsetManager,
mockStatusBarNotifier,
mAudioServiceFactory,
- CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mThreadHandler.getLooper());
stateMachine.initialize();
assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
}
@@ -575,7 +600,8 @@
mockWiredHeadsetManager,
mockStatusBarNotifier,
mAudioServiceFactory,
- earpieceControl);
+ earpieceControl,
+ mThreadHandler.getLooper());
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 e63fe9b..58f1ee7 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
@@ -33,6 +33,7 @@
import android.media.AudioManager;
import android.media.IAudioService;
import android.os.Handler;
+import android.os.HandlerThread;
import android.telecom.CallAudioState;
import android.test.suitebuilder.annotation.SmallTest;
@@ -46,6 +47,7 @@
import com.android.server.telecom.WiredHeadsetManager;
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -154,6 +156,7 @@
private static final int TEST_TIMEOUT = 500;
private AudioManager mockAudioManager;
private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
+ private HandlerThread mHandlerThread;
public CallAudioRouteTransitionTests(RoutingTestParameters params) {
mParams = params;
@@ -164,6 +167,8 @@
public void setUp() throws Exception {
super.setUp();
MockitoAnnotations.initMocks(this);
+ mHandlerThread = new HandlerThread("CallAudioRouteTransitionTests");
+ mHandlerThread.start();
mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
mockAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
@@ -185,6 +190,14 @@
any(CallAudioState.class));
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ mHandlerThread.quit();
+ mHandlerThread.join();
+ super.tearDown();
+ }
+
private void setupMocksForParams(final CallAudioRouteStateMachine sm,
RoutingTestParameters params) {
// Set up bluetooth and speakerphone state
@@ -206,8 +219,18 @@
return null;
}).when(mockBluetoothRouteManager).connectBluetoothAudio(nullable(String.class));
- when(mockAudioManager.isSpeakerphoneOn()).thenReturn(
- params.initialRoute == CallAudioState.ROUTE_SPEAKER);
+ // Set the speakerphone state depending on the message being sent. If it's one of the
+ // speakerphone override ones, set accordingly. Otherwise consult the initial route.
+ boolean speakerphoneOn;
+ if (params.action == CallAudioRouteStateMachine.SPEAKER_ON) {
+ speakerphoneOn = true;
+ } else if (params.action == CallAudioRouteStateMachine.SPEAKER_OFF) {
+ speakerphoneOn = false;
+ } else {
+ speakerphoneOn = params.initialRoute == CallAudioState.ROUTE_SPEAKER;
+ }
+ when(mockAudioManager.isSpeakerphoneOn()).thenReturn(speakerphoneOn);
+
when(fakeCall.getSupportedAudioRoutes()).thenReturn(params.callSupportedRoutes);
}
@@ -241,7 +264,8 @@
mockWiredHeadsetManager,
mockStatusBarNotifier,
mAudioServiceFactory,
- mParams.earpieceControl);
+ mParams.earpieceControl,
+ mHandlerThread.getLooper());
stateMachine.setCallAudioManager(mockCallAudioManager);
setupMocksForParams(stateMachine, mParams);
@@ -329,7 +353,8 @@
mockWiredHeadsetManager,
mockStatusBarNotifier,
mAudioServiceFactory,
- mParams.earpieceControl);
+ mParams.earpieceControl,
+ mHandlerThread.getLooper());
stateMachine.setCallAudioManager(mockCallAudioManager);
// Set up bluetooth and speakerphone state
@@ -742,6 +767,58 @@
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
));
+ params.add(new RoutingTestParameters(
+ "Speakerphone turned on during earpiece", // name
+ CallAudioState.ROUTE_EARPIECE, // initialRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+ NONE, // speakerInteraction
+ NONE, // bluetoothInteraction
+ CallAudioRouteStateMachine.SPEAKER_ON, // action
+ CallAudioState.ROUTE_SPEAKER, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailabl
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+ ));
+
+ params.add(new RoutingTestParameters(
+ "Speakerphone turned on during wired headset", // name
+ CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+ CallAudioState.ROUTE_EARPIECE
+ | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
+ NONE, // speakerInteraction
+ NONE, // bluetoothInteraction
+ CallAudioRouteStateMachine.SPEAKER_ON, // action
+ CallAudioState.ROUTE_SPEAKER, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE
+ | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+ ));
+
+ params.add(new RoutingTestParameters(
+ "Speakerphone turned on during bluetooth", // name
+ CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+ NONE, // speakerInteraction
+ OFF, // bluetoothInteraction
+ CallAudioRouteStateMachine.SPEAKER_ON, // action
+ CallAudioState.ROUTE_SPEAKER, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailabl
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+ ));
+
+ params.add(new RoutingTestParameters(
+ "Speakerphone turned off externally during speaker", // name
+ CallAudioState.ROUTE_SPEAKER, // initialRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+ NONE, // speakerInteraction
+ ON, // bluetoothInteraction
+ CallAudioRouteStateMachine.SPEAKER_OFF, // action
+ CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailabl
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+ ));
+
return params;
}
diff --git a/tests/src/com/android/server/telecom/tests/CallExtrasTest.java b/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
index b97f819..926d740 100644
--- a/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
@@ -108,7 +108,10 @@
mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
Connection connection = mConnectionServiceFixtureA.mLatestConnection;
- connection.putExtra(EXTRA_KEY_BOOL, true);
+
+ Bundle newExtras = new Bundle();
+ newExtras.putBoolean(EXTRA_KEY_BOOL, true);
+ connection.putExtras(newExtras);
mInCallServiceFixtureX.waitForUpdate();
assertTrue(mInCallServiceFixtureX.getCall(ids.mCallId).getExtras()
.containsKey(EXTRA_KEY_BOOL));
@@ -130,7 +133,9 @@
mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
Connection connection = mConnectionServiceFixtureA.mLatestConnection;
- connection.putExtra(EXTRA_KEY_INT, EXTRA_VALUE_INT);
+ Bundle newExtras = new Bundle();
+ newExtras.putInt(EXTRA_KEY_INT, EXTRA_VALUE_INT);
+ connection.putExtras(newExtras);
mInCallServiceFixtureX.waitForUpdate();
assertTrue(
mInCallServiceFixtureX.getCall(ids.mCallId).getExtras().containsKey(EXTRA_KEY_INT));
@@ -152,7 +157,9 @@
mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
Connection connection = mConnectionServiceFixtureA.mLatestConnection;
- connection.putExtra(EXTRA_KEY_STR, EXTRA_VALUE_STR);
+ Bundle newExtras = new Bundle();
+ newExtras.putString(EXTRA_KEY_STR, EXTRA_VALUE_STR);
+ connection.putExtras(newExtras);
mInCallServiceFixtureX.waitForUpdate();
assertTrue(
@@ -176,8 +183,10 @@
// Add something.
Connection connection = mConnectionServiceFixtureA.mLatestConnection;
- connection.putExtra(EXTRA_KEY_STR2, EXTRA_VALUE_STR);
- connection.putExtra(EXTRA_KEY_STR, EXTRA_VALUE_STR);
+ Bundle newExtras = new Bundle();
+ newExtras.putString(EXTRA_KEY_STR2, EXTRA_VALUE_STR);
+ newExtras.putString(EXTRA_KEY_STR, EXTRA_VALUE_STR);
+ connection.putExtras(newExtras);
mInCallServiceFixtureX.waitForUpdate();
assertTrue(
mInCallServiceFixtureX.getCall(ids.mCallId).getExtras().containsKey(EXTRA_KEY_STR));
@@ -218,7 +227,9 @@
assertTrue(
mInCallServiceFixtureX.getCall(ids.mCallId).getExtras().containsKey(EXTRA_KEY_INT));
- connection.putExtra(EXTRA_KEY_STR, EXTRA_VALUE2_STR);
+ Bundle newExtras = connection.getExtras();
+ newExtras.putString(EXTRA_KEY_STR, EXTRA_VALUE2_STR);
+ connection.putExtras(newExtras);
mInCallServiceFixtureX.waitForUpdate();
assertEquals(EXTRA_VALUE2_STR,
mInCallServiceFixtureX.getCall(ids.mCallId).getExtras().getString(EXTRA_KEY_STR));
diff --git a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
index 5be30fb..953c711 100644
--- a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
@@ -16,15 +16,13 @@
package com.android.server.telecom.tests;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -34,9 +32,9 @@
import android.content.ComponentName;
import android.content.ContentProvider;
+import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
-import android.content.IContentProvider;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.location.Country;
@@ -45,6 +43,7 @@
import android.net.Uri;
import android.os.Looper;
import android.os.PersistableBundle;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.CallLog;
@@ -69,10 +68,12 @@
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.TelephonyUtil;
+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.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
@@ -84,7 +85,6 @@
public class CallLogManagerTest extends TelecomTestCase {
private CallLogManager mCallLogManager;
- private IContentProvider mContentProvider;
private PhoneAccountHandle mDefaultAccountHandle;
private PhoneAccountHandle mOtherUserAccountHandle;
private PhoneAccountHandle mManagedProfileAccountHandle;
@@ -111,8 +111,10 @@
private static final String TEST_ISO = "KR";
private static final String TEST_ISO_2 = "JP";
- @Mock PhoneAccountRegistrar mMockPhoneAccountRegistrar;
-
+ @Mock(answer = Answers.CALLS_REAL_METHODS)
+ ContentProvider mContentProvider;
+ @Mock
+ PhoneAccountRegistrar mMockPhoneAccountRegistrar;
@Mock
MissedCallNotifier mMissedCallNotifier;
@@ -123,8 +125,6 @@
mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
mCallLogManager = new CallLogManager(mContext, mMockPhoneAccountRegistrar,
mMissedCallNotifier);
- mContentProvider =
- mContext.getContentResolver().acquireProvider("0@call_log");
mDefaultAccountHandle = new PhoneAccountHandle(
new ComponentName("com.android.server.telecom.tests", "CallLogManagerTest"),
TEST_PHONE_ACCOUNT_ID,
@@ -149,6 +149,9 @@
UserHandle.of(CURRENT_USER_ID)
);
+ // Since we can't mock ContentResolver directly, use a ContentProvider
+ when(mContext.getContentResolver()).thenReturn(ContentResolver.wrap(mContentProvider));
+
UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
UserInfo userInfo = new UserInfo(CURRENT_USER_ID, "test", 0);
UserInfo otherUserInfo = new UserInfo(OTHER_USER_ID, "test2", 0);
@@ -158,9 +161,9 @@
doAnswer(new Answer<Uri>() {
@Override
public Uri answer(InvocationOnMock invocation) throws Throwable {
- return (Uri) invocation.getArguments()[1];
+ return (Uri) invocation.getArguments()[0];
}
- }).when(mContentProvider).insert(anyString(), any(Uri.class), any(ContentValues.class));
+ }).when(mContentProvider).insert(any(Uri.class), any(ContentValues.class));
when(userManager.isUserRunning(any(UserHandle.class))).thenReturn(true);
when(userManager.isUserUnlocked(any(UserHandle.class))).thenReturn(true);
@@ -173,6 +176,12 @@
when(userManager.getUserInfo(eq(MANAGED_USER_ID))).thenReturn(managedProfileUserInfo);
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
@MediumTest
@Test
public void testDontLogCancelledCall() {
@@ -531,13 +540,24 @@
// Outgoing call placed through a phone account with multi user capability is inserted to
// all users except managed profile.
- ContentValues insertedValues = verifyInsertionWithCapture(CURRENT_USER_ID);
- assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
- Integer.valueOf(CallLog.Calls.OUTGOING_TYPE));
- insertedValues = verifyInsertionWithCapture(OTHER_USER_ID);
- assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
- Integer.valueOf(CallLog.Calls.OUTGOING_TYPE));
- verifyNoInsertionInUser(MANAGED_USER_ID);
+ SystemClock.sleep(TEST_TIMEOUT_MILLIS);
+
+ ArgumentCaptor<Uri> uris = ArgumentCaptor.forClass(Uri.class);
+ ArgumentCaptor<ContentValues> values = ArgumentCaptor.forClass(ContentValues.class);
+
+ verify(mContentProvider, atLeast(2)).insert(uris.capture(), values.capture());
+
+ assertTrue(uris.getAllValues().contains(
+ ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI, CURRENT_USER_ID)));
+ assertTrue(uris.getAllValues().contains(
+ ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI, OTHER_USER_ID)));
+ assertFalse(uris.getAllValues().contains(
+ ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI, MANAGED_USER_ID)));
+
+ for (ContentValues v : values.getAllValues()) {
+ assertEquals(v.getAsInteger(CallLog.Calls.TYPE),
+ Integer.valueOf(CallLog.Calls.OUTGOING_TYPE));
+ }
}
@MediumTest
@@ -565,13 +585,24 @@
// Incoming call using a phone account with multi user capability is inserted to all users
// except managed profile.
- ContentValues insertedValues = verifyInsertionWithCapture(CURRENT_USER_ID);
- assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
- Integer.valueOf(CallLog.Calls.INCOMING_TYPE));
- insertedValues = verifyInsertionWithCapture(OTHER_USER_ID);
- assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
- Integer.valueOf(CallLog.Calls.INCOMING_TYPE));
- verifyNoInsertionInUser(MANAGED_USER_ID);
+ SystemClock.sleep(TEST_TIMEOUT_MILLIS);
+
+ ArgumentCaptor<Uri> uris = ArgumentCaptor.forClass(Uri.class);
+ ArgumentCaptor<ContentValues> values = ArgumentCaptor.forClass(ContentValues.class);
+
+ verify(mContentProvider, atLeast(2)).insert(uris.capture(), values.capture());
+
+ assertTrue(uris.getAllValues().contains(
+ ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI, CURRENT_USER_ID)));
+ assertTrue(uris.getAllValues().contains(
+ ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI, OTHER_USER_ID)));
+ assertFalse(uris.getAllValues().contains(
+ ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI, MANAGED_USER_ID)));
+
+ for (ContentValues v : values.getAllValues()) {
+ assertEquals(v.getAsInteger(CallLog.Calls.TYPE),
+ Integer.valueOf(CallLog.Calls.INCOMING_TYPE));
+ }
}
@MediumTest
@@ -785,7 +816,7 @@
@SmallTest
@Test
- public void testLogConferenceWithNoChildren() {
+ public void testDoNotLogConferenceWithNoChildren() {
Call fakeCall = makeFakeCall(
DisconnectCause.LOCAL, // disconnectCauseCode
true, // isConference
@@ -801,7 +832,7 @@
);
when(fakeCall.hadChildren()).thenReturn(false);
- assertTrue(mCallLogManager.shouldLogDisconnectedCall(fakeCall, CallState.DISCONNECTED,
+ assertFalse(mCallLogManager.shouldLogDisconnectedCall(fakeCall, CallState.DISCONNECTED,
false /* isCanceled */));
}
@@ -922,48 +953,26 @@
}
private void verifyNoInsertion() {
- try {
- Thread.sleep(TEST_TIMEOUT_MILLIS);
- verify(mContentProvider, never()).insert(any(String.class),
- any(Uri.class), any(ContentValues.class));
- } catch (android.os.RemoteException e) {
- fail("Remote exception occurred during test execution");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
+ SystemClock.sleep(TEST_TIMEOUT_MILLIS);
+
+ verify(mContentProvider, never()).insert(any(Uri.class), any(ContentValues.class));
}
-
private void verifyNoInsertionInUser(int userId) {
- try {
- Uri uri = ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI, userId);
- Thread.sleep(TEST_TIMEOUT_MILLIS);
- verify(getContentProviderForUser(userId), never())
- .insert(any(String.class), eq(uri), any(ContentValues.class));
- } catch (android.os.RemoteException e) {
- fail("Remote exception occurred during test execution");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
+ SystemClock.sleep(TEST_TIMEOUT_MILLIS);
+
+ Uri uri = ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI, userId);
+ verify(mContentProvider, never()).insert(eq(uri), any(ContentValues.class));
}
private ContentValues verifyInsertionWithCapture(int userId) {
+ Uri uri = ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI, userId);
ArgumentCaptor<ContentValues> captor = ArgumentCaptor.forClass(ContentValues.class);
- try {
- Uri uri = ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI, userId);
- verify(getContentProviderForUser(userId), timeout(TEST_TIMEOUT_MILLIS).atLeastOnce())
- .insert(any(String.class), eq(uri), captor.capture());
- } catch (android.os.RemoteException e) {
- fail("Remote exception occurred during test execution");
- }
-
+ verify(mContentProvider, timeout(TEST_TIMEOUT_MILLIS).times(1)).insert(
+ eq(uri), captor.capture());
return captor.getValue();
}
- private IContentProvider getContentProviderForUser(int userId) {
- return mContext.getContentResolver().acquireProvider(userId + "@call_log");
- }
-
private Call makeFakeCall(int disconnectCauseCode, boolean isConference, boolean isIncoming,
long creationTimeMillis, long ageMillis, Uri callHandle,
PhoneAccountHandle phoneAccountHandle, int callVideoState,
diff --git a/tests/src/com/android/server/telecom/tests/CallRecordingTonePlayerTest.java b/tests/src/com/android/server/telecom/tests/CallRecordingTonePlayerTest.java
index eca374b..b5c6468 100644
--- a/tests/src/com/android/server/telecom/tests/CallRecordingTonePlayerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallRecordingTonePlayerTest.java
@@ -22,23 +22,39 @@
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecordingConfiguration;
+import android.media.MediaPlayer;
import android.media.MediaRecorder;
+import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.telecom.PhoneAccountHandle;
import android.test.suitebuilder.annotation.MediumTest;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallRecordingTonePlayer;
+import com.android.server.telecom.CallState;
import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -47,6 +63,7 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
import java.util.ArrayList;
import java.util.List;
@@ -61,25 +78,77 @@
private static final String PHONE_ACCOUNT_CLASS = "MyFancyConnectionService";
private static final String PHONE_ACCOUNT_ID = "1";
private static final String RECORDING_APP_PACKAGE = "com.recording.app";
+ private static final long TEST_RECORDING_TONE_INTERVAL = 300L;
private static final PhoneAccountHandle TEST_PHONE_ACCOUNT = new PhoneAccountHandle(
new ComponentName(PHONE_ACCOUNT_PACKAGE, PHONE_ACCOUNT_CLASS), PHONE_ACCOUNT_ID);
private CallRecordingTonePlayer mCallRecordingTonePlayer;
- private TelecomSystem.SyncRoot mSyncRoot = new TelecomSystem.SyncRoot() { };
- @Mock private AudioManager mAudioManager;
+ private TelecomSystem.SyncRoot mSyncRoot = new TelecomSystem.SyncRoot() {
+ };
+ @Mock
+ private AudioManager mAudioManager;
+ @Mock
+ private Timeouts.Adapter mTimeouts;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
MockitoAnnotations.initMocks(this);
+ when(mTimeouts.getCallRecordingToneRepeatIntervalMillis(nullable(ContentResolver.class)))
+ .thenReturn(500L);
mCallRecordingTonePlayer = new CallRecordingTonePlayer(
mComponentContextFixture.getTestDouble().getApplicationContext(),
- mAudioManager, mSyncRoot);
+ mAudioManager, mTimeouts, mSyncRoot);
when(mAudioManager.getActiveRecordingConfigurations()).thenReturn(null);
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @MediumTest
+ @Test
+ public void testToneLooping() throws Exception {
+ MediaPlayer mockMediaPlayer = mock(MediaPlayer.class);
+ MockitoSession session = ExtendedMockito.mockitoSession().mockStatic(MediaPlayer.class)
+ .startMocking();
+ ExtendedMockito.doReturn(mockMediaPlayer).when(() ->
+ MediaPlayer.create(nullable(Context.class), anyInt()));
+
+ when(mAudioManager.getActiveRecordingConfigurations()).thenReturn(
+ getAudioRecordingConfig(RECORDING_APP_PACKAGE));
+
+ AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
+ when(mockAudioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_TELEPHONY);
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
+ .thenReturn(new AudioDeviceInfo[] { mockAudioDeviceInfo });
+
+ Call call = addValidCall();
+ when(call.isActive()).thenReturn(true);
+ mCallRecordingTonePlayer.onCallStateChanged(call, CallState.NEW, CallState.ACTIVE);
+
+ waitForHandlerAction(Handler.getMain(), TEST_TIMEOUT);
+ verify(mockMediaPlayer).start();
+
+ // Sleep for 4x the interval, then make sure it played more. No exact count,
+ // since timing can be tricky in tests.
+ Thread.sleep(TEST_RECORDING_TONE_INTERVAL * 4);
+ verify(mockMediaPlayer, atLeast(2)).start();
+ reset(mockMediaPlayer);
+
+ // Remove the call and verify that we're not starting the tone anymore.
+ mCallRecordingTonePlayer.onCallRemoved(call);
+ Thread.sleep(TEST_RECORDING_TONE_INTERVAL * 3 + 50);
+ verify(mockMediaPlayer, never()).start();
+ verify(mockMediaPlayer).release();
+
+ session.finishMocking();
+ }
+
/**
* Ensures that child calls are not tracked.
*/
diff --git a/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
index 169c56a..ff16880 100644
--- a/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
@@ -23,11 +23,15 @@
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.net.Uri;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.UserHandle;
import android.telecom.GatewayInfo;
import android.telecom.PhoneAccountHandle;
import android.telephony.TelephonyManager;
+
+import com.android.internal.telecom.ICallRedirectionAdapter;
import com.android.internal.telecom.ICallRedirectionService;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallsManager;
@@ -39,23 +43,30 @@
import com.android.server.telecom.callredirection.CallRedirectionProcessor;
import com.android.server.telecom.callredirection.CallRedirectionProcessorHelper;
+import static org.junit.Assert.assertEquals;
+import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.junit.Before;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import java.util.ArrayList;
+
@RunWith(JUnit4.class)
public class CallRedirectionProcessorTest extends TelecomTestCase {
@Mock private Context mContext;
@@ -81,6 +92,11 @@
@Mock private Timeouts.Adapter mTimeoutsAdapter;
+ private static final Uri ORIGINAL_NUMBER_WITH_POST_DIAL = Uri.parse("tel:6505551212,,,1234");
+ private static final Uri ORIGINAL_NUMBER_NO_POST_DIAL = Uri.parse("tel:6505551212");
+ private static final Uri REDIRECTED_GATEWAY_NUMBER = Uri.parse("tel:6505551213");
+ private static final Uri REDIRECTED_GATEWAY_NUMBER_WITH_POST_DIAL =
+ Uri.parse("tel:6505551213,,,1234");
private static final String USER_DEFINED_PKG_NAME = "user_defined_pkg";
private static final String USER_DEFINED_CLS_NAME = "user_defined_cls";
private static final String CARRIER_PKG_NAME = "carrier_pkg";
@@ -126,6 +142,14 @@
anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ mProcessor.getHandler().removeCallbacksAndMessages(null);
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TelecomSystemTest.TEST_TIMEOUT);
+ super.tearDown();
+ }
+
private void setIsInCarMode(boolean isInCarMode) {
when(mSystemStateHelper.isCarMode()).thenReturn(isInCarMode);
}
@@ -151,7 +175,11 @@
}
private void startProcessWithNoGateWayInfo() {
- mProcessor = new CallRedirectionProcessor(mContext, mCallsManager, mCall, mHandle,
+ startProcessWithNoGateWayInfo(mHandle);
+ }
+
+ private void startProcessWithNoGateWayInfo(Uri handle) {
+ mProcessor = new CallRedirectionProcessor(mContext, mCallsManager, mCall, handle,
mPhoneAccountRegistrar, null, SPEAKER_PHONE_ON, VIDEO_STATE);
mProcessor.setCallRedirectionServiceHelper(mCallRedirectionProcessorHelper);
}
@@ -280,4 +308,50 @@
eq(mPhoneAccountHandle), eq(mGatewayInfo), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
}
+
+ @Test
+ public void testStripPostDialDigits() throws Exception {
+ startProcessWithNoGateWayInfo(ORIGINAL_NUMBER_WITH_POST_DIAL);
+ enableUserDefinedCallRedirectionService();
+ disableCarrierCallRedirectionService();
+
+ mProcessor.performCallRedirection();
+
+ // Capture binding and mock it out.
+ ArgumentCaptor<ServiceConnection> serviceConnectionCaptor = ArgumentCaptor.forClass(
+ ServiceConnection.class);
+ verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
+ serviceConnectionCaptor.capture(), anyInt(), any(UserHandle.class));
+
+ // Mock out a service which performed a redirection
+ IBinder mockBinder = mock(IBinder.class);
+ ICallRedirectionService mockCallRedirectionService = mock(ICallRedirectionService.class);
+ when(mockBinder.queryLocalInterface(anyString())).thenReturn(mockCallRedirectionService);
+ serviceConnectionCaptor.getValue().onServiceConnected(
+ USER_DEFINED_SERVICE_TEST_COMPONENT_NAME, mockBinder);
+
+ ArgumentCaptor<ICallRedirectionAdapter> redirectionAdapterCaptor = ArgumentCaptor.forClass(
+ ICallRedirectionAdapter.class);
+ ArgumentCaptor<Uri> uriArgumentCaptor = ArgumentCaptor.forClass(Uri.class);
+ verify(mockCallRedirectionService, times(1)).placeCall(redirectionAdapterCaptor.capture(),
+ uriArgumentCaptor.capture(), any(), anyBoolean());
+
+ // Verify the service did not get passed post-dial digits.
+ assertEquals(ORIGINAL_NUMBER_NO_POST_DIAL, uriArgumentCaptor.getValue());
+
+ // Pretend it was verified.
+ redirectionAdapterCaptor.getValue().redirectCall(REDIRECTED_GATEWAY_NUMBER,
+ mPhoneAccountHandle, false);
+
+ waitForHandlerAction(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY);
+
+ ArgumentCaptor<GatewayInfo> gatewayInfoArgumentCaptor = ArgumentCaptor.forClass(
+ GatewayInfo.class);
+ verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall),
+ eq(ORIGINAL_NUMBER_WITH_POST_DIAL), eq(mPhoneAccountHandle),
+ gatewayInfoArgumentCaptor.capture(), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+ eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
+ assertEquals(REDIRECTED_GATEWAY_NUMBER_WITH_POST_DIAL,
+ gatewayInfoArgumentCaptor.getValue().getGatewayAddress());
+ }
}
diff --git a/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java b/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java
deleted file mode 100644
index 1a01a95..0000000
--- a/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java
+++ /dev/null
@@ -1,373 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.Manifest;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.net.Uri;
-import android.os.PersistableBundle;
-import android.os.UserHandle;
-import android.provider.CallLog;
-import android.telecom.TelecomManager;
-import android.telephony.CarrierConfigManager;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.internal.telephony.CallerInfo;
-import com.android.server.telecom.Call;
-import com.android.server.telecom.CallScreeningServiceHelper;
-import com.android.server.telecom.CallerInfoLookupHelper;
-import com.android.server.telecom.CallsManager;
-import com.android.server.telecom.ParcelableCallUtils;
-import com.android.server.telecom.PhoneAccountRegistrar;
-import com.android.server.telecom.RoleManagerAdapter;
-import com.android.server.telecom.TelecomServiceImpl;
-import com.android.server.telecom.TelecomSystem;
-import com.android.server.telecom.callfiltering.CallFilterResultCallback;
-import com.android.server.telecom.callfiltering.CallFilteringResult;
-import com.android.server.telecom.callfiltering.CallScreeningServiceController;
-
-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.Collections;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-@RunWith(JUnit4.class)
-public class CallScreeningServiceControllerTest extends TelecomTestCase {
-
- @Mock Context mContext;
- @Mock Call mCall;
- @Mock private CallFilterResultCallback mCallback;
- @Mock CallsManager mCallsManager;
- @Mock RoleManagerAdapter mRoleManagerAdapter;
- @Mock CarrierConfigManager mCarrierConfigManager;
- @Mock private TelecomManager mTelecomManager;
- @Mock PackageManager mPackageManager;
- @Mock ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
- @Mock PhoneAccountRegistrar mPhoneAccountRegistrar;
- @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
-
- CallScreeningServiceHelper.AppLabelProxy mAppLabelProxy =
- new CallScreeningServiceHelper.AppLabelProxy() {
- @Override
- public CharSequence getAppLabel(String packageName) {
- return APP_NAME;
- }
- };
-
- private ResolveInfo mResolveInfo;
- private TelecomServiceImpl.SettingsSecureAdapter mSettingsSecureAdapter =
- spy(new CallScreeningServiceFilterTest.SettingsSecureAdapterFake());
- private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
-
- private static final String CALL_ID = "u89prgt9ps78y5";
- private static final Uri TEST_HANDLE = Uri.parse("tel:1235551234");
- private static final String DEFAULT_DIALER_PACKAGE = "com.android.dialer";
- private static final String PKG_NAME = "com.android.services.telecom.tests";
- private static final String CLS_NAME = "CallScreeningService";
- private static final String APP_NAME = "Screeny McScreenface";
- private static final ComponentName CARRIER_DEFINED_CALL_SCREENING = new ComponentName(
- "com.android.carrier", "com.android.carrier.callscreeningserviceimpl");
- private static final ComponentName DEFAULT_DIALER_CALL_SCREENING = new ComponentName(
- "com.android.dialer", "com.android.dialer.callscreeningserviceimpl");
- private static final ComponentName USER_CHOSEN_CALL_SCREENING = new ComponentName(
- "com.android.userchosen", "com.android.userchosen.callscreeningserviceimpl");
-
- private static final CallFilteringResult PASS_RESULT = new CallFilteringResult(
- true, // shouldAllowCall
- false, // shouldReject
- true, // shouldAddToCallLog
- true // shouldShowNotification
- );
-
- public static class SettingsSecureAdapterFake implements
- TelecomServiceImpl.SettingsSecureAdapter {
- @Override
- public void putStringForUser(ContentResolver resolver, String name, String value,
- int userHandle) {
-
- }
-
- @Override
- public String getStringForUser(ContentResolver resolver, String name, int userHandle) {
- return USER_CHOSEN_CALL_SCREENING.flattenToString();
- }
- }
-
- @Override
- @Before
- public void setUp() throws Exception {
- super.setUp();
- when(mRoleManagerAdapter.getCallCompanionApps()).thenReturn(Collections.emptyList());
- when(mRoleManagerAdapter.getDefaultCallScreeningApp()).thenReturn(null);
- when(mRoleManagerAdapter.getCarModeDialerApp()).thenReturn(null);
- when(mCallsManager.getRoleManagerAdapter()).thenReturn(mRoleManagerAdapter);
- when(mCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
- when(mContext.getPackageManager()).thenReturn(mPackageManager);
- when(mCall.getId()).thenReturn(CALL_ID);
-
- setCarrierDefinedCallScreeningApplication();
- when(TelecomManager.from(mContext)).thenReturn(mTelecomManager);
- when(mTelecomManager.getDefaultDialerPackage()).thenReturn(DEFAULT_DIALER_PACKAGE);
-
- mResolveInfo = new ResolveInfo() {{
- serviceInfo = new ServiceInfo();
- serviceInfo.packageName = PKG_NAME;
- serviceInfo.name = CLS_NAME;
- serviceInfo.permission = Manifest.permission.BIND_SCREENING_SERVICE;
- }};
-
- when(mPackageManager.queryIntentServicesAsUser(nullable(Intent.class), anyInt(), anyInt()))
- .thenReturn(Collections.singletonList(mResolveInfo));
- when(mParcelableCallUtilsConverter.toParcelableCall(
- eq(mCall), anyBoolean(), eq(mPhoneAccountRegistrar))).thenReturn(null);
- when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
- anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
- when(mCall.getHandle()).thenReturn(TEST_HANDLE);
- }
-
- @SmallTest
- @Test
- public void testAllAllowCall() {
- when(mRoleManagerAdapter.getDefaultCallScreeningApp()).thenReturn(
- USER_CHOSEN_CALL_SCREENING.getPackageName());
- CallScreeningServiceController controller = new CallScreeningServiceController(mContext,
- mCallsManager, mPhoneAccountRegistrar,
- mParcelableCallUtilsConverter, mLock,
- mSettingsSecureAdapter, mCallerInfoLookupHelper, mAppLabelProxy);
-
- controller.startFilterLookup(mCall, mCallback);
-
- controller.onCallScreeningFilterComplete(mCall, PASS_RESULT,
- CARRIER_DEFINED_CALL_SCREENING.getPackageName());
-
- CallerInfoLookupHelper.OnQueryCompleteListener queryListener = verifyLookupStart();
- CallerInfo callerInfo = new CallerInfo();
- callerInfo.contactExists = false;
- queryListener.onCallerInfoQueryComplete(TEST_HANDLE, callerInfo);
-
- controller.onCallScreeningFilterComplete(mCall, PASS_RESULT,
- DEFAULT_DIALER_CALL_SCREENING.getPackageName());
- controller.onCallScreeningFilterComplete(mCall, PASS_RESULT, USER_CHOSEN_CALL_SCREENING
- .getPackageName());
-
- verify(mContext, times(3)).bindServiceAsUser(any(Intent.class), any(ServiceConnection
- .class),
- eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
- eq(UserHandle.CURRENT));
-
- verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
- }
-
- @SmallTest
- @Test
- public void testCarrierAllowCallAndContactExists() {
- CallScreeningServiceController controller = new CallScreeningServiceController(mContext,
- mCallsManager,
- mPhoneAccountRegistrar, mParcelableCallUtilsConverter, mLock,
- mSettingsSecureAdapter, mCallerInfoLookupHelper, mAppLabelProxy);
-
- controller.startFilterLookup(mCall, mCallback);
-
- controller.onCallScreeningFilterComplete(mCall, PASS_RESULT,
- CARRIER_DEFINED_CALL_SCREENING.getPackageName());
-
- CallerInfoLookupHelper.OnQueryCompleteListener queryListener = verifyLookupStart();
- CallerInfo callerInfo = new CallerInfo();
- callerInfo.contactExists = true;
- queryListener.onCallerInfoQueryComplete(TEST_HANDLE, callerInfo);
-
- verify(mContext, times(1)).bindServiceAsUser(any(Intent.class), any(ServiceConnection
- .class),
- eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
- eq(UserHandle.CURRENT));
-
- verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
- }
-
- @SmallTest
- @Test
- public void testCarrierCallScreeningRejectCall() {
- CallScreeningServiceController controller = new CallScreeningServiceController(mContext,
- mCallsManager,
- mPhoneAccountRegistrar, mParcelableCallUtilsConverter, mLock,
- mSettingsSecureAdapter, mCallerInfoLookupHelper, mAppLabelProxy);
-
- controller.startFilterLookup(mCall, mCallback);
-
- controller.onCallScreeningFilterComplete(mCall, new CallFilteringResult(
- false, // shouldAllowCall
- true, // shouldReject
- false, // shouldAddToCallLog
- true, // shouldShowNotification
- CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE,
- APP_NAME,
- CARRIER_DEFINED_CALL_SCREENING.flattenToString()
- ), CARRIER_DEFINED_CALL_SCREENING.getPackageName());
-
- verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
- any(ServiceConnection.class),
- eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
- eq(UserHandle.CURRENT));
-
- verify(mCallback).onCallFilteringComplete(eq(mCall), eq(new CallFilteringResult(
- false, // shouldAllowCall
- true, // shouldReject
- false, // shouldAddToCallLog
- true, // shouldShowNotification
- CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
- APP_NAME, //callScreeningAppName
- CARRIER_DEFINED_CALL_SCREENING.flattenToString() //callScreeningComponentName
- )));
- }
-
- @SmallTest
- @Test
- public void testDefaultDialerRejectCall() {
- when(mRoleManagerAdapter.getDefaultCallScreeningApp()).thenReturn(
- USER_CHOSEN_CALL_SCREENING.getPackageName());
- CallScreeningServiceController controller = new CallScreeningServiceController(mContext,
- mCallsManager,
- mPhoneAccountRegistrar, mParcelableCallUtilsConverter, mLock,
- mSettingsSecureAdapter, mCallerInfoLookupHelper, mAppLabelProxy);
-
- controller.startFilterLookup(mCall, mCallback);
-
- controller.onCallScreeningFilterComplete(mCall, PASS_RESULT,
- CARRIER_DEFINED_CALL_SCREENING.getPackageName());
-
- CallerInfoLookupHelper.OnQueryCompleteListener queryListener = verifyLookupStart();
- CallerInfo callerInfo = new CallerInfo();
- callerInfo.contactExists = false;
- queryListener.onCallerInfoQueryComplete(TEST_HANDLE, callerInfo);
-
- controller.onCallScreeningFilterComplete(mCall, new CallFilteringResult(
- false, // shouldAllowCall
- true, // shouldReject
- false, // shouldAddToCallLog
- true, // shouldShowNotification
- CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE,
- APP_NAME,
- DEFAULT_DIALER_CALL_SCREENING.flattenToString()
- ), DEFAULT_DIALER_CALL_SCREENING.getPackageName());
-
- verify(mContext, times(3)).bindServiceAsUser(any(Intent.class),
- any(ServiceConnection.class),
- eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
- eq(UserHandle.CURRENT));
-
- verify(mCallback).onCallFilteringComplete(eq(mCall), eq(new CallFilteringResult(
- false, // shouldAllowCall
- true, // shouldReject
- true, // shouldAddToCallLog (we don't allow services to skip call log)
- true, // shouldShowNotification
- CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
- APP_NAME, //callScreeningAppName
- DEFAULT_DIALER_CALL_SCREENING.flattenToString() //callScreeningComponentName
- )));
- }
-
- @SmallTest
- @Test
- public void testUserChosenRejectCall() {
- when(mRoleManagerAdapter.getDefaultCallScreeningApp()).thenReturn(
- USER_CHOSEN_CALL_SCREENING.getPackageName());
- CallScreeningServiceController controller = new CallScreeningServiceController(mContext,
- mCallsManager,
- mPhoneAccountRegistrar, mParcelableCallUtilsConverter, mLock,
- mSettingsSecureAdapter, mCallerInfoLookupHelper, mAppLabelProxy);
-
- controller.startFilterLookup(mCall, mCallback);
-
- controller.onCallScreeningFilterComplete(mCall, PASS_RESULT,
- CARRIER_DEFINED_CALL_SCREENING.getPackageName());
-
- CallerInfoLookupHelper.OnQueryCompleteListener queryListener = verifyLookupStart();
- CallerInfo callerInfo = new CallerInfo();
- callerInfo.contactExists = false;
- queryListener.onCallerInfoQueryComplete(TEST_HANDLE, callerInfo);
-
- controller.onCallScreeningFilterComplete(mCall, PASS_RESULT,
- DEFAULT_DIALER_CALL_SCREENING.getPackageName());
- controller.onCallScreeningFilterComplete(mCall, new CallFilteringResult(
- false, // shouldAllowCall
- true, // shouldReject
- false, // shouldAddToCallLog
- true, // shouldShowNotification
- CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE,
- APP_NAME,
- USER_CHOSEN_CALL_SCREENING.flattenToString()
- ), USER_CHOSEN_CALL_SCREENING.getPackageName());
-
- verify(mContext, times(3)).bindServiceAsUser(any(Intent.class),
- any(ServiceConnection.class),
- eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
- eq(UserHandle.CURRENT));
-
- verify(mCallback).onCallFilteringComplete(eq(mCall), eq(new CallFilteringResult(
- false, // shouldAllowCall
- true, // shouldReject
- true, // shouldAddToCallLog (we don't allow services to skip call log)
- true, // shouldShowNotification
- CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
- APP_NAME, //callScreeningAppName
- USER_CHOSEN_CALL_SCREENING.flattenToString() //callScreeningComponentName
- )));
- }
-
- private CallerInfoLookupHelper.OnQueryCompleteListener verifyLookupStart() {
- return verifyLookupStart(TEST_HANDLE);
- }
-
- private CallerInfoLookupHelper.OnQueryCompleteListener verifyLookupStart(Uri handle) {
-
- ArgumentCaptor<CallerInfoLookupHelper.OnQueryCompleteListener> captor =
- ArgumentCaptor.forClass(CallerInfoLookupHelper.OnQueryCompleteListener.class);
- verify(mCallerInfoLookupHelper).startLookup(eq(handle), captor.capture());
- return captor.getValue();
- }
-
- private void setCarrierDefinedCallScreeningApplication() {
- String carrierDefined = CARRIER_DEFINED_CALL_SCREENING.flattenToString();
- PersistableBundle bundle = new PersistableBundle();
- bundle.putString(CarrierConfigManager.KEY_CARRIER_CALL_SCREENING_APP_STRING,
- carrierDefined);
- when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
- .thenReturn(mCarrierConfigManager);
- when(mCarrierConfigManager.getConfig()).thenReturn(bundle);
- }
-}
diff --git a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
index 5b47fe9..1345c01 100644
--- a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -11,14 +11,24 @@
* 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
+ * limitations under the License.
*/
package com.android.server.telecom.tests;
+import static org.junit.Assert.assertEquals;
+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.nullable;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.Manifest;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
@@ -26,26 +36,22 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.IBinder;
-import android.os.PersistableBundle;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.CallLog;
import android.telecom.CallScreeningService;
import android.telecom.ParcelableCall;
import android.telecom.TelecomManager;
-import android.telephony.CarrierConfigManager;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.internal.telecom.ICallScreeningAdapter;
import com.android.internal.telecom.ICallScreeningService;
+import com.android.server.telecom.AppLabelProxy;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ParcelableCallUtils;
import com.android.server.telecom.PhoneAccountRegistrar;
-import com.android.server.telecom.TelecomServiceImpl;
import com.android.server.telecom.callfiltering.CallFilteringResult;
import com.android.server.telecom.callfiltering.CallScreeningServiceFilter;
-import com.android.server.telecom.TelecomSystem;
import org.junit.Before;
import org.junit.Test;
@@ -55,94 +61,41 @@
import org.mockito.Mock;
import java.util.Collections;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.TimeUnit;
@RunWith(JUnit4.class)
public class CallScreeningServiceFilterTest extends TelecomTestCase {
+ static @Mock Call mCall;
@Mock Context mContext;
- @Mock CallsManager mCallsManager;
- @Mock PhoneAccountRegistrar mPhoneAccountRegistrar;
- @Mock ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
- private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
-
- @Mock Call mCall;
- @Mock CallScreeningServiceFilter.CallScreeningFilterResultCallback mCallback;
-
+ @Mock TelecomManager mTelecomManager;
@Mock PackageManager mPackageManager;
- @Mock IBinder mBinder;
+ @Mock CallsManager mCallsManager;
+ @Mock AppLabelProxy mAppLabelProxy;
+ @Mock ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
+ @Mock PhoneAccountRegistrar mPhoneAccountRegistrar;
@Mock ICallScreeningService mCallScreeningService;
- @Mock CarrierConfigManager mCarrierConfigManager;
- @Mock private TelecomManager mTelecomManager;
- private TelecomServiceImpl.SettingsSecureAdapter mSettingsSecureAdapter =
- spy(new SettingsSecureAdapterFake());
+ @Mock IBinder mBinder;
+ private static final String CALL_ID = "u89prgt9ps78y5";
private static final String PKG_NAME = "com.android.services.telecom.tests";
private static final String APP_NAME = "TeleTestApp";
private static final String CLS_NAME = "CallScreeningService";
private static final ComponentName COMPONENT_NAME = new ComponentName(PKG_NAME, CLS_NAME);
- private static final String CALL_ID = "u89prgt9ps78y5";
- private static final String DEFAULT_DIALER_PACKAGE = "com.android.dialer";
- private static final ComponentName CARRIER_DEFINED_CALL_SCREENING = new ComponentName(
- "com.android.carrier", "com.android.carrier.callscreeningserviceimpl");
- private static final String CARRIER_DEFINED_CALL_SCREENING_APP_NAME = "GMob";
- private static final ComponentName DEFAULT_DIALER_CALL_SCREENING = new ComponentName(
- "com.android.dialer", "com.android.dialer.callscreeningserviceimpl");
- private static final String DEFAULT_DIALER_APP_NAME = "Dialer";
- private static final ComponentName USER_CHOSEN_CALL_SCREENING = new ComponentName(
- "com.android.userchosen", "com.android.userchosen.callscreeningserviceimpl");
- private static final String USER_CHOSEN_CALL_SCREENING_APP_NAME = "UserChosen";
private ResolveInfo mResolveInfo;
+ private CallFilteringResult inputResult;
- private static final CallFilteringResult PASS_RESULT = new CallFilteringResult(
- true, // shouldAllowCall
- false, // shouldReject
- true, // shouldAddToCallLog
- true // shouldShowNotification
- );
-
- private static final CallFilteringResult PASS_RESULT_WITH_SILENCE = new CallFilteringResult(
- true, // shouldAllowCall
- false, // shouldReject
- true, // shouldSilence
- true, // shouldAddToCallLog
- true // shouldShowNotification
- );
-
- private CallScreeningServiceFilter mFilter;
-
- public static class SettingsSecureAdapterFake implements
- TelecomServiceImpl.SettingsSecureAdapter {
- @Override
- public void putStringForUser(ContentResolver resolver, String name, String value,
- int userHandle) {
-
- }
-
- @Override
- public String getStringForUser(ContentResolver resolver, String name, int userHandle) {
- return USER_CHOSEN_CALL_SCREENING.flattenToString();
- }
- }
+ private static final CallFilteringResult PASS_RESULT = new CallFilteringResult.Builder()
+ .setShouldAllowCall(true)
+ .setShouldReject(false)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(true)
+ .build();
@Override
@Before
public void setUp() throws Exception {
super.setUp();
- when(mCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
- when(mContext.getPackageManager()).thenReturn(mPackageManager);
- when(mCall.getId()).thenReturn(CALL_ID);
- doReturn(mCallScreeningService).when(mBinder).queryLocalInterface(anyString());
mResolveInfo = new ResolveInfo() {{
serviceInfo = new ServiceInfo();
@@ -150,213 +103,235 @@
serviceInfo.name = CLS_NAME;
serviceInfo.permission = Manifest.permission.BIND_SCREENING_SERVICE;
}};
+ inputResult = new CallFilteringResult.Builder()
+ .setShouldAllowCall(true)
+ .setShouldReject(false)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(true)
+ .build();
- mFilter = new CallScreeningServiceFilter(mContext, mCallsManager, mPhoneAccountRegistrar,
- mParcelableCallUtilsConverter, mLock, mSettingsSecureAdapter);
-
- when(mPackageManager.queryIntentServicesAsUser(nullable(Intent.class), anyInt(), anyInt()))
- .thenReturn(Collections.singletonList(mResolveInfo));
+ when(mCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
+ when(mCall.getId()).thenReturn(CALL_ID);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mContext.getSystemService(TelecomManager.class))
+ .thenReturn(mTelecomManager);
+ when(mTelecomManager.getSystemDialerPackage()).thenReturn(PKG_NAME);
+ when(mAppLabelProxy.getAppLabel(PKG_NAME)).thenReturn(APP_NAME);
when(mParcelableCallUtilsConverter.toParcelableCall(
eq(mCall), anyBoolean(), eq(mPhoneAccountRegistrar))).thenReturn(null);
when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
- }
-
- @SmallTest
- @Test
- public void testNoPackageName() {
- mFilter.startCallScreeningFilter(mCall, mCallback, null, null,
- CallScreeningServiceFilter.CALL_SCREENING_FILTER_TYPE_USER_SELECTED);
- verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(null));
- }
-
- @SmallTest
- @Test
- public void testNoResolveEntries() {
when(mPackageManager.queryIntentServicesAsUser(nullable(Intent.class), anyInt(), anyInt()))
- .thenReturn(Collections.emptyList());
- mFilter.startCallScreeningFilter(mCall, mCallback, PKG_NAME, APP_NAME,
- CallScreeningServiceFilter.CALL_SCREENING_FILTER_TYPE_USER_SELECTED);
- verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(PKG_NAME));
+ .thenReturn(Collections.singletonList(mResolveInfo));
+ doReturn(mCallScreeningService).when(mBinder).queryLocalInterface(anyString());
}
@SmallTest
@Test
- public void testBadResolveEntry() {
- mResolveInfo.serviceInfo = null;
- mFilter.startCallScreeningFilter(mCall, mCallback, PKG_NAME, APP_NAME,
- CallScreeningServiceFilter.CALL_SCREENING_FILTER_TYPE_USER_SELECTED);
- verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(PKG_NAME));
+ public void testNoPackageName() throws Exception {
+ CallScreeningServiceFilter filter = new CallScreeningServiceFilter(mCall, null,
+ CallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager,
+ mAppLabelProxy, mParcelableCallUtilsConverter);
+ assertEquals(PASS_RESULT,
+ filter.startFilterLookup(inputResult).toCompletableFuture().get(
+ CallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT,
+ TimeUnit.MILLISECONDS));
}
@SmallTest
@Test
- public void testPermissionlessFilterService() {
- mResolveInfo.serviceInfo.permission = null;
- mFilter.startCallScreeningFilter(mCall, mCallback, PKG_NAME, APP_NAME,
- CallScreeningServiceFilter.CALL_SCREENING_FILTER_TYPE_USER_SELECTED);
- verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(PKG_NAME));
- }
-
- @SmallTest
- @Test
- public void testContextFailToBind() {
+ public void testContextFailToBind() throws Exception {
when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
anyInt(), eq(UserHandle.CURRENT))).thenReturn(false);
- mFilter.startCallScreeningFilter(mCall, mCallback, PKG_NAME, APP_NAME,
- CallScreeningServiceFilter.CALL_SCREENING_FILTER_TYPE_USER_SELECTED);
- verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(PKG_NAME));
+ CallScreeningServiceFilter filter = new CallScreeningServiceFilter(mCall, PKG_NAME,
+ CallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager,
+ mAppLabelProxy, mParcelableCallUtilsConverter);
+ assertEquals(PASS_RESULT,
+ filter.startFilterLookup(inputResult).toCompletableFuture().get(
+ CallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT,
+ TimeUnit.MILLISECONDS));
}
@SmallTest
@Test
- public void testExceptionInScreeningService() throws Exception {
- doThrow(new RemoteException()).when(mCallScreeningService).screenCall(
- nullable(ICallScreeningAdapter.class), nullable(ParcelableCall.class));
- mFilter.startCallScreeningFilter(mCall, mCallback, PKG_NAME, APP_NAME,
- CallScreeningServiceFilter.CALL_SCREENING_FILTER_TYPE_USER_SELECTED);
- ServiceConnection serviceConnection = verifyBindingIntent();
- serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
- verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(PKG_NAME));
+ public void testNoResolveEntries() throws Exception {
+ when(mPackageManager.queryIntentServicesAsUser(nullable(Intent.class), anyInt(), anyInt()))
+ .thenReturn(Collections.emptyList());
+ CallScreeningServiceFilter filter = new CallScreeningServiceFilter(mCall, PKG_NAME,
+ CallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager,
+ mAppLabelProxy, mParcelableCallUtilsConverter);
+ assertEquals(PASS_RESULT,
+ filter.startFilterLookup(inputResult).toCompletableFuture().get(
+ CallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT,
+ TimeUnit.MILLISECONDS));
+ }
+
+ @SmallTest
+ @Test
+ public void testBadResolveEntry() throws Exception {
+ mResolveInfo.serviceInfo = null;
+ CallScreeningServiceFilter filter = new CallScreeningServiceFilter(mCall, PKG_NAME,
+ CallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager,
+ mAppLabelProxy, mParcelableCallUtilsConverter);
+ assertEquals(PASS_RESULT,
+ filter.startFilterLookup(inputResult).toCompletableFuture().get(
+ CallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT,
+ TimeUnit.MILLISECONDS));
+ }
+
+ @SmallTest
+ @Test
+ public void testNoBindingCondition() {
+ // Make sure there will be no binding if the package has no READ_CONTACT permission and
+ // contact exist.
+ when(mPackageManager.checkPermission(Manifest.permission.READ_CONTACTS, PKG_NAME))
+ .thenReturn(PackageManager.PERMISSION_DENIED);
+ when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
+ anyInt(), eq(UserHandle.CURRENT))).thenThrow(new SecurityException());
+ inputResult.contactExists = true;
+ CallScreeningServiceFilter filter = new CallScreeningServiceFilter(mCall, PKG_NAME,
+ CallScreeningServiceFilter.PACKAGE_TYPE_USER_CHOSEN, mContext, mCallsManager,
+ mAppLabelProxy, mParcelableCallUtilsConverter);
+ filter.startFilterLookup(inputResult);
+ }
+
+ @SmallTest
+ @Test
+ public void testBindingCondition() {
+ // Make sure there will be binding if the package has READ_CONTACT permission and contact
+ // exist.
+ inputResult.contactExists = true;
+ CallScreeningServiceFilter filter = new CallScreeningServiceFilter(mCall, PKG_NAME,
+ CallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager,
+ mAppLabelProxy, mParcelableCallUtilsConverter);
+ filter.startFilterLookup(inputResult);
+ ServiceConnection connection = verifyBindingIntent();
+ connection.onServiceDisconnected(COMPONENT_NAME);
}
@SmallTest
@Test
public void testAllowCall() throws Exception {
- mFilter.startCallScreeningFilter(mCall, mCallback, PKG_NAME, APP_NAME,
- CallScreeningServiceFilter.CALL_SCREENING_FILTER_TYPE_USER_SELECTED);
+ CallScreeningServiceFilter filter = new CallScreeningServiceFilter(mCall, PKG_NAME,
+ CallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager,
+ mAppLabelProxy, mParcelableCallUtilsConverter);
+ CompletionStage<CallFilteringResult> resultFuture = filter.startFilterLookup(inputResult);
+
ServiceConnection serviceConnection = verifyBindingIntent();
+
serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
csAdapter.allowCall(CALL_ID);
- verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(PKG_NAME));
+ assertEquals(PASS_RESULT,
+ resultFuture.toCompletableFuture().get(
+ CallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT,
+ TimeUnit.MILLISECONDS));
+ serviceConnection.onServiceDisconnected(COMPONENT_NAME);
+ }
+
+ @SmallTest
+ @Test
+ public void testDisallowCall() throws Exception {
+ CallFilteringResult expectedResult = new CallFilteringResult.Builder()
+ .setShouldAllowCall(false)
+ .setShouldReject(true)
+ .setShouldSilence(false)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(true)
+ .setCallBlockReason(CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
+ .setCallScreeningAppName(APP_NAME)
+ .setCallScreeningComponentName(COMPONENT_NAME.flattenToString())
+ .build();
+ CallScreeningServiceFilter filter = new CallScreeningServiceFilter(mCall, PKG_NAME,
+ CallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager,
+ mAppLabelProxy, mParcelableCallUtilsConverter);
+ CompletionStage<CallFilteringResult> resultFuture = filter.startFilterLookup(inputResult);
+
+ ServiceConnection serviceConnection = verifyBindingIntent();
+
+ serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+ ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
+ csAdapter.disallowCall(CALL_ID,
+ true, // shouldReject
+ true, //shouldAddToCallLog
+ true, // shouldShowNotification
+ COMPONENT_NAME);
+ assertEquals(expectedResult,
+ resultFuture.toCompletableFuture().get(
+ CallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT,
+ TimeUnit.MILLISECONDS));
+ serviceConnection.onServiceDisconnected(COMPONENT_NAME);
}
@SmallTest
@Test
public void testSilenceCall() throws Exception {
- mFilter.startCallScreeningFilter(mCall, mCallback, PKG_NAME, APP_NAME,
- CallScreeningServiceFilter.CALL_SCREENING_FILTER_TYPE_USER_SELECTED);
+ CallFilteringResult expectedResult = new CallFilteringResult.Builder()
+ .setShouldAllowCall(true)
+ .setShouldReject(false)
+ .setShouldSilence(true)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(true)
+ .build();
+ CallScreeningServiceFilter filter = new CallScreeningServiceFilter(mCall, PKG_NAME,
+ CallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager,
+ mAppLabelProxy, mParcelableCallUtilsConverter);
+ CompletionStage<CallFilteringResult> resultFuture = filter.startFilterLookup(inputResult);
+
ServiceConnection serviceConnection = verifyBindingIntent();
+
serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
csAdapter.silenceCall(CALL_ID);
- verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT_WITH_SILENCE),
- eq(PKG_NAME));
+ assertEquals(expectedResult,
+ resultFuture.toCompletableFuture().get(
+ CallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT,
+ TimeUnit.MILLISECONDS));
+
+ serviceConnection.onServiceDisconnected(COMPONENT_NAME);
}
@SmallTest
@Test
- public void testDisallowCallForCarrierDefined() throws Exception {
- mResolveInfo.serviceInfo.packageName = CARRIER_DEFINED_CALL_SCREENING.getPackageName();
- mResolveInfo.serviceInfo.name = CARRIER_DEFINED_CALL_SCREENING.getClassName();
- setCarrierDefinedCallScreeningApplication();
- when(TelecomManager.from(mContext)).thenReturn(mTelecomManager);
- when(mTelecomManager.getDefaultDialerPackage()).thenReturn(DEFAULT_DIALER_PACKAGE);
+ public void testScreenCallFurther() throws Exception {
+ CallFilteringResult expectedResult = new CallFilteringResult.Builder()
+ .setShouldAllowCall(true)
+ .setShouldReject(false)
+ .setShouldSilence(false)
+ .setShouldScreenViaAudio(true)
+ .setCallScreeningAppName(APP_NAME)
+ .build();
+ CallScreeningServiceFilter filter = new CallScreeningServiceFilter(mCall, PKG_NAME,
+ CallScreeningServiceFilter.PACKAGE_TYPE_DEFAULT_DIALER, mContext, mCallsManager,
+ mAppLabelProxy, mParcelableCallUtilsConverter);
+ CompletionStage<CallFilteringResult> resultFuture = filter.startFilterLookup(inputResult);
- mFilter.startCallScreeningFilter(mCall, mCallback,
- CARRIER_DEFINED_CALL_SCREENING.getPackageName(),
- CARRIER_DEFINED_CALL_SCREENING_APP_NAME,
- CallScreeningServiceFilter.CALL_SCREENING_FILTER_TYPE_CARRIER);
ServiceConnection serviceConnection = verifyBindingIntent();
+
serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
- csAdapter.disallowCall(CALL_ID,
- true, // shouldReject
- false, // shouldAddToCallLog
- true, // shouldShowNotification
- CARRIER_DEFINED_CALL_SCREENING
- );
- verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(new CallFilteringResult(
- false, // shouldAllowCall
- true, // shouldReject
- false, // shouldAddToCallLog
- true, // shouldShowNotification
- CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
- CARRIER_DEFINED_CALL_SCREENING_APP_NAME, //callScreeningAppName
- CARRIER_DEFINED_CALL_SCREENING.flattenToString() //callScreeningComponentName
- )), eq(CARRIER_DEFINED_CALL_SCREENING.getPackageName()));
- }
-
- @SmallTest
- @Test
- public void testDisallowCallForDefaultDialer() throws Exception {
- mResolveInfo.serviceInfo.packageName = DEFAULT_DIALER_CALL_SCREENING.getPackageName();
- mResolveInfo.serviceInfo.name = DEFAULT_DIALER_CALL_SCREENING.getClassName();
- setCarrierDefinedCallScreeningApplication();
- when(TelecomManager.from(mContext)).thenReturn(mTelecomManager);
- when(mTelecomManager.getDefaultDialerPackage()).thenReturn(DEFAULT_DIALER_PACKAGE);
-
- mFilter.startCallScreeningFilter(mCall, mCallback,
- DEFAULT_DIALER_CALL_SCREENING.getPackageName(),
- DEFAULT_DIALER_APP_NAME,
- CallScreeningServiceFilter.CALL_SCREENING_FILTER_TYPE_DEFAULT_DIALER);
- ServiceConnection serviceConnection = verifyBindingIntent();
- serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
- ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
- csAdapter.disallowCall(CALL_ID,
- true, // shouldReject
- false, // shouldAddToCallLog
- true, // shouldShowNotification
- DEFAULT_DIALER_CALL_SCREENING
- );
- verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(new CallFilteringResult(
- false, // shouldAllowCall
- true, // shouldReject
- true, // shouldAddToCallLog
- true, // shouldShowNotification
- CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
- DEFAULT_DIALER_APP_NAME, //callScreeningAppName
- DEFAULT_DIALER_CALL_SCREENING.flattenToString() //callScreeningComponentName
- )), eq(DEFAULT_DIALER_CALL_SCREENING.getPackageName()));
- }
-
- @SmallTest
- @Test
- public void testDisallowCallForUserChosen() throws Exception {
- mResolveInfo.serviceInfo.packageName = USER_CHOSEN_CALL_SCREENING.getPackageName();
- mResolveInfo.serviceInfo.name = USER_CHOSEN_CALL_SCREENING.getClassName();
- setCarrierDefinedCallScreeningApplication();
- when(TelecomManager.from(mContext)).thenReturn(mTelecomManager);
- when(mTelecomManager.getDefaultDialerPackage()).thenReturn(DEFAULT_DIALER_PACKAGE);
-
- mFilter.startCallScreeningFilter(mCall, mCallback,
- USER_CHOSEN_CALL_SCREENING.getPackageName(),
- USER_CHOSEN_CALL_SCREENING_APP_NAME,
- CallScreeningServiceFilter.CALL_SCREENING_FILTER_TYPE_USER_SELECTED);
- ServiceConnection serviceConnection = verifyBindingIntent();
- serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
- ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
- csAdapter.disallowCall(CALL_ID,
- true, // shouldReject
- false, // shouldAddToCallLog
- true, // shouldShowNotification
- USER_CHOSEN_CALL_SCREENING
- );
- verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(new CallFilteringResult(
- false, // shouldAllowCall
- true, // shouldReject
- true, // shouldAddToCallLog
- true, // shouldShowNotification
- CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
- USER_CHOSEN_CALL_SCREENING_APP_NAME, //callScreeningAppName
- USER_CHOSEN_CALL_SCREENING.flattenToString() //callScreeningComponentName
- )), eq(USER_CHOSEN_CALL_SCREENING.getPackageName()));
+ csAdapter.screenCallFurther(CALL_ID);
+ assertEquals(expectedResult,
+ resultFuture.toCompletableFuture().get(
+ CallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT,
+ TimeUnit.MILLISECONDS));
+ serviceConnection.onServiceDisconnected(COMPONENT_NAME);
}
private ServiceConnection verifyBindingIntent() {
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
- ArgumentCaptor<ServiceConnection> serviceCaptor =
- ArgumentCaptor.forClass(ServiceConnection.class);
- verify(mContext).bindServiceAsUser(intentCaptor.capture(), serviceCaptor.capture(),
+ ArgumentCaptor<ServiceConnection> serviceCaptor = ArgumentCaptor
+ .forClass(ServiceConnection.class);
+ verify(mContext, timeout(CallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT))
+ .bindServiceAsUser(intentCaptor.capture(), serviceCaptor.capture(),
eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
eq(UserHandle.CURRENT));
Intent capturedIntent = intentCaptor.getValue();
assertEquals(CallScreeningService.SERVICE_INTERFACE, capturedIntent.getAction());
assertEquals(mResolveInfo.serviceInfo.packageName, capturedIntent.getPackage());
- assertEquals(new ComponentName(mResolveInfo.serviceInfo.packageName, mResolveInfo
- .serviceInfo.name), capturedIntent.getComponent());
+ assertEquals(new ComponentName(mResolveInfo.serviceInfo.packageName,
+ mResolveInfo.serviceInfo.name), capturedIntent.getComponent());
return serviceCaptor.getValue();
}
@@ -364,17 +339,9 @@
private ICallScreeningAdapter getCallScreeningAdapter() throws Exception {
ArgumentCaptor<ICallScreeningAdapter> captor =
ArgumentCaptor.forClass(ICallScreeningAdapter.class);
- verify(mCallScreeningService).screenCall(captor.capture(), nullable(ParcelableCall.class));
+ verify(mCallScreeningService,
+ timeout(CallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT))
+ .screenCall(captor.capture(), nullable(ParcelableCall.class));
return captor.getValue();
}
-
- private void setCarrierDefinedCallScreeningApplication() {
- String carrierDefined = CARRIER_DEFINED_CALL_SCREENING.flattenToString();
- PersistableBundle bundle = new PersistableBundle();
- bundle.putString(CarrierConfigManager.KEY_CARRIER_CALL_SCREENING_APP_STRING,
- carrierDefined);
- when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
- .thenReturn(mCarrierConfigManager);
- when(mCarrierConfigManager.getConfig()).thenReturn(bundle);
- }
}
diff --git a/tests/src/com/android/server/telecom/tests/CallTest.java b/tests/src/com/android/server/telecom/tests/CallTest.java
new file mode 100644
index 0000000..541d278
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2019 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.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.Toast;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+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.ConnectionServiceWrapper;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.PhoneNumberUtilsAdapter;
+import com.android.server.telecom.TelecomSystem;
+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.mockito.Mock;
+
+@RunWith(AndroidJUnit4.class)
+public class CallTest extends TelecomTestCase {
+ private static final Uri TEST_ADDRESS = Uri.parse("tel:555-1212");
+ private static final PhoneAccountHandle SIM_1_HANDLE = new PhoneAccountHandle(
+ ComponentName.unflattenFromString("com.foo/.Blah"), "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();
+
+ @Mock private CallsManager mMockCallsManager;
+ @Mock private CallerInfoLookupHelper mMockCallerInfoLookupHelper;
+ @Mock private PhoneAccountRegistrar mMockPhoneAccountRegistrar;
+ @Mock private ClockProxy mMockClockProxy;
+ @Mock private ToastFactory mMockToastProxy;
+ @Mock private Toast mMockToast;
+ @Mock private PhoneNumberUtilsAdapter mMockPhoneNumberUtilsAdapter;
+ @Mock private ConnectionServiceWrapper mMockConnectionService;
+
+ private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
+
+ @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));
+ doReturn(new ComponentName(mContext, CallTest.class))
+ .when(mMockConnectionService).getComponentName();
+ doReturn(mMockToast).when(mMockToastProxy).makeText(any(), anyInt(), anyInt());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ @SmallTest
+ public void testSetHasGoneActive() {
+ Call call = new Call(
+ "1", /* callId */
+ mContext,
+ mMockCallsManager,
+ mLock,
+ null /* ConnectionServiceRepository */,
+ mMockPhoneNumberUtilsAdapter,
+ TEST_ADDRESS,
+ null /* GatewayInfo */,
+ null /* connectionManagerPhoneAccountHandle */,
+ SIM_1_HANDLE,
+ Call.CALL_DIRECTION_INCOMING,
+ false /* shouldAttachToExistingConnection*/,
+ false /* isConference */,
+ mMockClockProxy,
+ mMockToastProxy);
+
+ assertFalse(call.hasGoneActiveBefore());
+ call.setState(CallState.ACTIVE, "");
+ assertTrue(call.hasGoneActiveBefore());
+ call.setState(CallState.AUDIO_PROCESSING, "");
+ assertTrue(call.hasGoneActiveBefore());
+ }
+
+ @Test
+ @SmallTest
+ public void testDisconnectCauseWhenAudioProcessing() {
+ Call call = new Call(
+ "1", /* callId */
+ mContext,
+ mMockCallsManager,
+ mLock,
+ null /* ConnectionServiceRepository */,
+ mMockPhoneNumberUtilsAdapter,
+ TEST_ADDRESS,
+ null /* GatewayInfo */,
+ null /* connectionManagerPhoneAccountHandle */,
+ SIM_1_HANDLE,
+ Call.CALL_DIRECTION_INCOMING,
+ false /* shouldAttachToExistingConnection*/,
+ false /* isConference */,
+ mMockClockProxy,
+ mMockToastProxy);
+ call.setState(CallState.AUDIO_PROCESSING, "");
+ call.disconnect();
+ call.setDisconnectCause(new DisconnectCause(DisconnectCause.LOCAL));
+ assertEquals(DisconnectCause.REJECTED, call.getDisconnectCause().getCode());
+ }
+
+ @Test
+ @SmallTest
+ public void testDisconnectCauseWhenAudioProcessingAfterActive() {
+ Call call = new Call(
+ "1", /* callId */
+ mContext,
+ mMockCallsManager,
+ mLock,
+ null /* ConnectionServiceRepository */,
+ mMockPhoneNumberUtilsAdapter,
+ TEST_ADDRESS,
+ null /* GatewayInfo */,
+ null /* connectionManagerPhoneAccountHandle */,
+ SIM_1_HANDLE,
+ Call.CALL_DIRECTION_INCOMING,
+ false /* shouldAttachToExistingConnection*/,
+ false /* isConference */,
+ mMockClockProxy,
+ mMockToastProxy);
+ call.setState(CallState.AUDIO_PROCESSING, "");
+ call.setState(CallState.ACTIVE, "");
+ call.setState(CallState.AUDIO_PROCESSING, "");
+ call.disconnect();
+ call.setDisconnectCause(new DisconnectCause(DisconnectCause.LOCAL));
+ assertEquals(DisconnectCause.LOCAL, call.getDisconnectCause().getCode());
+ }
+
+ @Test
+ @SmallTest
+ public void testDisconnectCauseWhenSimulatedRingingAndDisconnect() {
+ Call call = new Call(
+ "1", /* callId */
+ mContext,
+ mMockCallsManager,
+ mLock,
+ null /* ConnectionServiceRepository */,
+ mMockPhoneNumberUtilsAdapter,
+ TEST_ADDRESS,
+ null /* GatewayInfo */,
+ null /* connectionManagerPhoneAccountHandle */,
+ SIM_1_HANDLE,
+ Call.CALL_DIRECTION_INCOMING,
+ false /* shouldAttachToExistingConnection*/,
+ false /* isConference */,
+ mMockClockProxy,
+ mMockToastProxy);
+ call.setState(CallState.SIMULATED_RINGING, "");
+ call.disconnect();
+ call.setDisconnectCause(new DisconnectCause(DisconnectCause.LOCAL));
+ assertEquals(DisconnectCause.MISSED, call.getDisconnectCause().getCode());
+ }
+
+ @Test
+ @SmallTest
+ public void testDisconnectCauseWhenSimulatedRingingAndReject() {
+ Call call = new Call(
+ "1", /* callId */
+ mContext,
+ mMockCallsManager,
+ mLock,
+ null /* ConnectionServiceRepository */,
+ mMockPhoneNumberUtilsAdapter,
+ TEST_ADDRESS,
+ null /* GatewayInfo */,
+ null /* connectionManagerPhoneAccountHandle */,
+ SIM_1_HANDLE,
+ Call.CALL_DIRECTION_INCOMING,
+ false /* shouldAttachToExistingConnection*/,
+ false /* isConference */,
+ mMockClockProxy,
+ mMockToastProxy);
+ call.setState(CallState.SIMULATED_RINGING, "");
+ call.reject(false, "");
+ call.setDisconnectCause(new DisconnectCause(DisconnectCause.LOCAL));
+ assertEquals(DisconnectCause.REJECTED, call.getDisconnectCause().getCode());
+ }
+
+ @Test
+ @SmallTest
+ public void testCanPullCallRemovedDuringEmergencyCall() {
+ Call call = new Call(
+ "1", /* callId */
+ mContext,
+ mMockCallsManager,
+ mLock,
+ null /* ConnectionServiceRepository */,
+ mMockPhoneNumberUtilsAdapter,
+ TEST_ADDRESS,
+ null /* GatewayInfo */,
+ null /* connectionManagerPhoneAccountHandle */,
+ SIM_1_HANDLE,
+ Call.CALL_DIRECTION_INCOMING,
+ false /* shouldAttachToExistingConnection*/,
+ false /* isConference */,
+ mMockClockProxy,
+ mMockToastProxy);
+ boolean[] hasCalledConnectionCapabilitiesChanged = new boolean[1];
+ call.addListener(new Call.ListenerBase() {
+ @Override
+ public void onConnectionCapabilitiesChanged(Call call) {
+ hasCalledConnectionCapabilitiesChanged[0] = true;
+ }
+ });
+ call.setConnectionService(mMockConnectionService);
+ call.setConnectionProperties(Connection.PROPERTY_IS_EXTERNAL_CALL);
+ call.setConnectionCapabilities(Connection.CAPABILITY_CAN_PULL_CALL);
+ call.setState(CallState.ACTIVE, "");
+ assertTrue(hasCalledConnectionCapabilitiesChanged[0]);
+ // Capability should be present
+ assertTrue((call.getConnectionCapabilities() | Connection.CAPABILITY_CAN_PULL_CALL) > 0);
+ hasCalledConnectionCapabilitiesChanged[0] = false;
+ // Emergency call in progress
+ call.setIsPullExternalCallSupported(false /*isPullCallSupported*/);
+ assertTrue(hasCalledConnectionCapabilitiesChanged[0]);
+ // Capability should not be present
+ assertEquals(0, call.getConnectionCapabilities() & Connection.CAPABILITY_CAN_PULL_CALL);
+ hasCalledConnectionCapabilitiesChanged[0] = false;
+ // Emergency call complete
+ call.setIsPullExternalCallSupported(true /*isPullCallSupported*/);
+ assertTrue(hasCalledConnectionCapabilitiesChanged[0]);
+ // Capability should be present
+ assertEquals(Connection.CAPABILITY_CAN_PULL_CALL,
+ call.getConnectionCapabilities() & Connection.CAPABILITY_CAN_PULL_CALL);
+ }
+
+ @Test
+ @SmallTest
+ public void testCanNotPullCallDuringEmergencyCall() {
+ Call call = new Call(
+ "1", /* callId */
+ mContext,
+ mMockCallsManager,
+ mLock,
+ null /* ConnectionServiceRepository */,
+ mMockPhoneNumberUtilsAdapter,
+ TEST_ADDRESS,
+ null /* GatewayInfo */,
+ null /* connectionManagerPhoneAccountHandle */,
+ SIM_1_HANDLE,
+ Call.CALL_DIRECTION_INCOMING,
+ false /* shouldAttachToExistingConnection*/,
+ false /* isConference */,
+ mMockClockProxy,
+ mMockToastProxy);
+ call.setConnectionService(mMockConnectionService);
+ call.setConnectionProperties(Connection.PROPERTY_IS_EXTERNAL_CALL);
+ call.setConnectionCapabilities(Connection.CAPABILITY_CAN_PULL_CALL);
+ call.setState(CallState.ACTIVE, "");
+ // Emergency call in progress, this should show a toast and never call pullExternalCall
+ // on the ConnectionService.
+ doReturn(true).when(mMockCallsManager).isInEmergencyCall();
+ call.pullExternalCall();
+ verify(mMockConnectionService, never()).pullExternalCall(any());
+ verify(mMockToast).show();
+ }
+
+ @Test
+ @SmallTest
+ public void testCallDirection() {
+ Call call = new Call(
+ "1", /* callId */
+ mContext,
+ mMockCallsManager,
+ mLock,
+ null /* ConnectionServiceRepository */,
+ mMockPhoneNumberUtilsAdapter,
+ TEST_ADDRESS,
+ null /* GatewayInfo */,
+ null /* connectionManagerPhoneAccountHandle */,
+ SIM_1_HANDLE,
+ Call.CALL_DIRECTION_UNDEFINED,
+ false /* shouldAttachToExistingConnection*/,
+ true /* isConference */,
+ mMockClockProxy,
+ mMockToastProxy);
+ boolean[] hasCallDirectionChanged = new boolean[1];
+ call.addListener(new Call.ListenerBase() {
+ @Override
+ public void onCallDirectionChanged(Call call) {
+ hasCallDirectionChanged[0] = true;
+ }
+ });
+ assertFalse(call.isIncoming());
+ call.setCallDirection(Call.CALL_DIRECTION_INCOMING);
+ assertTrue(hasCallDirectionChanged[0]);
+ assertTrue(call.isIncoming());
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/CallerInfoAsyncQueryFactoryFixture.java b/tests/src/com/android/server/telecom/tests/CallerInfoAsyncQueryFactoryFixture.java
index 5f9555c..68db09c 100644
--- a/tests/src/com/android/server/telecom/tests/CallerInfoAsyncQueryFactoryFixture.java
+++ b/tests/src/com/android/server/telecom/tests/CallerInfoAsyncQueryFactoryFixture.java
@@ -16,8 +16,8 @@
package com.android.server.telecom.tests;
-import com.android.internal.telephony.CallerInfo;
-import com.android.internal.telephony.CallerInfoAsyncQuery;
+import android.telecom.CallerInfo;
+import android.telecom.CallerInfoAsyncQuery;
import com.android.server.telecom.CallerInfoAsyncQueryFactory;
import android.content.Context;
@@ -27,7 +27,9 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Controls a test {@link CallerInfoAsyncQueryFactory} to abstract away the asynchronous retrieval
@@ -58,16 +60,24 @@
r.mCookie = cookie;
r.mListener = listener;
mRequests.add(r);
+ if (mStoredResponse != null) {
+ listener.onQueryComplete(token, cookie, mStoredResponse);
+ }
return Mockito.mock(CallerInfoAsyncQuery.class);
}
};
final List<Request> mRequests = Collections.synchronizedList(new ArrayList<Request>());
+ private CallerInfo mStoredResponse;
public CallerInfoAsyncQueryFactoryFixture() throws Exception {
Log.i(this, "Creating ...");
}
+ public void setResponse(CallerInfo callerInfo) {
+ mStoredResponse = callerInfo;
+ }
+
@Override
public CallerInfoAsyncQueryFactory getTestDouble() {
return mCallerInfoAsyncQueryFactory;
diff --git a/tests/src/com/android/server/telecom/tests/CallerInfoLookupHelperTest.java b/tests/src/com/android/server/telecom/tests/CallerInfoLookupHelperTest.java
index e26d7e5..7c001c0 100644
--- a/tests/src/com/android/server/telecom/tests/CallerInfoLookupHelperTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallerInfoLookupHelperTest.java
@@ -37,13 +37,14 @@
import androidx.test.InstrumentationRegistry;
-import com.android.internal.telephony.CallerInfo;
-import com.android.internal.telephony.CallerInfoAsyncQuery;
+import android.telecom.CallerInfo;
+import android.telecom.CallerInfoAsyncQuery;
import com.android.server.telecom.CallerInfoAsyncQueryFactory;
import com.android.server.telecom.CallerInfoLookupHelper;
import com.android.server.telecom.ContactsAsyncHelper;
import com.android.server.telecom.TelecomSystem;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -102,6 +103,12 @@
}
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
@SmallTest
@Test
public void testLookupWithEmptyHandle() {
@@ -118,7 +125,7 @@
public void testSimpleLookup() {
CallerInfoLookupHelper.OnQueryCompleteListener listener = mock(
CallerInfoLookupHelper.OnQueryCompleteListener.class);
- mCallerInfo1.contactDisplayPhotoUri = CONTACTS_PHOTO_URI;
+ mCallerInfo1.SetContactDisplayPhotoUri(CONTACTS_PHOTO_URI);
mCallerInfoLookupHelper.startLookup(URI1, listener);
waitForActionCompletion();
@@ -157,7 +164,7 @@
CallerInfoLookupHelper.OnQueryCompleteListener.class);
CallerInfoLookupHelper.OnQueryCompleteListener otherListener = mock(
CallerInfoLookupHelper.OnQueryCompleteListener.class);
- mCallerInfo1.contactDisplayPhotoUri = CONTACTS_PHOTO_URI;
+ mCallerInfo1.SetContactDisplayPhotoUri(CONTACTS_PHOTO_URI);
mCallerInfoLookupHelper.startLookup(URI1, callListener);
mCallerInfoLookupHelper.startLookup(URI1, otherListener);
@@ -198,7 +205,7 @@
CallerInfoLookupHelper.OnQueryCompleteListener.class);
CallerInfoLookupHelper.OnQueryCompleteListener otherListener = mock(
CallerInfoLookupHelper.OnQueryCompleteListener.class);
- mCallerInfo1.contactDisplayPhotoUri = CONTACTS_PHOTO_URI;
+ mCallerInfo1.SetContactDisplayPhotoUri(CONTACTS_PHOTO_URI);
mCallerInfoLookupHelper.startLookup(URI1, callListener);
waitForActionCompletion();
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 7adc99a..d164302 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -16,6 +16,7 @@
package com.android.server.telecom.tests;
+import static junit.framework.Assert.assertNotNull;
import static junit.framework.TestCase.fail;
import static org.junit.Assert.assertEquals;
@@ -25,11 +26,14 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyChar;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.timeout;
@@ -38,11 +42,13 @@
import static org.mockito.Mockito.when;
import android.content.ComponentName;
+import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.telecom.CallerInfo;
import android.telecom.Connection;
import android.telecom.Log;
import android.telecom.DisconnectCause;
@@ -54,8 +60,8 @@
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.Toast;
-import com.android.internal.telephony.CallerInfo;
import com.android.server.telecom.AsyncRingtonePlayer;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallAudioManager;
@@ -64,10 +70,10 @@
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallerInfoLookupHelper;
import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.CallsManagerListenerBase;
import com.android.server.telecom.ClockProxy;
import com.android.server.telecom.ConnectionServiceFocusManager;
import com.android.server.telecom.ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory;
-import com.android.server.telecom.ConnectionServiceWrapper;
import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.EmergencyCallHelper;
import com.android.server.telecom.HeadsetMediaButton;
@@ -89,7 +95,13 @@
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.CallFilteringResult;
+import com.android.server.telecom.callfiltering.IncomingCallFilter;
+import com.android.server.telecom.ui.AudioProcessingNotification;
+import com.android.server.telecom.ui.DisconnectedCallNotifier;
+import com.android.server.telecom.ui.ToastFactory;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -122,13 +134,18 @@
new UserHandle(SECONDARY_USER_ID));
private static final PhoneAccountHandle SIM_2_HANDLE = new PhoneAccountHandle(
ComponentName.unflattenFromString("com.foo/.Blah"), "Sim2");
+ private static final PhoneAccountHandle CONNECTION_MGR_1_HANDLE = new PhoneAccountHandle(
+ ComponentName.unflattenFromString("com.bar/.Conn"), "Cm1");
+ private static final PhoneAccountHandle CONNECTION_MGR_2_HANDLE = new PhoneAccountHandle(
+ ComponentName.unflattenFromString("com.spa/.Conn"), "Cm2");
private static final PhoneAccountHandle VOIP_1_HANDLE = new PhoneAccountHandle(
ComponentName.unflattenFromString("com.voip/.Stuff"), "Voip1");
private static final PhoneAccountHandle SELF_MANAGED_HANDLE = new PhoneAccountHandle(
ComponentName.unflattenFromString("com.baz/.Self"), "Self");
private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.Builder(SIM_1_HANDLE, "Sim1")
.setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
- | PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ | PhoneAccount.CAPABILITY_CALL_PROVIDER
+ | PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
.setIsEnabled(true)
.build();
private static final PhoneAccount SIM_2_ACCOUNT = new PhoneAccount.Builder(SIM_2_HANDLE, "Sim2")
@@ -155,6 +172,8 @@
private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
@Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
@Mock private MissedCallNotifier mMissedCallNotifier;
+ @Mock private DisconnectedCallNotifier.Factory mDisconnectedCallNotifierFactory;
+ @Mock private DisconnectedCallNotifier mDisconnectedCallNotifier;
@Mock private PhoneAccountRegistrar mPhoneAccountRegistrar;
@Mock private HeadsetMediaButton mHeadsetMediaButton;
@Mock private HeadsetMediaButtonFactory mHeadsetMediaButtonFactory;
@@ -174,6 +193,7 @@
@Mock private EmergencyCallHelper mEmergencyCallHelper;
@Mock private InCallTonePlayer.ToneGeneratorFactory mToneGeneratorFactory;
@Mock private ClockProxy mClockProxy;
+ @Mock private AudioProcessingNotification mAudioProcessingNotification;
@Mock private InCallControllerFactory mInCallControllerFactory;
@Mock private InCallController mInCallController;
@Mock private ConnectionServiceFocusManager mConnectionSvrFocusMgr;
@@ -183,6 +203,10 @@
@Mock private CallAudioModeStateMachine.Factory mCallAudioModeStateMachineFactory;
@Mock private BluetoothStateReceiver mBluetoothStateReceiver;
@Mock private RoleManagerAdapter mRoleManagerAdapter;
+ @Mock private IncomingCallFilter.Factory mIncomingCallFilterFactory;
+ @Mock private IncomingCallFilter mIncomingCallFilter;
+ @Mock private ToastFactory mToastFactory;
+ @Mock private Toast mToast;
private CallsManager mCallsManager;
@@ -203,15 +227,20 @@
anyInt())).thenReturn(mCallAudioRouteStateMachine);
when(mCallAudioModeStateMachineFactory.create(any(), any()))
.thenReturn(mCallAudioModeStateMachine);
+ when(mIncomingCallFilterFactory.create(any(), any(), any(), any(), any(), any()))
+ .thenReturn(mIncomingCallFilter);
when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
when(mClockProxy.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime());
when(mConnSvrFocusManagerFactory.create(any())).thenReturn(mConnectionSvrFocusMgr);
doNothing().when(mRoleManagerAdapter).setCurrentUserHandle(any());
+ when(mDisconnectedCallNotifierFactory.create(any(Context.class),any(CallsManager.class)))
+ .thenReturn(mDisconnectedCallNotifier);
mCallsManager = new CallsManager(
mComponentContextFixture.getTestDouble().getApplicationContext(),
mLock,
mCallerInfoLookupHelper,
mMissedCallNotifier,
+ mDisconnectedCallNotifierFactory,
mPhoneAccountRegistrar,
mHeadsetMediaButtonFactory,
mProximitySensorManagerFactory,
@@ -228,11 +257,14 @@
mEmergencyCallHelper,
mToneGeneratorFactory,
mClockProxy,
+ mAudioProcessingNotification,
mBluetoothStateReceiver,
mCallAudioRouteStateMachineFactory,
mCallAudioModeStateMachineFactory,
mInCallControllerFactory,
- mRoleManagerAdapter);
+ mRoleManagerAdapter,
+ mIncomingCallFilterFactory,
+ mToastFactory);
when(mPhoneAccountRegistrar.getPhoneAccount(
eq(SELF_MANAGED_HANDLE), any())).thenReturn(SELF_MANAGED_ACCOUNT);
@@ -240,13 +272,21 @@
eq(SIM_1_HANDLE), any())).thenReturn(SIM_1_ACCOUNT);
when(mPhoneAccountRegistrar.getPhoneAccount(
eq(SIM_2_HANDLE), any())).thenReturn(SIM_2_ACCOUNT);
+ when(mToastFactory.makeText(any(), anyInt(), anyInt())).thenReturn(mToast);
+ when(mToastFactory.makeText(any(), any(), anyInt())).thenReturn(mToast);
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
}
@MediumTest
@Test
public void testConstructPossiblePhoneAccounts() throws Exception {
// Should be empty since the URI is null.
- assertEquals(0, mCallsManager.constructPossiblePhoneAccounts(null, null, false).size());
+ assertEquals(0, mCallsManager.constructPossiblePhoneAccounts(null, null, false, false).size());
}
/**
@@ -261,7 +301,7 @@
Call ongoingCall = new Call(
"1", /* callId */
- mComponentContextFixture.getTestDouble(),
+ mContext,
mCallsManager,
mLock,
null /* ConnectionServiceRepository */,
@@ -273,12 +313,13 @@
Call.CALL_DIRECTION_INCOMING,
false /* shouldAttachToExistingConnection*/,
false /* isConference */,
- mClockProxy);
+ mClockProxy,
+ mToastFactory);
ongoingCall.setState(CallState.ACTIVE, "just cuz");
mCallsManager.addCall(ongoingCall);
List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
- TEST_ADDRESS, null, false);
+ TEST_ADDRESS, null, false, false);
assertEquals(1, phoneAccountHandles.size());
assertEquals(SIM_2_HANDLE, phoneAccountHandles.get(0));
}
@@ -293,7 +334,7 @@
setupMsimAccounts();
List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
- TEST_ADDRESS, null, false);
+ TEST_ADDRESS, null, false, false);
assertEquals(2, phoneAccountHandles.size());
}
@@ -322,7 +363,7 @@
public void testFindOutgoingCallPhoneAccountSelfManaged() throws Exception {
setupCallerInfoLookupHelper();
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- SELF_MANAGED_HANDLE, TEST_ADDRESS, false /* isVideo */, null /* userHandle */)
+ SELF_MANAGED_HANDLE, TEST_ADDRESS, false /* isVideo */, false /* isEmergency */, null /* userHandle */)
.get();
assertEquals(1, accounts.size());
assertEquals(SELF_MANAGED_HANDLE, accounts.get(0));
@@ -340,11 +381,11 @@
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
SIM_1_HANDLE);
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
- any(), anyInt())).thenReturn(
+ any(), anyInt(), anyInt())).thenReturn(
new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- null /* phoneAcct */, TEST_ADDRESS, false /* isVideo */, null /* userHandle */)
+ null /* phoneAcct */, TEST_ADDRESS, false /* isVideo */, false /* isEmergency */, null /* userHandle */)
.get();
// Should have found just the default.
@@ -364,11 +405,11 @@
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
null);
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
- any(), anyInt())).thenReturn(
+ any(), anyInt(), anyInt())).thenReturn(
new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- null /* phoneAcct */, TEST_ADDRESS, false /* isVideo */, null /* userHandle */)
+ null /* phoneAcct */, TEST_ADDRESS, false /* isVideo */, false /* isEmergency */, null /* userHandle */)
.get();
assertEquals(2, accounts.size());
@@ -388,11 +429,11 @@
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
null);
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
- any(), eq(PhoneAccount.CAPABILITY_VIDEO_CALLING))).thenReturn(
+ any(), eq(PhoneAccount.CAPABILITY_VIDEO_CALLING), anyInt())).thenReturn(
new ArrayList<>(Arrays.asList(SIM_2_HANDLE)));
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, null /* userHandle */)
+ null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, false /* isEmergency */, null /* userHandle */)
.get();
assertEquals(1, accounts.size());
@@ -412,14 +453,14 @@
null);
// When querying for video capable accounts, return nothing.
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
- any(), eq(PhoneAccount.CAPABILITY_VIDEO_CALLING))).thenReturn(
+ any(), eq(PhoneAccount.CAPABILITY_VIDEO_CALLING), anyInt())).thenReturn(
Collections.emptyList());
// When querying for non-video capable accounts, return one.
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
- any(), eq(0 /* none specified */))).thenReturn(
+ any(), eq(0 /* none specified */), anyInt())).thenReturn(
new ArrayList<>(Arrays.asList(SIM_1_HANDLE)));
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, null /* userHandle */)
+ null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, false /* isEmergency */, null /* userHandle */)
.get();
// Should have found one.
@@ -438,11 +479,11 @@
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
null);
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
- any(), anyInt())).thenReturn(
+ any(), anyInt(), anyInt())).thenReturn(
new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- SIM_2_HANDLE, TEST_ADDRESS, false /* isVideo */, null /* userHandle */).get();
+ SIM_2_HANDLE, TEST_ADDRESS, false /* isVideo */, false /* isEmergency */, null /* userHandle */).get();
assertEquals(1, accounts.size());
assertTrue(accounts.contains(SIM_2_HANDLE));
@@ -459,11 +500,11 @@
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
null);
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
- any(), anyInt())).thenReturn(
+ any(), anyInt(), anyInt())).thenReturn(
new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- null, TEST_ADDRESS2, false /* isVideo */, Process.myUserHandle()).get();
+ null, TEST_ADDRESS2, false /* isVideo */, false /* isEmergency */, Process.myUserHandle()).get();
assertEquals(1, accounts.size());
assertTrue(accounts.contains(SIM_1_HANDLE));
@@ -603,6 +644,23 @@
@SmallTest
@Test
+ public void testDuplicateAnswerCall() {
+ Call incomingCall = addSpyCall(CallState.RINGING);
+ doAnswer(invocation -> {
+ doReturn(CallState.ANSWERED).when(incomingCall).getState();
+ return null;
+ }).when(incomingCall).answer(anyInt());
+ mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
+ verifyFocusRequestAndExecuteCallback(incomingCall);
+ reset(mConnectionSvrFocusMgr);
+ mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
+ verifyFocusRequestAndExecuteCallback(incomingCall);
+
+ verify(incomingCall, times(2)).answer(anyInt());
+ }
+
+ @SmallTest
+ @Test
public void testAnswerCallWhenOngoingCallCanBeHeld() {
// GIVEN a CallsManager with ongoing call, and this call can be held
Call ongoingCall = addSpyCall();
@@ -611,7 +669,7 @@
when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
// WHEN answer an incoming call
- Call incomingCall = addSpyCall();
+ Call incomingCall = addSpyCall(CallState.RINGING);
mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
// THEN the ongoing call is held and the focus request for incoming call is sent
@@ -729,7 +787,7 @@
// GIVEN a CallsManager with no ongoing call.
// WHEN answer an incoming call
- Call incomingCall = addSpyCall();
+ Call incomingCall = addSpyCall(CallState.RINGING);
mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
// THEN the focus request for incoming call is sent
@@ -745,7 +803,7 @@
// GIVEN a CallsManager with no ongoing call.
// WHEN answer an already active call
- Call incomingCall = addSpyCall();
+ Call incomingCall = addSpyCall(CallState.RINGING);
mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
// THEN the focus request for incoming call is sent
@@ -754,8 +812,8 @@
// and the incoming call is answered.
verify(incomingCall).answer(VideoProfile.STATE_AUDIO_ONLY);
- // and the incoming call's state is still ACTIVE
- assertEquals(CallState.ACTIVE, incomingCall.getState());
+ // and the incoming call's state is now ANSWERED
+ assertEquals(CallState.ANSWERED, incomingCall.getState());
}
@SmallTest
@@ -891,6 +949,10 @@
mCallsManager.onMediaButton(HeadsetMediaButton.SHORT_PRESS);
// THEN the incoming call is answered
+ ArgumentCaptor<CallsManager.RequestCallback> captor = ArgumentCaptor.forClass(
+ CallsManager.RequestCallback.class);
+ verify(mConnectionSvrFocusMgr).requestFocus(eq(incomingCall), captor.capture());
+ captor.getValue().onRequestFocusDone(incomingCall);
verify(incomingCall).answer(VideoProfile.STATE_AUDIO_ONLY);
}
@@ -1024,7 +1086,8 @@
public void testIsInEmergencyCallLocal() {
// Setup a call which is considered emergency based on its phone number.
Call ongoingCall = addSpyCall();
- when(mPhoneNumberUtilsAdapter.isLocalEmergencyNumber(any(), any())).thenReturn(true);
+ when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+ .thenReturn(true);
ongoingCall.setHandle(Uri.fromParts("tel", "5551212", null),
TelecomManager.PRESENTATION_ALLOWED);
@@ -1033,6 +1096,121 @@
assertTrue(mCallsManager.isInEmergencyCall());
}
+ @SmallTest
+ @Test
+ public void testIsInEmergencyCallLocalDisconnected() {
+ // Setup a call which is considered emergency based on its phone number.
+ Call ongoingCall = addSpyCall();
+ when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+ .thenReturn(true);
+ ongoingCall.setHandle(Uri.fromParts("tel", "5551212", null),
+ TelecomManager.PRESENTATION_ALLOWED);
+
+ // and then set it as disconnected.
+ ongoingCall.setState(CallState.DISCONNECTED, "");
+ assertTrue(ongoingCall.isEmergencyCall());
+ assertFalse(ongoingCall.isNetworkIdentifiedEmergencyCall());
+ assertFalse(mCallsManager.isInEmergencyCall());
+ }
+
+ @SmallTest
+ @Test
+ public void testHasEmergencyCallIncomingCallPermitted() {
+ // Setup a call which is considered emergency based on its phone number.
+ Call ongoingCall = addSpyCall();
+ when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+ .thenReturn(true);
+ ongoingCall.setHandle(Uri.fromParts("tel", "5551212", null),
+ TelecomManager.PRESENTATION_ALLOWED);
+ when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SELF_MANAGED_HANDLE))
+ .thenReturn(SELF_MANAGED_ACCOUNT);
+ when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SIM_1_HANDLE))
+ .thenReturn(SIM_1_ACCOUNT);
+
+ assertFalse(mCallsManager.isIncomingCallPermitted(null, SELF_MANAGED_HANDLE));
+ assertFalse(mCallsManager.isIncomingCallPermitted(null, SIM_1_HANDLE));
+ }
+
+ @SmallTest
+ @Test
+ public void testMakeRoomForOutgoingCallAudioProcessingInProgress() {
+ Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.AUDIO_PROCESSING);
+
+ Call newEmergencyCall = createCall(SIM_1_HANDLE, CallState.NEW);
+ when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+ .thenReturn(true);
+ newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null),
+ TelecomManager.PRESENTATION_ALLOWED);
+
+ assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall));
+ verify(ongoingCall).disconnect(anyLong(), anyString());
+ }
+
+ @SmallTest
+ @Test
+ public void testMakeRoomForEmergencyDuringIncomingCall() {
+ Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.RINGING);
+
+ Call newEmergencyCall = createCall(SIM_1_HANDLE, CallState.NEW);
+ when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+ .thenReturn(true);
+ newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null),
+ TelecomManager.PRESENTATION_ALLOWED);
+
+ assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall));
+ verify(ongoingCall).reject(anyBoolean(), any(), any());
+ }
+
+ @SmallTest
+ @Test
+ public void testMakeRoomForEmergencyCallSimulatedRingingInProgress() {
+ Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.SIMULATED_RINGING);
+
+ Call newEmergencyCall = createCall(SIM_1_HANDLE, CallState.NEW);
+ when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+ .thenReturn(true);
+ newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null),
+ TelecomManager.PRESENTATION_ALLOWED);
+
+ assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall));
+ verify(ongoingCall).disconnect(anyString());
+ }
+
+ @SmallTest
+ @Test
+ public void testMakeRoomForEmergencyCallSimulatedRingingInProgressHasBeenActive() {
+ Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.ACTIVE);
+ ongoingCall.setState(CallState.SIMULATED_RINGING, "");
+
+ Call newEmergencyCall = createCall(SIM_1_HANDLE, CallState.NEW);
+ when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+ .thenReturn(true);
+ newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null),
+ TelecomManager.PRESENTATION_ALLOWED);
+
+ assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall));
+ verify(ongoingCall).reject(anyBoolean(), any(), any());
+ }
+
+ @SmallTest
+ @Test
+ public void testMakeRoomForEmergencyCallDuringActiveAndRingingCallDisconnectRinging() {
+ when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SIM_1_HANDLE))
+ .thenReturn(SIM_1_ACCOUNT);
+ Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+ doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+ Call ringingCall = addSpyCall(SIM_1_HANDLE, CallState.RINGING);
+
+ Call newEmergencyCall = createCall(SIM_1_HANDLE, CallState.NEW);
+ when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+ .thenReturn(true);
+ newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null),
+ TelecomManager.PRESENTATION_ALLOWED);
+
+ assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall));
+ verify(ringingCall).reject(anyBoolean(), any(), any());
+ }
+
/**
* Verifies that changes to a {@link PhoneAccount}'s
* {@link PhoneAccount#CAPABILITY_VIDEO_CALLING} capability will be reflected on a call.
@@ -1047,7 +1225,6 @@
@Override
public void onConnectionCapabilitiesChanged(Call call) {
try {
- Log.i("TYLER", "Listener got " + call.getConnectionCapabilities());
capabilitiesQueue.put(call.getConnectionCapabilities());
} catch (InterruptedException e) {
fail();
@@ -1070,28 +1247,196 @@
ongoingCall.setConnectionCapabilities(
Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
newCapabilities = capabilitiesQueue.poll(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
- assertTrue(Connection.can(newCapabilities,
- Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
+ assertTrue((newCapabilities & Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL)
+ == Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
assertTrue(ongoingCall.isVideoCallingSupportedByPhoneAccount());
+ }
- // Fire a changed event for the phone account making it not capable.
- mCallsManager.getPhoneAccountListener().onPhoneAccountChanged(mPhoneAccountRegistrar,
- SIM_2_ACCOUNT);
- newCapabilities = capabilitiesQueue.poll(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
- assertFalse(Connection.can(newCapabilities,
- Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
- assertFalse(ongoingCall.isVideoCallingSupportedByPhoneAccount());
+ /**
+ * Verifies that adding and removing a call triggers external calls to have capabilities
+ * recalculated.
+ */
+ @SmallTest
+ @Test
+ public void testExternalCallCapabilitiesUpdated() throws InterruptedException {
+ Call externalCall = addSpyCall(SIM_2_HANDLE, null, CallState.ACTIVE,
+ Connection.CAPABILITY_CAN_PULL_CALL, Connection.PROPERTY_IS_EXTERNAL_CALL);
+ LinkedBlockingQueue<Integer> capabilitiesQueue = new LinkedBlockingQueue<>(1);
+ externalCall.addListener(new Call.ListenerBase() {
+ @Override
+ public void onConnectionCapabilitiesChanged(Call call) {
+ try {
+ capabilitiesQueue.put(call.getConnectionCapabilities());
+ } catch (InterruptedException e) {
+ fail();
+ }
+ }
+ });
- // Fire a change for an unrelated phone account.
- PhoneAccount anotherVideoCapableAcct = new PhoneAccount.Builder(SIM_1_ACCOUNT)
- .setCapabilities(SIM_2_ACCOUNT.getCapabilities()
- | PhoneAccount.CAPABILITY_VIDEO_CALLING)
+ Call call = createSpyCall(SIM_2_HANDLE, CallState.DIALING);
+ doReturn(true).when(call).isEmergencyCall();
+ mCallsManager.addCall(call);
+ Integer result = capabilitiesQueue.poll(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+ assertNotNull(result);
+ assertEquals(0, Connection.CAPABILITY_CAN_PULL_CALL & result);
+
+ mCallsManager.removeCall(call);
+ result = capabilitiesQueue.poll(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+ assertNotNull(result);
+ assertEquals(Connection.CAPABILITY_CAN_PULL_CALL,
+ Connection.CAPABILITY_CAN_PULL_CALL & result);
+ }
+
+ /**
+ * Verifies that speakers is disabled when there's no video capabilities, even if a video call
+ * tried to place.
+ * @throws Exception
+ */
+ @SmallTest
+ @Test
+ public void testSpeakerDisabledWhenNoVideoCapabilities() throws Exception {
+ Call outgoingCall = addSpyCall(CallState.NEW);
+ when(mPhoneAccountRegistrar.getPhoneAccount(
+ any(PhoneAccountHandle.class), any(UserHandle.class))).thenReturn(SIM_1_ACCOUNT);
+ mCallsManager.placeOutgoingCall(outgoingCall, TEST_ADDRESS, null, true,
+ VideoProfile.STATE_TX_ENABLED);
+ assertFalse(outgoingCall.getStartWithSpeakerphoneOn());
+ }
+
+ /**
+ * Verify that a parent call will inherit the connect time of its children.
+ * @throws Exception
+ */
+ @SmallTest
+ @Test
+ public void testParentInheritsChildConnectTime() throws Exception {
+ Call callSim1 = createCall(SIM_1_HANDLE, null, CallState.ACTIVE);
+ Call callSim2 = createCall(SIM_1_HANDLE, null, CallState.ACTIVE);
+ callSim1.setConnectTimeMillis(100);
+
+ // Pretend it is a conference made later.
+ callSim2.setConnectTimeMillis(0);
+
+ // Make the first call a child of the second (pretend conference).
+ callSim1.setChildOf(callSim2);
+
+ assertEquals(100, callSim2.getConnectTimeMillis());
+
+ // Add another later call.
+ Call callSim3 = createCall(SIM_1_HANDLE, null, CallState.ACTIVE);
+ callSim3.setConnectTimeMillis(200);
+ callSim3.setChildOf(callSim2);
+
+ // Later call shouldn't impact parent.
+ assertEquals(100, callSim2.getConnectTimeMillis());
+ }
+
+ /**
+ * Make sure that CallsManager handles a screening result that has both
+ * silence and screen-further set to true as a request to screen further.
+ * @throws Exception
+ */
+ @SmallTest
+ @Test
+ public void testHandleSilenceVsBackgroundScreeningOrdering() throws Exception {
+ Call screenedCall = mock(Call.class);
+ String appName = "blah";
+ CallFilteringResult result = new CallFilteringResult.Builder()
+ .setShouldAllowCall(true)
+ .setShouldReject(false)
+ .setShouldSilence(true)
+ .setShouldScreenViaAudio(true)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(true)
+ .setCallScreeningAppName(appName)
.build();
- mCallsManager.getPhoneAccountListener().onPhoneAccountChanged(mPhoneAccountRegistrar,
- anotherVideoCapableAcct);
- // Call still should not be video capable
- assertFalse(Connection.can(ongoingCall.getConnectionCapabilities(),
- Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
+ mCallsManager.onCallFilteringComplete(screenedCall, result);
+
+ verify(mConnectionSvrFocusMgr).requestFocus(eq(screenedCall),
+ nullable(ConnectionServiceFocusManager.RequestFocusCallback.class));
+ verify(screenedCall).setAudioProcessingRequestingApp(appName);
+ }
+
+ /**
+ * Verify the behavior of the {@link CallsManager#areFromSameSource(Call, Call)} method.
+ * @throws Exception
+ */
+ @SmallTest
+ @Test
+ public void testAreFromSameSource() throws Exception {
+ Call callSim1 = createCall(SIM_1_HANDLE, null, CallState.ACTIVE);
+ Call callSim2 = createCall(SIM_2_HANDLE, null, CallState.ACTIVE);
+ Call callVoip1 = createCall(VOIP_1_HANDLE, null, CallState.ACTIVE);
+ assertTrue(CallsManager.areFromSameSource(callSim1, callSim1));
+ assertTrue(CallsManager.areFromSameSource(callSim1, callSim2));
+ assertFalse(CallsManager.areFromSameSource(callSim1, callVoip1));
+ assertFalse(CallsManager.areFromSameSource(callSim2, callVoip1));
+
+ Call callSim1ConnectionMgr1 = createCall(SIM_1_HANDLE, CONNECTION_MGR_1_HANDLE,
+ CallState.ACTIVE);
+ Call callSim2ConnectionMgr2 = createCall(SIM_2_HANDLE, CONNECTION_MGR_2_HANDLE,
+ CallState.ACTIVE);
+ assertFalse(CallsManager.areFromSameSource(callSim1ConnectionMgr1, callVoip1));
+ assertFalse(CallsManager.areFromSameSource(callSim2ConnectionMgr2, callVoip1));
+ // Even though the connection manager differs, the underlying telephony CS is the same
+ // so hold/swap will still work as expected.
+ assertTrue(CallsManager.areFromSameSource(callSim1ConnectionMgr1, callSim2ConnectionMgr2));
+
+ // Sometimes connection managers have been known to also have calls
+ Call callConnectionMgr = createCall(CONNECTION_MGR_2_HANDLE, CONNECTION_MGR_2_HANDLE,
+ CallState.ACTIVE);
+ assertTrue(CallsManager.areFromSameSource(callSim2ConnectionMgr2, callConnectionMgr));
+ }
+
+ /**
+ * Ensures that if we have two calls hosted by the same connection manager, but with
+ * different target phone accounts, we can swap between them.
+ * @throws Exception
+ */
+ @SmallTest
+ @Test
+ public void testSwapCallsWithSameConnectionMgr() throws Exception {
+ // GIVEN a CallsManager with ongoing call, and this call can not be held
+ Call ongoingCall = addSpyCall(SIM_1_HANDLE, CONNECTION_MGR_1_HANDLE, CallState.ACTIVE);
+ doReturn(false).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 the same connection manager, but a different target phone
+ // account. We have seen cases where a connection mgr adds its own calls and these can
+ // be problematic for swapping.
+ Call heldCall = addSpyCall(CONNECTION_MGR_1_HANDLE, CONNECTION_MGR_1_HANDLE,
+ CallState.ON_HOLD);
+
+ // WHEN unhold the held call
+ mCallsManager.unholdCall(heldCall);
+
+ // THEN the ongoing call is held
+ verify(ongoingCall).hold(any());
+ verifyFocusRequestAndExecuteCallback(heldCall);
+
+ // and held call is unhold now
+ verify(heldCall).unhold(any());
+ }
+
+ /**
+ * Verifies we inform the InCallService on local disconnect.
+ * @throws Exception
+ */
+ @SmallTest
+ @Test
+ public void testRequestDisconnect() throws Exception {
+ CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+ mCallsManager.addListener(listener);
+
+ Call ongoingCall = addSpyCall(CallState.ACTIVE);
+ mCallsManager.addCall(ongoingCall);
+
+ mCallsManager.disconnectCall(ongoingCall);
+ // Seems odd, but ultimately the call state is still active even though it is locally
+ // disconnecting.
+ verify(listener).onCallStateChanged(eq(ongoingCall), eq(CallState.ACTIVE),
+ eq(CallState.ACTIVE));
}
@SmallTest
@@ -1140,6 +1485,7 @@
// Mocks some methods to not call the real method.
doNothing().when(callSpy).unhold();
doNothing().when(callSpy).hold();
+ doNothing().when(callSpy).disconnect();
doNothing().when(callSpy).answer(Matchers.anyInt());
doNothing().when(callSpy).setStartWithSpeakerphoneOn(Matchers.anyBoolean());
@@ -1147,10 +1493,28 @@
return callSpy;
}
+ private Call createSpyCall(PhoneAccountHandle handle, int initialState) {
+ Call ongoingCall = createCall(handle, initialState);
+ Call callSpy = Mockito.spy(ongoingCall);
+
+ // Mocks some methods to not call the real method.
+ doNothing().when(callSpy).unhold();
+ doNothing().when(callSpy).hold();
+ doNothing().when(callSpy).disconnect();
+ doNothing().when(callSpy).answer(Matchers.anyInt());
+ doNothing().when(callSpy).setStartWithSpeakerphoneOn(Matchers.anyBoolean());
+
+ return callSpy;
+ }
+
+ private Call createCall(PhoneAccountHandle targetPhoneAccount, int initialState) {
+ return createCall(targetPhoneAccount, null /* connectionManager */, initialState);
+ }
+
private Call createCall(PhoneAccountHandle targetPhoneAccount,
PhoneAccountHandle connectionManagerAccount, int initialState) {
Call ongoingCall = new Call(String.format("TC@%d", sCallId++), /* callId */
- mComponentContextFixture.getTestDouble(),
+ mContext,
mCallsManager,
mLock, /* ConnectionServiceRepository */
null,
@@ -1162,15 +1526,12 @@
Call.CALL_DIRECTION_INCOMING,
false /* shouldAttachToExistingConnection*/,
false /* isConference */,
- mClockProxy);
+ mClockProxy,
+ mToastFactory);
ongoingCall.setState(initialState, "just cuz");
return ongoingCall;
}
- private Call createCall(PhoneAccountHandle targetPhoneAccount, int initialState) {
- return createCall(targetPhoneAccount, null /* connectionManager */, initialState);
- }
-
private void verifyFocusRequestAndExecuteCallback(Call call) {
ArgumentCaptor<CallsManager.RequestCallback> captor =
ArgumentCaptor.forClass(CallsManager.RequestCallback.class);
@@ -1181,10 +1542,9 @@
private void setupMsimAccounts() {
TelephonyManager mockTelephonyManager = mComponentContextFixture.getTelephonyManager();
- when(mockTelephonyManager.getMultiSimConfiguration()).thenReturn(
- TelephonyManager.MultiSimVariants.DSDS);
+ when(mockTelephonyManager.getMaxNumberOfSimultaneouslyActiveSims()).thenReturn(1);
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
- any(), anyInt())).thenReturn(
+ any(), anyInt(), anyInt())).thenReturn(
new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
when(mPhoneAccountRegistrar.getSimPhoneAccountsOfCurrentUser()).thenReturn(
new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
diff --git a/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java b/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java
new file mode 100644
index 0000000..4ef4596
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2019 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 junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.TestCase.assertNull;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.app.UiModeManager;
+
+import com.android.server.telecom.CarModeTracker;
+
+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.Mock;
+
+@RunWith(JUnit4.class)
+public class CarModeTrackerTest extends TelecomTestCase {
+ private static final String CAR_MODE_APP1_PACKAGE_NAME = "com.android.carmode1";
+ private static final String CAR_MODE_APP2_PACKAGE_NAME = "com.android.carmode2";
+ private static final String CAR_MODE_APP3_PACKAGE_NAME = "com.android.carmode3";
+ private static final String CAR_MODE_APP4_PACKAGE_NAME = "com.android.carmode4";
+
+ private CarModeTracker mCarModeTracker;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mCarModeTracker = new CarModeTracker();
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * Basic test; see if we can enter car mode.
+ */
+ @Test
+ public void testEnterCarModeBasic() {
+ // At start, no car mode apps.
+ assertNull(mCarModeTracker.getCurrentCarModePackage());
+ assertEquals(0, mCarModeTracker.getCarModeApps().size());
+
+ mCarModeTracker.handleEnterCarMode(100, CAR_MODE_APP1_PACKAGE_NAME);
+
+ // We should be tracking our car mode app.
+ assertEquals(1, mCarModeTracker.getCarModeApps().size());
+ assertTrue(mCarModeTracker.isInCarMode());
+ }
+
+ /**
+ * Ensure when we enter car mode with a package which has already entered car mode, the extra
+ * request is ignored.
+ */
+ @Test
+ public void testEnterCarModeAlreadyInCarMode() {
+ testEnterCarModeBasic();
+
+ mCarModeTracker.handleEnterCarMode(100, CAR_MODE_APP1_PACKAGE_NAME);
+ // Still should just be one.
+ assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+ assertEquals(1, mCarModeTracker.getCarModeApps().size());
+ }
+
+ /**
+ * Ensure when we enter car mode with a package which has already entered car mode, the extra
+ * request is ignored.
+ */
+ @Test
+ public void testEnterCarModePriorityInUse() {
+ testEnterCarModeBasic();
+
+ mCarModeTracker.handleEnterCarMode(100, CAR_MODE_APP2_PACKAGE_NAME);
+
+ // We should be tracking car mode with the first package; the 2nd should have been ignored.
+ assertEquals(1, mCarModeTracker.getCarModeApps().size());
+ assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+ assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCarModeApps().get(0));
+ }
+
+ /**
+ * Verifies only the first app at the default priority gets tracked.
+ */
+ @Test
+ public void testEnterCarModeDefaultPriority() {
+ assertEquals(0, mCarModeTracker.getCarModeApps().size());
+
+ mCarModeTracker.handleEnterCarMode(UiModeManager.DEFAULT_PRIORITY,
+ CAR_MODE_APP1_PACKAGE_NAME);
+ assertEquals(1, mCarModeTracker.getCarModeApps().size());
+ assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+
+ mCarModeTracker.handleEnterCarMode(UiModeManager.DEFAULT_PRIORITY,
+ CAR_MODE_APP2_PACKAGE_NAME);
+ assertEquals(1, mCarModeTracker.getCarModeApps().size());
+ assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+ }
+
+ /**
+ * Verifies that multiple apps can enter car mode at the default priority and a higher priority.
+ * Verifies that the priority ordering is retained as expected.
+ */
+ @Test
+ public void testEnterCarModeDefaultAndHigherPriority() {
+ testEnterCarModeDefaultPriority();
+
+ mCarModeTracker.handleEnterCarMode(100, CAR_MODE_APP3_PACKAGE_NAME);
+ assertEquals(2, mCarModeTracker.getCarModeApps().size());
+ assertEquals(CAR_MODE_APP3_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+ assertEquals(CAR_MODE_APP3_PACKAGE_NAME, mCarModeTracker.getCarModeApps().get(0));
+ assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCarModeApps().get(1));
+
+ mCarModeTracker.handleEnterCarMode(50, CAR_MODE_APP4_PACKAGE_NAME);
+ assertEquals(3, mCarModeTracker.getCarModeApps().size());
+ assertEquals(CAR_MODE_APP3_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+ assertEquals(CAR_MODE_APP3_PACKAGE_NAME, mCarModeTracker.getCarModeApps().get(0));
+ assertEquals(CAR_MODE_APP4_PACKAGE_NAME, mCarModeTracker.getCarModeApps().get(1));
+ assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCarModeApps().get(2));
+ }
+
+ /**
+ * Verifies entering and exiting car mode at default priority.
+ */
+ @Test
+ public void testEnterExitCarModeDefaultPriority() {
+ mCarModeTracker.handleEnterCarMode(UiModeManager.DEFAULT_PRIORITY,
+ CAR_MODE_APP1_PACKAGE_NAME);
+ assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+ mCarModeTracker.handleExitCarMode(UiModeManager.DEFAULT_PRIORITY,
+ CAR_MODE_APP1_PACKAGE_NAME);
+ assertNull(mCarModeTracker.getCurrentCarModePackage());
+ }
+
+ /**
+ * Verifies entering and exiting car mode at default priority.
+ * Ensures a different app can exit car mode at the default priority than the one which
+ * entered car mode at that priority.
+ */
+ @Test
+ public void testEnterExitCarModeDefaultPriorityDifferentApp() {
+ mCarModeTracker.handleEnterCarMode(UiModeManager.DEFAULT_PRIORITY,
+ CAR_MODE_APP1_PACKAGE_NAME);
+ assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+ mCarModeTracker.handleExitCarMode(UiModeManager.DEFAULT_PRIORITY,
+ CAR_MODE_APP2_PACKAGE_NAME);
+ assertNull(mCarModeTracker.getCurrentCarModePackage());
+ }
+
+ /**
+ * Verifies entering and exiting car mode at higher priority.
+ */
+ @Test
+ public void testEnterExitCarModeHigherPriority() {
+ mCarModeTracker.handleEnterCarMode(100, CAR_MODE_APP1_PACKAGE_NAME);
+ assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+ mCarModeTracker.handleExitCarMode(100, CAR_MODE_APP1_PACKAGE_NAME);
+ assertNull(mCarModeTracker.getCurrentCarModePackage());
+ }
+
+ /**
+ * Verifies entering and exiting car mode at higher priority, except the exiting app differs.
+ */
+ @Test
+ public void testEnterExitCarModeHigherPriorityDifferentApp() {
+ mCarModeTracker.handleEnterCarMode(100, CAR_MODE_APP1_PACKAGE_NAME);
+ assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+ mCarModeTracker.handleExitCarMode(100, CAR_MODE_APP2_PACKAGE_NAME);
+ assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+ }
+
+ /**
+ * Verifies entering and exiting car mode at a bunch of priorities with a bunch of apps.
+ */
+ @Test
+ public void testEnterExitCarModeMultiple() {
+ mCarModeTracker.handleEnterCarMode(100, CAR_MODE_APP1_PACKAGE_NAME);
+ assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+ mCarModeTracker.handleEnterCarMode(50, CAR_MODE_APP2_PACKAGE_NAME);
+ assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+ mCarModeTracker.handleEnterCarMode(UiModeManager.DEFAULT_PRIORITY,
+ CAR_MODE_APP3_PACKAGE_NAME);
+ assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+
+ mCarModeTracker.handleExitCarMode(100, CAR_MODE_APP1_PACKAGE_NAME);
+ assertEquals(CAR_MODE_APP2_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+ mCarModeTracker.handleExitCarMode(50, CAR_MODE_APP2_PACKAGE_NAME);
+ assertEquals(CAR_MODE_APP3_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+ mCarModeTracker.handleExitCarMode(UiModeManager.DEFAULT_PRIORITY,
+ CAR_MODE_APP3_PACKAGE_NAME);
+ assertNull(mCarModeTracker.getCurrentCarModePackage());
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index e6e8ba1..7effc47 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -63,6 +63,7 @@
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.TelephonyRegistryManager;
import android.test.mock.MockContext;
import java.io.File;
@@ -124,6 +125,11 @@
}
@Override
+ public Resources.Theme getTheme() {
+ return mResourcesTheme;
+ }
+
+ @Override
public File getFilesDir() {
try {
return File.createTempFile("temp", "temp").getParentFile();
@@ -195,6 +201,8 @@
return mCountryDetector;
case Context.ROLE_SERVICE:
return mRoleManager;
+ case Context.TELEPHONY_REGISTRY_SERVICE:
+ return mTelephonyRegistryManager;
default:
return null;
}
@@ -214,6 +222,8 @@
return Context.CARRIER_CONFIG_SERVICE;
} else if (svcClass == SubscriptionManager.class) {
return Context.TELEPHONY_SUBSCRIPTION_SERVICE;
+ } else if (svcClass == TelephonyRegistryManager.class) {
+ return Context.TELEPHONY_REGISTRY_SERVICE;
}
throw new UnsupportedOperationException();
}
@@ -446,6 +456,7 @@
// We then create a spy on the application context allowing standard Mockito-style
// when(...) logic to be used to add specific little responses where needed.
+ private final Resources.Theme mResourcesTheme = mock(Resources.Theme.class);
private final Resources mResources = mock(Resources.class);
private final Context mApplicationContextSpy = spy(mApplicationContext);
private final PackageManager mPackageManager = mock(PackageManager.class);
@@ -463,6 +474,8 @@
private final Configuration mResourceConfiguration = new Configuration();
private final ApplicationInfo mTestApplicationInfo = new ApplicationInfo();
private final RoleManager mRoleManager = mock(RoleManager.class);
+ private final TelephonyRegistryManager mTelephonyRegistryManager =
+ mock(TelephonyRegistryManager.class);
private TelecomManager mTelecomManager = mock(TelecomManager.class);
@@ -502,12 +515,12 @@
// Used in CreateConnectionProcessor to rank emergency numbers by viability.
// For the test, make them all equal to INVALID so that the preferred PhoneAccount will be
// chosen.
- when(mTelephonyManager.getSubIdForPhoneAccount((PhoneAccount) any())).thenReturn(
+ when(mTelephonyManager.getSubscriptionId(any())).thenReturn(
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
when(mTelephonyManager.getNetworkOperatorName()).thenReturn("label1");
- when(mTelephonyManager.getMultiSimConfiguration()).thenReturn(
- TelephonyManager.MultiSimVariants.UNKNOWN);
+ when(mTelephonyManager.getMaxNumberOfSimultaneouslyActiveSims()).thenReturn(1);
+ when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
when(mResources.getBoolean(eq(R.bool.grant_location_permission_enabled))).thenReturn(false);
doAnswer(new Answer<Void>(){
@Override
@@ -551,14 +564,25 @@
public void addInCallService(
ComponentName componentName,
- IInCallService service)
+ IInCallService service,
+ int uid)
throws Exception {
addService(InCallService.SERVICE_INTERFACE, componentName, service);
ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.permission = android.Manifest.permission.BIND_INCALL_SERVICE;
serviceInfo.packageName = componentName.getPackageName();
+ serviceInfo.applicationInfo = new ApplicationInfo();
+ serviceInfo.applicationInfo.uid = uid;
+ serviceInfo.metaData = new Bundle();
+ serviceInfo.metaData.putBoolean(TelecomManager.METADATA_IN_CALL_SERVICE_UI, false);
serviceInfo.name = componentName.getClassName();
mServiceInfoByComponentName.put(componentName, serviceInfo);
+
+ // Used in InCallController to check permissions for CONTROL_INCALL_EXPERIENCE
+ when(mPackageManager.getPackagesForUid(eq(uid))).thenReturn(new String[] {
+ componentName.getPackageName() });
+ when(mPackageManager.checkPermission(eq(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
+ eq(componentName.getPackageName()))).thenReturn(PackageManager.PERMISSION_GRANTED);
}
public void putResource(int id, final String value) {
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
old mode 100644
new mode 100755
index 9655476..46b1522
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -266,6 +266,26 @@
}
@Override
+ public void createConference(
+ PhoneAccountHandle connectionManagerPhoneAccount,
+ String id,
+ ConnectionRequest request,
+ boolean isIncoming,
+ boolean isUnknown,
+ Session.Info sessionInfo) { }
+
+ @Override
+ public void createConferenceComplete(String id, Session.Info sessionInfo) { }
+
+ @Override
+ public void createConferenceFailed(
+ PhoneAccountHandle connectionManagerPhoneAccount,
+ String callId,
+ ConnectionRequest request,
+ boolean isIncoming,
+ Session.Info sessionInfo) { }
+
+ @Override
public void abort(String callId, Session.Info info) throws RemoteException { }
@Override
@@ -280,10 +300,23 @@
throws RemoteException { }
@Override
+ public void transfer(String callId, Uri number, boolean isConfirmationRequired,
+ Session.Info info) throws RemoteException { }
+
+ @Override
+ public void consultativeTransfer(String callId, String otherCallId,
+ Session.Info info) throws RemoteException { }
+
+ @Override
public void reject(String callId, Session.Info info) throws RemoteException {
rejectedCallIds.add(callId);
}
+ @Override public void rejectWithReason(java.lang.String callId, int rejectReason,
+ android.telecom.Logging.Session.Info sessionInfo) throws RemoteException {
+ rejectedCallIds.add(callId);
+ }
+
@Override
public void rejectWithMessage(String callId, String message,
Session.Info info) throws RemoteException {
@@ -332,6 +365,12 @@
Session.Info info) throws RemoteException { }
@Override
+ public void addConferenceParticipants(String CallId, List<Uri> participants,
+ Session.Info sessionInfo) throws RemoteException {
+
+ }
+
+ @Override
public void onPostDialContinue(String callId, boolean proceed,
Session.Info info) throws RemoteException { }
@@ -422,6 +461,7 @@
boolean isVoipAudioMode;
Bundle extras;
boolean isConferenceCreated;
+ int callerNumberVerificationStatus;
}
public class ConferenceInfo {
@@ -658,23 +698,29 @@
mExtrasLock = new CountDownLatch(1);
}
+ public void waitForHandlerToClear() {
+ mConnectionServiceDelegate.getHandler().removeCallbacksAndMessages(null);
+ final CountDownLatch lock = new CountDownLatch(1);
+ mConnectionServiceDelegate.getHandler().post(lock::countDown);
+ while (lock.getCount() > 0) {
+ try {
+ lock.await(TelecomSystemTest.TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+ }
+ }
+
private ParcelableConference parcelable(ConferenceInfo c) {
- return new ParcelableConference(
- c.phoneAccount,
- c.state,
- c.capabilities,
- c.properties,
- c.connectionIds,
- c.videoProvider,
- c.videoState,
- c.connectTimeMillis,
- c.connectElapsedTimeMillis,
- c.statusHints,
- c.extras,
- null,
- 0,
- null,
- 0);
+ return new ParcelableConference.Builder(c.phoneAccount, c.state)
+ .setConnectionCapabilities(c.capabilities)
+ .setConnectionProperties(c.properties)
+ .setConnectionIds(c.connectionIds)
+ .setVideoAttributes(c.videoProvider, c.videoState)
+ .setConnectTimeMillis(c.connectTimeMillis, c.connectElapsedTimeMillis)
+ .setStatusHints(c.statusHints)
+ .setExtras(c.extras)
+ .build();
}
private ParcelableConnection parcelable(ConnectionInfo c) {
@@ -697,6 +743,7 @@
c.statusHints,
c.disconnectCause,
c.conferenceableConnectionIds,
- c.extras);
+ c.extras,
+ c.callerNumberVerificationStatus);
}
}
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java
index 77a9c0d..0d6ceba 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java
@@ -23,6 +23,7 @@
import com.android.server.telecom.ConnectionServiceFocusManager;
import com.android.server.telecom.ConnectionServiceFocusManager.*;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -71,6 +72,12 @@
mCallsManagerListener = captor.getValue();
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
@SmallTest
@Test
public void testRequestFocusWithoutActiveFocusExisted() {
diff --git a/tests/src/com/android/server/telecom/tests/ContactsAsyncHelperTest.java b/tests/src/com/android/server/telecom/tests/ContactsAsyncHelperTest.java
index 08d9a47..10cac93 100644
--- a/tests/src/com/android/server/telecom/tests/ContactsAsyncHelperTest.java
+++ b/tests/src/com/android/server/telecom/tests/ContactsAsyncHelperTest.java
@@ -32,12 +32,15 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.InstrumentationRegistry;
import com.android.server.telecom.ContactsAsyncHelper;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -46,6 +49,7 @@
import java.io.FileNotFoundException;
import java.io.InputStream;
+import java.util.concurrent.Executor;
@RunWith(JUnit4.class)
public class ContactsAsyncHelperTest extends TelecomTestCase {
@@ -96,10 +100,17 @@
mContext = InstrumentationRegistry.getTargetContext();
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
@SmallTest
@Test
public void testEmptyUri() throws Exception {
- ContactsAsyncHelper cah = new ContactsAsyncHelper(mNullContentResolverAdapter);
+ ContactsAsyncHelper cah = new ContactsAsyncHelper(mNullContentResolverAdapter,
+ Looper.getMainLooper());
try {
cah.startObtainPhotoAsync(TOKEN, mContext, null, mListener, COOKIE);
} catch (IllegalStateException e) {
@@ -113,7 +124,8 @@
@SmallTest
@Test
public void testNullReturnFromOpenInputStream() {
- ContactsAsyncHelper cah = new ContactsAsyncHelper(mNullContentResolverAdapter);
+ ContactsAsyncHelper cah = new ContactsAsyncHelper(mNullContentResolverAdapter,
+ Looper.getMainLooper());
cah.startObtainPhotoAsync(TOKEN, mContext, SAMPLE_CONTACT_PHOTO_URI, mListener, COOKIE);
verify(mListener, timeout(TEST_TIMEOUT)).onImageLoadComplete(eq(TOKEN),
@@ -123,7 +135,8 @@
@SmallTest
@Test
public void testImageScaling() {
- ContactsAsyncHelper cah = new ContactsAsyncHelper(mWorkingContentResolverAdapter);
+ ContactsAsyncHelper cah = new ContactsAsyncHelper(mWorkingContentResolverAdapter,
+ Looper.getMainLooper());
cah.startObtainPhotoAsync(TOKEN, mContext, SAMPLE_CONTACT_PHOTO_URI, mListener, COOKIE);
ArgumentCaptor<Drawable> photoCaptor = ArgumentCaptor.forClass(Drawable.class);
@@ -143,7 +156,8 @@
@SmallTest
@Test
public void testNoScaling() {
- ContactsAsyncHelper cah = new ContactsAsyncHelper(mWorkingContentResolverAdapter);
+ ContactsAsyncHelper cah = new ContactsAsyncHelper(mWorkingContentResolverAdapter,
+ Looper.getMainLooper());
cah.startObtainPhotoAsync(TOKEN, mContext, SAMPLE_CONTACT_PHOTO_URI_SMALL,
mListener, COOKIE);
diff --git a/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
index c2db626..845a838 100644
--- a/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
+++ b/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
@@ -48,7 +48,9 @@
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
+import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
@@ -286,6 +288,45 @@
}
/**
+ * Ensure that when a test emergency number is being dialed and we restrict the usable
+ * PhoneAccounts using {@link PhoneAccountRegistrar#filterRestrictedPhoneAccounts(List)}, the
+ * test emergency call is sent on the filtered PhoneAccount.
+ */
+ @SmallTest
+ @Test
+ public void testFakeEmergencyNumber() throws Exception {
+ when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockCall.isTestEmergencyCall()).thenReturn(true);
+ // Put in a regular phone account as the target and make sure it calls that.
+ PhoneAccount regularAccount = makeEmergencyTestPhoneAccount("tel_acct1", 0);
+ PhoneAccountHandle regularAccountHandle = regularAccount.getAccountHandle();
+ List<PhoneAccount> filteredList = new ArrayList<>();
+ filteredList.add(regularAccount);
+ when(mMockAccountRegistrar.filterRestrictedPhoneAccounts(anyList()))
+ .thenReturn(filteredList);
+ mapToSubSlot(regularAccount, 1 /*subId*/, 0 /*slotId*/);
+ setTargetPhoneAccount(mMockCall, regularAccount.getAccountHandle());
+ phoneAccounts.add(regularAccount);
+ // Do not use this account, even though it is a SIM subscription and can place emergency
+ // calls
+ ConnectionServiceWrapper service = makeConnectionServiceWrapper();
+ PhoneAccount emergencyPhoneAccount = makeEmergencyPhoneAccount("tel_emer", 0);
+ mapToSubSlot(emergencyPhoneAccount, 2 /*subId*/, 1 /*slotId*/);
+ phoneAccounts.add(emergencyPhoneAccount);
+
+ mTestCreateConnectionProcessor.process();
+
+ verify(mMockCall).setConnectionManagerPhoneAccount(eq(regularAccountHandle));
+ verify(mMockCall).setTargetPhoneAccount(eq(regularAccountHandle));
+ 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);
+ }
+
+ /**
* Ensure that the non-emergency capable PhoneAccount and the SIM manager is not chosen to place
* the emergency call if there is an emergency capable PhoneAccount available as well.
*/
@@ -293,6 +334,7 @@
@Test
public void testEmergencyCall() throws Exception {
when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockCall.isTestEmergencyCall()).thenReturn(false);
// Put in a regular phone account as the target to be sure it doesn't call that
PhoneAccount regularAccount = makePhoneAccount("tel_acct1",
PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
@@ -331,6 +373,7 @@
@Test
public void testEmergencyCallMultiSimNoPreferred() throws Exception {
when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockCall.isTestEmergencyCall()).thenReturn(false);
// Put in a non-SIM phone account as the target to be sure it doesn't call that.
PhoneAccount regularAccount = makePhoneAccount("tel_acct1", 0);
setTargetPhoneAccount(mMockCall, regularAccount.getAccountHandle());
@@ -368,6 +411,7 @@
@Test
public void testEmergencyCallMultiSimTelephonyPreferred() throws Exception {
when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockCall.isTestEmergencyCall()).thenReturn(false);
ConnectionServiceWrapper service = makeConnectionServiceWrapper();
PhoneAccount emergencyPhoneAccount1 = makeEmergencyPhoneAccount("tel_emer1", 0);
mapToSubSlot(emergencyPhoneAccount1, 1 /*subId*/, 0 /*slotId*/);
@@ -400,6 +444,7 @@
@Test
public void testEmergencyCallMultiSimUserPreferred() throws Exception {
when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockCall.isTestEmergencyCall()).thenReturn(false);
// Include a Connection Manager to be sure it doesn't call that
PhoneAccount callManagerPA = createNewConnectionManagerPhoneAccountForCall(mMockCall,
"cm_acct", 0);
@@ -436,6 +481,7 @@
@Test
public void testEmergencyCallMultiSimUserPreferredInvalidSlot() throws Exception {
when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockCall.isTestEmergencyCall()).thenReturn(false);
// Include a Connection Manager to be sure it doesn't call that
PhoneAccount callManagerPA = createNewConnectionManagerPhoneAccountForCall(mMockCall,
"cm_acct", 0);
@@ -472,6 +518,7 @@
@Test
public void testEmergencyCallMultiSimNoPreferenceInvalidSlot() throws Exception {
when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockCall.isTestEmergencyCall()).thenReturn(false);
// Include a Connection Manager to be sure it doesn't call that
PhoneAccount callManagerPA = createNewConnectionManagerPhoneAccountForCall(mMockCall,
"cm_acct", 0);
@@ -503,6 +550,7 @@
@Test
public void testEmergencyCallSimFailToConnectionManager() throws Exception {
when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockCall.isTestEmergencyCall()).thenReturn(false);
when(mMockCall.getHandle()).thenReturn(Uri.parse(""));
// Put in a regular phone account to be sure it doesn't call that
PhoneAccount regularAccount = makePhoneAccount("tel_acct1",
@@ -542,6 +590,16 @@
verify(service).createConnection(eq(mMockCall), any(CreateConnectionResponse.class));
}
+ private PhoneAccount makeEmergencyTestPhoneAccount(String id, int capabilities) {
+ final PhoneAccount emergencyPhoneAccount = makeQuickAccount(id, capabilities |
+ PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS);
+ PhoneAccountHandle emergencyPhoneAccountHandle = emergencyPhoneAccount.getAccountHandle();
+ givePhoneAccountBindPermission(emergencyPhoneAccountHandle);
+ when(mMockAccountRegistrar.getPhoneAccountUnchecked(emergencyPhoneAccountHandle))
+ .thenReturn(emergencyPhoneAccount);
+ return emergencyPhoneAccount;
+ }
+
private PhoneAccount makeEmergencyPhoneAccount(String id, int capabilities) {
final PhoneAccount emergencyPhoneAccount = makeQuickAccount(id, capabilities |
PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS |
diff --git a/tests/src/com/android/server/telecom/tests/DefaultDialerCacheTest.java b/tests/src/com/android/server/telecom/tests/DefaultDialerCacheTest.java
index e8bca03..e733465 100644
--- a/tests/src/com/android/server/telecom/tests/DefaultDialerCacheTest.java
+++ b/tests/src/com/android/server/telecom/tests/DefaultDialerCacheTest.java
@@ -30,6 +30,7 @@
import com.android.server.telecom.RoleManagerAdapter;
import com.android.server.telecom.TelecomSystem;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -101,6 +102,12 @@
when(mRoleManagerAdapter.getDefaultDialerApp(eq(USER2))).thenReturn(DIALER3);
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
@SmallTest
@Test
public void testThreeUsers() {
diff --git a/tests/src/com/android/server/telecom/tests/DirectToVoicemailCallFilterTest.java b/tests/src/com/android/server/telecom/tests/DirectToVoicemailCallFilterTest.java
deleted file mode 100644
index a685c5e..0000000
--- a/tests/src/com/android/server/telecom/tests/DirectToVoicemailCallFilterTest.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2016 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 android.net.Uri;
-import android.provider.CallLog;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.internal.telephony.CallerInfo;
-import com.android.server.telecom.Call;
-import com.android.server.telecom.callfiltering.CallFilterResultCallback;
-import com.android.server.telecom.CallerInfoLookupHelper;
-import com.android.server.telecom.callfiltering.CallFilteringResult;
-import com.android.server.telecom.callfiltering.DirectToVoicemailCallFilter;
-
-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 static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-@RunWith(JUnit4.class)
-public class DirectToVoicemailCallFilterTest extends TelecomTestCase {
- @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
- @Mock private CallFilterResultCallback mCallback;
- @Mock private Call mCall;
-
- private static final Uri TEST_HANDLE = Uri.parse("tel:1235551234");
-
- @Override
- @Before
- public void setUp() throws Exception {
- super.setUp();
- }
-
- @SmallTest
- @Test
- public void testSendToVoicemail() {
- CallerInfoLookupHelper.OnQueryCompleteListener queryListener = verifyLookupStart();
-
- CallerInfo callerInfo = new CallerInfo();
- callerInfo.shouldSendToVoicemail = true;
-
- queryListener.onCallerInfoQueryComplete(TEST_HANDLE, callerInfo);
- verify(mCallback).onCallFilteringComplete(mCall,
- new CallFilteringResult(
- false, // shouldAllowCall
- true, // shouldReject
- true, // shouldAddToCallLog
- true, // shouldShowNotification
- CallLog.Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL, //callBlockReason
- null, //callScreeningAppName
- null // callScreeningComponentName
- ));
- }
-
- @SmallTest
- @Test
- public void testDontSendToVoicemail() {
- CallerInfoLookupHelper.OnQueryCompleteListener queryListener = verifyLookupStart();
-
- CallerInfo callerInfo = new CallerInfo();
- callerInfo.shouldSendToVoicemail = false;
-
- queryListener.onCallerInfoQueryComplete(TEST_HANDLE, callerInfo);
- verify(mCallback).onCallFilteringComplete(mCall,
- new CallFilteringResult(
- true, // shouldAllowCall
- false, // shouldReject
- true, // shouldAddToCallLog
- true // shouldShowNotification
- ));
- }
-
- @SmallTest
- @Test
- public void testNullResponseFromLookupHelper() {
- CallerInfoLookupHelper.OnQueryCompleteListener queryListener = verifyLookupStart(null);
-
- queryListener.onCallerInfoQueryComplete(null, null);
- verify(mCallback).onCallFilteringComplete(mCall,
- new CallFilteringResult(
- true, // shouldAllowCall
- false, // shouldReject
- true, // shouldAddToCallLog
- true // shouldShowNotification
- ));
- }
-
- private CallerInfoLookupHelper.OnQueryCompleteListener verifyLookupStart() {
- return verifyLookupStart(TEST_HANDLE);
- }
-
- private CallerInfoLookupHelper.OnQueryCompleteListener verifyLookupStart(Uri handle) {
- when(mCall.getHandle()).thenReturn(handle);
- DirectToVoicemailCallFilter filter =
- new DirectToVoicemailCallFilter(mCallerInfoLookupHelper);
- filter.startFilterLookup(mCall, mCallback);
- ArgumentCaptor<CallerInfoLookupHelper.OnQueryCompleteListener> captor =
- ArgumentCaptor.forClass(CallerInfoLookupHelper.OnQueryCompleteListener.class);
- verify(mCallerInfoLookupHelper).startLookup(eq(handle), captor.capture());
- return captor.getValue();
- }
-}
diff --git a/tests/src/com/android/server/telecom/tests/DirectToVoicemailFilterTest.java b/tests/src/com/android/server/telecom/tests/DirectToVoicemailFilterTest.java
new file mode 100644
index 0000000..2ab4e78
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/DirectToVoicemailFilterTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2019 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 junit.framework.Assert.assertEquals;
+
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.net.Uri;
+import android.provider.CallLog;
+import android.telecom.CallerInfo;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallerInfoLookupHelper;
+import com.android.server.telecom.callfiltering.CallFilter;
+import com.android.server.telecom.callfiltering.CallFilteringResult;
+import com.android.server.telecom.callfiltering.DirectToVoicemailFilter;
+
+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.concurrent.CompletionStage;
+import java.util.concurrent.TimeUnit;
+
+
+@RunWith(JUnit4.class)
+public class DirectToVoicemailFilterTest extends TelecomTestCase {
+ @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
+ @Mock private Call mCall;
+
+ private final CallFilteringResult PASS_RESULT = new CallFilteringResult.Builder()
+ .setShouldAllowCall(true)
+ .setShouldReject(false)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(true)
+ .build();
+ private final CallFilteringResult DIRECT_TO_VOICEMAIL_RESULT = new CallFilteringResult.Builder()
+ .setShouldAllowCall(false)
+ .setShouldReject(true)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(true)
+ .setCallBlockReason(CallLog.Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL)
+ .setCallScreeningAppName(null)
+ .setCallScreeningComponentName(null)
+ .build();
+ private final long CALLER_INFO_TIMEOUT = 2000;
+
+ private Uri mTestHandle;
+ private DirectToVoicemailFilter mFilter;
+ private CallerInfo mCallerInfo;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mTestHandle = Uri.parse("tel:1235551234");
+ mFilter = new DirectToVoicemailFilter(mCall, mCallerInfoLookupHelper);
+ mCallerInfo = new CallerInfo();
+ when(mCall.getHandle()).thenReturn(mTestHandle);
+ }
+
+ @SmallTest
+ @Test
+ public void testSendToVoicemail() throws Exception {
+ mCallerInfo.shouldSendToVoicemail = true;
+
+ CompletionStage<CallFilteringResult> resultFuture = mFilter.startFilterLookup(PASS_RESULT);
+ CallerInfoLookupHelper.OnQueryCompleteListener listener = verifyLookupStart();
+ listener.onCallerInfoQueryComplete(mTestHandle, mCallerInfo);
+ assertEquals(DIRECT_TO_VOICEMAIL_RESULT, resultFuture.toCompletableFuture()
+ .get(CALLER_INFO_TIMEOUT, TimeUnit.MILLISECONDS));
+ }
+
+ @SmallTest
+ @Test
+ public void testDontSendToVoicemail() throws Exception {
+ mCallerInfo.shouldSendToVoicemail = false;
+
+ CompletionStage<CallFilteringResult> resultFuture = mFilter.startFilterLookup(PASS_RESULT);
+ CallerInfoLookupHelper.OnQueryCompleteListener listener = verifyLookupStart();
+ listener.onCallerInfoQueryComplete(mTestHandle, mCallerInfo);
+ assertEquals(PASS_RESULT, resultFuture.toCompletableFuture()
+ .get(CALLER_INFO_TIMEOUT, TimeUnit.MILLISECONDS));
+ }
+
+ @SmallTest
+ @Test
+ public void testNullResponseAndDifferentHandleFromLookupHelper() throws Exception {
+ mTestHandle = null;
+
+ CompletionStage<CallFilteringResult> resultFuture = mFilter.startFilterLookup(PASS_RESULT);
+ CallerInfoLookupHelper.OnQueryCompleteListener listener = verifyLookupStart();
+ listener.onCallerInfoQueryComplete(null, null);
+ assertEquals(PASS_RESULT, resultFuture.toCompletableFuture()
+ .get(CALLER_INFO_TIMEOUT, TimeUnit.MILLISECONDS));
+ }
+
+ @SmallTest
+ @Test
+ public void testNullResponseAndSameHandleFromLookupHelper() throws Exception {
+ CompletionStage<CallFilteringResult> resultFuture = mFilter.startFilterLookup(PASS_RESULT);
+ CallerInfoLookupHelper.OnQueryCompleteListener listener = verifyLookupStart();
+ listener.onCallerInfoQueryComplete(mTestHandle, null);
+ assertEquals(PASS_RESULT, resultFuture.toCompletableFuture()
+ .get(CALLER_INFO_TIMEOUT, TimeUnit.MILLISECONDS));
+ }
+
+ private CallerInfoLookupHelper.OnQueryCompleteListener verifyLookupStart() {
+ ArgumentCaptor<CallerInfoLookupHelper.OnQueryCompleteListener> captor =
+ ArgumentCaptor.forClass(CallerInfoLookupHelper.OnQueryCompleteListener.class);
+ verify(mCallerInfoLookupHelper, timeout(CALLER_INFO_TIMEOUT))
+ .startLookup(nullable(Uri.class), captor.capture());
+ return captor.getValue();
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/DisconnectedCallNotifierTest.java b/tests/src/com/android/server/telecom/tests/DisconnectedCallNotifierTest.java
new file mode 100644
index 0000000..2cdc23a
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/DisconnectedCallNotifierTest.java
@@ -0,0 +1,156 @@
+package com.android.server.telecom.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+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 android.app.Notification;
+import android.app.NotificationManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.TelephonyManager;
+
+import androidx.test.filters.SmallTest;
+
+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.ui.DisconnectedCallNotifier;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+public class DisconnectedCallNotifierTest extends TelecomTestCase {
+
+ private static final PhoneAccountHandle PHONE_ACCOUNT_HANDLE = new PhoneAccountHandle(
+ new ComponentName("com.android.server.telecom.tests", "DisconnectedCallNotifierTest"),
+ "testId");
+ private static final Uri TEL_CALL_HANDLE = Uri.parse("tel:+11915552620");
+
+ @Mock private CallsManager mCallsManager;
+ @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
+
+ private NotificationManager mNotificationManager;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+
+ mNotificationManager = (NotificationManager) mContext.getSystemService(
+ Context.NOTIFICATION_SERVICE);
+ TelephonyManager fakeTelephonyManager = (TelephonyManager) mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ when(fakeTelephonyManager.getNetworkCountryIso()).thenReturn("US");
+ doReturn(mCallerInfoLookupHelper).when(mCallsManager).getCallerInfoLookupHelper();
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ @SmallTest
+ public void testNotificationShownAfterEmergencyCall() {
+ Call call = createCall(new DisconnectCause(DisconnectCause.LOCAL,
+ DisconnectCause.REASON_EMERGENCY_CALL_PLACED));
+
+ DisconnectedCallNotifier notifier = new DisconnectedCallNotifier(mContext, mCallsManager);
+ notifier.onCallStateChanged(call, CallState.NEW, CallState.DIALING);
+ notifier.onCallStateChanged(call, CallState.DIALING, CallState.DISCONNECTED);
+ verify(mNotificationManager, never()).notifyAsUser(anyString(), anyInt(),
+ any(Notification.class), any(UserHandle.class));
+
+ doReturn(Collections.EMPTY_LIST).when(mCallsManager).getCalls();
+ notifier.onCallRemoved(call);
+ ArgumentCaptor<Notification> captor = ArgumentCaptor.forClass(Notification.class);
+ verify(mNotificationManager).notifyAsUser(anyString(), anyInt(),
+ captor.capture(), any(UserHandle.class));
+ Notification notification = captor.getValue();
+ assertNotNull(notification.contentIntent);
+ assertEquals(2, notification.actions.length);
+ }
+
+ @Test
+ @SmallTest
+ public void testNotificationShownForDisconnectedEmergencyCall() {
+ Call call = createCall(new DisconnectCause(DisconnectCause.LOCAL,
+ DisconnectCause.REASON_EMERGENCY_CALL_PLACED));
+ when(call.isEmergencyCall()).thenReturn(true);
+
+ DisconnectedCallNotifier notifier = new DisconnectedCallNotifier(mContext, mCallsManager);
+ notifier.onCallStateChanged(call, CallState.NEW, CallState.DIALING);
+ notifier.onCallStateChanged(call, CallState.DIALING, CallState.DISCONNECTED);
+ verify(mNotificationManager, never()).notifyAsUser(anyString(), anyInt(),
+ any(Notification.class), any(UserHandle.class));
+
+ doReturn(Collections.EMPTY_LIST).when(mCallsManager).getCalls();
+ notifier.onCallRemoved(call);
+ ArgumentCaptor<Notification> captor = ArgumentCaptor.forClass(Notification.class);
+ verify(mNotificationManager).notifyAsUser(anyString(), anyInt(),
+ captor.capture(), any(UserHandle.class));
+ Notification notification = captor.getValue();
+ assertNull(notification.contentIntent);
+ if (notification.actions != null) {
+ assertEquals(0, notification.actions.length);
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testNotificationNotShownAfterCall() {
+ Call call = createCall(new DisconnectCause(DisconnectCause.LOCAL));
+
+ DisconnectedCallNotifier notifier = new DisconnectedCallNotifier(mContext, mCallsManager);
+ notifier.onCallStateChanged(call, CallState.DIALING, CallState.DISCONNECTED);
+ verify(mNotificationManager, never()).notifyAsUser(anyString(), anyInt(),
+ any(Notification.class), any(UserHandle.class));
+
+ doReturn(Collections.EMPTY_LIST).when(mCallsManager).getCalls();
+ notifier.onCallRemoved(call);
+ verify(mNotificationManager, never()).notifyAsUser(anyString(), anyInt(),
+ any(Notification.class), any(UserHandle.class));
+ }
+
+ @Test
+ @SmallTest
+ public void testNotificationClearedForEmergencyCall() {
+ Call call = createCall(new DisconnectCause(DisconnectCause.LOCAL,
+ DisconnectCause.REASON_EMERGENCY_CALL_PLACED));
+
+ DisconnectedCallNotifier notifier = new DisconnectedCallNotifier(mContext, mCallsManager);
+ notifier.onCallStateChanged(call, CallState.DIALING, CallState.DISCONNECTED);
+ verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), any());
+ }
+
+ private Call createCall(DisconnectCause cause) {
+ Call call = mock(Call.class);
+ when(call.getDisconnectCause()).thenReturn(cause);
+ when(call.getTargetPhoneAccount()).thenReturn(PHONE_ACCOUNT_HANDLE);
+ when(call.getHandle()).thenReturn(TEL_CALL_HANDLE);
+ return call;
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/DtmfLocalTonePlayerTest.java b/tests/src/com/android/server/telecom/tests/DtmfLocalTonePlayerTest.java
index 24dd18f..85a5278 100644
--- a/tests/src/com/android/server/telecom/tests/DtmfLocalTonePlayerTest.java
+++ b/tests/src/com/android/server/telecom/tests/DtmfLocalTonePlayerTest.java
@@ -22,6 +22,7 @@
import com.android.server.telecom.DtmfLocalTonePlayer;
import com.android.server.telecom.R;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -42,6 +43,7 @@
DtmfLocalTonePlayer mPlayer;
+ @Override
@Before
public void setUp() throws Exception {
super.setUp();
@@ -50,6 +52,12 @@
when(mCall.getContext()).thenReturn(mContext);
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
@SmallTest
@Test
public void testSupportedStart() {
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index bbefae3..e16b598 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -16,8 +16,14 @@
package com.android.server.telecom.tests;
+import static com.android.server.telecom.InCallController.IN_CALL_SERVICE_NOTIFICATION_ID;
+import static com.android.server.telecom.InCallController.NOTIFICATION_TAG;
+
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.matches;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
@@ -33,21 +39,27 @@
import static org.mockito.Mockito.when;
import android.Manifest;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.UiModeManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.UserHandle;
import android.telecom.InCallService;
+import android.telecom.Log;
import android.telecom.ParcelableCall;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
@@ -61,6 +73,8 @@
import com.android.server.telecom.BluetoothHeadsetProxy;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.CarModeTracker;
+import com.android.server.telecom.ClockProxy;
import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.EmergencyCallHelper;
import com.android.server.telecom.InCallController;
@@ -82,30 +96,10 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
-import java.util.List;
import java.util.concurrent.CompletableFuture;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.matches;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-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.when;
-import static org.mockito.Mockito.verify;
-
@RunWith(JUnit4.class)
public class InCallControllerTests extends TelecomTestCase {
@Mock CallsManager mMockCallsManager;
@@ -119,16 +113,26 @@
@Mock Timeouts.Adapter mTimeoutsAdapter;
@Mock DefaultDialerCache mDefaultDialerCache;
@Mock RoleManagerAdapter mMockRoleManagerAdapter;
+ @Mock ClockProxy mClockProxy;
+ @Mock Analytics.CallInfoImpl mCallInfo;
+ @Mock NotificationManager mNotificationManager;
private static final int CURRENT_USER_ID = 900973;
private static final String DEF_PKG = "defpkg";
private static final String DEF_CLASS = "defcls";
+ private static final int DEF_UID = 1;
private static final String SYS_PKG = "syspkg";
private static final String SYS_CLASS = "syscls";
+ private static final int SYS_UID = 2;
private static final String COMPANION_PKG = "cpnpkg";
private static final String COMPANION_CLASS = "cpncls";
+ private static final int COMPANION_UID = 3;
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 PhoneAccountHandle PA_HANDLE =
new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"), "pa_id");
@@ -148,24 +152,50 @@
com.android.internal.R.string.config_defaultDialer);
doReturn(SYS_CLASS).when(mMockResources).getString(R.string.incall_default_class);
doReturn(true).when(mMockResources).getBoolean(R.bool.grant_location_permission_enabled);
- mEmergencyCallHelper = new EmergencyCallHelper(mMockContext, SYS_PKG,
+ when(mDefaultDialerCache.getSystemDialerApplication()).thenReturn(SYS_PKG);
+ when(mDefaultDialerCache.getSystemDialerComponent()).thenReturn(
+ new ComponentName(SYS_PKG, SYS_CLASS));
+ mEmergencyCallHelper = new EmergencyCallHelper(mMockContext, mDefaultDialerCache,
mTimeoutsAdapter);
when(mMockCallsManager.getRoleManagerAdapter()).thenReturn(mMockRoleManagerAdapter);
mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager,
mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter,
- mEmergencyCallHelper);
+ mEmergencyCallHelper, new CarModeTracker(), mClockProxy);
+ when(mMockContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
+ .thenReturn(mNotificationManager);
// Companion Apps don't have CONTROL_INCALL_EXPERIENCE permission.
+ doAnswer(invocation -> {
+ int uid = invocation.getArgument(0);
+ switch (uid) {
+ case DEF_UID:
+ return new String[] { DEF_PKG };
+ case SYS_UID:
+ return new String[] { SYS_PKG };
+ case COMPANION_UID:
+ return new String[] { COMPANION_PKG };
+ case CAR_UID:
+ return new String[] { CAR_PKG };
+ case CAR2_UID:
+ return new String[] { CAR2_PKG };
+ }
+ return null;
+ }).when(mMockPackageManager).getPackagesForUid(anyInt());
when(mMockPackageManager.checkPermission(
matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
matches(COMPANION_PKG))).thenReturn(PackageManager.PERMISSION_DENIED);
when(mMockPackageManager.checkPermission(
matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
- matches(CAR_PKG))).thenReturn(PackageManager.PERMISSION_DENIED);
+ matches(CAR_PKG))).thenReturn(PackageManager.PERMISSION_GRANTED);
+ when(mMockPackageManager.checkPermission(
+ matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
+ matches(CAR2_PKG))).thenReturn(PackageManager.PERMISSION_GRANTED);
}
@Override
@After
public void tearDown() throws Exception {
+ mInCallController.getHandler().removeCallbacksAndMessages(null);
+ waitForHandlerAction(mInCallController.getHandler(), 1000);
super.tearDown();
}
@@ -174,7 +204,7 @@
public void testBindToService_NoServicesFound_IncomingCall() throws Exception {
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
when(mMockCall.isIncoming()).thenReturn(true);
when(mMockCall.isExternalCall()).thenReturn(false);
when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
@@ -206,7 +236,7 @@
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
when(mMockCall.isIncoming()).thenReturn(false);
when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mMockCall.getIntentExtras()).thenReturn(callExtras);
@@ -244,7 +274,7 @@
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
when(mMockCall.isIncoming()).thenReturn(false);
when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mMockCall.getIntentExtras()).thenReturn(callExtras);
@@ -296,7 +326,7 @@
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- when(mMockCallsManager.hasEmergencyCall()).thenReturn(true);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
when(mMockCall.isEmergencyCall()).thenReturn(true);
when(mMockCall.isIncoming()).thenReturn(false);
when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
@@ -358,6 +388,98 @@
eq(Manifest.permission.ACCESS_FINE_LOCATION), eq(mUserHandle));
}
+ /**
+ * 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 testBindToService_SystemDialer_Crash() throws Exception {
+ Bundle callExtras = new Bundle();
+ callExtras.putBoolean("whatever", true);
+
+ when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
+ 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(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID))
+ .thenReturn(DEF_PKG);
+ ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
+ ArgumentCaptor.forClass(ServiceConnection.class);
+ when(mMockContext.bindServiceAsUser(any(Intent.class), serviceConnectionCaptor.capture(),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+ | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+ eq(UserHandle.CURRENT))).thenReturn(true);
+ when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
+ .thenReturn(300_000L);
+
+ setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+ setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
+
+ mInCallController.bindToServices(mMockCall);
+
+ // Query for the different InCallServices
+ ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
+ queryIntentCaptor.capture(),
+ eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID));
+
+ // Verify call for default dialer InCallService
+ assertEquals(DEF_PKG, queryIntentCaptor.getAllValues().get(0).getPackage());
+ // Verify call for car-mode InCallService
+ assertEquals(null, queryIntentCaptor.getAllValues().get(1).getPackage());
+ // Verify call for non-UI InCallServices
+ assertEquals(null, queryIntentCaptor.getAllValues().get(2).getPackage());
+
+ ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockContext, times(1)).bindServiceAsUser(
+ bindIntentCaptor.capture(),
+ any(ServiceConnection.class),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+ | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+ eq(UserHandle.CURRENT));
+
+ Intent bindIntent = bindIntentCaptor.getValue();
+ assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
+ assertEquals(SYS_PKG, bindIntent.getComponent().getPackageName());
+ assertEquals(SYS_CLASS, bindIntent.getComponent().getClassName());
+ assertEquals(PA_HANDLE, bindIntent.getExtras().getParcelable(
+ TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE));
+ assertEquals(callExtras, bindIntent.getExtras().getParcelable(
+ TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS));
+
+ verify(mMockPackageManager).grantRuntimePermission(eq(SYS_PKG),
+ eq(Manifest.permission.ACCESS_FINE_LOCATION), eq(mUserHandle));
+
+ // Emulate a crash in the system dialer; we'll use the captured service connection to signal
+ // to InCallController that the dialer died.
+ ServiceConnection serviceConnection = serviceConnectionCaptor.getValue();
+ serviceConnection.onServiceDisconnected(bindIntent.getComponent());
+
+ // We expect that the permission is revoked at this point.
+ verify(mMockPackageManager).revokeRuntimePermission(eq(SYS_PKG),
+ eq(Manifest.permission.ACCESS_FINE_LOCATION), eq(mUserHandle));
+
+ // 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(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+ | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+ eq(UserHandle.CURRENT));
+
+ // Verify we were re-granted the runtime permission.
+ verify(mMockPackageManager, times(2)).grantRuntimePermission(eq(SYS_PKG),
+ eq(Manifest.permission.ACCESS_FINE_LOCATION), eq(mUserHandle));
+ }
+
@MediumTest
@Test
public void testBindToService_DefaultDialer_FallBackToSystem() throws Exception {
@@ -366,7 +488,7 @@
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockCall));
when(mMockCallsManager.getAudioState()).thenReturn(null);
when(mMockCallsManager.canAddCall()).thenReturn(false);
@@ -445,6 +567,58 @@
assertEquals(SYS_CLASS, bindIntent.getComponent().getClassName());
}
+ @Test
+ public void testBindToService_NullBinding_FallBackToSystem() throws Exception {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.targetSdkVersion = Build.VERSION_CODES.R;
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockCall.isIncoming()).thenReturn(false);
+ when(mMockCall.isExternalCall()).thenReturn(false);
+ when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+ when(mMockCall.getAnalytics()).thenReturn(mCallInfo);
+ when(mMockContext.bindServiceAsUser(
+ any(Intent.class), any(ServiceConnection.class), anyInt(), any(UserHandle.class)))
+ .thenReturn(true);
+ when(mMockContext.getApplicationInfo()).thenReturn(applicationInfo);
+
+ setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+ mInCallController.bindToServices(mMockCall);
+
+ ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
+ ArgumentCaptor.forClass(ServiceConnection.class);
+ verify(mMockContext, times(1)).bindServiceAsUser(
+ bindIntentCaptor.capture(),
+ serviceConnectionCaptor.capture(),
+ anyInt(),
+ any(UserHandle.class));
+ ServiceConnection serviceConnection = serviceConnectionCaptor.getValue();
+ ComponentName defDialerComponentName = new ComponentName(DEF_PKG, DEF_CLASS);
+ ComponentName sysDialerComponentName = new ComponentName(SYS_PKG, SYS_CLASS);
+
+ IBinder mockBinder = mock(IBinder.class);
+ IInCallService mockInCallService = mock(IInCallService.class);
+ when(mockBinder.queryLocalInterface(anyString())).thenReturn(mockInCallService);
+
+ serviceConnection.onServiceConnected(defDialerComponentName, mockBinder);
+ // verify(mockInCallService).setInCallAdapter(any(IInCallAdapter.class));
+ serviceConnection.onNullBinding(defDialerComponentName);
+
+ verify(mNotificationManager).notify(eq(NOTIFICATION_TAG),
+ eq(IN_CALL_SERVICE_NOTIFICATION_ID), any(Notification.class));
+ verify(mCallInfo).addInCallService(eq(sysDialerComponentName.flattenToShortString()),
+ anyInt(), anyLong(), eq(true));
+
+ ArgumentCaptor<Intent> bindIntentCaptor2 = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockContext, times(2)).bindServiceAsUser(
+ bindIntentCaptor2.capture(),
+ any(ServiceConnection.class),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+ | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+ eq(UserHandle.CURRENT));
+ }
+
/**
* Ensures that the {@link InCallController} will bind to an {@link InCallService} which
* supports external calls.
@@ -492,7 +666,7 @@
public void testUnbindDueToCallDisconnect() throws Exception {
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
when(mMockCall.isIncoming()).thenReturn(true);
when(mMockCall.isExternalCall()).thenReturn(false);
when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
@@ -541,63 +715,6 @@
/**
* Ensures that the {@link InCallController} will bind to an {@link InCallService} which
- * supports third party companion calls.
- */
- @MediumTest
- @Test
- public void testBindToService_Companion() throws Exception {
- setupMocks(true /* isExternalCall */);
- setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
-
- List<String> companionAppsList = new ArrayList<>();
- companionAppsList.add(COMPANION_PKG);
- companionAppsList.add(COMPANION_PKG);
- when(mMockRoleManagerAdapter.getCallCompanionApps()).thenReturn(companionAppsList);
- mInCallController.bindToServices(mMockCall);
-
- // Query for the different InCallServices
- ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mMockPackageManager, times(6)).queryIntentServicesAsUser(
- queryIntentCaptor.capture(),
- eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID));
- // Verify call for default dialer InCallService
- assertEquals(DEF_PKG, queryIntentCaptor.getAllValues().get(0).getPackage());
- // Verify call for system dialer InCallService
- assertEquals(null, queryIntentCaptor.getAllValues().get(1).getPackage());
- // Verify call for car mode ui InCallServices
- assertEquals(null, queryIntentCaptor.getAllValues().get(2).getPackage());
- // Verify call for non-UI InCallServices
- assertEquals(null, queryIntentCaptor.getAllValues().get(3).getPackage());
- // Verify call for companion InCallServices
- assertEquals(COMPANION_PKG, queryIntentCaptor.getAllValues().get(4).getPackage());
- assertEquals(COMPANION_PKG, queryIntentCaptor.getAllValues().get(5).getPackage());
-
- // Bind InCallServices
- ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mMockContext, times(3)).bindServiceAsUser(
- bindIntentCaptor.capture(),
- any(ServiceConnection.class),
- eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
- | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
- eq(UserHandle.CURRENT));
- // Verify bind dialer
- Intent bindIntent = bindIntentCaptor.getAllValues().get(0);
- assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
- assertEquals(DEF_PKG, bindIntent.getComponent().getPackageName());
- assertEquals(DEF_CLASS, bindIntent.getComponent().getClassName());
- // Verify bind companion apps
- bindIntent = bindIntentCaptor.getAllValues().get(1);
- assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
- assertEquals(COMPANION_PKG, bindIntent.getComponent().getPackageName());
- assertEquals(COMPANION_CLASS, bindIntent.getComponent().getClassName());
- bindIntent = bindIntentCaptor.getAllValues().get(2);
- assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
- assertEquals(COMPANION_PKG, bindIntent.getComponent().getPackageName());
- assertEquals(COMPANION_CLASS, bindIntent.getComponent().getClassName());
- }
-
- /**
- * Ensures that the {@link InCallController} will bind to an {@link InCallService} which
* supports third party car mode ui calls
*/
@MediumTest
@@ -606,24 +723,12 @@
setupMocks(true /* isExternalCall */);
setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
- when(mMockRoleManagerAdapter.getCarModeDialerApp()).thenReturn(CAR_PKG);
// Enable car mode
when(mMockSystemStateHelper.isCarMode()).thenReturn(true);
- mInCallController.bindToServices(mMockCall);
+ mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true);
- // Query for the different InCallServices
- ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
- queryIntentCaptor.capture(),
- eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID));
- // Verify call for default dialer InCallService
- assertEquals(DEF_PKG, queryIntentCaptor.getAllValues().get(0).getPackage());
- // Verify call for system dialer InCallService
- assertEquals(null, queryIntentCaptor.getAllValues().get(1).getPackage());
- // Verify call for car mode ui InCallServices
- assertEquals(CAR_PKG, queryIntentCaptor.getAllValues().get(2).getPackage());
- // Verify call for non-UI InCallServices
- assertEquals(null, queryIntentCaptor.getAllValues().get(3).getPackage());
+ // Now bind; we should only bind to one app.
+ mInCallController.bindToServices(mMockCall);
// Bind InCallServices
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -635,10 +740,7 @@
eq(UserHandle.CURRENT));
// Verify bind car mode ui
assertEquals(1, bindIntentCaptor.getAllValues().size());
- Intent bindIntent = bindIntentCaptor.getAllValues().get(0);
- assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
- assertEquals(CAR_PKG, bindIntent.getComponent().getPackageName());
- assertEquals(CAR_CLASS, bindIntent.getComponent().getClassName());
+ verifyBinding(bindIntentCaptor, 0, CAR_PKG, CAR_CLASS);
}
@MediumTest
@@ -646,28 +748,16 @@
public void testNoBindToInvalidService_CarModeUI() throws Exception {
setupMocks(true /* isExternalCall */);
setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
+ mInCallController.bindToServices(mMockCall);
- when(mMockRoleManagerAdapter.getCarModeDialerApp()).thenReturn(CAR_PKG);
when(mMockPackageManager.checkPermission(
- matches(Manifest.permission.CALL_COMPANION_APP),
+ matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
matches(CAR_PKG))).thenReturn(PackageManager.PERMISSION_DENIED);
// Enable car mode
when(mMockSystemStateHelper.isCarMode()).thenReturn(true);
- mInCallController.bindToServices(mMockCall);
- // Query for the different InCallServices
- ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
- queryIntentCaptor.capture(),
- eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID));
- // Verify call for default dialer InCallService
- assertEquals(DEF_PKG, queryIntentCaptor.getAllValues().get(0).getPackage());
- // Verify call for system dialer InCallService
- assertEquals(null, queryIntentCaptor.getAllValues().get(1).getPackage());
- // Verify call for invalid car mode ui InCallServices
- assertEquals(CAR_PKG, queryIntentCaptor.getAllValues().get(2).getPackage());
- // Verify call for non-UI InCallServices
- assertEquals(null, queryIntentCaptor.getAllValues().get(3).getPackage());
+ // Register the fact that the invalid app entered car mode.
+ mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true);
// Bind InCallServices
ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -679,10 +769,99 @@
eq(UserHandle.CURRENT));
// Verify bind to default package, instead of the invalid car mode ui.
assertEquals(1, bindIntentCaptor.getAllValues().size());
- Intent bindIntent = bindIntentCaptor.getAllValues().get(0);
+ verifyBinding(bindIntentCaptor, 0, DEF_PKG, DEF_CLASS);
+ }
+
+ @MediumTest
+ @Test
+ public void testSanitizeContactName() throws Exception {
+ setupMocks(false /* isExternalCall */);
+ setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
+ when(mMockPackageManager.checkPermission(
+ matches(Manifest.permission.READ_CONTACTS),
+ matches(DEF_PKG))).thenReturn(PackageManager.PERMISSION_DENIED);
+ when(mMockCall.getName()).thenReturn("evil");
+
+ mInCallController.bindToServices(mMockCall);
+
+ // Bind InCallServices
+ ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
+ ArgumentCaptor.forClass(ServiceConnection.class);
+ verify(mMockContext, times(1)).bindServiceAsUser(
+ bindIntentCaptor.capture(),
+ serviceConnectionCaptor.capture(),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+ | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+ eq(UserHandle.CURRENT));
+ assertEquals(1, bindIntentCaptor.getAllValues().size());
+ verifyBinding(bindIntentCaptor, 0, DEF_PKG, DEF_CLASS);
+
+ IInCallService.Stub mockInCallServiceStub = mock(IInCallService.Stub.class);
+ IInCallService mockInCallService = mock(IInCallService.class);
+ when(mockInCallServiceStub.queryLocalInterface(anyString())).thenReturn(mockInCallService);
+ serviceConnectionCaptor.getValue().onServiceConnected(new ComponentName(DEF_PKG, DEF_CLASS),
+ mockInCallServiceStub);
+
+ mInCallController.onCallAdded(mMockCall);
+ ArgumentCaptor<ParcelableCall> parcelableCallCaptor =
+ ArgumentCaptor.forClass(ParcelableCall.class);
+ verify(mockInCallService).addCall(parcelableCallCaptor.capture());
+ assertTrue(TextUtils.isEmpty(parcelableCallCaptor.getValue().getContactDisplayName()));
+ }
+
+ /**
+ * Ensures that the {@link InCallController} will bind to a higher priority car mode service
+ * when one becomes available.
+ */
+ @MediumTest
+ @Test
+ public void testCarmodeRebindHigherPriority() throws Exception {
+ setupMocks(true /* isExternalCall */);
+ setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
+ // Bind to default dialer.
+ mInCallController.bindToServices(mMockCall);
+
+ // Enable car mode and enter car mode at default priority.
+ when(mMockSystemStateHelper.isCarMode()).thenReturn(true);
+ mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true);
+
+ // And change to the second car mode app.
+ mInCallController.handleCarModeChange(100, CAR2_PKG, true);
+
+ // Exit car mode at higher priority.
+ mInCallController.handleCarModeChange(100, CAR2_PKG, false);
+
+ // Bind InCallServices
+ ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockContext, times(4)).bindServiceAsUser(
+ bindIntentCaptor.capture(),
+ any(ServiceConnection.class),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+ | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+ eq(UserHandle.CURRENT));
+ // Verify bind car mode ui
+ assertEquals(4, bindIntentCaptor.getAllValues().size());
+
+ // Should have first bound to the default dialer.
+ verifyBinding(bindIntentCaptor, 0, DEF_PKG, DEF_CLASS);
+
+ // Should have next bound to the car mode app.
+ verifyBinding(bindIntentCaptor, 1, CAR_PKG, CAR_CLASS);
+
+ // Finally, should have bound to the higher priority car mode app
+ verifyBinding(bindIntentCaptor, 2, CAR2_PKG, CAR2_CLASS);
+
+ // Should have rebound to the car mode app.
+ verifyBinding(bindIntentCaptor, 3, CAR_PKG, CAR_CLASS);
+ }
+
+ public void verifyBinding(ArgumentCaptor<Intent> bindIntentCaptor, int i, String carPkg,
+ String carClass) {
+ Intent bindIntent = bindIntentCaptor.getAllValues().get(i);
assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
- assertEquals(DEF_PKG, bindIntent.getComponent().getPackageName());
- assertEquals(DEF_CLASS, bindIntent.getComponent().getClassName());
+ assertEquals(carPkg, bindIntent.getComponent().getPackageName());
+ assertEquals(carClass, bindIntent.getComponent().getClassName());
}
/**
@@ -694,7 +873,7 @@
public void testBindingFuture() throws Exception {
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
when(mMockCall.isIncoming()).thenReturn(false);
when(mMockCall.isExternalCall()).thenReturn(false);
when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
@@ -737,23 +916,127 @@
assertTrue(bindTimeout.getNow(false));
}
+ /**
+ * Verify that if we go from a dialer which doesn't support self managed calls to a car mode
+ * dialer that does support them, we will bind.
+ */
+ @MediumTest
+ @Test
+ public void testBindToService_SelfManagedCarModeUI() throws Exception {
+ setupMocks(true /* isExternalCall */, true /* isSelfManaged*/);
+ setupMockPackageManager(true /* default */, true /* system */, true /* external calls */,
+ false /* selfManagedInDefaultDialer */, true /* selfManagedInCarModeDialer */);
+
+ // Bind; we should not bind to anything right now; the dialer does not support self
+ // managed calls.
+ mInCallController.bindToServices(mMockCall);
+
+ // Bind InCallServices; make sure no binding took place. InCallController handles not
+ // binding initially, but the rebind (see next test case) will always happen.
+ verify(mMockContext, never()).bindServiceAsUser(
+ any(Intent.class),
+ any(ServiceConnection.class),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+ | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+ eq(UserHandle.CURRENT));
+
+ // Now switch to car mode.
+ // Enable car mode and enter car mode at default priority.
+ when(mMockSystemStateHelper.isCarMode()).thenReturn(true);
+ mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true);
+
+ ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockContext, times(1)).bindServiceAsUser(
+ bindIntentCaptor.capture(),
+ any(ServiceConnection.class),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+ | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+ eq(UserHandle.CURRENT));
+ // Verify bind car mode ui
+ assertEquals(1, bindIntentCaptor.getAllValues().size());
+ verifyBinding(bindIntentCaptor, 0, CAR_PKG, CAR_CLASS);
+ }
+
+ /**
+ * Verify that if we go from a dialer which doesn't support self managed calls to a car mode
+ * dialer that does not support them, the calls are not sent to the call mode UI.
+ */
+ @MediumTest
+ @Test
+ public void testBindToService_SelfManagedNoCarModeUI() throws Exception {
+ setupMocks(true /* isExternalCall */, true /* isSelfManaged*/);
+ setupMockPackageManager(true /* default */, true /* system */, true /* external calls */,
+ false /* selfManagedInDefaultDialer */, false /* selfManagedInCarModeDialer */);
+
+ // Bind; we should not bind to anything right now; the dialer does not support self
+ // managed calls.
+ mInCallController.bindToServices(mMockCall);
+
+ // Bind InCallServices; make sure no binding took place.
+ verify(mMockContext, never()).bindServiceAsUser(
+ any(Intent.class),
+ any(ServiceConnection.class),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+ | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+ eq(UserHandle.CURRENT));
+
+ // Now switch to car mode.
+ // Enable car mode and enter car mode at default priority.
+ when(mMockSystemStateHelper.isCarMode()).thenReturn(true);
+ mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true);
+
+ // We currently will bind to the car-mode InCallService even if there are no calls available
+ // for it. Its not perfect, but it reflects the fact that the InCallController isn't
+ // sophisticated enough to realize until its already bound whether there are in fact calls
+ // which will be sent to it.
+ ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
+ ArgumentCaptor.forClass(ServiceConnection.class);
+ verify(mMockContext, times(1)).bindServiceAsUser(
+ any(Intent.class),
+ serviceConnectionCaptor.capture(),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+ | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+ eq(UserHandle.CURRENT));
+
+ ServiceConnection serviceConnection = serviceConnectionCaptor.getValue();
+ ComponentName defDialerComponentName = new ComponentName(DEF_PKG, DEF_CLASS);
+ IBinder mockBinder = mock(IBinder.class);
+ IInCallService mockInCallService = mock(IInCallService.class);
+ when(mockBinder.queryLocalInterface(anyString())).thenReturn(mockInCallService);
+
+ // Emulate successful connection.
+ serviceConnection.onServiceConnected(defDialerComponentName, mockBinder);
+ verify(mockInCallService).setInCallAdapter(any(IInCallAdapter.class));
+
+ // We should not have gotten informed about any calls
+ verify(mockInCallService, never()).addCall(any(ParcelableCall.class));
+ }
+
private void setupMocks(boolean isExternalCall) {
+ setupMocks(isExternalCall, false /* isSelfManagedCall */);
+ }
+
+ private void setupMocks(boolean isExternalCall, boolean isSelfManagedCall) {
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
when(mMockCall.isIncoming()).thenReturn(false);
when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
when(mMockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
when(mMockCall.isExternalCall()).thenReturn(isExternalCall);
+ when(mMockCall.isSelfManaged()).thenReturn(isSelfManagedCall);
}
- private ResolveInfo getDefResolveInfo(final boolean includeExternalCalls) {
+ private ResolveInfo getDefResolveInfo(final boolean includeExternalCalls,
+ final boolean includeSelfManagedCalls) {
return new ResolveInfo() {{
serviceInfo = new ServiceInfo();
serviceInfo.packageName = DEF_PKG;
serviceInfo.name = DEF_CLASS;
+ serviceInfo.applicationInfo = new ApplicationInfo();
+ serviceInfo.applicationInfo.uid = DEF_UID;
serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
serviceInfo.metaData = new Bundle();
serviceInfo.metaData.putBoolean(
@@ -762,14 +1045,25 @@
serviceInfo.metaData.putBoolean(
TelecomManager.METADATA_INCLUDE_EXTERNAL_CALLS, true);
}
+ if (includeSelfManagedCalls) {
+ serviceInfo.metaData.putBoolean(
+ TelecomManager.METADATA_INCLUDE_SELF_MANAGED_CALLS, true);
+ }
}};
}
- private ResolveInfo getCarModeResolveinfo(final boolean includeExternalCalls) {
+ private ResolveInfo getCarModeResolveinfo(final String packageName, final String className,
+ final boolean includeExternalCalls, final boolean includeSelfManagedCalls) {
return new ResolveInfo() {{
serviceInfo = new ServiceInfo();
- serviceInfo.packageName = CAR_PKG;
- serviceInfo.name = CAR_CLASS;
+ serviceInfo.packageName = packageName;
+ serviceInfo.name = className;
+ serviceInfo.applicationInfo = new ApplicationInfo();
+ if (CAR_PKG.equals(packageName)) {
+ serviceInfo.applicationInfo.uid = CAR_UID;
+ } else {
+ serviceInfo.applicationInfo.uid = CAR2_UID;
+ }
serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
serviceInfo.metaData = new Bundle();
serviceInfo.metaData.putBoolean(
@@ -778,6 +1072,10 @@
serviceInfo.metaData.putBoolean(
TelecomManager.METADATA_INCLUDE_EXTERNAL_CALLS, true);
}
+ if (includeSelfManagedCalls) {
+ serviceInfo.metaData.putBoolean(
+ TelecomManager.METADATA_INCLUDE_SELF_MANAGED_CALLS, true);
+ }
}};
}
@@ -786,6 +1084,8 @@
serviceInfo = new ServiceInfo();
serviceInfo.packageName = SYS_PKG;
serviceInfo.name = SYS_CLASS;
+ serviceInfo.applicationInfo = new ApplicationInfo();
+ serviceInfo.applicationInfo.uid = SYS_UID;
serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
}};
}
@@ -795,13 +1095,22 @@
serviceInfo = new ServiceInfo();
serviceInfo.packageName = COMPANION_PKG;
serviceInfo.name = COMPANION_CLASS;
+ serviceInfo.applicationInfo = new ApplicationInfo();
+ serviceInfo.applicationInfo.uid = COMPANION_UID;
serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
}};
}
private void setupMockPackageManager(final boolean useDefaultDialer,
final boolean useSystemDialer, final boolean includeExternalCalls) {
+ setupMockPackageManager(useDefaultDialer, useSystemDialer, includeExternalCalls,
+ false /* self mgd */, false /* self mgd */);
+ }
+ private void setupMockPackageManager(final boolean useDefaultDialer,
+ final boolean useSystemDialer, final boolean includeExternalCalls,
+ final boolean includeSelfManagedCallsInDefaultDialer,
+ final boolean includeSelfManagedCallsInCarModeDialer) {
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
@@ -815,7 +1124,8 @@
LinkedList<ResolveInfo> resolveInfo = new LinkedList<ResolveInfo>();
if (!TextUtils.isEmpty(packageName)) {
if (packageName.equals(DEF_PKG) && useDefaultDialer) {
- resolveInfo.add(getDefResolveInfo(includeExternalCalls));
+ resolveInfo.add(getDefResolveInfo(includeExternalCalls,
+ includeSelfManagedCallsInDefaultDialer));
}
if (packageName.equals(SYS_PKG) && useSystemDialer) {
@@ -827,7 +1137,13 @@
}
if (packageName.equals(CAR_PKG)) {
- resolveInfo.add(getCarModeResolveinfo(includeExternalCalls));
+ resolveInfo.add(getCarModeResolveinfo(CAR_PKG, CAR_CLASS,
+ includeExternalCalls, includeSelfManagedCallsInCarModeDialer));
+ }
+
+ if (packageName.equals(CAR2_PKG)) {
+ resolveInfo.add(getCarModeResolveinfo(CAR2_PKG, CAR2_CLASS,
+ includeExternalCalls, includeSelfManagedCallsInCarModeDialer));
}
}
return resolveInfo;
diff --git a/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java b/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java
index fd581f3..fd7505b 100644
--- a/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java
@@ -66,7 +66,7 @@
@SmallTest
@Test
public void testRingingCallAdded() throws Exception {
- when(mCallsManager.getRingingCall()).thenReturn(mCall);
+ when(mCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(mCall);
when(mWakeLockAdapter.isHeld()).thenReturn(false);
mInCallWakeLockController.onCallAdded(mCall);
@@ -77,7 +77,7 @@
@SmallTest
@Test
public void testNonRingingCallAdded() throws Exception {
- when(mCallsManager.getRingingCall()).thenReturn(null);
+ when(mCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(null);
when(mWakeLockAdapter.isHeld()).thenReturn(false);
mInCallWakeLockController.onCallAdded(mCall);
@@ -88,7 +88,7 @@
@SmallTest
@Test
public void testRingingCallTransition() throws Exception {
- when(mCallsManager.getRingingCall()).thenReturn(mCall);
+ when(mCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(mCall);
when(mWakeLockAdapter.isHeld()).thenReturn(false);
mInCallWakeLockController.onCallStateChanged(mCall, CallState.NEW, CallState.RINGING);
@@ -99,7 +99,7 @@
@SmallTest
@Test
public void testRingingCallRemoved() throws Exception {
- when(mCallsManager.getRingingCall()).thenReturn(null);
+ when(mCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(null);
when(mWakeLockAdapter.isHeld()).thenReturn(false);
mInCallWakeLockController.onCallRemoved(mCall);
@@ -110,7 +110,7 @@
@SmallTest
@Test
public void testWakeLockReleased() throws Exception {
- when(mCallsManager.getRingingCall()).thenReturn(null);
+ when(mCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(null);
when(mWakeLockAdapter.isHeld()).thenReturn(true);
mInCallWakeLockController.onCallRemoved(mCall);
@@ -121,7 +121,7 @@
@SmallTest
@Test
public void testAcquireWakeLockWhenHeld() throws Exception {
- when(mCallsManager.getRingingCall()).thenReturn(mCall);
+ when(mCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(mCall);
when(mWakeLockAdapter.isHeld()).thenReturn(true);
mInCallWakeLockController.onCallAdded(mock(Call.class));
diff --git a/tests/src/com/android/server/telecom/tests/IncomingCallFilterGraphTest.java b/tests/src/com/android/server/telecom/tests/IncomingCallFilterGraphTest.java
new file mode 100644
index 0000000..8c0adfb
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/IncomingCallFilterGraphTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2019 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 android.content.ContentResolver;
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.callfiltering.CallFilter;
+import com.android.server.telecom.callfiltering.CallFilterResultCallback;
+import com.android.server.telecom.callfiltering.CallFilteringResult;
+import com.android.server.telecom.callfiltering.IncomingCallFilterGraph;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public class IncomingCallFilterGraphTest extends TelecomTestCase {
+ @Mock private Call mCall;
+ @Mock private Context mContext;
+ @Mock private Timeouts.Adapter mTimeoutsAdapter;
+ private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {};
+
+ private static final CallFilteringResult PASS_CALL_RESULT = new CallFilteringResult.Builder()
+ .setShouldAllowCall(true)
+ .setShouldReject(false)
+ .setShouldSilence(false)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(true).build();
+ private static final CallFilteringResult REJECT_CALL_RESULT = new CallFilteringResult.Builder()
+ .setShouldAllowCall(false)
+ .setShouldReject(true)
+ .setShouldSilence(false)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(true).build();
+ private final long FILTER_TIMEOUT = 5000;
+ private final long TEST_TIMEOUT = 7000;
+ private final long TIMEOUT_FILTER_SLEEP_TIME = 10000;
+
+ private class AllowFilter extends CallFilter {
+ @Override
+ public CompletionStage<CallFilteringResult> startFilterLookup(
+ CallFilteringResult priorStageResult) {
+ return CompletableFuture.completedFuture(PASS_CALL_RESULT);
+ }
+ }
+
+ private class DisallowFilter extends CallFilter {
+ @Override
+ public CompletionStage<CallFilteringResult> startFilterLookup(
+ CallFilteringResult priorStageResult) {
+ return CompletableFuture.completedFuture(REJECT_CALL_RESULT);
+ }
+ }
+
+ private class TimeoutFilter extends CallFilter {
+ @Override
+ public CompletionStage<CallFilteringResult> startFilterLookup(
+ CallFilteringResult priorStageResult) {
+ HandlerThread handlerThread = new HandlerThread("TimeoutFilter");
+ handlerThread.start();
+ Handler handler = new Handler(handlerThread.getLooper());
+
+ CompletableFuture<CallFilteringResult> resultFuture = new CompletableFuture<>();
+ handler.postDelayed(() -> resultFuture.complete(PASS_CALL_RESULT),
+ TIMEOUT_FILTER_SLEEP_TIME);
+ return CompletableFuture.completedFuture(PASS_CALL_RESULT);
+ }
+ }
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ when(mContext.getContentResolver()).thenReturn(null);
+ when(mTimeoutsAdapter.getCallScreeningTimeoutMillis(nullable(ContentResolver.class)))
+ .thenReturn(FILTER_TIMEOUT);
+
+ }
+
+ @SmallTest
+ @Test
+ public void testEmptyGraph() throws Exception {
+ CompletableFuture<CallFilteringResult> testResult = new CompletableFuture<>();
+ CallFilterResultCallback listener = (call, result) -> testResult.complete(result);
+
+ IncomingCallFilterGraph graph = new IncomingCallFilterGraph(mCall, listener, mContext,
+ mTimeoutsAdapter, mLock);
+ graph.performFiltering();
+
+ assertEquals(PASS_CALL_RESULT, testResult.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+ }
+
+ @SmallTest
+ @Test
+ public void testFiltersPerformOrder() throws Exception {
+ CompletableFuture<CallFilteringResult> testResult = new CompletableFuture<>();
+ CallFilterResultCallback listener = (call, result) -> testResult.complete(result);
+
+ IncomingCallFilterGraph graph = new IncomingCallFilterGraph(mCall, listener, mContext,
+ mTimeoutsAdapter, mLock);
+ AllowFilter allowFilter = new AllowFilter();
+ DisallowFilter disallowFilter = new DisallowFilter();
+ graph.addFilter(allowFilter);
+ graph.addFilter(disallowFilter);
+ IncomingCallFilterGraph.addEdge(allowFilter, disallowFilter);
+ graph.performFiltering();
+
+ assertEquals(REJECT_CALL_RESULT, testResult.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+ }
+
+ @SmallTest
+ @Test
+ public void testFiltersPerformInParallel() throws Exception {
+ CompletableFuture<CallFilteringResult> testResult = new CompletableFuture<>();
+ CallFilterResultCallback listener = (call, result) -> testResult.complete(result);
+
+ IncomingCallFilterGraph graph = new IncomingCallFilterGraph(mCall, listener, mContext,
+ mTimeoutsAdapter, mLock);
+ AllowFilter allowFilter1 = new AllowFilter();
+ AllowFilter allowFilter2 = new AllowFilter();
+ DisallowFilter disallowFilter = new DisallowFilter();
+ graph.addFilter(allowFilter1);
+ graph.addFilter(allowFilter2);
+ graph.addFilter(disallowFilter);
+ graph.performFiltering();
+
+ assertEquals(REJECT_CALL_RESULT, testResult.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+ }
+
+ @SmallTest
+ @Test
+ public void testFiltersTimeout() throws Exception {
+ CompletableFuture<CallFilteringResult> testResult = new CompletableFuture<>();
+ CallFilterResultCallback listener = (call, result) -> testResult.complete(result);
+
+ IncomingCallFilterGraph graph = new IncomingCallFilterGraph(mCall, listener, mContext,
+ mTimeoutsAdapter, mLock);
+ DisallowFilter disallowFilter = new DisallowFilter();
+ TimeoutFilter timeoutFilter = new TimeoutFilter();
+ graph.addFilter(disallowFilter);
+ graph.addFilter(timeoutFilter);
+ IncomingCallFilterGraph.addEdge(disallowFilter, timeoutFilter);
+ graph.performFiltering();
+
+ assertEquals(REJECT_CALL_RESULT, testResult.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java b/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
index e399088..8e2d11e 100644
--- a/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
@@ -17,18 +17,21 @@
package com.android.server.telecom.tests;
import android.content.ContentResolver;
-import android.content.IContentProvider;
import android.net.Uri;
-import android.provider.CallLog;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.CallLog.Calls;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.server.telecom.Call;
import com.android.server.telecom.Timeouts;
import com.android.server.telecom.callfiltering.CallFilterResultCallback;
import com.android.server.telecom.callfiltering.CallFilteringResult;
+import com.android.server.telecom.callfiltering.CallFilteringResult.Builder;
import com.android.server.telecom.callfiltering.IncomingCallFilter;
import com.android.server.telecom.TelecomSystem;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -65,48 +68,50 @@
private static final long SHORT_TIMEOUT = 100;
private static final CallFilteringResult PASS_CALL_RESULT =
- new CallFilteringResult(
- true, // shouldAllowCall
- false, // shouldReject
- true, // shouldAddToCallLog
- true // shouldShowNotification
- );
+ new Builder()
+ .setShouldAllowCall(true)
+ .setShouldReject(false)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(true)
+ .build();
private static final CallFilteringResult ASYNC_BLOCK_CHECK_BLOCK_RESULT =
- new CallFilteringResult(
- false, // shouldAllowCall
- true, // shouldReject
- true, // shouldAddToCallLog
- false, // shouldShowNotification
- CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER, //callBlockReason
- null, //callScreeningAppName
- null //callScreeningComponentName
- );
+ new Builder()
+ .setShouldAllowCall(false)
+ .setShouldReject(true)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(false)
+ .setCallBlockReason(Calls.BLOCK_REASON_BLOCKED_NUMBER)
+ .setCallScreeningAppName(null)
+ .setCallScreeningComponentName(null)
+ .build();
private static final CallFilteringResult DIRECT_TO_VOICEMAIL_CALL_BLOCK_RESULT =
- new CallFilteringResult(
- false, // shouldAllowCall
- true, // shouldReject
- true, // shouldAddToCallLog
- true, // shouldShowNotification
- CallLog.Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL, //callBlockReason
- null, //callScreeningAppName
- null //callScreeningComponentName
- );
+ new Builder()
+ .setShouldAllowCall(false)
+ .setShouldReject(true)
+ .setShouldAddToCallLog(true)
+ .setShouldShowNotification(true)
+ .setCallBlockReason(Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL)
+ .setCallScreeningAppName(null)
+ .setCallScreeningComponentName(null)
+ .build();
private static final CallFilteringResult CALL_SCREENING_SERVICE_BLOCK_RESULT =
- new CallFilteringResult(
- false, // shouldAllowCall
- true, // shouldReject
- false, // shouldAddToCallLog
- true, // shouldShowNotification
- CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
- "com.android.thirdparty", //callScreeningAppName
- "com.android.thirdparty/com.android.thirdparty.callscreeningserviceimpl"
- //callScreeningComponentName
- );
+ new Builder()
+ .setShouldAllowCall(false)
+ .setShouldReject(true)
+ .setShouldAddToCallLog(false)
+ .setShouldShowNotification(true)
+ .setCallBlockReason(Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
+ .setCallScreeningAppName("com.android.thirdparty")
+ .setCallScreeningComponentName(
+ "com.android.thirdparty/"
+ + "com.android.thirdparty.callscreeningserviceimpl")
+ .build();
private static final CallFilteringResult DEFAULT_RESULT = PASS_CALL_RESULT;
+ private Handler mHandler = new Handler(Looper.getMainLooper());
@Override
@Before
@@ -117,11 +122,19 @@
setTimeoutLength(LONG_TIMEOUT);
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ mHandler.removeCallbacksAndMessages(null);
+ waitForHandlerAction(mHandler, 1000);
+ super.tearDown();
+ }
+
@SmallTest
@Test
public void testAsyncBlockCallResultFilter() {
IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
- mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1));
+ mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1), mHandler);
testFilter.performFiltering();
verify(mFilter1).startFilterLookup(mCall, testFilter);
@@ -135,7 +148,7 @@
@Test
public void testDirectToVoiceMailCallResultFilter() {
IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
- mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1));
+ mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1), mHandler);
testFilter.performFiltering();
verify(mFilter1).startFilterLookup(mCall, testFilter);
@@ -149,7 +162,7 @@
@Test
public void testCallScreeningServiceBlockCallResultFilter() {
IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
- mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1));
+ mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1), mHandler);
testFilter.performFiltering();
verify(mFilter1).startFilterLookup(mCall, testFilter);
@@ -163,7 +176,7 @@
@Test
public void testPassCallResultFilter() {
IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
- mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1));
+ mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1), mHandler);
testFilter.performFiltering();
verify(mFilter1).startFilterLookup(mCall, testFilter);
@@ -183,7 +196,7 @@
add(mFilter4);
}};
IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
- mLock, mTimeoutsAdapter, filters);
+ mLock, mTimeoutsAdapter, filters, mHandler);
testFilter.performFiltering();
verify(mFilter1).startFilterLookup(mCall, testFilter);
verify(mFilter2).startFilterLookup(mCall, testFilter);
@@ -195,16 +208,15 @@
testFilter.onCallFilteringComplete(mCall, DIRECT_TO_VOICEMAIL_CALL_BLOCK_RESULT);
testFilter.onCallFilteringComplete(mCall, CALL_SCREENING_SERVICE_BLOCK_RESULT);
waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
- verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq(
- new CallFilteringResult(
- false, // shouldAllowCall
- true, // shouldReject
- false, // shouldAddToCallLog
- false, // shouldShowNotification
- CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER, //callBlockReason
- null, //callScreeningAppName
- null //callScreeningComponentName
- )));
+ verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq(new Builder()
+ .setShouldAllowCall(false)
+ .setShouldReject(true)
+ .setShouldAddToCallLog(false)
+ .setShouldShowNotification(false)
+ .setCallBlockReason(Calls.BLOCK_REASON_BLOCKED_NUMBER)
+ .setCallScreeningAppName(null)
+ .setCallScreeningComponentName(null)
+ .build()));
}
@SmallTest
@@ -217,7 +229,7 @@
add(mFilter3);
}};
IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
- mLock, mTimeoutsAdapter, filters);
+ mLock, mTimeoutsAdapter, filters, mHandler);
testFilter.performFiltering();
verify(mFilter1).startFilterLookup(mCall, testFilter);
verify(mFilter2).startFilterLookup(mCall, testFilter);
@@ -227,16 +239,15 @@
testFilter.onCallFilteringComplete(mCall, DIRECT_TO_VOICEMAIL_CALL_BLOCK_RESULT);
testFilter.onCallFilteringComplete(mCall, CALL_SCREENING_SERVICE_BLOCK_RESULT);
waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
- verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq(
- new CallFilteringResult(
- false, // shouldAllowCall
- true, // shouldReject
- false, // shouldAddToCallLog
- true, // shouldShowNotification
- CallLog.Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL, //callBlockReason
- null, ////callScreeningAppName
- null ////callScreeningComponentName
- )));
+ verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq(new Builder()
+ .setShouldAllowCall(false)
+ .setShouldReject(true)
+ .setShouldAddToCallLog(false)
+ .setShouldShowNotification(true)
+ .setCallBlockReason(Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL)
+ .setCallScreeningAppName(null)
+ .setCallScreeningComponentName(null)
+ .build()));
}
@SmallTest
@@ -248,7 +259,7 @@
add(mFilter2);
}};
IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
- mLock, mTimeoutsAdapter, filters);
+ mLock, mTimeoutsAdapter, filters, mHandler);
testFilter.performFiltering();
verify(mFilter1).startFilterLookup(mCall, testFilter);
verify(mFilter2).startFilterLookup(mCall, testFilter);
@@ -256,17 +267,16 @@
testFilter.onCallFilteringComplete(mCall, PASS_CALL_RESULT);
testFilter.onCallFilteringComplete(mCall, CALL_SCREENING_SERVICE_BLOCK_RESULT);
waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
- verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq(
- new CallFilteringResult(
- false, // shouldAllowCall
- true, // shouldReject
- false, // shouldAddToCallLog
- true, // shouldShowNotification
- CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
- "com.android.thirdparty", //callScreeningAppName
- "com.android.thirdparty/com.android.thirdparty.callscreeningserviceimpl"
- //callScreeningComponentName
- )));
+ verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq(new Builder()
+ .setShouldAllowCall(false)
+ .setShouldReject(true)
+ .setShouldAddToCallLog(false)
+ .setShouldShowNotification(true)
+ .setCallBlockReason(Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
+ .setCallScreeningAppName("com.android.thirdparty")
+ .setCallScreeningComponentName(
+ "com.android.thirdparty/com.android.thirdparty.callscreeningserviceimpl")
+ .build()));
}
@SmallTest
@@ -274,7 +284,7 @@
public void testFilterTimeout() throws Exception {
setTimeoutLength(SHORT_TIMEOUT);
IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
- mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1));
+ mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1), mHandler);
testFilter.performFiltering();
verify(mResultCallback, timeout((int) SHORT_TIMEOUT * 2)).onCallFilteringComplete(eq(mCall),
eq(DEFAULT_RESULT));
@@ -290,7 +300,7 @@
public void testFilterTimeoutDoesntTrip() throws Exception {
setTimeoutLength(SHORT_TIMEOUT);
IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
- mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1));
+ mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1), mHandler);
testFilter.performFiltering();
testFilter.onCallFilteringComplete(mCall, PASS_CALL_RESULT);
waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
diff --git a/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java b/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java
index f54783e..a871b73 100644
--- a/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java
+++ b/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java
@@ -28,6 +28,7 @@
import com.android.server.telecom.HandoverState;
import com.android.server.telecom.ui.IncomingCallNotifier;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -81,6 +82,12 @@
when(mRingingCall.getHandoverState()).thenReturn(HandoverState.HANDOVER_NONE);
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
/**
* Add a call that isn't ringing.
*/
diff --git a/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java b/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
index a3bdb10..10a0194 100644
--- a/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
@@ -39,7 +39,7 @@
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.SmallTest;
-import com.android.internal.telephony.CallerInfo;
+import android.telecom.CallerInfo;
import com.android.server.telecom.CallerInfoLookupHelper;
import com.android.server.telecom.Constants;
import com.android.server.telecom.DefaultDialerCache;
@@ -187,6 +187,7 @@
public void tearDown() throws Exception {
TelecomSystem.setInstance(null);
when(mTelecomSystem.isBootComplete()).thenReturn(false);
+ super.tearDown();
}
@SmallTest
@@ -444,7 +445,7 @@
CallLog.Calls.PRESENTATION_ALLOWED, CALL_TIMESTAMP)
.build();
- when(cp.query(anyString(), eq(queryUri), nullable(String[].class),
+ when(cp.query(anyString(), nullable(String.class), eq(queryUri), nullable(String[].class),
nullable(Bundle.class), nullable(ICancellationSignal.class)))
.thenReturn(mockMissedCallsCursor);
@@ -513,7 +514,7 @@
PRIMARY_USER.getIdentifier());
IContentProvider cp = getContentProviderForUser(PRIMARY_USER.getIdentifier());
- when(cp.query(anyString(), eq(queryUri), nullable(String[].class),
+ when(cp.query(anyString(), nullable(String.class), eq(queryUri), nullable(String[].class),
nullable(Bundle.class), nullable(ICancellationSignal.class)))
.thenReturn(mockMissedCallsCursor);
diff --git a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
index 81b4326..9470008 100644
--- a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
+++ b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
@@ -28,6 +28,7 @@
import static org.mockito.Matchers.isNotNull;
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -38,21 +39,23 @@
import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
import android.telecom.GatewayInfo;
+import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telephony.DisconnectCause;
+import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.NewOutgoingCallIntentBroadcaster;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.PhoneNumberUtilsAdapter;
@@ -61,6 +64,7 @@
import com.android.server.telecom.SystemStateHelper;
import com.android.server.telecom.TelecomSystem;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -84,17 +88,17 @@
@Mock private Call mCall;
@Mock private SystemStateHelper mSystemStateHelper;
@Mock private UserHandle mUserHandle;
+ @Mock private PhoneAccount mPhoneAccount;
@Mock private PhoneAccountRegistrar mPhoneAccountRegistrar;
@Mock private RoleManagerAdapter mRoleManagerAdapter;
+ @Mock private DefaultDialerCache mDefaultDialerCache;
- private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapterSpy;
+ private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter = new PhoneNumberUtilsAdapterImpl();
@Override
@Before
public void setUp() throws Exception {
super.setUp();
- mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
- mPhoneNumberUtilsAdapterSpy = spy(new PhoneNumberUtilsAdapterImpl());
when(mCall.getInitiatingUser()).thenReturn(UserHandle.CURRENT);
when(mCallsManager.getLock()).thenReturn(new TelecomSystem.SyncRoot() { });
when(mCallsManager.getSystemStateHelper()).thenReturn(mSystemStateHelper);
@@ -103,9 +107,32 @@
when(mCallsManager.getRoleManagerAdapter()).thenReturn(mRoleManagerAdapter);
when(mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(
any(PhoneAccountHandle.class))).thenReturn(-1);
+ when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(
+ any(PhoneAccountHandle.class))).thenReturn(mPhoneAccount);
+ when(mPhoneAccount.isSelfManaged()).thenReturn(true);
when(mSystemStateHelper.isCarMode()).thenReturn(false);
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @SmallTest
+ @Test
+ public void testSelfManagedCall() {
+ Uri handle = Uri.parse("tel:6505551234");
+ Intent selfManagedCallIntent = buildIntent(handle, Intent.ACTION_CALL, null);
+ selfManagedCallIntent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ new PhoneAccountHandle(new ComponentName("fakeTestPackage", "fakeTestClass"),
+ "id_tSMC"));
+ NewOutgoingCallIntentBroadcaster.CallDisposition callDisposition = processIntent(
+ selfManagedCallIntent, true);
+ assertEquals(false, callDisposition.requestRedirection);
+ assertEquals(DisconnectCause.NOT_DISCONNECTED, callDisposition.disconnectCause);
+ }
+
@SmallTest
@Test
public void testNullHandle() {
@@ -123,9 +150,10 @@
Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse(voicemailNumber));
intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, true);
- int result = processIntent(intent, true).disconnectCause;
-
- assertEquals(DisconnectCause.NOT_DISCONNECTED, result);
+ NewOutgoingCallIntentBroadcaster.CallDisposition callDisposition = processIntent(
+ intent, true);
+ assertEquals(false, callDisposition.requestRedirection);
+ assertEquals(DisconnectCause.NOT_DISCONNECTED, callDisposition.disconnectCause);
verify(mCallsManager).placeOutgoingCall(eq(mCall), eq(Uri.parse(voicemailNumber)),
nullable(GatewayInfo.class), eq(true), eq(VideoProfile.STATE_AUDIO_ONLY));
}
@@ -190,8 +218,8 @@
@Test
public void testEmergencyCallWithNonDefaultDialer() {
Uri handle = Uri.parse("tel:6505551911");
- doReturn(true).when(mPhoneNumberUtilsAdapterSpy).isPotentialLocalEmergencyNumber(
- any(Context.class), eq(handle.getSchemeSpecificPart()));
+ doReturn(true).when(mComponentContextFixture.getTelephonyManager())
+ .isPotentialEmergencyNumber(eq(handle.getSchemeSpecificPart()));
Intent intent = new Intent(Intent.ACTION_CALL, handle);
String ui_package_string = "sample_string_1";
@@ -200,6 +228,9 @@
ui_package_string);
mComponentContextFixture.putResource(R.string.dialer_default_class,
dialer_default_class_string);
+ when(mDefaultDialerCache.getSystemDialerApplication()).thenReturn(ui_package_string);
+ when(mDefaultDialerCache.getSystemDialerComponent()).thenReturn(
+ new ComponentName(ui_package_string, dialer_default_class_string));
int result = processIntent(intent, false).disconnectCause;
@@ -258,8 +289,8 @@
@Test
public void testActionEmergencyWithNonEmergencyNumber() {
Uri handle = Uri.parse("tel:6505551911");
- doReturn(false).when(mPhoneNumberUtilsAdapterSpy).isPotentialLocalEmergencyNumber(
- any(Context.class), eq(handle.getSchemeSpecificPart()));
+ doReturn(false).when(mComponentContextFixture.getTelephonyManager())
+ .isPotentialEmergencyNumber(eq(handle.getSchemeSpecificPart()));
Intent intent = new Intent(Intent.ACTION_CALL_EMERGENCY, handle);
int result = processIntent(intent, true).disconnectCause;
@@ -272,13 +303,16 @@
Uri handle = intent.getData();
int videoState = VideoProfile.STATE_BIDIRECTIONAL;
boolean isSpeakerphoneOn = true;
- doReturn(true).when(mPhoneNumberUtilsAdapterSpy).isPotentialLocalEmergencyNumber(
- any(Context.class), eq(handle.getSchemeSpecificPart()));
+ doReturn(true).when(mComponentContextFixture.getTelephonyManager())
+ .isPotentialEmergencyNumber(eq(handle.getSchemeSpecificPart()));
intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, isSpeakerphoneOn);
intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
- int result = processIntent(intent, true).disconnectCause;
- assertEquals(DisconnectCause.NOT_DISCONNECTED, result);
+ NewOutgoingCallIntentBroadcaster.CallDisposition callDisposition = processIntent(
+ intent, true);
+ assertEquals(false, callDisposition.requestRedirection);
+ assertEquals(DisconnectCause.NOT_DISCONNECTED, callDisposition.disconnectCause);
+
verify(mCallsManager).placeOutgoingCall(eq(mCall), eq(handle), isNull(GatewayInfo.class),
eq(isSpeakerphoneOn), eq(videoState));
@@ -391,12 +425,24 @@
String newEmergencyNumber = "1234567890";
result.receiver.setResultData(newEmergencyNumber);
- doReturn(true).when(mPhoneNumberUtilsAdapterSpy).isPotentialLocalEmergencyNumber(
- any(Context.class), eq(newEmergencyNumber));
+ doReturn(true).when(mComponentContextFixture.getTelephonyManager())
+ .isPotentialEmergencyNumber(eq(newEmergencyNumber));
result.receiver.onReceive(mContext, result.intent);
verify(mCall).disconnect(eq(0L));
}
+ /**
+ * Ensure if {@link TelephonyManager#isPotentialEmergencyNumber(String)} throws an exception of
+ * any sort that we don't crash Telecom.
+ */
+ @SmallTest
+ @Test
+ public void testThrowOnIsPotentialEmergencyNumber() {
+ doThrow(new IllegalStateException()).when(mComponentContextFixture.getTelephonyManager())
+ .isPotentialEmergencyNumber(anyString());
+ testUnmodifiedRegularCall();
+ }
+
private ReceiverIntentPair regularCallTestHelper(Intent intent,
Bundle expectedAdditionalExtras) {
Uri handle = intent.getData();
@@ -405,9 +451,11 @@
intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, isSpeakerphoneOn);
intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
- int result = processIntent(intent, true).disconnectCause;
+ NewOutgoingCallIntentBroadcaster.CallDisposition callDisposition = processIntent(
+ intent, true);
+ assertEquals(true, callDisposition.requestRedirection);
+ assertEquals(DisconnectCause.NOT_DISCONNECTED, callDisposition.disconnectCause);
- assertEquals(DisconnectCause.NOT_DISCONNECTED, result);
Bundle expectedExtras = createNumberExtras(handle.getSchemeSpecificPart());
if (expectedAdditionalExtras != null) {
expectedExtras.putAll(expectedAdditionalExtras);
@@ -426,11 +474,11 @@
private NewOutgoingCallIntentBroadcaster.CallDisposition processIntent(Intent intent,
boolean isDefaultPhoneApp) {
NewOutgoingCallIntentBroadcaster b = new NewOutgoingCallIntentBroadcaster(
- mContext, mCallsManager, mCall, intent, mPhoneNumberUtilsAdapterSpy,
- isDefaultPhoneApp);
+ mContext, mCallsManager, intent, mPhoneNumberUtilsAdapter,
+ isDefaultPhoneApp, mDefaultDialerCache);
NewOutgoingCallIntentBroadcaster.CallDisposition cd = b.evaluateCall();
if (cd.disconnectCause == DisconnectCause.NOT_DISCONNECTED) {
- b.processCall(cd);
+ b.processCall(mCall, cd);
}
return cd;
}
diff --git a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
index 3f4a4cd..6c941fe 100644
--- a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
+++ b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
@@ -2,22 +2,19 @@
import static com.android.server.telecom.TelecomSystem.*;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
-import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.telecom.Connection;
-import android.telecom.GatewayInfo;
import android.telecom.ParcelableCall;
import android.telecom.PhoneAccountHandle;
import android.test.suitebuilder.annotation.SmallTest;
@@ -26,12 +23,13 @@
import com.android.server.telecom.CallerInfoLookupHelper;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ClockProxy;
-import com.android.server.telecom.ConnectionServiceRepository;
import com.android.server.telecom.ParcelableCallUtils;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.ui.ToastFactory;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -44,6 +42,7 @@
private SyncRoot mLock = new SyncRoot() {};
@Mock private ClockProxy mClockProxy;
+ @Mock private ToastFactory mToastProxy;
@Mock private CallsManager mCallsManager;
@Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
@Mock private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
@@ -60,9 +59,10 @@
when(mCallsManager.getCallerInfoLookupHelper()).thenReturn(mCallerInfoLookupHelper);
when(mCallsManager.getPhoneAccountRegistrar()).thenReturn(mPhoneAccountRegistrar);
when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(any())).thenReturn(null);
- when(mPhoneNumberUtilsAdapter.isLocalEmergencyNumber(any(), any())).thenReturn(false);
+ when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+ .thenReturn(false);
mCall = new Call("1",
- null /* context */,
+ mContext /* context */,
mCallsManager,
mLock,
null /* ConnectionServiceRepository */,
@@ -75,7 +75,14 @@
Call.CALL_DIRECTION_INCOMING,
false /* shouldAttachToExistingConnection */,
false /* isConference */,
- mClockProxy /* ClockProxy */);
+ mClockProxy /* ClockProxy */,
+ mToastProxy);
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
}
@SmallTest
@@ -138,6 +145,48 @@
assertFalse(parceledExtras.containsKey(Connection.EXTRA_CALL_SUBJECT));
}
+ @SmallTest
+ @Test
+ public void testVerificationStatusParcelingForScreening() {
+ checkVerStatParcelingForCallScreening(Connection.VERIFICATION_STATUS_NOT_VERIFIED, false);
+ checkVerStatParcelingForCallScreening(Connection.VERIFICATION_STATUS_NOT_VERIFIED, true);
+ checkVerStatParcelingForCallScreening(Connection.VERIFICATION_STATUS_PASSED, false);
+ checkVerStatParcelingForCallScreening(Connection.VERIFICATION_STATUS_PASSED, true);
+ checkVerStatParcelingForCallScreening(Connection.VERIFICATION_STATUS_FAILED, false);
+ checkVerStatParcelingForCallScreening(Connection.VERIFICATION_STATUS_FAILED, true);
+ }
+
+ @SmallTest
+ @Test
+ public void testVerificationStatusParcelingForDialer() {
+ checkVerStatParcelingForDialer(Connection.VERIFICATION_STATUS_NOT_VERIFIED, false);
+ checkVerStatParcelingForDialer(Connection.VERIFICATION_STATUS_NOT_VERIFIED, true);
+ checkVerStatParcelingForDialer(Connection.VERIFICATION_STATUS_PASSED, false);
+ checkVerStatParcelingForDialer(Connection.VERIFICATION_STATUS_PASSED, true);
+ checkVerStatParcelingForDialer(Connection.VERIFICATION_STATUS_FAILED, false);
+ checkVerStatParcelingForDialer(Connection.VERIFICATION_STATUS_FAILED, true);
+ }
+
+ private void checkVerStatParcelingForCallScreening(int connectionVerificationStatus,
+ boolean isForSystemDialer) {
+ mCall.setCallerNumberVerificationStatus(connectionVerificationStatus);
+ ParcelableCall call = ParcelableCallUtils.toParcelableCallForScreening(mCall,
+ isForSystemDialer /* isPartOfSystemDialer */);
+ assertEquals(connectionVerificationStatus, call.getCallerNumberVerificationStatus());
+ }
+
+ private void checkVerStatParcelingForDialer(int connectionVerificationStatus,
+ boolean isForSystemDialer) {
+ mCall.setCallerNumberVerificationStatus(connectionVerificationStatus);
+ ParcelableCall call = ParcelableCallUtils.toParcelableCall(mCall,
+ false /* includevideoProvider */,
+ null /* phoneAccountRegistrar */,
+ false /* supportsExternalCalls */,
+ false /* includeRttCall */,
+ isForSystemDialer /* isForSystemDialer */);
+ assertEquals(connectionVerificationStatus, call.getCallerNumberVerificationStatus());
+ }
+
private Bundle getSomeExtras() {
Bundle extras = new Bundle();
extras.putString(Connection.EXTRA_SIP_INVITE, "scary data");
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index a978cfd..00ef87c 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -49,6 +49,7 @@
import com.android.internal.telecom.IConnectionService;
import com.android.internal.util.FastXmlSerializer;
+import com.android.server.telecom.AppLabelProxy;
import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.PhoneAccountRegistrar.DefaultPhoneAccountHandle;
@@ -69,6 +70,7 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -83,7 +85,7 @@
private PhoneAccountRegistrar mRegistrar;
@Mock private TelecomManager mTelecomManager;
@Mock private DefaultDialerCache mDefaultDialerCache;
- @Mock private PhoneAccountRegistrar.AppLabelProxy mAppLabelProxy;
+ @Mock private AppLabelProxy mAppLabelProxy;
@Override
@Before
@@ -153,6 +155,68 @@
assertPhoneAccountEquals(input, result);
}
+ @SmallTest
+ @Test
+ public void testFilterPhoneAccountForTest() throws Exception {
+ ComponentName componentA = new ComponentName("a", "a");
+ ComponentName componentB1 = new ComponentName("b", "b1");
+ ComponentName componentB2 = new ComponentName("b", "b2");
+ ComponentName componentC = new ComponentName("c", "c");
+
+ PhoneAccount simAccountA = new PhoneAccount.Builder(
+ makeQuickAccountHandle(componentA, "1"), "1")
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .setIsEnabled(true)
+ .build();
+
+ List<PhoneAccount> accountAList = new ArrayList<>();
+ accountAList.add(simAccountA);
+
+ PhoneAccount simAccountB1 = new PhoneAccount.Builder(
+ makeQuickAccountHandle(componentB1, "2"), "2")
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .setIsEnabled(true)
+ .build();
+
+ PhoneAccount simAccountB2 = new PhoneAccount.Builder(
+ makeQuickAccountHandle(componentB2, "3"), "3")
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .setIsEnabled(true)
+ .build();
+
+ List<PhoneAccount> accountBList = new ArrayList<>();
+ accountBList.add(simAccountB1);
+ accountBList.add(simAccountB2);
+
+ PhoneAccount simAccountC = new PhoneAccount.Builder(
+ makeQuickAccountHandle(componentC, "4"), "4")
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .setIsEnabled(true)
+ .build();
+
+ List<PhoneAccount> accountCList = new ArrayList<>();
+ accountCList.add(simAccountC);
+
+ List<PhoneAccount> allAccounts = new ArrayList<>();
+ allAccounts.addAll(accountAList);
+ allAccounts.addAll(accountBList);
+ allAccounts.addAll(accountCList);
+
+ assertEquals(allAccounts, mRegistrar.filterRestrictedPhoneAccounts(allAccounts));
+
+ mRegistrar.setTestPhoneAccountPackageNameFilter(componentA.getPackageName());
+ assertEquals(accountAList, mRegistrar.filterRestrictedPhoneAccounts(allAccounts));
+
+ mRegistrar.setTestPhoneAccountPackageNameFilter(componentB1.getPackageName());
+ assertEquals(accountBList, mRegistrar.filterRestrictedPhoneAccounts(allAccounts));
+
+ mRegistrar.setTestPhoneAccountPackageNameFilter(componentC.getPackageName());
+ assertEquals(accountCList, mRegistrar.filterRestrictedPhoneAccounts(allAccounts));
+
+ mRegistrar.setTestPhoneAccountPackageNameFilter(null);
+ assertEquals(allAccounts, mRegistrar.filterRestrictedPhoneAccounts(allAccounts));
+ }
+
@MediumTest
@Test
public void testDefaultPhoneAccountHandleEmptyGroup() throws Exception {
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index 75e89bc..38f63d2 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -40,6 +40,7 @@
import com.android.server.telecom.RingtoneFactory;
import com.android.server.telecom.SystemSettingsUtil;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -147,6 +148,12 @@
mRingerUnderTest.setBlockOnRingingFuture(mRingCompletionFuture);
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
@SmallTest
@Test
public void testNoActionInTheaterMode() {
@@ -367,7 +374,6 @@
mRingerUnderTest.startCallWaiting(mockCall1);
ensureRingerIsAudible();
enableRampingRinger();
- enableRampingRingerFromDeviceConfig();
mFuture.complete(false); // not using audio coupled haptics
enableVibrationWhenRinging();
assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
@@ -435,8 +441,4 @@
private void enableRampingRinger() {
when(mockSystemSettingsUtil.applyRampingRinger(any(Context.class))).thenReturn(true);
}
-
- private void enableRampingRingerFromDeviceConfig() {
- when(mockSystemSettingsUtil.enableRampingRingerFromDeviceConfig()).thenReturn(true);
- }
}
diff --git a/tests/src/com/android/server/telecom/tests/SessionManagerTest.java b/tests/src/com/android/server/telecom/tests/SessionManagerTest.java
index 85a9bff..cf84b7c 100644
--- a/tests/src/com/android/server/telecom/tests/SessionManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/SessionManagerTest.java
@@ -290,9 +290,9 @@
assertTrue(childSession.isSessionCompleted());
assertEquals(TEST_PARENT_NAME, mFullSessionMethodName);
// Reduce flakiness by assuming that the true completion time is within a threshold of
- // +-10 ms
- assertTrue(mfullSessionCompleteTime >= TEST_DELAY_TIME - 10);
- assertTrue(mfullSessionCompleteTime <= TEST_DELAY_TIME + 10);
+ // +-50 ms
+ assertTrue(mfullSessionCompleteTime >= TEST_DELAY_TIME / 2);
+ assertTrue(mfullSessionCompleteTime <= TEST_DELAY_TIME * 1.5);
}
/**
@@ -365,8 +365,8 @@
mTestSessionManager.endSession();
assertEquals(TEST_CHILD_NAME, mFullSessionMethodName);
- assertTrue(mfullSessionCompleteTime >= TEST_DELAY_TIME - 10);
- assertTrue(mfullSessionCompleteTime <= TEST_DELAY_TIME + 10);
+ assertTrue(mfullSessionCompleteTime >= TEST_DELAY_TIME / 2);
+ assertTrue(mfullSessionCompleteTime <= TEST_DELAY_TIME * 1.5);
}
/**
diff --git a/tests/src/com/android/server/telecom/tests/SessionTest.java b/tests/src/com/android/server/telecom/tests/SessionTest.java
index 7957a28..6a14a64 100644
--- a/tests/src/com/android/server/telecom/tests/SessionTest.java
+++ b/tests/src/com/android/server/telecom/tests/SessionTest.java
@@ -18,9 +18,12 @@
import static junit.framework.Assert.fail;
+import android.telecom.Log;
import android.telecom.Logging.Session;
import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -30,34 +33,205 @@
*/
@RunWith(JUnit4.class)
-public class SessionTest {
+public class SessionTest extends TelecomTestCase {
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
/**
* Ensure creating two sessions that are parent/child of each other does not lead to a crash
- * or infinite recursion.
+ * or infinite recursion when using Session#printFullSessionTree.
+ */
+ @SmallTest
+ @Test
+ public void testRecursion_printFullSessionTree() {
+ Log.startSession("testParent");
+ // Running in the same thread, so mark as invisible subsession
+ Session childSession = Log.getSessionManager()
+ .createSubsession(true /*isStartedFromActiveSession*/);
+ Log.continueSession(childSession, "child");
+ Session parentSession = childSession.getParentSession();
+ // Create a circular dependency and ensure we do not crash
+ parentSession.setParentSession(childSession);
+ childSession.addChild(parentSession);
+
+ // Make sure calling these methods does not result in a crash
+ try {
+ parentSession.printFullSessionTree();
+ childSession.printFullSessionTree();
+ } catch (Exception e) {
+ fail("Exception: " + e.getMessage());
+ } finally {
+ // End child
+ Log.endSession();
+ // End parent
+ Log.endSession();
+ }
+ }
+
+ /**
+ * Ensure creating two sessions that are parent/child of each other does not lead to a crash
+ * or infinite recursion when using Session#getFullMethodPath.
+ */
+ @SmallTest
+ @Test
+ public void testRecursion_getFullMethodPath() {
+ Log.startSession("testParent");
+ // Running in the same thread, so mark as invisible subsession
+ Session childSession = Log.getSessionManager()
+ .createSubsession(true /*isStartedFromActiveSession*/);
+ Log.continueSession(childSession, "child");
+ Session parentSession = childSession.getParentSession();
+ // Create a circular dependency and ensure we do not crash
+ parentSession.setParentSession(childSession);
+ childSession.addChild(parentSession);
+
+ // Make sure calling these methods does not result in a crash
+ try {
+ parentSession.getFullMethodPath(false /*truncatePath*/);
+ childSession.getFullMethodPath(false /*truncatePath*/);
+ } catch (Exception e) {
+ fail("Exception: " + e.getMessage());
+ } finally {
+ // End child
+ Log.endSession();
+ // End parent
+ Log.endSession();
+ }
+ }
+
+ /**
+ * Ensure creating two sessions that are parent/child of each other does not lead to a crash
+ * or infinite recursion when using Session#getFullMethodPath.
+ */
+ @SmallTest
+ @Test
+ public void testRecursion_getFullMethodPathTruncated() {
+ Log.startSession("testParent");
+ // Running in the same thread, so mark as invisible subsession
+ Session childSession = Log.getSessionManager()
+ .createSubsession(true /*isStartedFromActiveSession*/);
+ Log.continueSession(childSession, "child");
+ Session parentSession = childSession.getParentSession();
+ // Create a circular dependency and ensure we do not crash
+ parentSession.setParentSession(childSession);
+ childSession.addChild(parentSession);
+
+ // Make sure calling these methods does not result in a crash
+ try {
+ parentSession.getFullMethodPath(true /*truncatePath*/);
+ childSession.getFullMethodPath(true /*truncatePath*/);
+ } catch (Exception e) {
+ fail("Exception: " + e.getMessage());
+ } finally {
+ // End child
+ Log.endSession();
+ // End parent
+ Log.endSession();
+ }
+ }
+
+ /**
+ * Ensure creating two sessions that are parent/child of each other does not lead to a crash
+ * or infinite recursion when using Session#toString.
+ */
+ @SmallTest
+ @Test
+ public void testRecursion_toString() {
+ Log.startSession("testParent");
+ // Running in the same thread, so mark as invisible subsession
+ Session childSession = Log.getSessionManager()
+ .createSubsession(true /*isStartedFromActiveSession*/);
+ Log.continueSession(childSession, "child");
+ Session parentSession = childSession.getParentSession();
+ // Create a circular dependency and ensure we do not crash
+ parentSession.setParentSession(childSession);
+ childSession.addChild(parentSession);
+
+ // Make sure calling these methods does not result in a crash
+ try {
+
+ parentSession.toString();
+ childSession.toString();
+ } catch (Exception e) {
+ fail("Exception: " + e.getMessage());
+ } finally {
+ // End child
+ Log.endSession();
+ // End parent
+ Log.endSession();
+ }
+ }
+
+ /**
+ * Ensure creating two sessions that are parent/child of each other does not lead to a crash
+ * or infinite recursion when using Session#getInfo.
+ */
+ @SmallTest
+ @Test
+ public void testRecursion_getInfo() {
+ Log.startSession("testParent");
+ // Running in the same thread, so mark as invisible subsession
+ Session childSession = Log.getSessionManager()
+ .createSubsession(true /*isStartedFromActiveSession*/);
+ Log.continueSession(childSession, "child");
+ Session parentSession = childSession.getParentSession();
+ // Create a circular dependency and ensure we do not crash
+ parentSession.setParentSession(childSession);
+ childSession.addChild(parentSession);
+
+ // Make sure calling these methods does not result in a crash
+ try {
+ Session.Info.getInfo(parentSession);
+ Session.Info.getInfo(childSession);
+ } catch (Exception e) {
+ fail("Exception: " + e.getMessage());
+ } finally {
+ // End child
+ Log.endSession();
+ // End parent
+ Log.endSession();
+ }
+ }
+
+ /**
+ * Ensure creating two sessions that are parent/child of each other does not lead to a crash
+ * or infinite recursion in the general case.
*/
@SmallTest
@Test
public void testRecursion() {
Session parentSession = createTestSession("parent", "p");
Session childSession = createTestSession("child", "c");
+ // Create a circular dependency
parentSession.addChild(childSession);
- parentSession.setParentSession(childSession);
childSession.addChild(parentSession);
+ parentSession.setParentSession(childSession);
childSession.setParentSession(parentSession);
// Make sure calling these methods does not result in a crash
try {
parentSession.printFullSessionTree();
childSession.printFullSessionTree();
- parentSession.getFullMethodPath(false);
- childSession.getFullMethodPath(false);
+ parentSession.getFullMethodPath(false /*truncatePath*/);
+ childSession.getFullMethodPath(false /*truncatePath*/);
+ parentSession.getFullMethodPath(true /*truncatePath*/);
+ childSession.getFullMethodPath(true /*truncatePath*/);
parentSession.toString();
childSession.toString();
Session.Info.getInfo(parentSession);
Session.Info.getInfo(childSession);
} catch (Exception e) {
- fail();
+ fail("Exception: " + e.getMessage());
}
}
diff --git a/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java b/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java
index efe8796..2de3c83 100644
--- a/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java
+++ b/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java
@@ -20,6 +20,8 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
@@ -132,8 +134,10 @@
verify(mContext).registerReceiver(any(BroadcastReceiver.class), intentFilter.capture());
assertEquals(2, intentFilter.getValue().countActions());
- assertEquals(UiModeManager.ACTION_ENTER_CAR_MODE, intentFilter.getValue().getAction(0));
- assertEquals(UiModeManager.ACTION_EXIT_CAR_MODE, intentFilter.getValue().getAction(1));
+ assertEquals(UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED,
+ intentFilter.getValue().getAction(0));
+ assertEquals(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED,
+ intentFilter.getValue().getAction(1));
}
@SmallTest
@@ -145,13 +149,13 @@
verify(mContext).registerReceiver(receiver.capture(), any(IntentFilter.class));
- when(mIntentEnter.getAction()).thenReturn(UiModeManager.ACTION_ENTER_CAR_MODE);
+ when(mIntentEnter.getAction()).thenReturn(UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED);
receiver.getValue().onReceive(mContext, mIntentEnter);
- verify(mSystemStateListener).onCarModeChanged(true);
+ verify(mSystemStateListener).onCarModeChanged(anyInt(), isNull(), eq(true));
- when(mIntentExit.getAction()).thenReturn(UiModeManager.ACTION_EXIT_CAR_MODE);
+ when(mIntentExit.getAction()).thenReturn(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED);
receiver.getValue().onReceive(mContext, mIntentExit);
- verify(mSystemStateListener).onCarModeChanged(false);
+ verify(mSystemStateListener).onCarModeChanged(anyInt(), isNull(), eq(false));
receiver.getValue().onReceive(mContext, new Intent("invalid action"));
}
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index 2130121..126c0c0 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.CALL_PHONE;
import static android.Manifest.permission.CALL_PRIVILEGED;
import static android.Manifest.permission.MODIFY_PHONE_STATE;
+import static android.Manifest.permission.READ_PHONE_NUMBERS;
import static android.Manifest.permission.READ_PHONE_STATE;
import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
@@ -32,6 +33,7 @@
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -55,6 +57,7 @@
import com.android.server.telecom.components.UserCallIntentProcessor;
import com.android.server.telecom.components.UserCallIntentProcessorFactory;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -69,6 +72,7 @@
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;
+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 org.junit.Assert.assertEquals;
@@ -167,6 +171,8 @@
private TelecomServiceImpl.SettingsSecureAdapter mSettingsSecureAdapter =
spy(new SettingsSecureAdapterFake());
@Mock private UserCallIntentProcessor mUserCallIntentProcessor;
+ private PackageManager mPackageManager;
+ @Mock private ApplicationInfo mApplicationInfo;
private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
@@ -189,8 +195,10 @@
public void setUp() throws Exception {
super.setUp();
mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
- mComponentContextFixture.putBooleanResource(
- com.android.internal.R.bool.config_voice_capable, true);
+
+ TelephonyManager mockTelephonyManager =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ when(mockTelephonyManager.isVoiceCapable()).thenReturn(true);
doReturn(mContext).when(mContext).getApplicationContext();
doNothing().when(mContext).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class),
@@ -227,6 +235,14 @@
.thenReturn(DEFAULT_DIALER_PACKAGE);
when(mDefaultDialerCache.isDefaultOrSystemDialer(eq(DEFAULT_DIALER_PACKAGE), anyInt()))
.thenReturn(true);
+
+ mPackageManager = mContext.getPackageManager();
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
}
@SmallTest
@@ -239,13 +255,50 @@
.getOutgoingPhoneAccountForScheme(eq("sip"), any(UserHandle.class)))
.thenReturn(SIP_PA_HANDLE_17);
makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
+ PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_CURRENT).build();
+ phoneAccount.setIsEnabled(true);
+ doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+ eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class));
+ doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
PhoneAccountHandle returnedHandleTel
- = mTSIBinder.getDefaultOutgoingPhoneAccount("tel", DEFAULT_DIALER_PACKAGE);
+ = mTSIBinder.getDefaultOutgoingPhoneAccount("tel", DEFAULT_DIALER_PACKAGE, null);
assertEquals(TEL_PA_HANDLE_16, returnedHandleTel);
PhoneAccountHandle returnedHandleSip
- = mTSIBinder.getDefaultOutgoingPhoneAccount("sip", DEFAULT_DIALER_PACKAGE);
+ = mTSIBinder.getDefaultOutgoingPhoneAccount("sip", DEFAULT_DIALER_PACKAGE, null);
+ assertEquals(SIP_PA_HANDLE_17, returnedHandleSip);
+ }
+
+ @SmallTest
+ @Test
+ public void testGetDefaultOutgoingPhoneAccountSucceedsIfCallerIsSimCallManager()
+ throws RemoteException {
+ when(mFakePhoneAccountRegistrar
+ .getOutgoingPhoneAccountForScheme(eq("tel"), any(UserHandle.class)))
+ .thenReturn(TEL_PA_HANDLE_16);
+ when(mFakePhoneAccountRegistrar
+ .getOutgoingPhoneAccountForScheme(eq("sip"), any(UserHandle.class)))
+ .thenReturn(SIP_PA_HANDLE_17);
+ makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
+ PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_CURRENT).build();
+ phoneAccount.setIsEnabled(true);
+ doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+ eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class));
+ doReturn(TEL_PA_HANDLE_CURRENT).when(mFakePhoneAccountRegistrar)
+ .getSimCallManagerFromHandle(
+ eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class));
+ // doNothing will make #isCallerSimCallManager return true
+ doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+ doThrow(new SecurityException()).when(mContext)
+ .enforceCallingOrSelfPermission(eq(READ_PRIVILEGED_PHONE_STATE), anyString());
+
+ PhoneAccountHandle returnedHandleTel
+ = mTSIBinder.getDefaultOutgoingPhoneAccount("tel", DEFAULT_DIALER_PACKAGE, null);
+ assertEquals(TEL_PA_HANDLE_16, returnedHandleTel);
+
+ PhoneAccountHandle returnedHandleSip
+ = mTSIBinder.getDefaultOutgoingPhoneAccount("sip", DEFAULT_DIALER_PACKAGE, null);
assertEquals(SIP_PA_HANDLE_17, returnedHandleSip);
}
@@ -260,13 +313,21 @@
.thenReturn(TEL_PA_HANDLE_16);
when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_16)).thenReturn(
makePhoneAccount(TEL_PA_HANDLE_16).build());
- when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), anyString()))
+ when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), anyString(),
+ nullable(String.class), nullable(String.class)))
.thenReturn(AppOpsManager.MODE_IGNORED);
+ PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_CURRENT).build();
+ phoneAccount.setIsEnabled(true);
+ doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+ eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class));
+ doReturn(TEL_PA_HANDLE_16).when(mFakePhoneAccountRegistrar).getSimCallManagerFromHandle(
+ eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class));
+ doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
doThrow(new SecurityException()).when(mContext)
.enforceCallingOrSelfPermission(eq(READ_PRIVILEGED_PHONE_STATE), anyString());
PhoneAccountHandle returnedHandleTel
- = mTSIBinder.getDefaultOutgoingPhoneAccount("tel", "");
+ = mTSIBinder.getDefaultOutgoingPhoneAccount("tel", "", null);
assertNull(returnedHandleTel);
}
@@ -330,11 +391,10 @@
assertEquals(fullPHList,
mTSIBinder.getCallCapablePhoneAccounts(
- true, DEFAULT_DIALER_PACKAGE).getList());
-
- assertEquals(smallPHList,
+ true, DEFAULT_DIALER_PACKAGE, null).getList());
+ assertEquals(smallPHList,
mTSIBinder.getCallCapablePhoneAccounts(
- false, DEFAULT_DIALER_PACKAGE).getList());
+ false, DEFAULT_DIALER_PACKAGE, null).getList());
}
@SmallTest
@@ -349,7 +409,7 @@
List<PhoneAccountHandle> result = null;
try {
- result = mTSIBinder.getCallCapablePhoneAccounts(true, "").getList();
+ result = mTSIBinder.getCallCapablePhoneAccounts(true, "", null).getList();
} catch (SecurityException e) {
// intended behavior
}
@@ -680,14 +740,15 @@
Uri handle = Uri.parse("tel:6505551234");
Bundle extras = createSampleExtras();
- when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString()))
+ 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);
+ mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null);
placeCallTestHelper(handle, extras, true);
}
@@ -697,14 +758,15 @@
Uri handle = Uri.parse("tel:6505551234");
Bundle extras = createSampleExtras();
- when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString()))
+ when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+ nullable(String.class), nullable(String.class)))
.thenReturn(AppOpsManager.MODE_IGNORED);
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);
+ mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null);
placeCallTestHelper(handle, extras, false);
}
@@ -714,14 +776,15 @@
Uri handle = Uri.parse("tel:6505551234");
Bundle extras = createSampleExtras();
- when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString()))
+ when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+ nullable(String.class), nullable(String.class)))
.thenReturn(AppOpsManager.MODE_ALLOWED);
doReturn(PackageManager.PERMISSION_DENIED)
.when(mContext).checkCallingPermission(CALL_PHONE);
doReturn(PackageManager.PERMISSION_DENIED)
.when(mContext).checkCallingPermission(CALL_PRIVILEGED);
- mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE);
+ mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null);
placeCallTestHelper(handle, extras, false);
}
@@ -746,7 +809,7 @@
.when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
try {
- mTSIBinder.placeCall(handle, extras, "arbitrary_package_name");
+ mTSIBinder.placeCall(handle, extras, "arbitrary_package_name", null);
} catch (SecurityException e) {
// expected
}
@@ -819,7 +882,7 @@
doReturn(true).when(mFakePhoneAccountRegistrar).isVoiceMailNumber(TEL_PA_HANDLE_CURRENT,
vmNumber);
assertTrue(mTSIBinder.isVoiceMailNumber(TEL_PA_HANDLE_CURRENT,
- vmNumber, DEFAULT_DIALER_PACKAGE));
+ vmNumber, DEFAULT_DIALER_PACKAGE, null));
}
@SmallTest
@@ -833,7 +896,7 @@
when(mFakePhoneAccountRegistrar.getPhoneAccount(TEL_PA_HANDLE_CURRENT,
Binder.getCallingUserHandle())).thenReturn(null);
assertFalse(mTSIBinder
- .isVoiceMailNumber(TEL_PA_HANDLE_CURRENT, vmNumber, DEFAULT_DIALER_PACKAGE));
+ .isVoiceMailNumber(TEL_PA_HANDLE_CURRENT, vmNumber, DEFAULT_DIALER_PACKAGE, null));
}
@SmallTest
@@ -848,9 +911,9 @@
TelephonyManager mockTelephonyManager =
(TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- when(mockTelephonyManager.getVoiceMailNumber(subId)).thenReturn(vmNumber);
+ when(mockTelephonyManager.getVoiceMailNumber()).thenReturn(vmNumber);
- assertEquals(vmNumber, mTSIBinder.getVoiceMailNumber(null, DEFAULT_DIALER_PACKAGE));
+ assertEquals(vmNumber, mTSIBinder.getVoiceMailNumber(null, DEFAULT_DIALER_PACKAGE, null));
}
@SmallTest
@@ -864,28 +927,152 @@
TelephonyManager mockTelephonyManager =
(TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- when(mockTelephonyManager.getVoiceMailNumber(subId)).thenReturn(vmNumber);
+ when(mockTelephonyManager.getVoiceMailNumber()).thenReturn(vmNumber);
when(mFakePhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(TEL_PA_HANDLE_CURRENT))
.thenReturn(subId);
assertEquals(vmNumber,
- mTSIBinder.getVoiceMailNumber(TEL_PA_HANDLE_CURRENT, DEFAULT_DIALER_PACKAGE));
+ mTSIBinder.getVoiceMailNumber(TEL_PA_HANDLE_CURRENT, DEFAULT_DIALER_PACKAGE, null));
}
@SmallTest
@Test
- public void testGetLine1Number() throws Exception {
+ public void testGetLine1NumberWithNoPermissionTargetPreR() throws Exception {
+ setupGetLine1NumberTest();
+ setTargetSdkVersion(Build.VERSION_CODES.Q);
+
+ try {
+ String line1Number = mTSIBinder.getLine1Number(TEL_PA_HANDLE_CURRENT,
+ DEFAULT_DIALER_PACKAGE, null);
+ fail("Should have thrown a SecurityException when invoking getLine1Number without "
+ + "permission, received "
+ + line1Number);
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @SmallTest
+ @Test
+ public void testGetLine1NumberWithNoPermissionTargetR() throws Exception {
+ setupGetLine1NumberTest();
+
+ try {
+ String line1Number = mTSIBinder.getLine1Number(TEL_PA_HANDLE_CURRENT,
+ DEFAULT_DIALER_PACKAGE, null);
+ fail("Should have thrown a SecurityException when invoking getLine1Number without "
+ + "permission, received "
+ + line1Number);
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @SmallTest
+ @Test
+ public void testGetLine1NumberWithReadPhoneStateTargetPreR() throws Exception {
+ String line1Number = setupGetLine1NumberTest();
+ setTargetSdkVersion(Build.VERSION_CODES.Q);
+ grantPermissionAndAppOp(READ_PHONE_STATE, AppOpsManager.OPSTR_READ_PHONE_STATE);
+
+ assertEquals(line1Number,
+ mTSIBinder.getLine1Number(TEL_PA_HANDLE_CURRENT, DEFAULT_DIALER_PACKAGE, null));
+ }
+
+ @SmallTest
+ @Test
+ public void testGetLine1NumberWithReadPhoneStateTargetR() throws Exception {
+ setupGetLine1NumberTest();
+ grantPermissionAndAppOp(READ_PHONE_STATE, AppOpsManager.OPSTR_READ_PHONE_STATE);
+
+ try {
+ String line1Number = mTSIBinder.getLine1Number(TEL_PA_HANDLE_CURRENT,
+ DEFAULT_DIALER_PACKAGE, null);
+ fail("Should have thrown a SecurityException when invoking getLine1Number on target R"
+ + " with READ_PHONE_STATE permission, received "
+ + line1Number);
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @SmallTest
+ @Test
+ public void testGetLine1NumberWithReadPhoneNumbersTargetR() throws Exception {
+ String line1Number = setupGetLine1NumberTest();
+ grantPermissionAndAppOp(READ_PHONE_NUMBERS, AppOpsManager.OPSTR_READ_PHONE_NUMBERS);
+
+ assertEquals(line1Number,
+ mTSIBinder.getLine1Number(TEL_PA_HANDLE_CURRENT, DEFAULT_DIALER_PACKAGE, null));
+ }
+
+ @SmallTest
+ @Test
+ public void testGetLine1NumberWithReadSmsTargetR() throws Exception {
+ String line1Number = setupGetLine1NumberTest();
+ grantPermissionAndAppOp(READ_SMS, AppOpsManager.OPSTR_READ_SMS);
+
+ assertEquals(line1Number,
+ mTSIBinder.getLine1Number(TEL_PA_HANDLE_CURRENT, DEFAULT_DIALER_PACKAGE, null));
+ }
+
+ @SmallTest
+ @Test
+ public void testGetLine1NumberWithWriteSmsTargetR() throws Exception {
+ String line1Number = setupGetLine1NumberTest();
+ doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager).noteOpNoThrow(
+ eq(AppOpsManager.OPSTR_WRITE_SMS), anyInt(), eq(DEFAULT_DIALER_PACKAGE), any(),
+ any());
+
+ assertEquals(line1Number,
+ mTSIBinder.getLine1Number(TEL_PA_HANDLE_CURRENT, DEFAULT_DIALER_PACKAGE, null));
+ }
+
+
+ @SmallTest
+ @Test
+ public void testGetLine1NumberAsDefaultDialer() throws Exception {
+ String line1Number = setupGetLine1NumberTest();
+ doReturn(true).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+ eq(DEFAULT_DIALER_PACKAGE), anyInt());
+
+ assertEquals(line1Number,
+ mTSIBinder.getLine1Number(TEL_PA_HANDLE_CURRENT, DEFAULT_DIALER_PACKAGE, null));
+ }
+
+ private String setupGetLine1NumberTest() throws Exception {
int subId = 58374;
String line1Number = "9482752023479";
+
+ setTargetSdkVersion(Build.VERSION_CODES.R);
+ doReturn(AppOpsManager.MODE_DEFAULT).when(mAppOpsManager).noteOpNoThrow(anyString(),
+ anyInt(), eq(DEFAULT_DIALER_PACKAGE), any(), any());
makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_CURRENT);
when(mFakePhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(TEL_PA_HANDLE_CURRENT))
.thenReturn(subId);
TelephonyManager mockTelephonyManager =
(TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- when(mockTelephonyManager.getLine1Number(subId)).thenReturn(line1Number);
+ when(mockTelephonyManager.getLine1Number()).thenReturn(line1Number);
+ doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(anyString(),
+ anyString());
+ doReturn(PackageManager.PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(
+ anyString());
+ doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+ eq(DEFAULT_DIALER_PACKAGE), anyInt());
+ return line1Number;
+ }
- assertEquals(line1Number,
- mTSIBinder.getLine1Number(TEL_PA_HANDLE_CURRENT, DEFAULT_DIALER_PACKAGE));
+ private void grantPermissionAndAppOp(String permission, String appop) {
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(
+ eq(permission));
+ doNothing().when(mContext).enforceCallingOrSelfPermission(eq(permission), anyString());
+ doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager).noteOp(eq(appop), anyInt(),
+ eq(DEFAULT_DIALER_PACKAGE), any(), any());
+ doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager).noteOpNoThrow(eq(appop), anyInt(),
+ eq(DEFAULT_DIALER_PACKAGE), any(), any());
+ }
+
+ private void setTargetSdkVersion(int targetSdkVersion) throws Exception {
+ mApplicationInfo.targetSdkVersion = targetSdkVersion;
+ doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfoAsUser(
+ eq(DEFAULT_DIALER_PACKAGE), anyInt(), any());
}
@SmallTest
@@ -895,7 +1082,17 @@
when(call.getState()).thenReturn(CallState.RINGING);
when(mFakeCallsManager.getForegroundCall()).thenReturn(call);
assertTrue(mTSIBinder.endCall(TEST_PACKAGE));
- verify(call).reject(eq(false), isNull(), eq(TEST_PACKAGE));
+ verify(mFakeCallsManager).rejectCall(eq(call), eq(false), isNull());
+ }
+
+ @SmallTest
+ @Test
+ public void testEndCallWithSimulatedRingingForegroundCall() throws Exception {
+ Call call = mock(Call.class);
+ when(call.getState()).thenReturn(CallState.SIMULATED_RINGING);
+ when(mFakeCallsManager.getForegroundCall()).thenReturn(call);
+ assertTrue(mTSIBinder.endCall(TEST_PACKAGE));
+ verify(mFakeCallsManager).rejectCall(eq(call), eq(false), isNull());
}
@SmallTest
@@ -905,7 +1102,7 @@
when(call.getState()).thenReturn(CallState.ACTIVE);
when(mFakeCallsManager.getForegroundCall()).thenReturn(call);
assertTrue(mTSIBinder.endCall(TEST_PACKAGE));
- verify(call).disconnect(eq(0L), eq(TEST_PACKAGE));
+ verify(mFakeCallsManager).disconnectCall(eq(call));
}
@SmallTest
@@ -916,7 +1113,7 @@
when(mFakeCallsManager.getFirstCallWithState(any()))
.thenReturn(call);
assertTrue(mTSIBinder.endCall(TEST_PACKAGE));
- verify(call).disconnect(eq(0L), eq(TEST_PACKAGE));
+ verify(mFakeCallsManager).disconnectCall(eq(call));
}
@SmallTest
@@ -929,41 +1126,41 @@
@Test
public void testAcceptRingingCall() throws Exception {
Call call = mock(Call.class);
- when(mFakeCallsManager.getFirstCallWithState(anyInt())).thenReturn(call);
+ when(mFakeCallsManager.getFirstCallWithState(anyInt(), anyInt())).thenReturn(call);
// Not intended to be a real video state. Here to ensure that the call will be answered
// with whatever video state it's currently in.
int fakeVideoState = 29578215;
when(call.getVideoState()).thenReturn(fakeVideoState);
mTSIBinder.acceptRingingCall("");
- verify(call).answer(eq(fakeVideoState));
+ verify(mFakeCallsManager).answerCall(eq(call), eq(fakeVideoState));
}
@SmallTest
@Test
public void testAcceptRingingCallWithValidVideoState() throws Exception {
Call call = mock(Call.class);
- when(mFakeCallsManager.getFirstCallWithState(anyInt())).thenReturn(call);
+ when(mFakeCallsManager.getFirstCallWithState(anyInt(), anyInt())).thenReturn(call);
// Not intended to be a real video state. Here to ensure that the call will be answered
// with the video state passed in to acceptRingingCallWithVideoState
int fakeVideoState = 29578215;
int realVideoState = VideoProfile.STATE_RX_ENABLED | VideoProfile.STATE_TX_ENABLED;
when(call.getVideoState()).thenReturn(fakeVideoState);
mTSIBinder.acceptRingingCallWithVideoState("", realVideoState);
- verify(call).answer(realVideoState);
+ verify(mFakeCallsManager).answerCall(eq(call), eq(realVideoState));
}
@SmallTest
@Test
public void testIsInCall() throws Exception {
when(mFakeCallsManager.hasOngoingCalls()).thenReturn(true);
- assertTrue(mTSIBinder.isInCall(DEFAULT_DIALER_PACKAGE));
+ assertTrue(mTSIBinder.isInCall(DEFAULT_DIALER_PACKAGE, null));
}
@SmallTest
@Test
public void testNotIsInCall() throws Exception {
when(mFakeCallsManager.hasOngoingCalls()).thenReturn(false);
- assertFalse(mTSIBinder.isInCall(DEFAULT_DIALER_PACKAGE));
+ assertFalse(mTSIBinder.isInCall(DEFAULT_DIALER_PACKAGE, null));
}
@SmallTest
@@ -972,7 +1169,7 @@
doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
anyString(), any());
try {
- mTSIBinder.isInCall("blah");
+ mTSIBinder.isInCall("blah", null);
fail();
} catch (SecurityException e) {
// desired result
@@ -984,14 +1181,14 @@
@Test
public void testIsInManagedCall() throws Exception {
when(mFakeCallsManager.hasOngoingManagedCalls()).thenReturn(true);
- assertTrue(mTSIBinder.isInManagedCall(DEFAULT_DIALER_PACKAGE));
+ assertTrue(mTSIBinder.isInManagedCall(DEFAULT_DIALER_PACKAGE, null));
}
@SmallTest
@Test
public void testNotIsInManagedCall() throws Exception {
when(mFakeCallsManager.hasOngoingManagedCalls()).thenReturn(false);
- assertFalse(mTSIBinder.isInManagedCall(DEFAULT_DIALER_PACKAGE));
+ assertFalse(mTSIBinder.isInManagedCall(DEFAULT_DIALER_PACKAGE, null));
}
@SmallTest
@@ -1000,7 +1197,7 @@
doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
anyString(), any());
try {
- mTSIBinder.isInManagedCall("blah");
+ mTSIBinder.isInManagedCall("blah", null);
fail();
} catch (SecurityException e) {
// desired result
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 82b17be..442c310 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -21,14 +21,12 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.isNotNull;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -44,7 +42,6 @@
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.IContentProvider;
import android.content.Intent;
import android.media.AudioManager;
import android.media.IAudioService;
@@ -52,49 +49,55 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
import android.os.UserHandle;
-import android.provider.BlockedNumberContract;
import android.telecom.Call;
import android.telecom.ConnectionRequest;
import android.telecom.DisconnectCause;
+import android.telecom.Log;
import android.telecom.ParcelableCall;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
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;
import com.android.server.telecom.BluetoothPhoneServiceImpl;
import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.CallAudioModeStateMachine;
import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.CallerInfoLookupHelper;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.CallsManagerListenerBase;
import com.android.server.telecom.ClockProxy;
import com.android.server.telecom.ConnectionServiceFocusManager;
-import com.android.server.telecom.DefaultDialerCache;
+import com.android.server.telecom.ContactsAsyncHelper;
import com.android.server.telecom.HeadsetMediaButton;
import com.android.server.telecom.HeadsetMediaButtonFactory;
import com.android.server.telecom.InCallWakeLockController;
import com.android.server.telecom.InCallWakeLockControllerFactory;
import com.android.server.telecom.MissedCallNotifier;
import com.android.server.telecom.PhoneAccountRegistrar;
-import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
import com.android.server.telecom.ProximitySensorManager;
import com.android.server.telecom.ProximitySensorManagerFactory;
import com.android.server.telecom.RoleManagerAdapter;
import com.android.server.telecom.StatusBarNotifier;
+import com.android.server.telecom.SystemStateHelper;
import com.android.server.telecom.TelecomSystem;
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.CallFilterResultCallback;
+import com.android.server.telecom.callfiltering.IncomingCallFilter;
import com.android.server.telecom.components.UserCallIntentProcessor;
import com.android.server.telecom.ui.IncomingCallNotifier;
-import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
import com.google.common.base.Predicate;
@@ -106,6 +109,7 @@
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -179,18 +183,6 @@
}
MissedCallNotifierFakeImpl mMissedCallNotifier = new MissedCallNotifierFakeImpl();
- private class EmergencyNumberUtilsAdapter extends PhoneNumberUtilsAdapterImpl {
-
- @Override
- public boolean isLocalEmergencyNumber(Context context, String number) {
- return mIsEmergencyCall;
- }
-
- @Override
- public boolean isPotentialLocalEmergencyNumber(Context context, String number) {
- return mIsEmergencyCall;
- }
- }
private class IncomingCallAddedListener extends CallsManagerListenerBase {
@@ -206,8 +198,6 @@
}
}
- PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter = new EmergencyNumberUtilsAdapter();
-
@Mock HeadsetMediaButton mHeadsetMediaButton;
@Mock ProximitySensorManager mProximitySensorManager;
@Mock InCallWakeLockController mInCallWakeLockController;
@@ -216,16 +206,18 @@
@Mock IncomingCallNotifier mIncomingCallNotifier;
@Mock ClockProxy mClockProxy;
@Mock RoleManagerAdapter mRoleManagerAdapter;
+ @Mock ToneGenerator mToneGenerator;
final ComponentName mInCallServiceComponentNameX =
new ComponentName(
"incall-service-package-X",
"incall-service-class-X");
+ private static final int SERVICE_X_UID = 1;
final ComponentName mInCallServiceComponentNameY =
new ComponentName(
"incall-service-package-Y",
"incall-service-class-Y");
-
+ private static final int SERVICE_Y_UID = 1;
InCallServiceFixture mInCallServiceFixtureX;
InCallServiceFixture mInCallServiceFixtureY;
@@ -333,9 +325,11 @@
Context mSpyContext;
- private int mNumOutgoingCallsMade;
+ ConnectionServiceFocusManager mConnectionServiceFocusManager;
- private boolean mIsEmergencyCall;
+ private HandlerThread mHandlerThread;
+
+ private int mNumOutgoingCallsMade;
class IdPair {
final String mConnectionId;
@@ -354,9 +348,15 @@
doReturn(mSpyContext).when(mSpyContext).getApplicationContext();
doNothing().when(mSpyContext).sendBroadcastAsUser(any(), any(), any());
+ mHandlerThread = new HandlerThread("TelecomHandlerThread");
+ mHandlerThread.start();
+
mNumOutgoingCallsMade = 0;
- mIsEmergencyCall = false;
+ doReturn(false).when(mComponentContextFixture.getTelephonyManager())
+ .isEmergencyNumber(any());
+ doReturn(false).when(mComponentContextFixture.getTelephonyManager())
+ .isPotentialEmergencyNumber(any());
// First set up information about the In-Call services in the mock Context, since
// Telecom will search for these as soon as it is instantiated
@@ -364,6 +364,8 @@
// Next, create the TelecomSystem, our system under test
setupTelecomSystem();
+ // Need to reset teseting tag here
+ Log.setTag(TESTING_TAG);
// Finally, register the ConnectionServices with the PhoneAccountRegistrar of the
// now-running TelecomSystem
@@ -374,10 +376,36 @@
@Override
public void tearDown() throws Exception {
- mTelecomSystem.getCallsManager().getCallAudioManager()
- .getCallAudioRouteStateMachine().quitNow();
- mTelecomSystem.getCallsManager().getCallAudioManager()
- .getCallAudioModeStateMachine().quitNow();
+ mTelecomSystem.getCallsManager().waitOnHandlers();
+ LinkedList<HandlerThread> handlerThreads = mTelecomSystem.getCallsManager()
+ .getGraphHandlerThreads();
+ for (HandlerThread handlerThread : handlerThreads) {
+ handlerThread.quitSafely();
+ }
+ handlerThreads.clear();
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ waitForHandlerAction(mHandlerThread.getThreadHandler(), TEST_TIMEOUT);
+ // Bring down the threads that are active.
+ mHandlerThread.quit();
+ try {
+ mHandlerThread.join();
+ } catch (InterruptedException e) {
+ // don't do anything
+ }
+
+ mConnectionServiceFocusManager.getHandler().removeCallbacksAndMessages(null);
+ waitForHandlerAction(mConnectionServiceFocusManager.getHandler(), TEST_TIMEOUT);
+ mConnectionServiceFocusManager.getHandler().getLooper().quit();
+
+ mConnectionServiceFixtureA.waitForHandlerToClear();
+ mConnectionServiceFixtureB.waitForHandlerToClear();
+
+ // Print out any incomplete sessions for debugging tests
+ String sessions = Log.getSessionManager().printActiveSessions();
+ if (!TextUtils.isEmpty(sessions)) {
+ Log.w(this, "Active Sessions:\n" + sessions);
+ }
+
mTelecomSystem = null;
super.tearDown();
}
@@ -430,6 +458,12 @@
mCallerInfoAsyncQueryFactoryFixture = new CallerInfoAsyncQueryFactoryFixture();
+ ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory mConnServFMFactory =
+ requester -> {
+ mConnectionServiceFocusManager = new ConnectionServiceFocusManager(requester);
+ return mConnectionServiceFocusManager;
+ };
+
mTimeoutsAdapter = mock(Timeouts.Adapter.class);
when(mTimeoutsAdapter.getCallScreeningTimeoutMillis(any(ContentResolver.class)))
.thenReturn(TEST_TIMEOUT / 5L);
@@ -439,7 +473,6 @@
when(mClockProxy.elapsedRealtime()).thenReturn(TEST_CREATE_ELAPSED_TIME);
when(mRoleManagerAdapter.getCallCompanionApps()).thenReturn(Collections.emptyList());
when(mRoleManagerAdapter.getDefaultCallScreeningApp()).thenReturn(null);
- when(mRoleManagerAdapter.getCarModeDialerApp()).thenReturn(null);
mTelecomSystem = new TelecomSystem(
mComponentContextFixture.getTestDouble(),
(context, phoneAccountRegistrar, defaultDialerCache) -> mMissedCallNotifier,
@@ -449,12 +482,12 @@
inCallWakeLockControllerFactory,
() -> mAudioService,
(context, lock, callsManager, phoneAccountRegistrar) -> mBluetoothPhoneServiceImpl,
- ConnectionServiceFocusManager::new,
+ mConnServFMFactory,
mTimeoutsAdapter,
mAsyncRingtonePlayer,
- mPhoneNumberUtilsAdapter,
+ new PhoneNumberUtilsAdapterImpl(),
mIncomingCallNotifier,
- (streamType, volume) -> mock(ToneGenerator.class),
+ (streamType, volume) -> mToneGenerator,
new CallAudioRouteStateMachine.Factory() {
@Override
public CallAudioRouteStateMachine create(
@@ -472,11 +505,37 @@
statusBarNotifier,
audioServiceFactory,
// Force enable an earpiece for the end-to-end tests
- CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+ mHandlerThread.getLooper());
+ }
+ },
+ new CallAudioModeStateMachine.Factory() {
+ @Override
+ public CallAudioModeStateMachine create(SystemStateHelper systemStateHelper,
+ AudioManager am) {
+ return new CallAudioModeStateMachine(systemStateHelper, am,
+ mHandlerThread.getLooper());
}
},
mClockProxy,
- mRoleManagerAdapter);
+ mRoleManagerAdapter,
+ new IncomingCallFilter.Factory() {
+ @Override
+ public IncomingCallFilter create(Context context,
+ CallFilterResultCallback listener, com.android.server.telecom.Call call,
+ TelecomSystem.SyncRoot lock, Timeouts.Adapter timeoutsAdapter,
+ List<IncomingCallFilter.CallFilter> filters) {
+ return new IncomingCallFilter(context, listener, call, lock,
+ timeoutsAdapter, filters, mHandlerThread.getThreadHandler());
+ }
+ },
+ new ContactsAsyncHelper.Factory() {
+ @Override
+ public ContactsAsyncHelper create(
+ ContactsAsyncHelper.ContentResolverAdapter adapter) {
+ return new ContactsAsyncHelper(adapter, mHandlerThread.getLooper());
+ }
+ });
mComponentContextFixture.setTelecomManager(new TelecomManager(
mComponentContextFixture.getTestDouble(),
@@ -522,16 +581,17 @@
mComponentContextFixture.putResource(
com.android.server.telecom.R.string.incall_default_class,
mInCallServiceComponentNameX.getClassName());
- mComponentContextFixture.putBooleanResource(
- com.android.internal.R.bool.config_voice_capable, true);
+
+ doReturn(true).when(mComponentContextFixture.getTelephonyManager())
+ .isVoiceCapable();
mInCallServiceFixtureX = new InCallServiceFixture();
mInCallServiceFixtureY = new InCallServiceFixture();
mComponentContextFixture.addInCallService(mInCallServiceComponentNameX,
- mInCallServiceFixtureX.getTestDouble());
+ mInCallServiceFixtureX.getTestDouble(), SERVICE_X_UID);
mComponentContextFixture.addInCallService(mInCallServiceComponentNameY,
- mInCallServiceFixtureY.getTestDouble());
+ mInCallServiceFixtureY.getTestDouble(), SERVICE_Y_UID);
}
/**
@@ -646,7 +706,11 @@
int startingNumConnections = connectionServiceFixture.mConnectionById.size();
int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
- mIsEmergencyCall = true;
+ doReturn(true).when(mComponentContextFixture.getTelephonyManager())
+ .isEmergencyNumber(any());
+ doReturn(true).when(mComponentContextFixture.getTelephonyManager())
+ .isPotentialEmergencyNumber(any());
+
// Call will not use the ordered broadcaster, since it is an Emergency Call
startOutgoingPhoneCallWaitForBroadcaster(number, phoneAccountHandle,
connectionServiceFixture, initiatingUser, videoState, true /*isEmergency*/);
@@ -961,6 +1025,16 @@
if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+
+ if ((mInCallServiceFixtureX.getCall(ids.mCallId).getProperties() &
+ Call.Details.PROPERTY_IS_EXTERNAL_CALL) == 0) {
+ // Test the PhoneStateBroadcaster functionality if the call is not external.
+ verify(mContext.getSystemService(TelephonyRegistryManager.class),
+ timeout(TEST_TIMEOUT).atLeastOnce())
+ .notifyCallStateChangedForAllSubscriptions(
+ eq(TelephonyManager.CALL_STATE_OFFHOOK),
+ nullable(String.class));
+ }
}
return ids;
}
@@ -1009,6 +1083,16 @@
if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+
+ if ((mInCallServiceFixtureX.getCall(ids.mCallId).getProperties() &
+ Call.Details.PROPERTY_IS_EXTERNAL_CALL) == 0) {
+ // Test the PhoneStateBroadcaster functionality if the call is not external.
+ verify(mContext.getSystemService(TelephonyRegistryManager.class),
+ timeout(TEST_TIMEOUT).atLeastOnce())
+ .notifyCallStateChangedForAllSubscriptions(
+ eq(TelephonyManager.CALL_STATE_OFFHOOK),
+ nullable(String.class));
+ }
}
return ids;
}
diff --git a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
index 637895b..b0b1ec0 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
@@ -22,6 +22,7 @@
import androidx.test.InstrumentationRegistry;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.concurrent.CountDownLatch;
@@ -36,6 +37,7 @@
public void setUp() throws Exception {
Log.setTag(TESTING_TAG);
+ Log.setIsExtendedLoggingEnabled(true);
mMockitoHelper.setUp(InstrumentationRegistry.getContext(), getClass());
mComponentContextFixture = new ComponentContextFixture();
mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
@@ -47,6 +49,7 @@
public void tearDown() throws Exception {
mComponentContextFixture = null;
mMockitoHelper.tearDown();
+ Mockito.framework().clearInlineMocks();
}
protected static void waitForHandlerAction(Handler h, long timeoutMillis) {
diff --git a/tests/src/com/android/server/telecom/tests/VideoProfileTest.java b/tests/src/com/android/server/telecom/tests/VideoProfileTest.java
index 6acadf7..5ee0414 100644
--- a/tests/src/com/android/server/telecom/tests/VideoProfileTest.java
+++ b/tests/src/com/android/server/telecom/tests/VideoProfileTest.java
@@ -23,6 +23,8 @@
import android.telecom.VideoProfile;
import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -32,6 +34,19 @@
*/
@RunWith(JUnit4.class)
public class VideoProfileTest extends TelecomTestCase {
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
@SmallTest
@Test
public void testToString() {
diff --git a/tests/src/com/android/server/telecom/tests/VideoProviderProxyTest.java b/tests/src/com/android/server/telecom/tests/VideoProviderProxyTest.java
index b09aa5b..2b6c260 100644
--- a/tests/src/com/android/server/telecom/tests/VideoProviderProxyTest.java
+++ b/tests/src/com/android/server/telecom/tests/VideoProviderProxyTest.java
@@ -35,6 +35,7 @@
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.VideoProviderProxy;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -69,6 +70,12 @@
mVideoProviderProxy.addListener(mListener);
}
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
/**
* Tests the case where we receive a request to upgrade to video, except:
* 1. Phone account says we support video.
diff --git a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
index eacecf9..597924d 100644
--- a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
+++ b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
@@ -34,8 +34,10 @@
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
+import android.telecom.Call;
import android.telecom.Connection;
import android.telecom.Connection.VideoProvider;
+import android.telecom.DisconnectCause;
import android.telecom.InCallService;
import android.telecom.InCallService.VideoCall;
import android.telecom.VideoCallImpl;
@@ -64,6 +66,8 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import com.android.server.telecom.CallsManager;
+
/**
* Performs tests of the {@link VideoProvider} and {@link VideoCall} APIs. Ensures that requests
* sent from an InCallService are routed through Telecom to a VideoProvider, and that callbacks are
@@ -116,13 +120,18 @@
mVideoCallImpl = (VideoCallImpl) mVideoCall;
mVideoCall.registerCallback(mVideoCallCallback);
+ // A little hacky, but we do not want CallsManager spawning InCallTonePlayer threads.
+ CallsManager callsManager = mTelecomSystem.getCallsManager();
+ callsManager.removeListener(callsManager.getCallAudioManager());
+
mConnectionInfo = mConnectionServiceFixtureA.mConnectionById.get(mCallIds.mConnectionId);
mVerificationLock = new CountDownLatch(1);
- waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ mTelecomSystem.getCallsManager().waitOnHandlers();
doNothing().when(mContext).enforcePermission(anyString(), anyInt(), anyInt(), anyString());
doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager).noteOp(anyInt(), anyInt(),
anyString());
+
}
@Override