DO NOT MERGE - Merge pie-platform-release (PPRL.181205.001) into master
Bug: 120502534
Change-Id: I540c4d84dbaa45e59f24edfe3fa96003b6315191
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 588e5c3..9abe7e7 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -21,6 +21,8 @@
android:sharedUserId="android.uid.system">
<protected-broadcast android:name="android.intent.action.SHOW_MISSED_CALLS_NOTIFICATION" />
+ <protected-broadcast android:name="com.android.server.telecom.MESSAGE_SENT" />
+
<!-- Prevents the activity manager from delaying any activity-start
requests by this package, including requests immediately after
@@ -32,6 +34,7 @@
<uses-permission android:name="android.permission.BROADCAST_CALLLOG_INFO" />
<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.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
@@ -69,6 +72,11 @@
android:label="Process phone account registration"
android:protectionLevel="signature|system"/>
+ <permission
+ android:name="android.permission.HANDLE_CALL_INTENT"
+ android:label="Protects handling the call intent via the TelecomManager API."
+ android:protectionLevel="signature|system"/>
+
<application android:label="@string/telecommAppLabel"
android:icon="@mipmap/ic_launcher_phone"
android:allowBackup="false"
@@ -230,7 +238,7 @@
</intent-filter>
</receiver>
- <receiver android:name=".components.PhoneAccountBroadcastReceiver"
+ <receiver android:name=".components.AppUninstallBroadcastReceiver"
android:process="system">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
@@ -289,11 +297,18 @@
</intent-filter>
</activity>
- <receiver android:name=".components.PrimaryCallReceiver"
- android:exported="true"
- android:permission="android.permission.MODIFY_PHONE_STATE"
- android:process="system">
- </receiver>
+ <activity android:name="com.android.server.telecom.components.ChangeDefaultCallScreeningApp"
+ android:label="@string/change_default_dialer_dialog_title"
+ android:excludeFromRecents="true"
+ android:theme="@*android:style/Theme.Material.Light.Dialog.Alert"
+ android:priority="1000"
+ android:process=":ui">
+ </activity>
+
+ <activity android:name=".ui.TelecomDeveloperMenu"
+ android:label="@string/developer_title"
+ android:exported="false"
+ android:process=":ui" />
<service android:name=".components.BluetoothPhoneService"
android:singleUser="true"
diff --git a/OWNERS b/OWNERS
index 2f0bcec..94409ef 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,3 +1,4 @@
breadley@google.com
hallliu@google.com
tgunn@google.com
+paulye@google.com
diff --git a/proto/telecom.proto b/proto/telecom.proto
index df69ed7..2f4fae8 100644
--- a/proto/telecom.proto
+++ b/proto/telecom.proto
@@ -148,6 +148,7 @@
BLOCK_CHECK_FINISHED_TIMING = 9;
FILTERING_COMPLETED_TIMING = 10;
FILTERING_TIMED_OUT_TIMING = 11;
+ START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING = 12;
}
// The name of the event timing.
@@ -233,6 +234,18 @@
CONNECTION_MANAGER_NOT_SUPPORTED = 10;
}
+ // The source where user initiated this call.
+ enum CallSource {
+ // Call source is not specified.
+ CALL_SOURCE_UNSPECIFIED = 0;
+
+ // Dialpad at emergency dialer.
+ CALL_SOURCE_EMERGENCY_DIALPAD = 1;
+
+ // Shortcut button at emergency dialer.
+ CALL_SOURCE_EMERGENCY_SHORTCUT = 2;
+ }
+
// Start time of the connection.
// Rounded to the nearest 5 minute interval.
optional int64 start_time_5min = 1;
@@ -286,4 +299,7 @@
// A bitmask of the properties that were set at any point during the call.
// Bits are defined by android.telecom.Connection.PROPERTY_* constants.
optional int32 connection_properties = 17;
+
+ // Call source.
+ optional CallSource call_source = 18;
}
diff --git a/res/layout/telecom_developer_menu.xml b/res/layout/telecom_developer_menu.xml
new file mode 100644
index 0000000..0df0cdd
--- /dev/null
+++ b/res/layout/telecom_developer_menu.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <Switch
+ android:id="@+id/switchEnhancedCallBlocking"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/developer_enhanced_call_blocking"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/raw/endcall.ogg b/res/raw/endcall.ogg
new file mode 100644
index 0000000..1af440b
--- /dev/null
+++ b/res/raw/endcall.ogg
Binary files differ
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 8b488c2..ec4b4dd 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Vinnige antwoord"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Boodskap gestuur na <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Kon nie boodskap aan <xliff:g id="PHONE_NUMBER">%s</xliff:g> stuur nie."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Oproeprekeninge"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Net noodoproepe word toegelaat."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Hierdie program kan nie uitgaande oproepe maak sonder die foon se toestemming nie."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Oproepblokkering is gedeaktiveer"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Noodoproep gemaak"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Oproepblokkering is gedeaktiveer sodat noodeenhede jou kan kontak."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Telecom-ontwikkelaarkieslys"</string>
</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 6331a39..3f4e5d2 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ፈጣን ምላሽ"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"ለ <xliff:g id="PHONE_NUMBER">%s</xliff:g> የተላከ መልዕክት"</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"መልዕክት ወደ <xliff:g id="PHONE_NUMBER">%s</xliff:g> መላክ አልተሳካም።"</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"የመደወያ መለያዎች"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"የድንገተኛ አደጋ ጥሪዎች ብቻ ናቸው የሚፈቀዱት።"</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ይህ መተግበሪያ ያለስልኩ ፈቃድ ወጪ ጥሪዎችን ማድረግ አይችልም።"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"ጥሪ ማገድ ተሰናክሏል"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"የአደጋ ጊዜ ጥሪ ተደርጓል"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"የአደጋ ጊዜ ምላሽ ሰጪዎች እርስዎን ለማግኘት እንዲችሉ ጥሪ ማገድ ተሰናክሏል።"</string>
+ <string name="developer_title" msgid="1816273446906554627">"የቴሌኮም ገንቢ ምናሌ"</string>
</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 4a7ea15..b0602d8 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -28,15 +28,16 @@
<string name="notification_missedCall_message" msgid="3049928912736917988">"رسالة"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"تم كتم صوت المكالمة."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"تم تفعيل مكبر صوت الهاتف."</string>
- <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"لا يمكنني التحدث الآن. ماذا هناك؟"</string>
+ <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"لا يمكنني التحدث الآن. ما الأمر؟"</string>
<string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"سأعاود الاتصال بك."</string>
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"سأتصل بك لاحقًا."</string>
- <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"لا يمكنني التحدث الآن. اتصل بي لاحقًا."</string>
+ <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"لا يمكنني التحدث الآن. اتصل لاحقًا."</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"الردود السريعة"</string>
<string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"تعديل الردود السريعة"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"رد سريع"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"تم إرسال الرسالة إلى <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"تعذَّر إرسال الرسالة إلى <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"حسابات الاتصال"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"مسموح بمكالمات الطوارئ فقط."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"يتعذر على هذا التطبيق إجراء مكالمات صادرة بدون إذن من الهاتف."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"تم إيقاف حظر المكالمات"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"تم إجراء مكالمة طوارئ"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"تم إيقاف حظر المكالمات للسماح لمسؤولي استجابة الطوارئ بالاتصال بك."</string>
+ <string name="developer_title" msgid="1816273446906554627">"قائمة مطوّر برامج الاتصالات"</string>
</resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
deleted file mode 100644
index 13cb1c3..0000000
--- a/res/values-as/strings.xml
+++ /dev/null
@@ -1,100 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="382363169988504520">"কল পৰিচালনা"</string>
- <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"ফ’ন"</string>
- <string name="unknown" msgid="6878797917991465859">"অজ্ঞাত"</string>
- <string name="notification_missedCallTitle" msgid="7554385905572364535">"মিছ্ড কল"</string>
- <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"মিছ কৰা কৰ্মস্থানৰ কল"</string>
- <string name="notification_missedCallsTitle" msgid="1361677948941502522">"মিছ্ড কল"</string>
- <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>টা মিছ্ড কল"</string>
- <string name="notification_missedCallTicker" msgid="504686252427747209">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>ৰ পৰা মিছ্ড কল"</string>
- <string name="notification_missedCall_call_back" msgid="2684890353590890187">"কলবেক কৰক"</string>
- <string name="notification_missedCall_message" msgid="3049928912736917988">"বাৰ্তা"</string>
- <string name="accessibility_call_muted" msgid="2776111226185342220">"কল মিউট কৰা হৈছে।"</string>
- <string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"স্পীকাৰফ\'ন সক্ষম কৰা হৈছে।"</string>
- <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"এতিয়া কথা পাতিব নোৱাৰোঁ। কি খবৰ?"</string>
- <string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"মই আপোনাক লগে লগে কলবেক কৰি আছোঁ।"</string>
- <string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"মই আপোনাক পিছত কল কৰিম।"</string>
- <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"এতিয়া কথা পাতিব নোৱাৰোঁ। মোক পিছত কল কৰিবনে?"</string>
- <string name="respond_via_sms_setting_title" msgid="3754000371039709383">"ক্ষীপ্ৰ উত্তৰসমূহ"</string>
- <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"ক্ষীপ্ৰ উত্তৰসমূহ সম্পাদনা কৰক"</string>
- <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
- <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"তাৎক্ষণিক উত্তৰ"</string>
- <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>লৈ বাৰ্তা পঠিওৱা হ\'ল।"</string>
- <string name="enable_account_preference_title" msgid="2021848090086481720">"কলিং একাউণ্ট"</string>
- <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"কেৱল জৰুৰীকালীন কল কৰাৰ হে অনুমতি আছে।"</string>
- <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ফ\'ন অনুমতিটোৰ অবিহনে এই এপ্লিকেশ্বনটোৱে কোনো বহিৰ্গামী কল কৰিব নোৱাৰে।"</string>
- <string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"কল কৰিবৰ কাৰণে এটা মান্য নম্বৰ দিয়ক।"</string>
- <string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"এই মুহূৰ্তত কল যোগ কৰিব নোৱাৰি।"</string>
- <string name="no_vm_number" msgid="4164780423805688336">"ভইচমেইল নম্বৰ নাই"</string>
- <string name="no_vm_number_msg" msgid="1300729501030053828">"ছিম কাৰ্ডত কোনো ভইচমেইল নম্বৰ সঞ্চিত কৰি থোৱা হোৱা নাই।"</string>
- <string name="add_vm_number_str" msgid="4676479471644687453">"নম্বৰ যোগ কৰক"</string>
- <string name="change_default_dialer_dialog_title" msgid="9101655962941740507">"<xliff:g id="NEW_APP">%s</xliff:g>ক আপোনাৰ ডিফ\'ল্ট ফ\'ন এপ্ হিচাপে চিহ্নিত কৰেনে?"</string>
- <string name="change_default_dialer_dialog_affirmative" msgid="8606546663509166276">"ডিফ\'ল্ট ছেট কৰক"</string>
- <string name="change_default_dialer_dialog_negative" msgid="9078144617060173845">"বাতিল কৰক"</string>
- <string name="change_default_dialer_warning_message" msgid="1417671460801684999">"<xliff:g id="NEW_APP">%s</xliff:g>এ কল কৰা লগতে কলৰ সকলো দিশ নিয়ন্ত্ৰণ কৰিবলৈ সক্ষম হ\'ব। কেৱল আপুনি সম্পূৰ্ণৰূপে বিশ্বাস কৰা এপক হে আপোনাৰ ডিফ\'ল্ট ফ\'ন এপ্ হিচাপে চিহ্নিত কৰা উচিত।"</string>
- <string name="blocked_numbers" msgid="2751843139572970579">"অৱৰোধ কৰা নম্বৰসমূহ"</string>
- <string name="blocked_numbers_msg" msgid="1045015186124965643">"আপুনি অৱৰোধ কৰা নম্বৰসমূহৰ পৰা আৰু কল বা বাৰ্তা লাভ নকৰে।"</string>
- <string name="block_number" msgid="1101252256321306179">"নম্বৰ যোগ কৰক"</string>
- <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>ক অৱৰোধৰ পৰা আঁতৰাইনে?"</string>
- <string name="unblock_button" msgid="3078048901972674170">"অৱৰোধৰ পৰা আঁতৰাওক"</string>
- <string name="add_blocked_dialog_body" msgid="9030243212265516828">"এই নম্বৰৰ পৰা কল আৰু পাঠ বাৰ্তা অৱৰোধ কৰক"</string>
- <string name="add_blocked_number_hint" msgid="6847675097085433553">"ফ\'ন নম্বৰ"</string>
- <string name="block_button" msgid="8822290682524373357">"অৱৰোধ কৰক"</string>
- <string name="non_primary_user" msgid="5180129233352533459">"কেৱল ডিভাইচটোৰ গৰাকীয়েহে অৱৰোধ কৰা নম্বৰসমূহ চাব আৰু পৰিচালনা কৰিব পাৰে।"</string>
- <string name="delete_icon_description" msgid="8903995728252556724">"অৱৰোধৰ পৰা আঁতৰাওক"</string>
- <string name="blocked_numbers_butter_bar_title" msgid="438170866438793182">"সাময়িকভাৱে অৱৰোধৰ সুবিধা বন্ধ কৰি থোৱা হৈছে"</string>
- <string name="blocked_numbers_butter_bar_body" msgid="2223244484319442431">"আপুনি জৰুৰীকালীন নম্বৰ এটা ডায়েল কৰাৰ পিছত বা সেই নম্বৰটোলৈ পাঠ বাৰ্তা পঠিওৱাৰ পিছত নম্বৰটো অৱৰোধৰ পৰা আঁতৰোৱা হয় যাতে জৰুৰীকালীন সেৱাসমূহে আপোনাৰ সৈতে যোগাযোগ কৰিব পাৰে।"</string>
- <string name="blocked_numbers_butter_bar_button" msgid="2197943354922010696">"এতিয়াই পুনঃসক্ষম কৰক"</string>
- <string name="blocked_numbers_number_blocked_message" msgid="7678509606805029540">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> অৱৰোধ কৰা হৈছে"</string>
- <string name="blocked_numbers_number_unblocked_message" msgid="977894647366750418">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> অৱৰোধৰ পৰা আঁতৰ কৰা হৈছে"</string>
- <string name="blocked_numbers_block_emergency_number_message" msgid="917851876780698387">"জৰুৰীকালীন নম্বৰ অৱৰোধ কৰিব পৰা নাই।"</string>
- <string name="blocked_numbers_number_already_blocked_message" msgid="4392247814500811798">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g>ক ইতিমধ্যে অৱৰোধ কৰা হৈছে।"</string>
- <string name="toast_personal_call_msg" msgid="5115361633476779723">"কলটো কৰিবলৈ ব্যক্তিগত ডায়েলাৰৰ ব্যৱহাৰ কৰা হৈছে"</string>
- <string name="notification_incoming_call" msgid="7713197997773986670">"<xliff:g id="CALL_VIA">%1$s</xliff:g> <xliff:g id="CALL_FROM">%2$s</xliff:g>ৰ পৰা অহা কল"</string>
- <string name="notification_incoming_video_call" msgid="6638486071698373893">"<xliff:g id="CALL_VIA">%1$s</xliff:g> <xliff:g id="CALL_FROM">%2$s</xliff:g>ৰ পৰা অহা ভিডিঅ\' কল"</string>
- <string name="answering_ends_other_call" msgid="8282145910153766401">"উত্তৰ দিলে <xliff:g id="CALL_VIA">%1$s</xliff:g> কলটোৰ অন্ত পৰিব"</string>
- <string name="answering_ends_other_calls" msgid="1198589551399049197">"উত্তৰ দিলে <xliff:g id="CALL_VIA">%1$s</xliff:g> কলকেইটাৰ অন্ত পৰিব"</string>
- <string name="answering_ends_other_video_call" msgid="8510410917384186360">"উত্তৰ দিলে আপোনাৰ বৰ্তমান চলি থকা <xliff:g id="CALL_VIA">%1$s</xliff:g> ভিডিঅ\' কলটোৰ অন্ত পৰিব"</string>
- <string name="answering_ends_other_managed_call" msgid="5186137550267947785">"উত্তৰ দিলে আপোনাৰ বৰ্তমান চলি থকা কলটোৰ অন্ত পৰিব"</string>
- <string name="answering_ends_other_managed_calls" msgid="6429838309560397988">"উত্তৰ দিলে আপোনাৰ বৰ্তমান চলি থকা কলসমূহৰ অন্ত পৰিব"</string>
- <string name="answering_ends_other_managed_video_call" msgid="1585423762458248435">"উত্তৰ দিলে আপোনাৰ বৰ্তমান চলি থকা ভিডিঅ\' কলটোৰ অন্ত পৰিব"</string>
- <string name="answer_incoming_call" msgid="4140530013111794587">"উত্তৰ"</string>
- <string name="decline_incoming_call" msgid="806026168661598368">"প্ৰত্যাখ্যান কৰক"</string>
- <string name="cant_call_due_to_ongoing_call" msgid="4952615196237854748">"আপোনাৰ <xliff:g id="OTHER_CALL">%1$s</xliff:g> কল চলি থকাৰ কাৰণে বেলেগ কল কৰিব নোৱাৰি।"</string>
- <string name="cant_call_due_to_ongoing_calls" msgid="1380804892363503856">"আপোনাৰ <xliff:g id="OTHER_CALL">%1$s</xliff:g> কলকেইটা চলি থকাৰ কাৰণে বেলেগ কল কৰিব নোৱাৰি।"</string>
- <string name="cant_call_due_to_ongoing_unknown_call" msgid="149091978697302211">"অইন এটা এপত কল চলি থকাৰ কাৰণে বেলেগ কল কৰিব নোৱাৰি।"</string>
- <string name="notification_channel_incoming_call" msgid="3513761697082968084">"অন্তৰ্গামী কল"</string>
- <string name="notification_channel_missed_call" msgid="8727062678632713146">"মিছ্ড কল"</string>
- <string name="notification_channel_call_blocking" msgid="2943358779746676070">"কল অৱৰোধ"</string>
- <string name="alert_outgoing_call" msgid="982908156825958001">"এই কলটো কৰিলে আপোনাৰ <xliff:g id="OTHER_APP">%1$s</xliff:g> কলটোৰ অন্ত পৰিব।"</string>
- <string name="phone_settings_call_blocking_txt" msgid="3976004073043846733">"কল অৱৰোধ"</string>
- <string name="phone_settings_number_not_in_contact_txt" msgid="3126829421867168652">"আপোনাৰ সর্ম্পকসূচীত নথকা"</string>
- <string name="phone_settings_number_not_in_contact_summary_txt" msgid="9043147855140079119">"আপোনাৰ সর্ম্পকসূচীত নথকা নম্বৰ অৱৰোধ কৰক"</string>
- <string name="phone_settings_private_num_txt" msgid="8623574188879134262">"ব্য়ক্তিগত"</string>
- <string name="phone_settings_private_num_summary_txt" msgid="7516314821207782191">"যিসকল কল কৰোঁতাই তেওঁলোকৰ নম্বৰ প্ৰকাশ নকৰে তেওঁলোকক অৱৰোধ কৰক"</string>
- <string name="phone_settings_payphone_txt" msgid="2493356957416981318">"পে\'ফ\'ন"</string>
- <string name="phone_settings_payphone_summary_txt" msgid="6126709946103814653">"পে\'ফ\'নৰ পৰা অহা কল অৱৰোধ কৰক"</string>
- <string name="phone_settings_unknown_txt" msgid="5836407031508172721">"অজ্ঞাত"</string>
- <string name="phone_settings_unknown_summary_txt" msgid="3457690230497753233">"অচিনাক্ত কল কৰোঁতাৰ পৰা অহা কল অৱৰোধ কৰক"</string>
- <string name="phone_strings_call_blocking_turned_off_notification_title_txt" msgid="628536625775266096">"কল অৱৰোধ"</string>
- <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"কল অৱৰোধ সুবিধাটো অক্ষম কৰি থোৱা আছে"</string>
- <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"জৰুৰীকালীন কল ম\'ড"</string>
- <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"আপোনাক যাতে জৰুৰীকালীন সেৱা প্ৰদানকাৰীসকলে যোগাযোগ কৰিব পাৰে তাৰ বাবে কল অৱৰোধ সুবিধাটো অক্ষম কৰি থোৱা হৈছে।"</string>
-</resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 18aad3a..da3f1b5 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -31,12 +31,13 @@
<string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"İndi danışmaq olmur. Nə olub?"</string>
<string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Özüm zəng edəcəm."</string>
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Özüm sonra zəng edəcəm."</string>
- <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"İndi danışa bilmirəm. Sonra zəng edin."</string>
+ <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Danışa bilmirəm. Sonra zəngləşərik."</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Tez cavablar"</string>
- <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Tez cavablara düzəliş edin"</string>
+ <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Hazır cavablara düzəliş edin"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Tez cavab"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mesaj <xliff:g id="PHONE_NUMBER">%s</xliff:g> nömrəsinə göndərildi."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> nömrəsinə mesaj göndərmək alınmadı."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Hesabların çağırılması"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Yalnız təcili zənglərə icazə verilir."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Bu proqram Telefon icazəsi olmadan zəng edə bilməz."</string>
@@ -54,7 +55,7 @@
<string name="block_number" msgid="1101252256321306179">"Nömrə əlavə edin"</string>
<string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> nömrəsi blokdan çıxarılsın?"</string>
<string name="unblock_button" msgid="3078048901972674170">"Blokdan çıxar"</string>
- <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Zəngləri və mətnləri buradan blok edin"</string>
+ <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Bu nömrədən olan zəngləri və mətnləri bloklayın"</string>
<string name="add_blocked_number_hint" msgid="6847675097085433553">"Telefon nömrəsi"</string>
<string name="block_button" msgid="8822290682524373357">"Blok edin"</string>
<string name="non_primary_user" msgid="5180129233352533459">"Yalnız cihaz sahibi blok edilmiş nömrələrə baxa və idarə edə bilər."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Zəngi Bloklama deaktivdir"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Təcili zəng edildi"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"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="1816273446906554627">"Telecom Tərtibatçı Menyusu"</string>
</resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index b74041b..a67a34e 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Brzi odgovor"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Poruka je poslata na broj <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Slanje poruke na <xliff:g id="PHONE_NUMBER">%s</xliff:g> nije uspelo."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Nalozi za pozivanje"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Dozvoljeni su samo hitni pozivi."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ova aplikacija ne može da poziva bez dozvole za telefoniranje."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blokiranje poziva je onemogućeno"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Upućen je hitni poziv"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Blokiranje poziva je onemogućeno da bi hitne službe mogle da vas kontaktiraju."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Meni za programere Telecom-a"</string>
</resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 0cfa989..ef3f5ba 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Хуткі адказ"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Паведамленне адпраўлена на нумар <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Не ўдалося адправіць паведамленне на <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Уліковыя запісы для выклікаў"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Дазволены толькі экстранныя выклікі."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Гэта праграма не можа рабіць выходныя выклікі без дазволу тэлефона."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Блакіраванне выклікаў адключана"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Зроблены экстранны выклік"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Блакіраванне выклікаў было адключана, каб дазволіць аварыйнай брыгадзе звязацца з вамі."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Меню распрацоўшчыка Telecom"</string>
</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 102fb6f..ae4c619 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Бърз отговор"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"До <xliff:g id="PHONE_NUMBER">%s</xliff:g> бе изпратено съобщение."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Неуспешно изпращане на съобщението до <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Профили за обаждане"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Разрешени са само спешни обаждания."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Това приложение не може да извършва изходящи обаждания без разрешението за телефон."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Блокирането на обажданията е деактивирано"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Извършено бе спешно обаждане"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Блокирането на обажданията е деактивирано, за да могат службите за спешни случаи да се свържат с вас."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Меню за програмисти на Telecom"</string>
</resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 2bd1f43..a807100 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -26,7 +26,7 @@
<string name="notification_missedCallTicker" msgid="504686252427747209">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> এর থেকে মিসড কল"</string>
<string name="notification_missedCall_call_back" msgid="2684890353590890187">"কল ব্যাক করুন"</string>
<string name="notification_missedCall_message" msgid="3049928912736917988">"মেসেজ"</string>
- <string name="accessibility_call_muted" msgid="2776111226185342220">"কল নিঃশব্দ করা আছে৷"</string>
+ <string name="accessibility_call_muted" msgid="2776111226185342220">"কল মিউট করা আছে৷"</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"স্পীকারফোন সক্ষম করা আছে৷"</string>
<string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"এখন কথা বলতে পারছি না৷ কি খবর?"</string>
<string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"আমি আপনাকে কিছুক্ষণ পরেই কল করছি৷"</string>
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"দ্রুত প্রতিক্রিয়া"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> এ বার্তা পাঠানো হয়েছে৷"</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>-এ মেসেজ পাঠানো যায়নি।"</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"কলিং অ্যাকাউন্টগুলি"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"শুধুমাত্র জরুরি কলগুলিকে অনুমোদিত।"</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"এই অ্যাপ্লিকেশানটি ফোনের অনুমতি ছাড়া আউটগোয়িং কলগুলি করতে পারবে না।"</string>
@@ -49,12 +50,12 @@
<string name="change_default_dialer_dialog_affirmative" msgid="8606546663509166276">"ডিফল্ট হিসাবে সেট করুন"</string>
<string name="change_default_dialer_dialog_negative" msgid="9078144617060173845">"বাতিল করুন"</string>
<string name="change_default_dialer_warning_message" msgid="1417671460801684999">"<xliff:g id="NEW_APP">%s</xliff:g> কল করতে এবং কলের সমগ্র বৈশিষ্ট্য নিয়ন্ত্রণ করতে সক্ষম হবে৷ শুধুমাত্র আপনি যে অ্যাপ্সকে বিশ্বাস করেন সেগুলিকেই ডিফল্ট ফোন অ্যাপ হিসাবে সেট করা উচিৎ৷"</string>
- <string name="blocked_numbers" msgid="2751843139572970579">"অবরুদ্ধ নম্বরগুলি"</string>
- <string name="blocked_numbers_msg" msgid="1045015186124965643">"অবরুদ্ধ নম্বরগুলি থেকে আপনি কল বা এসএমএস পাবেন না।"</string>
+ <string name="blocked_numbers" msgid="2751843139572970579">"ব্লক করা নম্বরগুলি"</string>
+ <string name="blocked_numbers_msg" msgid="1045015186124965643">"ব্লক করা নম্বরগুলি থেকে আপনি কল বা এসএমএস পাবেন না।"</string>
<string name="block_number" msgid="1101252256321306179">"একটি নম্বর যোগ করুন"</string>
<string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> অবরোধ মুক্ত করবেন?"</string>
<string name="unblock_button" msgid="3078048901972674170">"অবরোধ মুক্ত করুন"</string>
- <string name="add_blocked_dialog_body" msgid="9030243212265516828">"এর থেকে কল এবং এসএমএস অবরোধ করুন"</string>
+ <string name="add_blocked_dialog_body" msgid="9030243212265516828">"এর থেকে কল এবং এসএমএস ব্লক করুন"</string>
<string name="add_blocked_number_hint" msgid="6847675097085433553">"ফোন নম্বর"</string>
<string name="block_button" msgid="8822290682524373357">"অবরোধ করুন"</string>
<string name="non_primary_user" msgid="5180129233352533459">"শুধুমাত্র ডিভাইসের মালিক এই অবরুদ্ধ নম্বরগুলিকে দেখতে এবং পরিচালনা করতে পারেন৷"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"কল ব্লক করার বৈশিষ্ট্য বন্ধ করা হয়েছে"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"জরুরি অবস্থার কল করা হয়েছে"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"কল ব্লক করার বৈশিষ্ট্য বন্ধ করা হয়েছে যাতে জরুরি অবস্থার সাহায্যকারী ব্যক্তি আপনার সাথে যোগাযোগ করতে পারেন।"</string>
+ <string name="developer_title" msgid="1816273446906554627">"টেলিকম ডেভেলপার মেনু"</string>
</resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 35c903f..badc0a6 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -31,12 +31,13 @@
<string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Ne mogu sada pričati. O čemu se radi?"</string>
<string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Nazvat ću te uskoro."</string>
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Nazvat ću te kasnije."</string>
- <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Ne mogu pričati. Nazovi me kasnije."</string>
+ <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Ne mogu sada pričati. Nazovi me kasnije."</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Brzi odgovori"</string>
<string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Uredi brze odgovore"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Brzi odgovor"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Poruka poslana na <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Slanje poruke na broj <xliff:g id="PHONE_NUMBER">%s</xliff:g> nije uspjelo."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Računi za pozivanje"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Dozvoljeni su samo hitni pozivi."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ova aplikacija ne može upućivati odlazne pozive bez odobrenja za Telefon."</string>
@@ -60,7 +61,7 @@
<string name="non_primary_user" msgid="5180129233352533459">"Samo vlasnik uređaja može pregledati i upravljati blokiranim brojevima."</string>
<string name="delete_icon_description" msgid="8903995728252556724">"Deblokiraj"</string>
<string name="blocked_numbers_butter_bar_title" msgid="438170866438793182">"Blokiranje je privremeno isključeno"</string>
- <string name="blocked_numbers_butter_bar_body" msgid="2223244484319442431">"Nakon što pozovete ili pošaljete poruku na broj za hitne slučajeve, blokiranje se isključuje da bi vas hitna služba mogla kontaktirati."</string>
+ <string name="blocked_numbers_butter_bar_body" msgid="2223244484319442431">"Nakon što pozovete ili pošaljete poruku na broj za hitne slučajeve, blokiranje se isključuje da bi vas hitne službe mogle kontaktirati."</string>
<string name="blocked_numbers_butter_bar_button" msgid="2197943354922010696">"Ponovo omogući sada"</string>
<string name="blocked_numbers_number_blocked_message" msgid="7678509606805029540">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> je blokiran"</string>
<string name="blocked_numbers_number_unblocked_message" msgid="977894647366750418">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> je deblokiran"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blokiranje poziva je onemogućeno"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Upućen je hitni poziv"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"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="1816273446906554627">"Meni za programere iz telekoma"</string>
</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 328845d..31720d5 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -31,12 +31,13 @@
<string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Ara no puc parlar. Què passa?"</string>
<string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Et truco de seguida."</string>
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Et truco més tard."</string>
- <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Ara no puc parlar. Truques després?"</string>
+ <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"No puc parlar. Em truques després?"</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Respostes ràpides"</string>
<string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Edita les respostes ràpides"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Resposta ràpida"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Missatge enviat a <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"No s\'ha pogut enviar el missatge a <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Comptes de trucades"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Només es permeten les trucades d\'emergència."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Aquesta aplicació no pot fer trucades sortints sense el permís del telèfon."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"El bloqueig de trucades s\'ha desactivat"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"S\'ha fet una trucada d\'emergència"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"El bloqueig de trucades s\'ha desactivat perquè els serveis d\'emergència puguin contactar amb tu."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Menú per a desenvolupadors de telecomunicacions"</string>
</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 76c40e6..bff9807 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Rychlá odpověď"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Zpráva byla odeslána na číslo <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Zprávu se nepodařilo odeslat na číslo <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Účty pro volání"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Jsou povolena pouze tísňová volání."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Tato aplikace nemůže provádět odchozí hovory bez oprávnění k použití telefonu."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blokování hovorů bylo vypnuto"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Uskutečněno tísňové volání"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Blokování hovorů bylo vypnuto, aby vás mohli kontaktovat pracovníci tísňových služeb."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Nabídka pro vývojáře Telecomu"</string>
</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index ae68c8a..90cc1c1 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Hurtigt svar"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Beskeden er sendt til <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Beskeden kunne ikke sendes til <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Opkaldskonti"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Kun nødopkald er tilladt."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Denne app kan ikke foretage udgående opkald uden opkaldstilladelse."</string>
@@ -85,7 +86,7 @@
<string name="notification_channel_call_blocking" msgid="2943358779746676070">"Opkaldsblokering"</string>
<string name="alert_outgoing_call" msgid="982908156825958001">"Hvis du foretager dette opkald, afsluttes dit opkald i <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
<string name="phone_settings_call_blocking_txt" msgid="3976004073043846733">"Opkaldsblokering"</string>
- <string name="phone_settings_number_not_in_contact_txt" msgid="3126829421867168652">"Numre er ikke i Kontaktpersoner"</string>
+ <string name="phone_settings_number_not_in_contact_txt" msgid="3126829421867168652">"Numre er ikke i Kontakter"</string>
<string name="phone_settings_number_not_in_contact_summary_txt" msgid="9043147855140079119">"Bloker numre, som ikke er i dine kontaktpersoner"</string>
<string name="phone_settings_private_num_txt" msgid="8623574188879134262">"Privat"</string>
<string name="phone_settings_private_num_summary_txt" msgid="7516314821207782191">"Bloker opkald fra personer, der ringer fra hemmeligt nummer"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Opkaldsblokering er deaktiveret"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Foretagne nødopkald"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Opkaldsblokering er blevet deaktiveret for at give nødnumre mulighed for at kontakte dig."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Udviklermenu for Telecom"</string>
</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 13f7e04..4c34419 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Kurzantwort"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Nachricht an <xliff:g id="PHONE_NUMBER">%s</xliff:g> gesendet"</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Fehler beim Senden der Nachricht an <xliff:g id="PHONE_NUMBER">%s</xliff:g>"</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Anrufkonten"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Es sind nur Notrufe erlaubt."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Diese App darf ohne die Berechtigung \"Standard-App für Telefonie\" keine ausgehenden Anrufe tätigen."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Anrufblockierung deaktiviert"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Notruf abgesetzt"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Die Anrufblockierung wurde deaktiviert, damit Ersthelfer und Rettungskräfte dich kontaktieren können."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Telecom-Entwicklermenü"</string>
</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index cb21b12..486670a 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Γρήγορη απάντηση"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Το μήνυμα εστάλη στο <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Η αποστολή του μηνύματος στο <xliff:g id="PHONE_NUMBER">%s</xliff:g> απέτυχε."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Λογαριασμοί κλήσης"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Επιτρέπονται μόνο κλήσεις έκτακτης ανάγκης."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Αυτή η εφαρμογή δεν μπορεί να πραγματοποιήσει εξερχόμενες κλήσεις χωρίς την άδεια \"Τηλέφωνο\"."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Φραγή κλήσεων απενεργοποιημένη"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Πραγματοποιήθηκε κλήση έκτακτης ανάγκης"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Η φραγή κλήσεων έχει απενεργοποιηθεί, ώστε να επιτρέπεται σε άτομα που ανταποκρίνονται σε έκτακτες ανάγκες να επικοινωνούν μαζί σας."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Μενού προγραμματιστών τηλεπικοινωνιών"</string>
</resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index d62ac55..fbbaafc 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Quick response"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Message sent to <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Message failed to send to <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Calling accounts"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Only emergency calls are allowed."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"This application cannot make outgoing calls without Phone permission."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Call Blocking disabled"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Emergency call made"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Call Blocking has been disabled to allow emergency responders to contact you."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Telecom Developer Menu"</string>
</resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index d62ac55..7d344f1 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -82,19 +82,5 @@
<string name="cant_call_due_to_ongoing_unknown_call" msgid="149091978697302211">"Call cannot be placed due to a call in another app."</string>
<string name="notification_channel_incoming_call" msgid="3513761697082968084">"Incoming calls"</string>
<string name="notification_channel_missed_call" msgid="8727062678632713146">"Missed calls"</string>
- <string name="notification_channel_call_blocking" msgid="2943358779746676070">"Call Blocking"</string>
<string name="alert_outgoing_call" msgid="982908156825958001">"Placing this call will end your <xliff:g id="OTHER_APP">%1$s</xliff:g> call."</string>
- <string name="phone_settings_call_blocking_txt" msgid="3976004073043846733">"Call Blocking"</string>
- <string name="phone_settings_number_not_in_contact_txt" msgid="3126829421867168652">"Numbers not in Contacts"</string>
- <string name="phone_settings_number_not_in_contact_summary_txt" msgid="9043147855140079119">"Block numbers that are not listed in your Contacts"</string>
- <string name="phone_settings_private_num_txt" msgid="8623574188879134262">"Private"</string>
- <string name="phone_settings_private_num_summary_txt" msgid="7516314821207782191">"Block callers who do not disclose their number"</string>
- <string name="phone_settings_payphone_txt" msgid="2493356957416981318">"Phonebox"</string>
- <string name="phone_settings_payphone_summary_txt" msgid="6126709946103814653">"Block calls from pay phones"</string>
- <string name="phone_settings_unknown_txt" msgid="5836407031508172721">"Unknown"</string>
- <string name="phone_settings_unknown_summary_txt" msgid="3457690230497753233">"Block calls from unidentified callers"</string>
- <string name="phone_strings_call_blocking_turned_off_notification_title_txt" msgid="628536625775266096">"Call Blocking"</string>
- <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Call Blocking disabled"</string>
- <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Emergency call made"</string>
- <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Call Blocking has been disabled to allow emergency responders to contact you."</string>
</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index d62ac55..fbbaafc 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Quick response"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Message sent to <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Message failed to send to <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Calling accounts"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Only emergency calls are allowed."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"This application cannot make outgoing calls without Phone permission."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Call Blocking disabled"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Emergency call made"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Call Blocking has been disabled to allow emergency responders to contact you."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Telecom Developer Menu"</string>
</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index d62ac55..fbbaafc 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Quick response"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Message sent to <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Message failed to send to <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Calling accounts"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Only emergency calls are allowed."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"This application cannot make outgoing calls without Phone permission."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Call Blocking disabled"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Emergency call made"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Call Blocking has been disabled to allow emergency responders to contact you."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Telecom Developer Menu"</string>
</resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 712b6e0..26daa02 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -82,19 +82,5 @@
<string name="cant_call_due_to_ongoing_unknown_call" msgid="149091978697302211">"Call cannot be placed due to a call in another app."</string>
<string name="notification_channel_incoming_call" msgid="3513761697082968084">"Incoming calls"</string>
<string name="notification_channel_missed_call" msgid="8727062678632713146">"Missed calls"</string>
- <string name="notification_channel_call_blocking" msgid="2943358779746676070">"Call Blocking"</string>
<string name="alert_outgoing_call" msgid="982908156825958001">"Placing this call will end your <xliff:g id="OTHER_APP">%1$s</xliff:g> call."</string>
- <string name="phone_settings_call_blocking_txt" msgid="3976004073043846733">"Call Blocking"</string>
- <string name="phone_settings_number_not_in_contact_txt" msgid="3126829421867168652">"Numbers not in Contacts"</string>
- <string name="phone_settings_number_not_in_contact_summary_txt" msgid="9043147855140079119">"Block numbers that are not listed in your Contacts"</string>
- <string name="phone_settings_private_num_txt" msgid="8623574188879134262">"Private"</string>
- <string name="phone_settings_private_num_summary_txt" msgid="7516314821207782191">"Block callers that do not disclose their number"</string>
- <string name="phone_settings_payphone_txt" msgid="2493356957416981318">"Pay phone"</string>
- <string name="phone_settings_payphone_summary_txt" msgid="6126709946103814653">"Block calls from pay phones"</string>
- <string name="phone_settings_unknown_txt" msgid="5836407031508172721">"Unknown"</string>
- <string name="phone_settings_unknown_summary_txt" msgid="3457690230497753233">"Block calls from unidentified callers"</string>
- <string name="phone_strings_call_blocking_turned_off_notification_title_txt" msgid="628536625775266096">"Call Blocking"</string>
- <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Call Blocking disabled"</string>
- <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Emergency call made"</string>
- <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Call Blocking has been disabled to allow emergency responders to contact you."</string>
</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 35f2cd7..e072cc2 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Respuesta rápida"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mensaje enviado a <xliff:g id="PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"No se pudo enviar el mensaje al <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Cuentas telefónicas"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Solo se permiten llamadas de emergencia."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Esta aplicación no puede realizar llamadas salientes sin permiso del teléfono."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Se inhabilitó el bloqueo de llamadas"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Se realizó una llamada de emergencia"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Se inhabilitó el bloqueo de llamadas para permitir que los servicios de emergencia se comuniquen contigo."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Menú para desarrolladores de Telecom"</string>
</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 38d1c20..7629edd 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Respuesta rápida"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mensaje enviado a <xliff:g id="PHONE_NUMBER">%s</xliff:g>"</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"No se ha podido enviar el mensaje al <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Cuentas de llamadas"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Solo se permiten llamadas de emergencia."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Esta aplicación no puede hacer llamadas sin permiso del teléfono."</string>
@@ -54,7 +55,7 @@
<string name="block_number" msgid="1101252256321306179">"Añadir un número"</string>
<string name="unblock_dialog_body" msgid="1614238499771862793">"¿Desbloquear el número <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
<string name="unblock_button" msgid="3078048901972674170">"Desbloquear"</string>
- <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Bloquear llamadas y mensajes de texto de"</string>
+ <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Bloquear llamadas y mensajes de texto del"</string>
<string name="add_blocked_number_hint" msgid="6847675097085433553">"Número de teléfono"</string>
<string name="block_button" msgid="8822290682524373357">"Bloquear"</string>
<string name="non_primary_user" msgid="5180129233352533459">"Solo el propietario del dispositivo puede ver y administrar los números bloqueados."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Se ha inhabilitado el bloqueo de llamadas"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Se ha hecho una llamada de emergencia"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Se ha inhabilitado el bloqueo de llamadas para que los servicios de emergencia puedan ponerse en contacto contigo."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Menú para desarrolladores de telecomunicaciones"</string>
</resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 53443ca..2d0f430 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Kiirvastus"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Sõnum on saadetud numbrile <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Sõnumi saatmine numbrile <xliff:g id="PHONE_NUMBER">%s</xliff:g> ebaõnnestus."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Kõnekontod"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Lubatud on ainult hädaabikõned."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"See rakendus ei saa ilma telefoni kasutamise loata välja helistada."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Kõnede blokeerimine on keelatud"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Tehti hädaabikõne"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Kõnede blokeerimine on keelatud, et lubada hädaabiteenustel teiega ühendust võtta."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Teenuse Telecom arendaja menüü"</string>
</resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index ef5902a..e938424 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Erantzun bizkorra"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mezua bidali da <xliff:g id="PHONE_NUMBER">%s</xliff:g> zenbakira."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Ezin izan da bidali mezua <xliff:g id="PHONE_NUMBER">%s</xliff:g> zenbakira."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Deiak egiteko kontuak"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Larrialdi-deiak bakarrik egin daitezke."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Aplikazioak deitu ahal izan dezan, telefonoaren eginbidea erabiltzeko baimena behar du."</string>
@@ -52,7 +53,7 @@
<string name="blocked_numbers" msgid="2751843139572970579">"Blokeatutako zenbakiak"</string>
<string name="blocked_numbers_msg" msgid="1045015186124965643">"Ez duzu jasoko deirik edo testu-mezurik blokeatutako zenbakietatik."</string>
<string name="block_number" msgid="1101252256321306179">"Gehitu zenbakia"</string>
- <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> desblokeatu?"</string>
+ <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> desblokeatu nahi duzu?"</string>
<string name="unblock_button" msgid="3078048901972674170">"Desblokeatu"</string>
<string name="add_blocked_dialog_body" msgid="9030243212265516828">"Blokeatu zenbaki honetatik jasotzen diren deiak eta testu-mezuak:"</string>
<string name="add_blocked_number_hint" msgid="6847675097085433553">"Telefono-zenbakia"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Desgaitu da deiak blokeatzeko aukera"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Larrialdi-deia egin da"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Desgaitu da deiak blokeatzeko aukera, larrialdietako zerbitzuak zurekin harremanetan jarri ahal daitezen."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Telekomunikazioen garatzaileen menua"</string>
</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index b3f8d1e..94cd288 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"پاسخ سریع"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"پیام به <xliff:g id="PHONE_NUMBER">%s</xliff:g> ارسال شد."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"پیام به <xliff:g id="PHONE_NUMBER">%s</xliff:g> ارسال نشد."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"حسابهای تماس"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"فقط تماسهای اضطراری مجاز است."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"این برنامه نمیتواند بدون اجازه تلفن، تماسهای خروجی برقرار کند."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"مسدود کردن تماس غیرفعال شد"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"تماس اضطراری برقرار شد"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"مسدود کردن تماس غیرفعال شده است تا پاسخدهندگان اضطراری بتوانند با شما تماس بگیرند."</string>
+ <string name="developer_title" msgid="1816273446906554627">"منوی برنامهنویس Telecom"</string>
</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 73cb390..de6baf8 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Pikavastaukset"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Viesti lähetetty numeroon <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Viestin lähetys numeroon <xliff:g id="PHONE_NUMBER">%s</xliff:g> epäonnistui."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Puhelutilit"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Vain hätäpuhelut sallittu"</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Tämä sovellus ei voi soittaa puheluita ilman Puhelin-lupaa."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Puhelujen esto poistettu käytöstä"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Hätäpuhelu soitettu"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Puhelujen esto on poistettu käytöstä, jotta pelastusviranomaiset voivat soittaa puhelimeesi."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Televiestinnän kehittäjävalikko"</string>
</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index b90b2ff..7e739dc 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Réponse rapide"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Message envoyé à <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Échec de l\'envoi du message au <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Comptes d\'appel"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Seuls les appels d\'urgence sont autorisés."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Cette application ne peut pas faire d\'appels sans l\'autorisation de l\'application Téléphone."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blocage des appels désactivé"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Appel d\'urgence effectué"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Le blocage des appels a été désactivé pour permettre aux intervenants d\'urgence de communiquer avec vous."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Menu Telecom Developer"</string>
</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 4989f54..afa7c4a 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -28,15 +28,16 @@
<string name="notification_missedCall_message" msgid="3049928912736917988">"Message"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"Son coupé"</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Haut-parleur activé"</string>
- <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Je peux pas parler. Qu\'y a-t-il ?"</string>
+ <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Je ne peux pas répondre. Ça va ?"</string>
<string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Je te rappelle tout de suite."</string>
- <string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Je t\'appellerai plus tard."</string>
- <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Peux pas parler. On se rappelle ?"</string>
+ <string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Je t\'appelle plus tard."</string>
+ <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Peux pas répondre. On se rappelle ?"</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Réponses rapides"</string>
<string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Modifier les réponses rapides"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Réponse rapide"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Message envoyé à <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Échec de l\'envoi du message au <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Comptes téléphoniques"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Seuls les appels d\'urgence sont autorisés."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Cette application ne peut pas passer d\'appels sortants sans l\'autorisation de l\'application Téléphone."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blocage d\'appels désactivé"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Appel d\'urgence"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Le blocage d\'appels a été désactivé pour que les services d\'urgence puissent vous contacter."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Menu Telecom Developer"</string>
</resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index b30dbbc..459b637 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Resposta rápida"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mensaxe enviada ao <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Non se puido enviar a mensaxe ao <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Contas de chamadas"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Só se permiten chamadas de urxencia."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Esta aplicación non pode facer chamadas saíntes sen permiso do teléfono."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Desactivouse o bloqueo de chamadas"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Realizouse unha chamada de urxencia"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Desactivouse o bloqueo de chamadas parar permitir que os servizos de urxencias se poidan poñer en contacto contigo."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Menú para programadores de telecomunicacións"</string>
</resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 950a0ed..dca0714 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ઝડપી પ્રતિસાદ"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> પર સંદેશ મોકલ્યો."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>ને સંદેશ મોકલવામાં નિષ્ફળ રહ્યાં."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"કૉલિંગ એકાઉન્ટ્સ"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"ફક્ત કટોકટીના કૉલ્સને મંજૂરી છે."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ફોન પરવાનગી વિના આ ઍપ્લિકેશન આઉટગોઇંગ કૉલ્સ કરી શકતી નથી."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"કૉલ બ્લૉક કરવાનું બંધ કરવામાં આવ્યું છે"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"કટોકટીનો કૉલ કર્યો"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"કટોકટીમાં પ્રતિસાદ કરનારાઓ તમારો સંપર્ક કરી શકે તે માટે કૉલ બ્લૉક કરવાનું બંધ કરવામાં આવ્યું છે."</string>
+ <string name="developer_title" msgid="1816273446906554627">"ટેલિકોમ ડેવલપર મેનૂ"</string>
</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index a804a8b..5eed8fa 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"झटपट उत्तर"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> को संदेश भेजा गया."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> पर मैसेज नहीं भेजा जा सका."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"कॉलिंग खाते"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"केवल आपातकालीन कॉल की अनुमति है."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"यह ऐप्लिकेशन फ़ोन अनुमति के बिना आउटगोइंग कॉल नहीं कर सकता."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"कॉल पर रोक लगाने की सुविधा बंद है"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"आपातकालीन कॉल किया गया"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"आपातकालीन सहायता कर्मचारी आपसे संपर्क कर सकें, इसलिए कॉल पर रोक लगाने की सुविधा बंद कर दी गई है."</string>
+ <string name="developer_title" msgid="1816273446906554627">"टेलीकॉम डेवलपर मेन्यू"</string>
</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 1b46287..0aa3743 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Brzi odgovor"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Poruka poslana na broj <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Slanje poruke na <xliff:g id="PHONE_NUMBER">%s</xliff:g> nije uspjelo."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Računi za pozivanje"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Dopušteni su samo hitni pozivi."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ova aplikacija ne može uspostavljati odlazne pozive bez dopuštenja za telefon."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blokiranje poziva je onemogućeno"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Hitni je poziv upućen"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Blokiranje poziva onemogućeno je da bi vas mogli kontaktirati djelatnici hitnih službi."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Izbornik Telecom Developer"</string>
</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index effd923..e0e5c1b 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Gyors válasz"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Üzenet elküldve ide: <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Sikertelen üzenetküldés (szám: <xliff:g id="PHONE_NUMBER">%s</xliff:g>)."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Telefonos fiókok"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Csak segélyhívás engedélyezett."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Az alkalmazásból nem lehet kimenő hívást kezdeményezni a Telefon (Phone) engedély nélkül."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Hívásletiltás kikapcsolva"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Segélyhívás indítva"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"A hívásletiltás ki van kapcsolva, hogy a segélyszolgálatok kapcsolatba léphessenek Önnel."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Telekommunikációs fejlesztői menü"</string>
</resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 5066698..e6d3cd5 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -25,7 +25,7 @@
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> բաց թողնված զանգ"</string>
<string name="notification_missedCallTicker" msgid="504686252427747209">"Բաց թողնված զանգ <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>-ից"</string>
<string name="notification_missedCall_call_back" msgid="2684890353590890187">"Հետ զանգել"</string>
- <string name="notification_missedCall_message" msgid="3049928912736917988">"Ուղարկել հաղորդագրություն"</string>
+ <string name="notification_missedCall_message" msgid="3049928912736917988">"Գրել"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"Զանգը խլացված է:"</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Բարձրախոսը միացված է:"</string>
<string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Հիմա չեմ կարող խոսել: Ի՞նչ կա:"</string>
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Արագ պատասխան"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Հաղորդագրությունն ուղարկվել է <xliff:g id="PHONE_NUMBER">%s</xliff:g>-ին:"</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"SMS-ը չհաջողվեց ուղարկել <xliff:g id="PHONE_NUMBER">%s</xliff:g> համարին:"</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Հաշիվներ զանգերի համար"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Միայն արտակարգ իրավիճակների զանգերն են թույլատրվում:"</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Առանց Հեռախոսի թույլտվության այս ծրագիրը չի կարող ելքային զանգեր կատարել:"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Զանգերի արգելափակումն անջատած է"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Կատարվեց շտապ կանչ"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Զանգերի արգելափակումն անջատվել է, որպեսզի արտակարգ ծառայությունները կարողանան ձեզ զանգել:"</string>
+ <string name="developer_title" msgid="1816273446906554627">"Telecom-ի մշակողի ընտրացանկ"</string>
</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 0d9a31f..2a613da 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Respons cepat"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Pesan dikirim ke <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Pesan gagal dikirim ke <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Akun pemanggil"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Hanya panggilan darurat yang diizinkan."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Aplikasi ini tidak dapat melakukan panggilan keluar tanpa izin Telepon."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Pemblokiran Panggilan dinonaktifkan"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Panggilan darurat dibuat"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Pemblokiran Panggilan dinonaktifkan untuk mengizinkan penjawab darurat menghubungi Anda."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Menu Developer Telecom"</string>
</resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index c41f657..9d1b7c4 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Snarsvar"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Skilaboð send til <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Ekki tókst að senda skilaboðin í <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Símtalareikningar"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Aðeins neyðarsímöl eru leyfð."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Þetta forrit getur ekki hringt án heimildar í símanum."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Ekki er lokað fyrir símtöl"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Neyðarsímtal var hringt"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"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="1816273446906554627">"Forritaravalmynd fyrir fjarskipti"</string>
</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index e5e63cc..70c4ce5 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Risposta rapida"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Messaggio inviato a <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Impossibile inviare il messaggio a <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Account di chiamata"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Sono consentite soltanto le chiamate di emergenza."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Non è possibile effettuare chiamate tramite questa applicazione senza l\'autorizzazione sul telefono."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blocco delle chiamate disattivato"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Chiamata di emergenza effettuata"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Il blocco delle chiamate è stato disattivato per consentire ai servizi di emergenza di contattarti."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Menu sviluppatore telecomunicazioni"</string>
</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 1de04c6..0bca54e 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -25,18 +25,19 @@
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> שיחות שלא נענו"</string>
<string name="notification_missedCallTicker" msgid="504686252427747209">"שיחה שלא נענתה מאת <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="2684890353590890187">"התקשר חזרה"</string>
- <string name="notification_missedCall_message" msgid="3049928912736917988">"שלח הודעה"</string>
+ <string name="notification_missedCall_message" msgid="3049928912736917988">"שליחת הודעה"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"שיחה מושתקת."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"רמקול מופעל."</string>
<string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"לא נוח לי עכשיו. מה קורה?"</string>
<string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"תיכף אחזור אליך."</string>
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"אני אתקשר אליך יותר מאוחר."</string>
- <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"לא נוח לי עכשיו. תתקשר מאוחר יותר?"</string>
+ <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"לא נוח לי עכשיו. נדבר אחר כך?"</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"תגובות מהירות"</string>
<string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"תגובות מהירות"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"תגובה מהירה"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"הודעה נשלחה אל <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"לא ניתן היה לשלוח את ההודעה ל-<xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"חשבונות לביצוע שיחות"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"ניתן לבצע רק שיחות חירום."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"לא ניתן לבצע שיחות יוצאות באמצעות האפליקציה הזו ללא ההרשאה \'טלפון\'."</string>
@@ -53,10 +54,10 @@
<string name="blocked_numbers_msg" msgid="1045015186124965643">"לא יגיעו אליך שיחות או הודעות טקסט מהמספרים החסומים."</string>
<string name="block_number" msgid="1101252256321306179">"מספר חדש"</string>
<string name="unblock_dialog_body" msgid="1614238499771862793">"האם לבטל את חסימת המספר <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
- <string name="unblock_button" msgid="3078048901972674170">"בטל חסימה"</string>
- <string name="add_blocked_dialog_body" msgid="9030243212265516828">"חסום שיחות והודעות טקסט מ-"</string>
+ <string name="unblock_button" msgid="3078048901972674170">"ביטול חסימה"</string>
+ <string name="add_blocked_dialog_body" msgid="9030243212265516828">"חסימת שיחות והודעות טקסט מ-"</string>
<string name="add_blocked_number_hint" msgid="6847675097085433553">"מספר טלפון"</string>
- <string name="block_button" msgid="8822290682524373357">"חסום"</string>
+ <string name="block_button" msgid="8822290682524373357">"חסימה"</string>
<string name="non_primary_user" msgid="5180129233352533459">"רק בעל המכשיר יכול להציג ולנהל מספרים חסומים."</string>
<string name="delete_icon_description" msgid="8903995728252556724">"ביטול חסימה"</string>
<string name="blocked_numbers_butter_bar_title" msgid="438170866438793182">"החסימה הושבתה זמנית"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"חסימת השיחות הושבתה"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"בוצעה שיחת חירום"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"חסימת השיחות הושבתה כדי לאפשר לצוותי עזרה ראשונה להתקשר אליך."</string>
+ <string name="developer_title" msgid="1816273446906554627">"תפריט למפתחי מערכות תקשורת"</string>
</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 70543e7..4ab68cc 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"クイック返信"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>にメッセージを送信しました。"</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> 宛にメッセージを送信できませんでした。"</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"通話アカウント"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"許可されているのは緊急通報のみです。"</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"このアプリは、電話権限がないため発信できません。"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"着信のブロックを無効にしました"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"緊急通報"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"着信のブロックを無効して、救急隊員などがあなたに連絡できるようにしました。"</string>
+ <string name="developer_title" msgid="1816273446906554627">"Telecom デベロッパー メニュー"</string>
</resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 1a39384..d94aa2c 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"სწრაფი პასუხი"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"შეტყობინება გაიგზავნა <xliff:g id="PHONE_NUMBER">%s</xliff:g>-თან."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"შეტყობინება ვერ გაიგზავნა <xliff:g id="PHONE_NUMBER">%s</xliff:g>-ზე."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"დარეკვის ანგარიშები"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"შესაძლებელია მხოლოდ გადაუდებელი ზარების განხორციელება."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ეს აპლიკაცია ტელეფონის ნებართვის გარეშე გამავალ ზარებს ვერ განახორციელებს."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"ზარების დაბლოკვა გათიშულია"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"საგანგებო ზარი შესრულდა"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"ზარების დაბლოკვა გათიშულია, რათა საგანგებო სიტუაციებში მოპასუხეებმა თქვენთან დაკავშირება შეძლონ"</string>
+ <string name="developer_title" msgid="1816273446906554627">"Telecom-ის დეველოპერის მენიუ"</string>
</resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 2e9d572..58d0cbf 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Жылдам жауап"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Хабар <xliff:g id="PHONE_NUMBER">%s</xliff:g> нөміріне жіберілді."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> нөміріне хабар жіберілмеді."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Қоңырау шалу есептік жазбалары"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Тек төтенше қоңырауларға рұқсат етілген."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"\"Телефон\" рұқсатынсыз бұл қолданба шығыс қоңырауларды соға алмайды."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Қоңырау бөгеу функциясы өшірулі"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Төтенше жағдай қоңырауы шалынды"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Төтенше жағдай қызметтері сізге хабарласа алуы үшін, қоңырау бөгеу функциясы өшірілді."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Telecom Developer мәзірі"</string>
</resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 0ee5037..f785707 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -28,7 +28,7 @@
<string name="notification_missedCall_message" msgid="3049928912736917988">"សារ"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"ការហៅបិទសំឡេង។"</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"បានបើកអូប៉ាល័រទូរស័ព្ទ។"</string>
- <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"មិនអាចនិយាយបានឥឡូវនេះ។ មានការអីដែរ?"</string>
+ <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"មិនអាចនិយាយបានទេ ឥឡូវនេះ។ មានការអីដែរ?"</string>
<string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"ខ្ញុំនឹងហៅទៅអ្នកវិញ។"</string>
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"ខ្ញុំនឹងហៅទៅអ្នកនៅពេលក្រោយ។"</string>
<string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"មិនអាចនិយាយបានទេឥឡូវនេះ។ ហៅមកខ្ញុំពេលក្រោយ?"</string>
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ឆ្លើយតបរហ័ស"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"បានផ្ញើសារទៅ <xliff:g id="PHONE_NUMBER">%s</xliff:g> ។"</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"មិនអាចផ្ញើសារទៅ <xliff:g id="PHONE_NUMBER">%s</xliff:g> បានទេ។"</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"គណនីហៅទូរសព្ទ"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"អនុញ្ញាតតែការហៅពេលមានអាសន្នប៉ុណ្ណោះ"</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"កម្មវិធីនេះមិនអាចធ្វើការហៅចេញដោយគ្មានការអនុញ្ញាត ទូរស័ព្ទ បានទេ។"</string>
@@ -49,21 +50,21 @@
<string name="change_default_dialer_dialog_affirmative" msgid="8606546663509166276">"កំណត់លំនាំដើម"</string>
<string name="change_default_dialer_dialog_negative" msgid="9078144617060173845">"បោះបង់"</string>
<string name="change_default_dialer_warning_message" msgid="1417671460801684999">"<xliff:g id="NEW_APP">%s</xliff:g> នឹងអាចដាក់ចុះ និងត្រួតពិនិត្យទិដ្ឋភាពការហៅទាំងអស់។ មានតែកម្មវិធីដែលអ្នកទុកចិត្តប៉ុណ្ណោះអាចត្រូវបានកំណត់ជាកម្មវិធីទូរសព្ទលំនាំដើម។"</string>
- <string name="blocked_numbers" msgid="2751843139572970579">"លេខដែលបានរារាំង"</string>
- <string name="blocked_numbers_msg" msgid="1045015186124965643">"អ្នកនឹងមិនទទួលបានការហៅទូរសព្ទ ឬសារពីលេខដែលបានរារាំងឡើយ។"</string>
+ <string name="blocked_numbers" msgid="2751843139572970579">"លេខដែលបានទប់ស្កាត់"</string>
+ <string name="blocked_numbers_msg" msgid="1045015186124965643">"អ្នកនឹងមិនទទួលបានការហៅទូរសព្ទ ឬសារពីលេខដែលបានទប់ស្កាត់ឡើយ។"</string>
<string name="block_number" msgid="1101252256321306179">"បញ្ចូលលេខ"</string>
- <string name="unblock_dialog_body" msgid="1614238499771862793">"ឈប់រារាំង <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> ឬ?"</string>
- <string name="unblock_button" msgid="3078048901972674170">"ឈប់រារាំង"</string>
- <string name="add_blocked_dialog_body" msgid="9030243212265516828">"រារាំងការហៅ និងការផ្ញើសារពី"</string>
+ <string name="unblock_dialog_body" msgid="1614238499771862793">"ឈប់ទប់ស្កាត់ <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> ឬ?"</string>
+ <string name="unblock_button" msgid="3078048901972674170">"ឈប់ទប់ស្កាត់"</string>
+ <string name="add_blocked_dialog_body" msgid="9030243212265516828">"ទប់ស្កាត់ការហៅ និងការផ្ញើសារពី"</string>
<string name="add_blocked_number_hint" msgid="6847675097085433553">"លេខទូរសព្ទ"</string>
- <string name="block_button" msgid="8822290682524373357">"រារាំង"</string>
+ <string name="block_button" msgid="8822290682524373357">"ទប់ស្កាត់"</string>
<string name="non_primary_user" msgid="5180129233352533459">"មានតែម្ចាស់ឧបករណ៍តែប៉ុណ្ណោះដែលអាចមើល និងគ្រប់គ្រងបញ្ជីរារាំងបាន"</string>
<string name="delete_icon_description" msgid="8903995728252556724">"ឈប់ទប់ស្កាត់"</string>
<string name="blocked_numbers_butter_bar_title" msgid="438170866438793182">"បានបិទការទប់ស្កាត់ជាបណ្ដោះអាសន្ន"</string>
<string name="blocked_numbers_butter_bar_body" msgid="2223244484319442431">"បន្ទាប់ពីអ្នកចុចហៅ ឬផ្ញើសារលេខអាសន្ន ការទប់ស្កាត់ត្រូវបានបិទដើម្បីប្រាកដថាសេវាកម្មអាសន្នអាចទាក់ទងអ្នកបាន។"</string>
<string name="blocked_numbers_butter_bar_button" msgid="2197943354922010696">"បើកដំណើរការឡើងវិញឥឡូវនេះ"</string>
<string name="blocked_numbers_number_blocked_message" msgid="7678509606805029540">"បានទប់ស្កាត់ <xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g>"</string>
- <string name="blocked_numbers_number_unblocked_message" msgid="977894647366750418">"បានអនុញ្ញាត <xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g>"</string>
+ <string name="blocked_numbers_number_unblocked_message" msgid="977894647366750418">"បានឈប់ទប់ស្កាត់ <xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g>"</string>
<string name="blocked_numbers_block_emergency_number_message" msgid="917851876780698387">"មិនអាចទប់ស្កាត់លេខបន្ទាន់បានទេ។"</string>
<string name="blocked_numbers_number_already_blocked_message" msgid="4392247814500811798">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> ត្រូវបានទប់ស្កាត់រួចហើយ។"</string>
<string name="toast_personal_call_msg" msgid="5115361633476779723">"កំពុងប្រើកម្មវិធីហៅផ្ទាល់ខ្លួនដើម្បីធ្វើការហៅទូរស័ព្ទ"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"បានបិទការទប់ស្កាត់ការហៅ"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"បានធ្វើការហៅបន្ទាន់"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"ការទប់ស្កាត់ការហៅត្រូវបានបិទ ដើម្បីអនុញ្ញាតឲ្យអ្នកឆ្លើយតបបន្ទាន់អាចទាក់ទងអ្នកបាន។"</string>
+ <string name="developer_title" msgid="1816273446906554627">"ម៉ឺនុយអ្នកអភិវឌ្ឍន៍ទូរគមនាគមន៍"</string>
</resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 1680bd6..e974cef 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ತ್ವರಿತ ಪ್ರತಿಕ್ರಿಯೆ"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> ಗೆ ಸಂದೇಶ ಕಳುಹಿಸಲಾಗಿದೆ."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> ಫೋನ್ ಸಂಖ್ಯೆಗೆ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸುವಲ್ಲಿ ವಿಫಲವಾಗಿದೆ."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"ಕರೆ ಮಾಡುವ ಖಾತೆಗಳು"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"ತುರ್ತು ಕರೆಗಳನ್ನು ಮಾಡಲು ಮಾತ್ರ ಅವಕಾಶವಿದೆ."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ಈ ಅಪ್ಲಿಕೇಶನ್ ಫೋನ್ ಅನುಮತಿಯಿಲ್ಲದೆ ಹೊರಹೋಗುವ ಕರೆಗಳನ್ನು ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"ಕರೆ ನಿರ್ಬಂಧಿಸುವಿಕೆಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"ತುರ್ತು ಕರೆ ಮಾಡಲಾಗಿದೆ"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"ನಿಮ್ಮನ್ನು ಸಂಪರ್ಕಿಸುವುದಕ್ಕಾಗಿ ತುರ್ತಾಗಿ ಪ್ರತಿಕ್ರಿಯಿಸುವವರ ಸಂಖ್ಯೆಯನ್ನು ನಿರ್ಬಂಧಿಸುವುದನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ."</string>
+ <string name="developer_title" msgid="1816273446906554627">"ಟೆಲಿಕಾಂ ಡೆವಲಪರ್ ಮೆನು"</string>
</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 474d9d0..8f94111 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"빠른 응답"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>(으)로 메시지를 보냈습니다."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>(으)로 메시지를 보내지 못했습니다."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"통화 계정"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"긴급 전화만 허용됩니다."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"전화 권한이 없으므로 애플리케이션에서 발신 전화를 걸 수 없습니다."</string>
@@ -62,8 +63,8 @@
<string name="blocked_numbers_butter_bar_title" msgid="438170866438793182">"차단 기능이 일시적으로 중지됨"</string>
<string name="blocked_numbers_butter_bar_body" msgid="2223244484319442431">"긴급 전화번호로 전화를 걸거나 문자를 보내면 긴급 서비스를 사용할 수 있도록 차단 기능이 중지됩니다."</string>
<string name="blocked_numbers_butter_bar_button" msgid="2197943354922010696">"지금 다시 사용 설정"</string>
- <string name="blocked_numbers_number_blocked_message" msgid="7678509606805029540">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g>번은 차단되었습니다."</string>
- <string name="blocked_numbers_number_unblocked_message" msgid="977894647366750418">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g>번은 차단 해제되었습니다."</string>
+ <string name="blocked_numbers_number_blocked_message" msgid="7678509606805029540">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g>번이 차단되었습니다."</string>
+ <string name="blocked_numbers_number_unblocked_message" msgid="977894647366750418">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g>번이 차단 해제되었습니다."</string>
<string name="blocked_numbers_block_emergency_number_message" msgid="917851876780698387">"긴급 전화번호를 차단할 수 없습니다."</string>
<string name="blocked_numbers_number_already_blocked_message" msgid="4392247814500811798">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g>번은 이미 차단되었습니다."</string>
<string name="toast_personal_call_msg" msgid="5115361633476779723">"전화를 걸 때 개인 다이얼러 사용"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"통화 차단이 사용 중지됨"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"긴급 통화가 사용됨"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"응급 구조 요원이 연락할 수 있도록 통화 차단이 사용 중지되었습니다."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Telecom 개발자 메뉴"</string>
</resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 760fdb1..ed2f143 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Тез жооп"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> номуруна билдирүү жөнөтүлдү."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Билдирүү <xliff:g id="PHONE_NUMBER">%s</xliff:g> номерине жөнөтүлбөй калды."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Чалуу каттоо эсептери"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Шашылыш чалууларга гана уруксат берилген."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Бул колдонмо тийиштүү уруксатсыз чалууларды жасай албайт."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Чалууну бөгөттөө өчүрүлдү"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Шашылыш чалуу аткарылды"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Өзгөчө кырдаалдардагы кызматчылар сиз менен байланышуусу үчүн чалууну бөгөттөө функциясы өчүрүлгөн."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Telecom иштеп чыгуучусунун менюсу"</string>
</resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 4c7ec9e..6698e14 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ຕອບກັບດ່ວນ"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"ສົ່ງຂໍ້ຄວາມຫາ <xliff:g id="PHONE_NUMBER">%s</xliff:g> ແລ້ວ."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"ສົ່ງຂໍ້ຄວາມໄປຫາ <xliff:g id="PHONE_NUMBER">%s</xliff:g> ບໍ່ສຳເລັດ."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"ບັນຊີໂທ"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"ອະນຸຍາດໃຫ້ໂທສຸກເສີນເທົ່ານັ້ນ."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ແອັບພລິເຄຊັນນີ້ບໍ່ສາມາດໂທອອກໄດ້ ໂດຍບໍ່ມີການອະນຸຍາດຂອງໂທລະສັບ."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"ປິດການບລັອກສາຍແລ້ວ"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"ໂທສຸກເສີນແລ້ວ"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"ປິດການບລັອກສາຍແລ້ວເພື່ອອະນຸຍາດໃຫ້ສາຍສຸກເສີນສາມາດຕິດຕໍ່ຫາທ່ານໄດ້."</string>
+ <string name="developer_title" msgid="1816273446906554627">"ເມນູນັກພັດທະນາໂທລະຄົມ"</string>
</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 4891114..e00e318 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Greitas atsakas"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Pranešimas išsiųstas numeriu <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Nepavyko išsiųsti pranešimo numeriu <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Skambinimo paskyros"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Leidžiami tik skambučiai pagalbos numeriu."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Naudojant šią programą negalima skambinti be telefono leidimo."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Skambučių blokavimas išjungtas"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Atliktas skambutis pagalbos numeriu"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Skambučių blokavimas išjungtas, kad pagalbos numeriu atsiliepusiems žmonėms būtų leidžiama su jumis susisiekti."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Telekomunikacijų kūrėjų meniu"</string>
</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 8b8d36a..bfd6c27 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Ātrā atbilde"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Ziņojums nosūt. uz šādu tālr. nr.: <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Neizdevās nosūtīt ziņojumu uz numuru <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Zvanu konti"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Ir atļauti tikai ārkārtas zvani."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Šajā lietojumprogrammā nevar veikt izejošos zvanus bez tālruņa atļaujas."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Zvanu bloķēšana atspējota"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Ārkārtas zvans ir veikts"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Zvanu bloķēšana ir atspējota, lai ļautu ar jums sazināties avārijas dienestu darbiniekiem."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Telecom izstrādātāja izvēlne"</string>
</resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index f984771..68337ac 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Брз одговор"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Порака е испратена на <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Пораката не можеше да се испрати на <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Сметки за повици"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Дозволени се само итни повици."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Оваа апликација не може да прави појдовни повици без дозволата Телефон."</string>
@@ -52,7 +53,7 @@
<string name="blocked_numbers" msgid="2751843139572970579">"Блокирани броеви"</string>
<string name="blocked_numbers_msg" msgid="1045015186124965643">"Нема да добивате повици или SMS од блокирани броеви."</string>
<string name="block_number" msgid="1101252256321306179">"Додај број"</string>
- <string name="unblock_dialog_body" msgid="1614238499771862793">"Одблокирајте го <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+ <string name="unblock_dialog_body" msgid="1614238499771862793">"Да се одблокира <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
<string name="unblock_button" msgid="3078048901972674170">"Одблокирај"</string>
<string name="add_blocked_dialog_body" msgid="9030243212265516828">"Блокирај повици и пораки од"</string>
<string name="add_blocked_number_hint" msgid="6847675097085433553">"Телефонски број"</string>
@@ -63,7 +64,7 @@
<string name="blocked_numbers_butter_bar_body" msgid="2223244484319442431">"Откако ќе повикате или ќе испратите SMS на број за итни случаи, блокирањето се исклучува за да може да ве контактираат службите за итни случаи."</string>
<string name="blocked_numbers_butter_bar_button" msgid="2197943354922010696">"Овозможи сега повторно"</string>
<string name="blocked_numbers_number_blocked_message" msgid="7678509606805029540">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> е блокиран"</string>
- <string name="blocked_numbers_number_unblocked_message" msgid="977894647366750418">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> е деблокиран"</string>
+ <string name="blocked_numbers_number_unblocked_message" msgid="977894647366750418">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> е одблокиран"</string>
<string name="blocked_numbers_block_emergency_number_message" msgid="917851876780698387">"Бројот за итни случаи не може да се блокира."</string>
<string name="blocked_numbers_number_already_blocked_message" msgid="4392247814500811798">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> е веќе блокиран."</string>
<string name="toast_personal_call_msg" msgid="5115361633476779723">"Користење на личниот бирач за остварување повик"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Блокирањето повици е оневозможено"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Воспоставен е итен повик"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Блокирањето повици е оневозможено за да им се овозможи на лицата од службите за итни случаи да контактираат со вас."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Програмерско мени за телекомуникации"</string>
</resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 0ce1758..bdd85f8 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ദ്രുത പ്രതികരണം"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> എന്നതിലേക്ക് സന്ദേശമയച്ചു."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> എന്ന നമ്പറിലേക്ക് സന്ദേശം അയക്കുന്നതിൽ പരാജയപ്പെട്ടു."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"കോളിംഗ് അക്കൗണ്ട്"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"അടിയന്തിര കോളുകൾ മാത്രമേ അനുവദിച്ചിട്ടുള്ളൂ."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ഫോൺ അനുമതിയില്ലാതെ ഈ അപ്ലിക്കേഷന് ഔട്ട്ഗോയിംഗ് കോളുകൾ വിളിക്കാൻ കഴിയില്ല."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"കോൾ ബ്ലോക്ക് ചെയ്യൽ പ്രവർത്തനരഹിതമാക്കി"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"അടിയന്തര കോൾ ചെയ്തു"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"അടിയന്തരമായി ബന്ധപ്പെടുന്നവരെ അനുവദിക്കാനായി കോൾ ബ്ലോക്ക് ചെയ്യൽ പ്രവർത്തനരഹിതമാക്കി."</string>
+ <string name="developer_title" msgid="1816273446906554627">"ടെലികോം ഡെവലപ്പര് മെനു"</string>
</resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 696e954..eee8bda 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Шуурхай хариу"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Зурвасыг <xliff:g id="PHONE_NUMBER">%s</xliff:g> руу илгээв."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> руу зурвас илгээж чадсангүй."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Дуудлагын эрхтэй бүртгэлүүд"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Зөвхөн яаралтай тусламжийн дуудлага хийх боломжтой."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Энэ апп нь утасны зөвшөөрөлгүйгээр дуудлага хийх боломжгүй."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Дуудлага хориглохыг идэвхгүй болгосон"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Яаралтай тусламжийн дуудлага хийсэн"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Яаралтай тусламжийнханд тантай холбогдохыг зөвшөөрөхийн тулд дуудлага хориглохыг идэвхгүй болгосон."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Телеком хөгжүүлэгчийн цэс"</string>
</resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 0896a49..a1b463c 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"द्रुत प्रतिसाद"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"मेसेज <xliff:g id="PHONE_NUMBER">%s</xliff:g> वर पाठविला."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> वर मेसेज पाठवता आला नाही."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"कॉल करण्याची खाती"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"फक्त आणीबाणी कॉल करण्याची परवानगी आहे."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"हा अॅप्लिकेशन फोन परवानगी शिवाय कॉल करू शकत नाही."</string>
@@ -49,16 +50,16 @@
<string name="change_default_dialer_dialog_affirmative" msgid="8606546663509166276">"डीफॉल्ट म्हणून सेट करा"</string>
<string name="change_default_dialer_dialog_negative" msgid="9078144617060173845">"रद्द करा"</string>
<string name="change_default_dialer_warning_message" msgid="1417671460801684999">"<xliff:g id="NEW_APP">%s</xliff:g> कॉल करण्यात आणि त्याचे सर्व पैलू नियंत्रित करण्यात सक्षम असेल. ज्या अॅप्सवर आपला विश्वास आहे फक्त त्यांंनाच आपला डीफॉल्ट फोन अॅप म्हणून सेट करावे."</string>
- <string name="blocked_numbers" msgid="2751843139572970579">"अवरोधित केलेले नंबर"</string>
- <string name="blocked_numbers_msg" msgid="1045015186124965643">"आपल्याला अवरोधित नंबरवरून कॉल किंवा मजकूर प्राप्त होणार नाहीत."</string>
+ <string name="blocked_numbers" msgid="2751843139572970579">"ब्लॉक केलेले नंबर"</string>
+ <string name="blocked_numbers_msg" msgid="1045015186124965643">"तुम्हाला ब्लॉक केलेल्या नंबरवरून कॉल किंवा मजकूर येणार नाहीत."</string>
<string name="block_number" msgid="1101252256321306179">"एक नंबर जोडा"</string>
- <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> अनावरोधित करायचा?"</string>
- <string name="unblock_button" msgid="3078048901972674170">"अनावरोधित करा"</string>
- <string name="add_blocked_dialog_body" msgid="9030243212265516828">"यावरील कॉल आणि मजकूर अवरोधित करा"</string>
+ <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> अनब्लॉक करायचा?"</string>
+ <string name="unblock_button" msgid="3078048901972674170">"ब्लॉक करा"</string>
+ <string name="add_blocked_dialog_body" msgid="9030243212265516828">"यावरील कॉल आणि मजकूर ब्लॉक करा"</string>
<string name="add_blocked_number_hint" msgid="6847675097085433553">"फोन नंबर"</string>
<string name="block_button" msgid="8822290682524373357">"अवरोधित करा"</string>
<string name="non_primary_user" msgid="5180129233352533459">"फक्त डिव्हाइस मालक अवरोधित केलेले नंबर पाहू आणि व्यवस्थापित करू शकतो."</string>
- <string name="delete_icon_description" msgid="8903995728252556724">"अनावरोधित करा"</string>
+ <string name="delete_icon_description" msgid="8903995728252556724">"ब्लॉक करा"</string>
<string name="blocked_numbers_butter_bar_title" msgid="438170866438793182">"अवरोधित करणे तात्पुरते बंद आहे"</string>
<string name="blocked_numbers_butter_bar_body" msgid="2223244484319442431">"आपण एखादा आणीबाणी नंबर डायल केला किंवा त्यावर मजकूर पाठविल्यानंतर, आणीबाणी सेवा आपल्याशी संपर्क साधू शकतात हे सुनिश्चित करण्यासाठी अवरोधित करणे बंद करते."</string>
<string name="blocked_numbers_butter_bar_button" msgid="2197943354922010696">"आता पुन्हा-सक्षम करा"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"कॉल ब्लॉक करणे बंद केले"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"आणीबाणी कॉल केला"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"आणीबाणीत प्रतिसाद देणार्यांना तुमच्याशी संपर्क साधण्याची अनुमती देण्यासाठी कॉल ब्लॉक करणे बंद केले आहे."</string>
+ <string name="developer_title" msgid="1816273446906554627">"टेलिकॉम डेव्हलपर मेनू"</string>
</resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index ded60ae..1bbbd3c 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -29,7 +29,7 @@
<string name="accessibility_call_muted" msgid="2776111226185342220">"Panggilan diredam."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Telefon pembesar suara didayakan."</string>
<string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Sedang sibuk. Ada apa?"</string>
- <string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Saya akn segera hubungi awak nanti."</string>
+ <string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Saya akan hubungi awak semula."</string>
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Saya akan hubungi awak kemudian."</string>
<string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Sedang sibuk. Telefon saya nanti?"</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Respons pantas"</string>
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Respons pantas"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mesej dihantar ke <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Mesej gagal dihantar kepada <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Akaun panggilan"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Panggilan kecemasan sahaja dibenarkan."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Aplikasi ini tidak boleh membuat panggilan keluar tanpa kebenaran Telefon."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Sekatan Panggilan dilumpuhkan"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Panggilan kecemasan dibuat"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Sekatan Panggilan telah dilumpuhkan untuk membolehkan pasukan bantuan kecemasan menghubungi anda."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Menu Pembangun Telekom"</string>
</resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 529e92a..237fb71 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -29,21 +29,22 @@
<string name="accessibility_call_muted" msgid="2776111226185342220">"နားထောင်ရုံသာ (စကားပြောပိတ်ထားသည်)"</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"စပီကာဖုန်း သုံးလို့ရသည်"</string>
<string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"အခုပြောလို့မရဘူး။ အကြောင်းထူးရှိလား။"</string>
- <string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"ပြန်ခေါ်လိုက်မယ်နော်"</string>
- <string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"နောက်မှ ပြန်ခေါ်လိုက်မယ်"</string>
+ <string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"အခုပဲ ပြန်ခေါ်လိုက်မယ်။"</string>
+ <string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"နောက်မှ ပြန်ခေါ်လိုက်မယ်။"</string>
<string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"အခုပြောလို့မရဘူး။ ပြန်ခေါ်ပါလား။"</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"အမြန်တုံ့ပြန်ချက်များ"</string>
<string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"အမြန်တုံ့ပြန်ချက်များပြင်ခြင်း"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"အမြန်တုံ့ပြန်ချက်"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> ထံ စာတိုပို့လိုက်ပါပြီ"</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> ထံသို့ မက်ဆေ့ဂျ် ပို့၍ မရပါ။"</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"ခေါ်ဆိုသော အကောင့်များ"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"အရေးပေါ်ခေါ်ဆိုမှုများသာ ခွင့်ပြုပါသည်။"</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ဤအပ္ပလီကေးရှင်းသည် ဖုန်းခွင့်ပြုချက်မရှိဘဲ အထွက်ခေါ်ဆိုမှု ပြုလုပ်၍မရပါ။"</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"ဖုန်းခေါ်ရန်အတွက်၊ သင့်လျော်သည့်နံပါတ် ရိုက်ထည့်ပါ။"</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"ဗွီဒီယိုခေါ်နေစဉ် ထပ်ခေါ်မရပါ။"</string>
- <string name="no_vm_number" msgid="4164780423805688336">"အသံစာပို့စနစ် နံပါတ် ပျောက်နေပါသည်"</string>
- <string name="no_vm_number_msg" msgid="1300729501030053828">"ဆင်းမ်ကဒ်ပေါ်တွင် အသံစာပို့စနစ် နံပါတ် သိမ်းဆည်ထားခြင်း မရှိပါ"</string>
+ <string name="no_vm_number" msgid="4164780423805688336">"အသံမေးလ် နံပါတ် ပျောက်နေပါသည်"</string>
+ <string name="no_vm_number_msg" msgid="1300729501030053828">"ဆင်းမ်ကဒ်ပေါ်တွင် အသံမေးလ် နံပါတ် သိမ်းဆည်ထားခြင်း မရှိပါ"</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"နံပါတ်ထပ်ထည့်ရန်"</string>
<string name="change_default_dialer_dialog_title" msgid="9101655962941740507">"<xliff:g id="NEW_APP">%s</xliff:g> ကို သင့်ဖုန်း၏မူရင်းအက်ပ်အဖြစ် ထားမလား။"</string>
<string name="change_default_dialer_dialog_affirmative" msgid="8606546663509166276">"မူရင်း သတ်မှတ်ရန်"</string>
@@ -54,7 +55,7 @@
<string name="block_number" msgid="1101252256321306179">"နံပါတ်တစ်ခု ထည့်ပါ"</string>
<string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> ကို ပိတ်ဆို့မှုပြန်ဖွင့်မလား။"</string>
<string name="unblock_button" msgid="3078048901972674170">"ပိတ်ဆို့မှုပြန်ဖွင့်ပါ"</string>
- <string name="add_blocked_dialog_body" msgid="9030243212265516828">"ဖော်ပြပါပုဂ္ဂိုလ်ထံမှ စာနှင့် ခေါ်ဆိုမှုများကို ပိတ်ဆို့ပါ"</string>
+ <string name="add_blocked_dialog_body" msgid="9030243212265516828">"ဤနံပါတ်မှ ခေါ်ဆိုမှုနှင့် စာများကို ပိတ်ဆို့ပါ"</string>
<string name="add_blocked_number_hint" msgid="6847675097085433553">"ဖုန်းနံပါတ်"</string>
<string name="block_button" msgid="8822290682524373357">"ပိတ်ဆို့ပါ"</string>
<string name="non_primary_user" msgid="5180129233352533459">"ပိတ်ဆို့ထားသည့် နံပါတ်များကို စက်ပစ္စည်းပိုင်ရှင်သာလျှင် ကြည့်ရှု၍ စီမံခန့်ခွဲနိုင်ပါသည်။"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"\'ခေါ်ဆိုမှု ပိတ်ခြင်း\' ကို ရပ်ထားပါသည်"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"အရေးပေါ် ခေါ်ဆိုမှု ပြုလုပ်ထားပါသည်"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"အရေးပေါ်တုံ့ပြန်သူများက သင့်အား ဆက်သွယ်နိုင်စေရန် \'ခေါ်ဆိုမှု ပိတ်ခြင်း\' ကို ရပ်ထားပါသည်။"</string>
+ <string name="developer_title" msgid="1816273446906554627">"Telecom ဆော့ဖ်ဝဲအင်ဂျင်နီယာ မီနူး"</string>
</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 6e63c1e..ce7936f 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Hurtigsvar"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Melding er sendt til <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Kunne ikke sende meldingen til <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Ringekontoer"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Kun nødanrop er mulig."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Denne appen kan ikke ringe uten tillatelse fra telefonen."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Anropsblokkering er slått av"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Nødanrop utført"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Anropsblokkering er slått av for å gjøre det mulig for nødtjenester å kontakte deg."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Meny for telekommunikasjonsutviklere"</string>
</resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index fe5220f..ad10907 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"द्रुत प्रतिक्रिया"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> लाई सन्देश पठाइयो।"</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> मा सन्देश पठाउन सकिएन।"</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"कलिङ खाताहरू"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"आपतकालीन कलहरूलाई मात्र अनुमति दिइएको छ।"</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"यो अनुप्रयोगले फोनको अनुमति बिना बहिर्गमन कलहरू गर्न सक्दैन।"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"कलमाथि रोक लगाउने सुविधालाई असक्षम पारियो"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"आपतकालीन कल गरियो"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"आपतकालीन अवस्थामा उद्दार गर्ने मान्छेहरूलाई तपाईंलाई सम्पर्क गर्न दिन कलमाथि रोक लगाउने सुविधा असक्षम पारिएको छ।"</string>
+ <string name="developer_title" msgid="1816273446906554627">"टेलिकमको विकासकर्ताको मेनु"</string>
</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index c23cbb7..df920e6 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -19,14 +19,14 @@
<string name="telecommAppLabel" product="default" msgid="382363169988504520">"Oproepbeheer"</string>
<string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefoon"</string>
<string name="unknown" msgid="6878797917991465859">"Onbekend"</string>
- <string name="notification_missedCallTitle" msgid="7554385905572364535">"Gemiste oproep"</string>
- <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"Gemiste zakelijke oproep"</string>
- <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Gemiste oproepen"</string>
- <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> gemiste oproepen"</string>
- <string name="notification_missedCallTicker" msgid="504686252427747209">"Gemiste oproep van <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
+ <string name="notification_missedCallTitle" msgid="7554385905572364535">"Gemist gesprek"</string>
+ <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"Gemist zakelijk gesprek"</string>
+ <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Gemiste gesprekken"</string>
+ <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> gemiste gesprekken"</string>
+ <string name="notification_missedCallTicker" msgid="504686252427747209">"Gemist gesprek van <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="2684890353590890187">"Terugbellen"</string>
<string name="notification_missedCall_message" msgid="3049928912736917988">"Bericht"</string>
- <string name="accessibility_call_muted" msgid="2776111226185342220">"Oproep gedempt."</string>
+ <string name="accessibility_call_muted" msgid="2776111226185342220">"Gesprek gedempt."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Telefoonluidspreker is ingeschakeld."</string>
<string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Kan nu niet opnemen. Alles goed?"</string>
<string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Ik bel je zo terug."</string>
@@ -37,24 +37,25 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Snelle reactie"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Bericht verzonden naar <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="enable_account_preference_title" msgid="2021848090086481720">"Oproepaccounts"</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Kan bericht niet verzenden naar <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Gespreksaccounts"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Alleen noodoproepen zijn toegestaan."</string>
- <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Deze app kan geen uitgaande oproepen starten zonder telefoonrechten."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Deze app kan geen uitgaande gesprekken starten zonder telefoonrechten."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Als je wilt bellen, moet je een geldig nummer invoeren."</string>
- <string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Oproep kan momenteel niet worden toegevoegd."</string>
+ <string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Gesprek kan momenteel niet worden toegevoegd."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Voicemailnummer ontbreekt"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Er is geen voicemailnummer op de simkaart opgeslagen."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Nummer toevoegen"</string>
<string name="change_default_dialer_dialog_title" msgid="9101655962941740507">"Wil je <xliff:g id="NEW_APP">%s</xliff:g> instellen als je standaard telefoon-app?"</string>
<string name="change_default_dialer_dialog_affirmative" msgid="8606546663509166276">"Standaard instellen"</string>
<string name="change_default_dialer_dialog_negative" msgid="9078144617060173845">"Annuleren"</string>
- <string name="change_default_dialer_warning_message" msgid="1417671460801684999">"<xliff:g id="NEW_APP">%s</xliff:g> kan oproepen plaatsen en alle aspecten hiervan beheren. Stel alleen apps in als je standaard telefoon-app als je ze vertrouwt."</string>
+ <string name="change_default_dialer_warning_message" msgid="1417671460801684999">"<xliff:g id="NEW_APP">%s</xliff:g> kan gesprekken plaatsen en alle aspecten hiervan beheren. Stel alleen apps in als je standaard telefoon-app als je ze vertrouwt."</string>
<string name="blocked_numbers" msgid="2751843139572970579">"Geblokkeerde nummers"</string>
- <string name="blocked_numbers_msg" msgid="1045015186124965643">"Je ontvangt geen oproepen of sms\'jes van geblokkeerde nummers."</string>
+ <string name="blocked_numbers_msg" msgid="1045015186124965643">"Je ontvangt geen gesprekken of sms\'jes van geblokkeerde nummers."</string>
<string name="block_number" msgid="1101252256321306179">"Een nummer toevoegen"</string>
<string name="unblock_dialog_body" msgid="1614238499771862793">"Blokkering van <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> opheffen?"</string>
<string name="unblock_button" msgid="3078048901972674170">"Blokkering opheffen"</string>
- <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Oproepen en sms\'jes blokkeren van"</string>
+ <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Gesprekken en sms\'jes blokkeren van"</string>
<string name="add_blocked_number_hint" msgid="6847675097085433553">"Telefoonnummer"</string>
<string name="block_button" msgid="8822290682524373357">"Blokkeren"</string>
<string name="non_primary_user" msgid="5180129233352533459">"Alleen de eigenaar van het apparaat kan geblokkeerd nummers bekijken en beheren."</string>
@@ -67,34 +68,35 @@
<string name="blocked_numbers_block_emergency_number_message" msgid="917851876780698387">"Kan alarmnummer niet blokkeren."</string>
<string name="blocked_numbers_number_already_blocked_message" msgid="4392247814500811798">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> is al geblokkeerd."</string>
<string name="toast_personal_call_msg" msgid="5115361633476779723">"De persoonlijke kiezer gebruiken om te bellen"</string>
- <string name="notification_incoming_call" msgid="7713197997773986670">"<xliff:g id="CALL_VIA">%1$s</xliff:g>-oproep van <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
+ <string name="notification_incoming_call" msgid="7713197997773986670">"<xliff:g id="CALL_VIA">%1$s</xliff:g>-gesprek van <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
<string name="notification_incoming_video_call" msgid="6638486071698373893">"<xliff:g id="CALL_VIA">%1$s</xliff:g>-videogesprek van <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
- <string name="answering_ends_other_call" msgid="8282145910153766401">"Als je opneemt, wordt je <xliff:g id="CALL_VIA">%1$s</xliff:g>-oproep beëindigd"</string>
- <string name="answering_ends_other_calls" msgid="1198589551399049197">"Als je opneemt, worden je <xliff:g id="CALL_VIA">%1$s</xliff:g>-oproepen beëindigd"</string>
+ <string name="answering_ends_other_call" msgid="8282145910153766401">"Als je opneemt, wordt je <xliff:g id="CALL_VIA">%1$s</xliff:g>-gesprek beëindigd"</string>
+ <string name="answering_ends_other_calls" msgid="1198589551399049197">"Als je opneemt, worden je <xliff:g id="CALL_VIA">%1$s</xliff:g>-gesprekken beëindigd"</string>
<string name="answering_ends_other_video_call" msgid="8510410917384186360">"Als je opneemt, wordt je <xliff:g id="CALL_VIA">%1$s</xliff:g>-videogesprek beëindigd"</string>
- <string name="answering_ends_other_managed_call" msgid="5186137550267947785">"Als je opneemt, wordt je actieve oproep beëindigd"</string>
- <string name="answering_ends_other_managed_calls" msgid="6429838309560397988">"Als je opneemt, worden je actieve oproepen beëindigd"</string>
+ <string name="answering_ends_other_managed_call" msgid="5186137550267947785">"Als je opneemt, wordt je actief gesprek beëindigd"</string>
+ <string name="answering_ends_other_managed_calls" msgid="6429838309560397988">"Als je opneemt, worden je actieve gesprekken beëindigd"</string>
<string name="answering_ends_other_managed_video_call" msgid="1585423762458248435">"Als je opneemt, wordt je actieve videogesprek beëindigd"</string>
<string name="answer_incoming_call" msgid="4140530013111794587">"Beantwoorden"</string>
<string name="decline_incoming_call" msgid="806026168661598368">"Weigeren"</string>
- <string name="cant_call_due_to_ongoing_call" msgid="4952615196237854748">"Oproep kan niet worden gestart vanwege je <xliff:g id="OTHER_CALL">%1$s</xliff:g>-oproep."</string>
- <string name="cant_call_due_to_ongoing_calls" msgid="1380804892363503856">"Oproep kan niet worden gestart vanwege je <xliff:g id="OTHER_CALL">%1$s</xliff:g>-oproepen."</string>
- <string name="cant_call_due_to_ongoing_unknown_call" msgid="149091978697302211">"Oproep kan niet worden gestart vanwege een oproep in een andere app."</string>
- <string name="notification_channel_incoming_call" msgid="3513761697082968084">"Inkomende oproepen"</string>
- <string name="notification_channel_missed_call" msgid="8727062678632713146">"Gemiste oproepen"</string>
- <string name="notification_channel_call_blocking" msgid="2943358779746676070">"Oproepen blokkeren"</string>
- <string name="alert_outgoing_call" msgid="982908156825958001">"Als je deze oproep start, wordt je <xliff:g id="OTHER_APP">%1$s</xliff:g>-oproep beëindigd."</string>
- <string name="phone_settings_call_blocking_txt" msgid="3976004073043846733">"Oproepen blokkeren"</string>
+ <string name="cant_call_due_to_ongoing_call" msgid="4952615196237854748">"Gesprek kan niet worden gestart vanwege je <xliff:g id="OTHER_CALL">%1$s</xliff:g>-gesprek."</string>
+ <string name="cant_call_due_to_ongoing_calls" msgid="1380804892363503856">"Gesprek kan niet worden gestart vanwege je <xliff:g id="OTHER_CALL">%1$s</xliff:g>-gesprekken."</string>
+ <string name="cant_call_due_to_ongoing_unknown_call" msgid="149091978697302211">"Gesprek kan niet worden gestart vanwege een gesprek in een andere app."</string>
+ <string name="notification_channel_incoming_call" msgid="3513761697082968084">"Inkomende gesprekken"</string>
+ <string name="notification_channel_missed_call" msgid="8727062678632713146">"Gemiste gesprekken"</string>
+ <string name="notification_channel_call_blocking" msgid="2943358779746676070">"Gesprekken blokkeren"</string>
+ <string name="alert_outgoing_call" msgid="982908156825958001">"Als je dit gesprek start, wordt je <xliff:g id="OTHER_APP">%1$s</xliff:g>-gesprek beëindigd."</string>
+ <string name="phone_settings_call_blocking_txt" msgid="3976004073043846733">"Gesprekken blokkeren"</string>
<string name="phone_settings_number_not_in_contact_txt" msgid="3126829421867168652">"Nummers die niet op je contactenlijst staan"</string>
<string name="phone_settings_number_not_in_contact_summary_txt" msgid="9043147855140079119">"Blokkeer nummers die niet op je contactenlijst staan"</string>
<string name="phone_settings_private_num_txt" msgid="8623574188879134262">"Privé"</string>
<string name="phone_settings_private_num_summary_txt" msgid="7516314821207782191">"Bellers met een anoniem nummer blokkeren"</string>
<string name="phone_settings_payphone_txt" msgid="2493356957416981318">"Betaaltelefoon"</string>
- <string name="phone_settings_payphone_summary_txt" msgid="6126709946103814653">"Oproepen van betaaltelefoons blokkeren"</string>
+ <string name="phone_settings_payphone_summary_txt" msgid="6126709946103814653">"Gesprekken van betaaltelefoons blokkeren"</string>
<string name="phone_settings_unknown_txt" msgid="5836407031508172721">"Onbekend"</string>
- <string name="phone_settings_unknown_summary_txt" msgid="3457690230497753233">"Oproepen van onbekende bellers blokkeren"</string>
- <string name="phone_strings_call_blocking_turned_off_notification_title_txt" msgid="628536625775266096">"Oproepen blokkeren"</string>
- <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Oproepen blokkeren uitgeschakeld"</string>
+ <string name="phone_settings_unknown_summary_txt" msgid="3457690230497753233">"Gesprekken van onbekende bellers blokkeren"</string>
+ <string name="phone_strings_call_blocking_turned_off_notification_title_txt" msgid="628536625775266096">"Gesprekken blokkeren"</string>
+ <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Gesprekken blokkeren uitgeschakeld"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Noodoproep geplaatst"</string>
- <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Oproepen blokkeren is uitgeschakeld zodat nooddiensten je kunnen bereiken."</string>
+ <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Gesprekken blokkeren is uitgeschakeld zodat nooddiensten je kunnen bereiken."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Telecomontwikkelaarsmenu"</string>
</resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
deleted file mode 100644
index c3c6cbe..0000000
--- a/res/values-or/strings.xml
+++ /dev/null
@@ -1,100 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="382363169988504520">"କଲ୍ ପରିଚାଳନା"</string>
- <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"ଫୋନ୍ କରନ୍ତୁ"</string>
- <string name="unknown" msgid="6878797917991465859">"ଅଜଣା"</string>
- <string name="notification_missedCallTitle" msgid="7554385905572364535">"ମିସଡ୍ କଲ୍"</string>
- <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"କାର୍ଯ୍ୟସ୍ଥଳୀରୁ ଆସିଥିବା ମିସଡ୍ କଲ୍"</string>
- <string name="notification_missedCallsTitle" msgid="1361677948941502522">"ମିସଡ୍ କଲ୍"</string>
- <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>ଟି ମିସଡ୍ କଲ୍"</string>
- <string name="notification_missedCallTicker" msgid="504686252427747209">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>ଙ୍କ ଠାରୁ ମିସ୍-କଲ୍ ମିଳିଛି"</string>
- <string name="notification_missedCall_call_back" msgid="2684890353590890187">"କଲବ୍ୟାକ୍ କରନ୍ତୁ"</string>
- <string name="notification_missedCall_message" msgid="3049928912736917988">"ମେସେଜ୍ ଦିଅନ୍ତୁ"</string>
- <string name="accessibility_call_muted" msgid="2776111226185342220">"କଲ୍ ମ୍ୟୁଟ୍ କରାଯାଇଛି।"</string>
- <string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"ସ୍ପିକରଫୋନ୍କୁ ସକ୍ଷମ କରାଯାଇଛି ।"</string>
- <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"ବର୍ତ୍ତମାନ କଥା ହୋଇପାରିବ ନାହିଁ। କଥା କ’ଣ?"</string>
- <string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"ମୁଁ ଟିକେ ପରେ ଆପଣଙ୍କୁ କଲ୍ କରିବି।"</string>
- <string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"ମୁଁ ଆପଣଙ୍କୁ ପରେ କଲ୍ କରିବି।"</string>
- <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"ବର୍ତ୍ତମାନ କଥା ହୋଇପାରିବ ନାହିଁ। ମୋତେ ପରେ କଲ୍ କରିବେ?"</string>
- <string name="respond_via_sms_setting_title" msgid="3754000371039709383">"ଶୀଘ୍ର ଉତ୍ତର"</string>
- <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"ଶୀଘ୍ର ଉତ୍ତରକୁ ଏଡିଟ୍ କରନ୍ତୁ"</string>
- <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
- <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ଶୀଘ୍ର ଉତ୍ତର"</string>
- <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>କୁ ମେସେଜ୍ ପଠାଗଲା।"</string>
- <string name="enable_account_preference_title" msgid="2021848090086481720">"କଲ୍ କରିବା ଆକାଉଣ୍ଟ"</string>
- <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"କେବଳ ଜରୁରିକାଳୀନ କଲ୍କୁ ଅନୁମତି ଦିଆଯାଇଛି।"</string>
- <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ଫୋନ୍ର ବିନାଅନୁମତିରେ ଏହି ଆପ୍ଲିକେଶନ୍ ଆଉଟ୍ଗୋଇଙ୍ଗ କଲ୍ କରିପାରିବ ନାହିଁ।"</string>
- <string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"ଗୋଟିଏ କଲ୍ କରିବା ପାଇଁ ଏକ ବୈଧ ନମ୍ବର୍ ପ୍ରବେଶ କରନ୍ତୁ।"</string>
- <string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"ଏହି ସମୟରେ କଲ୍ ଯୋଡ଼ାଯାଇପାରିବ ନାହିଁ।"</string>
- <string name="no_vm_number" msgid="4164780423805688336">"ହଜିଯାଇଥିବା ଭଏସମେଲ୍ ନମ୍ବର୍"</string>
- <string name="no_vm_number_msg" msgid="1300729501030053828">"SIM କାର୍ଡରେ କୌଣସି ଭଏସମେଲ୍ ନମ୍ବର୍ ଷ୍ଟୋର୍ କରାଯାଇନାହିଁ।"</string>
- <string name="add_vm_number_str" msgid="4676479471644687453">"ନମ୍ବର୍ ଯୋଡ଼ନ୍ତୁ"</string>
- <string name="change_default_dialer_dialog_title" msgid="9101655962941740507">"<xliff:g id="NEW_APP">%s</xliff:g>କୁ ଆପଣଙ୍କ ଫୋନ୍ର ଡିଫଲ୍ଟ ଆପ୍ କରିବେ?"</string>
- <string name="change_default_dialer_dialog_affirmative" msgid="8606546663509166276">"ଡିଫଲ୍ଟ ସେଟ୍ କରନ୍ତୁ"</string>
- <string name="change_default_dialer_dialog_negative" msgid="9078144617060173845">"କ୍ୟାନ୍ସଲ୍ କରନ୍ତୁ"</string>
- <string name="change_default_dialer_warning_message" msgid="1417671460801684999">"<xliff:g id="NEW_APP">%s</xliff:g> କଲ୍ କରିବା ଏବଂ କଲ୍ର ସମସ୍ତ ଦିଗକୁ ନିୟନ୍ତ୍ରଣ କରିବାରେ ସକ୍ଷମ ହେବ। କେବଳ ନିଜର ଭରସାଯୋଗ୍ୟ ଆପ୍କୁ ଡିଫଲ୍ଟ ଫୋନ୍ ଆପ୍ ଭାବେ ସେଟ୍ କରିବା ଉଚିତ୍।"</string>
- <string name="blocked_numbers" msgid="2751843139572970579">"ଅବରୋଧ କରାଯାଇଥିବା ନମ୍ବର୍"</string>
- <string name="blocked_numbers_msg" msgid="1045015186124965643">"ଅବରୋଧ କରାଯାଇଥିବା ନମ୍ବର୍ରୁ ଆପଣ କଲ୍ କିମ୍ବା ଟେକ୍ସଟ୍ ଗ୍ରହଣ କରିପାରିବେ ନାହିଁ।"</string>
- <string name="block_number" msgid="1101252256321306179">"ଗୋଟିଏ ନମ୍ବର୍ ଯୋଡ଼ନ୍ତୁ"</string>
- <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>ରୁ ଅବରୋଧ ହଟାଇବେ?"</string>
- <string name="unblock_button" msgid="3078048901972674170">"ଅବରୋଧ ହଟାନ୍ତୁ"</string>
- <string name="add_blocked_dialog_body" msgid="9030243212265516828">"ଏହାର କଲ୍ ଓ ଟେକ୍ସଟ୍କୁ ଅବରୋଧ କରନ୍ତୁ"</string>
- <string name="add_blocked_number_hint" msgid="6847675097085433553">"ଫୋନ୍ ନମ୍ଵର୍"</string>
- <string name="block_button" msgid="8822290682524373357">"ଅବରୋଧ କରନ୍ତୁ"</string>
- <string name="non_primary_user" msgid="5180129233352533459">"କେବଳ ଡିଭାଇସ୍ର ମାଲିକ ଅବରୋଧ କରାଯାଇଥିବା ନମ୍ବର୍କୁ ଦେଖିପାରିବେ ଓ ପରିଚାଳନା କରିପାରିବେ।"</string>
- <string name="delete_icon_description" msgid="8903995728252556724">"ଅବରୋଧ ହଟାନ୍ତୁ"</string>
- <string name="blocked_numbers_butter_bar_title" msgid="438170866438793182">"ଅସ୍ଥାୟୀରୂପେ ଅବରୋଧ ଅଫ୍ ଅଛି"</string>
- <string name="blocked_numbers_butter_bar_body" msgid="2223244484319442431">"ଆପଣ ଗୋଟିଏ ଜରୁରିକାଳୀନ ନମ୍ବର୍କୁ ଡାଏଲ୍ କିମ୍ବା ଟେକ୍ସଟ୍ କରିବା ପରେ, ଜରୁରିକାଳୀନ ସେବା ଆପଣଙ୍କୁ ଯୋଗାଯୋଗ କରିବାକୁ ସୁନିଶ୍ଚିତ କରିବା ପାଇଁ ଅବରୋଧକୁ ବନ୍ଦ କରିଦିଆଯାଇଥାଏ।"</string>
- <string name="blocked_numbers_butter_bar_button" msgid="2197943354922010696">"ବର୍ତ୍ତମାନ ପୁନଃସକ୍ଷମ କରନ୍ତୁ"</string>
- <string name="blocked_numbers_number_blocked_message" msgid="7678509606805029540">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> ଅବରୋଧ କରାଯାଇଛି"</string>
- <string name="blocked_numbers_number_unblocked_message" msgid="977894647366750418">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> ଅବରୋଧ ହଟାଇଦିଆଯାଇଛି"</string>
- <string name="blocked_numbers_block_emergency_number_message" msgid="917851876780698387">"ଜରୁରିକାଳୀନ ନମ୍ବର୍କୁ ଅବରୋଧ କରିବାରେ ଅକ୍ଷମ।"</string>
- <string name="blocked_numbers_number_already_blocked_message" msgid="4392247814500811798">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g>କୁ ଅବରୋଧ କରାଯାଇସରିଛି।"</string>
- <string name="toast_personal_call_msg" msgid="5115361633476779723">"କଲ୍ କରିବା ପାଇଁ ବ୍ୟକ୍ତିଗତ ଡାଏଲର୍କୁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
- <string name="notification_incoming_call" msgid="7713197997773986670">"<xliff:g id="CALL_FROM">%2$s</xliff:g> ଠାରୁ <xliff:g id="CALL_VIA">%1$s</xliff:g>କୁ କଲ୍ କରନ୍ତୁ"</string>
- <string name="notification_incoming_video_call" msgid="6638486071698373893">"<xliff:g id="CALL_FROM">%2$s</xliff:g> ଠାରୁ <xliff:g id="CALL_VIA">%1$s</xliff:g> ଭିଡିଓ କଲ୍ କରନ୍ତୁ"</string>
- <string name="answering_ends_other_call" msgid="8282145910153766401">"ଉତ୍ତର ଦେବାଦ୍ଵାରା ଆପଣଙ୍କର <xliff:g id="CALL_VIA">%1$s</xliff:g> କଲ୍ ସମାପ୍ତ ହୋଇଯିବ"</string>
- <string name="answering_ends_other_calls" msgid="1198589551399049197">"ଉତ୍ତର ଦେବାଦ୍ଵାରା ଆପଣଙ୍କର <xliff:g id="CALL_VIA">%1$s</xliff:g> କଲ୍ ସମାପ୍ତ ହୋଇଯିବ"</string>
- <string name="answering_ends_other_video_call" msgid="8510410917384186360">"ଉତ୍ତର ଦେବାଦ୍ଵାରା ଆପଣଙ୍କର <xliff:g id="CALL_VIA">%1$s</xliff:g> ଭିଡିଓ କଲ୍ ସମାପ୍ତ ହୋଇଯିବ"</string>
- <string name="answering_ends_other_managed_call" msgid="5186137550267947785">"ଉତ୍ତର ଦେବାଦ୍ଵାରା ଆପଣଙ୍କର ଜାରି ରହିଥିବା କଲ୍ ସମାପ୍ତ ହୋଇଯିବ"</string>
- <string name="answering_ends_other_managed_calls" msgid="6429838309560397988">"ଉତ୍ତର ଦେବାଦ୍ଵାରା ଆପଣଙ୍କର ଜାରି ରହିଥିବା କଲ୍ ସମାପ୍ତ ହୋଇଯିବ"</string>
- <string name="answering_ends_other_managed_video_call" msgid="1585423762458248435">"ଉତ୍ତର ଦେବାଦ୍ଵାରା ଆପଣଙ୍କର ଜାରି ରହିଥିବା ଭିଡିଓ କଲ୍ ସମାପ୍ତ ହୋଇଯିବ"</string>
- <string name="answer_incoming_call" msgid="4140530013111794587">"ଉତ୍ତର ଦିଅନ୍ତୁ"</string>
- <string name="decline_incoming_call" msgid="806026168661598368">"ଅସ୍ୱୀକାର"</string>
- <string name="cant_call_due_to_ongoing_call" msgid="4952615196237854748">"ଆପଣଙ୍କର <xliff:g id="OTHER_CALL">%1$s</xliff:g> କଲ୍ ହେତୁ କଲ୍ କରାଯାଇପାରିବ ନାହିଁ।"</string>
- <string name="cant_call_due_to_ongoing_calls" msgid="1380804892363503856">"ଆପଣଙ୍କର <xliff:g id="OTHER_CALL">%1$s</xliff:g> କଲ୍ ହେତୁ କଲ୍ କରାଯାଇପାରିବ ନାହିଁ।"</string>
- <string name="cant_call_due_to_ongoing_unknown_call" msgid="149091978697302211">"ଅନ୍ୟ ଆପ୍ରେ କରାଯାଇଥିବା କଲ୍ ହେତୁ କଲ୍ କରାଯାଇପାରିବ ନାହିଁ।"</string>
- <string name="notification_channel_incoming_call" msgid="3513761697082968084">"ଇନ୍କମିଙ୍ଗ କଲ୍"</string>
- <string name="notification_channel_missed_call" msgid="8727062678632713146">"ମିସଡ୍ କଲ୍"</string>
- <string name="notification_channel_call_blocking" msgid="2943358779746676070">"କଲ୍କୁ ଅବରୋଧ କରନ୍ତୁ"</string>
- <string name="alert_outgoing_call" msgid="982908156825958001">"ଏହି କଲ୍କୁ ସ୍ଥାପନ କରିବା ଦ୍ଵାରା ଆପଣଙ୍କର <xliff:g id="OTHER_APP">%1$s</xliff:g> କଲ୍ ସମାପ୍ତ ହୋଇଯିବ।"</string>
- <string name="phone_settings_call_blocking_txt" msgid="3976004073043846733">"କଲ୍କୁ ଅବରୋଧ କରନ୍ତୁ"</string>
- <string name="phone_settings_number_not_in_contact_txt" msgid="3126829421867168652">"ଯୋଗାଯୋଗରେ ନଥିବା ନମ୍ବର୍"</string>
- <string name="phone_settings_number_not_in_contact_summary_txt" msgid="9043147855140079119">"ଆପଣଙ୍କ ଯୋଗାଯୋଗରେ ତାଲିକାଭୁକ୍ତ ହୋଇନଥିବା ନମ୍ବର୍କୁ ଅବରୋଧ କରନ୍ତୁ"</string>
- <string name="phone_settings_private_num_txt" msgid="8623574188879134262">"ଗୋପନୀୟ"</string>
- <string name="phone_settings_private_num_summary_txt" msgid="7516314821207782191">"ନିଜର ନମ୍ବର୍କୁ ପ୍ରକାଶ କରୁନଥିବା କଲ୍କର୍ତ୍ତାଙ୍କୁ ଅବରୋଧ କରନ୍ତୁ"</string>
- <string name="phone_settings_payphone_txt" msgid="2493356957416981318">"ପେ-ଫୋନ୍"</string>
- <string name="phone_settings_payphone_summary_txt" msgid="6126709946103814653">"ପେ-ଫୋନ୍ରୁ କଲ୍କୁ ଅବରୋଧ କରନ୍ତୁ"</string>
- <string name="phone_settings_unknown_txt" msgid="5836407031508172721">"ଅଜଣା"</string>
- <string name="phone_settings_unknown_summary_txt" msgid="3457690230497753233">"ଅଚିହ୍ନା କଲକର୍ତ୍ତାଙ୍କର କଲ୍କୁ ଅବରୋଧ କରନ୍ତୁ"</string>
- <string name="phone_strings_call_blocking_turned_off_notification_title_txt" msgid="628536625775266096">"କଲ୍କୁ ଅବରୋଧ କରନ୍ତୁ"</string>
- <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"କଲ୍ ଅବରୋଧ ସୁବିଧାକୁ ଅକ୍ଷମ କରାଯାଇଛି"</string>
- <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"ଜରୁରିକାଳୀନ କଲ୍ କରାଗଲା"</string>
- <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"ଜରୁରିକାଳୀନ ସହାୟତା କର୍ମଚାରୀମାନେ ଆପଣଙ୍କୁ ଯୋଗଯୋଗ କରିବା ପାଇଁ କଲ୍ ଅବରୋଧକୁ ଅକ୍ଷମ କରାଯାଇଛି।"</string>
-</resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 076dbd0..12e0591 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ਤਤਕਾਲ ਜਵਾਬ"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"ਸੁਨੇਹਾ <xliff:g id="PHONE_NUMBER">%s</xliff:g> ਨੂੰ ਭੇਜਿਆ ਗਿਆ।"</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> \'ਤੇ ਸੁਨੇਹਾ ਭੇਜਣਾ ਅਸਫਲ ਰਿਹਾ।"</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"ਕਾਲਿੰਗ ਖਾਤੇ"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"ਸਿਰਫ਼ ਸੰਕਟ ਕਾਲਾਂ ਨੂੰ ਮਨਜ਼ੂਰੀ ਹੈ।"</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ਇਹ ਐਪਲੀਕੇਸ਼ਨ ਫੋਨ ਅਨੁਮਤੀ ਦੇ ਬਿਨਾਂ ਆਉਟਗੋਇੰਗ ਕਾਲਾਂ ਨਹੀਂ ਕਰ ਸਕਦੀ।"</string>
@@ -67,7 +68,7 @@
<string name="blocked_numbers_block_emergency_number_message" msgid="917851876780698387">"ਐਮਰਜੈਂਸੀ ਨੰਬਰ ਨੂੰ ਬਲੌਕ ਕਰਨ ਵਿੱਚ ਅਸਮਰੱਥ।"</string>
<string name="blocked_numbers_number_already_blocked_message" msgid="4392247814500811798">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> ਪਹਿਲਾਂ ਤੋਂ ਹੀ ਬਲੌਕ ਕੀਤਾ ਹੋਇਆ ਹੈ।"</string>
<string name="toast_personal_call_msg" msgid="5115361633476779723">"ਕਾਲ ਕਰਨ ਲਈ ਨਿੱਜੀ ਡਾਇਲਰ ਦੀ ਵਰਤੋਂ ਕਰਨੀ"</string>
- <string name="notification_incoming_call" msgid="7713197997773986670">"<xliff:g id="CALL_FROM">%2$s</xliff:g> ਵੱਲੋਂ <xliff:g id="CALL_VIA">%1$s</xliff:g> ਕਾਲ"</string>
+ <string name="notification_incoming_call" msgid="7713197997773986670">"<xliff:g id="CALL_VIA">%1$s</xliff:g> ਕਾਲ ਕਰਤਾ <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
<string name="notification_incoming_video_call" msgid="6638486071698373893">"<xliff:g id="CALL_FROM">%2$s</xliff:g> ਵੱਲੋਂ <xliff:g id="CALL_VIA">%1$s</xliff:g> ਵੀਡੀਓ ਕਾਲ"</string>
<string name="answering_ends_other_call" msgid="8282145910153766401">"ਜਵਾਬ ਦੇਣ ਨਾਲ ਤੁਹਾਡੀ <xliff:g id="CALL_VIA">%1$s</xliff:g> ਕਾਲ ਸਮਾਪਤ ਹੋ ਜਾਵੇਗੀ"</string>
<string name="answering_ends_other_calls" msgid="1198589551399049197">"ਜਵਾਬ ਦੇਣ ਨਾਲ ਤੁਹਾਡੀਆਂ <xliff:g id="CALL_VIA">%1$s</xliff:g> ਕਾਲਾਂ ਸਮਾਪਤ ਹੋ ਜਾਣਗੀਆਂ"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"ਕਾਲ ਬਲਾਕਿੰਗ ਵਿਕਲਪ ਬੰਦ ਕੀਤਾ ਗਿਆ"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"ਸੰਕਟਕਾਲੀਨ ਕਾਲ ਕੀਤੀ ਗਈ"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"ਸੰਕਟਕਾਲੀਨ ਸਥਿਤੀ ਵਿੱਚ ਮਦਦ ਕਰਨ ਵਾਲੇ ਵਿਅਕਤੀ ਨੂੰ ਤੁਹਾਨੂੰ ਸੰਪਰਕ ਕਰਨ ਦੇਣ ਲਈ ਕਾਲ ਬਲਾਕਿੰਗ ਵਿਕਲਪ ਬੰਦ ਕਰ ਦਿੱਤਾ ਗਿਆ ਹੈ।"</string>
+ <string name="developer_title" msgid="1816273446906554627">"ਟੈਲੀਕੋਮ ਵਿਕਾਸਕਾਰ ਮੀਨੂ"</string>
</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index b48bdb3..6119726 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Szybka odpowiedź"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Wiadomość wysłano na numer <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Nie udało się wysłać wiadomości do: <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Konta telefoniczne"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Dozwolone są tylko połączenia alarmowe."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ta aplikacja nie może wykonywać połączeń bez uprawnienia Telefon."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blokowanie połączeń wyłączone"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Wykonano połączenie alarmowe"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Blokowanie połączeń zostało wyłączone, aby służby ratownicze mogły się z Tobą skontaktować."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Menu programisty Telecom"</string>
</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 8dc9405..87cbf5c 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Resposta rápida"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mensagem enviada para <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Falha ao enviar a mensagem para <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Contas de chamadas"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Apenas são permitidas chamadas de emergência."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Esta aplicação não pode fazer chamadas sem a autorização do telefone."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Bloqueio de chamadas desativado"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Chamada de emergência efetuada"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"O bloqueio de chamadas foi desativado para permitir a receção de contactos de resposta a emergências."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Menu do programador de telecomunicações"</string>
</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 8b3e634..4c0372f 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -37,13 +37,14 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Resposta rápida"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mensagem enviada para <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Falha ao enviar a mensagem para <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Contas de chamadas"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Somente chamadas de emergência são permitidas."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Este aplicativo não pode fazer chamadas sem a permissão do smartphone."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Para realizar uma chamada, digite um número válido."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"No momento, não é possível adicionar a chamada."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Número correio de voz ausente"</string>
- <string name="no_vm_number_msg" msgid="1300729501030053828">"Não há um número correio de voz armazenado no cartão SIM."</string>
+ <string name="no_vm_number_msg" msgid="1300729501030053828">"Não há um número correio de voz armazenado no chip."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Adicionar número"</string>
<string name="change_default_dialer_dialog_title" msgid="9101655962941740507">"Usar o <xliff:g id="NEW_APP">%s</xliff:g> como seu app de telefone padrão?"</string>
<string name="change_default_dialer_dialog_affirmative" msgid="8606546663509166276">"Definir padrão"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Bloqueio de chamadas desativado"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"A chamada de emergência foi feita"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"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="1816273446906554627">"Menu do desenvolvedor de telecomunicação"</string>
</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index ae17707..ae96c51 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Răspuns rapid"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mesajul a fost trimis la <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Mesajul nu a fost trimis la <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Conturi pentru apelare"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Sunt permise doar apelurile de urgență."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Această aplicație nu poate efectua apeluri fără permisiunea Telefon."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blocarea apelurilor este dezactivată."</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"S-a efectuat un apel de urgență."</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Blocarea apelurilor a fost dezactivată pentru a permite serviciilor de urgență să vă contacteze."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Meniu pentru dezvoltatori de telecomunicații"</string>
</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 8b56d7b..d1ac989 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Быстрый ответ"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Сообщение отправлено на номер <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Не удалось отправить сообщение на номер <xliff:g id="PHONE_NUMBER">%s</xliff:g>"</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Аккаунты для звонков"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Только экстренные вызовы"</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Приложение не может совершать звонки без соответствующего разрешения."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Блокировка вызовов отключена"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Выполнен экстренный вызов"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Блокировка вызовов отключена, чтобы у экстренных служб была возможность позвонить вам."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Меню разработчика Telecom"</string>
</resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 538f96d..784cc66 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ක්ෂණික ප්රතිචාරය"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> හට පණිවිඩය යවන්න."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"පණිවිඩය <xliff:g id="PHONE_NUMBER">%s</xliff:g> වෙත යැවීමට අසමත් විය."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"ඇමතීමේ ගිණුම්"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"හදිසි ඇමතුම්වලට පමණක් ඉඩ දේ."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"දුරකථන අවසරයෙන් තොරව මෙම යෙදුමට පිටතට යන ඇමතුම් සිදු කළ නොහැකිය."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"ඇමතුම් අවහිර කිරීම අබල කර ඇත"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"හදිසි ඇමතුම් ගැනීම"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"හදිසි අමතන්නන්ට ඔබව සම්බන්ධ කර ගැනීමට ඉඩ දීමට ඇමතුම් අවහිර කිරීම අබල කර ඇත."</string>
+ <string name="developer_title" msgid="1816273446906554627">"ටෙලිකොම් සංවර්ධක මෙනුව"</string>
</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index fd7a85f..217a46e 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -28,15 +28,16 @@
<string name="notification_missedCall_message" msgid="3049928912736917988">"Napísať"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"Zvuk hovoru bol vypnutý."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Reproduktor je povolený."</string>
- <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Teraz nemôžem hovoriť, o čo ide?"</string>
+ <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Teraz nemôžem hovoriť, o čo ide?"</string>
<string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Zavolám späť."</string>
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Zavolám neskôr."</string>
- <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Teraz nemôžem, zavolajte inokedy."</string>
+ <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Nemôžem hovoriť, zavoláte neskôr?"</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Rýchle odpovede"</string>
<string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Upraviť rýchle odpovede"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Rýchla odpoveď"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Správa bola odoslaná na číslo <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Správu sa nepodarilo odoslať na číslo <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Telefónne účty"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Povolené sú len tiesňové volania."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"V tejto aplikácii nie je možné uskutočňovať odchádzajúce hovory bez povolenia Telefón"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blokovanie hovorov je vypnuté"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Bolo uskutočnené tiesňové volanie"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Blokovanie hovorov bolo vypnuté, aby vás mohli kontaktovať pracovníci tiesňových služieb."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Ponuka pre vývojárov Telecomu"</string>
</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 083a7c5..46fb2d6 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Hiter odgovor"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"SMS poslan na številko <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Sporočilo ni bilo poslano na številko <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Računi za klicanje"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Dovoljeno je samo opravljanje klicev v sili."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ta aplikacija lahko opravlja odhodne klice brez dovoljenja za telefon."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blokiranje klicev je onemogočeno"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Opravljen je klic v sili"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"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="1816273446906554627">"Meni za razvijalce Telecom"</string>
</resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 325f62b..8a2c88c 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Përgjigje e shpejtë"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mesazhi u dërgua te <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Dërgimi i mesazhit te <xliff:g id="PHONE_NUMBER">%s</xliff:g> dështoi."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Llogaritë e telefonatave"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Lejohen vetëm telefonatat e urgjencës."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ky aplikacion nuk mund të kryejë telefonata dalëse pa lejen e Telefonit."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Bllokimi i telefonatave u çaktivizua"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Telefonata e urgjencës u krye"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"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="1816273446906554627">"Menyja e zhvilluesit të telekomunikimit"</string>
</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 59f5969..dddb959 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Брзи одговор"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Порука је послата на број <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Слање поруке на <xliff:g id="PHONE_NUMBER">%s</xliff:g> није успело."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Налози за позивање"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Дозвољени су само хитни позиви."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ова апликација не може да позива без дозволе за телефонирање."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Блокирање позива је онемогућено"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Упућен је хитни позив"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Блокирање позива је онемогућено да би хитне службе могле да вас контактирају."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Мени за програмере Telecom-а"</string>
</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 78a3963..525069c 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Snabbsvar"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Meddelandet har skickats till <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Meddelande till <xliff:g id="PHONE_NUMBER">%s</xliff:g> inte skickat."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Konton för samtal"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Det går bara att ringa nödsamtal."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Den här appen kan inte göra utgående samtal utan behörigheten Telefon."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Samtalsblockering inaktiverad"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Nödsamtal ringt"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Samtalsblockering har inaktiverats för att tillåta att räddningstjänsten kontaktar dig."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Meny för telekomutvecklare"</string>
</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 86b83a5..810a977 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Majibu ya haraka"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Ujumbe uliotumwa kwa <xliff:g id="PHONE_NUMBER">%s</xliff:g> ."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Imeshindwa kutuma SMS kwa <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Akaunti za simu"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Piga simu za dharura pekee."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Programu hii haiwezi kupiga simu bila ruhusa ya Simu."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Kipengele cha Kuzuia Simu kimezimwa"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Simu ya dharura imepigwa"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Kipengele cha Kuzuia Simu kimezimwa ili kuruhusu wapigaji simu za dharura kuwasiliana nawe."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Menyu ya Msanidi programu wa Telecom"</string>
</resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 16cf7a3..afc6cde 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -28,15 +28,16 @@
<string name="notification_missedCall_message" msgid="3049928912736917988">"செய்தி"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"அழைப்பு முடக்கப்பட்டது."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"ஸ்பீக்கர்ஃபோன் இயக்கப்பட்டது."</string>
- <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"இப்போது பேசமுடியாது. என்ன விஷேசம்?"</string>
+ <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"இப்போது பேசமுடியாது. என்ன விசேஷம்?"</string>
<string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"சிறிதுநேரம் கழித்து நான் அழைக்கிறேன்."</string>
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"பிறகு அழைக்கிறேன்."</string>
- <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"இப்போது பேச முடியவில்லை. பிறகு அழைக்க முடியுமா?"</string>
+ <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"இப்போது பேசமுடியாது, பிறகு அழைக்கிறீர்களா?"</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"விரைவு பதில்கள்"</string>
<string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"விரைவு பதில்களை மாற்று"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"விரைவு பதில்"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> க்குச் செய்தி அனுப்பப்பட்டது."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"செய்தியை <xliff:g id="PHONE_NUMBER">%s</xliff:g>க்கு அனுப்ப முடியவில்லை."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"அழைப்புக் கணக்குகள்"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"அவசர அழைப்புகள் மட்டுமே அனுமதிக்கப்படும்."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ஃபோன் அனுமதியில்லாமல், பயன்பாட்டினால் வெளிச்செல்லும் அழைப்புகளைச் செய்ய முடியாது."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"அழைப்புத் தடுப்பு முடக்கப்பட்டுள்ளது"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"அவசர அழைப்பு செய்யப்பட்டது"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"அவசரநிலையில் பதிலளிப்பவர்களை உங்களைத் தொடர்புகொள்வதற்கு அனுமதிக்க, அழைப்புத் தடுப்பு முடக்கப்பட்டுள்ளது."</string>
+ <string name="developer_title" msgid="1816273446906554627">"டெலிகாம் டெவெலப்பர் மெனு"</string>
</resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index d6cd091..600c64e 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"శీఘ్ర ప్రతిస్పందన"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>కు సందేశం పంపబడింది."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>కు సందేశాన్ని పంపడం విఫలమైంది."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"కాలింగ్ ఖాతాలు"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"కేవలం అత్యవసర కాల్లు మాత్రమే అనుమతించబడతాయి."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ఈ అనువర్తనం ఫోన్ అనుమతి లేకుండా అవుట్గోయింగ్ కాల్లను చేయలేదు."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"కాల్ బ్లాక్ చేయడం నిలిపివేయబడింది"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"అత్యవసర కాల్ చేయబడింది"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"మిమ్మల్ని సంప్రదించడానికి అత్యవసర ప్రతిస్పందనదారులను అనుమతించడానికి కాల్ బ్లాక్ చేయడం నిలిపివేయబడింది."</string>
+ <string name="developer_title" msgid="1816273446906554627">"టెలికామ్ డెవలపర్ మెను"</string>
</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 6fab62c..39d0a8e 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"คำตอบด่วน"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"ส่งข้อความไปยัง <xliff:g id="PHONE_NUMBER">%s</xliff:g> แล้ว"</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"ส่งข้อความไปยัง <xliff:g id="PHONE_NUMBER">%s</xliff:g> ไม่สำเร็จ"</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"บัญชีการโทร"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"อนุญาตเฉพาะหมายเลขฉุกเฉิน"</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"แอปพลิเคชันนี้ไม่สามารถโทรออกโดยไม่มีสิทธิ์ใช้โทรศัพท์"</string>
@@ -50,7 +51,7 @@
<string name="change_default_dialer_dialog_negative" msgid="9078144617060173845">"ยกเลิก"</string>
<string name="change_default_dialer_warning_message" msgid="1417671460801684999">"<xliff:g id="NEW_APP">%s</xliff:g> จะจัดการการโทรและควบคุมการติดต่อทุกด้าน โปรดติดตั้งเฉพาะแอปที่คุณไว้วางใจให้เป็นแอปโทรศัพท์เริ่มต้น"</string>
<string name="blocked_numbers" msgid="2751843139572970579">"หมายเลขที่ถูกบล็อก"</string>
- <string name="blocked_numbers_msg" msgid="1045015186124965643">"คุณจะไม่สามารถรับสายหรือข้อความจากหมายเลขที่บล็อกได้"</string>
+ <string name="blocked_numbers_msg" msgid="1045015186124965643">"คุณจะไม่สามารถรับสายหรือข้อความจากหมายเลขที่บล็อก"</string>
<string name="block_number" msgid="1101252256321306179">"เพิ่มหมายเลข"</string>
<string name="unblock_dialog_body" msgid="1614238499771862793">"เลิกบล็อก <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> ใช่ไหม"</string>
<string name="unblock_button" msgid="3078048901972674170">"เลิกบล็อก"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"ปิดใช้การบล็อกสาย"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"โทรหมายเลขฉุกเฉินแล้ว"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"ปิดใช้การบล็อกสายแล้วเพื่อให้ทีมฉุกเฉินติดต่อคุณ"</string>
+ <string name="developer_title" msgid="1816273446906554627">"เมนูนักพัฒนาโทรคมนาคม"</string>
</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index d4182da..5992be2 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Mabilisang tugon"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Naipadala ang mensahe sa <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Hindi naipadala ang mensahe sa <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Account sa pagtawag"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Ang mga pang-emergency na tawag lang ang pinapayagan."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Hindi makakapagsagawa ng mga papalabas na tawag ang application na ito nang wala ang pahintulot ng Telepono."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Naka-disable ang Pag-block ng Tawag"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Ginawang emergency na tawag"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Na-disable ang Pag-block ng Tawag para payagan ang mga tumutugon sa emergency na kontakin ka."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Menu ng Telecom Developer"</string>
</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index b5a05f3..95ec0a6 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Hızlı yanıt"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mesaj, <xliff:g id="PHONE_NUMBER">%s</xliff:g> numaralı telefona gönderildi."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> numaralı telefona mesaj gönderilemedi."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Çağrı hesapları"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Yalnızca acil durum çağrılarına izin veriliyor."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Bu uygulama, Telefonun izni olmadan giden çağrılar yapamaz."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Çağrı Engelleme devre dışı bırakıldı"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Acil durum çağrısı yapıldı"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"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="1816273446906554627">"Telekomünikasyon Geliştirici Menüsü"</string>
</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index f17be5c..03ce788 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Швидка відповідь"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Повідомлення надіслано на номер <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Повідомлення не надіслано на номер <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Облікові записи для дзвінків"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Дозволено лише екстрені виклики."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Цей додаток не може телефонувати без відповідного дозволу."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Блокування викликів вимкнено"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Здійснено екстрений виклик"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Блокування викликів вимкнено, щоб ви могли отримувати екстрені сповіщення."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Меню розробника Telecom"</string>
</resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 186bdf1..b25e6d8 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"فوری جواب"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"پیغام <xliff:g id="PHONE_NUMBER">%s</xliff:g> کو بھیج دیا گیا۔"</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> پر پیغام نہیں بھیجا جا سکا۔"</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"کالنگ اکاؤنٹس"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"صرف ہنگامی کالز کی اجازت ہے۔"</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"یہ ایپلی کیشن فون کی اجازت کے بغیر باہر جانے والی کالیں نہیں کر سکتی۔"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"کال مسدود کرنا غیر فعال ہو گیا ہے"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"ہنگامی کال کی گئی"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"ہنگامی حالت میں جواب دہندگان کو آپ سے رابطہ کرنے کی اجازت دینے کیلئے کال مسدود کرنا غیر فعال ہو گیا ہے۔"</string>
+ <string name="developer_title" msgid="1816273446906554627">"ٹیلی کام ڈیولپر مینو"</string>
</resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 16ad56d..db3e791 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Tezkor javob"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Xabar <xliff:g id="PHONE_NUMBER">%s</xliff:g>ga jo‘natildi."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Xabar <xliff:g id="PHONE_NUMBER">%s</xliff:g> raqamiga yuborilmadi."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Chaqiruv uchun hisoblar"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Faqat favqulodda qo‘ng‘iroqlarga ruxsat berilgan."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Bu ilova Telefon ruxsatnomasisiz chiquvchi qo‘ng‘iroqlarni amalga oshira olmaydi."</string>
@@ -68,7 +69,7 @@
<string name="blocked_numbers_number_already_blocked_message" msgid="4392247814500811798">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> raqami allaqachon bloklangan."</string>
<string name="toast_personal_call_msg" msgid="5115361633476779723">"Qo‘ng‘iroq qilish uchun shaxsiy raqam tergichdan foydalanilmoqda"</string>
<string name="notification_incoming_call" msgid="7713197997773986670">"<xliff:g id="CALL_FROM">%2$s</xliff:g> <xliff:g id="CALL_VIA">%1$s</xliff:g> orqali chaqirmoqda"</string>
- <string name="notification_incoming_video_call" msgid="6638486071698373893">"<xliff:g id="CALL_FROM">%2$s</xliff:g> <xliff:g id="CALL_VIA">%1$s</xliff:g> orqali video qo‘ng‘iroq qilmoqda"</string>
+ <string name="notification_incoming_video_call" msgid="6638486071698373893">"<xliff:g id="CALL_FROM">%2$s</xliff:g> <xliff:g id="CALL_VIA">%1$s</xliff:g> orqali video chaqiruv"</string>
<string name="answering_ends_other_call" msgid="8282145910153766401">"Chaqiruvga javob berilsa, <xliff:g id="CALL_VIA">%1$s</xliff:g> qo‘ng‘irog‘i tugatiladi."</string>
<string name="answering_ends_other_calls" msgid="1198589551399049197">"Chaqiruvga javob berilsa, <xliff:g id="CALL_VIA">%1$s</xliff:g> qo‘ng‘iroqlari tugatiladi."</string>
<string name="answering_ends_other_video_call" msgid="8510410917384186360">"Chaqiruvga javob berilsa, <xliff:g id="CALL_VIA">%1$s</xliff:g> video suhbati tugatiladi."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Chaqiruvlarni bloklash funksiyasi yoqilmagan"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Favqulodda chaqiruv qilindi"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Sizga favqulodda chiqiruv qilish imkoni bo‘lishi uchun chaqiruvlarni bloklash funksiyasi o‘chirib qo‘yilgan."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Telecom dasturchisi menyusi"</string>
</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index b0f6560..8be0ca6 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Trả lời nhanh"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Đã gửi tin nhắn tới <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Không gửi được tin nhắn đến <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Tài khoản gọi"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Chỉ được phép thực hiện cuộc gọi khẩn cấp."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ứng dụng này không thể thực hiện cuộc gọi đi mà không có quyền của Điện thoại."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Đã tắt tính năng Chặn cuộc gọi"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Đã 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="4083285098613193052">"Đã 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="1816273446906554627">"Menu nhà phát triển dịch vụ viễn thông"</string>
</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 370b718..5514b3d 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"快速回复"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"讯息已发送至 <xliff:g id="PHONE_NUMBER">%s</xliff:g>。"</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"未能将信息发送到 <xliff:g id="PHONE_NUMBER">%s</xliff:g>。"</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"通话帐号"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"只能拨打紧急呼救电话。"</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"此应用没有电话权限,无法拨出电话。"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"来电屏蔽功能已停用"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"已拨打紧急呼救电话"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"系统已停用来电屏蔽功能,以便急救人员与您联系。"</string>
+ <string name="developer_title" msgid="1816273446906554627">"电信开发者菜单"</string>
</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 1d2dd46..4e48b81 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"快速回應"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"訊息已傳送至 <xliff:g id="PHONE_NUMBER">%s</xliff:g>。"</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"無法將訊息傳送至 <xliff:g id="PHONE_NUMBER">%s</xliff:g>。"</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"通話帳戶"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"只限緊急電話。"</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"此應用程式沒有電話權限,無法撥出電話。"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"已停用來電封鎖功能"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"已撥緊急電話"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"已停用來電封鎖功能,以便救援人員與您聯絡。"</string>
+ <string name="developer_title" msgid="1816273446906554627">"電信開發商選單"</string>
</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 70eb3b2..3fd47de 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -33,10 +33,11 @@
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"我晚點回電。"</string>
<string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"我現在不方便講話,晚點再打來好嗎?"</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"應答短訊"</string>
- <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"編輯應答短訊"</string>
+ <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"編輯快速回應"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"快速回應"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"訊息已傳送至 <xliff:g id="PHONE_NUMBER">%s</xliff:g>。"</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"無法將訊息傳送到 <xliff:g id="PHONE_NUMBER">%s</xliff:g>。"</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"通話帳戶"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"只能撥打緊急電話。"</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"這個應用程式不具備電話應用程式存取權限,無法撥出電話。"</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"已停用來電封鎖"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"已撥打緊急電話"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"系統已停用來電封鎖功能,以便緊急應變人員與你聯絡。"</string>
+ <string name="developer_title" msgid="1816273446906554627">"電信開發人員選單"</string>
</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 79e5846..0b34857 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -37,6 +37,7 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Izimpendulo ezisheshayo"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Umlayezo othunyelwe ku <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+ <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Umlayezo uhlulekile ukuthunyelwa ku-<xliff:g id="PHONE_NUMBER">%s</xliff:g>"</string>
<string name="enable_account_preference_title" msgid="2021848090086481720">"Ama-akhawunti wokushaya"</string>
<string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Amakholi aphuthumayo kuphela avunyelwe."</string>
<string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Lolu hlelo lokusebenza alikwazi ukwenza amakhli aphumayo ngaphandle kwemvume yefoni."</string>
@@ -97,4 +98,5 @@
<string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Ukuvimbela ikholi kukhutshaziwe"</string>
<string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Ikholi ephuthumayo yenziwe"</string>
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Ukuvimbela ikholi kukhutshaziwe ukuze kuvunyelwe abaphenduli besimo esiphuthumayo ukuthi baxhumane nawe."</string>
+ <string name="developer_title" msgid="1816273446906554627">"Imenyu yonjiniyela we-Telecom"</string>
</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 8c84688..c0e9669 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -60,4 +60,13 @@
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>
+
+ <!-- Lower threshold for the Y-component of gravity needed for the device orientation to be
+ classified as being on a user's ear. If the Y-component is less than this negative value,
+ the device is probably upside-down and therefore not on a ear -->
+ <item name="device_on_ear_y_gravity_negative_threshold" format="float" type="dimen">-1</item>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index bb63ad2..eff68a0 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -79,6 +79,10 @@
a text response. [CHAR LIMIT=40] -->
<string name="respond_via_sms_confirmation_format">Message sent to <xliff:g id="phone_number">%s</xliff:g>.</string>
+ <!-- "Respond via SMS": Error toast shown after failing to send
+ a text response. [CHAR LIMIT=40] -->
+ <string name="respond_via_sms_failure_format">Message failed to send to <xliff:g id="phone_number">%s</xliff:g>.</string>
+
<!-- Title of settings screen that allows user to enable and disable phone-accounts.
Each method for placing a call (SIM1, SIM2, SIP account, etc) has a phone-account.
Phone-accounts that are created by third party apps can be disabled and enabled by user.
@@ -107,7 +111,7 @@
<!-- Button label on the "Missing voicemail number" dialog -->
<string name="add_vm_number_str">Add number</string>
- <!-- Title of dialog used to comfirm whether the user intends to change the default dialer
+ <!-- Title of dialog used to confirm whether the user intends to change the default dialer
application [CHAR LIMIT=55]-->
<string name="change_default_dialer_dialog_title">Make <xliff:g id="new_app">%s</xliff:g> your default Phone app?</string>
<!-- Confirmation text that a user taps on to change the Default Phone App-->
@@ -117,6 +121,18 @@
<!-- Warning message indicating what may happen if a user allows a 3rd party app to become the default dialer.-->
<string name="change_default_dialer_warning_message"><xliff:g id="new_app">%s</xliff:g> will be able to place and control all aspects of calls. Only apps you trust should be set as the default Phone app.</string>
+ <!-- Title of dialog used to confirm whether the user intends to change the default call screening
+ application [CHAR LIMIT=55]-->
+ <string name="change_default_call_screening_dialog_title">Make <xliff:g id="new_app">%s</xliff:g> your default call screening app?</string>
+ <!-- Warning message indicating the old call screening app will no longer be able to screen calls.-->
+ <string name="change_default_call_screening_warning_message_for_disable_old_app"><xliff:g id="old_app">%s</xliff:g> will no longer be able to screen calls.</string>
+ <!-- Warning message indicating what may happen if a user allows a 3rd party app to become the default call screening.-->
+ <string name="change_default_call_screening_warning_message"><xliff:g id="new_app">%s</xliff:g> will be able to see information about callers not in your contacts and will be able to block these calls. Only apps you trust should be set as the default call screening app.</string>
+ <!-- Confirmation text that a user taps on to change the Default call screening App-->
+ <string name="change_default_call_screening_dialog_affirmative">Set Default</string>
+ <!-- Cancel text that a user taps on to not change the Default call screening App-->
+ <string name="change_default_call_screening_dialog_negative">Cancel</string>
+
<!-- Blocked numbers -->
<string name="blocked_numbers">Blocked numbers</string>
<!-- Text shown at the beginning of the blocked numbers screen to explain what the screen is about. -->
@@ -285,4 +301,10 @@
<string name="phone_strings_emergency_call_made_dialog_title_txt">Emergency call made</string>
<!-- Notification details that appear when the user taps the notification "phone_strings_call_blocking_turned_off_notification_text_txt". -->
<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>
+ <!-- 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 -->
+ <string name="developer_enhanced_call_blocking" translatable="false">Enhanced Call Blocking</string>
</resources>
diff --git a/scripts/telecom_testing.sh b/scripts/telecom_testing.sh
index 779f449..17309d6 100644
--- a/scripts/telecom_testing.sh
+++ b/scripts/telecom_testing.sh
@@ -121,7 +121,7 @@
if [ $coverage = true ] && [ $project =~ "telecom" ] ; then
e_options="${e_options} -e coverage 'true'"
fi
- adb shell am instrument ${e_options} -w "$package_prefix/$instrumentation"
+ adb shell am instrument --no-hidden-api-checks ${e_options} -w "$package_prefix/$instrumentation"
# Code coverage only enabled for Telecom.
if [ $coverage = true ] && [ $project =~ "telecom" ] ; then
diff --git a/src/com/android/server/telecom/Analytics.java b/src/com/android/server/telecom/Analytics.java
index 1c9ac35..61f7a30 100644
--- a/src/com/android/server/telecom/Analytics.java
+++ b/src/com/android/server/telecom/Analytics.java
@@ -143,6 +143,9 @@
ParcelableCallAnalytics.EventTiming.FILTERING_COMPLETED_TIMING);
put(LogUtils.Events.Timings.FILTERING_TIMED_OUT_TIMING,
ParcelableCallAnalytics.EventTiming.FILTERING_TIMED_OUT_TIMING);
+ put(LogUtils.Events.Timings.START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING,
+ ParcelableCallAnalytics.EventTiming.
+ START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING);
}};
public static final Map<Integer, String> sSessionIdToLogSession = new HashMap<>();
@@ -162,6 +165,9 @@
public void setCallIsAdditional(boolean isAdditional) {
}
+ public void setCallIsEmergency(boolean isEmergency) {
+ }
+
public void setCallIsInterrupted(boolean isInterrupted) {
}
@@ -191,6 +197,9 @@
public void addCallProperties(int properties) {
}
+
+ public void setCallSource(int callSource) {
+ }
}
/**
@@ -224,6 +233,7 @@
public List<TelecomLogClass.VideoEvent> videoEvents;
public List<TelecomLogClass.InCallServiceInfo> inCallServiceInfos;
public int callProperties = 0;
+ public int callSource = CALL_SOURCE_UNSPECIFIED;
private long mTimeOfLastVideoEvent = -1;
@@ -253,6 +263,7 @@
this.isVideo = other.isVideo;
this.videoEvents = other.videoEvents;
this.callProperties = other.callProperties;
+ this.callSource = other.callSource;
if (other.callTerminationReason != null) {
this.callTerminationReason = new DisconnectCause(
@@ -297,6 +308,12 @@
}
@Override
+ public void setCallIsEmergency(boolean isEmergency) {
+ Log.d(TAG, "setting call as emergency: " + isEmergency);
+ this.isEmergency = isEmergency;
+ }
+
+ @Override
public void setCallDisconnectCause(DisconnectCause disconnectCause) {
Log.d(TAG, "setting disconnectCause for call " + callId + " to " + disconnectCause);
this.callTerminationReason = disconnectCause;
@@ -356,6 +373,11 @@
}
@Override
+ public void setCallSource(int callSource) {
+ this.callSource = callSource;
+ }
+
+ @Override
public String toString() {
return "{\n"
+ " startTime: " + startTime + '\n'
@@ -363,6 +385,7 @@
+ " direction: " + getCallDirectionString() + '\n'
+ " isAdditionalCall: " + isAdditionalCall + '\n'
+ " isInterrupted: " + isInterrupted + '\n'
+ + " isEmergency: " + isEmergency + '\n'
+ " callTechnologies: " + getCallTechnologiesAsString() + '\n'
+ " callTerminationReason: " + getCallDisconnectReasonString() + '\n'
+ " connectionService: " + connectionService + '\n'
@@ -370,6 +393,7 @@
+ " inCallServices: " + getInCallServicesString() + '\n'
+ " callProperties: " + Connection.propertiesToStringShort(callProperties)
+ '\n'
+ + " callSource: " + getCallSourceString() + '\n'
+ "}\n";
}
@@ -412,6 +436,8 @@
videoEventProto.getVideoState())
).collect(Collectors.toList()));
+ result.setCallSource(analyticsProto.getCallSource());
+
return result;
}
@@ -438,7 +464,8 @@
.setIsCreatedFromExistingConnection(createdFromExistingConnection)
.setIsEmergencyCall(isEmergency)
.setIsVideoCall(isVideo)
- .setConnectionProperties(callProperties);
+ .setConnectionProperties(callProperties)
+ .setCallSource(callSource);
result.connectionService = new String[] {connectionService};
if (callEvents != null) {
@@ -502,6 +529,19 @@
s.append("]");
return s.toString();
}
+
+ private String getCallSourceString() {
+ switch (callSource) {
+ case CALL_SOURCE_UNSPECIFIED:
+ return "UNSPECIFIED";
+ case CALL_SOURCE_EMERGENCY_DIALPAD:
+ return "EMERGENCY_DIALPAD";
+ case CALL_SOURCE_EMERGENCY_SHORTCUT:
+ return "EMERGENCY_SHORTCUT";
+ default:
+ return "UNSPECIFIED";
+ }
+ }
}
public static final String TAG = "TelecomAnalytics";
@@ -517,6 +557,14 @@
public static final int SIP_PHONE = ParcelableCallAnalytics.SIP_PHONE;
public static final int THIRD_PARTY_PHONE = ParcelableCallAnalytics.THIRD_PARTY_PHONE;
+ // Constants for call source
+ public static final int CALL_SOURCE_UNSPECIFIED =
+ ParcelableCallAnalytics.CALL_SOURCE_UNSPECIFIED;
+ public static final int CALL_SOURCE_EMERGENCY_DIALPAD =
+ ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_DIALPAD;
+ public static final int CALL_SOURCE_EMERGENCY_SHORTCUT =
+ ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_SHORTCUT;
+
// Constants for video events
public static final int SEND_LOCAL_SESSION_MODIFY_REQUEST =
ParcelableCallAnalytics.VideoEvent.SEND_LOCAL_SESSION_MODIFY_REQUEST;
diff --git a/src/com/android/server/telecom/BluetoothHeadsetProxy.java b/src/com/android/server/telecom/BluetoothHeadsetProxy.java
index 0f492df..a43b3cd 100644
--- a/src/com/android/server/telecom/BluetoothHeadsetProxy.java
+++ b/src/com/android/server/telecom/BluetoothHeadsetProxy.java
@@ -43,9 +43,10 @@
}
public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
- int type) {
+ int type, String name) {
- mBluetoothHeadset.phoneStateChanged(numActive, numHeld, callState, number, type);
+ mBluetoothHeadset.phoneStateChanged(numActive, numHeld, callState, number, type,
+ name);
}
public List<BluetoothDevice> getConnectedDevices() {
@@ -83,4 +84,4 @@
public boolean isInbandRingingEnabled() {
return mBluetoothHeadset.isInbandRingingEnabled();
}
-}
\ No newline at end of file
+}
diff --git a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
index 804909c..ff358d5 100644
--- a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
+++ b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
@@ -514,8 +514,6 @@
mCallsManager.disconnectCall(activeCall);
if (ringingCall != null) {
mCallsManager.answerCall(ringingCall, VideoProfile.STATE_AUDIO_ONLY);
- } else if (heldCall != null) {
- mCallsManager.unholdCall(heldCall);
}
return true;
}
@@ -708,11 +706,16 @@
String ringingAddress = null;
int ringingAddressType = 128;
+ String ringingName = null;
if (ringingCall != null && ringingCall.getHandle() != null) {
ringingAddress = ringingCall.getHandle().getSchemeSpecificPart();
if (ringingAddress != null) {
ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress);
}
+ ringingName = ringingCall.getCallerDisplayName();
+ if (TextUtils.isEmpty(ringingName)) {
+ ringingName = ringingCall.getName();
+ }
}
if (ringingAddress == null) {
ringingAddress = "";
@@ -784,18 +787,21 @@
"numHeld %s, " +
"callState %s, " +
"ringing number %s, " +
- "ringing type %s",
+ "ringing type %s, " +
+ "ringing name %s",
mNumActiveCalls,
mNumHeldCalls,
CALL_STATE_DIALING,
Log.pii(mRingingAddress),
- mRingingAddressType);
+ mRingingAddressType,
+ Log.pii(ringingName));
mBluetoothHeadset.phoneStateChanged(
mNumActiveCalls,
mNumHeldCalls,
CALL_STATE_DIALING,
mRingingAddress,
- mRingingAddressType);
+ mRingingAddressType,
+ ringingName);
}
Log.i(TAG, "updateHeadsetWithCallState " +
@@ -803,19 +809,22 @@
"numHeld %s, " +
"callState %s, " +
"ringing number %s, " +
- "ringing type %s",
+ "ringing type %s, " +
+ "ringing name %s",
mNumActiveCalls,
mNumHeldCalls,
mBluetoothCallState,
Log.pii(mRingingAddress),
- mRingingAddressType);
+ mRingingAddressType,
+ Log.pii(ringingName));
mBluetoothHeadset.phoneStateChanged(
mNumActiveCalls,
mNumHeldCalls,
mBluetoothCallState,
mRingingAddress,
- mRingingAddressType);
+ mRingingAddressType,
+ ringingName);
mHeadsetUpdatedRecently = true;
}
@@ -875,6 +884,7 @@
return CALL_STATE_HELD;
case CallState.RINGING:
+ case CallState.ANSWERED:
if (isForegroundCall) {
return CALL_STATE_INCOMING;
} else {
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index bc20d1c..6784d2b 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -50,6 +50,7 @@
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;
@@ -414,7 +415,7 @@
private final TelecomSystem.SyncRoot mLock;
private final String mId;
private String mConnectionId;
- private Analytics.CallInfo mAnalytics;
+ private Analytics.CallInfo mAnalytics = new Analytics.CallInfo();
private char mPlayingDtmfTone;
private boolean mWasConferencePreviouslyMerged = false;
@@ -461,7 +462,7 @@
* Indicates whether the {@link PhoneAccount} associated with this call supports video calling.
* {@code True} if the phone account supports video calling, {@code false} otherwise.
*/
- private boolean mIsVideoCallingSupported = false;
+ private boolean mIsVideoCallingSupportedByPhoneAccount = false;
private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
@@ -523,18 +524,24 @@
private int mHandoverState = HandoverState.HANDOVER_NONE;
/**
+ * Indicates whether this call is using one of the
+ * {@link com.android.server.telecom.callfiltering.IncomingCallFilter.CallFilter} modules.
+ */
+ private boolean mIsUsingCallFiltering = false;
+
+ /**
* Persists the specified parameters and initializes the new instance.
- * @param context The context.
+ * @param context The context.
* @param repository The connection service repository.
* @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
* @param clockProxy
*/
@@ -544,8 +551,6 @@
CallsManager callsManager,
TelecomSystem.SyncRoot lock,
ConnectionServiceRepository repository,
- ContactsAsyncHelper contactsAsyncHelper,
- CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
Uri handle,
GatewayInfo gatewayInfo,
@@ -574,14 +579,13 @@
mShouldAttachToExistingConnection = shouldAttachToExistingConnection
|| callDirection == CALL_DIRECTION_INCOMING;
maybeLoadCannedSmsResponses();
- mAnalytics = new Analytics.CallInfo();
mClockProxy = clockProxy;
mCreationTimeMillis = mClockProxy.currentTimeMillis();
}
/**
* Persists the specified parameters and initializes the new instance.
- * @param context The context.
+ * @param context The context.
* @param repository The connection service repository.
* @param handle The handle to dial.
* @param gatewayInfo Gateway information to use for the call.
@@ -603,8 +607,6 @@
CallsManager callsManager,
TelecomSystem.SyncRoot lock,
ConnectionServiceRepository repository,
- ContactsAsyncHelper contactsAsyncHelper,
- CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
Uri handle,
GatewayInfo gatewayInfo,
@@ -616,8 +618,8 @@
long connectTimeMillis,
long connectElapsedTimeMillis,
ClockProxy clockProxy) {
- this(callId, context, callsManager, lock, repository, contactsAsyncHelper,
- callerInfoAsyncQueryFactory, phoneNumberUtilsAdapter, handle, gatewayInfo,
+ this(callId, context, callsManager, lock, repository,
+ phoneNumberUtilsAdapter, handle, gatewayInfo,
connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, callDirection,
shouldAttachToExistingConnection, isConference, clockProxy);
@@ -637,6 +639,10 @@
}
public void initAnalytics() {
+ initAnalytics(null);
+ }
+
+ public void initAnalytics(String callingPackage) {
int analyticsDirection;
switch (mCallDirection) {
case CALL_DIRECTION_OUTGOING:
@@ -651,7 +657,8 @@
analyticsDirection = Analytics.UNKNOWN_DIRECTION;
}
mAnalytics = Analytics.initiateCallAnalytics(mId, analyticsDirection);
- Log.addEvent(this, LogUtils.Events.CREATED);
+ mAnalytics.setCallIsEmergency(mIsEmergencyCall);
+ Log.addEvent(this, LogUtils.Events.CREATED, callingPackage);
}
public Analytics.CallInfo getAnalytics() {
@@ -869,15 +876,17 @@
* (see {@link CallState}), in practice those expectations break down when cellular systems
* misbehave and they do this very often. The result is that we do not enforce state transitions
* and instead keep the code resilient to unexpected state changes.
+ * @return true indicates if setState succeeded in setting the state to newState,
+ * else it is failed, and the call is still in its original state.
*/
- public void setState(int newState, String tag) {
+ public boolean setState(int newState, String tag) {
if (mState != newState) {
Log.v(this, "setState %s -> %s", mState, newState);
if (newState == CallState.DISCONNECTED && shouldContinueProcessingAfterDisconnect()) {
Log.w(this, "continuing processing disconnected call with another service");
mCreateConnectionProcessor.continueProcessingIfPossible(this, mDisconnectCause);
- return;
+ return false;
}
updateVideoHistoryViaState(mState, newState);
@@ -938,6 +947,9 @@
case CallState.RINGING:
event = LogUtils.Events.SET_RINGING;
break;
+ case CallState.ANSWERED:
+ event = LogUtils.Events.SET_ANSWERED;
+ break;
}
if (event != null) {
// The string data should be just the tag.
@@ -953,6 +965,7 @@
StatsLog.write(StatsLog.CALL_STATE_CHANGED, newState, statsdDisconnectCause,
isSelfManaged(), isExternalCall());
}
+ return true;
}
void setRingbackRequested(boolean ringbackRequested) {
@@ -1021,6 +1034,7 @@
mIsEmergencyCall = mHandle != null &&
mPhoneNumberUtilsAdapter.isLocalEmergencyNumber(mContext,
mHandle.getSchemeSpecificPart());
+ mAnalytics.setCallIsEmergency(mIsEmergencyCall);
}
startCallerInfoLookup();
for (Listener l : mListeners) {
@@ -1079,12 +1093,23 @@
return mDisconnectCause;
}
+ /**
+ * @return {@code true} if this is an outgoing call to emergency services. An outgoing call is
+ * identified as an emergency call by the dialer phone number.
+ */
@VisibleForTesting
public boolean isEmergencyCall() {
return mIsEmergencyCall;
}
/**
+ * @return {@code true} if the network has identified this call as an emergency call.
+ */
+ public boolean isNetworkIdentifiedEmergencyCall() {
+ return hasProperty(Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL);
+ }
+
+ /**
* @return The original handle this call is associated with. In-call services should use this
* handle when indicating in their UI the handle that is being called.
*/
@@ -1205,8 +1230,19 @@
return mUseCallRecordingTone;
}
- public boolean isVideoCallingSupported() {
- return mIsVideoCallingSupported;
+ /**
+ * @return {@code true} if the {@link Call}'s {@link #getTargetPhoneAccount()} supports video.
+ */
+ public boolean isVideoCallingSupportedByPhoneAccount() {
+ return mIsVideoCallingSupportedByPhoneAccount;
+ }
+
+ /**
+ * @return {@code true} if the {@link Call} locally supports video.
+ */
+ public boolean isLocallyVideoCapable() {
+ return (getConnectionCapabilities() & Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL)
+ == Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL;
}
public boolean isSelfManaged() {
@@ -1308,16 +1344,16 @@
if (mTargetPhoneAccountHandle == null) {
// If no target phone account handle is specified, assume we can potentially perform a
// video call; once the phone account is set, we can confirm that it is video capable.
- mIsVideoCallingSupported = true;
+ mIsVideoCallingSupportedByPhoneAccount = true;
Log.d(this, "checkIfVideoCapable: no phone account selected; assume video capable.");
return;
}
PhoneAccount phoneAccount =
phoneAccountRegistrar.getPhoneAccountUnchecked(mTargetPhoneAccountHandle);
- mIsVideoCallingSupported = phoneAccount != null && phoneAccount.hasCapabilities(
+ mIsVideoCallingSupportedByPhoneAccount = phoneAccount != null && phoneAccount.hasCapabilities(
PhoneAccount.CAPABILITY_VIDEO_CALLING);
- if (!mIsVideoCallingSupported && VideoProfile.isVideo(getVideoState())) {
+ if (!mIsVideoCallingSupportedByPhoneAccount && VideoProfile.isVideo(getVideoState())) {
// The PhoneAccount for the Call was set to one which does not support video calling,
// and the current call is configured to be a video call; downgrade to audio-only.
setVideoState(VideoProfile.STATE_AUDIO_ONLY);
@@ -1398,6 +1434,10 @@
return mConnectTimeMillis;
}
+ public void setConnectTimeMillis(long connectTimeMillis) {
+ mConnectTimeMillis = connectTimeMillis;
+ }
+
int getConnectionCapabilities() {
return mConnectionCapabilities;
}
@@ -1416,7 +1456,8 @@
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 (!isVideoCallingSupported() && doesCallSupportVideo(connectionCapabilities)) {
+ 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);
@@ -1437,7 +1478,7 @@
}
}
- void setConnectionProperties(int connectionProperties) {
+ public void setConnectionProperties(int connectionProperties) {
Log.v(this, "setConnectionProperties: %s", Connection.propertiesToString(
connectionProperties));
@@ -1852,7 +1893,7 @@
// Check to verify that the call is still in the ringing state. A call can change states
// between the time the user hits 'answer' and Telecom receives the command.
if (isRinging("answer")) {
- if (!isVideoCallingSupported() && VideoProfile.isVideo(videoState)) {
+ if (!isVideoCallingSupportedByPhoneAccount() && VideoProfile.isVideo(videoState)) {
// Video calling is not supported, yet the InCallService is attempting to answer as
// video. We will simply answer as audio-only.
videoState = VideoProfile.STATE_AUDIO_ONLY;
@@ -1979,6 +2020,7 @@
switch (mState) {
case CallState.NEW:
case CallState.RINGING:
+ case CallState.ANSWERED:
case CallState.DISCONNECTED:
case CallState.ABORTED:
return false;
@@ -2231,10 +2273,20 @@
public void sendCallEvent(String event, int targetSdkVer, Bundle extras) {
if (mConnectionService != null) {
if (android.telecom.Call.EVENT_REQUEST_HANDOVER.equals(event)) {
- if (targetSdkVer > Build.VERSION_CODES.O_MR1) {
+ if (targetSdkVer > Build.VERSION_CODES.P) {
Log.e(this, new Exception(), "sendCallEvent failed. Use public api handoverTo" +
- " for API > 27(O-MR1)");
- // TODO: Add "return" after DUO team adds new API support for handover
+ " for API > 28(P)");
+ // Event-based Handover APIs are deprecated, so inform the user.
+ 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.",
+ Toast.LENGTH_LONG).show();
+ }
+ });
+
+ // Uncomment and remove toast at feature complete: return;
}
// Handover requests are targeted at Telecom, not the ConnectionService.
@@ -2755,7 +2807,7 @@
public void setVideoState(int videoState) {
// If the phone account associated with this call does not support video calling, then we
// will automatically set the video state to audio-only.
- if (!isVideoCallingSupported()) {
+ if (!isVideoCallingSupportedByPhoneAccount()) {
Log.d(this, "setVideoState: videoState=%s defaulted to audio (video not supported)",
VideoProfile.videoStateToString(videoState));
videoState = VideoProfile.STATE_AUDIO_ONLY;
@@ -3048,13 +3100,13 @@
/**
* 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->ACTIVE. In these cases, clear the history. If a
+ * transitions DIALING->ACTIVE and RINGING->ANSWERED. In these cases, clear the history. If a
* call starts dialing/ringing as a VT call and gets downgraded to audio, we need to record
* the history as an audio call.
*/
private void updateVideoHistoryViaState(int oldState, int newState) {
- if ((oldState == CallState.DIALING || oldState == CallState.RINGING)
- && newState == CallState.ACTIVE) {
+ if ((oldState == CallState.DIALING && newState == CallState.ACTIVE)
+ || (oldState == CallState.RINGING && newState == CallState.ANSWERED)) {
mVideoStateHistory = mVideoState;
}
@@ -3069,4 +3121,8 @@
boolean wasHighDefAudio() {
return mWasHighDefAudio;
}
+
+ public void setIsUsingCallFiltering(boolean isUsingCallFiltering) {
+ mIsUsingCallFiltering = isUsingCallFiltering;
+ }
}
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 56f8db9..b68a851 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -105,11 +105,10 @@
Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(),
CallState.toString(oldState), CallState.toString(newState));
- for (int i = 0; i < mCallStateToCalls.size(); i++) {
- mCallStateToCalls.valueAt(i).remove(call);
- }
- if (mCallStateToCalls.get(newState) != null) {
- mCallStateToCalls.get(newState).add(call);
+ removeCallFromAllBins(call);
+ HashSet<Call> newBinForCall = getBinForCall(call);
+ if (newBinForCall != null) {
+ newBinForCall.add(call);
}
updateForegroundCall();
@@ -148,8 +147,9 @@
Log.d(LOG_TAG, "Call added with id TC@%s in state %s", call.getId(),
CallState.toString(call.getState()));
- if (mCallStateToCalls.get(call.getState()) != null) {
- mCallStateToCalls.get(call.getState()).add(call);
+ HashSet<Call> newBinForCall = getBinForCall(call);
+ if (newBinForCall != null) {
+ newBinForCall.add(call);
}
updateForegroundCall();
mCalls.add(call);
@@ -168,9 +168,7 @@
Log.d(LOG_TAG, "Call removed with id TC@%s in state %s", call.getId(),
CallState.toString(call.getState()));
- for (int i = 0; i < mCallStateToCalls.size(); i++) {
- mCallStateToCalls.valueAt(i).remove(call);
- }
+ removeCallFromAllBins(call);
updateForegroundCall();
mCalls.remove(call);
@@ -226,24 +224,6 @@
return;
}
- // This is called after the UI answers the call, but before the connection service
- // sets the call to active. Only thing to handle for mode here is the audio speedup thing.
-
- if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) {
- if (mForegroundCall == call) {
- Log.i(LOG_TAG, "Invoking the MT_AUDIO_SPEEDUP mechanism. Transitioning into " +
- "an active in-call audio state before connection service has " +
- "connected the call.");
- if (mCallStateToCalls.get(call.getState()) != null) {
- mCallStateToCalls.get(call.getState()).remove(call);
- }
- mActiveDialingOrConnectingCalls.add(call);
- mCallAudioModeStateMachine.sendMessageWithArgs(
- CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL,
- makeArgsForModeStateMachine());
- }
- }
-
// Turn off mute when a new incoming call is answered iff it's not a handover.
if (!call.isHandoverInProgress()) {
mute(false /* shouldMute */);
@@ -325,10 +305,7 @@
onCallAdded(call);
} else {
// The call joined a conference, so stop tracking it.
- if (mCallStateToCalls.get(call.getState()) != null) {
- mCallStateToCalls.get(call.getState()).remove(call);
- }
-
+ removeCallFromAllBins(call);
updateForegroundCall();
mCalls.remove(call);
}
@@ -469,9 +446,9 @@
}
@VisibleForTesting
- public void startCallWaiting() {
+ public void startCallWaiting(String reason) {
if (mRingingCalls.size() == 1) {
- mRinger.startCallWaiting(mRingingCalls.iterator().next());
+ mRinger.startCallWaiting(mRingingCalls.iterator().next(), reason);
}
}
@@ -591,6 +568,11 @@
onCallEnteringActiveDialingOrConnecting();
playRingbackForCall(call);
break;
+ case CallState.ANSWERED:
+ if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) {
+ onCallEnteringActiveDialingOrConnecting();
+ }
+ break;
}
}
@@ -681,6 +663,24 @@
Log.createSubsession());
}
+ private HashSet<Call> getBinForCall(Call call) {
+ if (call.getState() == CallState.ANSWERED) {
+ // If the call has the speed-up-mt-audio capability, treat answered state as active
+ // for audio purposes.
+ if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) {
+ return mActiveDialingOrConnectingCalls;
+ }
+ return mRingingCalls;
+ }
+ return mCallStateToCalls.get(call.getState());
+ }
+
+ private void removeCallFromAllBins(Call call) {
+ for (int i = 0; i < mCallStateToCalls.size(); i++) {
+ mCallStateToCalls.valueAt(i).remove(call);
+ }
+ }
+
private void playToneForDisconnectedCall(Call call) {
// If this call is being disconnected as a result of being handed over to another call,
// we will not play a disconnect tone.
@@ -727,9 +727,11 @@
Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
- mPlayerFactory.createPlayer(toneToPlay).startTone();
- mCallsManager.onDisconnectedTonePlaying(true);
- mIsDisconnectedTonePlaying = true;
+ boolean didToneStart = mPlayerFactory.createPlayer(toneToPlay).startTone();
+ if (didToneStart) {
+ mCallsManager.onDisconnectedTonePlaying(true);
+ mIsDisconnectedTonePlaying = true;
+ }
}
}
}
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 716f23a..42a1d36 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -29,6 +29,13 @@
import com.android.internal.util.StateMachine;
public class CallAudioModeStateMachine extends StateMachine {
+ public static class Factory {
+ public CallAudioModeStateMachine create(SystemStateHelper systemStateHelper,
+ AudioManager am) {
+ return new CallAudioModeStateMachine(systemStateHelper, am);
+ }
+ }
+
public static class MessageArgs {
public boolean hasActiveOrDialingCalls;
public boolean hasRingingCalls;
@@ -80,7 +87,6 @@
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 MT_AUDIO_SPEEDUP_FOR_RINGING_CALL = 2004;
public static final int TONE_STARTED_PLAYING = 3001;
public static final int TONE_STOPPED_PLAYING = 3002;
@@ -103,7 +109,6 @@
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(MT_AUDIO_SPEEDUP_FOR_RINGING_CALL, "MT_AUDIO_SPEEDUP_FOR_RINGING_CALL");
put(TONE_STARTED_PLAYING, "TONE_STARTED_PLAYING");
put(TONE_STOPPED_PLAYING, "TONE_STOPPED_PLAYING");
put(FOREGROUND_VOIP_MODE_CHANGE, "FOREGROUND_VOIP_MODE_CHANGE");
@@ -273,15 +278,6 @@
" Args are: " + args.toString());
transitionTo(mOtherFocusState);
return HANDLED;
- case MT_AUDIO_SPEEDUP_FOR_RINGING_CALL:
- // This happens when an IMS call is answered by the in-call UI. Special case
- // that we have to deal with for some reason.
-
- // The IMS audio routing may be via modem or via RTP stream. In case via RTP
- // stream, the state machine should transit to mVoipCallFocusState.
- transitionTo(args.foregroundCallIsVoip
- ? mVoipCallFocusState : mSimCallFocusState);
- return HANDLED;
case RINGER_MODE_CHANGE: {
Log.i(LOG_TAG, "RINGING state, received RINGER_MODE_CHANGE");
tryStartRinging();
@@ -338,7 +334,7 @@
return HANDLED;
case NEW_RINGING_CALL:
// Don't make a call ring over an active call, but do play a call waiting tone.
- mCallAudioManager.startCallWaiting();
+ mCallAudioManager.startCallWaiting("call already active");
return HANDLED;
case NEW_HOLDING_CALL:
// Don't do anything now. Putting an active call on hold will be handled when
@@ -393,7 +389,7 @@
return HANDLED;
case NEW_RINGING_CALL:
// Don't make a call ring over an active call, but do play a call waiting tone.
- mCallAudioManager.startCallWaiting();
+ mCallAudioManager.startCallWaiting("call already active");
return HANDLED;
case NEW_HOLDING_CALL:
// Don't do anything now. Putting an active call on hold will be handled when
@@ -447,8 +443,14 @@
? mVoipCallFocusState : mSimCallFocusState);
return HANDLED;
case NEW_RINGING_CALL:
- // Apparently this is current behavior. Should this be the case?
- transitionTo(mRingingFocusState);
+ // TODO: consider whether to move this into MessageArgs if more things start
+ // to use it.
+ if (args.hasHoldingCalls && mSystemStateHelper.isDeviceAtEar()) {
+ mCallAudioManager.startCallWaiting(
+ "Device is at ear with held call");
+ } else {
+ transitionTo(mRingingFocusState);
+ }
return HANDLED;
case NEW_HOLDING_CALL:
// Do nothing.
@@ -475,14 +477,17 @@
private final BaseState mOtherFocusState = new OtherFocusState();
private final AudioManager mAudioManager;
+ private final SystemStateHelper mSystemStateHelper;
private CallAudioManager mCallAudioManager;
private int mMostRecentMode;
private boolean mIsInitialized = false;
- public CallAudioModeStateMachine(AudioManager audioManager) {
+ public CallAudioModeStateMachine(SystemStateHelper systemStateHelper,
+ AudioManager audioManager) {
super(CallAudioModeStateMachine.class.getSimpleName());
mAudioManager = audioManager;
+ mSystemStateHelper = systemStateHelper;
mMostRecentMode = AudioManager.MODE_NORMAL;
addState(mUnfocusedState);
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index 4274017..e77e3e1 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -71,6 +71,24 @@
*/
public class CallAudioRouteStateMachine extends StateMachine {
+ public static class Factory {
+ public CallAudioRouteStateMachine create(
+ Context context,
+ CallsManager callsManager,
+ BluetoothRouteManager bluetoothManager,
+ WiredHeadsetManager wiredHeadsetManager,
+ StatusBarNotifier statusBarNotifier,
+ CallAudioManager.AudioServiceFactory audioServiceFactory,
+ int earpieceControl) {
+ return new CallAudioRouteStateMachine(context,
+ callsManager,
+ bluetoothManager,
+ wiredHeadsetManager,
+ statusBarNotifier,
+ audioServiceFactory,
+ earpieceControl);
+ }
+ }
/** Values for CallAudioRouteStateMachine constructor's earPieceRouting arg. */
public static final int EARPIECE_FORCE_DISABLED = 0;
public static final int EARPIECE_FORCE_ENABLED = 1;
@@ -290,6 +308,12 @@
mHasUserExplicitlyLeftBluetooth = false;
return NOT_HANDLED;
case SWITCH_FOCUS:
+ // Perform BT hearing aid active device caching/restoration
+ if (mAudioFocusType != NO_FOCUS && msg.arg1 == NO_FOCUS) {
+ mBluetoothRouteManager.restoreHearingAidDevice();
+ } else if (mAudioFocusType == NO_FOCUS && msg.arg1 != NO_FOCUS) {
+ mBluetoothRouteManager.cacheHearingAidDevice();
+ }
mAudioFocusType = msg.arg1;
return NOT_HANDLED;
default:
@@ -788,7 +812,8 @@
return HANDLED;
case SWITCH_FOCUS:
if (msg.arg1 == NO_FOCUS) {
- setBluetoothOff();
+ // Only disconnect SCO audio here instead of routing away from BT entirely.
+ mBluetoothRouteManager.disconnectSco();
reinitialize();
} else if (msg.arg1 == RINGING_FOCUS
&& !mBluetoothRouteManager.isInbandRingingEnabled()) {
@@ -1250,7 +1275,7 @@
*/
private int mDeviceSupportedRoutes;
private int mAvailableRoutes;
- private int mAudioFocusType;
+ private int mAudioFocusType = NO_FOCUS;
private boolean mWasOnSpeaker;
private boolean mIsMuted;
@@ -1606,7 +1631,8 @@
int supportedRouteMask = calculateSupportedRoutes() & getCurrentCallSupportedRoutes();
final int route;
- if ((supportedRouteMask & ROUTE_BLUETOOTH) != 0) {
+ if ((supportedRouteMask & ROUTE_BLUETOOTH) != 0
+ && mBluetoothRouteManager.hasBtActiveDevice()) {
route = ROUTE_BLUETOOTH;
} else if ((supportedRouteMask & ROUTE_WIRED_HEADSET) != 0) {
route = ROUTE_WIRED_HEADSET;
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index ff3b7b2..cea18dc 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -11,6 +11,7 @@
import android.os.UserManager;
import android.telecom.DefaultDialerManager;
import android.telecom.Log;
+import android.telecom.Logging.Session;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
@@ -19,6 +20,8 @@
import android.telephony.PhoneNumberUtils;
import android.widget.Toast;
+import java.util.concurrent.CompletableFuture;
+
/**
* Single point of entry for all outgoing and incoming calls.
* {@link com.android.server.telecom.components.UserCallIntentProcessor} serves as a trampoline that
@@ -28,7 +31,7 @@
public class CallIntentProcessor {
public interface Adapter {
void processOutgoingCallIntent(Context context, CallsManager callsManager,
- Intent intent);
+ Intent intent, String callingPackage);
void processIncomingCallIntent(CallsManager callsManager, Intent intent);
void processUnknownCallIntent(CallsManager callsManager, Intent intent);
}
@@ -36,8 +39,9 @@
public static class AdapterImpl implements Adapter {
@Override
public void processOutgoingCallIntent(Context context, CallsManager callsManager,
- Intent intent) {
- CallIntentProcessor.processOutgoingCallIntent(context, callsManager, intent);
+ Intent intent, String callingPackage) {
+ CallIntentProcessor.processOutgoingCallIntent(context, callsManager, intent,
+ callingPackage);
}
@Override
@@ -73,7 +77,7 @@
this.mCallsManager = callsManager;
}
- public void processIntent(Intent intent) {
+ public void processIntent(Intent intent, String callingPackage) {
final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false);
Log.i(this, "onReceive - isUnknownCall: %s", isUnknownCall);
@@ -81,7 +85,7 @@
if (isUnknownCall) {
processUnknownCallIntent(mCallsManager, intent);
} else {
- processOutgoingCallIntent(mContext, mCallsManager, intent);
+ processOutgoingCallIntent(mContext, mCallsManager, intent, callingPackage);
}
Trace.endSection();
}
@@ -91,11 +95,13 @@
* Processes CALL, CALL_PRIVILEGED, and CALL_EMERGENCY intents.
*
* @param intent Call intent containing data about the handle to call.
+ * @param callingPackage The package which initiated the outgoing call (if known).
*/
static void processOutgoingCallIntent(
Context context,
CallsManager callsManager,
- Intent intent) {
+ Intent intent,
+ String callingPackage) {
Uri handle = intent.getData();
String scheme = handle.getScheme();
@@ -143,13 +149,21 @@
UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
// Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
- Call call = callsManager
+ CompletableFuture<Call> callFuture = callsManager
.startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser,
- intent);
+ intent, callingPackage);
- if (call != null) {
- sendNewOutgoingCallIntent(context, call, callsManager, intent);
- }
+ final Session logSubsession = Log.createSubsession();
+ callFuture.thenAccept((call) -> {
+ if (call != null) {
+ Log.continueSession(logSubsession, "CIP.sNOCI");
+ try {
+ sendNewOutgoingCallIntent(context, call, callsManager, intent);
+ } finally {
+ Log.endSession();
+ }
+ }
+ });
}
static void sendNewOutgoingCallIntent(Context context, Call call, CallsManager callsManager,
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index 6e71b73..1d1a010 100755
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -39,6 +39,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.server.telecom.callfiltering.CallFilteringResult;
import java.util.Arrays;
import java.util.HashSet;
@@ -83,7 +84,8 @@
String postDialDigits, String viaNumber, int presentation, int callType,
int features, PhoneAccountHandle accountHandle, long creationDate,
long durationInMillis, Long dataUsage, UserHandle initiatingUser, boolean isRead,
- @Nullable LogCallCompletedListener logCallCompletedListener) {
+ @Nullable LogCallCompletedListener logCallCompletedListener, int callBlockReason,
+ String callScreeningAppName, String callScreeningComponentName) {
this.context = context;
this.callerInfo = callerInfo;
this.number = number;
@@ -99,6 +101,9 @@
this.initiatingUser = initiatingUser;
this.isRead = isRead;
this.logCallCompletedListener = logCallCompletedListener;
+ this.callBockReason = callBlockReason;
+ this.callScreeningAppName = callScreeningAppName;
+ this.callScreeningComponentName = callScreeningComponentName;
}
// Since the members are accessed directly, we don't use the
// mXxxx notation.
@@ -119,6 +124,10 @@
@Nullable
public final LogCallCompletedListener logCallCompletedListener;
+
+ public final int callBockReason;
+ public final String callScreeningAppName;
+ public final String callScreeningComponentName;
}
private static final String TAG = CallLogManager.class.getSimpleName();
@@ -182,22 +191,23 @@
// Always show the notification for managed calls. For self-managed calls, it is up to
// the app to show the notification, so suppress the notification when logging the call.
boolean showNotification = !call.isSelfManaged();
- logCall(call, type, showNotification);
+ logCall(call, type, showNotification, null /*result*/);
}
}
- void logCall(Call call, int type, boolean showNotificationForMissedCall) {
- if (type == Calls.MISSED_TYPE && showNotificationForMissedCall) {
- logCall(call, Calls.MISSED_TYPE,
- new LogCallCompletedListener() {
- @Override
- public void onLogCompleted(@Nullable Uri uri) {
- mMissedCallNotifier.showMissedCallNotification(
- new MissedCallNotifier.CallInfo(call));
- }
- });
+ void logCall(Call call, int type, boolean showNotificationForMissedCall, CallFilteringResult
+ result) {
+ if ((type == Calls.MISSED_TYPE || type == Calls.BLOCKED_TYPE) &&
+ showNotificationForMissedCall) {
+ logCall(call, type, new LogCallCompletedListener() {
+ @Override
+ public void onLogCompleted(@Nullable Uri uri) {
+ mMissedCallNotifier.showMissedCallNotification(
+ new MissedCallNotifier.CallInfo(call));
+ }
+ }, result);
} else {
- logCall(call, type, null);
+ logCall(call, type, null, result);
}
}
@@ -209,10 +219,13 @@
* {@link android.provider.CallLog.Calls#INCOMING_TYPE}
* {@link android.provider.CallLog.Calls#OUTGOING_TYPE}
* {@link android.provider.CallLog.Calls#MISSED_TYPE}
+ * {@link android.provider.CallLog.Calls#BLOCKED_TYPE}
* @param logCallCompletedListener optional callback called after the call is logged.
+ * @param result is generated when call type is
+ * {@link android.provider.CallLog.Calls#BLOCKED_TYPE}.
*/
void logCall(Call call, int callLogType,
- @Nullable LogCallCompletedListener logCallCompletedListener) {
+ @Nullable LogCallCompletedListener logCallCompletedListener, CallFilteringResult result) {
final long creationTime = call.getCreationTimeMillis();
final long age = call.getAgeMillis();
@@ -242,10 +255,22 @@
(call.getConnectionProperties() & Connection.PROPERTY_ASSISTED_DIALING_USED) ==
Connection.PROPERTY_ASSISTED_DIALING_USED,
call.wasEverRttCall());
- logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber,
- call.getHandlePresentation(), callLogType, callFeatures, accountHandle,
- creationTime, age, callDataUsage, call.isEmergencyCall(), call.getInitiatingUser(),
- call.isSelfManaged(), logCallCompletedListener);
+
+ if (callLogType == Calls.BLOCKED_TYPE) {
+ logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber,
+ call.getHandlePresentation(), callLogType, callFeatures, accountHandle,
+ creationTime, age, callDataUsage, call.isEmergencyCall(),
+ call.getInitiatingUser(), call.isSelfManaged(), logCallCompletedListener,
+ result.mCallBlockReason, result.mCallScreeningAppName,
+ result.mCallScreeningComponentName);
+ } else {
+ logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber,
+ call.getHandlePresentation(), callLogType, callFeatures, accountHandle,
+ creationTime, age, callDataUsage, call.isEmergencyCall(),
+ call.getInitiatingUser(), call.isSelfManaged(), logCallCompletedListener,
+ Calls.BLOCK_REASON_NOT_BLOCKED, null /*callScreeningAppName*/,
+ null /*callScreeningComponentName*/);
+ }
}
/**
@@ -265,6 +290,9 @@
* @param logCallCompletedListener optional callback called after the call is logged.
* @param initiatingUser The user the call was initiated under.
* @param isSelfManaged {@code true} if this is a self-managed call, {@code false} otherwise.
+ * @param callBlockReason The reason why the call is blocked.
+ * @param callScreeningAppName The call screening application name which block the call.
+ * @param callScreeningComponentName The call screening component name which block the call.
*/
private void logCall(
CallerInfo callerInfo,
@@ -281,7 +309,10 @@
boolean isEmergency,
UserHandle initiatingUser,
boolean isSelfManaged,
- @Nullable LogCallCompletedListener logCallCompletedListener) {
+ @Nullable LogCallCompletedListener logCallCompletedListener,
+ int callBlockReason,
+ String callScreeningAppName,
+ String callScreeningComponentName) {
// On some devices, to avoid accidental redialing of emergency numbers, we *never* log
// emergency calls to the Call Log. (This behavior is set on a per-product basis, based
@@ -314,7 +345,8 @@
}
AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, postDialDigits,
viaNumber, presentation, callType, features, accountHandle, start, duration,
- dataUsage, initiatingUser, isRead, logCallCompletedListener);
+ dataUsage, initiatingUser, isRead, logCallCompletedListener, callBlockReason,
+ callScreeningAppName, callScreeningComponentName);
logCallAsync(args);
} else {
Log.d(TAG, "Not adding emergency call to call log.");
@@ -475,7 +507,8 @@
return Calls.addCall(c.callerInfo, c.context, c.number, c.postDialDigits, c.viaNumber,
c.presentation, c.callType, c.features, c.accountHandle, c.timestamp,
c.durationInSec, c.dataUsage, userToBeInserted == null,
- userToBeInserted, c.isRead);
+ userToBeInserted, c.isRead, c.callBockReason, c.callScreeningAppName,
+ c.callScreeningComponentName);
}
diff --git a/src/com/android/server/telecom/CallState.java b/src/com/android/server/telecom/CallState.java
index 05ac38e..1e31732 100644
--- a/src/com/android/server/telecom/CallState.java
+++ b/src/com/android/server/telecom/CallState.java
@@ -114,6 +114,12 @@
*/
public static final int PULLING = TelecomProtoEnums.PULLING; // = 10
+ /**
+ * 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 String toString(int callState) {
switch (callState) {
case NEW:
@@ -138,6 +144,8 @@
return "DISCONNECTING";
case PULLING:
return "PULLING";
+ case ANSWERED:
+ return "ANSWERED";
default:
return "UNKNOWN";
}
diff --git a/src/com/android/server/telecom/CallerInfoLookupHelper.java b/src/com/android/server/telecom/CallerInfoLookupHelper.java
index f67a7f7..1e0745f 100644
--- a/src/com/android/server/telecom/CallerInfoLookupHelper.java
+++ b/src/com/android/server/telecom/CallerInfoLookupHelper.java
@@ -108,8 +108,9 @@
Log.i(this, "There is a previously incomplete query for handle %s. Adding to " +
"listeners for this query.", Log.piiHandle(handle));
info.listeners.add(listener);
- return;
}
+ // Since we have a pending query for this handle already, don't re-query it.
+ return;
} else {
CallerInfoQueryInfo info = new CallerInfoQueryInfo();
info.listeners.add(listener);
@@ -117,7 +118,7 @@
}
}
- mHandler.post(new Runnable("CILH.sL", mLock) {
+ mHandler.post(new Runnable("CILH.sL", null) {
@Override
public void loggedRun() {
Session continuedSession = Log.createSubsession();
@@ -171,7 +172,7 @@
}
private void startPhotoLookup(final Uri handle, final Uri contactPhotoUri) {
- mHandler.post(new Runnable("CILH.sPL", mLock) {
+ mHandler.post(new Runnable("CILH.sPL", null) {
@Override
public void loggedRun() {
Session continuedSession = Log.createSubsession();
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index f61a8ee..6130bb5 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -16,16 +16,20 @@
package com.android.server.telecom;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.pm.UserInfo;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.UserInfo;
import android.media.AudioManager;
import android.media.AudioSystem;
+import android.media.ToneGenerator;
+import android.media.MediaPlayer;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -45,20 +49,24 @@
import android.telecom.DisconnectCause;
import android.telecom.GatewayInfo;
import android.telecom.Log;
+import android.telecom.Logging.Runnable;
+import android.telecom.Logging.Session;
import android.telecom.ParcelableConference;
import android.telecom.ParcelableConnection;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
-import android.telecom.Logging.Runnable;
+import android.telecom.PhoneAccountSuggestion;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telephony.CarrierConfigManager;
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.util.Pair;
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;
@@ -68,7 +76,7 @@
import com.android.server.telecom.callfiltering.BlockCheckerAdapter;
import com.android.server.telecom.callfiltering.CallFilterResultCallback;
import com.android.server.telecom.callfiltering.CallFilteringResult;
-import com.android.server.telecom.callfiltering.CallScreeningServiceFilter;
+import com.android.server.telecom.callfiltering.CallScreeningServiceController;
import com.android.server.telecom.callfiltering.DirectToVoicemailCallFilter;
import com.android.server.telecom.callfiltering.IncomingCallFilter;
import com.android.server.telecom.components.ErrorDialogActivity;
@@ -88,8 +96,10 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -128,6 +138,7 @@
void onHoldToneRequested(Call call);
void onExternalCallChanged(Call call, boolean isExternalCall);
void onDisconnectedTonePlaying(boolean isTonePlaying);
+ void onConnectionTimeChanged(Call call);
}
/** Interface used to define the action which is executed delay under some condition. */
@@ -135,6 +146,26 @@
void performAction();
}
+ private class LoggedHandlerExecutor implements Executor {
+ private Handler mHandler;
+ private String mSessionName;
+
+ public LoggedHandlerExecutor(Handler handler, String sessionName) {
+ mHandler = handler;
+ mSessionName = sessionName;
+ }
+
+ @Override
+ public void execute(java.lang.Runnable command) {
+ mHandler.post(new Runnable(mSessionName, mLock) {
+ @Override
+ public void loggedRun() {
+ command.run();
+ }
+ }.prepare());
+ }
+ }
+
private static final String TAG = "CallsManager";
/**
@@ -191,12 +222,13 @@
*/
public static final int[] ONGOING_CALL_STATES =
{CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING, CallState.PULLING, CallState.ACTIVE,
- CallState.ON_HOLD, CallState.RINGING};
+ CallState.ON_HOLD, CallState.RINGING, CallState.ANSWERED};
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.ABORTED, CallState.DISCONNECTING, CallState.PULLING,
+ CallState.ANSWERED};
public static final String TELECOM_CALL_ID_PREFIX = "TC@";
@@ -225,9 +257,15 @@
/**
* A pending call is one which requires user-intervention in order to be placed.
- * Used by {@link #startCallConfirmation(Call)}.
+ * Used by {@link #startCallConfirmation}.
*/
private Call mPendingCall;
+ private CompletableFuture<Call> mPendingCallConfirm;
+ private CompletableFuture<Pair<Call, PhoneAccountHandle>> mPendingAccountSelection;
+
+ // Instance variables for testing -- we keep the latest copy of the outgoing call futures
+ // here so that we can wait on them in tests
+ private CompletableFuture<Call> mLatestPostSelectionProcessingFuture;
/**
* The current telecom call ID. Used when creating new instances of {@link Call}. Should
@@ -264,8 +302,6 @@
private final CallLogManager mCallLogManager;
private final Context mContext;
private final TelecomSystem.SyncRoot mLock;
- private final ContactsAsyncHelper mContactsAsyncHelper;
- private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final MissedCallNotifier mMissedCallNotifier;
private IncomingCallNotifier mIncomingCallNotifier;
@@ -280,6 +316,7 @@
/* Handler tied to thread in which CallManager was initialized. */
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final EmergencyCallHelper mEmergencyCallHelper;
+ private final RoleManagerAdapter mRoleManagerAdapter;
private final ConnectionServiceFocusManager.CallsManagerRequester mRequester =
new ConnectionServiceFocusManager.CallsManagerRequester() {
@@ -328,15 +365,33 @@
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
+ Log.startSession("CM.CCCR");
String action = intent.getAction();
if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)
|| SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED.equals(action)) {
- BlockedNumbersUtil.updateEmergencyCallNotification(context,
- SystemContract.shouldShowEmergencyCallNotification(context));
- }
+ new UpdateEmergencyCallNotificationTask().doInBackground(
+ Pair.create(context, Log.createSubsession()));
+ }
}
};
+ private static class UpdateEmergencyCallNotificationTask
+ extends AsyncTask<Pair<Context, Session>, Void, Void> {
+ @SafeVarargs
+ @Override
+ protected final Void doInBackground(Pair<Context, Session>... args) {
+ if (args == null || args.length != 1 || args[0] == null) {
+ Log.e(this, new IllegalArgumentException(), "Incorrect invocation");
+ return null;
+ }
+ Log.continueSession(args[0].second, "CM.UECNT");
+ Context context = args[0].first;
+ BlockedNumbersUtil.updateEmergencyCallNotification(context,
+ SystemContract.shouldShowEmergencyCallNotification(context));
+ return null;
+ }
+ }
+
/**
* Initializes the required Telecom components.
*/
@@ -344,8 +399,7 @@
public CallsManager(
Context context,
TelecomSystem.SyncRoot lock,
- ContactsAsyncHelper contactsAsyncHelper,
- CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
+ CallerInfoLookupHelper callerInfoLookupHelper,
MissedCallNotifier missedCallNotifier,
PhoneAccountRegistrar phoneAccountRegistrar,
HeadsetMediaButtonFactory headsetMediaButtonFactory,
@@ -356,7 +410,7 @@
CallAudioManager.AudioServiceFactory audioServiceFactory,
BluetoothRouteManager bluetoothManager,
WiredHeadsetManager wiredHeadsetManager,
- SystemStateProvider systemStateProvider,
+ SystemStateHelper systemStateHelper,
DefaultDialerCache defaultDialerCache,
Timeouts.Adapter timeoutsAdapter,
AsyncRingtonePlayer asyncRingtonePlayer,
@@ -365,12 +419,13 @@
InCallTonePlayer.ToneGeneratorFactory toneGeneratorFactory,
ClockProxy clockProxy,
BluetoothStateReceiver bluetoothStateReceiver,
- InCallControllerFactory inCallControllerFactory) {
+ CallAudioRouteStateMachine.Factory callAudioRouteStateMachineFactory,
+ CallAudioModeStateMachine.Factory callAudioModeStateMachineFactory,
+ InCallControllerFactory inCallControllerFactory,
+ RoleManagerAdapter roleManagerAdapter) {
mContext = context;
mLock = lock;
mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
- mContactsAsyncHelper = contactsAsyncHelper;
- mCallerInfoAsyncQueryFactory = callerInfoAsyncQueryFactory;
mPhoneAccountRegistrar = phoneAccountRegistrar;
mPhoneAccountRegistrar.addListener(mPhoneAccountListener);
mMissedCallNotifier = missedCallNotifier;
@@ -381,20 +436,20 @@
mDockManager = new DockManager(context);
mTimeoutsAdapter = timeoutsAdapter;
mEmergencyCallHelper = emergencyCallHelper;
- mCallerInfoLookupHelper = new CallerInfoLookupHelper(context, mCallerInfoAsyncQueryFactory,
- mContactsAsyncHelper, mLock);
+ mCallerInfoLookupHelper = callerInfoLookupHelper;
mDtmfLocalTonePlayer =
new DtmfLocalTonePlayer(new DtmfLocalTonePlayer.ToneGeneratorProxy());
- CallAudioRouteStateMachine callAudioRouteStateMachine = new CallAudioRouteStateMachine(
- context,
- this,
- bluetoothManager,
- wiredHeadsetManager,
- statusBarNotifier,
- audioServiceFactory,
- CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT
- );
+ CallAudioRouteStateMachine callAudioRouteStateMachine =
+ callAudioRouteStateMachineFactory.create(
+ context,
+ this,
+ bluetoothManager,
+ wiredHeadsetManager,
+ statusBarNotifier,
+ audioServiceFactory,
+ CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT
+ );
callAudioRouteStateMachine.initialize();
CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter =
@@ -404,27 +459,34 @@
wiredHeadsetManager,
mDockManager);
+ AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ InCallTonePlayer.MediaPlayerFactory mediaPlayerFactory =
+ (resourceId, attributes) ->
+ new InCallTonePlayer.MediaPlayerAdapterImpl(
+ MediaPlayer.create(mContext, resourceId, attributes,
+ audioManager.generateAudioSessionId()));
InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(
- callAudioRoutePeripheralAdapter, lock, toneGeneratorFactory);
+ callAudioRoutePeripheralAdapter, lock, toneGeneratorFactory, mediaPlayerFactory,
+ () -> audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0);
SystemSettingsUtil systemSettingsUtil = new SystemSettingsUtil();
RingtoneFactory ringtoneFactory = new RingtoneFactory(this, context);
SystemVibrator systemVibrator = new SystemVibrator(context);
mInCallController = inCallControllerFactory.create(context, mLock, this,
- systemStateProvider, defaultDialerCache, mTimeoutsAdapter,
+ systemStateHelper, defaultDialerCache, mTimeoutsAdapter,
emergencyCallHelper);
mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer,
- ringtoneFactory, systemVibrator, mInCallController);
+ ringtoneFactory, systemVibrator,
+ new Ringer.VibrationEffectProxy(), mInCallController);
mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext,
(AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE), mLock);
mCallAudioManager = new CallAudioManager(callAudioRouteStateMachine,
- this,new CallAudioModeStateMachine((AudioManager)
- mContext.getSystemService(Context.AUDIO_SERVICE)),
+ this, callAudioModeStateMachineFactory.create(systemStateHelper,
+ (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)),
playerFactory, mRinger, new RingbackPlayer(playerFactory),
bluetoothStateReceiver, mDtmfLocalTonePlayer);
- mConnectionSvrFocusMgr = connectionServiceFocusManagerFactory.create(
- mRequester, Looper.getMainLooper());
+ mConnectionSvrFocusMgr = connectionServiceFocusManagerFactory.create(mRequester);
mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this, mLock);
mTtyManager = new TtyManager(context, mWiredHeadsetManager);
mProximitySensorManager = proximitySensorManagerFactory.create(context, this);
@@ -434,6 +496,7 @@
new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, mLock, this);
mInCallWakeLockController = inCallWakeLockControllerFactory.create(context, this);
mClockProxy = clockProxy;
+ mRoleManagerAdapter = roleManagerAdapter;
mListeners.add(mInCallWakeLockController);
mListeners.add(statusBarNotifier);
@@ -483,6 +546,10 @@
return mCallerInfoLookupHelper;
}
+ public RoleManagerAdapter getRoleManagerAdapter() {
+ return mRoleManagerAdapter;
+ }
+
@Override
public void onSuccessfulOutgoingCall(Call call, int callState) {
Log.v(this, "onSuccessfulOutgoingCall, %s", call);
@@ -512,18 +579,33 @@
@Override
public void onSuccessfulIncomingCall(Call incomingCall) {
Log.d(this, "onSuccessfulIncomingCall");
- if (incomingCall.hasProperty(Connection.PROPERTY_EMERGENCY_CALLBACK_MODE)) {
- Log.i(this, "Skipping call filtering due to ECBM");
+ PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
+ incomingCall.getTargetPhoneAccount());
+ Bundle extras =
+ phoneAccount == null || phoneAccount.getExtras() == null
+ ? new Bundle()
+ : phoneAccount.getExtras();
+ if (incomingCall.hasProperty(Connection.PROPERTY_EMERGENCY_CALLBACK_MODE) ||
+ incomingCall.isSelfManaged() ||
+ extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING)) {
+ Log.i(this, "Skipping call filtering for %s (ecm=%b, selfMgd=%b, skipExtra=%b)",
+ incomingCall.getId(),
+ incomingCall.hasProperty(Connection.PROPERTY_EMERGENCY_CALLBACK_MODE),
+ incomingCall.isSelfManaged(),
+ extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING));
onCallFilteringComplete(incomingCall, new CallFilteringResult(true, false, true, true));
+ 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));
- filters.add(new CallScreeningServiceFilter(mContext, this, mPhoneAccountRegistrar,
- mDefaultDialerCache, new ParcelableCallUtils.Converter(), mLock));
+ mCallerInfoLookupHelper, null));
+ filters.add(new CallScreeningServiceController(mContext, this, mPhoneAccountRegistrar,
+ new ParcelableCallUtils.Converter(), mLock,
+ new TelecomServiceImpl.SettingsSecureAdapterImpl(), mCallerInfoLookupHelper));
new IncomingCallFilter(mContext, this, incomingCall, mLock,
mTimeoutsAdapter, filters).performFiltering();
}
@@ -549,12 +631,17 @@
} else {
Log.i(this, "onCallFilteringCompleted: Call rejected! " +
"Exceeds maximum number of ringing calls.");
- rejectCallAndLog(incomingCall);
+ rejectCallAndLog(incomingCall, result);
}
} else if (hasMaximumManagedDialingCalls(incomingCall)) {
- Log.i(this, "onCallFilteringCompleted: Call rejected! Exceeds maximum number of " +
- "dialing calls.");
- rejectCallAndLog(incomingCall);
+ if (shouldSilenceInsteadOfReject(incomingCall)) {
+ incomingCall.silence();
+ } else {
+
+ Log.i(this, "onCallFilteringCompleted: Call rejected! Exceeds maximum number of " +
+ "dialing calls.");
+ rejectCallAndLog(incomingCall, result);
+ }
} else {
addCall(incomingCall);
}
@@ -568,8 +655,8 @@
if (result.shouldShowNotification) {
Log.w(this, "onCallScreeningCompleted: blocked call, showing notification.");
}
- mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE,
- result.shouldShowNotification);
+ mCallLogManager.logCall(incomingCall, Calls.BLOCKED_TYPE,
+ result.shouldShowNotification, result);
} else if (result.shouldShowNotification) {
Log.i(this, "onCallScreeningCompleted: blocked call, showing notification.");
mMissedCallNotifier.showMissedCallNotification(
@@ -579,8 +666,10 @@
}
/**
- * Whether allow (silence rather than reject) the incoming call if it has a different source
- * (connection service) from the existing ringing call when reaching maximum ringing calls.
+ * In the event that the maximum supported calls of a given type is reached, the
+ * default behavior is to reject any additional calls of that type. This checks
+ * if the device is configured to silence instead of reject the call, provided
+ * that the incoming call is from a different source (connection service).
*/
private boolean shouldSilenceInsteadOfReject(Call incomingCall) {
if (!mContext.getResources().getBoolean(
@@ -588,8 +677,6 @@
return false;
}
- Call ringingCall = null;
-
for (Call call : mCalls) {
// Only operate on top-level calls
if (call.getParentCall() != null) {
@@ -600,8 +687,7 @@
continue;
}
- if (CallState.RINGING == call.getState() &&
- call.getConnectionService() == incomingCall.getConnectionService()) {
+ if (call.getConnectionService() == incomingCall.getConnectionService()) {
return false;
}
}
@@ -912,8 +998,6 @@
this,
mLock,
mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
handle,
null /* gatewayInfo */,
@@ -1047,8 +1131,6 @@
this,
mLock,
mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
handle,
null /* gatewayInfo */,
@@ -1108,20 +1190,24 @@
* For self-managed connections, we don't expect the Incall UI to launch, but this is still a
* first step in getting the self-managed ConnectionService to create the connection.
* @param handle Handle to connect the call with.
- * @param phoneAccountHandle The phone account which contains the component name of the
+ * @param requestedAccountHandle The phone account which contains the component name of the
* connection service to use for this call.
* @param extras The optional extras Bundle passed with the intent used for the incoming call.
* @param initiatingUser {@link UserHandle} of user that place the outgoing call.
* @param originalIntent
+ * @param callingPackage the package name of the app which initiated the outgoing call.
*/
@VisibleForTesting
- public Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras,
- UserHandle initiatingUser, Intent originalIntent) {
- boolean isReusedCall = true;
+ public @NonNull
+ CompletableFuture<Call> startOutgoingCall(Uri handle,
+ PhoneAccountHandle requestedAccountHandle,
+ Bundle extras, UserHandle initiatingUser, Intent originalIntent,
+ String callingPackage) {
+ boolean isReusedCall;
Call call = reuseOutgoingCall(handle);
PhoneAccount account =
- mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle, initiatingUser);
+ mPhoneAccountRegistrar.getPhoneAccount(requestedAccountHandle, initiatingUser);
boolean isSelfManaged = account != null && account.isSelfManaged();
// Create a call with original handle. The handle may be changed when the call is attached
@@ -1131,18 +1217,16 @@
this,
mLock,
mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
handle,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
- null /* phoneAccountHandle */,
+ null /* requestedAccountHandle */,
Call.CALL_DIRECTION_OUTGOING /* callDirection */,
false /* forceAttachToExistingConnection */,
false, /* isConference */
mClockProxy);
- call.initAnalytics();
+ call.initAnalytics(callingPackage);
// 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,
@@ -1155,6 +1239,8 @@
}
call.setInitiatingUser(initiatingUser);
isReusedCall = false;
+ } else {
+ isReusedCall = true;
}
int videoState = VideoProfile.STATE_AUDIO_ONLY;
@@ -1189,95 +1275,209 @@
call.setVideoState(videoState);
}
- List<PhoneAccountHandle> potentialPhoneAccounts = findOutgoingCallPhoneAccount(
- phoneAccountHandle, handle, VideoProfile.isVideo(videoState), initiatingUser);
- if (potentialPhoneAccounts.size() == 1) {
- phoneAccountHandle = potentialPhoneAccounts.get(0);
- } else {
- phoneAccountHandle = null;
- }
- call.setTargetPhoneAccount(phoneAccountHandle);
+ final int finalVideoState = videoState;
+ final Call finalCall = call;
+ Handler outgoingCallHandler = new Handler(Looper.getMainLooper());
+ // Create a empty CompletableFuture and compose it with findOutgoingPhoneAccount to get
+ // a first guess at the list of suitable outgoing PhoneAccounts.
+ // findOutgoingPhoneAccount returns a CompletableFuture which is either already complete
+ // (in the case where we don't need to do the per-contact lookup) or a CompletableFuture
+ // that completes once the contact lookup via CallerInfoLookupHelper is complete.
+ CompletableFuture<List<PhoneAccountHandle>> accountsForCall =
+ CompletableFuture.completedFuture((Void) null).thenComposeAsync((x) ->
+ findOutgoingCallPhoneAccount(requestedAccountHandle, handle,
+ VideoProfile.isVideo(finalVideoState), initiatingUser),
+ new LoggedHandlerExecutor(outgoingCallHandler, "CM.fOCP"));
- boolean isPotentialInCallMMICode = isPotentialInCallMMICode(handle) && !isSelfManaged;
+ // This is a block of code that executes after the list of potential phone accts has been
+ // retrieved.
+ CompletableFuture<List<PhoneAccountHandle>> setAccountHandle =
+ accountsForCall.whenCompleteAsync((potentialPhoneAccounts, exception) -> {
+ Log.i(CallsManager.this, "set outgoing call phone acct stage");
+ PhoneAccountHandle phoneAccountHandle;
+ if (potentialPhoneAccounts.size() == 1) {
+ phoneAccountHandle = potentialPhoneAccounts.get(0);
+ } else {
+ phoneAccountHandle = null;
+ }
+ finalCall.setTargetPhoneAccount(phoneAccountHandle);
+ }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.sOCPA"));
- // 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(call, call.isEmergencyCall()))) {
- Call foregroundCall = getForegroundCall();
- Log.d(this, "No more room for outgoing call %s ", call);
- if (foregroundCall.isSelfManaged()) {
- // If the ongoing call is a self-managed call, then prompt the user to ask if they'd
- // like to disconnect their ongoing call and place the outgoing call.
- call.setOriginalCallIntent(originalIntent);
- startCallConfirmation(call);
- } else {
- // If the ongoing call is a managed call, we will prevent the outgoing call from
- // dialing.
- notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call);
- }
- return null;
- }
- // The outgoing call can be placed, go forward.
+ // This composes the future containing the potential phone accounts with code that queries
+ // the suggestion service if necessary (i.e. if the list is longer than 1).
+ // If the suggestion service is queried, the inner lambda will return a future that
+ // completes when the suggestion service calls the callback.
+ CompletableFuture<List<PhoneAccountSuggestion>> suggestionFuture = accountsForCall.
+ thenComposeAsync(potentialPhoneAccounts -> {
+ Log.i(CallsManager.this, "call outgoing call suggestion service stage");
+ if (potentialPhoneAccounts.size() == 1) {
+ PhoneAccountSuggestion suggestion =
+ new PhoneAccountSuggestion(potentialPhoneAccounts.get(0),
+ PhoneAccountSuggestion.REASON_NONE, true);
+ return CompletableFuture.completedFuture(
+ Collections.singletonList(suggestion));
+ }
+ // todo: call onsuggestphoneaccount and bring back the list of suggestions
+ // from there. For now just map all the accounts to suggest_none
+ List<PhoneAccountSuggestion> suggestions =
+ potentialPhoneAccounts.stream().map(phoneAccountHandle ->
+ new PhoneAccountSuggestion(phoneAccountHandle,
+ PhoneAccountSuggestion.REASON_NONE, false)
+ ).collect(Collectors.toList());
- boolean needsAccountSelection =
- phoneAccountHandle == null && potentialPhoneAccounts.size() > 1
- && !call.isEmergencyCall() && !isSelfManaged;
- if (needsAccountSelection) {
- // This is the state where the user is expected to select an account
- call.setState(CallState.SELECT_PHONE_ACCOUNT, "needs account selection");
- // Create our own instance to modify (since extras may be Bundle.EMPTY)
- extras = new Bundle(extras);
- extras.putParcelableList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS,
- potentialPhoneAccounts);
- } else {
- PhoneAccount accountToUse =
- mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle, 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);
- }
- }
+ return CompletableFuture.completedFuture(suggestions);
+ }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.cOCSS"));
- call.setState(
- CallState.CONNECTING,
- phoneAccountHandle == null ? "no-handle" : phoneAccountHandle.toString());
- boolean isVoicemail = (call.getHandle() != null)
- && (PhoneAccount.SCHEME_VOICEMAIL.equals(call.getHandle().getScheme())
- || (accountToUse != null && mPhoneAccountRegistrar.isVoiceMailNumber(
- accountToUse.getAccountHandle(), call.getHandle().getSchemeSpecificPart())));
+ // This future checks the status of existing calls and attempts to make room for the
+ // outgoing call. The future returned by the inner method will usually be pre-completed --
+ // we only pause here if user interaction is required to disconnect a self-managed call.
+ // It runs after the account handle is set, independently of the phone account suggestion
+ // future.
+ 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()))) {
+ Call foregroundCall = getForegroundCall();
+ Log.d(CallsManager.this, "No more room for outgoing call %s ", finalCall);
+ if (foregroundCall.isSelfManaged()) {
+ // If the ongoing call is a self-managed call, then prompt the user to
+ // ask if they'd like to disconnect their ongoing call and place the
+ // outgoing call.
+ Log.i(CallsManager.this, "Prompting user to disconnect "
+ + "self-managed call");
+ finalCall.setOriginalCallIntent(originalIntent);
+ CompletableFuture<Call> completionFuture = new CompletableFuture<>();
+ startCallConfirmation(finalCall, completionFuture);
+ return completionFuture;
+ } else {
+ // If the ongoing call is a managed call, we will prevent the outgoing
+ // call from dialing.
+ notifyCreateConnectionFailed(
+ finalCall.getTargetPhoneAccount(), finalCall);
+ }
+ Log.i(CallsManager.this, "Aborting call since there's no room");
+ return CompletableFuture.completedFuture(null);
+ }
+ return CompletableFuture.completedFuture(finalCall);
+ }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.dSMCP"));
- if (!isVoicemail && (isRttSettingOn() || (extras != null
- && extras.getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, false)))) {
- Log.d(this, "Outgoing call requesting RTT, rtt setting is %b", isRttSettingOn());
- if (accountToUse != null
- && accountToUse.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
- call.createRttStreams();
- }
- // Even if the phone account doesn't support RTT yet, the connection manager might
- // change that. Set this to check it later.
- call.setRequestedToStartWithRtt();
- }
- }
- setIntentExtrasAndStartTime(call, extras);
+ // The outgoing call can be placed, go forward. This future glues together the results of
+ // the account suggestion stage and the make room for call stage.
+ CompletableFuture<Pair<Call, List<PhoneAccountSuggestion>>> preSelectStage =
+ makeRoomForCall.thenCombine(suggestionFuture, Pair::create);
- if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) {
- // Do not add the call if it is a potential MMI code.
- call.addListener(this);
- } else 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);
- }
+ // This future takes the list of suggested accounts and the call and determines if more
+ // user interaction in the form of a phone account selection screen is needed. If so, it
+ // will set the call to SELECT_PHONE_ACCOUNT, add it to our internal list/send it to dialer,
+ // and then execution will pause pending the dialer calling phoneAccountSelected.
+ CompletableFuture<Pair<Call, PhoneAccountHandle>> dialerSelectPhoneAccountFuture =
+ preSelectStage.thenComposeAsync(
+ (args) -> {
+ Log.i(CallsManager.this, "dialer phone acct select stage");
+ Call callToPlace = args.first;
+ List<PhoneAccountSuggestion> accountSuggestions = args.second;
+ if (callToPlace == null) {
+ return CompletableFuture.completedFuture(null);
+ }
+ if (accountSuggestions == null || accountSuggestions.isEmpty()) {
+ Log.i(CallsManager.this, "Aborting call since there are no"
+ + " available accounts.");
+ return CompletableFuture.completedFuture(null);
+ }
+ boolean needsAccountSelection = accountSuggestions.size() > 1
+ && !callToPlace.isEmergencyCall() && !isSelfManaged;
+ if (!needsAccountSelection) {
+ return CompletableFuture.completedFuture(Pair.create(callToPlace,
+ accountSuggestions.get(0).getPhoneAccountHandle()));
+ }
+ // This is the state where the user is expected to select an account
+ callToPlace.setState(CallState.SELECT_PHONE_ACCOUNT,
+ "needs account selection");
+ // Create our own instance to modify (since extras may be Bundle.EMPTY)
+ Bundle newExtras = new Bundle(extras);
+ List<PhoneAccountHandle> accountsFromSuggestions = accountSuggestions
+ .stream()
+ .map(PhoneAccountSuggestion::getPhoneAccountHandle)
+ .collect(Collectors.toList());
+ newExtras.putParcelableList(
+ android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS,
+ accountsFromSuggestions);
+ // Set a future in place so that we can proceed once the dialer replies.
+ mPendingAccountSelection = new CompletableFuture<>();
+ callToPlace.setIntentExtras(newExtras);
- return call;
+ addCall(callToPlace);
+ return mPendingAccountSelection;
+ }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.dSPA"));
+
+ // Finally, after all user interaction is complete, we execute this code to finish setting
+ // up the outgoing call. The inner method always returns a completed future containing the
+ // call that we've finished setting up.
+ mLatestPostSelectionProcessingFuture = dialerSelectPhoneAccountFuture
+ .thenComposeAsync(args -> {
+ if (args == null) {
+ return CompletableFuture.completedFuture(null);
+ }
+ Log.i(CallsManager.this, "post acct selection stage");
+ Call callToUse = args.first;
+ PhoneAccountHandle phoneAccountHandle = args.second;
+ PhoneAccount accountToUse = mPhoneAccountRegistrar
+ .getPhoneAccount(phoneAccountHandle, initiatingUser);
+ callToUse.setTargetPhoneAccount(phoneAccountHandle);
+ 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",
+ callToUse.getId());
+ callToUse.setIsVoipAudioMode(true);
+ }
+ }
+
+ callToUse.setState(
+ CallState.CONNECTING,
+ phoneAccountHandle == null ? "no-handle"
+ : phoneAccountHandle.toString());
+
+ boolean isVoicemail = isVoicemail(callToUse.getHandle(), accountToUse);
+
+ if (!isVoicemail && (isRttSettingOn() || (extras != null
+ && extras.getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT,
+ false)))) {
+ Log.d(this, "Outgoing call requesting RTT, rtt setting is %b",
+ isRttSettingOn());
+ if (accountToUse != null
+ && accountToUse.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
+ callToUse.createRttStreams();
+ }
+ // Even if the phone account doesn't support RTT yet,
+ // the connection manager might change that. Set this to check it later.
+ callToUse.setRequestedToStartWithRtt();
+ }
+
+ setIntentExtrasAndStartTime(callToUse, extras);
+ setCallSourceToAnalytics(callToUse, originalIntent);
+
+ if (isPotentialMMICode(handle) && !isSelfManaged) {
+ // Do not add the call if it is a potential MMI code.
+ callToUse.addListener(this);
+ } else if (!mCalls.contains(callToUse)) {
+ // We check if mCalls already contains the call because we could
+ // potentially be reusing
+ // a call which was previously added (See {@link #reuseOutgoingCall}).
+ addCall(callToUse);
+ }
+ return CompletableFuture.completedFuture(callToUse);
+ }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.pASP"));
+ return mLatestPostSelectionProcessingFuture;
}
/**
@@ -1301,55 +1501,71 @@
* @return
*/
@VisibleForTesting
- public List<PhoneAccountHandle> findOutgoingCallPhoneAccount(
+ public CompletableFuture<List<PhoneAccountHandle>> findOutgoingCallPhoneAccount(
PhoneAccountHandle targetPhoneAccountHandle, Uri handle, boolean isVideo,
UserHandle initiatingUser) {
- boolean isSelfManaged = isSelfManaged(targetPhoneAccountHandle, initiatingUser);
+ if (isSelfManaged(targetPhoneAccountHandle, initiatingUser)) {
+ return CompletableFuture.completedFuture(Arrays.asList(targetPhoneAccountHandle));
+ }
List<PhoneAccountHandle> accounts;
- if (!isSelfManaged) {
- // Try to find a potential phone account, taking into account whether this is a video
- // call.
- accounts = constructPossiblePhoneAccounts(handle, initiatingUser, isVideo);
- 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 */);
- }
- Log.v(this, "findOutgoingCallPhoneAccount: accounts = " + accounts);
-
- // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this
- // call as if a phoneAccount was not specified (does the default behavior instead).
- // Note: We will not attempt to dial with a requested phoneAccount if it is disabled.
- if (targetPhoneAccountHandle != null) {
- if (!accounts.contains(targetPhoneAccountHandle)) {
- targetPhoneAccountHandle = null;
- } else {
- // The target phone account is valid and was found.
- return Arrays.asList(targetPhoneAccountHandle);
- }
- }
-
- if (targetPhoneAccountHandle == null && accounts.size() > 0) {
- // No preset account, check if default exists that supports the URI scheme for the
- // handle and verify it can be used.
- if (accounts.size() > 1) {
- PhoneAccountHandle defaultPhoneAccountHandle =
- mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(
- handle.getScheme(), initiatingUser);
- if (defaultPhoneAccountHandle != null &&
- accounts.contains(defaultPhoneAccountHandle)) {
- accounts.clear();
- accounts.add(defaultPhoneAccountHandle);
- }
- }
- }
- } else {
- // Self-managed ConnectionServices can only have a single potential account.
- accounts = Arrays.asList(targetPhoneAccountHandle);
+ // Try to find a potential phone account, taking into account whether this is a video
+ // call.
+ accounts = constructPossiblePhoneAccounts(handle, initiatingUser, isVideo);
+ 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 */);
}
- return accounts;
+ Log.v(this, "findOutgoingCallPhoneAccount: accounts = " + accounts);
+
+ // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this
+ // call as if a phoneAccount was not specified (does the default behavior instead).
+ // Note: We will not attempt to dial with a requested phoneAccount if it is disabled.
+ if (targetPhoneAccountHandle != null) {
+ if (accounts.contains(targetPhoneAccountHandle)) {
+ // The target phone account is valid and was found.
+ return CompletableFuture.completedFuture(Arrays.asList(targetPhoneAccountHandle));
+ }
+ }
+
+ // Do the query for whether there's a preferred contact
+ final CompletableFuture<PhoneAccountHandle> userPreferredAccountForContact =
+ new CompletableFuture<>();
+ final List<PhoneAccountHandle> possibleAccounts = accounts;
+ mCallerInfoLookupHelper.startLookup(handle,
+ new CallerInfoLookupHelper.OnQueryCompleteListener() {
+ @Override
+ public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) {
+ // TODO: construct the acct handle from caller info
+ userPreferredAccountForContact.complete(null);
+ }
+
+ @Override
+ public void onContactPhotoQueryComplete(Uri handle, CallerInfo info) {
+ // ignore this
+ }
+ });
+
+ return userPreferredAccountForContact.thenApply(phoneAccountHandle -> {
+ if (phoneAccountHandle != null) {
+ return Collections.singletonList(phoneAccountHandle);
+ }
+ if (possibleAccounts.isEmpty() || possibleAccounts.size() == 1) {
+ return possibleAccounts;
+ }
+ // No preset account, check if default exists that supports the URI scheme for the
+ // handle and verify it can be used.
+ PhoneAccountHandle defaultPhoneAccountHandle =
+ mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(
+ handle.getScheme(), initiatingUser);
+ if (defaultPhoneAccountHandle != null &&
+ possibleAccounts.contains(defaultPhoneAccountHandle)) {
+ return Collections.singletonList(defaultPhoneAccountHandle);
+ }
+ return possibleAccounts;
+ });
}
/**
@@ -1608,6 +1824,15 @@
} else {
mLocallyDisconnectingCalls.add(call);
call.disconnect();
+ // Cancel any of the outgoing call futures if they're still around.
+ if (mPendingCallConfirm != null && !mPendingCallConfirm.isDone()) {
+ mPendingCallConfirm.complete(null);
+ mPendingCallConfirm = null;
+ }
+ if (mPendingAccountSelection != null && !mPendingAccountSelection.isDone()) {
+ mPendingAccountSelection.complete(null);
+ mPendingAccountSelection = null;
+ }
}
}
@@ -1663,7 +1888,7 @@
} else {
Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
String activeCallId = null;
- if (activeCall != null) {
+ if (activeCall != null && !activeCall.isLocallyDisconnecting()) {
activeCallId = activeCall.getId();
if (canHold(activeCall)) {
activeCall.hold("Swap to " + call.getId());
@@ -1829,51 +2054,15 @@
if (!mCalls.contains(call)) {
Log.i(this, "Attempted to add account to unknown call %s", call);
} else {
- call.setTargetPhoneAccount(account);
- PhoneAccount realPhoneAccount =
- mPhoneAccountRegistrar.getPhoneAccountUnchecked(account);
- if (realPhoneAccount != null && realPhoneAccount.getExtras() != null
- && realPhoneAccount.getExtras()
- .getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
- Log.d("phoneAccountSelected: default to voip mode for call %s", call.getId());
- call.setIsVoipAudioMode(true);
- }
-
- boolean isVoicemail = (call.getHandle() != null)
- && (PhoneAccount.SCHEME_VOICEMAIL.equals(call.getHandle().getScheme())
- || (realPhoneAccount != null && mPhoneAccountRegistrar.isVoiceMailNumber(
- realPhoneAccount.getAccountHandle(),
- call.getHandle().getSchemeSpecificPart())));
-
- if (!isVoicemail && (isRttSettingOn() || call.getIntentExtras()
- .getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, false))) {
- Log.d(this, "Outgoing call after account selection requesting RTT," +
- " rtt setting is %b", isRttSettingOn());
- if (realPhoneAccount != null
- && realPhoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
- call.createRttStreams();
- }
- // Even if the phone account doesn't support RTT yet, the connection manager might
- // change that. Set this to check it later.
- call.setRequestedToStartWithRtt();
- }
-
- if (!call.isNewOutgoingCallIntentBroadcastDone()) {
- return;
- }
-
- // Note: emergency calls never go through account selection dialog so they never
- // arrive here.
- if (makeRoomForOutgoingCall(call, false /* isEmergencyCall */)) {
- call.startCreateConnection(mPhoneAccountRegistrar);
- } else {
- call.disconnect("no room");
- }
-
if (setDefault) {
mPhoneAccountRegistrar
.setUserSelectedOutgoingPhoneAccount(account, call.getInitiatingUser());
}
+
+ if (mPendingAccountSelection != null) {
+ mPendingAccountSelection.complete(Pair.create(call, account));
+ mPendingAccountSelection = null;
+ }
}
}
@@ -2038,7 +2227,7 @@
* indicate to the user that the call cannot me placed due to an ongoing call in another app.
*
* Used when there are ongoing self-managed calls and the user tries to make an outgoing managed
- * call. Called by {@link #startCallConfirmation(Call)} when the user is already confirming an
+ * call. Called by {@link #startCallConfirmation} when the user is already confirming an
* outgoing call. Realistically this should almost never be called since in practice the user
* won't make multiple outgoing calls at the same time.
*
@@ -2073,7 +2262,8 @@
if (call.getConnectionService() == service) {
if (call.getState() != CallState.DISCONNECTED) {
markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.ERROR,
- "CS_DEATH"));
+ null /* message */, null /* description */, "CS_DEATH",
+ ToneGenerator.TONE_PROP_PROMPT));
}
markCallAsRemoved(call);
}
@@ -2104,14 +2294,25 @@
}
boolean hasRingingCall() {
- return getFirstCallWithState(CallState.RINGING) != null;
+ return getFirstCallWithState(CallState.RINGING, CallState.ANSWERED) != null;
}
- boolean onMediaButton(int type) {
+ @VisibleForTesting
+ public boolean onMediaButton(int type) {
if (hasAnyCalls()) {
Call ringingCall = getFirstCallWithState(CallState.RINGING);
if (HeadsetMediaButton.SHORT_PRESS == type) {
if (ringingCall == null) {
+ Call activeCall = getFirstCallWithState(CallState.ACTIVE);
+ Call onHoldCall = getFirstCallWithState(CallState.ON_HOLD);
+ if (activeCall != null && onHoldCall != null) {
+ // Two calls, short-press -> switch calls
+ Log.addEvent(onHoldCall, LogUtils.Events.INFO,
+ "two calls, media btn short press - switch call.");
+ unholdCall(onHoldCall);
+ return true;
+ }
+
Call callToHangup = getFirstCallWithState(CallState.RINGING, CallState.DIALING,
CallState.PULLING, CallState.ACTIVE, CallState.ON_HOLD);
Log.addEvent(callToHangup, LogUtils.Events.INFO,
@@ -2130,6 +2331,16 @@
LogUtils.Events.INFO, "media btn long press - reject");
ringingCall.reject(false, null);
} else {
+ Call activeCall = getFirstCallWithState(CallState.ACTIVE);
+ Call onHoldCall = getFirstCallWithState(CallState.ON_HOLD);
+ if (activeCall != null && onHoldCall != null) {
+ // Two calls, long-press -> end current call
+ Log.addEvent(activeCall, LogUtils.Events.INFO,
+ "two calls, media btn long press - end current call.");
+ disconnectCall(activeCall);
+ return true;
+ }
+
Log.addEvent(getForegroundCall(), LogUtils.Events.INFO,
"media btn long press - mute");
mCallAudioManager.toggleMute();
@@ -2189,7 +2400,7 @@
@VisibleForTesting
public Call getRingingCall() {
- return getFirstCallWithState(CallState.RINGING);
+ return getFirstCallWithState(CallState.RINGING, CallState.ANSWERED);
}
public Call getActiveCall() {
@@ -2239,6 +2450,11 @@
return mPhoneNumberUtilsAdapter;
}
+ @VisibleForTesting
+ public CompletableFuture<Call> getLatestPostSelectionProcessingFuture() {
+ return mLatestPostSelectionProcessingFuture;
+ }
+
/**
* Returns the first call that it finds with the given states. The states are treated as having
* priority order so that any call with the first state will be returned before any call with
@@ -2298,8 +2514,6 @@
this,
mLock,
mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
null /* handle */,
null /* gatewayInfo */,
@@ -2369,7 +2583,7 @@
* Reject an incoming call and manually add it to the Call Log.
* @param incomingCall Incoming call that has been rejected
*/
- private void rejectCallAndLog(Call incomingCall) {
+ private void rejectCallAndLog(Call incomingCall, CallFilteringResult result) {
if (incomingCall.getConnectionService() != null) {
// Only reject the call if it has not already been destroyed. If a call ends while
// incoming call filtering is taking place, it is possible that the call has already
@@ -2384,7 +2598,7 @@
// call notifier and the call logger manually.
// Do we need missed call notification for direct to Voicemail calls?
mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE,
- true /*showNotificationForMissedCall*/);
+ true /*showNotificationForMissedCall*/, result);
}
/**
@@ -2479,27 +2693,31 @@
// into a well-defined state machine.
// TODO: Define expected state transitions here, and log when an
// unexpected transition occurs.
- call.setState(newState, tag);
- maybeShowErrorDialogOnDisconnect(call);
+ if (call.setState(newState, tag)) {
+ maybeShowErrorDialogOnDisconnect(call);
- Trace.beginSection("onCallStateChanged");
+ Trace.beginSection("onCallStateChanged");
- maybeHandleHandover(call, newState);
+ maybeHandleHandover(call, newState);
- // Only broadcast state change for calls that are being tracked.
- if (mCalls.contains(call)) {
- updateCanAddCall();
- for (CallsManagerListener listener : mListeners) {
- if (LogUtils.SYSTRACE_DEBUG) {
- Trace.beginSection(listener.getClass().toString() + " onCallStateChanged");
- }
- listener.onCallStateChanged(call, oldState, newState);
- if (LogUtils.SYSTRACE_DEBUG) {
- Trace.endSection();
+ // Only broadcast state change for calls that are being tracked.
+ if (mCalls.contains(call)) {
+ updateCanAddCall();
+ for (CallsManagerListener listener : mListeners) {
+ if (LogUtils.SYSTRACE_DEBUG) {
+ Trace.beginSection(listener.getClass().toString() +
+ " onCallStateChanged");
+ }
+ listener.onCallStateChanged(call, oldState, newState);
+ if (LogUtils.SYSTRACE_DEBUG) {
+ Trace.endSection();
+ }
}
}
+ Trace.endSection();
+ } else {
+ Log.i(this, "failed in setting the state to new state");
}
- Trace.endSection();
}
}
@@ -2745,13 +2963,13 @@
private boolean hasMaximumManagedRingingCalls(Call exceptCall) {
return MAXIMUM_RINGING_CALLS <= getNumCallsWithState(false /* isSelfManaged */, exceptCall,
- null /* phoneAccountHandle */, CallState.RINGING);
+ null /* phoneAccountHandle */, CallState.RINGING, CallState.ANSWERED);
}
private boolean hasMaximumSelfManagedRingingCalls(Call exceptCall,
PhoneAccountHandle phoneAccountHandle) {
return MAXIMUM_RINGING_CALLS <= getNumCallsWithState(true /* isSelfManaged */, exceptCall,
- phoneAccountHandle, CallState.RINGING);
+ phoneAccountHandle, CallState.RINGING, CallState.ANSWERED);
}
private boolean hasMaximumOutgoingCalls(Call exceptCall) {
@@ -3010,8 +3228,6 @@
this,
mLock,
mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
connection.getHandle() /* handle */,
null /* gatewayInfo */,
@@ -3029,6 +3245,7 @@
setCallState(call, Call.getStateFromConnectionState(connection.getState()),
"existing connection");
+ call.setVideoState(connection.getVideoState());
call.setConnectionCapabilities(connection.getConnectionCapabilities());
call.setConnectionProperties(connection.getConnectionProperties());
call.setHandle(connection.getHandle(), connection.getHandlePresentation());
@@ -3117,6 +3334,7 @@
public void onUserSwitch(UserHandle userHandle) {
mCurrentUserHandle = userHandle;
mMissedCallNotifier.setCurrentUserHandle(userHandle);
+ mRoleManagerAdapter.setCurrentUserHandle(userHandle);
final UserManager userManager = UserManager.get(mContext);
List<UserInfo> profiles = userManager.getEnabledProfiles(userHandle.getIdentifier());
for (UserInfo profile : profiles) {
@@ -3245,7 +3463,7 @@
/**
* Used to confirm creation of an outgoing call which was marked as pending confirmation in
- * {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent)}.
+ * {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent, String)}.
* Called via {@link TelecomBroadcastIntentProcessor} for a call which was confirmed via
* {@link ConfirmCallDialogActivity}.
* @param callId The call ID of the call to confirm.
@@ -3254,23 +3472,20 @@
Log.i(this, "confirmPendingCall: callId=%s", callId);
if (mPendingCall != null && mPendingCall.getId().equals(callId)) {
Log.addEvent(mPendingCall, LogUtils.Events.USER_CONFIRMED);
- addCall(mPendingCall);
// We are going to place the new outgoing call, so disconnect any ongoing self-managed
// calls which are ongoing at this time.
disconnectSelfManagedCalls("outgoing call " + callId);
- // Kick of the new outgoing call intent from where it left off prior to confirming the
- // call.
- CallIntentProcessor.sendNewOutgoingCallIntent(mContext, mPendingCall, this,
- mPendingCall.getOriginalCallIntent());
+ mPendingCallConfirm.complete(mPendingCall);
+ mPendingCallConfirm = null;
mPendingCall = null;
}
}
/**
* Used to cancel an outgoing call which was marked as pending confirmation in
- * {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent)}.
+ * {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent, String)}.
* Called via {@link TelecomBroadcastIntentProcessor} for a call which was confirmed via
* {@link ConfirmCallDialogActivity}.
* @param callId The call ID of the call to cancel.
@@ -3282,25 +3497,29 @@
markCallAsDisconnected(mPendingCall, new DisconnectCause(DisconnectCause.CANCELED));
markCallAsRemoved(mPendingCall);
mPendingCall = null;
+ mPendingCallConfirm.complete(null);
+ mPendingCallConfirm = null;
}
}
/**
- * Called from {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent)} when
+ * Called from {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent, String)} when
* a managed call is added while there are ongoing self-managed calls. Starts
* {@link ConfirmCallDialogActivity} to prompt the user to see if they wish to place the
* outgoing call or not.
* @param call The call to confirm.
*/
- private void startCallConfirmation(Call call) {
+ private void startCallConfirmation(Call call, CompletableFuture<Call> confirmationFuture) {
if (mPendingCall != null) {
Log.i(this, "startCallConfirmation: call %s is already pending; disconnecting %s",
mPendingCall.getId(), call.getId());
markCallDisconnectedDueToSelfManagedCall(call);
+ confirmationFuture.complete(null);
return;
}
Log.addEvent(call, LogUtils.Events.USER_CONFIRMATION);
mPendingCall = call;
+ mPendingCallConfirm = confirmationFuture;
// Figure out the name of the app in charge of the self-managed call(s).
Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
@@ -3391,6 +3610,14 @@
mConnectionServiceRepository.dump(pw);
pw.decreaseIndent();
}
+
+ if (mRoleManagerAdapter != null && mRoleManagerAdapter instanceof RoleManagerAdapterImpl) {
+ RoleManagerAdapterImpl impl = (RoleManagerAdapterImpl) mRoleManagerAdapter;
+ pw.println("mRoleManager:");
+ pw.increaseIndent();
+ impl.dump(pw);
+ pw.decreaseIndent();
+ }
}
/**
@@ -3429,6 +3656,30 @@
call.setIntentExtras(extras);
}
+ private void setCallSourceToAnalytics(Call call, Intent originalIntent) {
+ if (originalIntent == null) {
+ return;
+ }
+
+ int callSource = originalIntent.getIntExtra(TelecomManager.EXTRA_CALL_SOURCE,
+ Analytics.CALL_SOURCE_UNSPECIFIED);
+
+ // Call source is only used by metrics, so we simply set it to Analytics directly.
+ call.getAnalytics().setCallSource(callSource);
+ }
+
+ private boolean isVoicemail(Uri callHandle, PhoneAccount phoneAccount) {
+ if (callHandle == null) {
+ return false;
+ }
+ if (PhoneAccount.SCHEME_VOICEMAIL.equals(callHandle.getScheme())) {
+ return true;
+ }
+ return phoneAccount != null && mPhoneAccountRegistrar.isVoiceMailNumber(
+ phoneAccount.getAccountHandle(),
+ callHandle.getSchemeSpecificPart());
+ }
+
/**
* Notifies the {@link android.telecom.ConnectionService} associated with a
* {@link PhoneAccountHandle} that the attempt to create a new connection has failed.
@@ -3504,8 +3755,12 @@
}
extras.putParcelable(TelecomManager.EXTRA_CALL_AUDIO_STATE,
mCallAudioManager.getCallAudioState());
- Call handoverToCall = startOutgoingCall(handoverFromCall.getHandle(), handoverToHandle,
- extras, getCurrentUserHandle(), null /* originalIntent */);
+ Call handoverToCall = createHandoverCall(handoverFromCall.getHandle(), handoverToHandle,
+ extras, getCurrentUserHandle());
+ if (handoverToCall == null) {
+ handoverFromCall.sendCallEvent(android.telecom.Call.EVENT_HANDOVER_FAILED, null);
+ return;
+ }
Log.addEvent(handoverFromCall, LogUtils.Events.START_HANDOVER,
"handOverFrom=%s, handOverTo=%s", handoverFromCall.getId(), handoverToCall.getId());
handoverFromCall.setHandoverDestinationCall(handoverToCall);
@@ -3518,6 +3773,110 @@
videoState);
}
+ 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
@@ -3560,7 +3919,7 @@
Call call = new Call(getNextCallId(), mContext,
this, mLock, mConnectionServiceRepository,
- mContactsAsyncHelper, mCallerInfoAsyncQueryFactory, mPhoneNumberUtilsAdapter,
+ mPhoneNumberUtilsAdapter,
handoverFromCall.getHandle(), null,
null, null,
Call.CALL_DIRECTION_OUTGOING, false,
@@ -3760,8 +4119,6 @@
this,
mLock,
mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
srcAddr,
null /* gatewayInfo */,
@@ -3826,7 +4183,7 @@
call.startCreateConnection(mPhoneAccountRegistrar);
}
- ConnectionServiceFocusManager getConnectionServiceFocusManager() {
+ public ConnectionServiceFocusManager getConnectionServiceFocusManager() {
return mConnectionSvrFocusMgr;
}
@@ -3892,6 +4249,9 @@
// 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);
}
@@ -3914,4 +4274,23 @@
}
}
}
+
+ public void resetConnectionTime(Call call) {
+ call.setConnectTimeMillis(System.currentTimeMillis());
+ if (mCalls.contains(call)) {
+ for (CallsManagerListener listener : mListeners) {
+ listener.onConnectionTimeChanged(call);
+ }
+ }
+ }
+
+ /**
+ * 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;
+ }
}
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index c0a71eb..cdc0209 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -92,4 +92,8 @@
@Override
public void onDisconnectedTonePlaying(boolean isTonePlaying) {
}
+
+ @Override
+ public void onConnectionTimeChanged(Call call) {
+ }
}
diff --git a/src/com/android/server/telecom/ConnectionServiceFocusManager.java b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
index 92570a0..9c0bfa2 100644
--- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java
+++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.content.ComponentName;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.telecom.Log;
@@ -30,14 +31,19 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class ConnectionServiceFocusManager {
private static final String TAG = "ConnectionSvrFocusMgr";
+ private static final int GET_CURRENT_FOCUS_TIMEOUT_MILLIS = 1000;
/** Factory interface used to create the {@link ConnectionServiceFocusManager} instance. */
public interface ConnectionServiceFocusManagerFactory {
- ConnectionServiceFocusManager create(CallsManagerRequester requester, Looper looper);
+ ConnectionServiceFocusManager create(CallsManagerRequester requester);
}
/**
@@ -271,10 +277,12 @@
private FocusManagerHandler mEventHandler;
public ConnectionServiceFocusManager(
- CallsManagerRequester callsManagerRequester, Looper looper) {
+ CallsManagerRequester callsManagerRequester) {
mCallsManagerRequester = callsManagerRequester;
mCallsManagerRequester.setCallsManagerListener(mCallsManagerListener);
- mEventHandler = new FocusManagerHandler(looper);
+ HandlerThread handlerThread = new HandlerThread(TAG);
+ handlerThread.start();
+ mEventHandler = new FocusManagerHandler(handlerThread.getLooper());
mCalls = new ArrayList<>();
}
@@ -298,8 +306,32 @@
* call is the current connection service focus. Also the state of the focus call must be one
* of {@link #PRIORITY_FOCUS_CALL_STATE}.
*/
- public CallFocus getCurrentFocusCall() {
- return mCurrentFocusCall;
+ public @Nullable CallFocus getCurrentFocusCall() {
+ if (mEventHandler.getLooper().isCurrentThread()) {
+ // return synchronously if we're on the same thread.
+ return mCurrentFocusCall;
+ }
+ final BlockingQueue<Optional<CallFocus>> currentFocusedCallQueue =
+ new LinkedBlockingQueue<>(1);
+ mEventHandler.post(() -> {
+ currentFocusedCallQueue.offer(
+ mCurrentFocusCall == null ? Optional.empty() : Optional.of(mCurrentFocusCall));
+ });
+ try {
+ Optional<CallFocus> syncCallFocus = currentFocusedCallQueue.poll(
+ GET_CURRENT_FOCUS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ if (syncCallFocus != null) {
+ return syncCallFocus.orElse(null);
+ } else {
+ Log.w(TAG, "Timed out waiting for synchronous current focus. Returning possibly"
+ + " inaccurate result");
+ return mCurrentFocusCall;
+ }
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Interrupted when waiting for synchronous current focus."
+ + " Returning possibly inaccurate result.");
+ return mCurrentFocusCall;
+ }
}
/** Returns the current connection service focus. */
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 6dd9a3a..6c63ecb 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -145,6 +145,26 @@
}
@Override
+ public void resetConnectionTime(String callId, Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, "CSW.rCCT");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ logIncoming("resetConnectionTime %s", callId);
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.resetConnectionTime(call);
+ } else {
+ // Log.w(this, "resetConnectionTime, unknown call id: %s", msg.obj);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ Log.endSession();
+ }
+ }
+
+ @Override
public void setVideoProvider(String callId, IVideoProvider videoProvider,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, "CSW.sVP");
diff --git a/src/com/android/server/telecom/DialerCodeReceiver.java b/src/com/android/server/telecom/DialerCodeReceiver.java
index 57f84a0..1cd922a 100644
--- a/src/com/android/server/telecom/DialerCodeReceiver.java
+++ b/src/com/android/server/telecom/DialerCodeReceiver.java
@@ -19,9 +19,12 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.os.UserHandle;
import android.telecom.Log;
import android.telecom.TelecomManager;
+import com.android.server.telecom.ui.TelecomDeveloperMenu;
+
/**
* Receiver for "secret codes" broadcast by Dialer.
*/
@@ -38,6 +41,9 @@
// Writes a MARK to the Telecom log.
public static final String TELECOM_SECRET_CODE_MARK = "826275";
+ // Opens the Telecom developer menu.
+ public static final String TELECOM_SECRET_CODE_MENU = "828282";
+
private final CallsManager mCallsManager;
DialerCodeReceiver(CallsManager callsManager) {
@@ -61,6 +67,11 @@
// add a non-call event.
Call currentCall = mCallsManager.getActiveCall();
Log.addEvent(currentCall, LogUtils.Events.USER_LOG_MARK);
+ } else if (intent.getData().getHost().equals(TELECOM_SECRET_CODE_MENU)) {
+ Log.i("DialerCodeReceiver", "Secret code used to open developer menu.");
+ Intent confirmIntent = new Intent(context, TelecomDeveloperMenu.class);
+ confirmIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivityAsUser(confirmIntent, UserHandle.CURRENT);
}
}
}
diff --git a/src/com/android/server/telecom/HeadsetMediaButton.java b/src/com/android/server/telecom/HeadsetMediaButton.java
index ec77289..ad95c34 100644
--- a/src/com/android/server/telecom/HeadsetMediaButton.java
+++ b/src/com/android/server/telecom/HeadsetMediaButton.java
@@ -26,14 +26,18 @@
import android.telecom.Log;
import android.view.KeyEvent;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* Static class to handle listening to the headset media buttons.
*/
public class HeadsetMediaButton extends CallsManagerListenerBase {
// Types of media button presses
- static final int SHORT_PRESS = 1;
- static final int LONG_PRESS = 2;
+ @VisibleForTesting
+ public static final int SHORT_PRESS = 1;
+ @VisibleForTesting
+ public static final int LONG_PRESS = 2;
private static final AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 9d20d4a..b02804d 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -47,7 +47,7 @@
// TODO: Needed for move to system service: import com.android.internal.R;
import com.android.internal.telecom.IInCallService;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.telecom.SystemStateProvider.SystemStateListener;
+import com.android.server.telecom.SystemStateHelper.SystemStateListener;
import java.util.ArrayList;
import java.util.Collection;
@@ -716,11 +716,6 @@
/** The in-call app implementations, see {@link IInCallService}. */
private final Map<InCallServiceInfo, IInCallService> mInCallServices = new ArrayMap<>();
- /**
- * The {@link ComponentName} of the bound In-Call UI Service.
- */
- private ComponentName mInCallUIComponentName;
-
private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId);
/** The {@link ComponentName} of the default InCall UI. */
@@ -729,7 +724,7 @@
private final Context mContext;
private final TelecomSystem.SyncRoot mLock;
private final CallsManager mCallsManager;
- private final SystemStateProvider mSystemStateProvider;
+ private final SystemStateHelper mSystemStateHelper;
private final Timeouts.Adapter mTimeoutsAdapter;
private final DefaultDialerCache mDefaultDialerCache;
private final EmergencyCallHelper mEmergencyCallHelper;
@@ -737,13 +732,13 @@
private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections;
public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
- SystemStateProvider systemStateProvider,
+ SystemStateHelper systemStateHelper,
DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter,
EmergencyCallHelper emergencyCallHelper) {
mContext = context;
mLock = lock;
mCallsManager = callsManager;
- mSystemStateProvider = systemStateProvider;
+ mSystemStateHelper = systemStateHelper;
mTimeoutsAdapter = timeoutsAdapter;
mDefaultDialerCache = defaultDialerCache;
mEmergencyCallHelper = emergencyCallHelper;
@@ -753,7 +748,7 @@
resources.getString(R.string.ui_default_package),
resources.getString(R.string.incall_default_class));
- mSystemStateProvider.addListener(mSystemStateListener);
+ mSystemStateHelper.addListener(mSystemStateListener);
}
@Override
@@ -960,6 +955,18 @@
updateCall(call);
}
+ @Override
+ public void onConnectionTimeChanged(Call call) {
+ Log.d(this, "onConnectionTimeChanged %s", call);
+ updateCall(call);
+ }
+
+ @Override
+ public void onIsVoipAudioModeChanged(Call call) {
+ Log.d(this, "onIsVoipAudioModeChanged %s", call);
+ updateCall(call);
+ }
+
void bringToForeground(boolean showDialpad) {
if (!mInCallServices.isEmpty()) {
for (IInCallService inCallService : mInCallServices.values()) {
@@ -1221,7 +1228,7 @@
}
private boolean shouldUseCarModeUI() {
- return mSystemStateProvider.isCarMode();
+ return mSystemStateHelper.isCarMode();
}
/**
@@ -1457,18 +1464,42 @@
pw.decreaseIndent();
}
+ /**
+ * @return The package name of the UI which is currently bound, or null if none.
+ */
+ private ComponentName getConnectedUi() {
+ InCallServiceInfo connectedUi = mInCallServices.keySet().stream().filter(
+ i -> i.getType() == IN_CALL_SERVICE_TYPE_DIALER_UI
+ || i.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI)
+ .findAny()
+ .orElse(null);
+ if (connectedUi != null) {
+ return connectedUi.mComponentName;
+ }
+ return null;
+ }
+
public boolean doesConnectedDialerSupportRinging() {
String ringingPackage = null;
- if (mInCallUIComponentName != null) {
- ringingPackage = mInCallUIComponentName.getPackageName().trim();
+
+ ComponentName connectedPackage = getConnectedUi();
+ if (connectedPackage != null) {
+ ringingPackage = connectedPackage.getPackageName().trim();
+ Log.d(this, "doesConnectedDialerSupportRinging: alreadyConnectedPackage=%s",
+ ringingPackage);
}
if (TextUtils.isEmpty(ringingPackage)) {
// The current in-call UI returned nothing, so lets use the default dialer.
- ringingPackage = DefaultDialerManager.getDefaultDialerApplication(
- mContext, UserHandle.USER_CURRENT);
+ ringingPackage = mDefaultDialerCache.getDefaultDialerApplication(
+ mCallsManager.getCurrentUserHandle().getIdentifier());
+ if (ringingPackage != null) {
+ Log.d(this, "doesConnectedDialerSupportRinging: notCurentlyConnectedPackage=%s",
+ ringingPackage);
+ }
}
if (TextUtils.isEmpty(ringingPackage)) {
+ Log.w(this, "doesConnectedDialerSupportRinging: no default dialer found; oh no!");
return false;
}
@@ -1478,11 +1509,15 @@
intent, PackageManager.GET_META_DATA,
mCallsManager.getCurrentUserHandle().getIdentifier());
if (entries.isEmpty()) {
+ Log.w(this, "doesConnectedDialerSupportRinging: couldn't find dialer's package info"
+ + " <sad trombone>");
return false;
}
ResolveInfo info = entries.get(0);
if (info.serviceInfo == null || info.serviceInfo.metaData == null) {
+ Log.w(this, "doesConnectedDialerSupportRinging: couldn't find dialer's metadata"
+ + " <even sadder trombone>");
return false;
}
diff --git a/src/com/android/server/telecom/InCallControllerFactory.java b/src/com/android/server/telecom/InCallControllerFactory.java
index e384af7..c3a7831 100644
--- a/src/com/android/server/telecom/InCallControllerFactory.java
+++ b/src/com/android/server/telecom/InCallControllerFactory.java
@@ -23,6 +23,6 @@
*/
public interface InCallControllerFactory {
InCallController create(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
- SystemStateProvider systemStateProvider, DefaultDialerCache defaultDialerCache,
+ SystemStateHelper systemStateHelper, DefaultDialerCache defaultDialerCache,
Timeouts.Adapter timeoutsAdapter, EmergencyCallHelper emergencyCallHelper);
}
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index a258aee..98d5bba 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -16,7 +16,9 @@
package com.android.server.telecom;
+import android.media.AudioAttributes;
import android.media.AudioManager;
+import android.media.MediaPlayer;
import android.media.ToneGenerator;
import android.os.Handler;
import android.os.Looper;
@@ -26,10 +28,15 @@
import com.android.internal.annotations.VisibleForTesting;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
/**
- * Play a call-related tone (ringback, busy signal, etc.) through ToneGenerator. To use, create an
- * instance using InCallTonePlayer.Factory (passing in the TONE_* constant for the tone you want)
- * and start() it. Implemented on top of {@link Thread} so that the tone plays in its own thread.
+ * Play a call-related tone (ringback, busy signal, etc.) either through ToneGenerator, or using a
+ * media resource file.
+ * To use, create an instance using InCallTonePlayer.Factory (passing in the TONE_* constant for
+ * the tone you want) and start() it. Implemented on top of {@link Thread} so that the tone plays in
+ * its own thread.
*/
public class InCallTonePlayer extends Thread {
@@ -41,12 +48,17 @@
private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
private final TelecomSystem.SyncRoot mLock;
private final ToneGeneratorFactory mToneGeneratorFactory;
+ private final MediaPlayerFactory mMediaPlayerFactory;
+ private final AudioManagerAdapter mAudioManagerAdapter;
- Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
- TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory) {
+ public Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
+ TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory,
+ MediaPlayerFactory mediaPlayerFactory, AudioManagerAdapter audioManagerAdapter) {
mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter;
mLock = lock;
mToneGeneratorFactory = toneGeneratorFactory;
+ mMediaPlayerFactory = mediaPlayerFactory;
+ mAudioManagerAdapter = audioManagerAdapter;
}
public void setCallAudioManager(CallAudioManager callAudioManager) {
@@ -55,7 +67,8 @@
public InCallTonePlayer createPlayer(int tone) {
return new InCallTonePlayer(tone, mCallAudioManager,
- mCallAudioRoutePeripheralAdapter, mLock, mToneGeneratorFactory);
+ mCallAudioRoutePeripheralAdapter, mLock, mToneGeneratorFactory,
+ mMediaPlayerFactory, mAudioManagerAdapter);
}
}
@@ -63,6 +76,55 @@
ToneGenerator get (int streamType, int volume);
}
+ public interface MediaPlayerAdapter {
+ void setLooping(boolean isLooping);
+ void setOnCompletionListener(MediaPlayer.OnCompletionListener listener);
+ void start();
+ void release();
+ int getDuration();
+ }
+
+ public static class MediaPlayerAdapterImpl implements MediaPlayerAdapter {
+ private MediaPlayer mMediaPlayer;
+
+ public MediaPlayerAdapterImpl(MediaPlayer mediaPlayer) {
+ mMediaPlayer = mediaPlayer;
+ }
+
+ @Override
+ public void setLooping(boolean isLooping) {
+ mMediaPlayer.setLooping(isLooping);
+ }
+
+ @Override
+ public void setOnCompletionListener(MediaPlayer.OnCompletionListener listener) {
+ mMediaPlayer.setOnCompletionListener(listener);
+ }
+
+ @Override
+ public void start() {
+ mMediaPlayer.start();
+ }
+
+ @Override
+ public void release() {
+ mMediaPlayer.release();
+ }
+
+ @Override
+ public int getDuration() {
+ return mMediaPlayer.getDuration();
+ }
+ }
+
+ public interface MediaPlayerFactory {
+ MediaPlayerAdapter get (int resourceId, AudioAttributes attributes);
+ }
+
+ public interface AudioManagerAdapter {
+ boolean isVolumeOverZero();
+ }
+
// The possible tones that we can play.
public static final int TONE_INVALID = 0;
public static final int TONE_BUSY = 1;
@@ -80,9 +142,12 @@
public static final int TONE_VOICE_PRIVACY = 13;
public static final int TONE_VIDEO_UPGRADE = 14;
+ private static final int TONE_RESOURCE_ID_UNDEFINED = -1;
+
private static final int RELATIVE_VOLUME_EMERGENCY = 100;
private static final int RELATIVE_VOLUME_HIPRI = 80;
private static final int RELATIVE_VOLUME_LOPRI = 50;
+ private static final int RELATIVE_VOLUME_UNDEFINED = -1;
// Buffer time (in msec) to add on to the tone timeout value. Needed mainly when the timeout
// value for a tone is exact duration of the tone itself.
@@ -111,6 +176,9 @@
/** Current state of the tone player. */
private int mState;
+ /** For tones which are not generated using ToneGenerator. */
+ private MediaPlayerAdapter mToneMediaPlayer = null;
+
/** Telecom lock object. */
private final TelecomSystem.SyncRoot mLock;
@@ -118,6 +186,8 @@
private final Object mSessionLock = new Object();
private final ToneGeneratorFactory mToneGenerator;
+ private final MediaPlayerFactory mMediaPlayerFactory;
+ private final AudioManagerAdapter mAudioManagerAdapter;
/**
* Initializes the tone player. Private; use the {@link Factory} to create tone players.
@@ -129,19 +199,22 @@
CallAudioManager callAudioManager,
CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
TelecomSystem.SyncRoot lock,
- ToneGeneratorFactory toneGeneratorFactory) {
+ ToneGeneratorFactory toneGeneratorFactory,
+ MediaPlayerFactory mediaPlayerFactor,
+ AudioManagerAdapter audioManagerAdapter) {
mState = STATE_OFF;
mToneId = toneId;
mCallAudioManager = callAudioManager;
mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter;
mLock = lock;
mToneGenerator = toneGeneratorFactory;
+ mMediaPlayerFactory = mediaPlayerFactor;
+ mAudioManagerAdapter = audioManagerAdapter;
}
/** {@inheritDoc} */
@Override
public void run() {
- ToneGenerator toneGenerator = null;
try {
synchronized (mSessionLock) {
if (mSession != null) {
@@ -154,6 +227,8 @@
final int toneType; // Passed to ToneGenerator.startTone.
final int toneVolume; // Passed to the ToneGenerator constructor.
final int toneLengthMillis;
+ final int mediaResourceId; // The resourceId of the tone to play. Used for media-based
+ // tones.
switch (mToneId) {
case TONE_BUSY:
@@ -161,11 +236,16 @@
toneType = ToneGenerator.TONE_SUP_BUSY;
toneVolume = RELATIVE_VOLUME_HIPRI;
toneLengthMillis = 4000;
+ mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
break;
case TONE_CALL_ENDED:
- toneType = ToneGenerator.TONE_PROP_PROMPT;
- toneVolume = RELATIVE_VOLUME_HIPRI;
- toneLengthMillis = 200;
+ // Don't use tone generator
+ toneType = ToneGenerator.TONE_UNKNOWN;
+ toneVolume = RELATIVE_VOLUME_UNDEFINED;
+ toneLengthMillis = 0;
+
+ // Use a tone resource file for a more rich, full-bodied tone experience.
+ mediaResourceId = R.raw.endcall;
break;
case TONE_OTA_CALL_ENDED:
// TODO: fill in
@@ -174,46 +254,55 @@
toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
toneVolume = RELATIVE_VOLUME_HIPRI;
toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
+ mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
break;
case TONE_CDMA_DROP:
toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
toneVolume = RELATIVE_VOLUME_LOPRI;
toneLengthMillis = 375;
+ mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
break;
case TONE_CONGESTION:
toneType = ToneGenerator.TONE_SUP_CONGESTION;
toneVolume = RELATIVE_VOLUME_HIPRI;
toneLengthMillis = 4000;
+ mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
break;
case TONE_INTERCEPT:
toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT;
toneVolume = RELATIVE_VOLUME_LOPRI;
toneLengthMillis = 500;
+ mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
break;
case TONE_OUT_OF_SERVICE:
toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
toneVolume = RELATIVE_VOLUME_LOPRI;
toneLengthMillis = 375;
+ mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
break;
case TONE_REDIAL:
toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE;
toneVolume = RELATIVE_VOLUME_LOPRI;
toneLengthMillis = 5000;
+ mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
break;
case TONE_REORDER:
toneType = ToneGenerator.TONE_CDMA_REORDER;
toneVolume = RELATIVE_VOLUME_HIPRI;
toneLengthMillis = 4000;
+ mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
break;
case TONE_RING_BACK:
toneType = ToneGenerator.TONE_SUP_RINGTONE;
toneVolume = RELATIVE_VOLUME_HIPRI;
toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
+ mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
break;
case TONE_UNOBTAINABLE_NUMBER:
toneType = ToneGenerator.TONE_SUP_ERROR;
toneVolume = RELATIVE_VOLUME_HIPRI;
toneLengthMillis = 4000;
+ mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
break;
case TONE_VOICE_PRIVACY:
// TODO: fill in.
@@ -223,6 +312,7 @@
toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
toneVolume = RELATIVE_VOLUME_HIPRI;
toneLengthMillis = 4000;
+ mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
break;
default:
throw new IllegalStateException("Bad toneId: " + mToneId);
@@ -233,16 +323,38 @@
stream = AudioManager.STREAM_BLUETOOTH_SCO;
}
+ if (toneType != ToneGenerator.TONE_UNKNOWN) {
+ playToneGeneratorTone(stream, toneVolume, toneType, toneLengthMillis);
+ } else if (mediaResourceId != TONE_RESOURCE_ID_UNDEFINED) {
+ playMediaTone(stream, mediaResourceId);
+ }
+ } finally {
+ cleanUpTonePlayer();
+ Log.endSession();
+ }
+ }
+
+ /**
+ * Play a tone generated by the {@link ToneGenerator}.
+ * @param stream The stream on which the tone will be played.
+ * @param toneVolume The volume of the tone.
+ * @param toneType The type of tone to play.
+ * @param toneLengthMillis How long to play the tone.
+ */
+ private void playToneGeneratorTone(int stream, int toneVolume, int toneType,
+ int toneLengthMillis) {
+ ToneGenerator toneGenerator = null;
+ try {
// If the ToneGenerator creation fails, just continue without it. It is a local audio
// signal, and is not as important.
try {
- Log.v(this, "Creating generator");
toneGenerator = mToneGenerator.get(stream, toneVolume);
} catch (RuntimeException e) {
Log.w(this, "Failed to create ToneGenerator.", e);
return;
}
+ Log.i(this, "playToneGeneratorTone: toneType=%d", toneType);
// TODO: Certain CDMA tones need to check the ringer-volume state before
// playing. See CallNotifier.InCallTonePlayer.
@@ -267,13 +379,61 @@
if (toneGenerator != null) {
toneGenerator.release();
}
- cleanUpTonePlayer();
- Log.endSession();
}
}
-
+
+ /**
+ * Plays an audio-file based media tone.
+ * @param stream The audio stream on which to play the tone.
+ * @param toneResourceId The resource ID of the tone to play.
+ */
+ private void playMediaTone(int stream, int toneResourceId) {
+ synchronized (this) {
+ if (mState != STATE_STOPPED) {
+ mState = STATE_ON;
+ }
+ Log.i(this, "playMediaTone: toneResourceId=%d", toneResourceId);
+ AudioAttributes attributes = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
+ .setLegacyStreamType(stream)
+ .build();
+ mToneMediaPlayer = mMediaPlayerFactory.get(toneResourceId, attributes);
+ mToneMediaPlayer.setLooping(false);
+ int durationMillis = mToneMediaPlayer.getDuration();
+ final CountDownLatch toneLatch = new CountDownLatch(1);
+ mToneMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ Log.i(this, "playMediaTone: toneResourceId=%d completed.", toneResourceId);
+ synchronized (this) {
+ mState = STATE_OFF;
+ }
+ mToneMediaPlayer.release();
+ mToneMediaPlayer = null;
+ toneLatch.countDown();
+ }
+ });
+ mToneMediaPlayer.start();
+ try {
+ // Wait for the tone to stop playing; timeout at 2x the length of the file just to
+ // be on the safe side.
+ toneLatch.await(durationMillis * 2, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException ie) {
+ Log.e(this, ie, "playMediaTone: tone playback interrupted.");
+ }
+ }
+
+ }
+
@VisibleForTesting
- public void startTone() {
+ public boolean startTone() {
+ // Skip playing the end call tone if the volume is silenced.
+ if (mToneId == TONE_CALL_ENDED && !mAudioManagerAdapter.isVolumeOverZero()) {
+ Log.i(this, "startTone: skip end-call tone as device is silenced.");
+ return false;
+ }
+
sTonesPlaying++;
if (sTonesPlaying == 1) {
mCallAudioManager.setIsTonePlaying(true);
@@ -287,6 +447,7 @@
}
super.start();
+ return true;
}
@Override
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index c4ccd8b..0ebbd2b 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -65,6 +65,7 @@
public static final String SET_ACTIVE = "SET_ACTIVE";
public static final String SET_HOLD = "SET_HOLD";
public static final String SET_RINGING = "SET_RINGING";
+ public static final String SET_ANSWERED = "SET_ANSWERED";
public static final String SET_DISCONNECTED = "SET_DISCONNECTED";
public static final String SET_DISCONNECTING = "SET_DISCONNECTING";
public static final String SET_SELECT_PHONE_ACCOUNT = "SET_SELECT_PHONE_ACCOUNT";
@@ -107,6 +108,8 @@
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 CONTROLLER_SCREENING_COMPLETED =
+ "CONTROLLER_SCREENING_COMPLETED";
public static final String SCREENING_COMPLETED = "SCREENING_COMPLETED";
public static final String BLOCK_CHECK_INITIATED = "BLOCK_CHECK_INITIATED";
public static final String BLOCK_CHECK_FINISHED = "BLOCK_CHECK_FINISHED";
@@ -136,6 +139,14 @@
public static final String HANDOVER_FAILED = "HANDOVER_FAILED";
public static final String START_RINBACK = "START_RINGBACK";
public static final String STOP_RINGBACK = "STOP_RINGBACK";
+ public static final String REDIRECTION_BOUND_USER = "REDIRECTION_BOUND_USER";
+ public static final String REDIRECTION_BOUND_CARRIER = "REDIRECTION_BOUND_CARRIER";
+ public static final String REDIRECTION_SENT_USER = "REDIRECTION_SENT_USER";
+ public static final String REDIRECTION_SENT_CARRIER = "REDIRECTION_SENT_CARRIER";
+ public static final String REDIRECTION_COMPLETED_USER = "REDIRECTION_COMPLETED_USER";
+ public static final String REDIRECTION_COMPLETED_CARRIER = "REDIRECTION_COMPLETED_CARRIER";
+ public static final String REDIRECTION_TIMED_OUT_USER = "REDIRECTION_TIMED_OUT_USER";
+ public static final String REDIRECTION_TIMED_OUT_CARRIER = "REDIRECTION_TIMED_OUT_CARRIER";
public static class Timings {
public static final String ACCEPT_TIMING = "accept";
@@ -150,6 +161,8 @@
public static final String BLOCK_CHECK_FINISHED_TIMING = "block_check_finished";
public static final String FILTERING_COMPLETED_TIMING = "filtering_completed";
public static final String FILTERING_TIMED_OUT_TIMING = "filtering_timed_out";
+ public static final String START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING =
+ "start_connection_to_request_disconnect";
private static final TimedEventPair[] sTimedEvents = {
new TimedEventPair(REQUEST_ACCEPT, SET_ACTIVE, ACCEPT_TIMING),
@@ -170,6 +183,8 @@
FILTERING_COMPLETED_TIMING),
new TimedEventPair(FILTERING_INITIATED, FILTERING_TIMED_OUT,
FILTERING_TIMED_OUT_TIMING, 6000L),
+ new TimedEventPair(START_CONNECTION, REQUEST_DISCONNECT,
+ START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING),
};
}
}
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index 77598c8..7eb8f0e 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -102,6 +102,10 @@
properties |= android.telecom.Call.Details.PROPERTY_ENTERPRISE_CALL;
}
+ if (call.getIsVoipAudioMode()) {
+ properties |= android.telecom.Call.Details.PROPERTY_VOIP_AUDIO_MODE;
+ }
+
// If this is a single-SIM device, the "default SIM" will always be the only SIM.
boolean isDefaultSmsAccount = phoneAccountRegistrar != null &&
phoneAccountRegistrar.isUserSelectedSmsPhoneAccount(call.getTargetPhoneAccount());
@@ -226,6 +230,8 @@
state = android.telecom.Call.STATE_HOLDING;
break;
case CallState.RINGING:
+ case CallState.ANSWERED:
+ // TODO: does in-call UI need to see ANSWERED?
state = android.telecom.Call.STATE_RINGING;
break;
case CallState.SELECT_PHONE_ACCOUNT:
@@ -345,7 +351,10 @@
android.telecom.Call.Details.PROPERTY_ASSISTED_DIALING_USED,
Connection.PROPERTY_IS_RTT,
- android.telecom.Call.Details.PROPERTY_RTT
+ android.telecom.Call.Details.PROPERTY_RTT,
+
+ Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL,
+ android.telecom.Call.Details.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL
};
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 d84fca5..196b8ad 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -135,7 +135,7 @@
CharSequence getAppLabel(String packageName);
}
- private static final String FILE_NAME = "phone-account-registrar-state.xml";
+ public static final String FILE_NAME = "phone-account-registrar-state.xml";
@VisibleForTesting
public static final int EXPECTED_STATE_VERSION = 9;
@@ -815,7 +815,7 @@
sb.append("[").append(account1.getAccountHandle());
appendDiff(sb, "addr", Log.piiHandle(account1.getAddress()),
Log.piiHandle(account2.getAddress()));
- appendDiff(sb, "cap", account1.getCapabilities(), account2.getCapabilities());
+ appendDiff(sb, "cap", account1.capabilitiesToString(), account2.capabilitiesToString());
appendDiff(sb, "hl", account1.getHighlightColor(), account2.getHighlightColor());
appendDiff(sb, "lbl", account1.getLabel(), account2.getLabel());
appendDiff(sb, "desc", account1.getShortDescription(), account2.getShortDescription());
diff --git a/src/com/android/server/telecom/QuickResponseUtils.java b/src/com/android/server/telecom/QuickResponseUtils.java
index 5f8269d..84d2636 100644
--- a/src/com/android/server/telecom/QuickResponseUtils.java
+++ b/src/com/android/server/telecom/QuickResponseUtils.java
@@ -117,4 +117,48 @@
Log.d(LOG_TAG, "maybeMigrateLegacyQuickResponses() - Done.");
return;
}
+
+ /**
+ * Determine if the user has changed any of the quick responses back to exactly the same text as
+ * the default text. If they did, clear the preference so we'll rely on the default value and
+ * still be able to re-translate automatically when language changes occur.
+ *
+ * @param context The current context.
+ * @param prefs The quick response shared prefs.
+ */
+ public static void maybeResetQuickResponses(Context context, SharedPreferences prefs) {
+ final Resources res = context.getResources();
+
+ String defaultResponse1 = res.getString(R.string.respond_via_sms_canned_response_1);
+ String currentValue1 = prefs.getString(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_1, "");
+ if (currentValue1.equals(defaultResponse1)) {
+ prefs.edit().remove(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_1).apply();
+ Log.i(QuickResponseUtils.class,
+ "maybeResetQuickResponses: response 1 is identical to default; clear pref.");
+ }
+
+ String defaultResponse2 = res.getString(R.string.respond_via_sms_canned_response_2);
+ String currentValue2 = prefs.getString(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_2, "");
+ if (currentValue2.equals(defaultResponse2)) {
+ prefs.edit().remove(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_2).apply();
+ Log.i(QuickResponseUtils.class,
+ "maybeResetQuickResponses: response 2 is identical to default; clear pref.");
+ }
+
+ String defaultResponse3 = res.getString(R.string.respond_via_sms_canned_response_3);
+ String currentValue3 = prefs.getString(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_3, "");
+ if (currentValue3.equals(defaultResponse3)) {
+ prefs.edit().remove(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_3).apply();
+ Log.i(QuickResponseUtils.class,
+ "maybeResetQuickResponses: response 3 is identical to default; clear pref.");
+ }
+
+ String defaultResponse4 = res.getString(R.string.respond_via_sms_canned_response_4);
+ String currentValue4 = prefs.getString(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_4, "");
+ if (currentValue4.equals(defaultResponse4)) {
+ prefs.edit().remove(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_4).apply();
+ Log.i(QuickResponseUtils.class,
+ "maybeResetQuickResponses: response 4 is identical to default; clear pref.");
+ }
+ }
}
diff --git a/src/com/android/server/telecom/RespondViaSmsManager.java b/src/com/android/server/telecom/RespondViaSmsManager.java
index 964f6ad..4c13222 100644
--- a/src/com/android/server/telecom/RespondViaSmsManager.java
+++ b/src/com/android/server/telecom/RespondViaSmsManager.java
@@ -18,10 +18,14 @@
// TODO: Needed for move to system service: import com.android.internal.R;
import com.android.internal.os.SomeArgs;
-import com.android.internal.telephony.SmsApplication;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Handler;
@@ -45,30 +49,37 @@
* Helper class to manage the "Respond via Message" feature for incoming calls.
*/
public class RespondViaSmsManager extends CallsManagerListenerBase {
- private static final int MSG_SHOW_SENT_TOAST = 2;
+ private static final String ACTION_MESSAGE_SENT = "com.android.server.telecom.MESSAGE_SENT";
+
+ private static final class MessageSentReceiver extends BroadcastReceiver {
+ private final String mContactName;
+ private final int mNumMessageParts;
+ private int mNumMessagesSent = 0;
+ MessageSentReceiver(String contactName, int numMessageParts) {
+ mContactName = contactName;
+ mNumMessageParts = numMessageParts;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (getResultCode() == Activity.RESULT_OK) {
+ mNumMessagesSent++;
+ if (mNumMessagesSent == mNumMessageParts) {
+ showMessageResultToast(mContactName, context, true);
+ context.unregisterReceiver(this);
+ }
+ } else {
+ context.unregisterReceiver(this);
+ showMessageResultToast(mContactName, context, false);
+ Log.w(RespondViaSmsManager.class.getSimpleName(),
+ "Message failed with error %s", getResultCode());
+ }
+ }
+ }
private final CallsManager mCallsManager;
private final TelecomSystem.SyncRoot mLock;
- private final Handler mHandler = new Handler(Looper.getMainLooper()) {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_SHOW_SENT_TOAST: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- String toastMessage = (String) args.arg1;
- Context context = (Context) args.arg2;
- showMessageSentToast(toastMessage, context);
- } finally {
- args.recycle();
- }
- break;
- }
- }
- }
- };
-
public RespondViaSmsManager(CallsManager callsManager, TelecomSystem.SyncRoot lock) {
mCallsManager = callsManager;
mLock = lock;
@@ -105,6 +116,11 @@
final ArrayList<String> textMessages = new ArrayList<>(
QuickResponseUtils.NUM_CANNED_RESPONSES);
+ // Where the user has changed a quick response back to the same text as the
+ // original text, clear the shared pref. This ensures we always load the resource
+ // in the current active language.
+ QuickResponseUtils.maybeResetQuickResponses(context, prefs);
+
// Note the default values here must agree with the corresponding
// android:defaultValue attributes in respond_via_sms_settings.xml.
textMessages.add(0, prefs.getString(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_1,
@@ -139,13 +155,15 @@
}
}
- private void showMessageSentToast(final String phoneNumber, final Context context) {
+ private static void showMessageResultToast(final String phoneNumber,
+ final Context context, boolean success) {
// ...and show a brief confirmation to the user (since
// otherwise it's hard to be sure that anything actually
// happened.)
final Resources res = context.getResources();
- final String formatString = res.getString(
- R.string.respond_via_sms_confirmation_format);
+ final String formatString = res.getString(success
+ ? R.string.respond_via_sms_confirmation_format
+ : R.string.respond_via_sms_failure_format);
final String confirmationMsg = String.format(formatString, phoneNumber);
int startingPosition = confirmationMsg.indexOf(phoneNumber);
int endingPosition = startingPosition + phoneNumber.length();
@@ -187,13 +205,20 @@
SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId);
try {
- smsManager.sendTextMessage(phoneNumber, null, textMessage, null /*sentIntent*/,
- null /*deliveryIntent*/);
-
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = !TextUtils.isEmpty(contactName) ? contactName : phoneNumber;
- args.arg2 = context;
- mHandler.obtainMessage(MSG_SHOW_SENT_TOAST, args).sendToTarget();
+ ArrayList<String> messageParts = smsManager.divideMessage(textMessage);
+ ArrayList<PendingIntent> sentIntents = new ArrayList<>(messageParts.size());
+ for (int i = 0; i < messageParts.size(); i++) {
+ Intent intent = new Intent(ACTION_MESSAGE_SENT);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(context, i, intent,
+ PendingIntent.FLAG_ONE_SHOT);
+ sentIntents.add(pendingIntent);
+ }
+ MessageSentReceiver receiver = new MessageSentReceiver(
+ !TextUtils.isEmpty(contactName) ? contactName : phoneNumber,
+ messageParts.size());
+ context.registerReceiver(receiver, new IntentFilter(ACTION_MESSAGE_SENT));
+ smsManager.sendMultipartTextMessage(phoneNumber, null, messageParts,
+ sentIntents/*sentIntent*/, null /*deliveryIntent*/);
} 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 2ea204a..3bee5f7 100644
--- a/src/com/android/server/telecom/RespondViaSmsSettings.java
+++ b/src/com/android/server/telecom/RespondViaSmsSettings.java
@@ -54,6 +54,7 @@
getPreferenceManager().setSharedPreferencesName(QuickResponseUtils.SHARED_PREFERENCES_NAME);
mPrefs = getPreferenceManager().getSharedPreferences();
+ QuickResponseUtils.maybeResetQuickResponses(this, mPrefs);
}
@Override
@@ -108,6 +109,9 @@
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);
+
return true; // means it's OK to update the state of the Preference with the new value
}
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 1d27f45..ee423da 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -39,6 +39,15 @@
*/
@VisibleForTesting
public class Ringer {
+ public static class VibrationEffectProxy {
+ public VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
+ return VibrationEffect.createWaveform(timings, amplitudes, repeat);
+ }
+
+ public VibrationEffect get(Uri ringtoneUri, Context context) {
+ return VibrationEffect.get(ringtoneUri, context);
+ }
+ }
@VisibleForTesting
public VibrationEffect mDefaultVibrationEffect;
@@ -89,6 +98,7 @@
private final Context mContext;
private final Vibrator mVibrator;
private final InCallController mInCallController;
+ private final VibrationEffectProxy mVibrationEffectProxy;
private InCallTonePlayer mCallWaitingPlayer;
private RingtoneFactory mRingtoneFactory;
@@ -115,6 +125,7 @@
AsyncRingtonePlayer asyncRingtonePlayer,
RingtoneFactory ringtoneFactory,
Vibrator vibrator,
+ VibrationEffectProxy vibrationEffectProxy,
InCallController inCallController) {
mSystemSettingsUtil = systemSettingsUtil;
@@ -126,12 +137,13 @@
mRingtonePlayer = asyncRingtonePlayer;
mRingtoneFactory = ringtoneFactory;
mInCallController = inCallController;
+ mVibrationEffectProxy = vibrationEffectProxy;
if (mContext.getResources().getBoolean(R.bool.use_simple_vibration_pattern)) {
- mDefaultVibrationEffect = VibrationEffect.createWaveform(SIMPLE_VIBRATION_PATTERN,
+ mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(SIMPLE_VIBRATION_PATTERN,
SIMPLE_VIBRATION_AMPLITUDE, REPEAT_SIMPLE_VIBRATION_AT);
} else {
- mDefaultVibrationEffect = VibrationEffect.createWaveform(PULSE_PATTERN,
+ mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(PULSE_PATTERN,
PULSE_AMPLITUDE, REPEAT_VIBRATION_AT);
}
}
@@ -167,7 +179,7 @@
if (endEarly) {
if (letDialerHandleRinging) {
- Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING);
+ Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Dialer handles");
}
Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " +
"isSelfManaged=%s, hasExternalRinger=%s", isTheaterModeOn,
@@ -188,9 +200,11 @@
mRingtonePlayer.play(mRingtoneFactory, foregroundCall);
effect = getVibrationEffectForCall(mRingtoneFactory, foregroundCall);
} else {
- Log.i(this, "startRinging: skipping because ringer would not be audible. " +
+ String reason = String.format(
"isVolumeOverZero=%s, shouldRingForContact=%s, isRingtonePresent=%s",
isVolumeOverZero, shouldRingForContact, isRingtonePresent);
+ Log.i(this, "startRinging: skipping because ringer would not be audible. " + reason);
+ Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Inaudible: " + reason);
effect = mDefaultVibrationEffect;
}
@@ -209,7 +223,7 @@
Ringtone ringtone = factory.getRingtone(call);
Uri ringtoneUri = ringtone != null ? ringtone.getUri() : null;
if (ringtoneUri != null) {
- effect = VibrationEffect.get(ringtoneUri, mContext);
+ effect = mVibrationEffectProxy.get(ringtoneUri, mContext);
}
if (effect == null) {
@@ -219,12 +233,16 @@
}
public void startCallWaiting(Call call) {
+ startCallWaiting(call, null);
+ }
+
+ public void startCallWaiting(Call call, String reason) {
if (mSystemSettingsUtil.isTheaterModeOn(mContext)) {
return;
}
if (mInCallController.doesConnectedDialerSupportRinging()) {
- Log.addEvent(call, LogUtils.Events.SKIP_RINGING);
+ Log.addEvent(call, LogUtils.Events.SKIP_RINGING, "Dialer handles");
return;
}
@@ -238,7 +256,7 @@
stopRinging();
if (mCallWaitingPlayer == null) {
- Log.addEvent(call, LogUtils.Events.START_CALL_WAITING_TONE);
+ Log.addEvent(call, LogUtils.Events.START_CALL_WAITING_TONE, reason);
mCallWaitingCall = call;
mCallWaitingPlayer =
mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
diff --git a/src/com/android/server/telecom/RoleManagerAdapter.java b/src/com/android/server/telecom/RoleManagerAdapter.java
new file mode 100644
index 0000000..f6cdc6c
--- /dev/null
+++ b/src/com/android/server/telecom/RoleManagerAdapter.java
@@ -0,0 +1,97 @@
+/*
+ * 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;
+
+import android.os.UserHandle;
+
+import java.util.List;
+
+/**
+ * Provides a means of wrapping {@code RoleManager} operations which Telecom uses to aid in testing
+ * and remove direct dependencies.
+ */
+public interface RoleManagerAdapter {
+ /**
+ * Returns the package name of the app which fills the {@link android.app.role.RoleManager} call
+ * redirection role.
+ * @return the package name of the app filling the role, {@code null} otherwise}.
+ */
+ String getDefaultCallRedirectionApp();
+
+ /**
+ * Override the {@link android.app.role.RoleManager} call redirection app with another value.
+ * Used for testing purposes only.
+ * @param packageName Package name of the app to fill the call redirection role. Where
+ * {@code null}, the override is removed.
+ */
+ void setTestDefaultCallRedirectionApp(String packageName);
+
+ /**
+ * Returns the package name of the app which fills the {@link android.app.role.RoleManager} call
+ * screening role.
+ * @return the package name of the app filling the role, {@code null} otherwise}.
+ */
+ String getDefaultCallScreeningApp();
+
+ /**
+ * Override the {@link android.app.role.RoleManager} call screening app with another value.
+ * Used for testing purposes only.
+ * @param packageName Package name of the app to fill the call screening role. Where
+ * {@code null}, the override is removed.
+ */
+ void setTestDefaultCallScreeningApp(String packageName);
+
+ /**
+ * Retrieves a list of package names of the app(s) which fill the
+ * {@link android.app.role.RoleManager} companion device role.
+ * @return List of package names filling the role, or empty list if there are none.
+ */
+ List<String> getCallCompanionApps();
+
+ /**
+ * Set a package to be added to the list of the {@link android.app.role.RoleManager} companion
+ * apps. Used for testing purposes only.
+ * @param packageName Package name of the app to be added or removed as an override call
+ * companion app.
+ * @param isAdded {@code true} if the specified package should be added, {@code false} if it
+ * should be removed.
+ */
+ void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded);
+
+ /**
+ * Returns the package name of the app which fills the {@link android.app.role.RoleManager}
+ * projection mode role.
+ * @return Package name of the car more app or {@code null} if there are no apps that fill this
+ * role.
+ */
+ String getCarModeDialerApp();
+
+ /**
+ * Override the {@link android.app.role.RoleManager} automotive app with another value.
+ * Used for testing purposes only.
+ * @param packageName Package name of the app to fill the automotive app role. 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.
+ */
+ void setCurrentUserHandle(UserHandle currentUserHandle);
+}
diff --git a/src/com/android/server/telecom/RoleManagerAdapterImpl.java b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
new file mode 100644
index 0000000..c912cc8
--- /dev/null
+++ b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
@@ -0,0 +1,173 @@
+/*
+ * 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;
+
+import android.os.UserHandle;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class RoleManagerAdapterImpl implements RoleManagerAdapter {
+ // TODO: replace with actual role manager const.
+ private static final String ROLE_CALL_REDIRECTION = "android.app.role.PROXY_CALLING_APP";
+ // TODO: replace with actual role manager const.
+ private static final String ROLE_CAR_MODE_DIALER = "android.app.role.ROLE_CAR_MODE_DIALER";
+ // TODO: replace with actual role manager const.
+ private static final String ROLE_CALL_SCREENING = "android.app.role.CALL_SCREENING";
+ // TODO: replace with actual role manager const.
+ private static final String ROLE_CALL_COMPANION_APP =
+ "android.app.role.ROLE_CALL_COMPANION_APP";
+
+ private String mOverrideDefaultCallRedirectionApp = null;
+ private String mOverrideDefaultCallScreeningApp = null;
+ private String mOverrideDefaultCarModeApp = null;
+ private List<String> mOverrideCallCompanionApps = new ArrayList<>();
+ private UserHandle mCurrentUserHandle;
+
+ public RoleManagerAdapterImpl() {
+ }
+
+ @Override
+ public String getDefaultCallRedirectionApp() {
+ if (mOverrideDefaultCallRedirectionApp != null) {
+ return mOverrideDefaultCallRedirectionApp;
+ }
+ return getRoleManagerCallRedirectionApp();
+ }
+
+ @Override
+ public void setTestDefaultCallRedirectionApp(String packageName) {
+ mOverrideDefaultCallRedirectionApp = packageName;
+ }
+
+ @Override
+ public String getDefaultCallScreeningApp() {
+ if (mOverrideDefaultCallScreeningApp != null) {
+ return mOverrideDefaultCallScreeningApp;
+ }
+ return getRoleManagerCallScreeningApp();
+ }
+
+ @Override
+ public void setTestDefaultCallScreeningApp(String packageName) {
+ mOverrideDefaultCallScreeningApp = packageName;
+ }
+
+ @Override
+ public List<String> getCallCompanionApps() {
+ List<String> callCompanionApps = getRoleManagerCallCompanionApps();
+ callCompanionApps.addAll(mOverrideCallCompanionApps);
+ return callCompanionApps;
+ }
+
+ @Override
+ public void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded) {
+ if (isAdded) {
+ mOverrideCallCompanionApps.add(packageName);
+ } else {
+ mOverrideCallCompanionApps.remove(packageName);
+ }
+ }
+
+ @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;
+ }
+
+ private String getRoleManagerCallRedirectionApp() {
+ // TODO: Link in RoleManager
+ return null;
+ }
+
+ private String getRoleManagerCallScreeningApp() {
+ // TODO: Link in RoleManager
+ return null;
+ }
+
+ private String getRoleManagerCarModeDialerApp() {
+ // TODO: Link in RoleManager
+ return null;
+ }
+
+ private List<String> getRoleManagerCallCompanionApps() {
+ // TODO: Link in RoleManager
+ return Collections.emptyList();
+ }
+
+ /**
+ * Dumps the state of the {@link InCallController}.
+ *
+ * @param pw The {@code IndentingPrintWriter} to write the state to.
+ */
+ public void dump(IndentingPrintWriter pw) {
+ pw.print("DefaultCallRedirectionApp: ");
+ if (mOverrideDefaultCallRedirectionApp != null) {
+ pw.print("(override ");
+ pw.print(mOverrideDefaultCallRedirectionApp);
+ pw.print(") ");
+ pw.print(getRoleManagerCallRedirectionApp());
+ }
+ pw.println();
+
+ pw.print("DefaultCallScreeningApp: ");
+ if (mOverrideDefaultCallScreeningApp != null) {
+ pw.print("(override ");
+ pw.print(mOverrideDefaultCallScreeningApp);
+ pw.print(") ");
+ pw.print(getRoleManagerCallScreeningApp());
+ }
+ pw.println();
+
+ pw.print("DefaultCarModeDialerApp: ");
+ if (mOverrideDefaultCallScreeningApp != null) {
+ pw.print("(override ");
+ pw.print(mOverrideDefaultCarModeApp);
+ pw.print(") ");
+ pw.print(getRoleManagerCarModeDialerApp());
+ }
+ pw.println();
+
+ pw.print("DefaultCallCompanionApps: ");
+ if (mOverrideDefaultCallScreeningApp != null) {
+ pw.print("(override ");
+ pw.print(mOverrideCallCompanionApps.stream().collect(Collectors.joining(", ")));
+ pw.print(") ");
+ List<String> appsInRole = getRoleManagerCallCompanionApps();
+ if (appsInRole != null) {
+ pw.print(appsInRole.stream().collect(Collectors.joining(", ")));
+ }
+ }
+ pw.println();
+ }
+}
diff --git a/src/com/android/server/telecom/ServiceBinder.java b/src/com/android/server/telecom/ServiceBinder.java
index f15570b..cf5407d 100644
--- a/src/com/android/server/telecom/ServiceBinder.java
+++ b/src/com/android/server/telecom/ServiceBinder.java
@@ -39,7 +39,7 @@
* Subclasses supply the service intent and component name and this class will invoke protected
* methods when the class is bound, unbound, or upon failure.
*/
-abstract class ServiceBinder {
+public abstract class ServiceBinder {
/**
* Callback to notify after a binding succeeds or fails.
diff --git a/src/com/android/server/telecom/SystemSettingsUtil.java b/src/com/android/server/telecom/SystemSettingsUtil.java
index 3c75e4d..97659a8 100644
--- a/src/com/android/server/telecom/SystemSettingsUtil.java
+++ b/src/com/android/server/telecom/SystemSettingsUtil.java
@@ -36,4 +36,14 @@
return Settings.System.getInt(context.getContentResolver(),
Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
}
+
+ public boolean isEnhancedCallBlockingEnabled(Context context) {
+ return Settings.System.getInt(context.getContentResolver(),
+ Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, 0) != 0;
+ }
+
+ public boolean setEnhancedCallBlockingEnabled(Context context, boolean enabled) {
+ return Settings.System.putInt(context.getContentResolver(),
+ Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, enabled ? 1 : 0);
+ }
}
diff --git a/src/com/android/server/telecom/SystemStateHelper.java b/src/com/android/server/telecom/SystemStateHelper.java
new file mode 100644
index 0000000..69a46c6
--- /dev/null
+++ b/src/com/android/server/telecom/SystemStateHelper.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2015 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.app.UiModeManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.telecom.Log;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Provides various system states to the rest of the telecom codebase.
+ */
+public class SystemStateHelper {
+ public static interface SystemStateListener {
+ public void onCarModeChanged(boolean isCarMode);
+ }
+
+ private final Context mContext;
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ 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();
+ } else {
+ Log.w(this, "Unexpected intent received: %s", intent.getAction());
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+ };
+
+ private Set<SystemStateListener> mListeners = new CopyOnWriteArraySet<>();
+ private boolean mIsCarMode;
+
+ public SystemStateHelper(Context context) {
+ mContext = context;
+
+ IntentFilter intentFilter = new IntentFilter(UiModeManager.ACTION_ENTER_CAR_MODE);
+ intentFilter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE);
+ mContext.registerReceiver(mBroadcastReceiver, intentFilter);
+ Log.i(this, "Registering car mode receiver: %s", intentFilter);
+
+ mIsCarMode = getSystemCarMode();
+ }
+
+ public void addListener(SystemStateListener listener) {
+ if (listener != null) {
+ mListeners.add(listener);
+ }
+ }
+
+ public boolean removeListener(SystemStateListener listener) {
+ return mListeners.remove(listener);
+ }
+
+ public boolean isCarMode() {
+ return mIsCarMode;
+ }
+
+ public boolean isDeviceAtEar() {
+ return isDeviceAtEar(mContext);
+ }
+
+ /**
+ * Returns a guess whether the phone is up to the user's ear. Use the proximity sensor and
+ * the gravity sensor to make a guess
+ * @return true if the proximity sensor is activated, the magnitude of gravity in directions
+ * parallel to the screen is greater than some configurable threshold, and the
+ * y-component of gravity isn't less than some other configurable threshold.
+ */
+ public static boolean isDeviceAtEar(Context context) {
+ SensorManager sm = context.getSystemService(SensorManager.class);
+ if (sm == null) {
+ return false;
+ }
+ Sensor grav = sm.getDefaultSensor(Sensor.TYPE_GRAVITY);
+ Sensor proximity = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+ if (grav == null || proximity == null) {
+ return false;
+ }
+
+ AtomicBoolean result = new AtomicBoolean(true);
+ CountDownLatch gravLatch = new CountDownLatch(1);
+ CountDownLatch proxLatch = new CountDownLatch(1);
+
+ final double xyGravityThreshold = context.getResources().getFloat(
+ R.dimen.device_on_ear_xy_gravity_threshold);
+ final double yGravityNegativeThreshold = context.getResources().getFloat(
+ R.dimen.device_on_ear_y_gravity_negative_threshold);
+
+ SensorEventListener listener = new SensorEventListener() {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if (event.sensor.getType() == Sensor.TYPE_GRAVITY) {
+ if (gravLatch.getCount() == 0) {
+ return;
+ }
+ double xyMag = Math.sqrt(event.values[0] * event.values[0]
+ + event.values[1] * event.values[1]);
+ if (xyMag < xyGravityThreshold
+ || event.values[1] < yGravityNegativeThreshold) {
+ result.set(false);
+ }
+ gravLatch.countDown();
+ } else if (event.sensor.getType() == Sensor.TYPE_PROXIMITY) {
+ if (proxLatch.getCount() == 0) {
+ return;
+ }
+ if (event.values[0] >= proximity.getMaximumRange()) {
+ result.set(false);
+ }
+ proxLatch.countDown();
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+ };
+
+ try {
+ sm.registerListener(listener, grav, SensorManager.SENSOR_DELAY_FASTEST);
+ sm.registerListener(listener, proximity, SensorManager.SENSOR_DELAY_FASTEST);
+ boolean accelValid = gravLatch.await(100, TimeUnit.MILLISECONDS);
+ boolean proxValid = proxLatch.await(100, TimeUnit.MILLISECONDS);
+ if (accelValid && proxValid) {
+ return result.get();
+ } else {
+ Log.w(SystemStateHelper.class.getSimpleName(),
+ "Timed out waiting for sensors: %b %b", accelValid, proxValid);
+ return false;
+ }
+ } catch (InterruptedException e) {
+ return false;
+ } finally {
+ sm.unregisterListener(listener);
+ }
+ }
+
+ 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() {
+ for (SystemStateListener listener : mListeners) {
+ listener.onCarModeChanged(mIsCarMode);
+ }
+ }
+
+ /**
+ * Checks the system for the current car mode.
+ *
+ * @return True if in car mode, false otherwise.
+ */
+ private boolean getSystemCarMode() {
+ UiModeManager uiModeManager =
+ (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
+
+ if (uiModeManager != null) {
+ return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR;
+ }
+
+ return false;
+ }
+}
diff --git a/src/com/android/server/telecom/SystemStateProvider.java b/src/com/android/server/telecom/SystemStateProvider.java
deleted file mode 100644
index e1938b1..0000000
--- a/src/com/android/server/telecom/SystemStateProvider.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2015 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.app.UiModeManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
-import android.telecom.Log;
-
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
-
-/**
- * Provides various system states to the rest of the telecom codebase. So far, that's only car-mode.
- */
-public class SystemStateProvider {
-
- public static interface SystemStateListener {
- public void onCarModeChanged(boolean isCarMode);
- }
-
- private final Context mContext;
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- 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();
- } else {
- Log.w(this, "Unexpected intent received: %s", intent.getAction());
- }
- } finally {
- Log.endSession();
- }
- }
- };
-
- private Set<SystemStateListener> mListeners = new CopyOnWriteArraySet<>();
- private boolean mIsCarMode;
-
- public SystemStateProvider(Context context) {
- mContext = context;
-
- IntentFilter intentFilter = new IntentFilter(UiModeManager.ACTION_ENTER_CAR_MODE);
- intentFilter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE);
- mContext.registerReceiver(mBroadcastReceiver, intentFilter);
- Log.i(this, "Registering car mode receiver: %s", intentFilter);
-
- mIsCarMode = getSystemCarMode();
- }
-
- public void addListener(SystemStateListener listener) {
- if (listener != null) {
- mListeners.add(listener);
- }
- }
-
- public boolean removeListener(SystemStateListener listener) {
- return mListeners.remove(listener);
- }
-
- public boolean isCarMode() {
- return mIsCarMode;
- }
-
- 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() {
- for (SystemStateListener listener : mListeners) {
- listener.onCarModeChanged(mIsCarMode);
- }
- }
-
- /**
- * Checks the system for the current car mode.
- *
- * @return True if in car mode, false otherwise.
- */
- private boolean getSystemCarMode() {
- UiModeManager uiModeManager =
- (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
-
- if (uiModeManager != null) {
- return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR;
- }
-
- return false;
- }
-}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index ea55e63..14d54b0 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -29,16 +29,22 @@
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
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.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Process;
import android.os.UserHandle;
+import android.provider.Settings;
+import android.telecom.CallScreeningService;
import android.telecom.Log;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -52,6 +58,7 @@
import com.android.internal.telecom.ITelecomService;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.telecom.components.ChangeDefaultCallScreeningApp;
import com.android.server.telecom.components.UserCallIntentProcessorFactory;
import com.android.server.telecom.settings.BlockedNumbersActivity;
@@ -78,8 +85,29 @@
}
}
+ public interface SettingsSecureAdapter {
+ void putStringForUser(ContentResolver resolver, String name, String value, int userHandle);
+
+ String getStringForUser(ContentResolver resolver, String name, int userHandle);
+ }
+
+ static class SettingsSecureAdapterImpl implements SettingsSecureAdapter {
+ @Override
+ public void putStringForUser(ContentResolver resolver, String name, String value,
+ int userHandle) {
+ Settings.Secure.putStringForUser(resolver, name, value, userHandle);
+ }
+
+ @Override
+ public String getStringForUser(ContentResolver resolver, String name, int userHandle) {
+ return Settings.Secure.getStringForUser(resolver, name, userHandle);
+ }
+ }
+
private static final String TIME_LINE_ARG = "timeline";
private static final int DEFAULT_VIDEO_STATE = -1;
+ private static final String PERMISSION_HANDLE_CALL_INTENT =
+ "android.permission.HANDLE_CALL_INTENT";
private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() {
@Override
@@ -440,6 +468,11 @@
if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
enforceRegisterMultiUser();
}
+ Bundle extras = account.getExtras();
+ if (extras != null
+ && extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING)) {
+ enforceRegisterSkipCallFiltering();
+ }
enforceUserHandleMatchesCaller(account.getAccountHandle());
final long token = Binder.clearCallingIdentity();
try {
@@ -1313,6 +1346,147 @@
}
}
+ /**
+ * @see android.telecom.TelecomManager#requestChangeDefaultCallScreeningApp
+ */
+ @Override
+ public void requestChangeDefaultCallScreeningApp(ComponentName componentName, String
+ callingPackage) {
+ try {
+ Log.startSession("TSI.rCDCSA");
+ synchronized (mLock) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ if (callingPackage.equals(componentName.getPackageName())) {
+ final Intent intent = new Intent(mContext,
+ ChangeDefaultCallScreeningApp.class);
+ intent.putExtra(
+ TelecomManager.EXTRA_DEFAULT_CALL_SCREENING_APP_COMPONENT_NAME,
+ componentName.flattenToString());
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ } else {
+ throw new SecurityException(
+ "calling package name does't match the package of the passed "
+ + "component name.");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#isDefaultCallScreeningApp
+ */
+ @Override
+ public boolean isDefaultCallScreeningApp(ComponentName componentName) {
+ try {
+ Log.startSession("TSI.iDCSA");
+ synchronized (mLock) {
+ if (componentName == null) {
+ return false;
+ }
+ mAppOpsManager
+ .checkPackage(Binder.getCallingUid(), componentName.getPackageName());
+
+ long token = Binder.clearCallingIdentity();
+ try {
+ final String defaultPackage = mSettingsSecureAdapter
+ .getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT,
+ UserHandle.USER_CURRENT);
+
+ if (!TextUtils.isEmpty(defaultPackage) && !TextUtils
+ .isEmpty(componentName.flattenToString()) && TextUtils
+ .equals(defaultPackage, componentName.flattenToString())) {
+ return true;
+ } else {
+ Log.d(this,
+ "Provided package name is not the current default Call Screening application.");
+ return false;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#setDefaultCallScreeningApp
+ */
+ @Override
+ public void setDefaultCallScreeningApp(ComponentName componentName) {
+ try {
+ Log.startSession("TSI.sDCSA");
+ enforcePermission(MODIFY_PHONE_STATE);
+ enforcePermission(WRITE_SECURE_SETTINGS);
+ synchronized (mLock) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ try {
+ mContext.getPackageManager().getApplicationInfo(
+ componentName.getPackageName(), 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalArgumentException(
+ "the specified package name does't exist componentName = " +
+ componentName);
+ }
+
+ Intent intent = new Intent(CallScreeningService.SERVICE_INTERFACE)
+ .setPackage(componentName.getPackageName());
+ List<ResolveInfo> entries = mContext.getPackageManager()
+ .queryIntentServicesAsUser(intent, 0,
+ mCallsManager.getCurrentUserHandle().getIdentifier());
+ if (entries.isEmpty()) {
+ throw new IllegalArgumentException(
+ "The specified package name doesn't have call screening services");
+ }
+
+ try {
+ ServiceInfo serviceInfo = mContext.getPackageManager().getServiceInfo(
+ componentName, 0);
+ if (!Manifest.permission.BIND_SCREENING_SERVICE.equals(serviceInfo
+ .permission)) {
+ throw new IllegalArgumentException(
+ "The passed component doesn't require " +
+ "BIND_SCREENING_SERVICE permission");
+ }
+
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalArgumentException(
+ "the specified component name does't exist componentName = "
+ + componentName);
+ }
+
+ final String oldComponentName = mSettingsSecureAdapter
+ .getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT,
+ UserHandle.USER_CURRENT);
+
+ broadcastCallScreeningAppChangedIntent(oldComponentName, false);
+
+ mSettingsSecureAdapter.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT,
+ componentName.flattenToString(), UserHandle.USER_CURRENT);
+
+ broadcastCallScreeningAppChangedIntent(componentName.flattenToString(),
+ true);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
@Override
public TelecomAnalytics dumpCallAnalytics() {
try {
@@ -1445,6 +1619,141 @@
Log.endSession();
}
}
+
+ /**
+ * See {@link TelecomManager#isInEmergencyCall()}
+ */
+ @Override
+ public boolean isInEmergencyCall() {
+ try {
+ Log.startSession("TSI.iIEC");
+ enforceModifyPermission();
+ synchronized (mLock) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ boolean isInEmergencyCall = mCallsManager.isInEmergencyCall();
+ Log.i(this, "isInEmergencyCall: %b", isInEmergencyCall);
+ return isInEmergencyCall;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ /**
+ * See {@link TelecomManager#handleCallIntent(Intent)} ()}
+ */
+ @Override
+ public void handleCallIntent(Intent intent) {
+ try {
+ Log.startSession("TSI.hCI");
+ synchronized (mLock) {
+ mContext.enforceCallingOrSelfPermission(PERMISSION_HANDLE_CALL_INTENT,
+ "handleCallIntent is for internal use only.");
+
+ long token = Binder.clearCallingIdentity();
+ try {
+ Log.i(this, "handleCallIntent: handling call intent");
+ mCallIntentProcessorAdapter.processOutgoingCallIntent(mContext,
+ mCallsManager, intent, null /* callingPackage */);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
+ public void setTestDefaultCallRedirectionApp(String packageName) {
+ try {
+ Log.startSession("TSI.sTDCRA");
+ enforceModifyPermission();
+ if (!Build.IS_USERDEBUG) {
+ throw new SecurityException("Test-only API.");
+ }
+ synchronized (mLock) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mCallsManager.getRoleManagerAdapter().setTestDefaultCallRedirectionApp(
+ packageName);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
+ public void setTestDefaultCallScreeningApp(String packageName) {
+ try {
+ Log.startSession("TSI.sTDCSA");
+ enforceModifyPermission();
+ if (!Build.IS_USERDEBUG) {
+ throw new SecurityException("Test-only API.");
+ }
+ synchronized (mLock) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mCallsManager.getRoleManagerAdapter().setTestDefaultCallScreeningApp(
+ packageName);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
+ public void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded) {
+ try {
+ Log.startSession("TSI.aORTCCA");
+ enforceModifyPermission();
+ if (!Build.IS_USERDEBUG) {
+ throw new SecurityException("Test-only API.");
+ }
+ synchronized (mLock) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mCallsManager.getRoleManagerAdapter().addOrRemoveTestCallCompanionApp(
+ packageName, isAdded);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @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();
+ }
+ }
};
/**
@@ -1495,6 +1804,7 @@
private final UserCallIntentProcessorFactory mUserCallIntentProcessorFactory;
private final DefaultDialerCache mDefaultDialerCache;
private final SubscriptionManagerAdapter mSubscriptionManagerAdapter;
+ private final SettingsSecureAdapter mSettingsSecureAdapter;
private final TelecomSystem.SyncRoot mLock;
public TelecomServiceImpl(
@@ -1505,6 +1815,7 @@
UserCallIntentProcessorFactory userCallIntentProcessorFactory,
DefaultDialerCache defaultDialerCache,
SubscriptionManagerAdapter subscriptionManagerAdapter,
+ SettingsSecureAdapter settingsSecureAdapter,
TelecomSystem.SyncRoot lock) {
mContext = context;
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
@@ -1518,6 +1829,7 @@
mDefaultDialerCache = defaultDialerCache;
mCallIntentProcessorAdapter = callIntentProcessorAdapter;
mSubscriptionManagerAdapter = subscriptionManagerAdapter;
+ mSettingsSecureAdapter = settingsSecureAdapter;
}
public ITelecomService.Stub getBinder() {
@@ -1671,6 +1983,13 @@
}
}
+ private void enforceRegisterSkipCallFiltering() {
+ if (!isCallerSystemApp()) {
+ throw new SecurityException(
+ "EXTRA_SKIP_CALL_FILTERING is only available to system apps.");
+ }
+ }
+
private void enforceUserHandleMatchesCaller(PhoneAccountHandle accountHandle) {
if (!Binder.getCallingUserHandle().equals(accountHandle.getUserHandle())) {
throw new SecurityException("Calling UserHandle does not match PhoneAccountHandle's");
@@ -1787,4 +2106,24 @@
// If only TX or RX were set (or neither), the video state is valid.
return remainingState == 0;
}
+
+ private void broadcastCallScreeningAppChangedIntent(String componentName,
+ boolean isDefault) {
+ if (TextUtils.isEmpty(componentName)) {
+ return;
+ }
+
+ ComponentName broadcastComponentName = ComponentName.unflattenFromString(componentName);
+
+ if (broadcastComponentName != null) {
+ Intent intent = new Intent(TelecomManager
+ .ACTION_DEFAULT_CALL_SCREENING_APP_CHANGED);
+ intent.putExtra(TelecomManager
+ .EXTRA_IS_DEFAULT_CALL_SCREENING_APP, isDefault);
+ intent.putExtra(TelecomManager
+ .EXTRA_DEFAULT_CALL_SCREENING_APP_COMPONENT_NAME, componentName);
+ intent.setPackage(broadcastComponentName.getPackageName());
+ mContext.sendBroadcast(intent);
+ }
+ }
}
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 3fd0e21..ffa6035 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -96,6 +96,8 @@
.addDataAuthority(DialerCodeReceiver.TELECOM_SECRET_CODE_DEBUG_OFF, null);
DIALER_SECRET_CODE_FILTER
.addDataAuthority(DialerCodeReceiver.TELECOM_SECRET_CODE_MARK, null);
+ DIALER_SECRET_CODE_FILTER
+ .addDataAuthority(DialerCodeReceiver.TELECOM_SECRET_CODE_MENU, null);
}
private static TelecomSystem INSTANCE = null;
@@ -193,6 +195,7 @@
PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
IncomingCallNotifier incomingCallNotifier,
InCallTonePlayer.ToneGeneratorFactory toneGeneratorFactory,
+ CallAudioRouteStateMachine.Factory callAudioRouteStateMachineFactory,
ClockProxy clockProxy) {
mContext = context.getApplicationContext();
LogUtils.initLogging(mContext);
@@ -227,7 +230,7 @@
}
});
BluetoothDeviceManager bluetoothDeviceManager = new BluetoothDeviceManager(mContext,
- new BluetoothAdapterProxy(), mLock);
+ new BluetoothAdapterProxy());
BluetoothRouteManager bluetoothRouteManager = new BluetoothRouteManager(mContext, mLock,
bluetoothDeviceManager, new Timeouts.Adapter());
BluetoothStateReceiver bluetoothStateReceiver = new BluetoothStateReceiver(
@@ -235,18 +238,22 @@
mContext.registerReceiver(bluetoothStateReceiver, BluetoothStateReceiver.INTENT_FILTER);
WiredHeadsetManager wiredHeadsetManager = new WiredHeadsetManager(mContext);
- SystemStateProvider systemStateProvider = new SystemStateProvider(mContext);
+ SystemStateHelper systemStateHelper = new SystemStateHelper(mContext);
mMissedCallNotifier = missedCallNotifierImplFactory
.makeMissedCallNotifierImpl(mContext, mPhoneAccountRegistrar, defaultDialerCache);
+ CallerInfoLookupHelper callerInfoLookupHelper =
+ new CallerInfoLookupHelper(context, callerInfoAsyncQueryFactory,
+ mContactsAsyncHelper, mLock);
+
EmergencyCallHelper emergencyCallHelper = new EmergencyCallHelper(mContext,
mContext.getResources().getString(R.string.ui_default_package), timeoutsAdapter);
InCallControllerFactory inCallControllerFactory = new InCallControllerFactory() {
@Override
public InCallController create(Context context, SyncRoot lock,
- CallsManager callsManager, SystemStateProvider systemStateProvider,
+ CallsManager callsManager, SystemStateHelper systemStateProvider,
DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter,
EmergencyCallHelper emergencyCallHelper) {
return new InCallController(context, lock, callsManager, systemStateProvider,
@@ -254,11 +261,12 @@
}
};
+ RoleManagerAdapter roleManagerAdapter = new RoleManagerAdapterImpl();
+
mCallsManager = new CallsManager(
mContext,
mLock,
- mContactsAsyncHelper,
- callerInfoAsyncQueryFactory,
+ callerInfoLookupHelper,
mMissedCallNotifier,
mPhoneAccountRegistrar,
headsetMediaButtonFactory,
@@ -268,7 +276,7 @@
audioServiceFactory,
bluetoothRouteManager,
wiredHeadsetManager,
- systemStateProvider,
+ systemStateHelper,
defaultDialerCache,
timeoutsAdapter,
asyncRingtonePlayer,
@@ -277,7 +285,10 @@
toneGeneratorFactory,
clockProxy,
bluetoothStateReceiver,
- inCallControllerFactory);
+ callAudioRouteStateMachineFactory,
+ new CallAudioModeStateMachine.Factory(),
+ inCallControllerFactory,
+ roleManagerAdapter);
mIncomingCallNotifier = incomingCallNotifier;
incomingCallNotifier.setCallsManagerProxy(new IncomingCallNotifier.CallsManagerProxy() {
@@ -327,6 +338,7 @@
},
defaultDialerCache,
new TelecomServiceImpl.SubscriptionManagerAdapterImpl(),
+ new TelecomServiceImpl.SettingsSecureAdapterImpl(),
mLock);
Log.endSession();
}
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index 5187641..9257654 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -18,6 +18,7 @@
import android.content.ContentResolver;
import android.provider.Settings;
+import android.telecom.CallRedirectionService;
import java.util.concurrent.TimeUnit;
/**
@@ -49,6 +50,14 @@
public long getEmergencyCallbackWindowMillis(ContentResolver cr) {
return Timeouts.getEmergencyCallbackWindowMillis(cr);
}
+
+ public long getUserDefinedCallRedirectionTimeoutMillis(ContentResolver cr) {
+ return Timeouts.getUserDefinedCallRedirectionTimeoutMillis(cr);
+ }
+
+ public long getCarrierCallRedirectionTimeoutMillis(ContentResolver cr) {
+ return Timeouts.getCarrierCallRedirectionTimeoutMillis(cr);
+ }
}
/** A prefix to use for all keys so to not clobber the global namespace. */
@@ -158,4 +167,23 @@
return get(contentResolver, "emergency_callback_window_millis",
TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES));
}
+
+ /**
+ * Returns the amount of time for an user-defined {@link CallRedirectionService}.
+ *
+ * @param contentResolver The content resolved.
+ */
+ public static long getUserDefinedCallRedirectionTimeoutMillis(ContentResolver contentResolver) {
+ return get(contentResolver, "user_defined_call_redirection_timeout",
+ 3000L /* 3 seconds */);
+ }
+
+ /**
+ * Returns the amount of time for a carrier {@link CallRedirectionService}.
+ *
+ * @param contentResolver The content resolved.
+ */
+ public static long getCarrierCallRedirectionTimeoutMillis(ContentResolver contentResolver) {
+ return get(contentResolver, "carrier_call_redirection_timeout", 3000L /* 3 seconds */);
+ }
}
diff --git a/src/com/android/server/telecom/VideoProviderProxy.java b/src/com/android/server/telecom/VideoProviderProxy.java
index 7d90a6d..6e1f01d 100644
--- a/src/com/android/server/telecom/VideoProviderProxy.java
+++ b/src/com/android/server/telecom/VideoProviderProxy.java
@@ -20,7 +20,6 @@
import android.app.AppOpsManager;
import android.content.Context;
import android.net.Uri;
-import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.Looper;
@@ -33,6 +32,7 @@
import android.text.TextUtils;
import android.view.Surface;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telecom.IVideoCallback;
import com.android.internal.telecom.IVideoProvider;
@@ -40,8 +40,6 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-import static android.Manifest.permission.CALL_PHONE;
-
/**
* Proxies video provider messages from {@link InCallService.VideoCall}
* implementations to the underlying {@link Connection.VideoProvider} implementation. Also proxies
@@ -55,7 +53,7 @@
/**
* Listener for Telecom components interested in callbacks from the video provider.
*/
- interface Listener {
+ public interface Listener {
void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile);
}
@@ -112,7 +110,7 @@
* @param call The current call.
* @throws RemoteException Remote exception.
*/
- VideoProviderProxy(TelecomSystem.SyncRoot lock,
+ public VideoProviderProxy(TelecomSystem.SyncRoot lock,
IVideoProvider videoProvider, Call call, CurrentUserProxy currentUserProxy)
throws RemoteException {
@@ -136,11 +134,16 @@
}
}
+ @VisibleForTesting
+ public VideoCallListenerBinder getVideoCallListenerBinder() {
+ return mVideoCallListenerBinder;
+ }
+
/**
* IVideoCallback stub implementation. An instance of this class receives callbacks from the
* {@code ConnectionService}'s video provider.
*/
- private final class VideoCallListenerBinder extends IVideoCallback.Stub {
+ public final class VideoCallListenerBinder extends IVideoCallback.Stub {
/**
* Proxies a request from the {@link #mConectionServiceVideoProvider} to the
* {@link InCallService} when a session modification request is received.
@@ -160,13 +163,14 @@
Analytics.RECEIVE_REMOTE_SESSION_MODIFY_REQUEST,
videoProfile.getVideoState());
- if (!mCall.isVideoCallingSupported() &&
- VideoProfile.isVideo(videoProfile.getVideoState())) {
- // If video calling is not supported by the phone account, and we receive
- // a request to upgrade to video, automatically reject it without informing
- // the InCallService.
-
- Log.addEvent(mCall, LogUtils.Events.SEND_VIDEO_RESPONSE, "video not supported");
+ if ((!mCall.isVideoCallingSupportedByPhoneAccount()
+ || !mCall.isLocallyVideoCapable())
+ && VideoProfile.isVideo(videoProfile.getVideoState())) {
+ // If video calling is not supported by the phone account, or is not
+ // locally video capable and we receive a request to upgrade to video,
+ // automatically reject it without informing the InCallService.
+ Log.addEvent(mCall, LogUtils.Events.SEND_VIDEO_RESPONSE,
+ "video not supported");
VideoProfile responseProfile = new VideoProfile(
VideoProfile.STATE_AUDIO_ONLY);
try {
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index ce35587..4800832 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -18,25 +18,22 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.telecom.Log;
import com.android.server.telecom.BluetoothAdapterProxy;
import com.android.server.telecom.BluetoothHeadsetProxy;
-import com.android.server.telecom.TelecomSystem;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
+import java.util.Set;
public class BluetoothDeviceManager {
private final BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
@@ -50,8 +47,12 @@
mBluetoothHeadsetService =
new BluetoothHeadsetProxy((BluetoothHeadset) proxy);
Log.i(this, "- Got BluetoothHeadset: " + mBluetoothHeadsetService);
+ } else if (profile == BluetoothProfile.HEARING_AID) {
+ mBluetoothHearingAidService = (BluetoothHearingAid) proxy;
+ Log.i(this, "- Got BluetoothHearingAid: "
+ + mBluetoothHearingAidService);
} else {
- Log.w(this, "Connected to non-headset bluetooth service." +
+ Log.w(this, "Connected to non-requested bluetooth service." +
" Not changing bluetooth headset.");
}
}
@@ -65,12 +66,25 @@
Log.startSession("BMSL.oSD");
try {
synchronized (mLock) {
- mBluetoothHeadsetService = null;
- Log.i(BluetoothDeviceManager.this, "Lost BluetoothHeadset service. " +
- "Removing all tracked devices.");
+ LinkedHashMap<String, BluetoothDevice> lostServiceDevices;
+ if (profile == BluetoothProfile.HEADSET) {
+ mBluetoothHeadsetService = null;
+ Log.i(BluetoothDeviceManager.this,
+ "Lost BluetoothHeadset service. " +
+ "Removing all tracked devices.");
+ lostServiceDevices = mHfpDevicesByAddress;
+ } else if (profile == BluetoothProfile.HEARING_AID) {
+ mBluetoothHearingAidService = null;
+ Log.i(BluetoothDeviceManager.this,
+ "Lost BluetoothHearingAid service. " +
+ "Removing all tracked devices.");
+ lostServiceDevices = mHearingAidDevicesByAddress;
+ } else {
+ return;
+ }
List<BluetoothDevice> devicesToRemove = new LinkedList<>(
- mConnectedDevicesByAddress.values());
- mConnectedDevicesByAddress.clear();
+ lostServiceDevices.values());
+ lostServiceDevices.clear();
for (BluetoothDevice device : devicesToRemove) {
mBluetoothRouteManager.onDeviceLost(device.getAddress());
}
@@ -81,20 +95,27 @@
}
};
- private final LinkedHashMap<String, BluetoothDevice> mConnectedDevicesByAddress =
+ private final LinkedHashMap<String, BluetoothDevice> mHfpDevicesByAddress =
new LinkedHashMap<>();
- private final TelecomSystem.SyncRoot mLock;
+ private final LinkedHashMap<String, BluetoothDevice> mHearingAidDevicesByAddress =
+ new LinkedHashMap<>();
+ private final LinkedHashMap<BluetoothDevice, Long> mHearingAidDeviceSyncIds =
+ new LinkedHashMap<>();
+
+ // This lock only protects internal state -- it doesn't lock on anything going into Telecom.
+ private final Object mLock = new Object();
private BluetoothRouteManager mBluetoothRouteManager;
private BluetoothHeadsetProxy mBluetoothHeadsetService;
+ private BluetoothHearingAid mBluetoothHearingAidService;
+ private BluetoothDevice mBluetoothHearingAidActiveDeviceCache;
- public BluetoothDeviceManager(Context context, BluetoothAdapterProxy bluetoothAdapter,
- TelecomSystem.SyncRoot lock) {
- mLock = lock;
-
+ public BluetoothDeviceManager(Context context, BluetoothAdapterProxy bluetoothAdapter) {
if (bluetoothAdapter != null) {
bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
BluetoothProfile.HEADSET);
+ bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
+ BluetoothProfile.HEARING_AID);
}
}
@@ -103,52 +124,172 @@
}
public int getNumConnectedDevices() {
- return mConnectedDevicesByAddress.size();
+ synchronized (mLock) {
+ return mHfpDevicesByAddress.size() + mHearingAidDevicesByAddress.size();
+ }
}
public Collection<BluetoothDevice> getConnectedDevices() {
- return mConnectedDevicesByAddress.values();
+ synchronized (mLock) {
+ ArrayList<BluetoothDevice> result = new ArrayList<>(mHfpDevicesByAddress.values());
+ result.addAll(mHearingAidDevicesByAddress.values());
+ return Collections.unmodifiableCollection(result);
+ }
}
- public String getMostRecentlyConnectedDevice(String excludeAddress) {
- String result = null;
- synchronized (mLock) {
- for (String addr : mConnectedDevicesByAddress.keySet()) {
- if (!Objects.equals(addr, excludeAddress)) {
- result = addr;
+ // Same as getConnectedDevices except it filters out the hearing aid devices that are linked
+ // together by their hiSyncId.
+ public Collection<BluetoothDevice> getUniqueConnectedDevices() {
+ ArrayList<BluetoothDevice> result = new ArrayList<>(mHfpDevicesByAddress.values());
+ Set<Long> seenHiSyncIds = new LinkedHashSet<>();
+ // Add the left-most active device to the seen list so that we match up with the list
+ // generated in BluetoothRouteManager.
+ if (mBluetoothHearingAidService != null) {
+ for (BluetoothDevice device : mBluetoothHearingAidService.getActiveDevices()) {
+ if (device != null) {
+ result.add(device);
+ seenHiSyncIds.add(mHearingAidDeviceSyncIds.getOrDefault(device, -1L));
+ break;
}
}
}
- return result;
+ synchronized (mLock) {
+ for (BluetoothDevice d : mHearingAidDevicesByAddress.values()) {
+ long hiSyncId = mHearingAidDeviceSyncIds.getOrDefault(d, -1L);
+ if (seenHiSyncIds.contains(hiSyncId)) {
+ continue;
+ }
+ result.add(d);
+ seenHiSyncIds.add(hiSyncId);
+ }
+ }
+ return Collections.unmodifiableCollection(result);
}
public BluetoothHeadsetProxy getHeadsetService() {
return mBluetoothHeadsetService;
}
+ public BluetoothHearingAid getHearingAidService() {
+ return mBluetoothHearingAidService;
+ }
+
public void setHeadsetServiceForTesting(BluetoothHeadsetProxy bluetoothHeadset) {
mBluetoothHeadsetService = bluetoothHeadset;
}
- public BluetoothDevice getDeviceFromAddress(String address) {
- return mConnectedDevicesByAddress.get(address);
+ public void setHearingAidServiceForTesting(BluetoothHearingAid bluetoothHearingAid) {
+ mBluetoothHearingAidService = bluetoothHearingAid;
}
- void onDeviceConnected(BluetoothDevice device) {
+ void onDeviceConnected(BluetoothDevice device, boolean isHearingAid) {
synchronized (mLock) {
- if (!mConnectedDevicesByAddress.containsKey(device.getAddress())) {
- mConnectedDevicesByAddress.put(device.getAddress(), device);
+ LinkedHashMap<String, BluetoothDevice> targetDeviceMap;
+ if (isHearingAid) {
+ if (mBluetoothHearingAidService == null) {
+ Log.w(this, "Hearing aid service null when receiving device added broadcast");
+ return;
+ }
+ long hiSyncId = mBluetoothHearingAidService.getHiSyncId(device);
+ mHearingAidDeviceSyncIds.put(device, hiSyncId);
+ targetDeviceMap = mHearingAidDevicesByAddress;
+ } else {
+ if (mBluetoothHeadsetService == null) {
+ Log.w(this, "Headset service null when receiving device added broadcast");
+ return;
+ }
+ targetDeviceMap = mHfpDevicesByAddress;
+ }
+ if (!targetDeviceMap.containsKey(device.getAddress())) {
+ targetDeviceMap.put(device.getAddress(), device);
mBluetoothRouteManager.onDeviceAdded(device.getAddress());
}
}
}
- void onDeviceDisconnected(BluetoothDevice device) {
+ void onDeviceDisconnected(BluetoothDevice device, boolean isHearingAid) {
synchronized (mLock) {
- if (mConnectedDevicesByAddress.containsKey(device.getAddress())) {
- mConnectedDevicesByAddress.remove(device.getAddress());
+ LinkedHashMap<String, BluetoothDevice> targetDeviceMap;
+ if (isHearingAid) {
+ mHearingAidDeviceSyncIds.remove(device);
+ targetDeviceMap = mHearingAidDevicesByAddress;
+ } else {
+ targetDeviceMap = mHfpDevicesByAddress;
+ }
+ if (targetDeviceMap.containsKey(device.getAddress())) {
+ targetDeviceMap.remove(device.getAddress());
mBluetoothRouteManager.onDeviceLost(device.getAddress());
}
}
}
+
+ public void disconnectAudio() {
+ if (mBluetoothHearingAidService == null) {
+ Log.w(this, "Trying to disconnect audio but no hearing aid service exists");
+ } else {
+ for (BluetoothDevice device : mBluetoothHearingAidService.getActiveDevices()) {
+ if (device != null) {
+ mBluetoothHearingAidService.setActiveDevice(null);
+ }
+ }
+ }
+ disconnectSco();
+ }
+
+ public void disconnectSco() {
+ if (mBluetoothHeadsetService == null) {
+ Log.w(this, "Trying to disconnect audio but no headset service exists.");
+ } else {
+ mBluetoothHeadsetService.disconnectAudio();
+ }
+ }
+
+ // Connect audio to the bluetooth device at address, checking to see whether it's a hearing aid
+ // or a HFP device, and using the proper BT API.
+ public boolean connectAudio(String address) {
+ if (mHearingAidDevicesByAddress.containsKey(address)) {
+ if (mBluetoothHearingAidService == null) {
+ Log.w(this, "Attempting to turn on audio when the hearing aid service is null");
+ return false;
+ }
+ return mBluetoothHearingAidService.setActiveDevice(
+ mHearingAidDevicesByAddress.get(address));
+ } 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);
+ if (!success) {
+ Log.w(this, "Couldn't set active device to %s", address);
+ return false;
+ }
+ if (!mBluetoothHeadsetService.isAudioOn()) {
+ return mBluetoothHeadsetService.connectAudio();
+ }
+ return true;
+ } else {
+ Log.w(this, "Attempting to turn on audio for a disconnected device");
+ return false;
+ }
+ }
+
+ public void cacheHearingAidDevice() {
+ if (mBluetoothHearingAidService != null) {
+ for (BluetoothDevice device : mBluetoothHearingAidService.getActiveDevices()) {
+ if (device != null) {
+ mBluetoothHearingAidActiveDeviceCache = device;
+ }
+ }
+ }
+ }
+
+ public void restoreHearingAidDevice() {
+ if (mBluetoothHearingAidActiveDeviceCache != null && mBluetoothHearingAidService != null) {
+ mBluetoothHearingAidService.setActiveDevice(mBluetoothHearingAidActiveDeviceCache);
+ mBluetoothHearingAidActiveDeviceCache = null;
+ }
+ }
+
}
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index ba56145..0118d1f 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -18,6 +18,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHearingAid;
import android.content.Context;
import android.os.Message;
import android.telecom.Log;
@@ -33,13 +34,10 @@
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.Timeouts;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -57,8 +55,8 @@
put(CONNECT_HFP, "CONNECT_HFP");
put(DISCONNECT_HFP, "DISCONNECT_HFP");
put(RETRY_HFP_CONNECTION, "RETRY_HFP_CONNECTION");
- put(HFP_IS_ON, "HFP_IS_ON");
- put(HFP_LOST, "HFP_LOST");
+ put(BT_AUDIO_IS_ON, "BT_AUDIO_IS_ON");
+ put(BT_AUDIO_LOST, "BT_AUDIO_LOST");
put(CONNECTION_TIMEOUT, "CONNECTION_TIMEOUT");
put(GET_CURRENT_STATE, "GET_CURRENT_STATE");
put(RUN_RUNNABLE, "RUN_RUNNABLE");
@@ -97,9 +95,9 @@
public static final int RETRY_HFP_CONNECTION = 102;
// arg2: the address of the device that is on
- public static final int HFP_IS_ON = 200;
- // arg2: the address of the device that lost HFP
- public static final int HFP_LOST = 201;
+ public static final int BT_AUDIO_IS_ON = 200;
+ // arg2: the address of the device that lost BT audio
+ public static final int BT_AUDIO_LOST = 201;
// No args; only used internally
public static final int CONNECTION_TIMEOUT = 300;
@@ -125,8 +123,9 @@
BluetoothDevice erroneouslyConnectedDevice = getBluetoothAudioConnectedDevice();
if (erroneouslyConnectedDevice != null) {
Log.w(LOG_TAG, "Entering AudioOff state but device %s appears to be connected. " +
- "Disconnecting.", erroneouslyConnectedDevice);
- disconnectAudio();
+ "Switching to audio-on state for that device.", erroneouslyConnectedDevice);
+ // change this to just transition to the new audio on state
+ transitionToActualState();
}
cleanupStatesForDisconnectedDevices();
if (mListener != null) {
@@ -151,7 +150,7 @@
removeDevice((String) args.arg2);
break;
case CONNECT_HFP:
- String actualAddress = connectHfpAudio((String) args.arg2);
+ String actualAddress = connectBtAudio((String) args.arg2);
if (actualAddress != null) {
transitionTo(getConnectingStateForAddress(actualAddress,
@@ -166,7 +165,7 @@
break;
case RETRY_HFP_CONNECTION:
Log.i(LOG_TAG, "Retrying HFP connection to %s", (String) args.arg2);
- String retryAddress = connectHfpAudio((String) args.arg2, args.argi1);
+ String retryAddress = connectBtAudio((String) args.arg2, args.argi1);
if (retryAddress != null) {
transitionTo(getConnectingStateForAddress(retryAddress,
@@ -178,12 +177,13 @@
case CONNECTION_TIMEOUT:
// Ignore.
break;
- case HFP_IS_ON:
+ case BT_AUDIO_IS_ON:
String address = (String) args.arg2;
Log.w(LOG_TAG, "HFP audio unexpectedly turned on from device %s", address);
- transitionTo(getConnectedStateForAddress(address, "AudioOff/HFP_IS_ON"));
+ transitionTo(getConnectedStateForAddress(address,
+ "AudioOff/BT_AUDIO_IS_ON"));
break;
- case HFP_LOST:
+ case BT_AUDIO_LOST:
Log.i(LOG_TAG, "Received HFP off for device %s while HFP off.",
(String) args.arg2);
break;
@@ -253,7 +253,7 @@
// Ignore repeated connection attempts to the same device
break;
}
- String actualAddress = connectHfpAudio(address);
+ String actualAddress = connectBtAudio(address);
if (actualAddress != null) {
transitionTo(getConnectingStateForAddress(actualAddress,
@@ -264,14 +264,13 @@
}
break;
case DISCONNECT_HFP:
- disconnectAudio();
- transitionTo(mAudioOffState);
+ mDeviceManager.disconnectAudio();
break;
case RETRY_HFP_CONNECTION:
if (Objects.equals(address, mDeviceAddress)) {
Log.d(LOG_TAG, "Retry message came through while connecting.");
} else {
- String retryAddress = connectHfpAudio(address, args.argi1);
+ String retryAddress = connectBtAudio(address, args.argi1);
if (retryAddress != null) {
transitionTo(getConnectingStateForAddress(retryAddress,
"AudioConnecting/RETRY_HFP_CONNECTION"));
@@ -285,7 +284,7 @@
mDeviceAddress);
transitionToActualState();
break;
- case HFP_IS_ON:
+ case BT_AUDIO_IS_ON:
if (Objects.equals(mDeviceAddress, address)) {
Log.i(LOG_TAG, "HFP connection success for device %s.", mDeviceAddress);
transitionTo(mAudioConnectedStates.get(mDeviceAddress));
@@ -293,10 +292,10 @@
Log.w(LOG_TAG, "In connecting state for device %s but %s" +
" is now connected", mDeviceAddress, address);
transitionTo(getConnectedStateForAddress(address,
- "AudioConnecting/HFP_IS_ON"));
+ "AudioConnecting/BT_AUDIO_IS_ON"));
}
break;
- case HFP_LOST:
+ case BT_AUDIO_LOST:
if (Objects.equals(mDeviceAddress, address)) {
Log.i(LOG_TAG, "Connection with device %s failed.",
mDeviceAddress);
@@ -366,7 +365,7 @@
// Ignore connection to already connected device.
break;
}
- String actualAddress = connectHfpAudio(address);
+ String actualAddress = connectBtAudio(address);
if (actualAddress != null) {
transitionTo(getConnectingStateForAddress(address,
@@ -377,14 +376,13 @@
}
break;
case DISCONNECT_HFP:
- disconnectAudio();
- transitionTo(mAudioOffState);
+ mDeviceManager.disconnectAudio();
break;
case RETRY_HFP_CONNECTION:
if (Objects.equals(address, mDeviceAddress)) {
Log.d(LOG_TAG, "Retry message came through while connected.");
} else {
- String retryAddress = connectHfpAudio(address, args.argi1);
+ String retryAddress = connectBtAudio(address, args.argi1);
if (retryAddress != null) {
transitionTo(getConnectingStateForAddress(retryAddress,
"AudioConnected/RETRY_HFP_CONNECTION"));
@@ -396,17 +394,18 @@
case CONNECTION_TIMEOUT:
Log.w(LOG_TAG, "Received CONNECTION_TIMEOUT while connected.");
break;
- case HFP_IS_ON:
+ case BT_AUDIO_IS_ON:
if (Objects.equals(mDeviceAddress, address)) {
- Log.i(LOG_TAG, "Received redundant HFP_IS_ON for %s", mDeviceAddress);
+ Log.i(LOG_TAG,
+ "Received redundant BT_AUDIO_IS_ON for %s", mDeviceAddress);
} else {
Log.w(LOG_TAG, "In connected state for device %s but %s" +
" is now connected", mDeviceAddress, address);
transitionTo(getConnectedStateForAddress(address,
- "AudioConnected/HFP_IS_ON"));
+ "AudioConnected/BT_AUDIO_IS_ON"));
}
break;
- case HFP_LOST:
+ case BT_AUDIO_LOST:
if (Objects.equals(mDeviceAddress, address)) {
Log.i(LOG_TAG, "HFP connection with device %s lost.", mDeviceAddress);
transitionToActualState();
@@ -439,8 +438,9 @@
private BluetoothStateListener mListener;
private BluetoothDeviceManager mDeviceManager;
- // Tracks the active device in the BT stack.
- private BluetoothDevice mActiveDeviceCache = null;
+ // Tracks the active devices in the BT stack (HFP or hearing aid).
+ private BluetoothDevice mHfpActiveDeviceCache = null;
+ private BluetoothDevice mHearingAidActiveDeviceCache = null;
public BluetoothRouteManager(Context context, TelecomSystem.SyncRoot lock,
BluetoothDeviceManager deviceManager, Timeouts.Adapter timeoutsAdapter) {
@@ -537,6 +537,18 @@
sendMessage(DISCONNECT_HFP, args);
}
+ public void disconnectSco() {
+ mDeviceManager.disconnectSco();
+ }
+
+ public void cacheHearingAidDevice() {
+ mDeviceManager.cacheHearingAidDevice();
+ }
+
+ public void restoreHearingAidDevice() {
+ mDeviceManager.restoreHearingAidDevice();
+ }
+
public void setListener(BluetoothStateListener listener) {
mListener = listener;
}
@@ -559,29 +571,38 @@
mListener.onBluetoothDeviceListChanged();
}
- public void onActiveDeviceChanged(BluetoothDevice device) {
- BluetoothDevice oldActiveDevice = mActiveDeviceCache;
- mActiveDeviceCache = device;
- if ((oldActiveDevice == null) ^ (device == null)) {
- if (device == null) {
- mListener.onBluetoothActiveDeviceGone();
- } else {
- mListener.onBluetoothActiveDevicePresent();
- }
+ public void onActiveDeviceChanged(BluetoothDevice device, boolean isHearingAid) {
+ boolean wasActiveDevicePresent = mHearingAidActiveDeviceCache != null
+ || mHfpActiveDeviceCache != null;
+ if (isHearingAid) {
+ mHearingAidActiveDeviceCache = device;
+ } else {
+ mHfpActiveDeviceCache = device;
+ }
+ boolean isActiveDevicePresent = mHearingAidActiveDeviceCache != null
+ || mHfpActiveDeviceCache != null;
+
+ if (wasActiveDevicePresent && !isActiveDevicePresent) {
+ mListener.onBluetoothActiveDeviceGone();
+ } else if (!wasActiveDevicePresent && isActiveDevicePresent) {
+ mListener.onBluetoothActiveDevicePresent();
}
}
- public Collection<BluetoothDevice> getConnectedDevices() {
- return Collections.unmodifiableCollection(
- new ArrayList<>(mDeviceManager.getConnectedDevices()));
+ public boolean hasBtActiveDevice() {
+ return mHearingAidActiveDeviceCache != null || mHfpActiveDeviceCache != null;
}
- private String connectHfpAudio(String address) {
- return connectHfpAudio(address, 0);
+ public Collection<BluetoothDevice> getConnectedDevices() {
+ return mDeviceManager.getUniqueConnectedDevices();
+ }
+
+ private String connectBtAudio(String address) {
+ return connectBtAudio(address, 0);
}
/**
- * Initiates a HFP connection to the BT address specified.
+ * Initiates a connection to the BT address specified.
* Note: This method is not synchronized on the Telecom lock, so don't try and call back into
* Telecom from within it.
* @param address The address that should be tried first. May be null.
@@ -589,23 +610,29 @@
* @return The address of the device that's actually being connected to, or null if no
* connection was successful.
*/
- private String connectHfpAudio(String address, int retryCount) {
- Collection<BluetoothDevice> deviceList = getConnectedDevices();
+ private String connectBtAudio(String address, int retryCount) {
+ Collection<BluetoothDevice> deviceList = mDeviceManager.getConnectedDevices();
Optional<BluetoothDevice> matchingDevice = deviceList.stream()
.filter(d -> Objects.equals(d.getAddress(), address))
.findAny();
String actualAddress = matchingDevice.isPresent()
? address : getActiveDeviceAddress();
+ if (actualAddress == null) {
+ Log.i(this, "No device specified and BT stack has no active device."
+ + " Using arbitrary device");
+ if (deviceList.size() > 0) {
+ actualAddress = deviceList.iterator().next().getAddress();
+ } else {
+ Log.i(this, "No devices available at all. Not connecting.");
+ return null;
+ }
+ }
if (!matchingDevice.isPresent()) {
Log.i(this, "No device with address %s available. Using %s instead.",
address, actualAddress);
}
- if (actualAddress == null) {
- Log.i(this, "No device specified and BT stack has no active device. Not connecting.");
- return null;
- }
- if (!connectAudio(actualAddress)) {
+ if (!mDeviceManager.connectAudio(actualAddress)) {
boolean shouldRetry = retryCount < MAX_CONNECTION_RETRIES;
Log.w(LOG_TAG, "Could not connect to %s. Will %s", actualAddress,
shouldRetry ? "retry" : "not retry");
@@ -625,7 +652,13 @@
}
private String getActiveDeviceAddress() {
- return mActiveDeviceCache == null ? null : mActiveDeviceCache.getAddress();
+ if (mHfpActiveDeviceCache != null) {
+ return mHfpActiveDeviceCache.getAddress();
+ }
+ if (mHearingAidActiveDeviceCache != null) {
+ return mHearingAidActiveDeviceCache.getAddress();
+ }
+ return null;
}
private void transitionToActualState() {
@@ -646,20 +679,29 @@
@VisibleForTesting
public BluetoothDevice getBluetoothAudioConnectedDevice() {
BluetoothHeadsetProxy bluetoothHeadset = mDeviceManager.getHeadsetService();
- if (bluetoothHeadset == null) {
- Log.i(this, "getBluetoothAudioConnectedDevice: no headset service available.");
+ BluetoothHearingAid bluetoothHearingAid = mDeviceManager.getHearingAidService();
+
+ if (bluetoothHeadset == null && bluetoothHearingAid == null) {
+ Log.i(this, "getBluetoothAudioConnectedDevice: no service available.");
return null;
}
- List<BluetoothDevice> deviceList = bluetoothHeadset.getConnectedDevices();
- for (int i = 0; i < deviceList.size(); i++) {
- BluetoothDevice device = deviceList.get(i);
- boolean isAudioOn = bluetoothHeadset.getAudioState(device)
- != BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
- Log.v(this, "isBluetoothAudioConnected: ==> isAudioOn = " + isAudioOn
- + "for headset: " + device);
- if (isAudioOn) {
- return device;
+ if (bluetoothHeadset != null) {
+ for (BluetoothDevice device : bluetoothHeadset.getConnectedDevices()) {
+ boolean isAudioOn = bluetoothHeadset.getAudioState(device)
+ != BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+ Log.v(this, "isBluetoothAudioConnected: ==> isAudioOn = " + isAudioOn
+ + "for headset: " + device);
+ if (isAudioOn) {
+ return device;
+ }
+ }
+ }
+ if (bluetoothHearingAid != null) {
+ for (BluetoothDevice device : bluetoothHearingAid.getActiveDevices()) {
+ if (device != null) {
+ return device;
+ }
}
}
return null;
@@ -681,37 +723,6 @@
return bluetoothHeadset.isInbandRingingEnabled();
}
- private boolean connectAudio(String address) {
- BluetoothHeadsetProxy bluetoothHeadset = mDeviceManager.getHeadsetService();
- if (bluetoothHeadset == null) {
- Log.w(this, "Trying to connect audio but no headset service exists.");
- return false;
- }
- BluetoothDevice device = mDeviceManager.getDeviceFromAddress(address);
- if (device == null) {
- Log.w(this, "Attempting to turn on audio for a disconnected device");
- return false;
- }
- boolean success = bluetoothHeadset.setActiveDevice(device);
- if (!success) {
- Log.w(LOG_TAG, "Couldn't set active device to %s", address);
- return false;
- }
- if (!bluetoothHeadset.isAudioOn()) {
- return bluetoothHeadset.connectAudio();
- }
- return true;
- }
-
- private void disconnectAudio() {
- BluetoothHeadsetProxy bluetoothHeadset = mDeviceManager.getHeadsetService();
- if (bluetoothHeadset == null) {
- Log.w(this, "Trying to disconnect audio but no headset service exists.");
- } else {
- bluetoothHeadset.disconnectAudio();
- }
- }
-
private boolean addDevice(String address) {
if (mAudioConnectingStates.containsKey(address)) {
Log.i(this, "Attempting to add device %s twice.", address);
@@ -770,23 +781,30 @@
@VisibleForTesting
public void setInitialStateForTesting(String stateName, BluetoothDevice device) {
- switch (stateName) {
- case AUDIO_OFF_STATE_NAME:
- transitionTo(mAudioOffState);
- break;
- case AUDIO_CONNECTING_STATE_NAME_PREFIX:
- transitionTo(getConnectingStateForAddress(device.getAddress(),
- "setInitialStateForTesting"));
- break;
- case AUDIO_CONNECTED_STATE_NAME_PREFIX:
- transitionTo(getConnectedStateForAddress(device.getAddress(),
- "setInitialStateForTesting"));
- break;
- }
+ sendMessage(RUN_RUNNABLE, (Runnable) () -> {
+ switch (stateName) {
+ case AUDIO_OFF_STATE_NAME:
+ transitionTo(mAudioOffState);
+ break;
+ case AUDIO_CONNECTING_STATE_NAME_PREFIX:
+ transitionTo(getConnectingStateForAddress(device.getAddress(),
+ "setInitialStateForTesting"));
+ break;
+ case AUDIO_CONNECTED_STATE_NAME_PREFIX:
+ transitionTo(getConnectedStateForAddress(device.getAddress(),
+ "setInitialStateForTesting"));
+ break;
+ }
+ Log.i(LOG_TAG, "transition for testing done: %s", stateName);
+ });
}
@VisibleForTesting
- public void setActiveDeviceCacheForTesting(BluetoothDevice device) {
- mActiveDeviceCache = device;
+ public void setActiveDeviceCacheForTesting(BluetoothDevice device, boolean isHearingAid) {
+ if (isHearingAid) {
+ mHearingAidActiveDeviceCache = device;
+ } else {
+ mHfpActiveDeviceCache = device;
+ }
}
}
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index f9c6437..0176a43 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -18,6 +18,8 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -27,8 +29,8 @@
import com.android.internal.os.SomeArgs;
-import static com.android.server.telecom.bluetooth.BluetoothRouteManager.HFP_IS_ON;
-import static com.android.server.telecom.bluetooth.BluetoothRouteManager.HFP_LOST;
+import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_IS_ON;
+import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_LOST;
public class BluetoothStateReceiver extends BroadcastReceiver {
@@ -39,6 +41,8 @@
INTENT_FILTER.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
INTENT_FILTER.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
INTENT_FILTER.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
+ INTENT_FILTER.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
+ INTENT_FILTER.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
}
// If not in a call, BSR won't listen to the Bluetooth stack's HFP on/off messages, since
@@ -55,9 +59,11 @@
case BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED:
handleAudioStateChanged(intent);
break;
+ case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED:
case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED:
handleConnectionStateChanged(intent);
break;
+ case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED:
handleActiveDeviceChanged(intent);
break;
@@ -91,10 +97,10 @@
args.arg2 = device.getAddress();
switch (bluetoothHeadsetAudioState) {
case BluetoothHeadset.STATE_AUDIO_CONNECTED:
- mBluetoothRouteManager.sendMessage(HFP_IS_ON, args);
+ mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args);
break;
case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
- mBluetoothRouteManager.sendMessage(HFP_LOST, args);
+ mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args);
break;
}
}
@@ -114,19 +120,36 @@
Log.i(LOG_TAG, "Device %s changed state to %d",
device.getAddress(), bluetoothHeadsetState);
- if (bluetoothHeadsetState == BluetoothHeadset.STATE_CONNECTED) {
- mBluetoothDeviceManager.onDeviceConnected(device);
- } else if (bluetoothHeadsetState == BluetoothHeadset.STATE_DISCONNECTED
- || bluetoothHeadsetState == BluetoothHeadset.STATE_DISCONNECTING) {
- mBluetoothDeviceManager.onDeviceDisconnected(device);
+ boolean isHearingAid = BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED
+ .equals(intent.getAction());
+ if (bluetoothHeadsetState == BluetoothProfile.STATE_CONNECTED) {
+ mBluetoothDeviceManager.onDeviceConnected(device, isHearingAid);
+ } else if (bluetoothHeadsetState == BluetoothProfile.STATE_DISCONNECTED
+ || bluetoothHeadsetState == BluetoothProfile.STATE_DISCONNECTING) {
+ mBluetoothDeviceManager.onDeviceDisconnected(device, isHearingAid);
}
}
private void handleActiveDeviceChanged(Intent intent) {
BluetoothDevice device =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- Log.i(LOG_TAG, "Device %s is now the preferred HFP device", device);
- mBluetoothRouteManager.onActiveDeviceChanged(device);
+ boolean isHearingAid =
+ BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED.equals(intent.getAction());
+ Log.i(LOG_TAG, "Device %s is now the preferred BT device for %s", device,
+ isHearingAid ? "heading aid" : "HFP");
+
+ mBluetoothRouteManager.onActiveDeviceChanged(device, isHearingAid);
+ if (isHearingAid) {
+ Session session = Log.createSubsession();
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = session;
+ if (device == null) {
+ mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args);
+ } else {
+ args.arg2 = device.getAddress();
+ mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args);
+ }
+ }
}
public BluetoothStateReceiver(BluetoothDeviceManager deviceManager,
diff --git a/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java b/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
index 748d15e..0abd15d 100644
--- a/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
+++ b/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
@@ -21,6 +21,7 @@
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;
@@ -40,17 +41,20 @@
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) {
+ CallerInfoLookupHelper callerInfoLookupHelper, CallBlockListener callBlockListener) {
mContext = context;
mBlockCheckerAdapter = blockCheckerAdapter;
mCallerInfoLookupHelper = callerInfoLookupHelper;
+ mCallBlockListener = callBlockListener;
}
@Override
@@ -104,7 +108,8 @@
extras.putBoolean(BlockedNumberContract.EXTRA_CONTACT_EXIST,
Boolean.valueOf(params[2]));
}
- return mBlockCheckerAdapter.isBlocked(mContext, params[0], extras);
+ mBlockStatus = mBlockCheckerAdapter.getBlockStatus(mContext, params[0], extras);
+ return mBlockStatus != BlockedNumberContract.STATUS_NOT_BLOCKED;
} finally {
Log.endSession();
}
@@ -119,9 +124,18 @@
result = new CallFilteringResult(
false, // shouldAllowCall
true, //shouldReject
- false, //shouldAddToCallLog
- false // shouldShowNotification
+ 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
@@ -130,10 +144,36 @@
true // shouldShowNotification
);
}
- Log.addEvent(mIncomingCall, LogUtils.Events.BLOCK_CHECK_FINISHED, result);
+ 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 8c74fa9..4a5ac60 100644
--- a/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java
+++ b/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java
@@ -30,9 +30,15 @@
* @param context the context of the caller.
* @param number the number to check.
* @param extras the extra attribute of the number.
- * @return {@code true} if the number is blocked. {@code false} otherwise.
+ * @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}.
*/
- public boolean isBlocked(Context context, String number, Bundle extras) {
- return BlockChecker.isBlocked(context, number, extras);
+ public int getBlockStatus(Context context, String number, Bundle extras) {
+ return BlockChecker.getBlockStatus(context, number, extras);
}
}
diff --git a/src/com/android/server/telecom/callfiltering/CallBlockListener.java b/src/com/android/server/telecom/callfiltering/CallBlockListener.java
new file mode 100644
index 0000000..d0a9949
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/CallBlockListener.java
@@ -0,0 +1,26 @@
+/*
+ * 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.os.UserHandle;
+
+/**
+ * Listener for components which wish to be notified when a call is blocked.
+ */
+public interface CallBlockListener {
+ void onCallBlocked(int blockStatus, String number, UserHandle userHandle);
+}
diff --git a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
index 9e35d86..1213131 100644
--- a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
+++ b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
@@ -16,11 +16,18 @@
package com.android.server.telecom.callfiltering;
+import android.provider.CallLog;
+import android.provider.CallLog.Calls;
+import android.text.TextUtils;
+
public class CallFilteringResult {
public boolean shouldAllowCall;
public boolean shouldReject;
public boolean shouldAddToCallLog;
public boolean shouldShowNotification;
+ public int mCallBlockReason = CallLog.Calls.BLOCK_REASON_NOT_BLOCKED;
+ public String mCallScreeningAppName = null;
+ public String mCallScreeningComponentName = null;
public CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
shouldAddToCallLog, boolean shouldShowNotification) {
@@ -30,22 +37,85 @@
this.shouldShowNotification = shouldShowNotification;
}
+ public CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
+ shouldAddToCallLog, boolean shouldShowNotification, int callBlockReason, String
+ callScreeningAppName, String callScreeningComponentName) {
+ this.shouldAllowCall = shouldAllowCall;
+ this.shouldReject = shouldReject;
+ this.shouldAddToCallLog = shouldAddToCallLog;
+ this.shouldShowNotification = shouldShowNotification;
+ this.mCallBlockReason = callBlockReason;
+ this.mCallScreeningAppName = callScreeningAppName;
+ this.mCallScreeningComponentName = callScreeningComponentName;
+ }
+
/**
- * Combine this CallFilteringResult with another, returning a CallFilteringResult with
- * the more restrictive properties of the two.
+ * 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
+ * shall be used to populate the call block reason, component name, etc.
*/
public CallFilteringResult combine(CallFilteringResult other) {
if (other == null) {
return this;
}
+ if (isBlockedByProvider(mCallBlockReason)) {
+ return getCombinedCallFilteringResult(other, mCallBlockReason,
+ null /*callScreeningAppName*/, null /*callScreeningComponentName*/);
+ } else if (isBlockedByProvider(other.mCallBlockReason)) {
+ return getCombinedCallFilteringResult(other, other.mCallBlockReason,
+ null /*callScreeningAppName*/, null /*callScreeningComponentName*/);
+ }
+
+ if (mCallBlockReason == Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL
+ || other.mCallBlockReason == Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL) {
+ return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL,
+ null /*callScreeningAppName*/, null /*callScreeningComponentName*/);
+ }
+
+ if (shouldReject && mCallBlockReason == CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE) {
+ return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_CALL_SCREENING_SERVICE,
+ mCallScreeningAppName, mCallScreeningComponentName);
+ } else if (other.shouldReject && other.mCallBlockReason == CallLog.Calls
+ .BLOCK_REASON_CALL_SCREENING_SERVICE) {
+ return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_CALL_SCREENING_SERVICE,
+ other.mCallScreeningAppName, other.mCallScreeningComponentName);
+ }
+
return new CallFilteringResult(
- shouldAllowCall && other.shouldAllowCall,
- shouldReject || other.shouldReject,
- shouldAddToCallLog && other.shouldAddToCallLog,
- shouldShowNotification && other.shouldShowNotification);
+ shouldAllowCall && other.shouldAllowCall,
+ shouldReject || other.shouldReject,
+ shouldAddToCallLog && other.shouldAddToCallLog,
+ shouldShowNotification && other.shouldShowNotification);
}
+ private boolean isBlockedByProvider(int blockReason) {
+ if (blockReason == Calls.BLOCK_REASON_BLOCKED_NUMBER
+ || blockReason == Calls.BLOCK_REASON_UNKNOWN_NUMBER
+ || blockReason == Calls.BLOCK_REASON_RESTRICTED_NUMBER
+ || blockReason == Calls.BLOCK_REASON_PAY_PHONE
+ || blockReason == Calls.BLOCK_REASON_NOT_IN_CONTACTS) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private CallFilteringResult getCombinedCallFilteringResult(CallFilteringResult other,
+ int callBlockReason, String callScreeningAppName, String callScreeningComponentName) {
+ return new CallFilteringResult(
+ shouldAllowCall && other.shouldAllowCall,
+ shouldReject || other.shouldReject,
+ shouldAddToCallLog && other.shouldAddToCallLog,
+ shouldShowNotification && other.shouldShowNotification,
+ callBlockReason,
+ callScreeningAppName,
+ callScreeningComponentName);
+ }
+
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -56,7 +126,24 @@
if (shouldAllowCall != that.shouldAllowCall) return false;
if (shouldReject != that.shouldReject) return false;
if (shouldAddToCallLog != that.shouldAddToCallLog) return false;
- return shouldShowNotification == that.shouldShowNotification;
+ if (shouldShowNotification != that.shouldShowNotification) return false;
+ if (mCallBlockReason != that.mCallBlockReason) 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;
+ }
+
+ return false;
}
@Override
@@ -87,6 +174,21 @@
if (shouldShowNotification) {
sb.append(", notified");
}
+
+ if (mCallBlockReason != 0) {
+ sb.append(", mCallBlockReason = ");
+ sb.append(mCallBlockReason);
+ }
+
+ if (!TextUtils.isEmpty(mCallScreeningAppName)) {
+ sb.append(", mCallScreeningAppName = ");
+ sb.append(mCallScreeningAppName);
+ }
+
+ if (!TextUtils.isEmpty(mCallScreeningComponentName)) {
+ sb.append(", mCallScreeningComponentName = ");
+ sb.append(mCallScreeningComponentName);
+ }
sb.append("]");
return sb.toString();
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceController.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceController.java
new file mode 100644
index 0000000..40bf398
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceController.java
@@ -0,0 +1,264 @@
+/*
+ * 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.os.UserHandle;
+import android.provider.CallLog;
+import android.provider.Settings;
+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.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 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) {
+ mContext = context;
+ mCallsManager = callsManager;
+ mPhoneAccountRegistrar = phoneAccountRegistrar;
+ mParcelableCallUtilsConverter = parcelableCallUtilsConverter;
+ mTelecomLock = lock;
+ mSettingsSecureAdapter = settingsSecureAdapter;
+ mCallerInfoLookupHelper = callerInfoLookupHelper;
+ }
+
+ @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())) {
+ mIsDefaultDialerFinished = true;
+ if (result.mCallBlockReason == CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE ||
+ mIsUserChosenFinished) {
+ finishCallScreening();
+ }
+ } else if (!TextUtils.isEmpty(packageName) &&
+ packageName.equals(getUserChosenPackageName())) {
+ mIsUserChosenFinished = true;
+ if (mIsDefaultDialerFinished) {
+ finishCallScreening();
+ }
+ }
+ }
+ }
+
+ private void bindCarrierService() {
+ String carrierPackageName = getCarrierPackageName();
+ if (TextUtils.isEmpty(carrierPackageName)) {
+ mIsCarrierFinished = true;
+ bindDefaultDialerAndUserChosenService();
+ } else {
+ createCallScreeningServiceFilter().startCallScreeningFilter(mCall, this,
+ carrierPackageName);
+ }
+
+ // 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();
+ if (TextUtils.isEmpty(dialerPackageName)) {
+ mIsDefaultDialerFinished = true;
+ } else {
+ createCallScreeningServiceFilter().startCallScreeningFilter(mCall,
+ CallScreeningServiceController.this, dialerPackageName);
+ }
+
+ String userChosenPackageName = getUserChosenPackageName();
+ if (TextUtils.isEmpty(userChosenPackageName)) {
+ mIsUserChosenFinished = true;
+ } else {
+ createCallScreeningServiceFilter().startCallScreeningFilter(mCall,
+ CallScreeningServiceController.this, userChosenPackageName);
+ }
+
+ 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 getUserChosenPackageName() {
+ ComponentName componentName = null;
+ String defaultCallScreeningApplication = mSettingsSecureAdapter.getStringForUser(mContext
+ .getContentResolver(), Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT,
+ UserHandle.USER_CURRENT);
+
+ if (!TextUtils.isEmpty(defaultCallScreeningApplication)) {
+ componentName = ComponentName.unflattenFromString(defaultCallScreeningApplication);
+ }
+
+ return componentName != null ? componentName.getPackageName() : null;
+ }
+}
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
index 4830b31..f0832e6 100644
--- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
@@ -24,21 +24,25 @@
import android.content.pm.ResolveInfo;
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.CallScreeningService;
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.Call;
import com.android.server.telecom.CallsManager;
-import com.android.server.telecom.DefaultDialerCache;
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.TelecomServiceImpl.SettingsSecureAdapter;
import com.android.server.telecom.TelecomSystem;
import java.util.List;
@@ -47,7 +51,13 @@
* Binds to {@link ICallScreeningService} to allow call blocking. A single instance of this class
* handles a single call.
*/
-public class CallScreeningServiceFilter implements IncomingCallFilter.CallFilter {
+public class CallScreeningServiceFilter {
+
+ public interface CallScreeningFilterResultCallback {
+ void onCallScreeningFilterComplete(Call call, CallFilteringResult result, String
+ packageName);
+ }
+
private class CallScreeningServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
@@ -108,20 +118,26 @@
String callId,
boolean shouldReject,
boolean shouldAddToCallLog,
- boolean shouldShowNotification) {
+ boolean shouldShowNotification,
+ ComponentName componentName) {
Log.startSession("CSCR.dC");
long token = Binder.clearCallingIdentity();
try {
synchronized (mTelecomLock) {
+ boolean isServiceRequestingLogging = isLoggable(componentName,
+ shouldAddToCallLog);
Log.i(this, "disallowCall(%s), shouldReject: %b, shouldAddToCallLog: %b, "
+ "shouldShowNotification: %b", callId, shouldReject,
- shouldAddToCallLog, shouldShowNotification);
+ isServiceRequestingLogging, shouldShowNotification);
if (mCall != null && mCall.getId().equals(callId)) {
mResult = new CallFilteringResult(
false, // shouldAllowCall
shouldReject, //shouldReject
- shouldAddToCallLog, //shouldAddToCallLog
- shouldShowNotification // shouldShowNotification
+ isServiceRequestingLogging, //shouldAddToCallLog
+ shouldShowNotification, // shouldShowNotification
+ CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
+ componentName.getPackageName(), //callScreeningAppName
+ componentName.flattenToString() //callScreeningComponentName
);
} else {
Log.w(this, "disallowCall, unknown call id: %s", callId);
@@ -138,16 +154,17 @@
private final Context mContext;
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final CallsManager mCallsManager;
- private final DefaultDialerCache mDefaultDialerCache;
private final ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
private final TelecomSystem.SyncRoot mTelecomLock;
+ private final SettingsSecureAdapter mSettingsSecureAdapter;
private Call mCall;
- private CallFilterResultCallback mCallback;
+ private CallScreeningFilterResultCallback mCallback;
private ICallScreeningService mService;
private ServiceConnection mConnection;
-
+ private String mPackageName;
private boolean mHasFinished = false;
+
private CallFilteringResult mResult = new CallFilteringResult(
true, // shouldAllowCall
false, //shouldReject
@@ -159,26 +176,28 @@
Context context,
CallsManager callsManager,
PhoneAccountRegistrar phoneAccountRegistrar,
- DefaultDialerCache defaultDialerCache,
ParcelableCallUtils.Converter parcelableCallUtilsConverter,
- TelecomSystem.SyncRoot lock) {
+ TelecomSystem.SyncRoot lock,
+ SettingsSecureAdapter settingsSecureAdapter) {
mContext = context;
mPhoneAccountRegistrar = phoneAccountRegistrar;
mCallsManager = callsManager;
- mDefaultDialerCache = defaultDialerCache;
mParcelableCallUtilsConverter = parcelableCallUtilsConverter;
mTelecomLock = lock;
+ mSettingsSecureAdapter = settingsSecureAdapter;
}
- @Override
- public void startFilterLookup(Call call, CallFilterResultCallback callback) {
+ public void startCallScreeningFilter(Call call,
+ CallScreeningFilterResultCallback callback,
+ String packageName) {
if (mHasFinished) {
Log.w(this, "Attempting to reuse CallScreeningServiceFilter. Ignoring.");
return;
}
- Log.addEvent(call, LogUtils.Events.SCREENING_SENT);
+ Log.addEvent(call, LogUtils.Events.SCREENING_SENT, packageName);
mCall = call;
mCallback = callback;
+ mPackageName = packageName;
if (!bindService()) {
Log.i(this, "Could not bind to call screening service");
finishCallScreening();
@@ -188,7 +207,7 @@
private void finishCallScreening() {
if (!mHasFinished) {
Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, mResult);
- mCallback.onCallFilteringComplete(mCall, mResult);
+ mCallback.onCallScreeningFilterComplete(mCall, mResult, mPackageName);
if (mConnection != null) {
// We still need to call unbind even if the service disconnected.
@@ -201,25 +220,23 @@
}
private boolean bindService() {
- String dialerPackage = mDefaultDialerCache
- .getDefaultDialerApplication(UserHandle.USER_CURRENT);
- if (TextUtils.isEmpty(dialerPackage)) {
- Log.i(this, "Default dialer is empty. Not performing call screening.");
+ if (TextUtils.isEmpty(mPackageName)) {
+ Log.i(this, "PackageName is empty. Not performing call screening.");
return false;
}
Intent intent = new Intent(CallScreeningService.SERVICE_INTERFACE)
- .setPackage(dialerPackage);
+ .setPackage(mPackageName);
List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
intent, 0, mCallsManager.getCurrentUserHandle().getIdentifier());
if (entries.isEmpty()) {
- Log.i(this, "There are no call screening services installed on this device.");
+ Log.i(this, mPackageName + "is no call screening services installed on this device.");
return false;
}
ResolveInfo entry = entries.get(0);
if (entry.serviceInfo == null) {
- Log.w(this, "The call screening service has invalid service info");
+ Log.w(this, mPackageName + " call screening service has invalid service info");
return false;
}
@@ -261,4 +278,56 @@
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;
+ }
}
diff --git a/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java b/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
index 2ac82dc..3a8ff7d 100644
--- a/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
+++ b/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
@@ -17,6 +17,7 @@
package com.android.server.telecom.callfiltering;
import android.net.Uri;
+import android.provider.CallLog;
import android.telecom.Log;
import com.android.internal.telephony.CallerInfo;
@@ -49,7 +50,11 @@
false, // shouldAllowCall
true, // shouldReject
true, // shouldAddToCallLog
- true // shouldShowNotification
+ true, // shouldShowNotification
+ CallLog.Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL,
+ //callBlockReason
+ null, //callScreeningAppName
+ null // callScreeningComponentName
);
} else {
result = new CallFilteringResult(
diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionCallback.java b/src/com/android/server/telecom/callredirection/CallRedirectionCallback.java
new file mode 100644
index 0000000..24ac326
--- /dev/null
+++ b/src/com/android/server/telecom/callredirection/CallRedirectionCallback.java
@@ -0,0 +1,23 @@
+/*
+ * 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.callredirection;
+
+import com.android.server.telecom.Call;
+
+public interface CallRedirectionCallback {
+ void onCallRedirectionComplete(Call call);
+}
diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
new file mode 100644
index 0000000..abb87e1
--- /dev/null
+++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
@@ -0,0 +1,409 @@
+/*
+ * 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.callredirection;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.telecom.CallRedirectionService;
+import android.telecom.Log;
+import android.telecom.Logging.Runnable;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.CarrierConfigManager;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telecom.ICallRedirectionAdapter;
+import com.android.internal.telecom.ICallRedirectionService;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.LogUtils;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
+
+import java.util.List;
+
+/**
+ * A single instance of call redirection processor that handles the call redirection with
+ * user-defined {@link CallRedirectionService} and carrier {@link CallRedirectionService} for a
+ * single call.
+ *
+ * A user-defined call redirection will be performed firstly and a carrier call redirection will be
+ * performed after that; there will be a total of two call redirection cycles.
+ *
+ * A call redirection cycle is a cycle:
+ * 1) Telecom requests a call redirection of a call with a specific {@link CallRedirectionService},
+ * 2) Telecom receives the response either from a specific {@link CallRedirectionService} or from
+ * the timeout.
+ *
+ * Telecom should return to {@link CallsManager} at the end of current call redirection
+ * cycle, if
+ * 1) {@link CallRedirectionService} sends {@link CallRedirectionService#cancelCall()} response
+ * before timeout;
+ * or 2) Telecom finishes call redirection with carrier {@link CallRedirectionService}.
+ */
+public class CallRedirectionProcessor implements CallRedirectionCallback {
+
+ private class CallRedirectionAttempt {
+ private final ComponentName mComponentName;
+ private final String mServiceType;
+ private ServiceConnection mConnection;
+ private ICallRedirectionService mService;
+
+ private CallRedirectionAttempt(ComponentName componentName, String serviceType) {
+ mComponentName = componentName;
+ mServiceType = serviceType;
+ }
+
+ private void process() {
+ Intent intent = new Intent(CallRedirectionService.SERVICE_INTERFACE)
+ .setComponent(mComponentName);
+ ServiceConnection connection = new CallRedirectionServiceConnection();
+ if (mContext.bindServiceAsUser(
+ intent,
+ connection,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+ UserHandle.CURRENT)) {
+ Log.d(this, "bindService, found " + mServiceType + " call redirection service,"
+ + " waiting for it to connect");
+ mConnection = connection;
+ }
+ }
+
+ private void onServiceBound(ICallRedirectionService service) {
+ mService = service;
+ try {
+ mService.placeCall(new CallRedirectionAdapter(), mHandle, mPhoneAccountHandle);
+ Log.addEvent(mCall, mServiceType.equals(SERVICE_TYPE_USER_DEFINED)
+ ? LogUtils.Events.REDIRECTION_SENT_USER
+ : LogUtils.Events.REDIRECTION_SENT_CARRIER);
+ Log.d(this, "Requested placeCall with [handle]" + Log.pii(mHandle)
+ + " [phoneAccountHandle]" + mPhoneAccountHandle);
+ } catch (RemoteException e) {
+ Log.e(this, e, "Failed to request with the found " + mServiceType + " call"
+ + " redirection service");
+ finishCallRedirection();
+ }
+ }
+
+ private void finishCallRedirection() {
+ if (((mServiceType.equals(SERVICE_TYPE_CARRIER)) && mIsCarrierRedirectionPending)
+ || ((mServiceType.equals(SERVICE_TYPE_USER_DEFINED))
+ && mIsUserDefinedRedirectionPending)) {
+ if (mConnection != null) {
+ // We still need to call unbind even if the service disconnected.
+ mContext.unbindService(mConnection);
+ mConnection = null;
+ }
+ mService = null;
+ onCallRedirectionComplete(mCall);
+ }
+ }
+
+ private class CallRedirectionServiceConnection implements ServiceConnection {
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder service) {
+ Log.startSession("CRSC.oSC");
+ try {
+ synchronized (mTelecomLock) {
+ Log.addEvent(mCall, mServiceType.equals(SERVICE_TYPE_USER_DEFINED)
+ ? LogUtils.Events.REDIRECTION_BOUND_USER
+ : LogUtils.Events.REDIRECTION_BOUND_CARRIER, componentName);
+ onServiceBound(ICallRedirectionService.Stub.asInterface(service));
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) {
+ Log.startSession("CRSC.oSD");
+ try {
+ synchronized (mTelecomLock) {
+ finishCallRedirection();
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+ }
+
+ private class CallRedirectionAdapter extends ICallRedirectionAdapter.Stub {
+ @Override
+ public void cancelCall() {
+ Log.startSession("CRA.cC");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mTelecomLock) {
+ Log.d(this, "Received cancelCall from " + mServiceType + " call"
+ + " redirection service");
+ mShouldCancelCall = true;
+ finishCallRedirection();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ Log.endSession();
+ }
+ }
+
+ @Override
+ public void placeCallUnmodified() {
+ Log.startSession("CRA.pCU");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mTelecomLock) {
+ Log.d(this, "Received placeCallUnmodified from " + mServiceType + " call"
+ + " redirection service");
+ finishCallRedirection();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ Log.endSession();
+ }
+ }
+
+ @Override
+ public void redirectCall(Uri handle, PhoneAccountHandle targetPhoneAccount) {
+ Log.startSession("CRA.rC");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mTelecomLock) {
+ mHandle = handle;
+ mPhoneAccountHandle = targetPhoneAccount;
+ Log.d(this, "Received redirectCall with [handle]" + Log.pii(mHandle)
+ + " [phoneAccountHandle]" + mPhoneAccountHandle + " from "
+ + mServiceType + " call" + " redirection service");
+ finishCallRedirection();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ Log.endSession();
+ }
+ }
+ }
+ }
+
+ private final Context mContext;
+ private final CallsManager mCallsManager;
+ private final Call mCall;
+ private final PhoneAccountRegistrar mPhoneAccountRegistrar;
+ private final Timeouts.Adapter mTimeoutsAdapter;
+ private final TelecomSystem.SyncRoot mTelecomLock;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ private CallRedirectionAttempt mAttempt;
+ public static final String SERVICE_TYPE_CARRIER = "carrier";
+ public static final String SERVICE_TYPE_USER_DEFINED = "user_defined";
+
+ private PhoneAccountHandle mPhoneAccountHandle;
+ private Uri mHandle;
+
+ /**
+ * Indicates if Telecom should cancel the call when the whole call redirection finishes.
+ */
+ private boolean mShouldCancelCall = false;
+ /**
+ * Indicates if Telecom is waiting for a callback from a user-defined
+ * {@link CallRedirectionService}.
+ */
+ private boolean mIsUserDefinedRedirectionPending = false;
+ /**
+ * Indicates if Telecom is waiting for a callback from a carrier
+ * {@link CallRedirectionService}.
+ */
+ private boolean mIsCarrierRedirectionPending = false;
+
+ public CallRedirectionProcessor(
+ Context context,
+ CallsManager callsManager,
+ Call call,
+ PhoneAccountRegistrar phoneAccountRegistrar,
+ Uri handle,
+ PhoneAccountHandle phoneAccountHandle,
+ Timeouts.Adapter timeoutsAdapter,
+ TelecomSystem.SyncRoot lock) {
+ mContext = context;
+ mCallsManager = callsManager;
+ mCall = call;
+ mPhoneAccountRegistrar = phoneAccountRegistrar;
+ mHandle = handle;
+ mPhoneAccountHandle = phoneAccountHandle;
+ mTimeoutsAdapter = timeoutsAdapter;
+ mTelecomLock = lock;
+ }
+
+ @Override
+ public void onCallRedirectionComplete(Call call) {
+ // synchronized on mTelecomLock to enter into Telecom.
+ mHandler.post(new Runnable("CRP.oCRC", mTelecomLock) {
+ @Override
+ public void loggedRun() {
+ if (mIsUserDefinedRedirectionPending) {
+ Log.addEvent(mCall, LogUtils.Events.REDIRECTION_COMPLETED_USER);
+ mIsUserDefinedRedirectionPending = false;
+ if (mShouldCancelCall) {
+ // TODO mCallsManager.onCallRedirectionComplete
+ } else {
+ performCarrierCallRedirection();
+ }
+ }
+ if (mIsCarrierRedirectionPending) {
+ Log.addEvent(mCall, LogUtils.Events.REDIRECTION_COMPLETED_CARRIER);
+ mIsCarrierRedirectionPending = false;
+ // TODO mCallsManager.onCallRedirectionComplete
+ }
+ }
+ }.prepare());
+ }
+
+ /*
+ * The entry to perform call redirection of the call from (@link CallsManager)
+ */
+ public void performCallRedirection() {
+ performUserDefinedCallRedirection();
+ }
+
+ private void performUserDefinedCallRedirection() {
+ Log.d(this, "performUserDefinedCallRedirection");
+ ComponentName componentName = getUserDefinedCallRedirectionService(mContext);
+ if (componentName != null && canBindToCallRedirectionService(mContext, componentName)) {
+ mAttempt = new CallRedirectionAttempt(componentName, SERVICE_TYPE_USER_DEFINED);
+ mAttempt.process();
+ mIsUserDefinedRedirectionPending = true;
+ processTimeoutForCallRedirection(SERVICE_TYPE_USER_DEFINED);
+ } else {
+ Log.i(this, "There are no user-defined call redirection services installed on this"
+ + " device.");
+ performCarrierCallRedirection();
+ }
+ }
+
+ private void performCarrierCallRedirection() {
+ Log.d(this, "performCarrierCallRedirection");
+ ComponentName componentName = getCarrierCallRedirectionService(
+ mContext, mPhoneAccountHandle);
+ if (componentName != null && canBindToCallRedirectionService(mContext, componentName)) {
+ mAttempt = new CallRedirectionAttempt(componentName, SERVICE_TYPE_CARRIER);
+ mAttempt.process();
+ mIsCarrierRedirectionPending = true;
+ processTimeoutForCallRedirection(SERVICE_TYPE_CARRIER);
+ } else {
+ Log.i(this, "There are no carrier call redirection services installed on this"
+ + " device.");
+ // TODO return to CallsManager.onCallRedirectionComplete
+ }
+ }
+
+ private void processTimeoutForCallRedirection(String serviceType) {
+ long timeout = serviceType.equals(SERVICE_TYPE_USER_DEFINED) ?
+ mTimeoutsAdapter.getUserDefinedCallRedirectionTimeoutMillis(
+ mContext.getContentResolver()) : mTimeoutsAdapter
+ .getCarrierCallRedirectionTimeoutMillis(mContext.getContentResolver());
+
+ mHandler.postDelayed(new Runnable("CRP.pTFCR", null) {
+ @Override
+ public void loggedRun() {
+ boolean isCurrentRedirectionPending =
+ serviceType.equals(SERVICE_TYPE_USER_DEFINED) ?
+ mIsUserDefinedRedirectionPending : mIsCarrierRedirectionPending;
+ if (isCurrentRedirectionPending) {
+ Log.i(CallRedirectionProcessor.this,
+ serviceType + "call redirection has timed out.");
+ Log.addEvent(mCall, serviceType.equals(SERVICE_TYPE_USER_DEFINED)
+ ? LogUtils.Events.REDIRECTION_TIMED_OUT_USER
+ : LogUtils.Events.REDIRECTION_TIMED_OUT_CARRIER);
+ onCallRedirectionComplete(mCall);
+ }
+ }
+ }.prepare(), timeout);
+ }
+
+ private ComponentName getUserDefinedCallRedirectionService(Context context) {
+ // TODO get service component name from settings default value:
+ // android.provider.Settings#CALL_REDIRECTION_DEFAULT_APPLICATION
+ return null;
+ }
+
+ private ComponentName getCarrierCallRedirectionService(Context context, PhoneAccountHandle
+ targetPhoneAccountHandle) {
+ CarrierConfigManager configManager = (CarrierConfigManager)
+ context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (configManager == null) {
+ Log.i(this, "Cannot get CarrierConfigManager.");
+ return null;
+ }
+ PersistableBundle pb = configManager.getConfigForSubId(mPhoneAccountRegistrar
+ .getSubscriptionIdForPhoneAccount(targetPhoneAccountHandle));
+ if (pb == null) {
+ Log.i(this, "Cannot get PersistableBundle.");
+ return null;
+ }
+ String componentNameString = pb.getString(
+ CarrierConfigManager.KEY_CALL_REDIRECTION_SERVICE_COMPONENT_NAME_STRING);
+ return new ComponentName(context, componentNameString);
+ }
+
+ private boolean canBindToCallRedirectionService(Context context, ComponentName componentName) {
+ Intent intent = new Intent(CallRedirectionService.SERVICE_INTERFACE);
+ intent.setComponent(componentName);
+ List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
+ intent, 0, mCallsManager.getCurrentUserHandle().getIdentifier());
+ if (entries.isEmpty()) {
+ Log.i(this, "There are no call redirection services installed on this device.");
+ return false;
+ } else if (entries.size() != 1) {
+ Log.i(this, "There are multiple call redirection services installed on this device.");
+ return false;
+ } else {
+ ResolveInfo entry = entries.get(0);
+ if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals(
+ Manifest.permission.BIND_CALL_REDIRECTION_SERVICE)) {
+ Log.w(this, "CallRedirectionService must require BIND_CALL_REDIRECTION_SERVICE"
+ + " permission: " + entry.serviceInfo.packageName);
+ return false;
+ }
+ AppOpsManager appOps = (AppOpsManager) context.getSystemService(
+ Context.APP_OPS_SERVICE);
+ if (appOps.noteOp(AppOpsManager.OP_PROCESS_OUTGOING_CALLS, Binder.getCallingUid(),
+ entry.serviceInfo.packageName) != AppOpsManager.MODE_ALLOWED) {
+ Log.w(this, "App Ops does not allow " + entry.serviceInfo.packageName);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns the handler for testing purposes.
+ */
+ @VisibleForTesting
+ public Handler getHandler() {
+ return mHandler;
+ }
+}
diff --git a/src/com/android/server/telecom/components/PhoneAccountBroadcastReceiver.java b/src/com/android/server/telecom/components/AppUninstallBroadcastReceiver.java
similarity index 68%
rename from src/com/android/server/telecom/components/PhoneAccountBroadcastReceiver.java
rename to src/com/android/server/telecom/components/AppUninstallBroadcastReceiver.java
index 7737cd8..3a0d517 100644
--- a/src/com/android/server/telecom/components/PhoneAccountBroadcastReceiver.java
+++ b/src/com/android/server/telecom/components/AppUninstallBroadcastReceiver.java
@@ -19,9 +19,12 @@
import com.android.server.telecom.PhoneAccountRegistrar;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.telecom.TelecomManager;
import java.lang.String;
@@ -30,14 +33,20 @@
* Captures {@code android.intent.action.ACTION_PACKAGE_FULLY_REMOVED} intents and triggers the
* removal of associated {@link android.telecom.PhoneAccount}s via the
* {@link com.android.telecom.PhoneAccountRegistrar}.
+ *
* Note: This class listens for the {@code PACKAGE_FULLY_REMOVED} intent rather than
* {@code PACKAGE_REMOVED} as {@code PACKAGE_REMOVED} is triggered on re-installation of the same
* package, where {@code PACKAGE_FULLY_REMOVED} is triggered only when an application is completely
- * uninstalled. This is desirable as we do not wish to un-register all
+ * uninstalled.
+ *
+ * This is desirable as we do not wish to un-register all
* {@link android.telecom.PhoneAccount}s associated with a package being re-installed to ensure
* the enabled state of the accounts is retained.
+ *
+ * When default call screening application is removed, set
+ * {@link Settings.Secure.CALL_SCREENING_DEFAULT_APPLICATION} as null into provider.
*/
-public class PhoneAccountBroadcastReceiver extends BroadcastReceiver {
+public class AppUninstallBroadcastReceiver extends BroadcastReceiver {
/**
* Receives the intents the class is configured to received.
*
@@ -54,6 +63,7 @@
String packageName = uri.getSchemeSpecificPart();
handlePackageRemoved(context, packageName);
+ handleUninstallOfCallScreeningService(context, packageName);
}
}
@@ -69,4 +79,20 @@
telecomManager.clearAccountsForPackage(packageName);
}
}
+
+ private void handleUninstallOfCallScreeningService(Context context, String packageName) {
+ ComponentName componentName = null;
+ String defaultCallScreeningApp = Settings.Secure
+ .getStringForUser(context.getContentResolver(),
+ Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT, UserHandle.USER_CURRENT);
+
+ if (defaultCallScreeningApp != null) {
+ componentName = ComponentName.unflattenFromString(defaultCallScreeningApp);
+ }
+
+ if (componentName != null && componentName.getPackageName().equals(packageName)) {
+ Settings.Secure.putStringForUser(context.getContentResolver(),
+ Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT, null, UserHandle.USER_CURRENT);
+ }
+ }
}
diff --git a/src/com/android/server/telecom/components/ChangeDefaultCallScreeningApp.java b/src/com/android/server/telecom/components/ChangeDefaultCallScreeningApp.java
new file mode 100644
index 0000000..a88c7d1
--- /dev/null
+++ b/src/com/android/server/telecom/components/ChangeDefaultCallScreeningApp.java
@@ -0,0 +1,141 @@
+/*
+ * 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.components;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+import com.android.server.telecom.R;
+
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.telecom.Log;
+import android.telecom.TelecomManager;
+import android.text.TextUtils;
+
+public class ChangeDefaultCallScreeningApp extends AlertActivity implements
+ DialogInterface.OnClickListener {
+
+ private static final String TAG = ChangeDefaultCallScreeningApp.class.getSimpleName();
+ private ComponentName mNewComponentName;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mNewComponentName = ComponentName.unflattenFromString(getIntent().getStringExtra(
+ TelecomManager.EXTRA_DEFAULT_CALL_SCREENING_APP_COMPONENT_NAME));
+
+ if (canChangeDefaultCallScreening()) {
+ buildDialog();
+ } else {
+ finish();
+ }
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case BUTTON_POSITIVE:
+ try {
+ TelecomManager.from(this).setDefaultCallScreeningApp(mNewComponentName);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, e, e.getMessage());
+ break;
+ }
+ break;
+ case BUTTON_NEGATIVE:
+ break;
+ }
+ }
+
+ private boolean canChangeDefaultCallScreening() {
+ boolean canChange;
+ String defaultComponentName = Settings.Secure
+ .getStringForUser(getApplicationContext().getContentResolver(),
+ Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT, UserHandle.USER_CURRENT);
+
+ ComponentName oldComponentName = TextUtils.isEmpty(defaultComponentName) ? null
+ : ComponentName.unflattenFromString(defaultComponentName);
+
+ canChange = oldComponentName == null || !oldComponentName.getPackageName()
+ .equals(mNewComponentName.getPackageName()) || !oldComponentName.flattenToString()
+ .equals(mNewComponentName.flattenToString());
+
+ return canChange;
+ }
+
+ private void buildDialog() {
+ final String newPackageLabel = getApplicationLabel(mNewComponentName);
+ final AlertController.AlertParams p = mAlertParams;
+
+ p.mTitle = getString(R.string.change_default_call_screening_dialog_title, newPackageLabel);
+ p.mMessage = getDialogBody(newPackageLabel);
+ p.mPositiveButtonText = getString(
+ R.string.change_default_call_screening_dialog_affirmative);
+ p.mNegativeButtonText = getString(R.string.change_default_call_screening_dialog_negative);
+ p.mPositiveButtonListener = this;
+ p.mNegativeButtonListener = this;
+
+ setupAlert();
+ }
+
+ private String getDialogBody(String newPackageLabel) {
+ final String oldPackage = Settings.Secure.getStringForUser(
+ getApplicationContext().getContentResolver(),
+ Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT, UserHandle.USER_CURRENT);
+ ComponentName oldPackageComponentName = null;
+ if (!TextUtils.isEmpty(oldPackage)) {
+ oldPackageComponentName = ComponentName.unflattenFromString(oldPackage);
+ }
+
+ String dialogBody = getString(R.string.change_default_call_screening_warning_message,
+ newPackageLabel);
+
+ if (oldPackageComponentName != null) {
+ dialogBody = getString(
+ R.string.change_default_call_screening_warning_message_for_disable_old_app,
+ getApplicationLabel(oldPackageComponentName)) + " "
+ + dialogBody;
+ }
+
+ return dialogBody;
+ }
+
+ /**
+ * Returns the application label that corresponds to the given package name
+ *
+ * @return Application label for the given package name, or null if not found.
+ */
+ private String getApplicationLabel(ComponentName componentName) {
+ final PackageManager pm = getPackageManager();
+
+ try {
+ ApplicationInfo info = pm.getApplicationInfo(componentName.getPackageName(), 0);
+ return pm.getApplicationLabel(info).toString();
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Application info not found for packageName " + componentName
+ .getPackageName());
+ }
+
+ return componentName.getPackageName();
+ }
+}
diff --git a/src/com/android/server/telecom/components/PrimaryCallReceiver.java b/src/com/android/server/telecom/components/PrimaryCallReceiver.java
deleted file mode 100644
index f19a243..0000000
--- a/src/com/android/server/telecom/components/PrimaryCallReceiver.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.android.server.telecom.components;
-
-import com.android.server.telecom.TelecomSystem;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.telecom.Log;
-
-/**
- * Single point of entry for all outgoing and incoming calls. {@link UserCallIntentProcessor} serves
- * as a trampoline that captures call intents for individual users and forwards it to
- * the {@link PrimaryCallReceiver} which interacts with the rest of Telecom, both of which run only as
- * the primary user.
- */
-public class PrimaryCallReceiver extends BroadcastReceiver implements TelecomSystem.Component {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.startSession("PCR.oR");
- synchronized (getTelecomSystem().getLock()) {
- getTelecomSystem().getCallIntentProcessor().processIntent(intent);
- }
- Log.endSession();
- }
-
- @Override
- public TelecomSystem getTelecomSystem() {
- return TelecomSystem.getInstance();
- }
-}
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 9a09636..153ddc4 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -33,6 +33,7 @@
import com.android.server.telecom.AsyncRingtonePlayer;
import com.android.server.telecom.BluetoothAdapterProxy;
import com.android.server.telecom.BluetoothPhoneServiceImpl;
+import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.CallerInfoAsyncQueryFactory;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ClockProxy;
@@ -167,20 +168,13 @@
phoneAccountRegistrar);
}
},
- new ConnectionServiceFocusManager
- .ConnectionServiceFocusManagerFactory() {
- @Override
- public ConnectionServiceFocusManager create(
- ConnectionServiceFocusManager.CallsManagerRequester requester,
- Looper looper) {
- return new ConnectionServiceFocusManager(requester, looper);
- }
- },
+ ConnectionServiceFocusManager::new,
new Timeouts.Adapter(),
new AsyncRingtonePlayer(shouldPauseBetweenRingtoneRepeat),
new PhoneNumberUtilsAdapterImpl(),
new IncomingCallNotifier(context),
ToneGenerator::new,
+ new CallAudioRouteStateMachine.Factory(),
new ClockProxy() {
@Override
public long currentTimeMillis() {
diff --git a/src/com/android/server/telecom/components/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
index 0c8525f..ae4a7d8 100644
--- a/src/com/android/server/telecom/components/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -159,7 +159,7 @@
// Save the user handle of current user before forwarding the intent to primary user.
intent.putExtra(CallIntentProcessor.KEY_INITIATING_USER, mUserHandle);
- sendIntentToDestination(intent, isLocalInvocation);
+ sendIntentToDestination(intent, isLocalInvocation, callingPackageName);
}
private boolean isDefaultOrSystemDialer(String callingPackageName) {
@@ -189,27 +189,29 @@
}
/**
- * Potentially trampolines the intent to the broadcast receiver that runs only as the primary
- * user. If the caller is local to the Telecom service, we send the intent to Telecom without
- * rebroadcasting it.
+ * Potentially trampolines the intent to Telecom via TelecomServiceImpl.
+ * If the caller is local to the Telecom service, we send the intent to Telecom without
+ * sending it through TelecomServiceImpl.
*/
- private boolean sendIntentToDestination(Intent intent, boolean isLocalInvocation) {
+ private boolean sendIntentToDestination(Intent intent, boolean isLocalInvocation,
+ String callingPackage) {
intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, false);
intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- intent.setClass(mContext, PrimaryCallReceiver.class);
if (isLocalInvocation) {
// We are invoking this from TelecomServiceImpl, so TelecomSystem is available. Don't
// bother trampolining the intent, just sent it directly to the call intent processor.
// TODO: We should not be using an intent here; this whole flows needs cleanup.
Log.i(this, "sendIntentToDestination: send intent to Telecom directly.");
synchronized (TelecomSystem.getInstance().getLock()) {
- TelecomSystem.getInstance().getCallIntentProcessor().processIntent(intent);
+ TelecomSystem.getInstance().getCallIntentProcessor().processIntent(intent,
+ callingPackage);
}
} else {
// We're calling from the UserCallActivity, so the TelecomSystem is not in the same
// process; we need to trampoline to TelecomSystem in the system server process.
Log.i(this, "sendIntentToDestination: trampoline to Telecom.");
- mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
+ TelecomManager tm = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
+ tm.handleCallIntent(intent);
}
return true;
}
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
index ae7e661..1593e23 100644
--- a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
+++ b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
@@ -121,6 +121,7 @@
mAddButton = (TextView) findViewById(R.id.add_blocked);
mAddButton.setOnClickListener(this);
+ mAddButton.setContentDescription(getText(R.string.block_number));
mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
String[] fromColumns = {BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER};
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersUtil.java b/src/com/android/server/telecom/settings/BlockedNumbersUtil.java
index 4f45720..5acfe64 100644
--- a/src/com/android/server/telecom/settings/BlockedNumbersUtil.java
+++ b/src/com/android/server/telecom/settings/BlockedNumbersUtil.java
@@ -33,6 +33,7 @@
import android.widget.Toast;
import com.android.server.telecom.R;
+import com.android.server.telecom.SystemSettingsUtil;
import com.android.server.telecom.ui.NotificationChannelManager;
import java.util.Locale;
@@ -134,7 +135,8 @@
carrierConfig = configManager.getDefaultConfig();
}
return carrierConfig.getBoolean(
- CarrierConfigManager.KEY_SUPPORT_ENHANCED_CALL_BLOCKING_BOOL);
+ CarrierConfigManager.KEY_SUPPORT_ENHANCED_CALL_BLOCKING_BOOL)
+ || new SystemSettingsUtil().isEnhancedCallBlockingEnabled(context);
}
/**
diff --git a/src/com/android/server/telecom/ui/TelecomDeveloperMenu.java b/src/com/android/server/telecom/ui/TelecomDeveloperMenu.java
new file mode 100644
index 0000000..ae2e853
--- /dev/null
+++ b/src/com/android/server/telecom/ui/TelecomDeveloperMenu.java
@@ -0,0 +1,55 @@
+/*
+ * 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.ui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.Switch;
+
+import com.android.server.telecom.R;
+import com.android.server.telecom.SystemSettingsUtil;
+
+/**
+ * Telecom Developer Settings Menu.
+ */
+public class TelecomDeveloperMenu extends Activity {
+
+ private Switch mEnhancedCallingSwitch;
+ private SystemSettingsUtil mSystemSettingsUtil;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mSystemSettingsUtil = new SystemSettingsUtil();
+ setContentView(R.layout.telecom_developer_menu);
+
+ mEnhancedCallingSwitch = findViewById(R.id.switchEnhancedCallBlocking);
+ mEnhancedCallingSwitch.setOnClickListener(l -> {
+ handleEnhancedCallingToggle();
+ });
+ loadPreferences();
+ }
+
+ private void handleEnhancedCallingToggle() {
+ mSystemSettingsUtil.setEnhancedCallBlockingEnabled(this,
+ mEnhancedCallingSwitch.isChecked());
+ }
+
+ private void loadPreferences() {
+ mEnhancedCallingSwitch.setChecked(mSystemSettingsUtil.isEnhancedCallBlockingEnabled(this));
+ }
+}
\ No newline at end of file
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index 48451d1..02443ba 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -19,9 +19,10 @@
package="com.android.server.telecom.testapps">
<uses-sdk
- android:minSdkVersion="23"
- android:targetSdkVersion="23" />
+ android:minSdkVersion="28"
+ android:targetSdkVersion="28" />
+ <uses-permission android:name="android.permission.ACCEPT_HANDOVER" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.CALL_PHONE" />
diff --git a/testapps/res/layout/self_managed_call_list_item.xml b/testapps/res/layout/self_managed_call_list_item.xml
index 66b5b21..6a31a50 100644
--- a/testapps/res/layout/self_managed_call_list_item.xml
+++ b/testapps/res/layout/self_managed_call_list_item.xml
@@ -65,13 +65,18 @@
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="Speaker"
+ android:text="🔊"
android:id="@+id/speakerButton" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="Earpiece"
+ android:text="👂"
android:id="@+id/earpieceButton" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="🎧"
+ android:id="@+id/bluetoothButton" />
<CheckBox
android:id="@+id/holdable"
android:layout_width="wrap_content"
diff --git a/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java b/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java
index 758ae4f..1c84d29 100644
--- a/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java
+++ b/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java
@@ -19,6 +19,7 @@
import com.android.server.telecom.testapps.R;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -26,6 +27,7 @@
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.Icon;
+import android.media.AudioAttributes;
import android.net.Uri;
import android.os.Bundle;
import android.telecom.PhoneAccount;
@@ -45,6 +47,7 @@
*/
public class CallServiceNotifier {
private static final CallServiceNotifier INSTANCE = new CallServiceNotifier();
+ private static final String CHANNEL_ID = "channel1";
public static final String CALL_PROVIDER_ID = "testapps_TestConnectionService_CALL_PROVIDER_ID";
public static final String SIM_SUBSCRIPTION_ID =
@@ -86,6 +89,9 @@
*/
public void updateNotification(Context context) {
log("adding the notification ------------");
+ NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Test Channel",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ getNotificationManager(context).createNotificationChannel(channel);
getNotificationManager(context).notify(CALL_NOTIFICATION_ID, getMainNotification(context));
getNotificationManager(context).notify(
PHONE_ACCOUNT_NOTIFICATION_ID, getPhoneAccountNotification(context));
@@ -219,7 +225,7 @@
* Creates a notification object for using the telecom APIs.
*/
private Notification getPhoneAccountNotification(Context context) {
- final Notification.Builder builder = new Notification.Builder(context);
+ final Notification.Builder builder = new Notification.Builder(context, CHANNEL_ID);
// Both notifications have buttons and only the first one with buttons will show its
// buttons. Since the phone accounts notification is always first, setting false ensures
// it can be dismissed to use the other notification.
@@ -244,7 +250,7 @@
* Creates a notification object out of the current calls state.
*/
private Notification getMainNotification(Context context) {
- final Notification.Builder builder = new Notification.Builder(context);
+ final Notification.Builder builder = new Notification.Builder(context, CHANNEL_ID);
builder.setOngoing(true);
builder.setPriority(Notification.PRIORITY_HIGH);
builder.setSmallIcon(android.R.drawable.stat_sys_phone_call);
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
index 8eaa282..75ceb62 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
@@ -104,6 +104,16 @@
}
};
+ private View.OnClickListener mBluetoothListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ View parent = (View) v.getParent().getParent();
+ SelfManagedConnection connection = (SelfManagedConnection) parent.getTag();
+ connection.setAudioRoute(CallAudioState.ROUTE_BLUETOOTH);
+ notifyDataSetChanged();
+ }
+ };
+
private View.OnClickListener mHoldableListener = new View.OnClickListener() {
@Override
public void onClick (View v) {
@@ -221,6 +231,8 @@
speakerButton.setOnClickListener(mSpeakerListener);
View earpieceButton = view.findViewById(R.id.earpieceButton);
earpieceButton.setOnClickListener(mEarpieceListener);
+ View bluetoothButton = view.findViewById(R.id.bluetoothButton);
+ bluetoothButton.setOnClickListener(mBluetoothListener);
View missedButton = view.findViewById(R.id.missedButton);
missedButton.setOnClickListener(mMissedListener);
missedButton.setVisibility(isRinging ? View.VISIBLE : View.GONE);
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
index a7b1350..959b855 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
@@ -17,6 +17,10 @@
package com.android.server.telecom.testapps;
import android.app.Activity;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.media.AudioAttributes;
+import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.telecom.ConnectionRequest;
@@ -100,6 +104,7 @@
| WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
getWindow().addFlags(flags);
+ configureNotificationChannel();
setContentView(R.layout.self_managed_sample_main);
mCheckIfPermittedBeforeCalling = (CheckBox) findViewById(
R.id.checkIfPermittedBeforeCalling);
@@ -114,12 +119,12 @@
mPlaceIncomingCallButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- placeIncomingCall(false /* isHandoverFrom */);
+ placeIncomingCall();
}
});
mHandoverFrom = (Button) findViewById(R.id.handoverFrom);
mHandoverFrom.setOnClickListener((v -> {
- placeIncomingCall(true /* isHandoverFrom */);
+ initiateHandover();
}));
mUseAcct1Button = findViewById(R.id.useAcct1Button);
@@ -171,7 +176,14 @@
tm.placeCall(Uri.parse(mNumber.getText().toString()), extras);
}
- private void placeIncomingCall(boolean isHandoverFrom) {
+ private void initiateHandover() {
+ TelecomManager tm = TelecomManager.from(this);
+ PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
+ Uri address = Uri.parse(mNumber.getText().toString());
+ tm.acceptHandover(address, VideoProfile.STATE_BIDIRECTIONAL, phoneAccountHandle);
+ }
+
+ private void placeIncomingCall() {
TelecomManager tm = TelecomManager.from(this);
PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
@@ -191,9 +203,22 @@
extras.putInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE,
VideoProfile.STATE_BIDIRECTIONAL);
}
- if (isHandoverFrom) {
- extras.putBoolean(TelecomManager.EXTRA_IS_HANDOVER, true);
- }
tm.addNewIncomingCall(getSelectedPhoneAccountHandle(), extras);
}
+
+ private void configureNotificationChannel() {
+ NotificationChannel channel = new NotificationChannel(
+ SelfManagedConnection.INCOMING_CALL_CHANNEL_ID, "Incoming Calls",
+ NotificationManager.IMPORTANCE_MAX);
+ channel.setShowBadge(false);
+ Uri ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
+ channel.setSound(ringtoneUri, new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .build());
+ channel.enableLights(true);
+
+ NotificationManager mgr = getSystemService(NotificationManager.class);
+ mgr.createNotificationChannel(channel);
+ }
}
\ No newline at end of file
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
index 8d0af04..ebd7423 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
@@ -22,7 +22,10 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Icon;
+import android.media.AudioAttributes;
import android.media.MediaPlayer;
+import android.media.RingtoneManager;
+import android.net.Uri;
import android.os.Bundle;
import android.telecom.Call;
import android.telecom.CallAudioState;
@@ -43,7 +46,7 @@
public void onConnectionStateChanged(SelfManagedConnection connection) {}
public void onConnectionRemoved(SelfManagedConnection connection) {}
}
-
+ public static final String INCOMING_CALL_CHANNEL_ID = "INCOMING_CALL_CHANNEL_ID";
public static final String EXTRA_PHONE_ACCOUNT_HANDLE =
"com.android.server.telecom.testapps.extra.PHONE_ACCOUNT_HANDLE";
public static final String CALL_NOTIFICATION = "com.android.server.telecom.testapps.CALL";
@@ -58,6 +61,7 @@
private boolean mIsIncomingCallUiShowing;
private Listener mListener;
private boolean mIsHandover;
+ private Notification.Builder mNotificationBuilder;
SelfManagedConnection(SelfManagedCallList callList, Context context, boolean isIncoming) {
mCallList = callList;
@@ -93,7 +97,8 @@
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 1, intent, 0);
// Build the notification as an ongoing high priority item.
- final Notification.Builder builder = new Notification.Builder(mContext);
+ final Notification.Builder builder = new Notification.Builder(mContext,
+ INCOMING_CALL_CHANNEL_ID);
builder.setOngoing(true);
builder.setPriority(Notification.PRIORITY_HIGH);
@@ -131,9 +136,12 @@
PendingIntent.FLAG_UPDATE_CURRENT))
.build());
+ Notification notification = builder.build();
+ notification.flags |= Notification.FLAG_INSISTENT;
NotificationManager notificationManager = mContext.getSystemService(
NotificationManager.class);
- notificationManager.notify(CALL_NOTIFICATION, mCallId, builder.build());
+ mNotificationBuilder = builder;
+ notificationManager.notify(CALL_NOTIFICATION, mCallId, notification);
}
@Override
@@ -172,6 +180,15 @@
setConnectionDisconnected(DisconnectCause.LOCAL);
}
+ @Override
+ public void onSilence() {
+ // Re-post our notification without a ringtone.
+ mNotificationBuilder.setOnlyAlertOnce(true);
+ NotificationManager notificationManager = mContext.getSystemService(
+ NotificationManager.class);
+ notificationManager.notify(CALL_NOTIFICATION, mCallId, mNotificationBuilder.build());
+ }
+
public void setConnectionActive() {
mMediaPlayer.start();
setActive();
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
index d5d79af..f2b6496 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
@@ -24,9 +24,7 @@
import android.telecom.Log;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
-import android.telecom.VideoProfile;
-import java.util.Objects;
import java.util.Random;
/**
@@ -45,13 +43,25 @@
PhoneAccountHandle connectionManagerAccount,
final ConnectionRequest request) {
- return createSelfManagedConnection(request, false);
+ return createSelfManagedConnection(request, false, false /* isHandover */);
}
@Override
public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManagerPhoneAccount,
ConnectionRequest request) {
- return createSelfManagedConnection(request, true);
+ return createSelfManagedConnection(request, true, false /* isHandover */);
+ }
+
+ @Override
+ public Connection onCreateOutgoingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle,
+ ConnectionRequest request) {
+ return createSelfManagedConnection(request, false, true /* isHandover */);
+ }
+
+ @Override
+ public Connection onCreateIncomingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle,
+ ConnectionRequest request) {
+ return createSelfManagedConnection(request, true, true /* isHandover */);
}
@Override
@@ -77,13 +87,15 @@
mCallList.notifyConnectionServiceFocusGained();
}
- private Connection createSelfManagedConnection(ConnectionRequest request, boolean isIncoming) {
+ private Connection createSelfManagedConnection(ConnectionRequest request, boolean isIncoming,
+ boolean isHandover) {
SelfManagedConnection connection = new SelfManagedConnection(mCallList,
getApplicationContext(), isIncoming);
connection.setListener(mCallList.getConnectionListener());
connection.setConnectionProperties(Connection.PROPERTY_SELF_MANAGED);
connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
- connection.setAudioModeIsVoip(true);
+ // Purposely do not set the audio mode to voip since we expect this to be the default:
+ // connection.setAudioModeIsVoip(true);
connection.setVideoState(request.getVideoState());
Random random = new Random();
connection.setCallerDisplayName(TEST_NAMES[random.nextInt(TEST_NAMES.length)],
@@ -97,11 +109,10 @@
if (requestExtras != null) {
boolean isHoldable = requestExtras.getBoolean(EXTRA_HOLDABLE, false);
Log.i(this, "createConnection: isHandover=%b, handoverFrom=%s, holdable=%b",
- requestExtras.getBoolean(TelecomManager.EXTRA_IS_HANDOVER),
+ isHandover,
requestExtras.getString(TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT),
isHoldable);
- connection.setIsHandover(requestExtras.getBoolean(TelecomManager.EXTRA_IS_HANDOVER,
- false));
+ connection.setIsHandover(isHandover);
if (isHoldable) {
connection.setConnectionCapabilities(connection.getConnectionCapabilities() |
Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD);
diff --git a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
index 52dcadb..1a2aac6 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
@@ -16,12 +16,18 @@
package com.android.server.telecom.testapps;
+import static android.media.AudioAttributes.CONTENT_TYPE_SPEECH;
+import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
+
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
import android.media.MediaPlayer;
+import android.media.ToneGenerator;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -57,6 +63,11 @@
public static final String EXTRA_HANDLE = "extra_handle";
+ /**
+ * If an outgoing call ends with 2879 (BUSY), the test CS will indicate the call is busy.
+ */
+ public static final String BUSY_SUFFIX = "2879";
+
private static final String LOG_TAG = TestConnectionService.class.getSimpleName();
private static TestConnectionService INSTANCE;
@@ -204,8 +215,15 @@
void startOutgoing() {
setDialing();
mHandler.postDelayed(() -> {
- setActive();
- activateCall(TestConnection.this);
+ if (getAddress().getSchemeSpecificPart().endsWith(BUSY_SUFFIX)) {
+ setDisconnected(new DisconnectCause(DisconnectCause.REMOTE, "Line busy",
+ "Line busy", "Line busy", ToneGenerator.TONE_SUP_BUSY));
+ destroyCall(this);
+ destroy();
+ } else {
+ setActive();
+ activateCall(TestConnection.this);
+ }
}, 4000);
if (mOriginalRequest.isRequestingRtt()) {
Log.i(LOG_TAG, "Is RTT call. Starting chatbot service.");
@@ -582,8 +600,16 @@
}
private MediaPlayer createMediaPlayer() {
+ AudioAttributes attributes = new AudioAttributes.Builder()
+ .setUsage(USAGE_VOICE_COMMUNICATION)
+ .setContentType(CONTENT_TYPE_SPEECH)
+ .build();
+
+ final int audioSessionId = ((AudioManager) getSystemService(
+ Context.AUDIO_SERVICE)).generateAudioSessionId();
// Prepare the media player to play a tone when there is a call.
- MediaPlayer mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.beep_boop);
+ MediaPlayer mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.beep_boop, attributes,
+ audioSessionId);
mediaPlayer.setLooping(true);
return mediaPlayer;
}
diff --git a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
index 9851253..2a5b33a 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
@@ -18,6 +18,7 @@
import android.app.Activity;
import android.bluetooth.BluetoothDevice;
+import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.telecom.Call;
@@ -209,11 +210,8 @@
handoverButton.setOnClickListener((v) -> {
Call call = mCallList.getCall(0);
- Bundle extras = new Bundle();
- extras.putParcelable(Call.EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE,
- getHandoverToPhoneAccountHandle());
- extras.putInt(Call.EXTRA_HANDOVER_VIDEO_STATE, VideoProfile.STATE_BIDIRECTIONAL);
- call.sendCallEvent(Call.EVENT_REQUEST_HANDOVER, extras);
+ call.handoverTo(getHandoverToPhoneAccountHandle(), VideoProfile.STATE_BIDIRECTIONAL,
+ null);
});
}
@@ -263,17 +261,8 @@
}
private PhoneAccountHandle getHandoverToPhoneAccountHandle() {
- TelecomManager tm = TelecomManager.from(this);
-
- List<PhoneAccountHandle> handles = tm.getAllPhoneAccountHandles();
- Optional<PhoneAccountHandle> found = handles.stream().filter(h -> {
- PhoneAccount account = tm.getPhoneAccount(h);
- Bundle extras = account.getExtras();
- return extras != null && extras.getBoolean(PhoneAccount.EXTRA_SUPPORTS_HANDOVER_TO);
- }).findFirst();
- PhoneAccountHandle foundHandle = found.orElse(null);
- Log.i(TestInCallUI.class.getSimpleName(), "getHandoverToPhoneAccountHandle() = " +
- foundHandle);
- return foundHandle;
+ return new PhoneAccountHandle(new ComponentName(
+ SelfManagedCallList.class.getPackage().getName(),
+ SelfManagedConnectionService.class.getName()), "1");
}
}
diff --git a/tests/Android.mk b/tests/Android.mk
index 5f083de..5abf999 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -20,15 +20,15 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
android-ex-camera2 \
guava \
- mockito-target \
+ mockito-target-inline \
android-support-test \
platform-test-annotations
LOCAL_STATIC_ANDROID_LIBRARIES := \
- android-support-core-ui \
- android-support-core-utils \
- android-support-compat \
- android-support-fragment
+ androidx.legacy_legacy-support-core-ui \
+ androidx.legacy_legacy-support-core-utils \
+ androidx.core_core \
+ androidx.fragment_fragment
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
@@ -51,12 +51,13 @@
LOCAL_USE_AAPT2 := true
+LOCAL_JNI_SHARED_LIBRARIES := \
+ libdexmakerjvmtiagent \
+
LOCAL_AAPT_FLAGS := \
--auto-add-overlay \
--extra-packages com.android.server.telecom
-LOCAL_JACK_FLAGS := --multi-dex native
-
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_PACKAGE_NAME := TelecomUnitTests
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 0e79fce..8132157 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -34,6 +34,7 @@
<!-- Used to access TelephonyManager APIs -->
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<application android:label="@string/app_name"
android:debuggable="true">
diff --git a/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java b/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java
index 9b2b3fb..91ddf5a 100644
--- a/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java
@@ -16,10 +16,14 @@
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;
@@ -49,7 +53,6 @@
@RunWith(JUnit4.class)
public class AsyncBlockCheckFilterTest extends TelecomTestCase {
- @Mock private Context mContext;
@Mock private BlockCheckerAdapter mBlockCheckerAdapter;
@Mock private Call mCall;
@Mock private CallFilterResultCallback mCallback;
@@ -60,8 +63,12 @@
private static final CallFilteringResult BLOCK_RESULT = new CallFilteringResult(
false, // shouldAllowCall
true, //shouldReject
- false, //shouldAddToCallLog
- false // shouldShowNotification
+ true, //shouldAddToCallLog
+ false, // shouldShowNotification
+ CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER, //blockReason
+ null, // callScreeningAppName
+ null //callScreeningComponentName
+
);
private static final CallFilteringResult PASS_RESULT = new CallFilteringResult(
@@ -72,7 +79,7 @@
);
private static final Uri TEST_HANDLE = Uri.parse("tel:1235551234");
- private static final int TEST_TIMEOUT = 100;
+ private static final int TEST_TIMEOUT = 1000;
@Override
@Before
@@ -80,7 +87,7 @@
super.setUp();
when(mCall.getHandle()).thenReturn(TEST_HANDLE);
mFilter = new AsyncBlockCheckFilter(mContext, mBlockCheckerAdapter,
- mCallerInfoLookupHelper);
+ mCallerInfoLookupHelper, null);
}
@SmallTest
@@ -89,9 +96,9 @@
final CountDownLatch latch = new CountDownLatch(1);
doAnswer(invocation -> {
latch.countDown();
- return true;
+ return STATUS_BLOCKED_IN_LIST;
}).when(mBlockCheckerAdapter)
- .isBlocked(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
+ .getBlockStatus(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
any(Bundle.class));
setEnhancedBlockingEnabled(false);
@@ -108,9 +115,9 @@
final CountDownLatch latch = new CountDownLatch(1);
doAnswer(invocation -> {
latch.countDown();
- return true;
+ return STATUS_BLOCKED_IN_LIST;
}).when(mBlockCheckerAdapter)
- .isBlocked(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
+ .getBlockStatus(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
any(Bundle.class));
setEnhancedBlockingEnabled(true);
@@ -128,9 +135,9 @@
final CountDownLatch latch = new CountDownLatch(1);
doAnswer(invocation -> {
latch.countDown();
- return false;
+ return STATUS_NOT_BLOCKED;
}).when(mBlockCheckerAdapter)
- .isBlocked(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
+ .getBlockStatus(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
any(Bundle.class));
setEnhancedBlockingEnabled(false);
@@ -147,9 +154,9 @@
final CountDownLatch latch = new CountDownLatch(1);
doAnswer(invocation -> {
latch.countDown();
- return false;
+ return STATUS_NOT_BLOCKED;
}).when(mBlockCheckerAdapter)
- .isBlocked(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
+ .getBlockStatus(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
any(Bundle.class));
setEnhancedBlockingEnabled(true);
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index e304d34..51bfed7 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertFalse;
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.anyString;
@@ -337,6 +338,9 @@
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);
+ verifyAndProcessOutgoingCallBroadcast(mPhoneAccountA0.getAccountHandle());
IdPair ids = outgoingCallPhoneAccountSelected(mPhoneAccountA0.getAccountHandle(),
startingNumConnections, startingNumCalls, mConnectionServiceFixtureA);
@@ -548,8 +552,10 @@
// 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);
IdPair incoming = startAndMakeActiveIncomingCall("650-555-2323",
mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
verify(mConnectionServiceFixtureA.getTestDouble())
.hold(eq(outgoing.mConnectionId), any());
mConnectionServiceFixtureA.mConnectionById.get(outgoing.mConnectionId).state =
@@ -585,10 +591,15 @@
.setMicrophoneMute(eq(false), any(String.class), any(Integer.class));
mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_SPEAKER, null);
+ waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
+ .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
verify(audioManager, timeout(TEST_TIMEOUT))
.setSpeakerphoneOn(true);
mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_EARPIECE, null);
- verify(audioManager, timeout(TEST_TIMEOUT))
+ 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))
.setSpeakerphoneOn(false);
mConnectionServiceFixtureA.
@@ -784,7 +795,7 @@
anyString(),
eq(BlockedNumberContract.SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER),
eq(phoneNumber),
- isNull(Bundle.class))).thenAnswer(answer);
+ nullable(Bundle.class))).thenAnswer(answer);
}
private void verifyNoBlockChecks() {
@@ -912,12 +923,12 @@
Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL);
com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
.iterator().next();
- assert(call.isVideoCallingSupported());
+ assert(call.isVideoCallingSupportedByPhoneAccount());
assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
// Change the phone account to one which supports video calling.
call.setTargetPhoneAccount(mPhoneAccountA1.getAccountHandle());
- assert(call.isVideoCallingSupported());
+ assert(call.isVideoCallingSupportedByPhoneAccount());
assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
}
@@ -935,12 +946,12 @@
Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL);
com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
.iterator().next();
- assert(call.isVideoCallingSupported());
+ assert(call.isVideoCallingSupportedByPhoneAccount());
assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
// Change the phone account to one which does not support video calling.
call.setTargetPhoneAccount(mPhoneAccountA2.getAccountHandle());
- assert(!call.isVideoCallingSupported());
+ assert(!call.isVideoCallingSupportedByPhoneAccount());
assertEquals(VideoProfile.STATE_AUDIO_ONLY, call.getVideoState());
}
@@ -1045,8 +1056,13 @@
mConnectionServiceFixtureA);
// Should have reverted back to earpiece.
- assertEquals(CallAudioState.ROUTE_EARPIECE,
- mInCallServiceFixtureX.mCallAudioState.getRoute());
+ assertTrueWithTimeout(new Predicate<Void>() {
+ @Override
+ public boolean apply(Void aVoid) {
+ return mInCallServiceFixtureX.mCallAudioState.getRoute()
+ == CallAudioState.ROUTE_EARPIECE;
+ }
+ });
}
/**
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
index 4a48f1b..9fd97f8 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
@@ -18,16 +18,15 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Intent;
-import android.content.IntentFilter;
import android.os.Parcel;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.server.telecom.BluetoothAdapterProxy;
import com.android.server.telecom.BluetoothHeadsetProxy;
-import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
@@ -41,15 +40,20 @@
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.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
@RunWith(JUnit4.class)
public class BluetoothDeviceManagerTest extends TelecomTestCase {
@Mock BluetoothRouteManager mRouteManager;
@Mock BluetoothHeadsetProxy mHeadsetProxy;
@Mock BluetoothAdapterProxy mAdapterProxy;
+ @Mock BluetoothHearingAid mBluetoothHearingAid;
BluetoothDeviceManager mBluetoothDeviceManager;
BluetoothProfile.ServiceListener serviceListenerUnderTest;
@@ -58,18 +62,24 @@
private BluetoothDevice device1;
private BluetoothDevice device2;
private BluetoothDevice device3;
+ private BluetoothDevice device4;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
device1 = makeBluetoothDevice("00:00:00:00:00:01");
+ // hearing aid
device2 = makeBluetoothDevice("00:00:00:00:00:02");
device3 = makeBluetoothDevice("00:00:00:00:00:03");
+ // hearing aid
+ device4 = makeBluetoothDevice("00:00:00:00:00:04");
+
+ when(mBluetoothHearingAid.getHiSyncId(device2)).thenReturn(100L);
+ when(mBluetoothHearingAid.getHiSyncId(device4)).thenReturn(100L);
mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
- mBluetoothDeviceManager = new BluetoothDeviceManager(mContext, mAdapterProxy,
- new TelecomSystem.SyncRoot() { });
+ mBluetoothDeviceManager = new BluetoothDeviceManager(mContext, mAdapterProxy);
mBluetoothDeviceManager.setBluetoothRouteManager(mRouteManager);
ArgumentCaptor<BluetoothProfile.ServiceListener> serviceCaptor =
@@ -82,80 +92,119 @@
null /* route mgr not needed here */);
mBluetoothDeviceManager.setHeadsetServiceForTesting(mHeadsetProxy);
+ mBluetoothDeviceManager.setHearingAidServiceForTesting(mBluetoothHearingAid);
}
@SmallTest
@Test
public void testSingleDeviceConnectAndDisconnect() {
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
assertEquals(1, mBluetoothDeviceManager.getNumConnectedDevices());
- assertEquals(device1.getAddress(),
- mBluetoothDeviceManager.getMostRecentlyConnectedDevice(null));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device1));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device1, false));
assertEquals(0, mBluetoothDeviceManager.getNumConnectedDevices());
- assertNull(mBluetoothDeviceManager.getMostRecentlyConnectedDevice(null));
}
@SmallTest
@Test
+ public void testAddDeviceFailsWhenServicesAreNull() {
+ mBluetoothDeviceManager.setHeadsetServiceForTesting(null);
+ mBluetoothDeviceManager.setHearingAidServiceForTesting(null);
+
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true));
+
+ assertEquals(0, mBluetoothDeviceManager.getNumConnectedDevices());
+ }
+
+ @SmallTest
+ @Test
public void testMultiDeviceConnectAndDisconnect() {
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device1));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device1, false));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3));
- receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3, false));
assertEquals(2, mBluetoothDeviceManager.getNumConnectedDevices());
- assertEquals(device3.getAddress(),
- mBluetoothDeviceManager.getMostRecentlyConnectedDevice(null));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device3));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device3, false));
assertEquals(1, mBluetoothDeviceManager.getNumConnectedDevices());
- assertEquals(device2.getAddress(),
- mBluetoothDeviceManager.getMostRecentlyConnectedDevice(null));
}
@SmallTest
@Test
- public void testExclusionaryGetRecentDevices() {
+ public void testHearingAidDedup() {
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device1));
- receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3));
- receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2));
- assertEquals(2, mBluetoothDeviceManager.getNumConnectedDevices());
- assertEquals(device2.getAddress(),
- mBluetoothDeviceManager.getMostRecentlyConnectedDevice(device3.getAddress()));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device4, true));
+ assertEquals(3, mBluetoothDeviceManager.getNumConnectedDevices());
+ assertEquals(2, mBluetoothDeviceManager.getUniqueConnectedDevices().size());
}
@SmallTest
@Test
public void testHeadsetServiceDisconnect() {
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1));
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
receiverUnderTest.onReceive(mContext,
- buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2));
- serviceListenerUnderTest.onServiceDisconnected(0);
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3, false));
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true));
+ serviceListenerUnderTest.onServiceDisconnected(BluetoothProfile.HEADSET);
verify(mRouteManager).onDeviceLost(device1.getAddress());
- verify(mRouteManager).onDeviceLost(device2.getAddress());
+ verify(mRouteManager).onDeviceLost(device3.getAddress());
+ verify(mRouteManager, never()).onDeviceLost(device2.getAddress());
assertNull(mBluetoothDeviceManager.getHeadsetService());
- assertEquals(0, mBluetoothDeviceManager.getNumConnectedDevices());
+ assertEquals(1, mBluetoothDeviceManager.getNumConnectedDevices());
}
- private Intent buildConnectionActionIntent(int state, BluetoothDevice device) {
- Intent i = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+ @SmallTest
+ @Test
+ public void testConnectDisconnectAudioHeadset() {
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
+ when(mHeadsetProxy.setActiveDevice(nullable(BluetoothDevice.class))).thenReturn(true);
+ mBluetoothDeviceManager.connectAudio(device1.getAddress());
+ verify(mHeadsetProxy).setActiveDevice(device1);
+ verify(mHeadsetProxy).connectAudio();
+ verify(mBluetoothHearingAid, never()).setActiveDevice(nullable(BluetoothDevice.class));
+
+ mBluetoothDeviceManager.disconnectAudio();
+ verify(mHeadsetProxy).disconnectAudio();
+ }
+
+ @SmallTest
+ @Test
+ public void testConnectDisconnectAudioHearingAid() {
+ receiverUnderTest.onReceive(mContext,
+ buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true));
+ mBluetoothDeviceManager.connectAudio(device2.getAddress());
+ verify(mBluetoothHearingAid).setActiveDevice(device2);
+ verify(mHeadsetProxy, never()).connectAudio();
+ verify(mHeadsetProxy, never()).setActiveDevice(nullable(BluetoothDevice.class));
+
+ when(mBluetoothHearingAid.getActiveDevices()).thenReturn(Arrays.asList(device2, null));
+
+ mBluetoothDeviceManager.disconnectAudio();
+ verify(mBluetoothHearingAid).setActiveDevice(null);
+ verify(mHeadsetProxy).disconnectAudio();
+ }
+
+ private Intent buildConnectionActionIntent(int state, BluetoothDevice device,
+ boolean isHearingAid) {
+ Intent i = new Intent(isHearingAid
+ ? BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED
+ : BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
i.putExtra(BluetoothHeadset.EXTRA_STATE, state);
i.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
return i;
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java b/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
index a5feab7..a960d91 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
@@ -309,17 +309,18 @@
mBluetoothPhoneService.mBinder.queryPhoneState();
verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(1), eq(CALL_STATE_IDLE),
- eq(""), eq(128));
+ eq(""), eq(128), nullable(String.class));
when(parentCall.wasConferencePreviouslyMerged()).thenReturn(true);
mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(parentCall);
verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(0), eq(CALL_STATE_IDLE),
- eq(""), eq(128));
+ eq(""), eq(128), nullable(String.class));
when(mMockCallsManager.getHeldCall()).thenReturn(null);
// Spurious call to onIsConferencedChanged.
mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(parentCall);
// Make sure the call has only occurred collectively 2 times (not on the third)
verify(mMockBluetoothHeadset, times(2)).phoneStateChanged(any(int.class),
- any(int.class), any(int.class), nullable(String.class), any(int.class));
+ any(int.class), any(int.class), nullable(String.class), any(int.class),
+ nullable(String.class));
}
@MediumTest
@@ -611,7 +612,7 @@
mBluetoothPhoneService.mBinder.queryPhoneState();
verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
- eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown));
+ eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
}
@MediumTest
@@ -632,7 +633,7 @@
mBluetoothPhoneService.mBinder.queryPhoneState();
verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(0), eq(CALL_STATE_IDLE),
- eq(""), eq(128));
+ eq(""), eq(128), nullable(String.class));
}
@MediumTest
@@ -681,7 +682,8 @@
CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD);
verify(mMockCallsManager).disconnectCall(eq(activeCall));
- verify(mMockCallsManager).unholdCall(eq(heldCall));
+ // Call unhold will occur as part of CallsManager auto-unholding the background call on its
+ // own.
assertEquals(didProcess, true);
}
@@ -771,7 +773,7 @@
verify(parentCall).swapConference();
verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(1), eq(CALL_STATE_IDLE), eq(""),
- eq(128));
+ eq(128), nullable(String.class));
assertEquals(didProcess, true);
}
@@ -785,7 +787,7 @@
mBluetoothPhoneService.mCallsManagerListener.onCallAdded(ringingCall);
verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
- eq("555000"), eq(PhoneNumberUtils.TOA_Unknown));
+ eq("555000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
}
@@ -807,7 +809,7 @@
mBluetoothPhoneService.mCallsManagerListener.onCallAdded(parentCall);
verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(1), eq(CALL_STATE_IDLE),
- eq(""), eq(128));
+ eq(""), eq(128), nullable(String.class));
}
@@ -820,7 +822,7 @@
mBluetoothPhoneService.mCallsManagerListener.onCallRemoved(activeCall);
verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_IDLE),
- eq(""), eq(128));
+ eq(""), eq(128), nullable(String.class));
}
@MediumTest
@@ -838,7 +840,7 @@
CallState.ACTIVE, CallState.ON_HOLD);
verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
- anyString(), anyInt());
+ anyString(), anyInt(), nullable(String.class));
}
@MediumTest
@@ -850,7 +852,7 @@
CallState.CONNECTING, CallState.DIALING);
verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
- anyString(), anyInt());
+ anyString(), anyInt(), nullable(String.class));
}
@MediumTest
@@ -861,8 +863,10 @@
mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(outgoingCall,
CallState.NEW, CallState.DIALING);
- verify(mMockBluetoothHeadset).phoneStateChanged(0, 0, CALL_STATE_DIALING, "", 128);
- verify(mMockBluetoothHeadset).phoneStateChanged(0, 0, CALL_STATE_ALERTING, "", 128);
+ verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_DIALING),
+ eq(""), eq(128), nullable(String.class));
+ verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_ALERTING),
+ eq(""), eq(128), nullable(String.class));
}
@MediumTest
@@ -873,16 +877,16 @@
mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(disconnectedCall,
CallState.DISCONNECTING, CallState.DISCONNECTED);
verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_DISCONNECTED),
- eq(""), eq(128));
+ eq(""), eq(128), nullable(String.class));
doReturn(false).when(mMockCallsManager).hasOnlyDisconnectedCalls();
mBluetoothPhoneService.mCallsManagerListener.onDisconnectedTonePlaying(true);
verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_DISCONNECTED),
- eq(""), eq(128));
+ eq(""), eq(128), nullable(String.class));
mBluetoothPhoneService.mCallsManagerListener.onDisconnectedTonePlaying(false);
verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_IDLE),
- eq(""), eq(128));
+ eq(""), eq(128), nullable(String.class));
}
@MediumTest
@@ -893,7 +897,7 @@
mBluetoothPhoneService.mCallsManagerListener.onCallAdded(ringingCall);
verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
- eq("555-0000"), eq(PhoneNumberUtils.TOA_Unknown));
+ eq("555-0000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
//Switch to active
doReturn(null).when(mMockCallsManager).getRingingCall();
@@ -903,7 +907,7 @@
CallState.RINGING, CallState.ACTIVE);
verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(0), eq(CALL_STATE_IDLE),
- eq(""), eq(128));
+ eq(""), eq(128), nullable(String.class));
}
@MediumTest
@@ -916,7 +920,7 @@
CallState.ACTIVE, CallState.ON_HOLD);
verify(mMockBluetoothHeadset, never()).phoneStateChanged(eq(0), eq(2), eq(CALL_STATE_HELD),
- eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown));
+ eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
}
@MediumTest
@@ -942,18 +946,18 @@
// CDMA "conference"
mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(activeCall);
verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
- anyString(), anyInt());
+ anyString(), anyInt(), nullable(String.class));
mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(heldCall);
verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
- anyString(), anyInt());
+ anyString(), anyInt(), nullable(String.class));
mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(parentCall);
verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
- anyString(), anyInt());
+ anyString(), anyInt(), nullable(String.class));
calls.add(heldCall);
mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(parentCall);
verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(1), eq(CALL_STATE_IDLE),
- eq(""), eq(128));
+ eq(""), eq(128), nullable(String.class));
}
@MediumTest
@@ -967,7 +971,7 @@
mBluetoothPhoneService.mBluetoothAdapterReceiver.onReceive(mContext, intent);
verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
- eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown));
+ eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
}
private void addCallCapability(Call call, int capability) {
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
index 5b45828..42626d9 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
@@ -18,6 +18,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHearingAid;
import android.content.ContentResolver;
import android.os.Parcel;
import android.telecom.Log;
@@ -37,13 +38,12 @@
import org.mockito.Mock;
import java.util.Arrays;
-import java.util.Objects;
+import java.util.Collections;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -58,6 +58,7 @@
@Mock private BluetoothDeviceManager mDeviceManager;
@Mock private BluetoothHeadsetProxy mHeadsetProxy;
+ @Mock private BluetoothHearingAid mBluetoothHearingAid;
@Mock private Timeouts.Adapter mTimeoutsAdapter;
@Mock private BluetoothRouteManager.BluetoothStateListener mListener;
@@ -130,18 +131,14 @@
when(mDeviceManager.getNumConnectedDevices()).thenReturn(devices.length);
when(mDeviceManager.getConnectedDevices()).thenReturn(Arrays.asList(devices));
when(mHeadsetProxy.getConnectedDevices()).thenReturn(Arrays.asList(devices));
+ when(mHeadsetProxy.getAudioState(any(BluetoothDevice.class)))
+ .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+ when(mBluetoothHearingAid.getConnectedDevices()).thenReturn(Collections.emptyList());
+ when(mBluetoothHearingAid.getActiveDevices()).thenReturn(Arrays.asList(null, null));
if (activeDevice != null) {
when(mHeadsetProxy.getAudioState(eq(activeDevice)))
.thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTED);
}
- doAnswer(invocation -> {
- BluetoothDevice first = getFirstExcluding(devices,
- (String) invocation.getArguments()[0]);
- return first == null ? null : first.getAddress();
- }).when(mDeviceManager).getMostRecentlyConnectedDevice(nullable(String.class));
- for (BluetoothDevice device : devices) {
- when(mDeviceManager.getDeviceFromAddress(device.getAddress())).thenReturn(device);
- }
}
static void executeRoutingAction(BluetoothRouteManager brm, int message, String
@@ -165,6 +162,7 @@
private void resetMocks() {
reset(mDeviceManager, mListener, mHeadsetProxy, mTimeoutsAdapter);
when(mDeviceManager.getHeadsetService()).thenReturn(mHeadsetProxy);
+ when(mDeviceManager.getHearingAidService()).thenReturn(mBluetoothHearingAid);
when(mHeadsetProxy.connectAudio()).thenReturn(true);
when(mHeadsetProxy.setActiveDevice(nullable(BluetoothDevice.class))).thenReturn(true);
when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
@@ -174,17 +172,6 @@
}
private void verifyConnectionAttempt(BluetoothDevice device, int numTimes) {
- verify(mHeadsetProxy, times(numTimes)).setActiveDevice(device);
- verify(mHeadsetProxy, atLeast(numTimes)).connectAudio();
- }
-
- private static BluetoothDevice getFirstExcluding(
- BluetoothDevice[] devices, String excludeAddress) {
- for (BluetoothDevice x : devices) {
- if (!Objects.equals(excludeAddress, x.getAddress())) {
- return x;
- }
- }
- return null;
+ verify(mDeviceManager, times(numTimes)).connectAudio(device.getAddress());
}
}
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
index e7cd6ee..4325831 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
@@ -18,9 +18,12 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHearingAid;
import android.content.ContentResolver;
+import android.telecom.Log;
import android.test.suitebuilder.annotation.SmallTest;
+import com.android.internal.os.SomeArgs;
import com.android.server.telecom.BluetoothHeadsetProxy;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.Timeouts;
@@ -36,19 +39,20 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
-import java.util.Objects;
import static com.android.server.telecom.tests.BluetoothRouteManagerTest.DEVICE1;
import static com.android.server.telecom.tests.BluetoothRouteManagerTest.DEVICE2;
import static com.android.server.telecom.tests.BluetoothRouteManagerTest.DEVICE3;
import static com.android.server.telecom.tests.BluetoothRouteManagerTest.executeRoutingAction;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -73,6 +77,7 @@
private BluetoothDevice[] connectedDevices;
// the active device as returned by BluetoothHeadset#getActiveDevice
private BluetoothDevice activeDevice = null;
+ private List<BluetoothDevice> hearingAidBtDevices = Collections.emptyList();
public BluetoothRouteTestParametersBuilder setName(String name) {
this.name = name;
@@ -141,6 +146,12 @@
return this;
}
+ public BluetoothRouteTestParametersBuilder setHearingAidBtDevices(
+ List<BluetoothDevice> hearingAidBtDevices) {
+ this.hearingAidBtDevices = hearingAidBtDevices;
+ return this;
+ }
+
public BluetoothRouteTestParameters build() {
return new BluetoothRouteTestParameters(name,
initialBluetoothState,
@@ -153,7 +164,8 @@
connectedDevices,
messageDevice,
audioOnDevice,
- activeDevice);
+ activeDevice,
+ hearingAidBtDevices);
}
}
@@ -172,13 +184,15 @@
public BluetoothDevice[] connectedDevices; // array of connected devices
// the active device as returned by BluetoothHeadset#getActiveDevice
private BluetoothDevice activeDevice = null;
+ private List<BluetoothDevice> hearingAidBtDevices;
public BluetoothRouteTestParameters(String name, String initialBluetoothState,
BluetoothDevice initialDevice, int messageType, ListenerUpdate[]
expectedListenerUpdates, int expectedBluetoothInteraction, BluetoothDevice
expectedConnectionDevice, String expectedFinalStateName,
BluetoothDevice[] connectedDevices, BluetoothDevice messageDevice,
- BluetoothDevice audioOnDevice, BluetoothDevice activeDevice) {
+ BluetoothDevice audioOnDevice, BluetoothDevice activeDevice,
+ List<BluetoothDevice> hearingAidBtDevices) {
this.name = name;
this.initialBluetoothState = initialBluetoothState;
this.initialDevice = initialDevice;
@@ -191,6 +205,7 @@
this.messageDevice = messageDevice;
this.audioOnDevice = audioOnDevice;
this.activeDevice = activeDevice;
+ this.hearingAidBtDevices = hearingAidBtDevices;
}
@Override
@@ -207,6 +222,7 @@
", expectedFinalStateName='" + expectedFinalStateName + '\'' +
", connectedDevices=" + Arrays.toString(connectedDevices) +
", activeDevice='" + activeDevice + '\'' +
+ ", hearingAidBtDevices ='" + hearingAidBtDevices + '\'' +
'}';
}
}
@@ -220,6 +236,7 @@
private final BluetoothRouteTestParameters mParams;
@Mock private BluetoothDeviceManager mDeviceManager;
@Mock private BluetoothHeadsetProxy mHeadsetProxy;
+ @Mock private BluetoothHearingAid mBluetoothHearingAid;
@Mock private Timeouts.Adapter mTimeoutsAdapter;
@Mock private BluetoothRouteManager.BluetoothStateListener mListener;
@@ -241,21 +258,37 @@
setupConnectedDevices(mParams.connectedDevices,
mParams.audioOnDevice, mParams.activeDevice);
- sm.setActiveDeviceCacheForTesting(mParams.activeDevice);
+ sm.setActiveDeviceCacheForTesting(mParams.activeDevice,
+ mParams.hearingAidBtDevices.contains(mParams.messageDevice));
+ if (mParams.initialDevice != null) {
+ doAnswer(invocation -> {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = Log.createSubsession();
+ args.arg2 = mParams.initialDevice.getAddress();
+ sm.sendMessage(BluetoothRouteManager.BT_AUDIO_LOST, args);
+ when(mHeadsetProxy.getAudioState(eq(mParams.initialDevice)))
+ .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+ return true;
+ }).when(mDeviceManager).disconnectAudio();
+ }
// Go through the utility methods for these two messages
if (mParams.messageType == BluetoothRouteManager.NEW_DEVICE_CONNECTED) {
sm.onDeviceAdded(mParams.messageDevice.getAddress());
- sm.onActiveDeviceChanged(mParams.messageDevice);
+ sm.onActiveDeviceChanged(mParams.messageDevice,
+ mParams.hearingAidBtDevices.contains(mParams.messageDevice));
} else if (mParams.messageType == BluetoothRouteManager.LOST_DEVICE) {
sm.onDeviceLost(mParams.messageDevice.getAddress());
- sm.onActiveDeviceChanged(null);
+ sm.onActiveDeviceChanged(null,
+ mParams.hearingAidBtDevices.contains(mParams.messageDevice));
} else {
executeRoutingAction(sm, mParams.messageType,
mParams.messageDevice == null ? null : mParams.messageDevice.getAddress());
}
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
assertEquals(mParams.expectedFinalStateName, sm.getCurrentState().getName());
for (ListenerUpdate lu : mParams.expectedListenerUpdates) {
@@ -280,19 +313,15 @@
switch (mParams.expectedBluetoothInteraction) {
case NONE:
- verify(mHeadsetProxy, never()).connectAudio();
- verify(mHeadsetProxy, never()).setActiveDevice(nullable(BluetoothDevice.class));
- verify(mHeadsetProxy, never()).disconnectAudio();
+ verify(mDeviceManager, never()).connectAudio(nullable(String.class));
break;
case CONNECT:
- verify(mHeadsetProxy).connectAudio();
- verify(mHeadsetProxy).setActiveDevice(mParams.expectedConnectionDevice);
- verify(mHeadsetProxy, never()).disconnectAudio();
+ verify(mDeviceManager).connectAudio(mParams.expectedConnectionDevice.getAddress());
+ verify(mDeviceManager, never()).disconnectAudio();
break;
case DISCONNECT:
- verify(mHeadsetProxy, never()).connectAudio();
- verify(mHeadsetProxy, never()).setActiveDevice(nullable(BluetoothDevice.class));
- verify(mHeadsetProxy).disconnectAudio();
+ verify(mDeviceManager, never()).connectAudio(nullable(String.class));
+ verify(mDeviceManager).disconnectAudio();
break;
}
@@ -306,23 +335,24 @@
when(mDeviceManager.getConnectedDevices()).thenReturn(Arrays.asList(devices));
when(mHeadsetProxy.getConnectedDevices()).thenReturn(Arrays.asList(devices));
when(mHeadsetProxy.getActiveDevice()).thenReturn(activeDevice);
+ when(mHeadsetProxy.getAudioState(any(BluetoothDevice.class)))
+ .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
if (audioOnDevice != null) {
when(mHeadsetProxy.getAudioState(eq(audioOnDevice)))
.thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTED);
}
- doAnswer(invocation -> {
- BluetoothDevice first = getFirstExcluding(devices,
- (String) invocation.getArguments()[0]);
- return first == null ? null : first.getAddress();
- }).when(mDeviceManager).getMostRecentlyConnectedDevice(nullable(String.class));
- for (BluetoothDevice device : devices) {
- when(mDeviceManager.getDeviceFromAddress(device.getAddress())).thenReturn(device);
- }
}
private BluetoothRouteManager setupStateMachine(String initialState,
BluetoothDevice initialDevice) {
resetMocks();
+ when(mDeviceManager.getHeadsetService()).thenReturn(mHeadsetProxy);
+ when(mDeviceManager.getHearingAidService()).thenReturn(mBluetoothHearingAid);
+ when(mDeviceManager.connectAudio(nullable(String.class))).thenReturn(true);
+ when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
+ nullable(ContentResolver.class))).thenReturn(100000L);
+ when(mTimeoutsAdapter.getBluetoothPendingTimeoutMillis(
+ nullable(ContentResolver.class))).thenReturn(100000L);
BluetoothRouteManager sm = new BluetoothRouteManager(mContext,
new TelecomSystem.SyncRoot() { }, mDeviceManager, mTimeoutsAdapter);
sm.setListener(mListener);
@@ -333,24 +363,7 @@
}
private void resetMocks() {
- reset(mDeviceManager, mListener, mHeadsetProxy, mTimeoutsAdapter);
- when(mDeviceManager.getHeadsetService()).thenReturn(mHeadsetProxy);
- when(mHeadsetProxy.connectAudio()).thenReturn(true);
- when(mHeadsetProxy.setActiveDevice(nullable(BluetoothDevice.class))).thenReturn(true);
- when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
- nullable(ContentResolver.class))).thenReturn(100000L);
- when(mTimeoutsAdapter.getBluetoothPendingTimeoutMillis(
- nullable(ContentResolver.class))).thenReturn(100000L);
- }
-
- private static BluetoothDevice getFirstExcluding(
- BluetoothDevice[] devices, String excludeAddress) {
- for (BluetoothDevice x : devices) {
- if (!Objects.equals(excludeAddress, x.getAddress())) {
- return x;
- }
- }
- return null;
+ clearInvocations(mDeviceManager, mListener, mHeadsetProxy, mTimeoutsAdapter);
}
@Parameterized.Parameters(name = "{0}")
@@ -390,7 +403,7 @@
.setInitialDevice(DEVICE2)
.setAudioOnDevice(DEVICE2)
.setConnectedDevices(DEVICE2, DEVICE1)
- .setMessageType(BluetoothRouteManager.HFP_IS_ON)
+ .setMessageType(BluetoothRouteManager.BT_AUDIO_IS_ON)
.setMessageDevice(DEVICE2)
.setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
.setExpectedBluetoothInteraction(NONE)
@@ -404,7 +417,7 @@
.setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX)
.setInitialDevice(DEVICE2)
.setConnectedDevices(DEVICE2)
- .setMessageType(BluetoothRouteManager.HFP_LOST)
+ .setMessageType(BluetoothRouteManager.BT_AUDIO_LOST)
.setMessageDevice(DEVICE2)
.setExpectedListenerUpdates(ListenerUpdate.AUDIO_DISCONNECTED)
.setExpectedBluetoothInteraction(NONE)
@@ -418,7 +431,7 @@
.setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX)
.setInitialDevice(DEVICE2)
.setConnectedDevices(DEVICE2, DEVICE1, DEVICE3)
- .setMessageType(BluetoothRouteManager.HFP_LOST)
+ .setMessageType(BluetoothRouteManager.BT_AUDIO_LOST)
.setMessageDevice(DEVICE2)
.setExpectedListenerUpdates(ListenerUpdate.AUDIO_DISCONNECTED)
.setExpectedBluetoothInteraction(NONE)
@@ -503,7 +516,7 @@
.setInitialDevice(DEVICE2)
.setConnectedDevices(DEVICE2, DEVICE1)
.setAudioOnDevice(DEVICE1)
- .setMessageType(BluetoothRouteManager.HFP_IS_ON)
+ .setMessageType(BluetoothRouteManager.BT_AUDIO_IS_ON)
.setMessageDevice(DEVICE1)
.setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
.setExpectedBluetoothInteraction(NONE)
@@ -565,7 +578,7 @@
.setInitialBluetoothState(BluetoothRouteManager.AUDIO_OFF_STATE_NAME)
.setInitialDevice(null)
.setConnectedDevices(DEVICE2, DEVICE3)
- .setMessageType(BluetoothRouteManager.HFP_IS_ON)
+ .setMessageType(BluetoothRouteManager.BT_AUDIO_IS_ON)
.setMessageDevice(DEVICE3)
.setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
.setExpectedBluetoothInteraction(NONE)
@@ -573,6 +586,19 @@
+ ":" + DEVICE3)
.build());
+ result.add(new BluetoothRouteTestParametersBuilder()
+ .setName("Hearing aid device disconnects with headset present")
+ .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX)
+ .setInitialDevice(DEVICE2)
+ .setConnectedDevices(DEVICE2, DEVICE3)
+ .setHearingAidBtDevices(Collections.singletonList(DEVICE2))
+ .setMessageType(BluetoothRouteManager.LOST_DEVICE)
+ .setMessageDevice(DEVICE2)
+ .setExpectedListenerUpdates(ListenerUpdate.AUDIO_DISCONNECTED,
+ ListenerUpdate.DEVICE_LIST_CHANGED)
+ .setExpectedBluetoothInteraction(NONE)
+ .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_OFF_STATE_NAME)
+ .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 5e23dcc..01add22 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
@@ -77,7 +77,7 @@
InCallTonePlayer mockInCallTonePlayer = mock(InCallTonePlayer.class);
doAnswer((invocation2) -> {
mCallAudioManager.setIsTonePlaying(true);
- return null;
+ return true;
}).when(mockInCallTonePlayer).startTone();
return mockInCallTonePlayer;
}).when(mPlayerFactory).createPlayer(anyInt());
@@ -198,13 +198,16 @@
Call call = createIncomingCall();
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.onIncomingCallAnswered(call);
+ mCallAudioManager.onCallStateChanged(call, CallState.RINGING, CallState.ANSWERED);
verify(mCallAudioModeStateMachine).sendMessageWithArgs(
- eq(CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL), captor.capture());
+ eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+ verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+ eq(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS), captor.capture());
CallAudioModeStateMachine.MessageArgs correctArgs =
new CallAudioModeStateMachine.MessageArgs(
true, // hasActiveOrDialingCalls
@@ -217,7 +220,7 @@
assertMessageArgEquality(correctArgs, captor.getValue());
assertMessageArgEquality(correctArgs, captor.getValue());
when(call.getState()).thenReturn(CallState.ACTIVE);
- mCallAudioManager.onCallStateChanged(call, CallState.RINGING, CallState.ACTIVE);
+ mCallAudioManager.onCallStateChanged(call, CallState.ANSWERED, CallState.ACTIVE);
disconnectCall(call);
stopTone();
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
index f253d19..56f585f 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
@@ -22,6 +22,7 @@
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioModeStateMachine;
import com.android.server.telecom.CallAudioRouteStateMachine;
+import com.android.server.telecom.SystemStateHelper;
import org.junit.Before;
import org.junit.Test;
@@ -30,9 +31,11 @@
import org.mockito.Mock;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
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;
@@ -40,6 +43,7 @@
public class CallAudioModeStateMachineTest extends TelecomTestCase {
private static final int TEST_TIMEOUT = 1000;
+ @Mock private SystemStateHelper mSystemStateHelper;
@Mock private AudioManager mAudioManager;
@Mock private CallAudioManager mCallAudioManager;
@@ -52,7 +56,8 @@
@SmallTest
@Test
public void testNoFocusWhenRingerSilenced() throws Throwable {
- CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mAudioManager);
+ CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
+ mAudioManager);
sm.setCallAudioManager(mCallAudioManager);
sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -83,8 +88,47 @@
@SmallTest
@Test
+ public void testNoRingWhenDeviceIsAtEar() {
+ CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
+ mAudioManager);
+ 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
+ ));
+ 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
+ ));
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+ verify(mAudioManager, never()).requestAudioFocusForCall(anyInt(), anyInt());
+ verify(mAudioManager, never()).setMode(anyInt());
+ verify(mCallAudioManager, never()).startRinging();
+ verify(mCallAudioManager).startCallWaiting(nullable(String.class));
+ }
+
+ @SmallTest
+ @Test
public void testRegainFocusWhenHfpIsConnectedSilenced() throws Throwable {
- CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mAudioManager);
+ CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
+ mAudioManager);
sm.setCallAudioManager(mCallAudioManager);
sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -117,7 +161,7 @@
sm.sendMessage(CallAudioModeStateMachine.RINGER_MODE_CHANGE);
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
- verify(mCallAudioManager).startRinging();
+ verify(mCallAudioManager, times(2)).startRinging();
verify(mAudioManager).requestAudioFocusForCall(AudioManager.STREAM_RING,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
verify(mAudioManager).setMode(AudioManager.MODE_RINGTONE);
@@ -127,6 +171,6 @@
private void resetMocks() {
- reset(mCallAudioManager, mAudioManager);
+ 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 b8b4859..81339ed 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java
@@ -16,11 +16,13 @@
package com.android.server.telecom.tests;
+import android.content.Context;
import android.media.AudioManager;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioModeStateMachine;
+import com.android.server.telecom.SystemStateHelper;
import org.junit.Before;
import org.junit.Test;
@@ -35,6 +37,7 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
@@ -95,6 +98,7 @@
private static final int TEST_TIMEOUT = 1000;
+ @Mock private SystemStateHelper mSystemStateHelper;
@Mock private AudioManager mAudioManager;
@Mock private CallAudioManager mCallAudioManager;
private final ModeTestParameters mParams;
@@ -112,7 +116,8 @@
@Test
@SmallTest
public void modeTransitionTest() {
- CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mAudioManager);
+ CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
+ mAudioManager);
sm.setCallAudioManager(mCallAudioManager);
sm.sendMessage(mParams.initialAudioState);
waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -163,11 +168,11 @@
switch (mParams.expectedCallWaitingInteraction) {
case NO_CHANGE:
- verify(mCallAudioManager, never()).startCallWaiting();
+ verify(mCallAudioManager, never()).startCallWaiting(nullable(String.class));
verify(mCallAudioManager, never()).stopCallWaiting();
break;
case ON:
- verify(mCallAudioManager).startCallWaiting();
+ verify(mCallAudioManager).startCallWaiting(nullable(String.class));
break;
case OFF:
verify(mCallAudioManager).stopCallWaiting();
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index 9c90d3e..7f289b8 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -538,12 +538,35 @@
initializationTestHelper(expectedState, CallAudioRouteStateMachine.EARPIECE_FORCE_DISABLED);
}
+ @SmallTest
+ @Test
+ public void testInitializationWithAvailableButInactiveBtDevice() {
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+ CallAudioState.ROUTE_SPEAKER | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_EARPIECE);
+ when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
+ when(mockBluetoothRouteManager.hasBtActiveDevice()).thenReturn(false);
+
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothRouteManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory,
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+ stateMachine.initialize();
+ assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+ }
+
private void initializationTestHelper(CallAudioState expectedState,
int earpieceControl) {
when(mockWiredHeadsetManager.isPluggedIn()).thenReturn(
(expectedState.getSupportedRouteMask() & CallAudioState.ROUTE_WIRED_HEADSET) != 0);
when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(
(expectedState.getSupportedRouteMask() & CallAudioState.ROUTE_BLUETOOTH) != 0);
+ when(mockBluetoothRouteManager.hasBtActiveDevice()).thenReturn(
+ (expectedState.getSupportedRouteMask() & CallAudioState.ROUTE_BLUETOOTH) != 0);
CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
mContext,
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
index 19630b1..26d2419 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
@@ -16,6 +16,18 @@
package com.android.server.telecom.tests;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.media.AudioManager;
@@ -26,7 +38,6 @@
import com.android.server.telecom.Call;
import com.android.server.telecom.CallAudioManager;
-import com.android.server.telecom.CallAudioModeStateMachine;
import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ConnectionServiceWrapper;
@@ -41,6 +52,7 @@
import org.junit.runners.Parameterized;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
@@ -48,21 +60,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.CountDownLatch;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.ArgumentMatchers.same;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
@RunWith(Parameterized.class)
public class CallAudioRouteTransitionTests extends TelecomTestCase {
@@ -191,16 +188,16 @@
private void setupMocksForParams(final CallAudioRouteStateMachine sm,
RoutingTestParameters params) {
// Set up bluetooth and speakerphone state
- when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(
- params.initialRoute == CallAudioState.ROUTE_BLUETOOTH);
- when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(
- (params.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0
- || (params.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0);
- when(mockBluetoothRouteManager.getConnectedDevices())
- .thenReturn(params.availableBluetoothDevices);
+ doReturn(params.initialRoute == CallAudioState.ROUTE_BLUETOOTH)
+ .when(mockBluetoothRouteManager).isBluetoothAudioConnectedOrPending();
+ doReturn((params.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0
+ || (params.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0)
+ .when(mockBluetoothRouteManager).isBluetoothAvailable();
+ doReturn(params.availableBluetoothDevices)
+ .when(mockBluetoothRouteManager).getConnectedDevices();
if (params.initialBluetoothDevice != null) {
- when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice())
- .thenReturn(params.initialBluetoothDevice);
+ doReturn(params.initialBluetoothDevice)
+ .when(mockBluetoothRouteManager).getBluetoothAudioConnectedDevice();
}
@@ -264,9 +261,8 @@
}
waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
- // Reset mocks to discard stuff from initialization
- resetMocks();
- setupMocksForParams(stateMachine, mParams);
+ // Clear invocations on mocks to discard stuff from initialization
+ clearInvocations();
sendActionToStateMachine(stateMachine);
@@ -337,11 +333,11 @@
stateMachine.setCallAudioManager(mockCallAudioManager);
// Set up bluetooth and speakerphone state
- when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(
- (mParams.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0
- || (mParams.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0);
- when(mockBluetoothRouteManager.getConnectedDevices())
- .thenReturn(mParams.availableBluetoothDevices);
+ doReturn((mParams.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0 ||
+ (mParams.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0)
+ .when(mockBluetoothRouteManager).isBluetoothAvailable();
+ doReturn(mParams.availableBluetoothDevices)
+ .when(mockBluetoothRouteManager).getConnectedDevices();
when(mockAudioManager.isSpeakerphoneOn()).thenReturn(
mParams.initialRoute == CallAudioState.ROUTE_SPEAKER);
when(fakeCall.getSupportedAudioRoutes()).thenReturn(mParams.callSupportedRoutes);
@@ -777,16 +773,8 @@
any(Call.class), any(CallAudioState.class));
}
- private void resetMocks() {
- reset(mockAudioManager, mockBluetoothRouteManager, mockCallsManager,
+ private void clearInvocations() {
+ Mockito.clearInvocations(mockAudioManager, mockBluetoothRouteManager, mockCallsManager,
mockConnectionServiceWrapper);
- fakeCall = mock(Call.class);
- when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall);
- when(fakeCall.getConnectionService()).thenReturn(mockConnectionServiceWrapper);
- when(fakeCall.isAlive()).thenReturn(true);
- when(fakeCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_ALL);
- when(mockCallsManager.getLock()).thenReturn(mLock);
- doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class),
- any(CallAudioState.class));
}
-}
+}
\ No newline at end of file
diff --git a/tests/src/com/android/server/telecom/tests/CallExtrasTest.java b/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
index 44578c5..3bedb75 100644
--- a/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
@@ -22,6 +22,7 @@
import android.telecom.Connection;
import android.telecom.InCallService;
import android.telecom.ParcelableCall;
+import android.support.test.filters.FlakyTest;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
@@ -398,6 +399,7 @@
* @throws Exception
*/
@LargeTest
+ @FlakyTest(bugId = 117751305)
@Test
public void testConferenceExtraOperations() throws Exception {
ParcelableCall call = makeConferenceCall();
diff --git a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
index 690a38a..c83c3f3 100644
--- a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
@@ -538,6 +538,7 @@
}
@MediumTest
+ @FlakyTest(bugId = 117751305)
@Test
public void testLogCallDirectionIncomingWithMultiUserCapability() {
when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
diff --git a/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java b/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java
new file mode 100644
index 0000000..324375b
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java
@@ -0,0 +1,351 @@
+/*
+ * 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.CallerInfoLookupHelper;
+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.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 CarrierConfigManager mCarrierConfigManager;
+ @Mock private TelecomManager mTelecomManager;
+ @Mock PackageManager mPackageManager;
+ @Mock ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
+ @Mock PhoneAccountRegistrar mPhoneAccountRegistrar;
+ @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
+
+ 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 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(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() {
+ CallScreeningServiceController controller = new CallScreeningServiceController(mContext,
+ mCallsManager,
+ mPhoneAccountRegistrar, mParcelableCallUtilsConverter, mLock,
+ mSettingsSecureAdapter, mCallerInfoLookupHelper);
+
+ 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);
+
+ 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);
+
+ controller.startFilterLookup(mCall, mCallback);
+
+ controller.onCallScreeningFilterComplete(mCall, new CallFilteringResult(
+ false, // shouldAllowCall
+ true, // shouldReject
+ false, // shouldAddToCallLog
+ true, // shouldShowNotification
+ CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE,
+ CARRIER_DEFINED_CALL_SCREENING.getPackageName(),
+ 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
+ CARRIER_DEFINED_CALL_SCREENING.getPackageName(), //callScreeningAppName
+ CARRIER_DEFINED_CALL_SCREENING.flattenToString() //callScreeningComponentName
+ )));
+ }
+
+ @SmallTest
+ @Test
+ public void testDefaultDialerRejectCall() {
+ CallScreeningServiceController controller = new CallScreeningServiceController(mContext,
+ mCallsManager,
+ mPhoneAccountRegistrar, mParcelableCallUtilsConverter, mLock,
+ mSettingsSecureAdapter, mCallerInfoLookupHelper);
+
+ 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,
+ DEFAULT_DIALER_CALL_SCREENING.getPackageName(),
+ 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
+ false, // shouldAddToCallLog
+ true, // shouldShowNotification
+ CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
+ DEFAULT_DIALER_CALL_SCREENING.getPackageName(), //callScreeningAppName
+ DEFAULT_DIALER_CALL_SCREENING.flattenToString() //callScreeningComponentName
+ )));
+ }
+
+ @SmallTest
+ @Test
+ public void testUserChosenRejectCall() {
+ CallScreeningServiceController controller = new CallScreeningServiceController(mContext,
+ mCallsManager,
+ mPhoneAccountRegistrar, mParcelableCallUtilsConverter, mLock,
+ mSettingsSecureAdapter, mCallerInfoLookupHelper);
+
+ 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,
+ USER_CHOSEN_CALL_SCREENING.getPackageName(),
+ 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
+ false, // shouldAddToCallLog
+ true, // shouldShowNotification
+ CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
+ USER_CHOSEN_CALL_SCREENING.getPackageName(), //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 a319fad..fbdd0c0 100644
--- a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
@@ -25,20 +26,23 @@
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.Call;
import com.android.server.telecom.CallsManager;
-import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.ParcelableCallUtils;
import com.android.server.telecom.PhoneAccountRegistrar;
-import com.android.server.telecom.callfiltering.CallFilterResultCallback;
+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;
@@ -60,6 +64,7 @@
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;
@@ -68,21 +73,31 @@
@Mock Context mContext;
@Mock CallsManager mCallsManager;
@Mock PhoneAccountRegistrar mPhoneAccountRegistrar;
- @Mock DefaultDialerCache mDefaultDialerCache;
@Mock ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
@Mock Call mCall;
- @Mock CallFilterResultCallback mCallback;
+ @Mock CallScreeningServiceFilter.CallScreeningFilterResultCallback mCallback;
@Mock PackageManager mPackageManager;
@Mock IBinder mBinder;
@Mock ICallScreeningService mCallScreeningService;
+ @Mock CarrierConfigManager mCarrierConfigManager;
+ @Mock private TelecomManager mTelecomManager;
+ private TelecomServiceImpl.SettingsSecureAdapter mSettingsSecureAdapter =
+ spy(new SettingsSecureAdapterFake());
private static final String PKG_NAME = "com.android.services.telecom.tests";
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 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 ResolveInfo mResolveInfo;
@@ -95,6 +110,20 @@
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();
+ }
+ }
+
@Override
@Before
public void setUp() throws Exception {
@@ -112,10 +141,8 @@
}};
mFilter = new CallScreeningServiceFilter(mContext, mCallsManager, mPhoneAccountRegistrar,
- mDefaultDialerCache, mParcelableCallUtilsConverter, mLock);
+ mParcelableCallUtilsConverter, mLock, mSettingsSecureAdapter);
- when(mDefaultDialerCache.getDefaultDialerApplication(eq(UserHandle.USER_CURRENT)))
- .thenReturn(PKG_NAME);
when(mPackageManager.queryIntentServicesAsUser(nullable(Intent.class), anyInt(), anyInt()))
.thenReturn(Collections.singletonList(mResolveInfo));
when(mParcelableCallUtilsConverter.toParcelableCall(
@@ -126,11 +153,9 @@
@SmallTest
@Test
- public void testNoDefaultDialer() {
- when(mDefaultDialerCache.getDefaultDialerApplication(eq(UserHandle.USER_CURRENT)))
- .thenReturn(null);
- mFilter.startFilterLookup(mCall, mCallback);
- verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+ public void testNoPackageName() {
+ mFilter.startCallScreeningFilter(mCall, mCallback, null);
+ verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(null));
}
@SmallTest
@@ -138,24 +163,24 @@
public void testNoResolveEntries() {
when(mPackageManager.queryIntentServicesAsUser(nullable(Intent.class), anyInt(), anyInt()))
.thenReturn(Collections.emptyList());
- mFilter.startFilterLookup(mCall, mCallback);
- verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+ mFilter.startCallScreeningFilter(mCall, mCallback, PKG_NAME);
+ verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(PKG_NAME));
}
@SmallTest
@Test
public void testBadResolveEntry() {
mResolveInfo.serviceInfo = null;
- mFilter.startFilterLookup(mCall, mCallback);
- verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+ mFilter.startCallScreeningFilter(mCall, mCallback, PKG_NAME);
+ verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(PKG_NAME));
}
@SmallTest
@Test
public void testPermissionlessFilterService() {
mResolveInfo.serviceInfo.permission = null;
- mFilter.startFilterLookup(mCall, mCallback);
- verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+ mFilter.startCallScreeningFilter(mCall, mCallback, PKG_NAME);
+ verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(PKG_NAME));
}
@SmallTest
@@ -163,8 +188,8 @@
public void testContextFailToBind() {
when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
anyInt(), eq(UserHandle.CURRENT))).thenReturn(false);
- mFilter.startFilterLookup(mCall, mCallback);
- verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+ mFilter.startCallScreeningFilter(mCall, mCallback, PKG_NAME);
+ verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(PKG_NAME));
}
@SmallTest
@@ -172,41 +197,114 @@
public void testExceptionInScreeningService() throws Exception {
doThrow(new RemoteException()).when(mCallScreeningService).screenCall(
nullable(ICallScreeningAdapter.class), nullable(ParcelableCall.class));
- mFilter.startFilterLookup(mCall, mCallback);
+ mFilter.startCallScreeningFilter(mCall, mCallback, PKG_NAME);
ServiceConnection serviceConnection = verifyBindingIntent();
serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
- verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+ verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(PKG_NAME));
}
@SmallTest
@Test
public void testAllowCall() throws Exception {
- mFilter.startFilterLookup(mCall, mCallback);
+ mFilter.startCallScreeningFilter(mCall, mCallback, PKG_NAME);
ServiceConnection serviceConnection = verifyBindingIntent();
serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
csAdapter.allowCall(CALL_ID);
- verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+ verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(PKG_NAME));
}
@SmallTest
@Test
- public void testDisallowCall() throws Exception {
- mFilter.startFilterLookup(mCall, mCallback);
+ 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);
+
+ mFilter.startCallScreeningFilter(mCall, mCallback,
+ CARRIER_DEFINED_CALL_SCREENING.getPackageName());
ServiceConnection serviceConnection = verifyBindingIntent();
serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
csAdapter.disallowCall(CALL_ID,
true, // shouldReject
false, // shouldAddToCallLog
- true // shouldShowNotification
+ true, // shouldShowNotification
+ CARRIER_DEFINED_CALL_SCREENING
);
- verify(mCallback).onCallFilteringComplete(eq(mCall), eq(new CallFilteringResult(
+ verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(new CallFilteringResult(
false, // shouldAllowCall
true, // shouldReject
false, // shouldAddToCallLog
- true // shouldShowNotification
- )));
+ true, // shouldShowNotification
+ CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
+ CARRIER_DEFINED_CALL_SCREENING.getPackageName(), //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());
+ 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_CALL_SCREENING.getPackageName(), //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());
+ 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.getPackageName(), //callScreeningAppName
+ USER_CHOSEN_CALL_SCREENING.flattenToString() //callScreeningComponentName
+ )), eq(USER_CHOSEN_CALL_SCREENING.getPackageName()));
}
private ServiceConnection verifyBindingIntent() {
@@ -219,8 +317,9 @@
Intent capturedIntent = intentCaptor.getValue();
assertEquals(CallScreeningService.SERVICE_INTERFACE, capturedIntent.getAction());
- assertEquals(PKG_NAME, capturedIntent.getPackage());
- assertEquals(COMPONENT_NAME, capturedIntent.getComponent());
+ assertEquals(mResolveInfo.serviceInfo.packageName, capturedIntent.getPackage());
+ assertEquals(new ComponentName(mResolveInfo.serviceInfo.packageName, mResolveInfo
+ .serviceInfo.name), capturedIntent.getComponent());
return serviceCaptor.getValue();
}
@@ -231,4 +330,14 @@
verify(mCallScreeningService).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/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index ed2f6b1..fe4213f 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -17,12 +17,15 @@
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.anyBoolean;
import static org.mockito.ArgumentMatchers.anyChar;
import static org.mockito.ArgumentMatchers.anyInt;
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.never;
@@ -31,20 +34,24 @@
import android.content.ComponentName;
import android.net.Uri;
+import android.os.Bundle;
import android.os.SystemClock;
import android.telecom.Connection;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.MediumTest;
-
import android.test.suitebuilder.annotation.SmallTest;
import com.android.server.telecom.AsyncRingtonePlayer;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.CallAudioModeStateMachine;
+import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallerInfoAsyncQueryFactory;
+import com.android.server.telecom.CallerInfoLookupHelper;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ClockProxy;
import com.android.server.telecom.ConnectionServiceFocusManager;
@@ -65,7 +72,8 @@
import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.ProximitySensorManager;
import com.android.server.telecom.ProximitySensorManagerFactory;
-import com.android.server.telecom.SystemStateProvider;
+import com.android.server.telecom.RoleManagerAdapter;
+import com.android.server.telecom.SystemStateHelper;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.Timeouts;
import com.android.server.telecom.WiredHeadsetManager;
@@ -81,6 +89,8 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.Arrays;
@@ -114,8 +124,7 @@
private static final Uri TEST_ADDRESS = Uri.parse("tel:555-1212");
private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
- @Mock private ContactsAsyncHelper mContactsAsyncHelper;
- @Mock private CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
+ @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
@Mock private MissedCallNotifier mMissedCallNotifier;
@Mock private PhoneAccountRegistrar mPhoneAccountRegistrar;
@Mock private HeadsetMediaButton mHeadsetMediaButton;
@@ -128,7 +137,7 @@
@Mock private CallAudioManager.AudioServiceFactory mAudioServiceFactory;
@Mock private BluetoothRouteManager mBluetoothRouteManager;
@Mock private WiredHeadsetManager mWiredHeadsetManager;
- @Mock private SystemStateProvider mSystemStateProvider;
+ @Mock private SystemStateHelper mSystemStateHelper;
@Mock private DefaultDialerCache mDefaultDialerCache;
@Mock private Timeouts.Adapter mTimeoutsAdapter;
@Mock private AsyncRingtonePlayer mAsyncRingtonePlayer;
@@ -139,7 +148,12 @@
@Mock private InCallControllerFactory mInCallControllerFactory;
@Mock private InCallController mInCallController;
@Mock private ConnectionServiceFocusManager mConnectionSvrFocusMgr;
+ @Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+ @Mock private CallAudioRouteStateMachine.Factory mCallAudioRouteStateMachineFactory;
+ @Mock private CallAudioModeStateMachine mCallAudioModeStateMachine;
+ @Mock private CallAudioModeStateMachine.Factory mCallAudioModeStateMachineFactory;
@Mock private BluetoothStateReceiver mBluetoothStateReceiver;
+ @Mock private RoleManagerAdapter mRoleManagerAdapter;
private CallsManager mCallsManager;
@@ -156,14 +170,18 @@
mProximitySensorManager);
when(mInCallControllerFactory.create(any(), any(), any(), any(), any(), any(),
any())).thenReturn(mInCallController);
+ when(mCallAudioRouteStateMachineFactory.create(any(), any(), any(), any(), any(), any(),
+ anyInt())).thenReturn(mCallAudioRouteStateMachine);
+ when(mCallAudioModeStateMachineFactory.create(any(), any()))
+ .thenReturn(mCallAudioModeStateMachine);
when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
when(mClockProxy.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime());
- when(mConnSvrFocusManagerFactory.create(any(), any())).thenReturn(mConnectionSvrFocusMgr);
+ when(mConnSvrFocusManagerFactory.create(any())).thenReturn(mConnectionSvrFocusMgr);
+ doNothing().when(mRoleManagerAdapter).setCurrentUserHandle(any());
mCallsManager = new CallsManager(
mComponentContextFixture.getTestDouble().getApplicationContext(),
mLock,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
+ mCallerInfoLookupHelper,
mMissedCallNotifier,
mPhoneAccountRegistrar,
mHeadsetMediaButtonFactory,
@@ -173,7 +191,7 @@
mAudioServiceFactory,
mBluetoothRouteManager,
mWiredHeadsetManager,
- mSystemStateProvider,
+ mSystemStateHelper,
mDefaultDialerCache,
mTimeoutsAdapter,
mAsyncRingtonePlayer,
@@ -182,7 +200,10 @@
mToneGeneratorFactory,
mClockProxy,
mBluetoothStateReceiver,
- mInCallControllerFactory);
+ mCallAudioRouteStateMachineFactory,
+ mCallAudioModeStateMachineFactory,
+ mInCallControllerFactory,
+ mRoleManagerAdapter);
when(mPhoneAccountRegistrar.getPhoneAccount(
eq(SELF_MANAGED_HANDLE), any())).thenReturn(SELF_MANAGED_ACCOUNT);
@@ -215,8 +236,6 @@
mCallsManager,
mLock,
null /* ConnectionServiceRepository */,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
TEST_ADDRESS,
null /* GatewayInfo */,
@@ -249,6 +268,15 @@
assertEquals(2, phoneAccountHandles.size());
}
+ private void setupCallerInfoLookupHelper() {
+ doAnswer(invocation -> {
+ Uri handle = invocation.getArgument(0);
+ CallerInfoLookupHelper.OnQueryCompleteListener listener = invocation.getArgument(1);
+ listener.onCallerInfoQueryComplete(handle, null);
+ return null;
+ }).when(mCallerInfoLookupHelper).startLookup(any(Uri.class),
+ any(CallerInfoLookupHelper.OnQueryCompleteListener.class));
+ }
/**
* Tests finding the outgoing call phone account where the call is being placed on a
* self-managed ConnectionService.
@@ -257,8 +285,10 @@
@MediumTest
@Test
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 */, null /* userHandle */)
+ .get();
assertEquals(1, accounts.size());
assertEquals(SELF_MANAGED_HANDLE, accounts.get(0));
}
@@ -271,6 +301,7 @@
@MediumTest
@Test
public void testFindOutgoingCallAccountDefault() throws Exception {
+ setupCallerInfoLookupHelper();
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
SIM_1_HANDLE);
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
@@ -278,7 +309,8 @@
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 */, null /* userHandle */)
+ .get();
// Should have found just the default.
assertEquals(1, accounts.size());
@@ -293,6 +325,7 @@
@MediumTest
@Test
public void testFindOutgoingCallAccountNoDefault() throws Exception {
+ setupCallerInfoLookupHelper();
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
null);
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
@@ -300,7 +333,8 @@
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 */, null /* userHandle */)
+ .get();
assertEquals(2, accounts.size());
assertTrue(accounts.contains(SIM_1_HANDLE));
@@ -315,6 +349,7 @@
@MediumTest
@Test
public void testFindOutgoingCallAccountVideo() throws Exception {
+ setupCallerInfoLookupHelper();
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
null);
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
@@ -322,7 +357,8 @@
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 */, null /* userHandle */)
+ .get();
assertEquals(1, accounts.size());
assertTrue(accounts.contains(SIM_2_HANDLE));
@@ -336,6 +372,7 @@
@MediumTest
@Test
public void testFindOutgoingCallAccountVideoNotAvailable() throws Exception {
+ setupCallerInfoLookupHelper();
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
null);
// When querying for video capable accounts, return nothing.
@@ -347,7 +384,8 @@
any(), eq(0 /* none specified */))).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 */, null /* userHandle */)
+ .get();
// Should have found one.
assertEquals(1, accounts.size());
@@ -361,6 +399,7 @@
@MediumTest
@Test
public void testUseSpecifiedAccount() throws Exception {
+ setupCallerInfoLookupHelper();
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
null);
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
@@ -368,7 +407,7 @@
new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- SIM_2_HANDLE, TEST_ADDRESS, false /* isVideo */, null /* userHandle */);
+ SIM_2_HANDLE, TEST_ADDRESS, false /* isVideo */, null /* userHandle */).get();
assertEquals(1, accounts.size());
assertTrue(accounts.contains(SIM_2_HANDLE));
@@ -564,6 +603,9 @@
Call ongoingCall = addSpyCallWithConnectionService(connSvr1);
doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+ doReturn(CallState.ACTIVE).when(ongoingCall).getState();
+ when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
Call heldCall = addSpyCallWithConnectionService(connSvr1);
doReturn(CallState.ON_HOLD).when(heldCall).getState();
@@ -573,6 +615,7 @@
// WHEN answer an incoming call which ConnectionService is connSvr1
Call incomingCall = addSpyCallWithConnectionService(connSvr1);
+ doReturn(true).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
// THEN the previous held call is disconnected
@@ -609,6 +652,25 @@
@SmallTest
@Test
+ public void testAnswerAlreadyActiveCall() {
+ // GIVEN a CallsManager with no ongoing call.
+
+ // WHEN answer an already active call
+ Call incomingCall = addSpyCall();
+ mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
+
+ // THEN the focus request for incoming call is sent
+ verifyFocusRequestAndExecuteCallback(incomingCall);
+
+ // 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());
+ }
+
+ @SmallTest
+ @Test
public void testSetActiveCallWhenOngoingCallCanNotBeHeldAndFromDifferentConnectionService() {
ConnectionServiceWrapper connSvr1 = Mockito.mock(ConnectionServiceWrapper.class);
ConnectionServiceWrapper connSvr2 = Mockito.mock(ConnectionServiceWrapper.class);
@@ -684,6 +746,180 @@
assertEquals(CallState.ACTIVE, newCall.getState());
}
+ @SmallTest
+ @Test
+ public void testNoFilteringOfSelfManagedCalls() {
+ ConnectionServiceWrapper connSvr1 = Mockito.mock(ConnectionServiceWrapper.class);
+
+ // GIVEN an incoming call which is self managed.
+ Call incomingCall = addSpyCallWithConnectionService(connSvr1);
+ doReturn(false).when(incomingCall).can(Connection.CAPABILITY_HOLD);
+ doReturn(false).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+ doReturn(true).when(incomingCall).isSelfManaged();
+ doReturn(true).when(incomingCall).setState(anyInt(), any());
+
+ // WHEN the incoming call is successfully added.
+ mCallsManager.onSuccessfulIncomingCall(incomingCall);
+
+ // THEN the incoming call is not using call filtering
+ verify(incomingCall).setIsUsingCallFiltering(eq(false));
+ }
+
+ @SmallTest
+ @Test
+ public void testAcceptIncomingCallWhenHeadsetMediaButtonShortPress() {
+ // GIVEN an incoming call
+ Call incomingCall = addSpyCall();
+ doReturn(CallState.RINGING).when(incomingCall).getState();
+
+ // WHEN media button short press
+ mCallsManager.onMediaButton(HeadsetMediaButton.SHORT_PRESS);
+
+ // THEN the incoming call is answered
+ verify(incomingCall).answer(VideoProfile.STATE_AUDIO_ONLY);
+ }
+
+ @SmallTest
+ @Test
+ public void testRejectIncomingCallWhenHeadsetMediaButtonLongPress() {
+ // GIVEN an incoming call
+ Call incomingCall = addSpyCall();
+ doReturn(CallState.RINGING).when(incomingCall).getState();
+
+ // WHEN media button long press
+ mCallsManager.onMediaButton(HeadsetMediaButton.LONG_PRESS);
+
+ // THEN the incoming call is rejected
+ verify(incomingCall).reject(false, null);
+ }
+
+ @SmallTest
+ @Test
+ public void testHangupOngoingCallWhenHeadsetMediaButtonShortPress() {
+ // GIVEN an ongoing call
+ Call ongoingCall = addSpyCall();
+ doReturn(CallState.ACTIVE).when(ongoingCall).getState();
+
+ // WHEN media button short press
+ mCallsManager.onMediaButton(HeadsetMediaButton.SHORT_PRESS);
+
+ // THEN the active call is disconnected
+ verify(ongoingCall).disconnect();
+ }
+
+ @SmallTest
+ @Test
+ public void testToggleMuteWhenHeadsetMediaButtonLongPressDuringOngoingCall() {
+ // GIVEN an ongoing call
+ Call ongoingCall = addSpyCall();
+ doReturn(CallState.ACTIVE).when(ongoingCall).getState();
+
+ // WHEN media button long press
+ mCallsManager.onMediaButton(HeadsetMediaButton.LONG_PRESS);
+
+ // THEN the microphone toggle mute
+ verify(mCallAudioRouteStateMachine)
+ .sendMessageWithSessionInfo(CallAudioRouteStateMachine.TOGGLE_MUTE);
+ }
+
+ @SmallTest
+ @Test
+ public void testSwapCallsWhenHeadsetMediaButtonShortPressDuringTwoCalls() {
+ // GIVEN an ongoing call, and this call can be held
+ Call ongoingCall = addSpyCall();
+ doReturn(CallState.ACTIVE).when(ongoingCall).getState();
+ doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+ doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+ when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+ // and a held call
+ Call heldCall = addSpyCall();
+ doReturn(CallState.ON_HOLD).when(heldCall).getState();
+
+ // WHEN media button short press
+ mCallsManager.onMediaButton(HeadsetMediaButton.SHORT_PRESS);
+
+ // THEN the ongoing call is held, and the focus request for heldCall call is sent
+ verify(ongoingCall).hold(nullable(String.class));
+ verifyFocusRequestAndExecuteCallback(heldCall);
+
+ // and held call is unhold now
+ verify(heldCall).unhold(nullable(String.class));
+ }
+
+ @SmallTest
+ @Test
+ public void testHangupActiveCallWhenHeadsetMediaButtonLongPressDuringTwoCalls() {
+ // GIVEN an ongoing call
+ Call ongoingCall = addSpyCall();
+ doReturn(CallState.ACTIVE).when(ongoingCall).getState();
+
+ // and a held call
+ Call heldCall = addSpyCall();
+ doReturn(CallState.ON_HOLD).when(heldCall).getState();
+
+ // WHEN media button long press
+ mCallsManager.onMediaButton(HeadsetMediaButton.LONG_PRESS);
+
+ // THEN the ongoing call is disconnected
+ verify(ongoingCall).disconnect();
+ }
+
+ @SmallTest
+ @Test
+ public void testNoFilteringOfCallsWhenPhoneAccountRequestsSkipped() {
+ ConnectionServiceWrapper connSvr1 = Mockito.mock(ConnectionServiceWrapper.class);
+
+ // GIVEN an incoming call which is from a PhoneAccount that requested to skip filtering.
+ Call incomingCall = addSpyCallWithConnectionService(connSvr1);
+ Bundle extras = new Bundle();
+ extras.putBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING, true);
+ PhoneAccount skipRequestedAccount = new PhoneAccount.Builder(SIM_2_HANDLE, "Skipper")
+ .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
+ | PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .setExtras(extras)
+ .setIsEnabled(true)
+ .build();
+ when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SIM_2_HANDLE))
+ .thenReturn(skipRequestedAccount);
+ doReturn(false).when(incomingCall).can(Connection.CAPABILITY_HOLD);
+ doReturn(false).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+ doReturn(false).when(incomingCall).isSelfManaged();
+ doReturn(true).when(incomingCall).setState(anyInt(), any());
+
+ // WHEN the incoming call is successfully added.
+ mCallsManager.onSuccessfulIncomingCall(incomingCall);
+
+ // THEN the incoming call is not using call filtering
+ verify(incomingCall).setIsUsingCallFiltering(eq(false));
+ }
+
+ @SmallTest
+ @Test
+ public void testIsInEmergencyCallNetwork() {
+ // Setup a call which the network identified as an emergency call.
+ Call ongoingCall = addSpyCall();
+ ongoingCall.setConnectionProperties(Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL);
+
+ assertFalse(ongoingCall.isEmergencyCall());
+ assertTrue(ongoingCall.isNetworkIdentifiedEmergencyCall());
+ assertTrue(mCallsManager.isInEmergencyCall());
+ }
+
+ @SmallTest
+ @Test
+ 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);
+ ongoingCall.setHandle(Uri.fromParts("tel", "5551212", null),
+ TelecomManager.PRESENTATION_ALLOWED);
+
+ assertTrue(ongoingCall.isEmergencyCall());
+ assertFalse(ongoingCall.isNetworkIdentifiedEmergencyCall());
+ assertTrue(mCallsManager.isInEmergencyCall());
+ }
+
private Call addSpyCallWithConnectionService(ConnectionServiceWrapper connSvr) {
Call call = addSpyCall();
doReturn(connSvr).when(call).getConnectionService();
@@ -696,8 +932,6 @@
mCallsManager,
mLock, /* ConnectionServiceRepository */
null,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
TEST_ADDRESS,
null /* GatewayInfo */,
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 01d312b..3be9594 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -190,6 +190,8 @@
public String getSystemServiceName(Class<?> svcClass) {
if (svcClass == UserManager.class) {
return Context.USER_SERVICE;
+ } else if (svcClass == AudioManager.class) {
+ return Context.AUDIO_SERVICE;
}
throw new UnsupportedOperationException();
}
@@ -523,6 +525,10 @@
});
}
+ public void putFloatResource(int id, final float value) {
+ when(mResources.getFloat(eq(id))).thenReturn(value);
+ }
+
public void putBooleanResource(int id, boolean value) {
when(mResources.getBoolean(eq(id))).thenReturn(value);
}
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index 3154b7d..6c4e2e0 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -286,7 +286,9 @@
@Override
public void rejectWithMessage(String callId, String message,
- Session.Info info) throws RemoteException { }
+ Session.Info info) throws RemoteException {
+ rejectedCallIds.add(callId);
+ }
@Override
public void disconnect(String callId, Session.Info info) throws RemoteException { }
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java
index 3c2cc61..77a9c0d 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java
@@ -16,7 +16,6 @@
package com.android.server.telecom.tests;
-import android.os.Looper;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallState;
@@ -63,8 +62,7 @@
@Before
public void setUp() throws Exception {
super.setUp();
- mFocusManagerUT = new ConnectionServiceFocusManager(
- mockCallsManagerRequester, Looper.getMainLooper());
+ mFocusManagerUT = new ConnectionServiceFocusManager(mockCallsManagerRequester);
mNewCall = createFakeCall(mNewConnectionService, CallState.NEW);
mActiveCall = createFakeCall(mActiveConnectionService, CallState.ACTIVE);
ArgumentCaptor<CallsManager.CallsManagerListener> captor =
diff --git a/tests/src/com/android/server/telecom/tests/DirectToVoicemailCallFilterTest.java b/tests/src/com/android/server/telecom/tests/DirectToVoicemailCallFilterTest.java
index af4c168..a685c5e 100644
--- a/tests/src/com/android/server/telecom/tests/DirectToVoicemailCallFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/DirectToVoicemailCallFilterTest.java
@@ -17,6 +17,7 @@
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;
@@ -65,7 +66,10 @@
false, // shouldAllowCall
true, // shouldReject
true, // shouldAddToCallLog
- true // shouldShowNotification
+ true, // shouldShowNotification
+ CallLog.Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL, //callBlockReason
+ null, //callScreeningAppName
+ null // callScreeningComponentName
));
}
diff --git a/tests/src/com/android/server/telecom/tests/EventManagerTest.java b/tests/src/com/android/server/telecom/tests/EventManagerTest.java
index 24394ec..8bf8d1d 100644
--- a/tests/src/com/android/server/telecom/tests/EventManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/EventManagerTest.java
@@ -16,6 +16,9 @@
package com.android.server.telecom.tests;
+import android.net.Uri;
+import android.os.Build;
+import android.telecom.Log;
import android.telecom.Logging.EventManager;
import android.test.suitebuilder.annotation.SmallTest;
@@ -217,4 +220,23 @@
assertEquals(0, timings1.size());
assertEquals(0, timings2.size());
}
+
+ /**
+ * Ensure PII logging will log the last 2 digits of a phone number.
+ */
+ @SmallTest
+ @Test
+ public void testLogLast2DigitsPhone() {
+ if (Build.IS_USER) {
+ return;
+ }
+ assertEquals("tel:**********12",
+ Log.piiHandle(Uri.fromParts("tel", "+16505551212", null)));
+ assertEquals("tel:*****12",
+ Log.piiHandle(Uri.fromParts("tel", "5551212", null)));
+ assertEquals("tel:*11",
+ Log.piiHandle(Uri.fromParts("tel", "411", null)));
+ assertEquals("tel:1",
+ Log.piiHandle(Uri.fromParts("tel", "1", null)));
+ }
}
\ No newline at end of file
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 0671a4e..d76bb6c 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -50,7 +50,7 @@
import com.android.server.telecom.InCallController;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.R;
-import com.android.server.telecom.SystemStateProvider;
+import com.android.server.telecom.SystemStateHelper;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.Timeouts;
@@ -88,7 +88,7 @@
@Mock CallsManager mMockCallsManager;
@Mock PhoneAccountRegistrar mMockPhoneAccountRegistrar;
@Mock BluetoothHeadsetProxy mMockBluetoothHeadset;
- @Mock SystemStateProvider mMockSystemStateProvider;
+ @Mock SystemStateHelper mMockSystemStateHelper;
@Mock PackageManager mMockPackageManager;
@Mock Call mMockCall;
@Mock Resources mMockResources;
@@ -122,7 +122,7 @@
mEmergencyCallHelper = new EmergencyCallHelper(mMockContext, SYS_PKG,
mTimeoutsAdapter);
mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager,
- mMockSystemStateProvider, mDefaultDialerCache, mTimeoutsAdapter,
+ mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter,
mEmergencyCallHelper);
}
diff --git a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
index 69fcdd8..d114cb8 100644
--- a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
@@ -76,7 +76,11 @@
@Override
public void updateCall(ParcelableCall call) throws RemoteException {
if (!mCallById.containsKey(call.getId())) {
- throw new RuntimeException("Call " + call.getId() + " not added yet");
+ // This used to throw an exception, however the actual InCallService implementation
+ // ignores updates for calls which don't yet exist. This is not a problem as when
+ // a call is added to an InCallService its entire state is parceled and sent to the
+ // InCallService.
+ return;
}
mLatestCallId = call.getId();
mCallById.put(call.getId(), call);
diff --git a/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
new file mode 100644
index 0000000..eba494f
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.CallAudioRoutePeripheralAdapter;
+import com.android.server.telecom.InCallTonePlayer;
+import com.android.server.telecom.TelecomSystem;
+
+import android.media.MediaPlayer;
+import android.media.ToneGenerator;
+import android.test.suitebuilder.annotation.SmallTest;
+
+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 InCallTonePlayerTest extends TelecomTestCase {
+
+ private InCallTonePlayer.Factory mFactory;
+
+ @Mock
+ private CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
+
+ @Mock
+ private TelecomSystem.SyncRoot mLock;
+
+ @Mock
+ private ToneGenerator mToneGenerator;
+
+ @Mock
+ private InCallTonePlayer.ToneGeneratorFactory mToneGeneratorFactory;
+
+ private InCallTonePlayer.MediaPlayerAdapter mMediaPlayerAdapter =
+ new InCallTonePlayer.MediaPlayerAdapter() {
+ private MediaPlayer.OnCompletionListener mListener;
+
+ @Override
+ public void setLooping(boolean isLooping) {
+ // Do nothing.
+ }
+
+ @Override
+ public void setOnCompletionListener(MediaPlayer.OnCompletionListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void start() {
+ mListener.onCompletion(null);
+ }
+
+ @Override
+ public void release() {
+ // Do nothing.
+ }
+
+ @Override
+ public int getDuration() {
+ return 0;
+ }
+ };
+
+ @Mock
+ private InCallTonePlayer.MediaPlayerFactory mMediaPlayerFactory;
+
+ @Mock
+ private InCallTonePlayer.AudioManagerAdapter mAudioManagerAdapter;
+
+ @Mock
+ private CallAudioManager mCallAudioManager;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ when(mToneGeneratorFactory.get(anyInt(), anyInt())).thenReturn(mToneGenerator);
+ when(mMediaPlayerFactory.get(anyInt(), any())).thenReturn(mMediaPlayerAdapter);
+
+ mFactory = new InCallTonePlayer.Factory(mCallAudioRoutePeripheralAdapter, mLock,
+ mToneGeneratorFactory, mMediaPlayerFactory, mAudioManagerAdapter);
+ mFactory.setCallAudioManager(mCallAudioManager);
+ }
+
+ @SmallTest
+ @Test
+ public void testNoEndCallToneInSilence() {
+ when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(false);
+ InCallTonePlayer player = mFactory.createPlayer(InCallTonePlayer.TONE_CALL_ENDED);
+ assertFalse(player.startTone());
+
+ // Verify we didn't play a tone.
+ verify(mCallAudioManager, never()).setIsTonePlaying(eq(true));
+ verify(mMediaPlayerFactory, never()).get(anyInt(), any());
+ }
+
+ @SmallTest
+ @Test
+ public void testEndCallToneWhenNotSilenced() {
+ when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
+ InCallTonePlayer player = mFactory.createPlayer(InCallTonePlayer.TONE_CALL_ENDED);
+ assertTrue(player.startTone());
+
+ // Verify we did play a tone.
+ verify(mCallAudioManager).setIsTonePlaying(eq(true));
+ verify(mMediaPlayerFactory, timeout(5000)).get(anyInt(), any());
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java b/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
index d975ec2..e399088 100644
--- a/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
@@ -19,6 +19,7 @@
import android.content.ContentResolver;
import android.content.IContentProvider;
import android.net.Uri;
+import android.provider.CallLog;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.server.telecom.Call;
@@ -55,6 +56,7 @@
@Mock private IncomingCallFilter.CallFilter mFilter1;
@Mock private IncomingCallFilter.CallFilter mFilter2;
@Mock private IncomingCallFilter.CallFilter mFilter3;
+ @Mock private IncomingCallFilter.CallFilter mFilter4;
@Mock private Timeouts.Adapter mTimeoutsAdapter;
@@ -62,7 +64,7 @@
private static final long LONG_TIMEOUT = 1000000;
private static final long SHORT_TIMEOUT = 100;
- private static final CallFilteringResult RESULT1 =
+ private static final CallFilteringResult PASS_CALL_RESULT =
new CallFilteringResult(
true, // shouldAllowCall
false, // shouldReject
@@ -70,23 +72,41 @@
true // shouldShowNotification
);
- private static final CallFilteringResult RESULT2 =
- new CallFilteringResult(
- false, // shouldAllowCall
- true, // shouldReject
- false, // shouldAddToCallLog
- true // shouldShowNotification
- );
-
- private static final CallFilteringResult RESULT3 =
+ private static final CallFilteringResult ASYNC_BLOCK_CHECK_BLOCK_RESULT =
new CallFilteringResult(
false, // shouldAllowCall
true, // shouldReject
true, // shouldAddToCallLog
- false // shouldShowNotification
+ false, // shouldShowNotification
+ CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER, //callBlockReason
+ null, //callScreeningAppName
+ null //callScreeningComponentName
);
- private static final CallFilteringResult DEFAULT_RESULT = RESULT1;
+ 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
+ );
+
+ 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
+ );
+
+ private static final CallFilteringResult DEFAULT_RESULT = PASS_CALL_RESULT;
@Override
@Before
@@ -99,20 +119,97 @@
@SmallTest
@Test
- public void testSingleFilter() {
+ public void testAsyncBlockCallResultFilter() {
IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1));
testFilter.performFiltering();
verify(mFilter1).startFilterLookup(mCall, testFilter);
- testFilter.onCallFilteringComplete(mCall, RESULT1);
+ testFilter.onCallFilteringComplete(mCall, ASYNC_BLOCK_CHECK_BLOCK_RESULT);
waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
- verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq(RESULT1));
+ verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq
+ (ASYNC_BLOCK_CHECK_BLOCK_RESULT));
}
@SmallTest
@Test
- public void testMultipleFilters() {
+ public void testDirectToVoiceMailCallResultFilter() {
+ IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
+ mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1));
+ testFilter.performFiltering();
+ verify(mFilter1).startFilterLookup(mCall, testFilter);
+
+ testFilter.onCallFilteringComplete(mCall, DIRECT_TO_VOICEMAIL_CALL_BLOCK_RESULT);
+ waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
+ verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq
+ (DIRECT_TO_VOICEMAIL_CALL_BLOCK_RESULT));
+ }
+
+ @SmallTest
+ @Test
+ public void testCallScreeningServiceBlockCallResultFilter() {
+ IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
+ mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1));
+ testFilter.performFiltering();
+ verify(mFilter1).startFilterLookup(mCall, testFilter);
+
+ testFilter.onCallFilteringComplete(mCall, CALL_SCREENING_SERVICE_BLOCK_RESULT);
+ waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
+ verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq
+ (CALL_SCREENING_SERVICE_BLOCK_RESULT));
+ }
+
+ @SmallTest
+ @Test
+ public void testPassCallResultFilter() {
+ IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
+ mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1));
+ testFilter.performFiltering();
+ verify(mFilter1).startFilterLookup(mCall, testFilter);
+
+ testFilter.onCallFilteringComplete(mCall, PASS_CALL_RESULT);
+ waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
+ verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq(PASS_CALL_RESULT));
+ }
+
+ @SmallTest
+ @Test
+ public void testMultipleFiltersForAsyncBlockCheckFilter() {
+ List<IncomingCallFilter.CallFilter> filters =
+ new ArrayList<IncomingCallFilter.CallFilter>() {{
+ add(mFilter1);
+ add(mFilter2);
+ add(mFilter3);
+ add(mFilter4);
+ }};
+ IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
+ mLock, mTimeoutsAdapter, filters);
+ testFilter.performFiltering();
+ verify(mFilter1).startFilterLookup(mCall, testFilter);
+ verify(mFilter2).startFilterLookup(mCall, testFilter);
+ verify(mFilter3).startFilterLookup(mCall, testFilter);
+ verify(mFilter4).startFilterLookup(mCall, testFilter);
+
+ testFilter.onCallFilteringComplete(mCall, PASS_CALL_RESULT);
+ testFilter.onCallFilteringComplete(mCall, ASYNC_BLOCK_CHECK_BLOCK_RESULT);
+ 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
+ )));
+ }
+
+ @SmallTest
+ @Test
+ public void testMultipleFiltersForDirectToVoicemailCallFilter() {
List<IncomingCallFilter.CallFilter> filters =
new ArrayList<IncomingCallFilter.CallFilter>() {{
add(mFilter1);
@@ -126,16 +223,49 @@
verify(mFilter2).startFilterLookup(mCall, testFilter);
verify(mFilter3).startFilterLookup(mCall, testFilter);
- testFilter.onCallFilteringComplete(mCall, RESULT1);
- testFilter.onCallFilteringComplete(mCall, RESULT2);
- testFilter.onCallFilteringComplete(mCall, RESULT3);
+ testFilter.onCallFilteringComplete(mCall, PASS_CALL_RESULT);
+ 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
+ true, // shouldShowNotification
+ CallLog.Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL, //callBlockReason
+ null, ////callScreeningAppName
+ null ////callScreeningComponentName
+ )));
+ }
+
+ @SmallTest
+ @Test
+ public void testMultipleFiltersForCallScreeningServiceFilter() {
+ List<IncomingCallFilter.CallFilter> filters =
+ new ArrayList<IncomingCallFilter.CallFilter>() {{
+ add(mFilter1);
+ add(mFilter2);
+ }};
+ IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
+ mLock, mTimeoutsAdapter, filters);
+ testFilter.performFiltering();
+ verify(mFilter1).startFilterLookup(mCall, testFilter);
+ verify(mFilter2).startFilterLookup(mCall, testFilter);
+
+ 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
)));
}
@@ -148,7 +278,7 @@
testFilter.performFiltering();
verify(mResultCallback, timeout((int) SHORT_TIMEOUT * 2)).onCallFilteringComplete(eq(mCall),
eq(DEFAULT_RESULT));
- testFilter.onCallFilteringComplete(mCall, RESULT1);
+ testFilter.onCallFilteringComplete(mCall, PASS_CALL_RESULT);
waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
// verify that we don't report back again with the result
verify(mResultCallback, atMost(1)).onCallFilteringComplete(any(Call.class),
@@ -162,7 +292,7 @@
IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1));
testFilter.performFiltering();
- testFilter.onCallFilteringComplete(mCall, RESULT1);
+ testFilter.onCallFilteringComplete(mCall, PASS_CALL_RESULT);
waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
Thread.sleep(SHORT_TIMEOUT);
verify(mResultCallback, atMost(1)).onCallFilteringComplete(any(Call.class),
@@ -172,9 +302,13 @@
@SmallTest
@Test
public void testToString() {
- assertEquals("[Allow, logged, notified]", RESULT1.toString());
- assertEquals("[Reject, notified]", RESULT2.toString());
- assertEquals("[Reject, logged]", RESULT3.toString());
+ assertEquals("[Allow, logged, notified]", PASS_CALL_RESULT.toString());
+ assertEquals("[Reject, notified, mCallBlockReason = 1, mCallScreeningAppName = com" +
+ ".android.thirdparty, mCallScreeningComponentName = com.android.thirdparty/com" +
+ ".android.thirdparty.callscreeningserviceimpl]",
+ CALL_SCREENING_SERVICE_BLOCK_RESULT.toString());
+ assertEquals("[Reject, logged, mCallBlockReason = 3]",
+ ASYNC_BLOCK_CHECK_BLOCK_RESULT.toString());
}
private void setTimeoutLength(long length) 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 25460de..02ee137 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -18,12 +18,12 @@
import android.app.NotificationManager;
import android.content.Context;
-import android.content.res.Resources;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.Ringtone;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Parcel;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.telecom.TelecomManager;
@@ -42,20 +42,56 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import java.util.Objects;
+
@RunWith(JUnit4.class)
public class RingerTest extends TelecomTestCase {
private static final Uri FAKE_RINGTONE_URI = Uri.parse("content://media/fake/audio/1729");
+ private static class UriVibrationEffect extends VibrationEffect {
+ final Uri mUri;
+
+ private UriVibrationEffect(Uri uri) {
+ mUri = uri;
+ }
+
+ @Override
+ public void validate() {
+ // not needed
+ }
+
+ @Override
+ public long getDuration() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // not needed
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ UriVibrationEffect that = (UriVibrationEffect) o;
+ return Objects.equals(mUri, that.mUri);
+ }
+ }
@Mock InCallTonePlayer.Factory mockPlayerFactory;
@Mock SystemSettingsUtil mockSystemSettingsUtil;
@@ -63,6 +99,7 @@
@Mock RingtoneFactory mockRingtoneFactory;
@Mock Vibrator mockVibrator;
@Mock InCallController mockInCallController;
+ @Spy Ringer.VibrationEffectProxy spyVibrationEffectProxy;
@Mock InCallTonePlayer mockTonePlayer;
@Mock Call mockCall1;
@@ -76,15 +113,21 @@
public void setUp() throws Exception {
super.setUp();
mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
- mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
- mockRingtonePlayer, mockRingtoneFactory, mockVibrator, mockInCallController);
+ doAnswer(invocation -> {
+ Uri ringtoneUriForEffect = invocation.getArgument(0);
+ return new UriVibrationEffect(ringtoneUriForEffect);
+ }).when(spyVibrationEffectProxy).get(any(), any());
when(mockPlayerFactory.createPlayer(anyInt())).thenReturn(mockTonePlayer);
mockAudioManager =
(AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
when(mockAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
NotificationManager notificationManager =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ when(mockTonePlayer.startTone()).thenReturn(true);
when(notificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
+ mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
+ mockRingtonePlayer, mockRingtoneFactory, mockVibrator, spyVibrationEffectProxy,
+ mockInCallController);
}
@SmallTest
@@ -207,9 +250,6 @@
@SmallTest
@Test
public void testCustomVibrationForRingtone() {
- Resources resources = mContext.getResources();
- when(resources.getStringArray(com.android.internal.R.array.config_ringtoneEffectUris))
- .thenReturn(new String[] { FAKE_RINGTONE_URI.toString() });
mRingerUnderTest.startCallWaiting(mockCall1);
Ringtone mockRingtone = mock(Ringtone.class);
when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
@@ -218,7 +258,7 @@
assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
verify(mockTonePlayer).stopTone();
verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class));
- verify(mockVibrator).vibrate(eq(VibrationEffect.get(FAKE_RINGTONE_URI, mContext)),
+ verify(mockVibrator).vibrate(eq(spyVibrationEffectProxy.get(FAKE_RINGTONE_URI, mContext)),
any(AudioAttributes.class));
}
diff --git a/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java b/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java
new file mode 100644
index 0000000..efe8796
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2015 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.anyInt;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.UiModeManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.SystemStateHelper;
+import com.android.server.telecom.SystemStateHelper.SystemStateListener;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.internal.util.reflection.FieldSetter;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/**
+ * Unit tests for SystemStateHelper
+ */
+@RunWith(JUnit4.class)
+public class SystemStateHelperTest extends TelecomTestCase {
+
+ Context mContext;
+ @Mock SystemStateListener mSystemStateListener;
+ @Mock Sensor mGravitySensor;
+ @Mock Sensor mProxSensor;
+ @Mock UiModeManager mUiModeManager;
+ @Mock SensorManager mSensorManager;
+ @Mock Intent mIntentEnter;
+ @Mock Intent mIntentExit;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+ doReturn(mSensorManager).when(mContext).getSystemService(SensorManager.class);
+ when(mGravitySensor.getType()).thenReturn(Sensor.TYPE_GRAVITY);
+ when(mProxSensor.getType()).thenReturn(Sensor.TYPE_PROXIMITY);
+ when(mProxSensor.getMaximumRange()).thenReturn(5.0f);
+ when(mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)).thenReturn(mGravitySensor);
+ when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(mProxSensor);
+
+ mComponentContextFixture.putFloatResource(
+ R.dimen.device_on_ear_xy_gravity_threshold, 5.5f);
+ mComponentContextFixture.putFloatResource(
+ R.dimen.device_on_ear_y_gravity_negative_threshold, -1f);
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @SmallTest
+ @Test
+ public void testListeners() throws Exception {
+ SystemStateHelper systemStateHelper = new SystemStateHelper(mContext);
+
+ assertFalse(systemStateHelper.removeListener(mSystemStateListener));
+ systemStateHelper.addListener(mSystemStateListener);
+ assertTrue(systemStateHelper.removeListener(mSystemStateListener));
+ assertFalse(systemStateHelper.removeListener(mSystemStateListener));
+ }
+
+ @SmallTest
+ @Test
+ public void testQuerySystemForCarMode_True() {
+ when(mContext.getSystemService(Context.UI_MODE_SERVICE)).thenReturn(mUiModeManager);
+ when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ assertTrue(new SystemStateHelper(mContext).isCarMode());
+ }
+
+ @SmallTest
+ @Test
+ public void testQuerySystemForCarMode_False() {
+ when(mContext.getSystemService(Context.UI_MODE_SERVICE)).thenReturn(mUiModeManager);
+ when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_NORMAL);
+ assertFalse(new SystemStateHelper(mContext).isCarMode());
+ }
+
+ @SmallTest
+ @Test
+ public void testReceiverAndIntentFilter() {
+ ArgumentCaptor<IntentFilter> intentFilter = ArgumentCaptor.forClass(IntentFilter.class);
+ new SystemStateHelper(mContext);
+ 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));
+ }
+
+ @SmallTest
+ @Test
+ public void testOnEnterExitCarMode() {
+ ArgumentCaptor<BroadcastReceiver> receiver =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ new SystemStateHelper(mContext).addListener(mSystemStateListener);
+
+ verify(mContext).registerReceiver(receiver.capture(), any(IntentFilter.class));
+
+ when(mIntentEnter.getAction()).thenReturn(UiModeManager.ACTION_ENTER_CAR_MODE);
+ receiver.getValue().onReceive(mContext, mIntentEnter);
+ verify(mSystemStateListener).onCarModeChanged(true);
+
+ when(mIntentExit.getAction()).thenReturn(UiModeManager.ACTION_EXIT_CAR_MODE);
+ receiver.getValue().onReceive(mContext, mIntentExit);
+ verify(mSystemStateListener).onCarModeChanged(false);
+
+ receiver.getValue().onReceive(mContext, new Intent("invalid action"));
+ }
+
+ @SmallTest
+ @Test
+ public void testDeviceOnEarCorrectlyDetected() {
+ doAnswer(invocation -> {
+ SensorEventListener listener = invocation.getArgument(0);
+ Sensor sensor = invocation.getArgument(1);
+ if (sensor.getType() == Sensor.TYPE_GRAVITY) {
+ listener.onSensorChanged(makeSensorEvent(
+ new float[]{1.0f, 9.0f, 1.0f}, Sensor.TYPE_GRAVITY));
+ } else {
+ listener.onSensorChanged(makeSensorEvent(
+ new float[]{0.0f}, Sensor.TYPE_PROXIMITY));
+ }
+ return true;
+ }).when(mSensorManager)
+ .registerListener(any(SensorEventListener.class), any(Sensor.class), anyInt());
+
+ assertTrue(SystemStateHelper.isDeviceAtEar(mContext));
+ verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
+ }
+
+ @SmallTest
+ @Test
+ public void testDeviceIsNotOnEarWithProxNotSensed() {
+ doAnswer(invocation -> {
+ SensorEventListener listener = invocation.getArgument(0);
+ Sensor sensor = invocation.getArgument(1);
+ if (sensor.getType() == Sensor.TYPE_GRAVITY) {
+ listener.onSensorChanged(makeSensorEvent(
+ new float[]{1.0f, 9.0f, 1.0f}, Sensor.TYPE_GRAVITY));
+ } else {
+ // do nothing to simulate proximity sensor not reporting
+ }
+ return true;
+ }).when(mSensorManager)
+ .registerListener(any(SensorEventListener.class), any(Sensor.class), anyInt());
+
+ assertFalse(SystemStateHelper.isDeviceAtEar(mContext));
+ verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
+ }
+
+ @SmallTest
+ @Test
+ public void testDeviceIsNotOnEarWithWrongOrientation() {
+ doAnswer(invocation -> {
+ SensorEventListener listener = invocation.getArgument(0);
+ Sensor sensor = invocation.getArgument(1);
+ if (sensor.getType() == Sensor.TYPE_GRAVITY) {
+ listener.onSensorChanged(makeSensorEvent(
+ new float[]{1.0f, 1.0f, 9.0f}, Sensor.TYPE_GRAVITY));
+ } else {
+ listener.onSensorChanged(makeSensorEvent(
+ new float[]{0.0f}, Sensor.TYPE_PROXIMITY));
+ }
+ return true;
+ }).when(mSensorManager)
+ .registerListener(any(SensorEventListener.class), any(Sensor.class), anyInt());
+
+ assertFalse(SystemStateHelper.isDeviceAtEar(mContext));
+ verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
+ }
+
+ @SmallTest
+ @Test
+ public void testDeviceIsNotOnEarWithMissingSensor() {
+ when(mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)).thenReturn(null);
+ doAnswer(invocation -> {
+ SensorEventListener listener = invocation.getArgument(0);
+ Sensor sensor = invocation.getArgument(1);
+ if (sensor.getType() == Sensor.TYPE_GRAVITY) {
+ listener.onSensorChanged(makeSensorEvent(
+ new float[]{1.0f, 9.0f, 1.0f}, Sensor.TYPE_GRAVITY));
+ } else {
+ listener.onSensorChanged(makeSensorEvent(
+ new float[]{0.0f}, Sensor.TYPE_PROXIMITY));
+ }
+ return true;
+ }).when(mSensorManager)
+ .registerListener(any(SensorEventListener.class), any(Sensor.class), anyInt());
+
+ assertFalse(SystemStateHelper.isDeviceAtEar(mContext));
+ }
+
+ @SmallTest
+ @Test
+ public void testDeviceIsNotOnEarWithTimeout() {
+ doAnswer(invocation -> {
+ SensorEventListener listener = invocation.getArgument(0);
+ Sensor sensor = invocation.getArgument(1);
+ if (sensor.getType() == Sensor.TYPE_GRAVITY) {
+ // do nothing
+ } else {
+ listener.onSensorChanged(makeSensorEvent(
+ new float[]{0.0f}, Sensor.TYPE_PROXIMITY));
+ }
+ return true;
+ }).when(mSensorManager)
+ .registerListener(any(SensorEventListener.class), any(Sensor.class), anyInt());
+
+ assertFalse(SystemStateHelper.isDeviceAtEar(mContext));
+ }
+
+ @SmallTest
+ @Test
+ public void testDeviceIsOnEarWithMultiSensorInputs() {
+ doAnswer(invocation -> {
+ SensorEventListener listener = invocation.getArgument(0);
+ Sensor sensor = invocation.getArgument(1);
+ if (sensor.getType() == Sensor.TYPE_GRAVITY) {
+ listener.onSensorChanged(makeSensorEvent(
+ new float[]{1.0f, 9.0f, 1.0f}, Sensor.TYPE_GRAVITY));
+ listener.onSensorChanged(makeSensorEvent(
+ new float[]{1.0f, -9.0f, 1.0f}, Sensor.TYPE_GRAVITY));
+ listener.onSensorChanged(makeSensorEvent(
+ new float[]{1.0f, 0.0f, 8.0f}, Sensor.TYPE_GRAVITY));
+ } else {
+ listener.onSensorChanged(makeSensorEvent(
+ new float[]{0.0f}, Sensor.TYPE_PROXIMITY));
+ }
+ return true;
+ }).when(mSensorManager)
+ .registerListener(any(SensorEventListener.class), any(Sensor.class), anyInt());
+
+ assertTrue(SystemStateHelper.isDeviceAtEar(mContext));
+ verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
+ }
+
+ private SensorEvent makeSensorEvent(float[] values, int sensorType) throws Exception {
+ SensorEvent event = mock(SensorEvent.class);
+ Sensor mockSensor = mock(Sensor.class);
+ when(mockSensor.getType()).thenReturn(sensorType);
+ FieldSetter.setField(event, SensorEvent.class.getDeclaredField("sensor"), mockSensor);
+ FieldSetter.setField(event, SensorEvent.class.getDeclaredField("values"), values);
+ return event;
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/SystemStateProviderTest.java b/tests/src/com/android/server/telecom/tests/SystemStateProviderTest.java
deleted file mode 100644
index 033f929..0000000
--- a/tests/src/com/android/server/telecom/tests/SystemStateProviderTest.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2015 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.Matchers.any;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.UiModeManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.server.telecom.SystemStateProvider;
-import com.android.server.telecom.SystemStateProvider.SystemStateListener;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Unit tests for SystemStateProvider
- */
-@RunWith(JUnit4.class)
-public class SystemStateProviderTest extends TelecomTestCase {
-
- @Mock Context mContext;
- @Mock SystemStateListener mSystemStateListener;
- @Mock UiModeManager mUiModeManager;
- @Mock Intent mIntentEnter;
- @Mock Intent mIntentExit;
-
- @Override
- @Before
- public void setUp() throws Exception {
- super.setUp();
- MockitoAnnotations.initMocks(this);
- }
-
- @Override
- @After
- public void tearDown() throws Exception {
- super.tearDown();
- }
-
- @SmallTest
- @Test
- public void testListeners() throws Exception {
- SystemStateProvider systemStateProvider = new SystemStateProvider(mContext);
-
- assertFalse(systemStateProvider.removeListener(mSystemStateListener));
- systemStateProvider.addListener(mSystemStateListener);
- assertTrue(systemStateProvider.removeListener(mSystemStateListener));
- assertFalse(systemStateProvider.removeListener(mSystemStateListener));
- }
-
- @SmallTest
- @Test
- public void testQuerySystemForCarMode_True() {
- when(mContext.getSystemService(Context.UI_MODE_SERVICE)).thenReturn(mUiModeManager);
- when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
- assertTrue(new SystemStateProvider(mContext).isCarMode());
- }
-
- @SmallTest
- @Test
- public void testQuerySystemForCarMode_False() {
- when(mContext.getSystemService(Context.UI_MODE_SERVICE)).thenReturn(mUiModeManager);
- when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_NORMAL);
- assertFalse(new SystemStateProvider(mContext).isCarMode());
- }
-
- @SmallTest
- @Test
- public void testReceiverAndIntentFilter() {
- ArgumentCaptor<IntentFilter> intentFilter = ArgumentCaptor.forClass(IntentFilter.class);
- new SystemStateProvider(mContext);
- 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));
- }
-
- @SmallTest
- @Test
- public void testOnEnterExitCarMode() {
- ArgumentCaptor<BroadcastReceiver> receiver =
- ArgumentCaptor.forClass(BroadcastReceiver.class);
- new SystemStateProvider(mContext).addListener(mSystemStateListener);
-
- verify(mContext).registerReceiver(receiver.capture(), any(IntentFilter.class));
-
- when(mIntentEnter.getAction()).thenReturn(UiModeManager.ACTION_ENTER_CAR_MODE);
- receiver.getValue().onReceive(mContext, mIntentEnter);
- verify(mSystemStateListener).onCarModeChanged(true);
-
- when(mIntentExit.getAction()).thenReturn(UiModeManager.ACTION_EXIT_CAR_MODE);
- receiver.getValue().onReceive(mContext, mIntentExit);
- verify(mSystemStateListener).onCarModeChanged(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 cbca5e1..a459aac 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -25,6 +25,7 @@
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -87,6 +88,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -98,7 +100,7 @@
public static class CallIntentProcessAdapterFake implements CallIntentProcessor.Adapter {
@Override
public void processOutgoingCallIntent(Context context, CallsManager callsManager,
- Intent intent) {
+ Intent intent, String callingPackage) {
}
@@ -121,6 +123,20 @@
}
}
+ 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 THIRD_PARTY_CALL_SCREENING.flattenToString();
+ }
+ }
+
private static class AnyStringIn implements ArgumentMatcher<String> {
private Collection<String> mStrings;
public AnyStringIn(Collection<String> strings) {
@@ -145,6 +161,8 @@
@Mock private DefaultDialerCache mDefaultDialerCache;
private TelecomServiceImpl.SubscriptionManagerAdapter mSubscriptionManagerAdapter =
spy(new SubscriptionManagerAdapterFake());
+ private TelecomServiceImpl.SettingsSecureAdapter mSettingsSecureAdapter =
+ spy(new SettingsSecureAdapterFake());
@Mock private UserCallIntentProcessor mUserCallIntentProcessor;
private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
@@ -160,6 +178,8 @@
new ComponentName("test", "telComponentName"), "2", Binder.getCallingUserHandle());
private static final PhoneAccountHandle SIP_PA_HANDLE_CURRENT = new PhoneAccountHandle(
new ComponentName("test", "sipComponentName"), "3", Binder.getCallingUserHandle());
+ private static final ComponentName THIRD_PARTY_CALL_SCREENING = new ComponentName("com.android" +
+ ".thirdparty", "com.android.thirdparty.callscreeningserviceimpl");
@Override
@Before
@@ -185,6 +205,7 @@
},
mDefaultDialerCache,
mSubscriptionManagerAdapter,
+ mSettingsSecureAdapter,
mLock);
mTSIBinder = telecomServiceImpl.getBinder();
mComponentContextFixture.setTelecomManager(mTelecomManager);
@@ -743,6 +764,80 @@
@SmallTest
@Test
+ public void testRequestChangeDefaultCallScreeningAppCallingPackageMatchComponentName()
+ throws Exception {
+ String callingPackage = "com.android.thirdparty";
+
+ doNothing().when(mContext).startActivity(any(Intent.class));
+
+ mTSIBinder.requestChangeDefaultCallScreeningApp(THIRD_PARTY_CALL_SCREENING,
+ callingPackage);
+
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext, times(1)).startActivity(intentCaptor.capture());
+
+ Intent capturedIntent = intentCaptor.getValue();
+ String className = capturedIntent.getComponent().getClassName();
+ assertEquals(className,
+ "com.android.server.telecom.components.ChangeDefaultCallScreeningApp");
+
+ String packageNameExtra = capturedIntent.getStringExtra(
+ TelecomManager.EXTRA_DEFAULT_CALL_SCREENING_APP_COMPONENT_NAME);
+ assertEquals(packageNameExtra, THIRD_PARTY_CALL_SCREENING.flattenToString());
+ }
+
+ @SmallTest
+ @Test
+ public void testRequestChangeDefaultCallScreeningAppCallingPackageNoMatchComponentName()
+ throws Exception {
+ boolean exceptionThrown = false;
+ String callingPackage = "com.android.unknown";
+
+ try {
+ mTSIBinder
+ .requestChangeDefaultCallScreeningApp(THIRD_PARTY_CALL_SCREENING, callingPackage);
+ } catch (SecurityException e) {
+ exceptionThrown = true;
+ }
+
+ assertTrue(exceptionThrown);
+ }
+
+ @SmallTest
+ @Test
+ public void testIsDefaultCallScreeningApp() throws Exception {
+ doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+ assertTrue(mTSIBinder.isDefaultCallScreeningApp(THIRD_PARTY_CALL_SCREENING));
+ }
+
+ @SmallTest
+ @Test
+ public void testIsDefaultCallScreeningAppFailure() throws Exception {
+ ComponentName unknownComponentName = new ComponentName("com.android.unknown",
+ "com.android.unknown.callscreeningserviceimpl");
+ assertFalse(mTSIBinder.isDefaultCallScreeningApp(unknownComponentName));
+ }
+
+ @SmallTest
+ @Test
+ public void testSetDefaultCallScreeningAppSpecifiedComponentNameNoExist() throws Exception {
+ boolean exceptionThrown = false;
+
+ when(mContext.getPackageManager()
+ .getApplicationInfo(THIRD_PARTY_CALL_SCREENING.getPackageName(), 0))
+ .thenThrow(new IllegalArgumentException());
+
+ try {
+ mTSIBinder.setDefaultCallScreeningApp(THIRD_PARTY_CALL_SCREENING);
+ } catch (IllegalArgumentException e) {
+ exceptionThrown = true;
+ }
+
+ assertTrue(exceptionThrown);
+ }
+
+ @SmallTest
+ @Test
public void testSetDefaultDialerNoModifyPhoneStatePermission() throws Exception {
doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
eq(MODIFY_PHONE_STATE), nullable(String.class));
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 4cf7644..c60a428 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -69,6 +69,7 @@
import com.android.server.telecom.AsyncRingtonePlayer;
import com.android.server.telecom.BluetoothPhoneServiceImpl;
import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.CallerInfoLookupHelper;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.CallsManagerListenerBase;
@@ -85,8 +86,11 @@
import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
import com.android.server.telecom.ProximitySensorManager;
import com.android.server.telecom.ProximitySensorManagerFactory;
+import com.android.server.telecom.StatusBarNotifier;
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.components.UserCallIntentProcessor;
import com.android.server.telecom.ui.IncomingCallNotifier;
import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
@@ -98,6 +102,7 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -383,8 +388,8 @@
IInCallAdapter inCallAdapter = mInCallServiceFixtureX.getInCallAdapter();
inCallAdapter.conference(callId1.mCallId, callId2.mCallId);
- // Wait for wacky non-deterministic behavior
- Thread.sleep(200);
+ // Wait for the handler in ConnectionService
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
ParcelableCall call1 = mInCallServiceFixtureX.getCall(callId1.mCallId);
ParcelableCall call2 = mInCallServiceFixtureX.getCall(callId2.mCallId);
// Check that the two calls end up with a parent in the end
@@ -402,6 +407,15 @@
}
private void setupTelecomSystem() throws Exception {
+ // Remove any cached PhoneAccount xml
+ File phoneAccountFile =
+ new File(mComponentContextFixture.getTestDouble()
+ .getApplicationContext().getFilesDir(),
+ PhoneAccountRegistrar.FILE_NAME);
+ if (phoneAccountFile.exists()) {
+ phoneAccountFile.delete();
+ }
+
// Use actual implementations instead of mocking the interface out.
HeadsetMediaButtonFactory headsetMediaButtonFactory =
spy(new HeadsetMediaButtonFactoryF());
@@ -422,45 +436,39 @@
when(mClockProxy.elapsedRealtime()).thenReturn(TEST_CREATE_ELAPSED_TIME);
mTelecomSystem = new TelecomSystem(
mComponentContextFixture.getTestDouble(),
- new MissedCallNotifierImplFactory() {
- @Override
- public MissedCallNotifier makeMissedCallNotifierImpl(Context context,
- PhoneAccountRegistrar phoneAccountRegistrar,
- DefaultDialerCache defaultDialerCache) {
- return mMissedCallNotifier;
- }
- },
+ (context, phoneAccountRegistrar, defaultDialerCache) -> mMissedCallNotifier,
mCallerInfoAsyncQueryFactoryFixture.getTestDouble(),
headsetMediaButtonFactory,
proximitySensorManagerFactory,
inCallWakeLockControllerFactory,
- new CallAudioManager.AudioServiceFactory() {
- @Override
- public IAudioService getAudioService() {
- return mAudioService;
- }
- },
- new BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory() {
- @Override
- public BluetoothPhoneServiceImpl makeBluetoothPhoneServiceImpl(Context context,
- TelecomSystem.SyncRoot lock, CallsManager callsManager,
- PhoneAccountRegistrar phoneAccountRegistrar) {
- return mBluetoothPhoneServiceImpl;
- }
- },
- new ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory() {
- @Override
- public ConnectionServiceFocusManager create(
- ConnectionServiceFocusManager.CallsManagerRequester requester,
- Looper looper) {
- return new ConnectionServiceFocusManager(requester, looper);
- }
- },
+ () -> mAudioService,
+ (context, lock, callsManager, phoneAccountRegistrar) -> mBluetoothPhoneServiceImpl,
+ ConnectionServiceFocusManager::new,
mTimeoutsAdapter,
mAsyncRingtonePlayer,
mPhoneNumberUtilsAdapter,
mIncomingCallNotifier,
(streamType, volume) -> mock(ToneGenerator.class),
+ new CallAudioRouteStateMachine.Factory() {
+ @Override
+ public CallAudioRouteStateMachine create(
+ Context context,
+ CallsManager callsManager,
+ BluetoothRouteManager bluetoothManager,
+ WiredHeadsetManager wiredHeadsetManager,
+ StatusBarNotifier statusBarNotifier,
+ CallAudioManager.AudioServiceFactory audioServiceFactory,
+ int earpieceControl) {
+ return new CallAudioRouteStateMachine(context,
+ callsManager,
+ bluetoothManager,
+ wiredHeadsetManager,
+ statusBarNotifier,
+ audioServiceFactory,
+ // Force enable an earpiece for the end-to-end tests
+ CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+ }
+ },
mClockProxy);
mComponentContextFixture.setTelecomManager(new TelecomManager(
@@ -553,8 +561,11 @@
ConnectionServiceFixture connectionServiceFixture)
throws Exception {
- return startOutgoingPhoneCallPendingCreateConnection(number, null,
- connectionServiceFixture, Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY);
+ startOutgoingPhoneCallWaitForBroadcaster(number, null,
+ connectionServiceFixture, Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY,
+ false /*isEmergency*/);
+
+ return mInCallServiceFixtureX.mLatestCallId;
}
protected IdPair outgoingCallPhoneAccountSelected(PhoneAccountHandle phoneAccountHandle,
@@ -674,23 +685,22 @@
Context localAppContext = mComponentContextFixture.getTestDouble().getApplicationContext();
new UserCallIntentProcessor(localAppContext, userHandle).processIntent(
actionCallIntent, null, true /* hasCallAppOp*/, false /* isLocal */);
- // UserCallIntentProcessor's mContext.sendBroadcastAsUser(...) will call to an empty method
- // as to not actually try to send an intent to PrimaryCallReceiver. We verify that it was
- // called correctly in order to continue.
- verify(localAppContext).sendBroadcastAsUser(actionCallIntent, UserHandle.SYSTEM);
- mTelecomSystem.getCallIntentProcessor().processIntent(actionCallIntent);
// Wait for handler to start CallerInfo lookup.
waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
// Send the CallerInfo lookup reply.
mCallerInfoAsyncQueryFactoryFixture.mRequests.forEach(
CallerInfoAsyncQueryFactoryFixture.Request::reply);
+ if (phoneAccountHandle != null) {
+ mTelecomSystem.getCallsManager().getLatestPostSelectionProcessingFuture().join();
+ }
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
boolean isSelfManaged = phoneAccountHandle == mPhoneAccountSelfManaged.getAccountHandle();
if (!hasInCallAdapter && !isSelfManaged) {
- verify(mInCallServiceFixtureX.getTestDouble())
+ verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
.setInCallAdapter(
any(IInCallAdapter.class));
- verify(mInCallServiceFixtureY.getTestDouble())
+ verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT))
.setInCallAdapter(
any(IInCallAdapter.class));
}
@@ -702,7 +712,13 @@
int videoState) throws Exception {
startOutgoingPhoneCallWaitForBroadcaster(number,phoneAccountHandle,
connectionServiceFixture, initiatingUser, videoState, false /*isEmergency*/);
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ verifyAndProcessOutgoingCallBroadcast(phoneAccountHandle);
+ return mInCallServiceFixtureX.mLatestCallId;
+ }
+
+ protected void verifyAndProcessOutgoingCallBroadcast(PhoneAccountHandle phoneAccountHandle) {
ArgumentCaptor<Intent> newOutgoingCallIntent =
ArgumentCaptor.forClass(Intent.class);
ArgumentCaptor<BroadcastReceiver> newOutgoingCallReceiver =
@@ -731,7 +747,6 @@
newOutgoingCallIntent.getValue());
}
- return mInCallServiceFixtureX.mLatestCallId;
}
// When Telecom is redialing due to an error, we need to make sure the number of connections
@@ -763,14 +778,16 @@
ConnectionServiceFixture connectionServiceFixture) throws Exception {
// Wait for the focus tracker.
- waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ waitForHandlerAction(mTelecomSystem.getCallsManager()
+ .getConnectionServiceFocusManager().getHandler(), TEST_TIMEOUT);
verify(connectionServiceFixture.getTestDouble())
.createConnection(eq(phoneAccountHandle), anyString(), any(ConnectionRequest.class),
eq(false)/*isIncoming*/, anyBoolean(), any());
// Wait for handleCreateConnectionComplete
waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
- assertEquals(startingNumConnections + 1, connectionServiceFixture.mConnectionById.size());
+ assertEquals(startingNumConnections + 1,
+ connectionServiceFixture.mConnectionById.size());
// Wait for the callback in ConnectionService#onAdapterAttached to execute.
waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
@@ -847,14 +864,6 @@
//Wait for/Verify call blocking happened asynchronously
incomingCallAddedLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
- IContentProvider blockedNumberProvider =
- mSpyContext.getContentResolver().acquireProvider(BlockedNumberContract.AUTHORITY);
- verify(blockedNumberProvider, timeout(TEST_TIMEOUT)).call(
- anyString(),
- eq(BlockedNumberContract.SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER),
- eq(number),
- isNotNull(Bundle.class));
-
// For the case of incoming calls, Telecom connecting the InCall services and adding the
// Call is triggered by the async completion of the CallerInfoAsyncQuery. Once the Call
// is added, future interactions as triggered by the ConnectionService, through the various
@@ -972,6 +981,9 @@
mInCallServiceFixtureX.mInCallAdapter
.answerCall(ids.mCallId, videoState);
+ // Wait on the CS focus manager handler
+ waitForHandlerAction(mTelecomSystem.getCallsManager()
+ .getConnectionServiceFocusManager().getHandler(), TEST_TIMEOUT);
if (!VideoProfile.isVideo(videoState)) {
verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
diff --git a/tests/src/com/android/server/telecom/tests/VideoProviderProxyTest.java b/tests/src/com/android/server/telecom/tests/VideoProviderProxyTest.java
new file mode 100644
index 0000000..a6eecf7
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/VideoProviderProxyTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.IBinder;
+import android.telecom.VideoProfile;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telecom.IVideoProvider;
+import com.android.server.telecom.Analytics;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CurrentUserProxy;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.VideoProviderProxy;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class VideoProviderProxyTest extends TelecomTestCase {
+
+ private TelecomSystem.SyncRoot mLock;
+ private VideoProviderProxy mVideoProviderProxy;
+ @Mock private IVideoProvider mVideoProvider;
+ @Mock private IBinder mIBinder;
+ @Mock private Call mCall;
+ @Mock private Analytics.CallInfo mCallInfo;
+ @Mock private CurrentUserProxy mCurrentUserProxy;
+ @Mock private VideoProviderProxy.Listener mListener;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ mLock = new TelecomSystem.SyncRoot() { };
+
+ when(mVideoProvider.asBinder()).thenReturn(mIBinder);
+ doNothing().when(mIBinder).linkToDeath(any(), anyInt());
+ when(mCall.getAnalytics()).thenReturn(mCallInfo);
+ doNothing().when(mCallInfo).addVideoEvent(anyInt(), anyInt());
+ mVideoProviderProxy = new VideoProviderProxy(mLock, mVideoProvider, mCall,
+ mCurrentUserProxy);
+ mVideoProviderProxy.addListener(mListener);
+ }
+
+ /**
+ * Tests the case where we receive a request to upgrade to video, except:
+ * 1. Phone account says we support video.
+ * 2. Call says we don't support video.
+ *
+ * Ensures that we send back a response immediately to indicate the call should remain as
+ * audio-only.
+ * @throws Exception
+ */
+ @SmallTest
+ @Test
+ public void testReceiveUpgradeRequestWhenLocalDoesntSupportVideo() throws Exception {
+ // Given a call which supports video at the phone account level, but is not currently
+ // marked as supporting video locally.
+ when(mCall.isLocallyVideoCapable()).thenReturn(false);
+ when(mCall.isVideoCallingSupportedByPhoneAccount()).thenReturn(true);
+
+ // Simulate receiving a request to upgrade to video.
+ mVideoProviderProxy.getVideoCallListenerBinder().receiveSessionModifyRequest(
+ new VideoProfile(VideoProfile.STATE_BIDIRECTIONAL));
+
+ // Make sure that we send back a response rejecting the request.
+ ArgumentCaptor<VideoProfile> capturedProfile = ArgumentCaptor.forClass(VideoProfile.class);
+ verify(mVideoProvider).sendSessionModifyResponse(capturedProfile.capture());
+ assertEquals(VideoProfile.STATE_AUDIO_ONLY, capturedProfile.getValue().getVideoState());
+ }
+
+ /**
+ * Tests the case where we receive a request to upgrade to video and video is supported.
+ * @throws Exception
+ */
+ @SmallTest
+ @Test
+ public void testReceiveUpgradeRequestWhenVideoIsSupported() throws Exception {
+ // Given a call which supports video at the phone account level, and is currently marked as
+ // supporting video locally.
+ when(mCall.isLocallyVideoCapable()).thenReturn(true);
+ when(mCall.isVideoCallingSupportedByPhoneAccount()).thenReturn(true);
+
+ // Simulate receiving a request to upgrade to video.
+ mVideoProviderProxy.getVideoCallListenerBinder().receiveSessionModifyRequest(
+ new VideoProfile(VideoProfile.STATE_BIDIRECTIONAL));
+
+ // Ensure it gets proxied back to the caller.
+
+ ArgumentCaptor<VideoProfile> capturedProfile = ArgumentCaptor.forClass(VideoProfile.class);
+ verify(mListener).onSessionModifyRequestReceived(any(), capturedProfile.capture());
+ assertEquals(VideoProfile.STATE_BIDIRECTIONAL, capturedProfile.getValue().getVideoState());
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
index 75dc36f..eacecf9 100644
--- a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
+++ b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
@@ -34,6 +34,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
+import android.telecom.Connection;
import android.telecom.Connection.VideoProvider;
import android.telecom.InCallService;
import android.telecom.InCallService.VideoCall;
@@ -96,7 +97,8 @@
super.setUp();
mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
-
+ mConnectionServiceFixtureA.mConnectionServiceDelegate.mCapabilities
+ |= Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL;
mCallIds = startAndMakeActiveOutgoingCall(
"650-555-1212",
mPhoneAccountA0.getAccountHandle(),